You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
11 KiB
300 lines
11 KiB
/*
|
|
* Command palette recipe — ⌘K command bar.
|
|
*
|
|
* [data-command] → root container (input + list)
|
|
* [data-command-input] → search input
|
|
* [data-command-list] → scrolling listbox
|
|
* [data-command-viewport] → optional inner scroller (if List wraps it)
|
|
* [data-command-empty] → "no results" message
|
|
* [data-command-loading] → progress bar slot
|
|
* [data-command-group] → group container
|
|
* [data-command-group-heading]→ group label
|
|
* [data-command-group-items] → group items wrapper
|
|
* [data-command-item] → row (highlighted via data-selected)
|
|
* [data-command-link-item] → anchor-style row
|
|
* [data-command-separator] → 1px divider
|
|
* [data-command-item-icon] → leading glyph slot (eidos sub-part)
|
|
* [data-command-item-shortcut]→ trailing kbd hint slot (eidos sub-part)
|
|
*
|
|
* Sizes — sm | md | lg via `data-size` on [data-command].
|
|
* Inside `<Command.Dialog>` the panel chrome comes from dialog.css; this
|
|
* recipe only paints the inner palette.
|
|
*/
|
|
|
|
[data-command] {
|
|
--_command-input-px: var(--space-3);
|
|
--_command-input-font-size: var(--font-size-md);
|
|
--_command-item-px: var(--space-3);
|
|
--_command-item-font-size: var(--font-size-md);
|
|
/* Radius of the surface the palette lives in — the concentric reference for
|
|
the input (which sits at the surface's top corners). Inline that surface is
|
|
the command's own panel (radius-lg, matching the combobox popup). Inside
|
|
<Command.Dialog> it's overridden to the dialog panel's radius so the input
|
|
corner stays parallel to the ACTUAL outer corner. */
|
|
--_command-radius: var(--radius-lg);
|
|
/* Concentric items: expose the radius + inline inset to the shared
|
|
`[data-shape-nest]` rule. Outer = the command's list radius (`lg`, FIXED) so
|
|
items stay concentric with the list region in BOTH the inline palette and
|
|
inside <Command.Dialog>. The items aren't at the dialog's corner (the input +
|
|
list padding sit above them), so they must NOT inflate to the dialog radius;
|
|
only the input adapts to it via --_command-radius. gap = the list inline pad. */
|
|
--shape-outer-radius: var(--radius-lg);
|
|
--shape-nest-gap: var(--space-1);
|
|
|
|
display: flex;
|
|
flex-direction: column;
|
|
inline-size: 100%;
|
|
min-inline-size: 0;
|
|
/* Typography is anchored on the root so EVERY descendant (item, group
|
|
heading, icon, shortcut) inherits the UI font + leading regardless of
|
|
context. Inside <Command.Dialog> the palette is portaled, which severs the
|
|
ambient page-font inheritance — without this the rows fall back to the
|
|
browser serif (Times New Roman). Self-contained typography keeps the
|
|
portaled dialog identical to the inline palette. */
|
|
font-family: var(--font-ui);
|
|
line-height: var(--leading-ui);
|
|
color: var(--color-content-primary);
|
|
/* Elevation — surface · border · shadow — from the `overlay` plane via
|
|
`data-depth` (A1). Inside <Command.Dialog> the dialog panel is the surface,
|
|
so the `[data-dialog-content] [data-command]` override below (higher
|
|
specificity) strips the plane. Radius / overflow stay the panel's own. */
|
|
border-radius: var(--_command-radius);
|
|
overflow: hidden;
|
|
outline: none;
|
|
}
|
|
|
|
/* Inside a Dialog (`<Command.Dialog>`) the dialog panel IS the surface — strip
|
|
ONLY the command's own panel chrome (border / bg / shadow) so the two panels
|
|
don't nest. Everything else (the boxed input, the list padding, the type
|
|
scale, the item rows) is inherited unchanged, so the palette is identical to
|
|
the inline <Command>, just hosted inside the dialog panel. */
|
|
[data-dialog-content] [data-command] {
|
|
/* The visible surface is now the dialog panel — adopt ITS radius so the input
|
|
stays concentric with the actual outer corner the user sees. */
|
|
--_command-radius: var(--dialog-content-radius);
|
|
border: 0;
|
|
border-radius: 0;
|
|
background: transparent;
|
|
box-shadow: none;
|
|
}
|
|
|
|
/* The command brings its own internal spacing (input margin + list padding), so
|
|
the dialog content adds none of its own. */
|
|
[data-dialog-content]:has([data-command]) {
|
|
padding: 0;
|
|
}
|
|
|
|
/* Clear the floating top-right close: the input BOX (not just its text) stops
|
|
before the close — margin, not padding — so the two never overlap, with a
|
|
comfortable gap. `--control-height-md` is the close's REAL footprint (an md
|
|
IconButton); the old `--dialog-close-size` token under-measured it → 2px gap. */
|
|
[data-dialog-content] [data-command-input] {
|
|
margin-inline-end: calc(var(--dialog-close-inset) + var(--control-height-md) + var(--space-1));
|
|
}
|
|
|
|
/* Vertically center the floating close on the boxed input row — the dialog's
|
|
default top inset (for a title row) sits it too low for the command input. */
|
|
[data-dialog-content]:has([data-command]) [data-dialog-close][data-position='top-right'] {
|
|
top: var(--space-2);
|
|
}
|
|
|
|
[data-command][data-size='sm'] {
|
|
--_command-input-px: var(--space-2);
|
|
--_command-input-font-size: var(--font-size-sm);
|
|
--_command-item-px: var(--space-2);
|
|
--_command-item-font-size: var(--font-size-sm);
|
|
}
|
|
|
|
[data-command][data-size='lg'] {
|
|
--_command-input-px: var(--space-4);
|
|
--_command-input-font-size: var(--font-size-lg);
|
|
--_command-item-px: var(--space-4);
|
|
--_command-item-font-size: var(--font-size-lg);
|
|
}
|
|
|
|
/* ── Input ──────────────────────────────────────────────────────────── */
|
|
|
|
/* Boxed search field — same field treatment as the Select trigger (border +
|
|
radius + surface bg + focus ring), inset from the panel edges, instead of the
|
|
old flush borderless input with a bottom-rule separator. */
|
|
[data-command-input] {
|
|
box-sizing: border-box;
|
|
/* Same density-driven block padding as the options (archetype) so the input
|
|
row and the option rows share one vertical rhythm. */
|
|
padding-block: var(--space-1-5);
|
|
margin: var(--space-2);
|
|
padding-inline: var(--_command-input-px);
|
|
border: var(--border-width) solid var(--color-border-default);
|
|
border-radius: var(--radius-md);
|
|
background: var(--color-surface-default);
|
|
color: var(--color-content-primary);
|
|
font-family: var(--font-ui);
|
|
font-size: var(--_command-input-font-size);
|
|
line-height: var(--leading-ui);
|
|
outline: 0;
|
|
transition:
|
|
border-color var(--duration-fast) var(--ease-default),
|
|
box-shadow var(--duration-fast) var(--ease-default);
|
|
}
|
|
|
|
[data-command-input]:focus-visible {
|
|
border-color: var(--color-primary-border);
|
|
box-shadow: var(--focus-ring);
|
|
}
|
|
|
|
[data-command-input]::placeholder {
|
|
color: var(--color-content-muted);
|
|
}
|
|
|
|
/* ── Loading progress (slim bar under Input) ───────────────────────── */
|
|
|
|
[data-command-loading] {
|
|
display: block;
|
|
block-size: 2px;
|
|
background: linear-gradient(
|
|
to right,
|
|
var(--color-primary-solid) 0%,
|
|
var(--color-primary-solid) var(--_command-loading-progress, 0%),
|
|
transparent var(--_command-loading-progress, 0%)
|
|
);
|
|
}
|
|
|
|
/* ── List + Viewport ──────────────────────────────────────────────── */
|
|
|
|
/* The soma list is a transparent marker; the composed <ScrollArea autosize>
|
|
(command-list.svelte) owns the scroll + the design-system scrollbar, shared
|
|
with select / combobox so the scroll chrome (incl. its colour) is common. */
|
|
[data-command-list] {
|
|
display: contents;
|
|
}
|
|
|
|
[data-command-list] [data-scroll-area-viewport] {
|
|
--scroll-area-auto-max-block: min(60vh, 24rem);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-1);
|
|
/* Inline inset matches the input's inline margin (space-2) so the option pills
|
|
and the search box share ONE left/right edge. */
|
|
padding-inline: var(--space-2);
|
|
padding-block-end: var(--space-1);
|
|
overscroll-behavior: contain;
|
|
}
|
|
|
|
/* Reserve the scrollbar footprint + a matching inline gap when it shows. */
|
|
[data-command-list]
|
|
[data-scroll-area]:has(> [data-scroll-area-scrollbar][data-orientation='vertical'])
|
|
> [data-scroll-area-viewport] {
|
|
padding-inline-end: calc(var(--space-2) + var(--space-3-5));
|
|
}
|
|
|
|
[data-command-viewport] {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-1);
|
|
max-block-size: min(60vh, 24rem);
|
|
padding: var(--space-1);
|
|
overflow: auto;
|
|
overscroll-behavior: contain;
|
|
}
|
|
|
|
/* ── Group / GroupHeading / GroupItems ────────────────────────────── */
|
|
|
|
[data-command-group] {
|
|
display: grid;
|
|
gap: var(--space-1);
|
|
}
|
|
|
|
[data-command-group-heading] {
|
|
padding-block: var(--space-1);
|
|
padding-inline: var(--_command-item-px);
|
|
color: var(--color-content-muted);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 500;
|
|
text-transform: uppercase;
|
|
letter-spacing: var(--tracking-caps);
|
|
}
|
|
|
|
[data-command-group-items] {
|
|
display: grid;
|
|
gap: 1px;
|
|
}
|
|
|
|
/* ── Item / LinkItem ──────────────────────────────────────────────── */
|
|
|
|
[data-command-item],
|
|
[data-command-link-item] {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
padding-inline: var(--_command-item-px);
|
|
/* Concentric radius from the shared `[data-shape-nest]` rule — the panel exposes
|
|
--shape-outer-radius (its REAL radius, not radius-md) + --shape-nest-gap above.
|
|
Height + block padding: the canonical archetype. */
|
|
color: var(--color-content-primary);
|
|
font-size: var(--_command-item-font-size);
|
|
line-height: var(--leading-ui);
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
transition:
|
|
background var(--duration-fast) var(--ease-default),
|
|
color var(--duration-fast) var(--ease-default);
|
|
}
|
|
|
|
/* The active-row highlight comes from the canonical archetype layer
|
|
(archetypes.css), shared with select / combobox / listbox / menus. */
|
|
|
|
[data-command-item][data-disabled],
|
|
[data-command-link-item][data-disabled] {
|
|
cursor: default;
|
|
opacity: var(--opacity-disabled);
|
|
}
|
|
|
|
/* ── Item sub-parts (eidos-only) ──────────────────────────────────── */
|
|
|
|
[data-command-item-icon] {
|
|
display: inline-flex;
|
|
flex: 0 0 auto;
|
|
align-items: center;
|
|
justify-content: center;
|
|
inline-size: 1.125em;
|
|
block-size: 1.125em;
|
|
color: var(--color-content-secondary);
|
|
}
|
|
|
|
[data-command-item-shortcut] {
|
|
display: inline-flex;
|
|
flex: 0 0 auto;
|
|
align-items: center;
|
|
gap: var(--space-1);
|
|
margin-inline-start: auto;
|
|
color: var(--color-content-muted);
|
|
font-size: 0.875em;
|
|
}
|
|
|
|
/* When the row is highlighted, lift the muted sub-parts to primary. */
|
|
[data-command-item][data-selected] [data-command-item-icon],
|
|
[data-command-item][data-selected] [data-command-item-shortcut],
|
|
[data-command-link-item][data-selected] [data-command-item-icon],
|
|
[data-command-link-item][data-selected] [data-command-item-shortcut] {
|
|
color: var(--color-content-primary);
|
|
}
|
|
|
|
/* ── Empty state ──────────────────────────────────────────────────── */
|
|
|
|
[data-command-empty] {
|
|
padding-block: var(--space-4);
|
|
padding-inline: var(--_command-item-px);
|
|
color: var(--color-content-muted);
|
|
font-size: var(--_command-item-font-size);
|
|
text-align: center;
|
|
}
|
|
|
|
/* ── Separator ────────────────────────────────────────────────────── */
|
|
|
|
[data-command-separator] {
|
|
block-size: 1px;
|
|
margin-block: var(--space-1);
|
|
background: var(--color-border-subtle);
|
|
}
|