diff --git a/frontend/src/components/simulator/PartInspectorDialog.css b/frontend/src/components/simulator/PartInspectorDialog.css index 19fe2827..b8d8cd0c 100644 --- a/frontend/src/components/simulator/PartInspectorDialog.css +++ b/frontend/src/components/simulator/PartInspectorDialog.css @@ -9,7 +9,9 @@ background-color: #2d2d2d; border: 1px solid #555; border-radius: 10px; - width: 560px; + /* Wide enough that a pinout table in a datasheet fits without sideways + scrolling, and clamped so the dialog can never run off the right edge. */ + width: 720px; max-width: calc(100vw - 24px); max-height: calc(100vh - 24px); display: flex; @@ -298,13 +300,24 @@ /* ── Actions ─────────────────────────────────────────────────────────── */ +/* One action bar across the bottom of the dialog, OUTSIDE the tabs, so the + same buttons are there whichever tab is open. Rotate/Delete (and the board + actions) sit left; Buy sits right. */ .pid-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; - justify-content: center; - margin-top: auto; + padding: 10px 12px; + border-top: 1px solid #444; + flex: 0 0 auto; +} + +.pid-actions-right { + margin-left: auto; + display: flex; + align-items: center; + gap: 8px; } /* Solid fills, per the brand kit: interactive surfaces are the accent blue @@ -343,8 +356,36 @@ .pid-confirm-label { font-size: 12px; color: #ffb4ab; - flex-basis: 100%; - text-align: center; + display: flex; + align-items: center; + gap: 6px; +} + +.pid-confirm-hint { + color: #b58b86; + font-style: normal; + font-size: 11px; +} + +/* Buy in the action bar: same solid treatment as the other buttons, in the + brand's success green so it reads as the outward link, not an app action. */ +.pid-buy-button { + display: inline-flex; + align-items: center; + gap: 6px; + background: #2c2c2e; + border: 1px solid #4a4a4c; + border-radius: 6px; + color: #f5f5f7; + padding: 6px 14px; + font-size: 12px; + font-weight: 500; + text-decoration: none; + cursor: pointer; +} + +.pid-buy-button:hover { + background: #3a3a3c; } /* ── Tabs ────────────────────────────────────────────────────────────── */ @@ -382,9 +423,37 @@ max-height: 46vh; } -/* The datasheet reuses the picker card's .cip-doc styles as-is. */ +/* The datasheet reuses the picker card's .cip-doc styles, with one override: + NO sideways scrolling. The doc tables have a nowrap first column (a pin + name), which pushed wide pinouts past the edge; here they wrap instead and + long tokens break rather than widening the pane. */ +.pid-tab-body--doc { + overflow-x: hidden; +} + .pid-tab-body--doc .cip-doc { max-height: none; + overflow-wrap: anywhere; +} + +.pid-tab-body--doc .cip-doc table { + table-layout: fixed; + width: 100%; +} + +.pid-tab-body--doc .cip-doc td:first-child { + white-space: normal; +} + +.pid-tab-body--doc .cip-doc pre { + white-space: pre-wrap; + word-break: break-word; +} + +/* The body's own Buy footer is suppressed here — the dialog puts Buy in the + action bar, where it stays visible on both tabs. */ +.pid-tab-body--doc .cip-footer { + display: none; } /* ── Property rows ───────────────────────────────────────────────────── */ diff --git a/frontend/src/components/simulator/PartInspectorDialog.tsx b/frontend/src/components/simulator/PartInspectorDialog.tsx index 0c8f1200..f30113dd 100644 --- a/frontend/src/components/simulator/PartInspectorDialog.tsx +++ b/frontend/src/components/simulator/PartInspectorDialog.tsx @@ -4,8 +4,9 @@ * Merges the two surfaces that used to exist separately: the old * ComponentPropertyDialog (a 250px column of pin names, editors and * Rotate/Delete) and the component-picker info card (datasheet, brand, Buy). - * Layout: actions and a live preview of the part on the left; Properties / - * Datasheet tabs on the right. + * Layout: a live preview of the part on the left, Properties / Datasheet + * tabs on the right, and one action bar across the bottom (Rotate/Delete on + * the left, Buy on the right) that stays put whichever tab is open. * * The preview is the REAL web component, mounted and scaled — not a drawing. * Pin markers are laid over it at their true positions and their labels flank @@ -37,6 +38,7 @@ import { useComponentDoc, HIDDEN_PROPS, formatValue, + safeHref, type PanelData, } from '../ComponentInfoPanel'; import { @@ -356,6 +358,10 @@ export const PartInspectorDialog: React.FC = ({ ? componentMetadata.thumbnail : null; + // Same http(s)-only guard the picker card applies — docs are a surface the + // pro overlay can write to, so the validation is load-bearing. + const buyHref = safeHref(doc?.buy); + const editableProps = componentMetadata.properties.filter((p: any) => isEditable(p)); const readOnlyProps = componentMetadata.properties.filter( (p: any) => !isEditable(p) && !HIDDEN_PROPS.has(p.name), @@ -489,65 +495,6 @@ export const PartInspectorDialog: React.FC = ({ )} -
- {confirmingDelete ? ( - <> - - {t('editor.componentProps.confirmDelete', { name: componentMetadata.name })} - {deleteHint && {deleteHint}} - - - - - ) : ( - <> - {/* Boards pass no onRotate (they do not rotate); they pass - Board Options / Flash as extraActions instead. */} - {onRotate && ( - - )} - {extraActions?.map((a) => ( - - ))} - - - )} -
{/* Right column: tabs. */} @@ -684,6 +631,98 @@ export const PartInspectorDialog: React.FC = ({ )} + +
+ {confirmingDelete ? ( + <> + + {t('editor.componentProps.confirmDelete', { name: componentMetadata.name })} + {deleteHint && {deleteHint}} + + + + + ) : ( + <> + {/* Boards pass no onRotate (they do not rotate); they pass + Board Options / Flash as extraActions instead. */} + {onRotate && ( + + )} + {extraActions?.map((a) => ( + + ))} + + + )} + + {/* Buy lives here, right-aligned, so it is reachable from BOTH + tabs — it used to sit at the end of the datasheet body, which + meant scrolling to the bottom of the right tab to find it. */} + {buyHref && ( +
+ + + Buy + +
+ )} +
, document.body, );