feat(ui): one action bar for both tabs, and no sideways scroll in the datasheet

- Rotate/Delete (and the board actions) moved OUT of the left column into a
  bar across the bottom of the dialog, so they are in the same place on both
  tabs. Buy joins them, right-aligned: it used to be the last thing in the
  datasheet body, which meant scrolling that pane to the end to reach it, and
  it did not exist at all on the Properties tab.
- The datasheet scrolled sideways because the doc tables have a nowrap first
  column (the pin name) and wide pinouts pushed past the pane. The dialog is
  wider (720px, still clamped to the viewport so it cannot run off the right
  edge) and the doc now wraps: fixed table layout, wrapping first column,
  pre-wrap on code blocks, overflow-x hidden.
This commit is contained in:
David Montero Crespo 2026-08-04 05:05:32 +02:00
parent 269503b386
commit c6256cc49a
2 changed files with 175 additions and 67 deletions

View File

@ -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 ───────────────────────────────────────────────────── */

View File

@ -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<PartInspectorDialogProps> = ({
? 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<PartInspectorDialogProps> = ({
</div>
)}
<div className="pid-actions">
{confirmingDelete ? (
<>
<span className="pid-confirm-label">
{t('editor.componentProps.confirmDelete', { name: componentMetadata.name })}
{deleteHint && <em className="pid-confirm-hint">{deleteHint}</em>}
</span>
<button
className="pid-action-button"
onClick={() => setConfirmingDelete(false)}
title={t('editor.componentProps.cancel')}
>
{t('editor.componentProps.cancel')}
</button>
<button
className="pid-action-button pid-action-delete"
onClick={() => {
setConfirmingDelete(false);
onDelete(componentId);
}}
title={t('editor.componentProps.confirmDeleteTitle')}
>
{deleteLabel ?? t('editor.componentProps.delete')}
</button>
</>
) : (
<>
{/* Boards pass no onRotate (they do not rotate); they pass
Board Options / Flash as extraActions instead. */}
{onRotate && (
<button
className="pid-action-button"
onClick={() => onRotate(componentId)}
title={t('editor.componentProps.rotate90')}
>
{t('editor.componentProps.rotate')}
</button>
)}
{extraActions?.map((a) => (
<button
key={a.id}
className="pid-action-button"
onClick={a.disabled ? undefined : a.onSelect}
disabled={a.disabled}
title={a.title}
>
{a.label}
</button>
))}
<button
className="pid-action-button pid-action-delete"
onClick={() => setConfirmingDelete(true)}
title={t('editor.componentProps.deleteTitle')}
>
{deleteLabel ?? t('editor.componentProps.delete')}
</button>
</>
)}
</div>
</div>
{/* Right column: tabs. */}
@ -684,6 +631,98 @@ export const PartInspectorDialog: React.FC<PartInspectorDialogProps> = ({
)}
</div>
</div>
<div className="pid-actions">
{confirmingDelete ? (
<>
<span className="pid-confirm-label">
{t('editor.componentProps.confirmDelete', { name: componentMetadata.name })}
{deleteHint && <em className="pid-confirm-hint">{deleteHint}</em>}
</span>
<button
className="pid-action-button"
onClick={() => setConfirmingDelete(false)}
title={t('editor.componentProps.cancel')}
>
{t('editor.componentProps.cancel')}
</button>
<button
className="pid-action-button pid-action-delete"
onClick={() => {
setConfirmingDelete(false);
onDelete(componentId);
}}
title={t('editor.componentProps.confirmDeleteTitle')}
>
{deleteLabel ?? t('editor.componentProps.delete')}
</button>
</>
) : (
<>
{/* Boards pass no onRotate (they do not rotate); they pass
Board Options / Flash as extraActions instead. */}
{onRotate && (
<button
className="pid-action-button"
onClick={() => onRotate(componentId)}
title={t('editor.componentProps.rotate90')}
>
{t('editor.componentProps.rotate')}
</button>
)}
{extraActions?.map((a) => (
<button
key={a.id}
className="pid-action-button"
onClick={a.disabled ? undefined : a.onSelect}
disabled={a.disabled}
title={a.title}
>
{a.label}
</button>
))}
<button
className="pid-action-button pid-action-delete"
onClick={() => setConfirmingDelete(true)}
title={t('editor.componentProps.deleteTitle')}
>
{deleteLabel ?? t('editor.componentProps.delete')}
</button>
</>
)}
{/* 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 && (
<div className="pid-actions-right">
<a
className="pid-buy-button"
href={buyHref}
target="_blank"
rel="noopener noreferrer"
title={buyHref}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<circle cx="9" cy="21" r="1" />
<circle cx="20" cy="21" r="1" />
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" />
</svg>
Buy
</a>
</div>
)}
</div>
</div>,
document.body,
);