|
|
/*
|
|
|
* Menubar recipe — desktop File / Edit / View bar.
|
|
|
*
|
|
|
* Scope: provider bar surface + per-menu trigger states. Per-menu
|
|
|
* Content / Item / Sub-tree / Checkbox / Radio chrome is reused from
|
|
|
* dropdown-menu.css since soma's Menubar literally re-exports the
|
|
|
* DropdownMenu parts for those slots (parts emit the same
|
|
|
* `data-dropdown-menu-*` attrs and the shared menu-indicator partial
|
|
|
* picks them up automatically).
|
|
|
*
|
|
|
* What's menubar-specific:
|
|
|
*
|
|
|
* [data-menubar] → bar surface
|
|
|
* [data-menubar-trigger] → in-bar trigger ("File", "Edit", …)
|
|
|
* [data-menubar-content] → per-menu panel (same chrome as
|
|
|
* [data-dropdown-menu-content] via
|
|
|
* the dropdown-menu recipe; we only
|
|
|
* add size variants here)
|
|
|
*
|
|
|
* Sizes — sm | md | lg (xs and xl out of band for a desktop chrome bar).
|
|
|
*/
|
|
|
|
|
|
/* ── Provider — the bar itself ──────────────────────────────────────── */
|
|
|
|
|
|
[data-menubar] {
|
|
|
--_menubar-height: var(--control-height-md);
|
|
|
--_menubar-padding: var(--space-1);
|
|
|
--_menubar-gap: var(--space-1);
|
|
|
/* Radius DECOUPLED from size (Radix model) — archetype default, constant across sizes. */
|
|
|
--_menubar-radius: var(--radius-default);
|
|
|
--_menubar-trigger-padding-inline: var(--space-3);
|
|
|
--_menubar-font-size: var(--font-size-md);
|
|
|
/* Concentric nesting (Arq. 6 / shape engine): the in-bar trigger consumes the
|
|
|
shared `[data-shape-nest]` rule instead of hand-rolling its own calc — the bar
|
|
|
exposes its radius + padding as the nest's outer-radius + gap. */
|
|
|
--shape-outer-radius: var(--_menubar-radius);
|
|
|
--shape-nest-gap: var(--_menubar-padding);
|
|
|
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
|
gap: var(--_menubar-gap);
|
|
|
padding: var(--_menubar-padding);
|
|
|
border: var(--border-width) solid var(--color-border-default);
|
|
|
border-radius: var(--_menubar-radius);
|
|
|
background: var(--color-surface-raised);
|
|
|
}
|
|
|
|
|
|
[data-menubar][data-size='sm'] {
|
|
|
--_menubar-height: var(--control-height-sm);
|
|
|
--_menubar-padding: calc(var(--space-1) * 0.5);
|
|
|
--_menubar-gap: calc(var(--space-1) * 0.5);
|
|
|
--_menubar-trigger-padding-inline: var(--space-2);
|
|
|
--_menubar-font-size: var(--font-size-sm);
|
|
|
}
|
|
|
|
|
|
[data-menubar][data-size='lg'] {
|
|
|
--_menubar-height: var(--control-height-lg);
|
|
|
--_menubar-padding: var(--space-2);
|
|
|
--_menubar-gap: var(--space-2);
|
|
|
--_menubar-trigger-padding-inline: var(--space-4);
|
|
|
--_menubar-font-size: var(--font-size-lg);
|
|
|
}
|
|
|
|
|
|
/* ── Trigger — in-bar menu opener ───────────────────────────────────── */
|
|
|
|
|
|
[data-menubar-trigger] {
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
min-block-size: calc(var(--_menubar-height) - var(--_menubar-padding) * 2 - 2px);
|
|
|
padding-inline: var(--_menubar-trigger-padding-inline);
|
|
|
border: none;
|
|
|
/* Radius from the shared `[data-shape-nest]` rule — concentric with the bar
|
|
|
(--shape-outer-radius − --shape-nest-gap, set on [data-menubar] above). */
|
|
|
background: transparent;
|
|
|
color: var(--color-content-primary);
|
|
|
font-family: var(--style-label-font-family);
|
|
|
font-size: var(--_menubar-font-size);
|
|
|
font-weight: var(--font-weight-medium);
|
|
|
line-height: var(--leading-ui);
|
|
|
white-space: nowrap;
|
|
|
cursor: pointer;
|
|
|
outline: none;
|
|
|
transition:
|
|
|
background var(--duration-fast) var(--ease-default),
|
|
|
color var(--duration-fast) var(--ease-default);
|
|
|
}
|
|
|
|
|
|
[data-menubar-trigger]:hover:not([data-disabled]),
|
|
|
[data-menubar-trigger][data-highlighted]:not([data-disabled]) {
|
|
|
background: var(--color-surface-overlay);
|
|
|
}
|
|
|
|
|
|
[data-menubar-trigger][data-state='open'],
|
|
|
[data-menubar-trigger][data-state='open']:hover,
|
|
|
[data-menubar-trigger][data-state='open'][data-highlighted] {
|
|
|
background: var(--color-primary-element);
|
|
|
color: var(--color-content-primary);
|
|
|
}
|
|
|
|
|
|
[data-menubar-trigger]:focus-visible {
|
|
|
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
|
outline-offset: 1px;
|
|
|
position: relative;
|
|
|
z-index: 1;
|
|
|
}
|
|
|
|
|
|
[data-menubar-trigger][data-disabled] {
|
|
|
color: var(--color-content-muted);
|
|
|
pointer-events: none;
|
|
|
cursor: default;
|
|
|
}
|
|
|
|
|
|
/* ── Content — per-menu panel ───────────────────────────────────────────
|
|
|
* Chrome (border, bg, shadow, padding, item rows, separator, sub-trigger
|
|
|
* chevron, group heading) comes from `dropdown-menu.css` via the
|
|
|
* `[data-dropdown-menu-*]` selectors that the Menu/Item/Sub parts emit
|
|
|
* (soma re-exports the DropdownMenu parts for the per-menu inner slots).
|
|
|
*
|
|
|
* We add only the menubar-specific marker selector here. The
|
|
|
* `[data-menubar-content]` selector matches the panel that wraps the
|
|
|
* Item rows — it's the same element that ALSO carries
|
|
|
* `[data-dropdown-menu-content]` because soma's MenubarContent
|
|
|
* specializes DropdownMenuContent. So the dropdown-menu panel rules
|
|
|
* already paint it; we don't repeat them.
|
|
|
*/
|
|
|
|
|
|
/* ── Menu panel gap — measured from the BAR, not the in-bar trigger ──────────
|
|
|
* The menu anchors to the in-bar trigger, but the trigger is INSET from the bar
|
|
|
* edge — by the bar's padding PLUS its own 1px vertical centering (the trigger's
|
|
|
* `min-block-size` subtracts 2px, so with `align-items: center` it sits 1px off
|
|
|
* each padding edge). So the canonical `menu` gap (from the trigger) would leave
|
|
|
* the panel flush with the BAR. Add that full inset (padding + 1px) back per size
|
|
|
* so the visible gap reads exactly `--floating-gap-menu` (--space-1, 4px) FROM THE
|
|
|
* BAR — "the space is the menu, not the option". The shared positioner reads
|
|
|
* `--floating-gap` off the content; these (0,2,0) beat the foundation
|
|
|
* `[data-floating-gap='menu']` (0,1,0). */
|
|
|
[data-menubar-content][data-size='md'],
|
|
|
[data-menubar-panel][data-size='md'] {
|
|
|
--floating-gap: calc(var(--floating-gap-menu) + var(--space-1) + 1px);
|
|
|
}
|
|
|
|
|
|
[data-menubar-content][data-size='sm'],
|
|
|
[data-menubar-panel][data-size='sm'] {
|
|
|
--floating-gap: calc(var(--floating-gap-menu) + var(--space-1) * 0.5 + 1px);
|
|
|
}
|
|
|
|
|
|
[data-menubar-content][data-size='lg'],
|
|
|
[data-menubar-panel][data-size='lg'] {
|
|
|
--floating-gap: calc(var(--floating-gap-menu) + var(--space-2) + 1px);
|
|
|
}
|
|
|
|
|
|
/* ── Panel — per-entry role=dialog form surface ──────────────────────────
|
|
|
* The alternative to Content for a bar entry: a free surface for form
|
|
|
* controls (Fields, selects, sliders). It does NOT carry
|
|
|
* `data-dropdown-menu-content` (it is the menubar's own part), so the
|
|
|
* dropdown recipe never paints it: elevation (surface · border · shadow)
|
|
|
* comes from the `overlay` plane via `data-depth` (stamped by the eidos
|
|
|
* wrapper, same as Content); everything else is defined here. Portal
|
|
|
* typography rule: font-family + line-height + color anchored on the
|
|
|
* portaled root. */
|
|
|
[data-menubar-panel] {
|
|
|
/* Portaled root: bring box-sizing along so a consumer-set
|
|
|
* `inline-size: var(--floating-anchor-width)` (mega-panel idiom) matches
|
|
|
* the anchor EDGE to edge, padding and border included. */
|
|
|
box-sizing: border-box;
|
|
|
z-index: var(--menubar-panel-z);
|
|
|
min-inline-size: var(--menubar-panel-min-width);
|
|
|
/* Cap the INTRINSIC size: the floating wrapper measures the panel's
|
|
|
* max-content (a grid's track sum can dwarf its used inline-size), and an
|
|
|
* inflated measurement makes the positioner flip/clamp the placement. */
|
|
|
max-inline-size: 100vw;
|
|
|
max-block-size: var(--menubar-panel-max-height);
|
|
|
overflow-y: auto;
|
|
|
overscroll-behavior: contain;
|
|
|
padding: var(--menubar-panel-padding);
|
|
|
border-radius: var(--menubar-panel-radius);
|
|
|
font-family: var(--style-label-font-family);
|
|
|
font-size: var(--font-size-md);
|
|
|
line-height: var(--leading-ui);
|
|
|
color: var(--color-content-primary);
|
|
|
}
|
|
|
|
|
|
[data-menubar-panel][data-size='sm'] {
|
|
|
font-size: var(--font-size-sm);
|
|
|
}
|
|
|
|
|
|
[data-menubar-panel][data-size='lg'] {
|
|
|
font-size: var(--font-size-lg);
|
|
|
}
|
|
|
|
|
|
/* Anchored (mega-panel): the panel matches its custom anchor's width. Soma
|
|
|
* pre-measures the anchor and publishes `--menubar-panel-anchor-width` on
|
|
|
* the element; until that lands (one frame on the very first open) the cap
|
|
|
* falls back to the panel's min-width so the positioner's FIRST measurement
|
|
|
* is already small — align start then never flips, and the panel simply
|
|
|
* widens in place when the var arrives. */
|
|
|
[data-menubar-panel][data-anchored] {
|
|
|
/* The per-size `--floating-gap` rules above compensate the trigger's
|
|
|
* inset INSIDE the bar (bar padding + 1px centering) so a trigger-anchored
|
|
|
* surface reads the canonical menu gap FROM THE BAR. Anchored to the bar
|
|
|
* itself, the positioner already measures from the bar edge — the canon
|
|
|
* applies raw, no compensation. */
|
|
|
--floating-gap: var(--floating-gap-menu);
|
|
|
inline-size: var(--menubar-panel-anchor-width, var(--menubar-panel-min-width));
|
|
|
max-inline-size: var(--menubar-panel-anchor-width, var(--menubar-panel-min-width));
|
|
|
/* The positioner measures the floating WRAPPER (min-width: max-content),
|
|
|
* and a grid child contributes its TRACK-SUM intrinsic size there — width
|
|
|
* caps on the grid itself don't stop it, so an inflated measurement flips
|
|
|
* the placement. Inline-size containment makes this panel's contribution
|
|
|
* its used width: in anchored mode the width comes from the anchor, never
|
|
|
* from the content. */
|
|
|
contain: inline-size;
|
|
|
}
|
|
|
|
|
|
/* Focus lives on the panel's controls; the surface itself (focused on
|
|
|
* pointer opens) shows no ring. Same treatment as the menu panels. */
|
|
|
[data-menubar-panel]:focus-visible {
|
|
|
outline: none;
|
|
|
}
|