Initial commit: HMDesignSystem baseline
This commit is contained in:
commit
b6a0a61103
486 files changed
+68432
No files matched your search
@@ -0,0 +1,67 @@
|
||||
/* Accordion mapping. ui-viz lists Accordion only as a component name (no spec), so these are current HM values built from the HM
|
||||
Semantic tokens. Items are separated by a 1px divider; no outer box. Radix attributes drive the states (data-state, data-disabled);
|
||||
the open / close animation and the chevron rotation stay the shadcn ones. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-accordion-text: var(--hm-sem-fg-primary);
|
||||
--hm-accordion-content-text: var(--hm-sem-fg-primary);
|
||||
--hm-accordion-divider-width: 1px;
|
||||
--hm-accordion-divider-color: var(--hm-sem-stroke-input-default);
|
||||
|
||||
--hm-accordion-trigger-min-height: 48px;
|
||||
--hm-accordion-trigger-padding-y: 12px;
|
||||
--hm-accordion-trigger-gap: 16px;
|
||||
--hm-accordion-trigger-font-size: 16px;
|
||||
--hm-accordion-trigger-line-height: 24px;
|
||||
--hm-accordion-trigger-font-weight: 500;
|
||||
--hm-accordion-icon-size: 20px;
|
||||
--hm-accordion-icon-color: var(--hm-sem-fg-secondary);
|
||||
|
||||
--hm-accordion-content-font-size: 14px;
|
||||
--hm-accordion-content-line-height: 20px;
|
||||
--hm-accordion-content-padding-bottom: 16px;
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-accordion-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-slot="accordion-item"] {
|
||||
border-bottom: var(--hm-accordion-divider-width) solid var(--hm-accordion-divider-color);
|
||||
}
|
||||
[data-slot="accordion-item"]:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
[data-slot="accordion-trigger"] {
|
||||
box-sizing: border-box;
|
||||
min-height: var(--hm-accordion-trigger-min-height);
|
||||
padding: var(--hm-accordion-trigger-padding-y) 0;
|
||||
gap: var(--hm-accordion-trigger-gap);
|
||||
align-items: center;
|
||||
color: var(--hm-accordion-text);
|
||||
font-size: var(--hm-accordion-trigger-font-size);
|
||||
line-height: var(--hm-accordion-trigger-line-height);
|
||||
font-weight: var(--hm-accordion-trigger-font-weight);
|
||||
overflow-wrap: anywhere;
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="accordion-trigger"]:hover:not(:disabled) {
|
||||
background: transparent;
|
||||
}
|
||||
[data-slot="accordion-trigger"]:disabled {
|
||||
opacity: var(--hm-accordion-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
[data-slot="accordion-trigger-icon"] {
|
||||
width: var(--hm-accordion-icon-size);
|
||||
height: var(--hm-accordion-icon-size);
|
||||
color: var(--hm-accordion-icon-color);
|
||||
}
|
||||
|
||||
[data-slot="accordion-content-inner"] {
|
||||
padding-bottom: var(--hm-accordion-content-padding-bottom);
|
||||
color: var(--hm-accordion-content-text);
|
||||
font-size: var(--hm-accordion-content-font-size);
|
||||
line-height: var(--hm-accordion-content-line-height);
|
||||
font-weight: 400;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/* Alert Dialog mapping. ui-viz has no Alert Dialog spec (its Dialog doc lists it as a follow-up), so these are current HM values built
|
||||
from the HM Semantic tokens. A compact confirmation: Header (Title + Description) and Actions, no Close (X), no Body region.
|
||||
Radix AlertDialog keeps Portal, focus trap, initial focus on Cancel, Escape, no outside dismiss and focus return. Action and
|
||||
Cancel are the HM Button (composed in the TSX), so no button style is set here. The Overlay keeps the shadcn default.
|
||||
Values repeat Dialog's look in meaning only: no --hm-dialog-* token is referenced. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-alert-dialog-background: var(--hm-sem-bg-surface);
|
||||
--hm-alert-dialog-text: var(--hm-sem-fg-primary);
|
||||
--hm-alert-dialog-border-width: 1px;
|
||||
--hm-alert-dialog-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-alert-dialog-radius: 8px;
|
||||
--hm-alert-dialog-shadow: 0 0 12px color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
|
||||
|
||||
--hm-alert-dialog-width: 400px;
|
||||
--hm-alert-dialog-edge: 16px;
|
||||
--hm-alert-dialog-padding: 24px;
|
||||
--hm-alert-dialog-header-gap: 8px;
|
||||
--hm-alert-dialog-actions-gap: 8px;
|
||||
--hm-alert-dialog-actions-margin-top: 24px;
|
||||
|
||||
--hm-alert-dialog-title-font-size: 18px;
|
||||
--hm-alert-dialog-title-line-height: 28px;
|
||||
--hm-alert-dialog-title-font-weight: 700;
|
||||
--hm-alert-dialog-description-font-size: 14px;
|
||||
--hm-alert-dialog-description-line-height: 20px;
|
||||
--hm-alert-dialog-description-color: var(--hm-sem-fg-secondary);
|
||||
}
|
||||
|
||||
[data-slot="alert-dialog-content"] {
|
||||
box-sizing: border-box;
|
||||
width: min(var(--hm-alert-dialog-width), calc(100vw - 2 * var(--hm-alert-dialog-edge)));
|
||||
max-height: calc(100dvh - 2 * var(--hm-alert-dialog-edge));
|
||||
overflow: auto;
|
||||
padding: var(--hm-alert-dialog-padding);
|
||||
border: var(--hm-alert-dialog-border-width) solid var(--hm-alert-dialog-border-color);
|
||||
border-radius: var(--hm-alert-dialog-radius);
|
||||
background: var(--hm-alert-dialog-background);
|
||||
color: var(--hm-alert-dialog-text);
|
||||
box-shadow: var(--hm-alert-dialog-shadow);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-slot="alert-dialog-header"] {
|
||||
gap: var(--hm-alert-dialog-header-gap);
|
||||
}
|
||||
[data-slot="alert-dialog-title"] {
|
||||
font-size: var(--hm-alert-dialog-title-font-size);
|
||||
line-height: var(--hm-alert-dialog-title-line-height);
|
||||
font-weight: var(--hm-alert-dialog-title-font-weight);
|
||||
color: var(--hm-alert-dialog-text);
|
||||
}
|
||||
[data-slot="alert-dialog-description"] {
|
||||
font-size: var(--hm-alert-dialog-description-font-size);
|
||||
line-height: var(--hm-alert-dialog-description-line-height);
|
||||
font-weight: 400;
|
||||
color: var(--hm-alert-dialog-description-color);
|
||||
}
|
||||
[data-slot="alert-dialog-footer"] {
|
||||
gap: var(--hm-alert-dialog-actions-gap);
|
||||
margin-top: var(--hm-alert-dialog-actions-margin-top);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/* Alert mapping — current HM values from the shared Semantic tokens. ui-viz has no Alert spec (and its success / warning / information
|
||||
status colors are undecided), so only a neutral default and a destructive variant exist. Variants use data-variant. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-alert-radius: var(--hm-radius-control-md);
|
||||
--hm-alert-padding-x: 16px;
|
||||
--hm-alert-padding-y: 12px;
|
||||
--hm-alert-border-width: 1px;
|
||||
--hm-alert-icon-size: 16px;
|
||||
--hm-alert-title-font-size: 14px;
|
||||
--hm-alert-title-line-height: 20px;
|
||||
--hm-alert-title-font-weight: 500;
|
||||
--hm-alert-description-font-size: 14px;
|
||||
--hm-alert-description-line-height: 20px;
|
||||
--hm-alert-description-font-weight: 400;
|
||||
|
||||
--hm-alert-default-background: var(--hm-sem-bg-surface);
|
||||
--hm-alert-default-border-color: var(--hm-sem-stroke-default);
|
||||
--hm-alert-default-title-color: var(--hm-sem-fg-primary);
|
||||
--hm-alert-default-description-color: var(--hm-sem-fg-secondary);
|
||||
|
||||
--hm-alert-destructive-background: var(--hm-sem-bg-surface);
|
||||
--hm-alert-destructive-border-color: var(--hm-sem-action-destructive);
|
||||
--hm-alert-destructive-title-color: var(--hm-sem-action-destructive);
|
||||
--hm-alert-destructive-description-color: var(--hm-sem-fg-secondary);
|
||||
}
|
||||
|
||||
[data-slot="alert"] {
|
||||
box-sizing: border-box;
|
||||
padding: var(--hm-alert-padding-y) var(--hm-alert-padding-x);
|
||||
border: var(--hm-alert-border-width) solid var(--hm-alert-default-border-color);
|
||||
border-radius: var(--hm-alert-radius);
|
||||
background: var(--hm-alert-default-background);
|
||||
font-size: var(--hm-alert-description-font-size);
|
||||
line-height: var(--hm-alert-description-line-height);
|
||||
}
|
||||
[data-slot="alert"][data-variant="default"] {
|
||||
color: var(--hm-alert-default-title-color);
|
||||
}
|
||||
[data-slot="alert"][data-variant="destructive"] {
|
||||
border-color: var(--hm-alert-destructive-border-color);
|
||||
background: var(--hm-alert-destructive-background);
|
||||
color: var(--hm-alert-destructive-title-color);
|
||||
}
|
||||
[data-slot="alert-title"] {
|
||||
font-size: var(--hm-alert-title-font-size);
|
||||
line-height: var(--hm-alert-title-line-height);
|
||||
font-weight: var(--hm-alert-title-font-weight);
|
||||
}
|
||||
[data-slot="alert-description"] {
|
||||
font-size: var(--hm-alert-description-font-size);
|
||||
line-height: var(--hm-alert-description-line-height);
|
||||
font-weight: var(--hm-alert-description-font-weight);
|
||||
}
|
||||
[data-slot="alert"][data-variant="default"] [data-slot="alert-description"] {
|
||||
color: var(--hm-alert-default-description-color);
|
||||
}
|
||||
[data-slot="alert"][data-variant="destructive"] [data-slot="alert-description"] {
|
||||
color: var(--hm-alert-destructive-description-color);
|
||||
}
|
||||
[data-slot="alert"] > svg {
|
||||
width: var(--hm-alert-icon-size);
|
||||
height: var(--hm-alert-icon-size);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* Avatar mapping. ui-viz lists Avatar only as a component name (no spec), so these are current HM values. Sizes follow the shadcn
|
||||
API (sm | default | lg). The fallback uses the green tint face (the shared accent). */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-avatar-size-sm: 24px;
|
||||
--hm-avatar-size-md: 32px;
|
||||
--hm-avatar-size-lg: 40px;
|
||||
--hm-avatar-radius: 9999px;
|
||||
|
||||
--hm-avatar-fallback-background: var(--accent);
|
||||
--hm-avatar-fallback-text: var(--accent-foreground);
|
||||
--hm-avatar-fallback-font-weight: 500;
|
||||
--hm-avatar-fallback-font-size-sm: 10px;
|
||||
--hm-avatar-fallback-font-size-md: 12px;
|
||||
--hm-avatar-fallback-font-size-lg: 14px;
|
||||
}
|
||||
|
||||
[data-slot="avatar"] {
|
||||
--hm-avatar-size: var(--hm-avatar-size-md);
|
||||
--hm-avatar-fallback-font-size: var(--hm-avatar-fallback-font-size-md);
|
||||
width: var(--hm-avatar-size);
|
||||
height: var(--hm-avatar-size);
|
||||
border-radius: var(--hm-avatar-radius);
|
||||
}
|
||||
[data-slot="avatar"][data-size="sm"] {
|
||||
--hm-avatar-size: var(--hm-avatar-size-sm);
|
||||
--hm-avatar-fallback-font-size: var(--hm-avatar-fallback-font-size-sm);
|
||||
}
|
||||
[data-slot="avatar"][data-size="lg"] {
|
||||
--hm-avatar-size: var(--hm-avatar-size-lg);
|
||||
--hm-avatar-fallback-font-size: var(--hm-avatar-fallback-font-size-lg);
|
||||
}
|
||||
[data-slot="avatar-fallback"] {
|
||||
border-radius: var(--hm-avatar-radius);
|
||||
background: var(--hm-avatar-fallback-background);
|
||||
color: var(--hm-avatar-fallback-text);
|
||||
font-size: var(--hm-avatar-fallback-font-size);
|
||||
font-weight: var(--hm-avatar-fallback-font-weight);
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/* Badge mapping — current HM values from the shared Semantic tokens (ui-viz has no Badge spec; it was on hold there, so it is reference only).
|
||||
Variants use data-variant. Only roles that exist in the HM Theme: primary, neutral, destructive, outline. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-badge-radius: var(--hm-radius-control-md);
|
||||
--hm-badge-padding-x: 8px;
|
||||
--hm-badge-padding-y: 2px;
|
||||
--hm-badge-border-width: 1px;
|
||||
--hm-badge-font-size: 12px;
|
||||
--hm-badge-line-height: 18px;
|
||||
--hm-badge-font-weight: 500;
|
||||
|
||||
/* Default is the Strong Brand / Primary color (Primary Green), not the Active State Semantic. */
|
||||
--hm-badge-default-background: var(--hm-sem-action-primary);
|
||||
--hm-badge-default-text: var(--hm-sem-on-action-primary);
|
||||
--hm-badge-default-border-color: var(--hm-sem-action-primary);
|
||||
|
||||
--hm-badge-secondary-background: var(--hm-sem-bg-subtle);
|
||||
--hm-badge-secondary-text: var(--hm-sem-fg-secondary);
|
||||
--hm-badge-secondary-border-color: var(--hm-sem-bg-subtle);
|
||||
|
||||
--hm-badge-destructive-background: var(--hm-sem-action-destructive);
|
||||
--hm-badge-destructive-text: var(--hm-sem-on-action-primary);
|
||||
--hm-badge-destructive-border-color: var(--hm-sem-action-destructive);
|
||||
|
||||
--hm-badge-outline-background: var(--hm-sem-bg-surface);
|
||||
--hm-badge-outline-text: var(--hm-sem-fg-primary);
|
||||
--hm-badge-outline-border-color: var(--hm-sem-stroke-default);
|
||||
}
|
||||
|
||||
[data-slot="badge"] {
|
||||
box-sizing: border-box;
|
||||
padding: var(--hm-badge-padding-y) var(--hm-badge-padding-x);
|
||||
border: var(--hm-badge-border-width) solid transparent;
|
||||
border-radius: var(--hm-badge-radius);
|
||||
font-size: var(--hm-badge-font-size);
|
||||
line-height: var(--hm-badge-line-height);
|
||||
font-weight: var(--hm-badge-font-weight);
|
||||
}
|
||||
[data-slot="badge"][data-variant="default"] {
|
||||
background: var(--hm-badge-default-background);
|
||||
color: var(--hm-badge-default-text);
|
||||
border-color: var(--hm-badge-default-border-color);
|
||||
}
|
||||
[data-slot="badge"][data-variant="secondary"] {
|
||||
background: var(--hm-badge-secondary-background);
|
||||
color: var(--hm-badge-secondary-text);
|
||||
border-color: var(--hm-badge-secondary-border-color);
|
||||
}
|
||||
[data-slot="badge"][data-variant="destructive"] {
|
||||
background: var(--hm-badge-destructive-background);
|
||||
color: var(--hm-badge-destructive-text);
|
||||
border-color: var(--hm-badge-destructive-border-color);
|
||||
}
|
||||
[data-slot="badge"][data-variant="outline"] {
|
||||
background: var(--hm-badge-outline-background);
|
||||
color: var(--hm-badge-outline-text);
|
||||
border-color: var(--hm-badge-outline-border-color);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/* Breadcrumb mapping. ui-viz lists Breadcrumb only as a component name (no spec), so these are current HM values built from the HM
|
||||
Semantic tokens. Parent links are secondary text that turns primary on hover; the current page is a primary-colored label. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-breadcrumb-font-size: 14px;
|
||||
--hm-breadcrumb-line-height: 20px;
|
||||
--hm-breadcrumb-font-weight: 400;
|
||||
--hm-breadcrumb-gap: 8px;
|
||||
|
||||
--hm-breadcrumb-link-color: var(--hm-sem-fg-secondary);
|
||||
--hm-breadcrumb-link-hover-color: var(--hm-sem-fg-primary);
|
||||
--hm-breadcrumb-page-color: var(--hm-sem-fg-primary);
|
||||
--hm-breadcrumb-page-font-weight: 700;
|
||||
--hm-breadcrumb-separator-color: var(--hm-sem-fg-secondary);
|
||||
--hm-breadcrumb-separator-size: 16px;
|
||||
--hm-breadcrumb-ellipsis-size: 20px;
|
||||
--hm-breadcrumb-focus-radius: 2px;
|
||||
}
|
||||
|
||||
[data-slot="breadcrumb-list"] {
|
||||
gap: var(--hm-breadcrumb-gap);
|
||||
color: var(--hm-breadcrumb-link-color);
|
||||
font-size: var(--hm-breadcrumb-font-size);
|
||||
line-height: var(--hm-breadcrumb-line-height);
|
||||
font-weight: var(--hm-breadcrumb-font-weight);
|
||||
}
|
||||
[data-slot="breadcrumb-item"] {
|
||||
gap: var(--hm-breadcrumb-gap);
|
||||
}
|
||||
[data-slot="breadcrumb-link"] {
|
||||
color: var(--hm-breadcrumb-link-color);
|
||||
border-radius: var(--hm-breadcrumb-focus-radius);
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="breadcrumb-link"]:hover {
|
||||
color: var(--hm-breadcrumb-link-hover-color);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
[data-slot="breadcrumb-link"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
[data-slot="breadcrumb-page"] {
|
||||
color: var(--hm-breadcrumb-page-color);
|
||||
font-weight: var(--hm-breadcrumb-page-font-weight);
|
||||
cursor: default;
|
||||
}
|
||||
[data-slot="breadcrumb-separator"] {
|
||||
color: var(--hm-breadcrumb-separator-color);
|
||||
}
|
||||
[data-slot="breadcrumb-separator"] > svg {
|
||||
width: var(--hm-breadcrumb-separator-size);
|
||||
height: var(--hm-breadcrumb-separator-size);
|
||||
}
|
||||
[data-slot="breadcrumb-ellipsis"] {
|
||||
width: var(--hm-breadcrumb-ellipsis-size);
|
||||
height: var(--hm-breadcrumb-ellipsis-size);
|
||||
color: var(--hm-breadcrumb-separator-color);
|
||||
}
|
||||
[data-slot="breadcrumb-ellipsis"] > svg {
|
||||
width: var(--hm-breadcrumb-separator-size);
|
||||
height: var(--hm-breadcrumb-separator-size);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/* Button mapping: shared HM theme values are the defaults; each mapping can be overridden alone.
|
||||
variant = ui_viz role, appearance = ui_viz treatment. Only [data-appearance] buttons (the HM roles) are styled here;
|
||||
ghost and link carry no data-appearance and keep the shadcn defaults. */
|
||||
:root, [data-hm-theme] {
|
||||
/* ui_viz: docs/components/button.md §1.1 D-087 — Confirmed */
|
||||
--hm-button-radius: var(--hm-radius-button);
|
||||
--hm-button-font-family: var(--hm-font-family-base);
|
||||
--hm-button-weight-brand: var(--hm-font-weight-bold);
|
||||
--hm-button-weight-standard: var(--hm-font-weight-medium);
|
||||
--hm-button-text: var(--hm-sem-on-action-primary);
|
||||
--hm-button-disabled-opacity: 0.6;
|
||||
|
||||
/* ui_viz: docs/components/button.md §26 / §1.1 D-087 — Confirmed (size, padding-x, label size / line-height) */
|
||||
--hm-button-height-sm: 28px;
|
||||
--hm-button-height-md: var(--hm-control-height-md);
|
||||
--hm-button-height-lg: 42px;
|
||||
--hm-button-padding-x-sm: 10px;
|
||||
--hm-button-padding-x-md: 14px;
|
||||
--hm-button-padding-x-lg: 18px;
|
||||
--hm-button-font-size-sm: 12px;
|
||||
--hm-button-font-size-md: var(--hm-font-size-button-md);
|
||||
--hm-button-font-size-lg: 15px;
|
||||
--hm-button-line-height-sm: 18px;
|
||||
--hm-button-line-height-md: 20px;
|
||||
--hm-button-line-height-lg: 22px;
|
||||
|
||||
/* ui_viz: docs/components/button.md §1.1 D-087 Brand gradient (180deg, bg stops 0/30/65/100, border stops 0/50/100) — Confirmed */
|
||||
--hm-button-brand-green-background: var(--hm-sem-surface-action-primary);
|
||||
--hm-button-brand-green-border: var(--hm-sem-border-action-primary);
|
||||
--hm-button-brand-brown-background: linear-gradient(180deg, var(--hm-sem-palette-brown-light-2) 0%, var(--hm-sem-palette-brown-light-1) 30%, var(--hm-sem-palette-brown-dark-1) 65%, var(--hm-sem-palette-brown-deep) 100%);
|
||||
--hm-button-brand-brown-border: linear-gradient(180deg, var(--hm-sem-palette-brown-dark-1) 0%, var(--hm-sem-palette-brown-highlight) 50%, var(--hm-sem-palette-brown-deep) 100%);
|
||||
--hm-button-brand-orange-background: linear-gradient(180deg, var(--hm-sem-palette-orange-light) 0%, var(--hm-sem-palette-orange-mid) 30%, var(--hm-sem-palette-orange-dark) 65%, var(--hm-sem-palette-orange-deep) 100%);
|
||||
--hm-button-brand-orange-border: linear-gradient(180deg, var(--hm-sem-palette-orange-border) 0%, var(--hm-sem-palette-orange-light-4) 50%, var(--hm-sem-palette-orange-border-deep) 100%);
|
||||
/* ui_viz: docs/components/button.md §1.1 D-087 Brand bevel (Green, Brown only) — Confirmed */
|
||||
--hm-button-brand-bevel: inset 0 1px 1px var(--hm-color-action-shadow-light), inset 0 -1px 2px var(--hm-color-action-shadow-dark);
|
||||
|
||||
/* ui_viz: docs/components/button.md §28 / §1.1 D-086, D-087 Standard Solid (face / border) — Confirmed */
|
||||
--hm-button-solid-green-background: var(--hm-sem-palette-green-light-1);
|
||||
--hm-button-solid-green-border: var(--hm-sem-palette-green-dark-1);
|
||||
--hm-button-solid-brown-background: var(--hm-sem-palette-brown-light-1);
|
||||
--hm-button-solid-brown-border: var(--hm-sem-palette-brown-dark-1);
|
||||
--hm-button-solid-orange-background: var(--hm-sem-palette-orange-default);
|
||||
--hm-button-solid-orange-border: var(--hm-sem-palette-orange-default);
|
||||
--hm-button-solid-gray-background: var(--hm-sem-palette-gray);
|
||||
--hm-button-solid-gray-border: var(--hm-sem-palette-gray-dark);
|
||||
|
||||
/* ui_viz: docs/components/button.md §28 Standard Outline (ink) and D-087 Red Outline Hover/Pressed — Confirmed */
|
||||
--hm-button-outline-green-ink: var(--hm-sem-action-primary);
|
||||
--hm-button-outline-brown-ink: var(--hm-sem-palette-brown-default);
|
||||
--hm-button-outline-red-ink: var(--hm-sem-action-destructive);
|
||||
--hm-button-outline-red-surface: var(--hm-color-action-destructive-hover);
|
||||
--hm-button-outline-surface: var(--hm-sem-bg-surface);
|
||||
|
||||
/* ui_viz: docs/components/button.md §12 Hover and Pressed are the same Black 20% state surface — Confirmed */
|
||||
--hm-button-state-overlay: var(--hm-color-action-overlay-hover);
|
||||
}
|
||||
|
||||
/* Roles: pick the colors of each treatment. */
|
||||
[data-slot="button"][data-variant="primary"] {
|
||||
--hm-button-brand-background: var(--hm-button-brand-green-background);
|
||||
--hm-button-brand-border: var(--hm-button-brand-green-border);
|
||||
--hm-button-solid-background: var(--hm-button-solid-green-background);
|
||||
--hm-button-solid-border: var(--hm-button-solid-green-border);
|
||||
--hm-button-outline-ink: var(--hm-button-outline-green-ink);
|
||||
}
|
||||
[data-slot="button"][data-variant="secondary"] {
|
||||
--hm-button-brand-background: var(--hm-button-brand-brown-background);
|
||||
--hm-button-brand-border: var(--hm-button-brand-brown-border);
|
||||
--hm-button-solid-background: var(--hm-button-solid-brown-background);
|
||||
--hm-button-solid-border: var(--hm-button-solid-brown-border);
|
||||
--hm-button-outline-ink: var(--hm-button-outline-brown-ink);
|
||||
}
|
||||
[data-slot="button"][data-variant="modify"] {
|
||||
--hm-button-brand-background: var(--hm-button-brand-orange-background);
|
||||
--hm-button-brand-border: var(--hm-button-brand-orange-border);
|
||||
--hm-button-solid-background: var(--hm-button-solid-orange-background);
|
||||
--hm-button-solid-border: var(--hm-button-solid-orange-border);
|
||||
}
|
||||
[data-slot="button"][data-variant="cancel"] {
|
||||
--hm-button-solid-background: var(--hm-button-solid-gray-background);
|
||||
--hm-button-solid-border: var(--hm-button-solid-gray-border);
|
||||
--hm-button-outline-ink: var(--hm-sem-action-cancel);
|
||||
}
|
||||
[data-slot="button"][data-variant="destructive"] {
|
||||
--hm-button-outline-ink: var(--hm-button-outline-red-ink);
|
||||
}
|
||||
|
||||
/* Shared. The last two shadows keep the shadcn focus and invalid ring visible on top of the bevel and state surface. */
|
||||
[data-slot="button"][data-appearance] {
|
||||
--hm-button-border-width: 1px;
|
||||
--hm-button-bevel: 0 0 #0000;
|
||||
--hm-button-state-surface: 0 0 #0000;
|
||||
border: var(--hm-button-border-width) solid transparent;
|
||||
border-radius: var(--hm-button-radius);
|
||||
font-family: var(--hm-button-font-family);
|
||||
font-weight: var(--hm-button-weight-standard);
|
||||
box-shadow: var(--hm-button-state-surface), var(--hm-button-bevel), var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000);
|
||||
}
|
||||
|
||||
/* Size: min-height with vertical padding; explicit wrapping labels can still grow. */
|
||||
[data-slot="button"][data-appearance]:is([data-size="default"], [data-size="sm"], [data-size="lg"]) {
|
||||
--hm-button-height: var(--hm-button-height-md);
|
||||
--hm-button-padding-x: var(--hm-button-padding-x-md);
|
||||
--hm-button-font-size: var(--hm-button-font-size-md);
|
||||
--hm-button-line-height: var(--hm-button-line-height-md);
|
||||
height: auto;
|
||||
min-height: var(--hm-button-height);
|
||||
max-width: 100%;
|
||||
padding: calc((var(--hm-button-height) - var(--hm-button-line-height)) / 2 - var(--hm-button-border-width)) var(--hm-button-padding-x);
|
||||
font-size: var(--hm-button-font-size);
|
||||
line-height: var(--hm-button-line-height);
|
||||
text-align: center;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Keep the existing shadcn nowrap default. Normal labels retain their intrinsic
|
||||
width when a parent is narrower; consumers can opt into the existing long-label
|
||||
behavior with className="whitespace-normal", without a new prop.
|
||||
Utilities remain able to override these layout defaults. */
|
||||
@layer components {
|
||||
[data-slot="button"][data-appearance]:is([data-size="default"], [data-size="sm"], [data-size="lg"]) {
|
||||
min-width: max-content;
|
||||
}
|
||||
[data-slot="button"][data-appearance].whitespace-normal:is([data-size="default"], [data-size="sm"], [data-size="lg"]) {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
[data-slot="button"][data-appearance][data-size="sm"] {
|
||||
--hm-button-height: var(--hm-button-height-sm);
|
||||
--hm-button-padding-x: var(--hm-button-padding-x-sm);
|
||||
--hm-button-font-size: var(--hm-button-font-size-sm);
|
||||
--hm-button-line-height: var(--hm-button-line-height-sm);
|
||||
}
|
||||
[data-slot="button"][data-appearance][data-size="lg"] {
|
||||
--hm-button-height: var(--hm-button-height-lg);
|
||||
--hm-button-padding-x: var(--hm-button-padding-x-lg);
|
||||
--hm-button-font-size: var(--hm-button-font-size-lg);
|
||||
--hm-button-line-height: var(--hm-button-line-height-lg);
|
||||
}
|
||||
|
||||
/* Brand: two-layer gradient (face on padding-box, border on border-box), white label, weight 700. */
|
||||
[data-slot="button"][data-appearance="brand"] {
|
||||
--hm-button-border-width: 1.5px;
|
||||
border-color: transparent;
|
||||
background: var(--hm-button-brand-background) padding-box, var(--hm-button-brand-border) border-box;
|
||||
color: var(--hm-button-text);
|
||||
font-weight: var(--hm-button-weight-brand);
|
||||
}
|
||||
[data-slot="button"][data-appearance="brand"]:is([data-variant="primary"], [data-variant="secondary"]) {
|
||||
--hm-button-bevel: var(--hm-button-brand-bevel);
|
||||
}
|
||||
|
||||
/* Solid: flat face, 1px border, white label. */
|
||||
[data-slot="button"][data-appearance="solid"] {
|
||||
border-color: var(--hm-button-solid-border);
|
||||
background: var(--hm-button-solid-background);
|
||||
color: var(--hm-button-text);
|
||||
}
|
||||
|
||||
/* Brand and Solid: Hover and Pressed are the same Black 20% over the face. */
|
||||
[data-slot="button"]:is([data-appearance="brand"], [data-appearance="solid"]):is(:hover, :active):not(:disabled) {
|
||||
--hm-button-state-surface: inset 0 0 0 999px var(--hm-button-state-overlay);
|
||||
}
|
||||
|
||||
/* Outline: white face, 1px role ink. Hover fills with the role ink; Pressed is white with Black 20%. */
|
||||
[data-slot="button"][data-appearance="outline"] {
|
||||
border-color: var(--hm-button-outline-ink);
|
||||
background: var(--hm-button-outline-surface);
|
||||
color: var(--hm-button-outline-ink);
|
||||
}
|
||||
[data-slot="button"][data-appearance="outline"]:hover:not(:disabled) {
|
||||
background: var(--hm-button-outline-ink);
|
||||
color: var(--hm-button-text);
|
||||
}
|
||||
[data-slot="button"][data-appearance="outline"]:active:not(:disabled) {
|
||||
background: var(--hm-button-outline-surface);
|
||||
color: var(--hm-button-outline-ink);
|
||||
--hm-button-state-surface: inset 0 0 0 999px var(--hm-button-state-overlay);
|
||||
}
|
||||
/* Red Outline exception: Hover and Pressed are a Red 8% face, no Black overlay. */
|
||||
[data-slot="button"][data-appearance="outline"][data-variant="destructive"]:is(:hover, :active):not(:disabled) {
|
||||
background: var(--hm-button-outline-red-surface);
|
||||
color: var(--hm-button-outline-ink);
|
||||
--hm-button-state-surface: 0 0 #0000;
|
||||
}
|
||||
|
||||
/* Disabled keeps the Default paint, dims the whole button, and shows the forbidden cursor. */
|
||||
[data-slot="button"][data-appearance]:disabled {
|
||||
opacity: var(--hm-button-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/* Calendar mapping. ui-viz has no Calendar spec (inventory lists Date Picker by name only), so these are current HM values built
|
||||
from the HM Semantic / Primitive tokens. react-day-picker keeps its default class names (rdp-*) and puts the state attributes
|
||||
on the day cell (data-selected, data-today, data-outside, data-disabled, data-focused); the range states are its classes
|
||||
(rdp-range_start / _middle / _end). Everything is scoped under [data-slot="calendar"]. The Calendar draws no surface of its own
|
||||
(the container, e.g. Popover or Card, owns it). Nothing here references Button / Input / Popover tokens. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-calendar-padding: 12px;
|
||||
--hm-calendar-text: var(--hm-sem-fg-primary);
|
||||
--hm-calendar-month-gap: 16px;
|
||||
|
||||
--hm-calendar-caption-height: 32px;
|
||||
--hm-calendar-caption-font-size: 14px;
|
||||
--hm-calendar-caption-line-height: 20px;
|
||||
--hm-calendar-caption-font-weight: 700;
|
||||
|
||||
--hm-calendar-nav-size: 28px;
|
||||
--hm-calendar-nav-radius: var(--hm-radius-control-md);
|
||||
--hm-calendar-nav-icon-size: 16px;
|
||||
--hm-calendar-nav-hover-background: var(--hm-sem-surface-highlight);
|
||||
|
||||
--hm-calendar-weekday-color: var(--hm-sem-fg-secondary);
|
||||
--hm-calendar-weekday-font-size: 12px;
|
||||
--hm-calendar-weekday-line-height: 18px;
|
||||
--hm-calendar-weekday-font-weight: 500;
|
||||
|
||||
--hm-calendar-day-size: 36px;
|
||||
--hm-calendar-day-radius: var(--hm-radius-control-md);
|
||||
--hm-calendar-day-font-size: 14px;
|
||||
--hm-calendar-day-line-height: 20px;
|
||||
--hm-calendar-day-hover-background: var(--hm-sem-surface-highlight);
|
||||
--hm-calendar-today-background: var(--hm-sem-bg-subtle);
|
||||
--hm-calendar-today-font-weight: 700;
|
||||
/* Single selected and range start / end use the Active Semantic fill; the range middle keeps the soft selection surface
|
||||
(--accent 45%, text --accent-foreground). */
|
||||
--hm-calendar-selected-background: var(--hm-sem-state-active);
|
||||
--hm-calendar-selected-text: var(--hm-sem-on-state-active);
|
||||
--hm-calendar-range-edge-background: var(--hm-sem-state-active);
|
||||
--hm-calendar-range-edge-text: var(--hm-sem-on-state-active);
|
||||
--hm-calendar-range-middle-background: var(--hm-sem-surface-highlight);
|
||||
--hm-calendar-range-middle-text: var(--accent-foreground);
|
||||
--hm-calendar-outside-color: var(--hm-sem-fg-secondary);
|
||||
--hm-calendar-outside-opacity: 0.6;
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-calendar-disabled-opacity: 0.4;
|
||||
}
|
||||
|
||||
[data-slot="calendar"] {
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
padding: var(--hm-calendar-padding);
|
||||
color: var(--hm-calendar-text);
|
||||
}
|
||||
|
||||
[data-slot="calendar"] .rdp-months {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--hm-calendar-month-gap);
|
||||
}
|
||||
[data-slot="calendar"] .rdp-month {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Caption: centered label; the nav buttons sit on both sides of the first caption row. */
|
||||
[data-slot="calendar"] .rdp-month_caption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: var(--hm-calendar-caption-height);
|
||||
font-size: var(--hm-calendar-caption-font-size);
|
||||
line-height: var(--hm-calendar-caption-line-height);
|
||||
font-weight: var(--hm-calendar-caption-font-weight);
|
||||
}
|
||||
[data-slot="calendar"] .rdp-nav {
|
||||
position: absolute;
|
||||
top: var(--hm-calendar-padding);
|
||||
left: var(--hm-calendar-padding);
|
||||
right: var(--hm-calendar-padding);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: var(--hm-calendar-caption-height);
|
||||
pointer-events: none;
|
||||
}
|
||||
[data-slot="calendar"] .rdp-button_previous,
|
||||
[data-slot="calendar"] .rdp-button_next {
|
||||
pointer-events: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--hm-calendar-nav-size);
|
||||
height: var(--hm-calendar-nav-size);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: var(--hm-calendar-nav-radius);
|
||||
background: transparent;
|
||||
color: var(--hm-calendar-text);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="calendar"] .rdp-button_previous:hover:not(:disabled),
|
||||
[data-slot="calendar"] .rdp-button_next:hover:not(:disabled) {
|
||||
background: var(--hm-calendar-nav-hover-background);
|
||||
}
|
||||
[data-slot="calendar"] .rdp-button_previous:focus-visible,
|
||||
[data-slot="calendar"] .rdp-button_next:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
[data-slot="calendar"] .rdp-button_previous:disabled,
|
||||
[data-slot="calendar"] .rdp-button_next:disabled {
|
||||
opacity: var(--hm-calendar-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
[data-slot="calendar"] .rdp-button_previous svg,
|
||||
[data-slot="calendar"] .rdp-button_next svg {
|
||||
width: var(--hm-calendar-nav-icon-size);
|
||||
height: var(--hm-calendar-nav-icon-size);
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
[data-slot="calendar"] .rdp-month_grid {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
[data-slot="calendar"] .rdp-weekday {
|
||||
width: var(--hm-calendar-day-size);
|
||||
padding: 4px 0;
|
||||
color: var(--hm-calendar-weekday-color);
|
||||
font-size: var(--hm-calendar-weekday-font-size);
|
||||
line-height: var(--hm-calendar-weekday-line-height);
|
||||
font-weight: var(--hm-calendar-weekday-font-weight);
|
||||
text-align: center;
|
||||
}
|
||||
[data-slot="calendar"] .rdp-day {
|
||||
width: var(--hm-calendar-day-size);
|
||||
height: var(--hm-calendar-day-size);
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
[data-slot="calendar"] .rdp-day_button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: var(--hm-calendar-day-radius);
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-size: var(--hm-calendar-day-font-size);
|
||||
line-height: var(--hm-calendar-day-line-height);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="calendar"] .rdp-day_button:hover:not(:disabled) {
|
||||
background: var(--hm-calendar-day-hover-background);
|
||||
}
|
||||
[data-slot="calendar"] .rdp-day_button:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Today / outside / disabled / selected, from the day cell attributes. */
|
||||
[data-slot="calendar"] .rdp-day[data-today] .rdp-day_button {
|
||||
background: var(--hm-calendar-today-background);
|
||||
font-weight: var(--hm-calendar-today-font-weight);
|
||||
}
|
||||
[data-slot="calendar"] .rdp-day[data-outside] .rdp-day_button {
|
||||
color: var(--hm-calendar-outside-color);
|
||||
opacity: var(--hm-calendar-outside-opacity);
|
||||
}
|
||||
[data-slot="calendar"] .rdp-day[data-disabled] .rdp-day_button {
|
||||
opacity: var(--hm-calendar-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
[data-slot="calendar"] .rdp-day[data-selected] .rdp-day_button,
|
||||
[data-slot="calendar"] .rdp-day[data-selected] .rdp-day_button:hover:not(:disabled) {
|
||||
background: var(--hm-calendar-selected-background);
|
||||
color: var(--hm-calendar-selected-text);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Range: start and end keep the Active fill (inner corners square); the middle uses the soft surface with square corners. */
|
||||
[data-slot="calendar"] .rdp-day.rdp-range_middle[data-selected] .rdp-day_button,
|
||||
[data-slot="calendar"] .rdp-day.rdp-range_middle[data-selected] .rdp-day_button:hover:not(:disabled) {
|
||||
background: var(--hm-calendar-range-middle-background);
|
||||
color: var(--hm-calendar-range-middle-text);
|
||||
border-radius: 0;
|
||||
}
|
||||
[data-slot="calendar"] .rdp-day.rdp-range_start[data-selected] .rdp-day_button,
|
||||
[data-slot="calendar"] .rdp-day.rdp-range_start[data-selected] .rdp-day_button:hover:not(:disabled),
|
||||
[data-slot="calendar"] .rdp-day.rdp-range_end[data-selected] .rdp-day_button,
|
||||
[data-slot="calendar"] .rdp-day.rdp-range_end[data-selected] .rdp-day_button:hover:not(:disabled) {
|
||||
background: var(--hm-calendar-range-edge-background);
|
||||
color: var(--hm-calendar-range-edge-text);
|
||||
}
|
||||
[data-slot="calendar"] .rdp-range_start .rdp-day_button {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
[data-slot="calendar"] .rdp-range_end .rdp-day_button {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/* Card mapping: ui_viz Content Card (Type B). HM spec: docs/components/card.md.
|
||||
Source refs below (content-container.md §7, §10 and content-header.md §7, §9) are external ui_viz docs/patterns/ files, not in this repo.
|
||||
ui_viz defines no Hover / Focus / Selected / Disabled, no shadow and no footer: those stay shadcn defaults or are absent. */
|
||||
:root, [data-hm-theme] {
|
||||
/* ui_viz: content-container.md §7.2 — container-radius 4px = radius.container.default — Confirmed. Not aliased to Input or Button. */
|
||||
--hm-card-radius: var(--hm-radius-container);
|
||||
/* ui_viz: content-container.md §7.2 — border 1px, body-padding 24px all sides — SOURCE_ADOPTED (Canonical pattern, Q6) */
|
||||
--hm-card-border-width: 1px;
|
||||
--hm-card-padding: 24px;
|
||||
/* ui_viz: content-container.md §7.2, §10.2 — border colors were LOCAL_EXCEPTION component values (#DEDBD2 / #ECD4A2).
|
||||
HM maps them to shared Semantic tokens: color.stroke.input.default and color.palette.brown-light-4 (see docs/components/card.md §6.1). */
|
||||
--hm-card-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-card-border-color-on-content: var(--hm-sem-palette-brown-light-4);
|
||||
/* ui_viz: content-container.md §7.2 — surface: color.bg.surface (White) or color.bg.content (Beige) — Confirmed */
|
||||
--hm-card-surface: var(--hm-sem-bg-surface);
|
||||
--hm-card-surface-content: var(--hm-sem-bg-content);
|
||||
--hm-card-text: var(--hm-sem-fg-primary);
|
||||
|
||||
/* ui_viz: content-header.md §7.1, §9 — content profile: title <-> meta gap 20px, title heading-md 20/28/600 (CANONICAL_OVERRIDE of Source 19px) */
|
||||
--hm-card-header-gap: 20px;
|
||||
--hm-card-title-font-size: 20px;
|
||||
--hm-card-title-line-height: 28px;
|
||||
--hm-card-title-font-weight: 600;
|
||||
--hm-card-title-color: var(--hm-sem-fg-primary);
|
||||
}
|
||||
|
||||
/* Default size. size="sm" is a shadcn size that ui_viz does not define, so it keeps the shadcn spacing (12px) from the TSX classes. */
|
||||
:where([data-slot="card"]:not([data-size="sm"])) {
|
||||
--card-spacing: var(--hm-card-padding);
|
||||
}
|
||||
[data-slot="card"] {
|
||||
border: var(--hm-card-border-width) solid var(--hm-card-border-color);
|
||||
border-radius: var(--hm-card-radius);
|
||||
background: var(--hm-card-surface);
|
||||
color: var(--hm-card-text);
|
||||
box-shadow: none;
|
||||
}
|
||||
[data-slot="card"][data-surface="content"] {
|
||||
border-color: var(--hm-card-border-color-on-content);
|
||||
background: var(--hm-card-surface-content);
|
||||
}
|
||||
|
||||
/* Header: title group at the start, Action (Meta) at the end, centered on the cross axis. */
|
||||
[data-slot="card-header"] {
|
||||
column-gap: var(--hm-card-header-gap);
|
||||
}
|
||||
[data-slot="card-action"] {
|
||||
align-self: center;
|
||||
}
|
||||
:where([data-slot="card"]:not([data-size="sm"])) [data-slot="card-title"] {
|
||||
font-size: var(--hm-card-title-font-size);
|
||||
line-height: var(--hm-card-title-line-height);
|
||||
font-weight: var(--hm-card-title-font-weight);
|
||||
color: var(--hm-card-title-color);
|
||||
}
|
||||
|
||||
/* Footer is not defined by ui_viz (shadcn default). Its bottom corners follow the container radius inside the border. */
|
||||
[data-slot="card-footer"] {
|
||||
border-bottom-left-radius: calc(var(--hm-card-radius) - var(--hm-card-border-width));
|
||||
border-bottom-right-radius: calc(var(--hm-card-radius) - var(--hm-card-border-width));
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/* Carousel mapping. ui-viz has no Carousel spec (it is not in the earlier inventory), so these are current HM values built from the HM
|
||||
Semantic / Primitive tokens. Embla owns the scrolling; here only the slide gap and the previous / next controls are styled.
|
||||
The controls are Carousel's own buttons (they do not use the Button component or its tokens) and sit inside the carousel edge
|
||||
(8px inset) so they are never clipped by a narrow parent. The slide width is the consumer's (`basis-*` on CarouselItem).
|
||||
Everything is in `@layer components`, so a consumer's utility classes (gap, control position, size) still win.
|
||||
Nothing here references Button / Scroll Area tokens. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-carousel-gap: 16px;
|
||||
|
||||
--hm-carousel-control-size: 32px;
|
||||
--hm-carousel-control-inset: 8px;
|
||||
--hm-carousel-control-icon-size: 16px;
|
||||
--hm-carousel-control-background: var(--hm-sem-bg-surface);
|
||||
--hm-carousel-control-text: var(--hm-sem-fg-primary);
|
||||
--hm-carousel-control-border-width: 1px;
|
||||
--hm-carousel-control-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-carousel-control-hover-background: var(--accent);
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-carousel-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Gap: the track pulls back by one gap and every slide pads it (the shadcn approach), so slides keep equal widths. */
|
||||
[data-slot="carousel"][data-orientation="horizontal"] [data-slot="carousel-track"] {
|
||||
margin-left: calc(-1 * var(--hm-carousel-gap));
|
||||
}
|
||||
[data-slot="carousel"][data-orientation="horizontal"] [data-slot="carousel-item"] {
|
||||
padding-left: var(--hm-carousel-gap);
|
||||
}
|
||||
[data-slot="carousel"][data-orientation="vertical"] [data-slot="carousel-track"] {
|
||||
margin-top: calc(-1 * var(--hm-carousel-gap));
|
||||
}
|
||||
[data-slot="carousel"][data-orientation="vertical"] [data-slot="carousel-item"] {
|
||||
padding-top: var(--hm-carousel-gap);
|
||||
}
|
||||
[data-slot="carousel-content"] {
|
||||
cursor: grab;
|
||||
}
|
||||
[data-slot="carousel-content"]:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
[data-slot="carousel-previous"],
|
||||
[data-slot="carousel-next"] {
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-carousel-control-size);
|
||||
height: var(--hm-carousel-control-size);
|
||||
padding: 0;
|
||||
border: var(--hm-carousel-control-border-width) solid var(--hm-carousel-control-border-color);
|
||||
border-radius: 9999px;
|
||||
background: var(--hm-carousel-control-background);
|
||||
color: var(--hm-carousel-control-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="carousel-previous"] svg,
|
||||
[data-slot="carousel-next"] svg {
|
||||
width: var(--hm-carousel-control-icon-size);
|
||||
height: var(--hm-carousel-control-icon-size);
|
||||
}
|
||||
[data-slot="carousel-previous"]:hover:not(:disabled),
|
||||
[data-slot="carousel-next"]:hover:not(:disabled) {
|
||||
background: var(--hm-carousel-control-hover-background);
|
||||
}
|
||||
[data-slot="carousel-previous"]:focus-visible,
|
||||
[data-slot="carousel-next"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
[data-slot="carousel-previous"]:disabled,
|
||||
[data-slot="carousel-next"]:disabled {
|
||||
opacity: var(--hm-carousel-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Position: inside the edge, centered on the cross axis (the vertical carousel turns the arrows 90 degrees). */
|
||||
[data-slot="carousel"][data-orientation="horizontal"] [data-slot="carousel-previous"] {
|
||||
left: var(--hm-carousel-control-inset);
|
||||
}
|
||||
[data-slot="carousel"][data-orientation="horizontal"] [data-slot="carousel-next"] {
|
||||
right: var(--hm-carousel-control-inset);
|
||||
}
|
||||
[data-slot="carousel"][data-orientation="vertical"] [data-slot="carousel-previous"] {
|
||||
top: var(--hm-carousel-control-inset);
|
||||
}
|
||||
[data-slot="carousel"][data-orientation="vertical"] [data-slot="carousel-next"] {
|
||||
bottom: var(--hm-carousel-control-inset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* Checkbox mapping: current HM values built from the shared Semantic tokens (ui-viz has no Checkbox spec; reference only).
|
||||
States use Radix attributes: [data-state="checked"|"indeterminate"], :hover, :focus-visible, :disabled, [aria-invalid="true"]. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-checkbox-size: 18px;
|
||||
--hm-checkbox-radius: var(--hm-radius-control-md);
|
||||
--hm-checkbox-border-width: 1px;
|
||||
--hm-checkbox-icon-size: 14px;
|
||||
--hm-checkbox-background: var(--hm-sem-bg-surface);
|
||||
--hm-checkbox-border-color: var(--hm-sem-stroke-default);
|
||||
--hm-checkbox-hover-border-color: var(--hm-sem-action-primary);
|
||||
--hm-checkbox-checked-background: var(--hm-sem-state-active);
|
||||
--hm-checkbox-checked-border-color: var(--hm-sem-state-active);
|
||||
--hm-checkbox-checked-hover-overlay: var(--hm-color-action-overlay-hover);
|
||||
--hm-checkbox-indicator-color: var(--hm-sem-on-state-active);
|
||||
--hm-checkbox-error-border-color: var(--hm-sem-action-destructive);
|
||||
}
|
||||
|
||||
[data-slot="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-checkbox-size);
|
||||
height: var(--hm-checkbox-size);
|
||||
padding: 0;
|
||||
border: var(--hm-checkbox-border-width) solid var(--hm-checkbox-border-color);
|
||||
border-radius: var(--hm-checkbox-radius);
|
||||
background-color: var(--hm-checkbox-background);
|
||||
background-image: none;
|
||||
color: var(--hm-checkbox-indicator-color);
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="checkbox"] svg { width: var(--hm-checkbox-icon-size); height: var(--hm-checkbox-icon-size); }
|
||||
|
||||
[data-slot="checkbox"]:hover:not(:disabled):not([data-state="checked"]):not([data-state="indeterminate"]) {
|
||||
border-color: var(--hm-checkbox-hover-border-color);
|
||||
}
|
||||
[data-slot="checkbox"][data-state="checked"],
|
||||
[data-slot="checkbox"][data-state="indeterminate"] {
|
||||
border-color: var(--hm-checkbox-checked-border-color);
|
||||
background-color: var(--hm-checkbox-checked-background);
|
||||
}
|
||||
[data-slot="checkbox"][data-state="checked"]:hover:not(:disabled),
|
||||
[data-slot="checkbox"][data-state="indeterminate"]:hover:not(:disabled) {
|
||||
background-image: linear-gradient(var(--hm-checkbox-checked-hover-overlay), var(--hm-checkbox-checked-hover-overlay));
|
||||
}
|
||||
/* Focus level is the shadcn ring (ui_viz global focus is TBD); keep the ring variables because the theme sets box-shadow: none. */
|
||||
[data-slot="checkbox"]:focus-visible {
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000);
|
||||
}
|
||||
[data-slot="checkbox"][aria-invalid="true"] {
|
||||
border-color: var(--hm-checkbox-error-border-color);
|
||||
}
|
||||
[data-slot="checkbox"]:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/* Collapsible mapping. ui-viz has no Collapsible spec, so these are current HM values built from the HM Semantic tokens.
|
||||
Collapsible is one independent disclosure: a trigger and its content, with no divider system. A plain trigger (no asChild) gets
|
||||
a text-button look; with asChild the child (for example an HM Button) keeps its own style. Radix attributes drive the states.
|
||||
No --hm-accordion-* token is referenced. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-collapsible-trigger-text: var(--hm-sem-fg-primary);
|
||||
--hm-collapsible-trigger-gap: 8px;
|
||||
--hm-collapsible-trigger-font-size: 14px;
|
||||
--hm-collapsible-trigger-line-height: 20px;
|
||||
--hm-collapsible-trigger-font-weight: 500;
|
||||
--hm-collapsible-icon-size: 16px;
|
||||
--hm-collapsible-icon-color: var(--hm-sem-fg-secondary);
|
||||
|
||||
--hm-collapsible-content-gap: 8px;
|
||||
--hm-collapsible-content-text: var(--hm-sem-fg-primary);
|
||||
--hm-collapsible-content-font-size: 14px;
|
||||
--hm-collapsible-content-line-height: 20px;
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-collapsible-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-slot="collapsible-trigger"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--hm-collapsible-trigger-gap);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--hm-collapsible-trigger-text);
|
||||
font-size: var(--hm-collapsible-trigger-font-size);
|
||||
line-height: var(--hm-collapsible-trigger-line-height);
|
||||
font-weight: var(--hm-collapsible-trigger-font-weight);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="collapsible-trigger"]:hover:not(:disabled) {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
[data-slot="collapsible-trigger"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
[data-slot="collapsible-trigger"]:disabled {
|
||||
opacity: var(--hm-collapsible-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
/* An icon placed directly in a plain trigger follows the open state. */
|
||||
[data-slot="collapsible-trigger"] > svg {
|
||||
width: var(--hm-collapsible-icon-size);
|
||||
height: var(--hm-collapsible-icon-size);
|
||||
color: var(--hm-collapsible-icon-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
[data-slot="collapsible-trigger"][data-state="open"] > svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
[data-slot="collapsible-content"] {
|
||||
padding-top: var(--hm-collapsible-content-gap);
|
||||
color: var(--hm-collapsible-content-text);
|
||||
font-size: var(--hm-collapsible-content-font-size);
|
||||
line-height: var(--hm-collapsible-content-line-height);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/* Combobox mapping. ui-viz has no Combobox spec (it has a Select and a Search Field, both different components), so these are current
|
||||
HM values built from the HM Semantic / Primitive tokens. Combobox is a composition (Popover + Command): the popup surface comes
|
||||
from Popover, the search input and list from Command. Scoped popup overrides align the selection language with Select without
|
||||
changing either shared component. The popup is as wide as the trigger (an inline style sets its width / padding).
|
||||
The trigger look is in `@layer components` so a consumer's utility classes (width, height) still win. Nothing here references
|
||||
Button / Input / Select / Popover / Command tokens. */
|
||||
:root, [data-hm-theme] {
|
||||
/* Inner heights; the 1px border is added around them (outer 34 / 44), like the Select trigger. */
|
||||
--hm-combobox-height-md: 32px;
|
||||
--hm-combobox-height-lg: 42px;
|
||||
--hm-combobox-border-width: 1px;
|
||||
--hm-combobox-padding-x: 12px;
|
||||
--hm-combobox-gap: 12px;
|
||||
--hm-combobox-radius: var(--hm-radius-control-md);
|
||||
--hm-combobox-font-size-md: 14px;
|
||||
--hm-combobox-line-height-md: 20px;
|
||||
--hm-combobox-font-size-lg: 16px;
|
||||
--hm-combobox-line-height-lg: 24px;
|
||||
|
||||
--hm-combobox-background: var(--hm-sem-bg-surface);
|
||||
--hm-combobox-text: var(--hm-sem-fg-primary);
|
||||
--hm-combobox-placeholder: var(--hm-sem-fg-placeholder);
|
||||
--hm-combobox-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-combobox-focus-border-color: var(--hm-sem-stroke-input-focus);
|
||||
--hm-combobox-seam-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-combobox-icon-size: 18px;
|
||||
--hm-combobox-icon-color: var(--hm-sem-fg-secondary);
|
||||
--hm-combobox-check-size: 16px;
|
||||
|
||||
--hm-combobox-popup-radius: var(--hm-radius-input);
|
||||
--hm-combobox-popup-shadow: 0 2px 24px color-mix(in srgb, var(--hm-sem-palette-black) 12%, transparent);
|
||||
--hm-combobox-option-state-background: var(--hm-sem-surface-highlight);
|
||||
--hm-combobox-option-state-text: var(--accent-foreground);
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-combobox-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Share the connecting border via sideOffset=-1; only the seam uses the neutral stroke. */
|
||||
[data-slot="popover-content"][data-combobox-popup] {
|
||||
border-color: var(--hm-combobox-focus-border-color);
|
||||
border-radius: var(--hm-combobox-popup-radius);
|
||||
box-shadow: var(--hm-combobox-popup-shadow);
|
||||
--hm-combobox-option-font-size: var(--hm-combobox-font-size-lg);
|
||||
--hm-combobox-option-line-height: var(--hm-combobox-line-height-lg);
|
||||
}
|
||||
[data-slot="popover-content"][data-combobox-popup][data-side="bottom"] {
|
||||
border-radius: 0 0 var(--hm-combobox-popup-radius) var(--hm-combobox-popup-radius);
|
||||
border-top-color: var(--hm-combobox-seam-color);
|
||||
}
|
||||
[data-slot="popover-content"][data-combobox-popup][data-side="top"] {
|
||||
border-radius: var(--hm-combobox-popup-radius) var(--hm-combobox-popup-radius) 0 0;
|
||||
border-bottom-color: var(--hm-combobox-seam-color);
|
||||
}
|
||||
[data-combobox-popup][data-size="medium"] {
|
||||
--hm-combobox-option-font-size: var(--hm-combobox-font-size-md);
|
||||
--hm-combobox-option-line-height: var(--hm-combobox-line-height-md);
|
||||
}
|
||||
[data-combobox-popup] [data-slot="command"] {
|
||||
border-radius: inherit;
|
||||
}
|
||||
[data-combobox-popup] [data-slot="command-item"] {
|
||||
/* Retain Command's 36px height, 12px horizontal padding and search layout. */
|
||||
border-radius: 0;
|
||||
font-size: var(--hm-combobox-option-font-size);
|
||||
line-height: var(--hm-combobox-option-line-height);
|
||||
font-weight: 400;
|
||||
}
|
||||
[data-combobox-popup] [data-slot="command-item"][data-selected="true"],
|
||||
[data-combobox-popup] [data-slot="command-item"][data-checked] {
|
||||
background: var(--hm-combobox-option-state-background);
|
||||
color: var(--hm-combobox-option-state-text);
|
||||
}
|
||||
|
||||
@layer components {
|
||||
[data-slot="combobox-trigger"] {
|
||||
--hm-combobox-height: var(--hm-combobox-height-lg);
|
||||
--hm-combobox-font-size: var(--hm-combobox-font-size-lg);
|
||||
--hm-combobox-line-height: var(--hm-combobox-line-height-lg);
|
||||
box-sizing: border-box;
|
||||
min-width: 0; /* lets a long value truncate instead of widening a grid / flex parent */
|
||||
max-width: 100%;
|
||||
min-height: calc(var(--hm-combobox-height) + 2 * var(--hm-combobox-border-width));
|
||||
gap: var(--hm-combobox-gap);
|
||||
padding: 0 var(--hm-combobox-padding-x);
|
||||
border: var(--hm-combobox-border-width) solid var(--hm-combobox-border-color);
|
||||
border-radius: var(--hm-combobox-radius);
|
||||
background: var(--hm-combobox-background);
|
||||
color: var(--hm-combobox-text);
|
||||
font-size: var(--hm-combobox-font-size);
|
||||
line-height: var(--hm-combobox-line-height);
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="combobox-trigger"][data-size="medium"] {
|
||||
--hm-combobox-height: var(--hm-combobox-height-md);
|
||||
--hm-combobox-font-size: var(--hm-combobox-font-size-md);
|
||||
--hm-combobox-line-height: var(--hm-combobox-line-height-md);
|
||||
}
|
||||
[data-slot="combobox-trigger"][data-empty] [data-slot="combobox-value"] {
|
||||
color: var(--hm-combobox-placeholder);
|
||||
}
|
||||
[data-slot="combobox-value"] {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[data-slot="combobox-icon"] {
|
||||
flex-shrink: 0;
|
||||
width: var(--hm-combobox-icon-size);
|
||||
height: var(--hm-combobox-icon-size);
|
||||
color: var(--hm-combobox-icon-color);
|
||||
}
|
||||
[data-slot="combobox-trigger"]:focus-visible,
|
||||
[data-slot="combobox-trigger"][data-state="open"] {
|
||||
border-color: var(--hm-combobox-focus-border-color);
|
||||
}
|
||||
[data-slot="combobox-trigger"][data-state="open"][data-side="bottom"] {
|
||||
border-radius: var(--hm-combobox-radius) var(--hm-combobox-radius) 0 0;
|
||||
border-bottom-color: var(--hm-combobox-seam-color);
|
||||
}
|
||||
[data-slot="combobox-trigger"][data-state="open"][data-side="top"] {
|
||||
border-radius: 0 0 var(--hm-combobox-radius) var(--hm-combobox-radius);
|
||||
border-top-color: var(--hm-combobox-seam-color);
|
||||
}
|
||||
[data-slot="combobox-trigger"]:disabled {
|
||||
opacity: var(--hm-combobox-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Items inside the popup: a long label is truncated and the check stays at the end. */
|
||||
[data-slot="combobox-item-label"] {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[data-slot="combobox-item-check"] {
|
||||
margin-left: auto;
|
||||
width: var(--hm-combobox-check-size);
|
||||
height: var(--hm-combobox-check-size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/* Command mapping. ui-viz has no Command spec (it has a Search Field / candidate list, a different component), so these are current
|
||||
HM values built from the HM Semantic / Primitive tokens. cmdk keeps filtering, keyboard and ARIA; the highlighted item is
|
||||
[data-selected="true"], a disabled one [data-disabled="true"]. The Command draws its own surface (white, radius) but no border or
|
||||
shadow: it is meant to sit inside a container (Popover, Dialog) or a bordered box. Nothing references Input / Popover /
|
||||
Dropdown Menu tokens. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-command-background: var(--hm-sem-bg-surface);
|
||||
--hm-command-text: var(--hm-sem-fg-primary);
|
||||
--hm-command-radius: var(--hm-radius-container);
|
||||
|
||||
--hm-command-input-height: 40px;
|
||||
--hm-command-input-padding-x: 12px;
|
||||
--hm-command-input-gap: 8px;
|
||||
--hm-command-input-font-size: 14px;
|
||||
--hm-command-input-line-height: 20px;
|
||||
--hm-command-input-placeholder: var(--hm-sem-fg-placeholder);
|
||||
--hm-command-input-icon-size: 16px;
|
||||
--hm-command-input-icon-color: var(--hm-sem-fg-secondary);
|
||||
--hm-command-divider-color: var(--hm-sem-stroke-input-default);
|
||||
|
||||
--hm-command-list-max-height: 280px;
|
||||
--hm-command-list-padding: 4px;
|
||||
|
||||
--hm-command-item-height: 36px;
|
||||
--hm-command-item-padding-x: 12px;
|
||||
--hm-command-item-gap: 8px;
|
||||
--hm-command-item-radius: 2px;
|
||||
--hm-command-item-font-size: 14px;
|
||||
--hm-command-item-line-height: 20px;
|
||||
--hm-command-highlight-background: var(--hm-sem-surface-highlight);
|
||||
--hm-command-highlight-text: var(--accent-foreground);
|
||||
|
||||
--hm-command-group-label-color: var(--hm-sem-fg-secondary);
|
||||
--hm-command-group-label-font-size: 12px;
|
||||
--hm-command-group-label-line-height: 18px;
|
||||
--hm-command-group-label-font-weight: 500;
|
||||
--hm-command-shortcut-color: var(--hm-sem-fg-secondary);
|
||||
--hm-command-shortcut-font-size: 12px;
|
||||
|
||||
--hm-command-empty-padding-y: 24px;
|
||||
--hm-command-empty-color: var(--hm-sem-fg-secondary);
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-command-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-slot="command"] {
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--hm-command-radius);
|
||||
background: var(--hm-command-background);
|
||||
color: var(--hm-command-text);
|
||||
}
|
||||
|
||||
[data-slot="command-input-wrapper"] {
|
||||
box-sizing: border-box;
|
||||
height: var(--hm-command-input-height);
|
||||
gap: var(--hm-command-input-gap);
|
||||
padding: 0 var(--hm-command-input-padding-x);
|
||||
border-bottom: 1px solid var(--hm-command-divider-color);
|
||||
}
|
||||
[data-slot="command-input-wrapper"] > svg {
|
||||
width: var(--hm-command-input-icon-size);
|
||||
height: var(--hm-command-input-icon-size);
|
||||
color: var(--hm-command-input-icon-color);
|
||||
}
|
||||
[data-slot="command-input"] {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-size: var(--hm-command-input-font-size);
|
||||
line-height: var(--hm-command-input-line-height);
|
||||
}
|
||||
[data-slot="command-input"]::placeholder {
|
||||
color: var(--hm-command-input-placeholder);
|
||||
}
|
||||
|
||||
[data-slot="command-list"] {
|
||||
max-height: var(--hm-command-list-max-height);
|
||||
padding: var(--hm-command-list-padding);
|
||||
}
|
||||
[data-slot="command-empty"] {
|
||||
padding: var(--hm-command-empty-padding-y) 12px;
|
||||
color: var(--hm-command-empty-color);
|
||||
font-size: var(--hm-command-item-font-size);
|
||||
line-height: var(--hm-command-item-line-height);
|
||||
text-align: center;
|
||||
}
|
||||
[data-slot="command-separator"] {
|
||||
margin: var(--hm-command-list-padding) calc(-1 * var(--hm-command-list-padding));
|
||||
background: var(--hm-command-divider-color);
|
||||
}
|
||||
|
||||
[data-slot="command-group"] [cmdk-group-heading] {
|
||||
padding: 6px var(--hm-command-item-padding-x);
|
||||
color: var(--hm-command-group-label-color);
|
||||
font-size: var(--hm-command-group-label-font-size);
|
||||
line-height: var(--hm-command-group-label-line-height);
|
||||
font-weight: var(--hm-command-group-label-font-weight);
|
||||
}
|
||||
|
||||
[data-slot="command-item"] {
|
||||
box-sizing: border-box;
|
||||
min-height: var(--hm-command-item-height);
|
||||
gap: var(--hm-command-item-gap);
|
||||
padding: 0 var(--hm-command-item-padding-x);
|
||||
border-radius: var(--hm-command-item-radius);
|
||||
font-size: var(--hm-command-item-font-size);
|
||||
line-height: var(--hm-command-item-line-height);
|
||||
}
|
||||
/* Highlighted: pointer hover and keyboard focus share cmdk's data-selected. */
|
||||
[data-slot="command-item"][data-selected="true"] {
|
||||
background: var(--hm-command-highlight-background);
|
||||
color: var(--hm-command-highlight-text);
|
||||
}
|
||||
[data-slot="command-item"][data-disabled="true"] {
|
||||
opacity: var(--hm-command-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
[data-slot="command-shortcut"] {
|
||||
color: var(--hm-command-shortcut-color);
|
||||
font-size: var(--hm-command-shortcut-font-size);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
[data-slot="command-item"][data-selected="true"] [data-slot="command-shortcut"] {
|
||||
color: inherit;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/* Context Menu mapping. Same anchored floating menu family as Dropdown Menu, opened by pointer context (right click / context key / long press).
|
||||
Surface values come from the Floating Semantic (--hm-sem-floating-*); item and text values are current HM values. Radix state attributes
|
||||
(data-highlighted, data-disabled, data-state) drive the states. Tokens are this component's own and never reference --hm-dropdown-menu-*. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-context-menu-background: var(--hm-sem-floating-background);
|
||||
--hm-context-menu-text: var(--hm-sem-fg-primary);
|
||||
--hm-context-menu-border-width: var(--hm-sem-floating-border-width);
|
||||
--hm-context-menu-border-color: var(--hm-sem-floating-border-color);
|
||||
--hm-context-menu-radius: var(--hm-sem-floating-radius);
|
||||
--hm-context-menu-shadow: var(--hm-sem-floating-shadow);
|
||||
--hm-context-menu-padding: 4px;
|
||||
|
||||
--hm-context-menu-item-height: 36px;
|
||||
--hm-context-menu-item-padding-x: 12px;
|
||||
--hm-context-menu-item-gap: 8px;
|
||||
--hm-context-menu-item-radius: 2px;
|
||||
--hm-context-menu-font-size: 14px;
|
||||
--hm-context-menu-line-height: 20px;
|
||||
--hm-context-menu-font-weight: 400;
|
||||
|
||||
/* Highlighted (hover and keyboard) uses the same face as the Select option: the shadcn accent. */
|
||||
--hm-context-menu-highlight-background: var(--hm-sem-surface-highlight);
|
||||
--hm-context-menu-highlight-text: var(--accent-foreground);
|
||||
--hm-context-menu-destructive-text: var(--hm-sem-action-destructive);
|
||||
--hm-context-menu-destructive-highlight-background: var(--hm-color-action-destructive-hover);
|
||||
|
||||
--hm-context-menu-label-color: var(--hm-sem-fg-secondary);
|
||||
--hm-context-menu-label-font-size: 12px;
|
||||
--hm-context-menu-label-line-height: 18px;
|
||||
--hm-context-menu-label-font-weight: 500;
|
||||
--hm-context-menu-shortcut-color: var(--hm-sem-fg-secondary);
|
||||
--hm-context-menu-shortcut-font-size: 12px;
|
||||
--hm-context-menu-separator-color: var(--hm-sem-stroke-input-default);
|
||||
|
||||
/* Check / radio indicator column at the start of the item. */
|
||||
--hm-context-menu-indicator-size: 16px;
|
||||
--hm-context-menu-radio-dot-size: 8px;
|
||||
}
|
||||
|
||||
[data-slot="context-menu-content"],
|
||||
[data-slot="context-menu-sub-content"] {
|
||||
box-sizing: border-box;
|
||||
padding: var(--hm-context-menu-padding);
|
||||
border: var(--hm-context-menu-border-width) solid var(--hm-context-menu-border-color);
|
||||
border-radius: var(--hm-context-menu-radius);
|
||||
background: var(--hm-context-menu-background);
|
||||
color: var(--hm-context-menu-text);
|
||||
box-shadow: var(--hm-context-menu-shadow);
|
||||
font-size: var(--hm-context-menu-font-size);
|
||||
line-height: var(--hm-context-menu-line-height);
|
||||
font-weight: var(--hm-context-menu-font-weight);
|
||||
}
|
||||
|
||||
[data-slot="context-menu-item"],
|
||||
[data-slot="context-menu-checkbox-item"],
|
||||
[data-slot="context-menu-radio-item"],
|
||||
[data-slot="context-menu-sub-trigger"] {
|
||||
box-sizing: border-box;
|
||||
min-height: var(--hm-context-menu-item-height);
|
||||
gap: var(--hm-context-menu-item-gap);
|
||||
padding: 0 var(--hm-context-menu-item-padding-x);
|
||||
border-radius: var(--hm-context-menu-item-radius);
|
||||
}
|
||||
/* Checkbox / radio items reserve the indicator column; `inset` aligns plain items to the same text start. */
|
||||
[data-slot="context-menu-checkbox-item"],
|
||||
[data-slot="context-menu-radio-item"],
|
||||
[data-slot="context-menu-item"][data-inset],
|
||||
[data-slot="context-menu-sub-trigger"][data-inset] {
|
||||
padding-left: calc(var(--hm-context-menu-item-padding-x) + var(--hm-context-menu-indicator-size) + var(--hm-context-menu-item-gap));
|
||||
}
|
||||
[data-slot="context-menu-item-indicator"] {
|
||||
left: var(--hm-context-menu-item-padding-x);
|
||||
width: var(--hm-context-menu-indicator-size);
|
||||
height: var(--hm-context-menu-indicator-size);
|
||||
}
|
||||
[data-slot="context-menu-radio-dot"] {
|
||||
width: var(--hm-context-menu-radio-dot-size);
|
||||
height: var(--hm-context-menu-radio-dot-size);
|
||||
}
|
||||
|
||||
/* Highlighted: Radix sets data-highlighted for pointer hover and keyboard focus alike. An open sub menu keeps its trigger highlighted. */
|
||||
[data-slot="context-menu-item"][data-highlighted],
|
||||
[data-slot="context-menu-checkbox-item"][data-highlighted],
|
||||
[data-slot="context-menu-radio-item"][data-highlighted],
|
||||
[data-slot="context-menu-sub-trigger"][data-highlighted],
|
||||
[data-slot="context-menu-sub-trigger"][data-state="open"] {
|
||||
background: var(--hm-context-menu-highlight-background);
|
||||
color: var(--hm-context-menu-highlight-text);
|
||||
}
|
||||
[data-slot="context-menu-item"][data-variant="destructive"] {
|
||||
color: var(--hm-context-menu-destructive-text);
|
||||
}
|
||||
[data-slot="context-menu-item"][data-variant="destructive"][data-highlighted] {
|
||||
background: var(--hm-context-menu-destructive-highlight-background);
|
||||
color: var(--hm-context-menu-destructive-text);
|
||||
}
|
||||
|
||||
[data-slot="context-menu-label"] {
|
||||
padding: 6px var(--hm-context-menu-item-padding-x);
|
||||
color: var(--hm-context-menu-label-color);
|
||||
font-size: var(--hm-context-menu-label-font-size);
|
||||
line-height: var(--hm-context-menu-label-line-height);
|
||||
font-weight: var(--hm-context-menu-label-font-weight);
|
||||
}
|
||||
[data-slot="context-menu-label"][data-inset] {
|
||||
padding-left: calc(var(--hm-context-menu-item-padding-x) + var(--hm-context-menu-indicator-size) + var(--hm-context-menu-item-gap));
|
||||
}
|
||||
[data-slot="context-menu-separator"] {
|
||||
margin: var(--hm-context-menu-padding) calc(-1 * var(--hm-context-menu-padding));
|
||||
background: var(--hm-context-menu-separator-color);
|
||||
}
|
||||
[data-slot="context-menu-shortcut"] {
|
||||
color: var(--hm-context-menu-shortcut-color);
|
||||
font-size: var(--hm-context-menu-shortcut-font-size);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
[data-slot="context-menu-item"][data-highlighted] [data-slot="context-menu-shortcut"],
|
||||
[data-slot="context-menu-checkbox-item"][data-highlighted] [data-slot="context-menu-shortcut"],
|
||||
[data-slot="context-menu-radio-item"][data-highlighted] [data-slot="context-menu-shortcut"] {
|
||||
color: inherit;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { runtimeThemeDefaults } from "./default-values.generated"
|
||||
|
||||
/** Public Runtime Theme contract v1. Component mappings and derived CSS are private. */
|
||||
export const runtimeThemeContract = {
|
||||
"color.bg.content": { property: "--hm-sem-bg-content", defaultValue: runtimeThemeDefaults["--hm-sem-bg-content"], type: "color" },
|
||||
"color.bg.surface": { property: "--hm-sem-bg-surface", defaultValue: runtimeThemeDefaults["--hm-sem-bg-surface"], type: "color" },
|
||||
"color.fg.primary": { property: "--hm-sem-fg-primary", defaultValue: runtimeThemeDefaults["--hm-sem-fg-primary"], type: "color" },
|
||||
"color.fg.secondary": { property: "--hm-sem-fg-secondary", defaultValue: runtimeThemeDefaults["--hm-sem-fg-secondary"], type: "color" },
|
||||
"color.fg.placeholder": { property: "--hm-sem-fg-placeholder", defaultValue: runtimeThemeDefaults["--hm-sem-fg-placeholder"], type: "color" },
|
||||
"color.action.primary": { property: "--hm-sem-action-primary", defaultValue: runtimeThemeDefaults["--hm-sem-action-primary"], type: "color" },
|
||||
"color.action.cancel": { property: "--hm-sem-action-cancel", defaultValue: runtimeThemeDefaults["--hm-sem-action-cancel"], type: "color" },
|
||||
"color.action.destructive": { property: "--hm-sem-action-destructive", defaultValue: runtimeThemeDefaults["--hm-sem-action-destructive"], type: "color" },
|
||||
"color.stroke.default": { property: "--hm-sem-stroke-default", defaultValue: runtimeThemeDefaults["--hm-sem-stroke-default"], type: "color" },
|
||||
"color.stroke.input.default": { property: "--hm-sem-stroke-input-default", defaultValue: runtimeThemeDefaults["--hm-sem-stroke-input-default"], type: "color" },
|
||||
"color.stroke.input.focus": { property: "--hm-sem-stroke-input-focus", defaultValue: runtimeThemeDefaults["--hm-sem-stroke-input-focus"], type: "color" },
|
||||
"color.bg.brand.stop.0": { property: "--hm-sem-brand-bg-stop-0", defaultValue: runtimeThemeDefaults["--hm-sem-brand-bg-stop-0"], type: "color" },
|
||||
"color.bg.brand.stop.12": { property: "--hm-sem-brand-bg-stop-12", defaultValue: runtimeThemeDefaults["--hm-sem-brand-bg-stop-12"], type: "color" },
|
||||
"color.bg.brand.stop.42": { property: "--hm-sem-brand-bg-stop-42", defaultValue: runtimeThemeDefaults["--hm-sem-brand-bg-stop-42"], type: "color" },
|
||||
"color.bg.brand.stop.78": { property: "--hm-sem-brand-bg-stop-78", defaultValue: runtimeThemeDefaults["--hm-sem-brand-bg-stop-78"], type: "color" },
|
||||
"color.bg.brand.stop.100": { property: "--hm-sem-brand-bg-stop-100", defaultValue: runtimeThemeDefaults["--hm-sem-brand-bg-stop-100"], type: "color" },
|
||||
"surface.action.primary.stop.0": { property: "--hm-sem-action-surface-stop-0", defaultValue: runtimeThemeDefaults["--hm-sem-action-surface-stop-0"], type: "color" },
|
||||
"surface.action.primary.stop.30": { property: "--hm-sem-action-surface-stop-30", defaultValue: runtimeThemeDefaults["--hm-sem-action-surface-stop-30"], type: "color" },
|
||||
"surface.action.primary.stop.65": { property: "--hm-sem-action-surface-stop-65", defaultValue: runtimeThemeDefaults["--hm-sem-action-surface-stop-65"], type: "color" },
|
||||
"surface.action.primary.stop.100": { property: "--hm-sem-action-surface-stop-100", defaultValue: runtimeThemeDefaults["--hm-sem-action-surface-stop-100"], type: "color" },
|
||||
"surface.action.primary.border.stop.0": { property: "--hm-sem-action-border-stop-0", defaultValue: runtimeThemeDefaults["--hm-sem-action-border-stop-0"], type: "color" },
|
||||
"surface.action.primary.border.stop.50": { property: "--hm-sem-action-border-stop-50", defaultValue: runtimeThemeDefaults["--hm-sem-action-border-stop-50"], type: "color" },
|
||||
"surface.action.primary.border.stop.100": { property: "--hm-sem-action-border-stop-100", defaultValue: runtimeThemeDefaults["--hm-sem-action-border-stop-100"], type: "color" },
|
||||
"radius.input.default": { property: "--hm-radius-input", defaultValue: runtimeThemeDefaults["--hm-radius-input"], type: "radius" },
|
||||
"radius.container.default": { property: "--hm-radius-container", defaultValue: runtimeThemeDefaults["--hm-radius-container"], type: "radius" },
|
||||
"color.state.active": { property: "--hm-sem-state-active", defaultValue: runtimeThemeDefaults["--hm-sem-state-active"], type: "color" },
|
||||
"color.on.state.active": { property: "--hm-sem-on-state-active", defaultValue: runtimeThemeDefaults["--hm-sem-on-state-active"], type: "color" },
|
||||
"color.fg.inverse": { property: "--hm-sem-on-action-primary", defaultValue: runtimeThemeDefaults["--hm-sem-on-action-primary"], type: "color" },
|
||||
"color.bg.subtle": { property: "--hm-sem-bg-subtle", defaultValue: runtimeThemeDefaults["--hm-sem-bg-subtle"], type: "color" },
|
||||
"color.accent": { property: "--hm-sem-accent", defaultValue: runtimeThemeDefaults["--hm-sem-accent"], type: "color" },
|
||||
"color.on.accent": { property: "--hm-sem-on-accent", defaultValue: runtimeThemeDefaults["--hm-sem-on-accent"], type: "color" },
|
||||
"color.palette.green-light-4": { property: "--hm-sem-palette-green-light-4", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-light-4"], type: "color" },
|
||||
"color.palette.green-light-3": { property: "--hm-sem-palette-green-light-3", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-light-3"], type: "color" },
|
||||
"color.palette.green-light-2": { property: "--hm-sem-palette-green-light-2", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-light-2"], type: "color" },
|
||||
"color.palette.green-light-1": { property: "--hm-sem-palette-green-light-1", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-light-1"], type: "color" },
|
||||
"color.palette.green-default": { property: "--hm-sem-palette-green-default", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-default"], type: "color" },
|
||||
"color.palette.green-dark-1": { property: "--hm-sem-palette-green-dark-1", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-dark-1"], type: "color" },
|
||||
"color.palette.green-dark-2": { property: "--hm-sem-palette-green-dark-2", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-dark-2"], type: "color" },
|
||||
"color.palette.brown-light-2": { property: "--hm-sem-palette-brown-light-2", defaultValue: runtimeThemeDefaults["--hm-sem-palette-brown-light-2"], type: "color" },
|
||||
"color.palette.brown-light-1": { property: "--hm-sem-palette-brown-light-1", defaultValue: runtimeThemeDefaults["--hm-sem-palette-brown-light-1"], type: "color" },
|
||||
"color.palette.brown-default": { property: "--hm-sem-palette-brown-default", defaultValue: runtimeThemeDefaults["--hm-sem-palette-brown-default"], type: "color" },
|
||||
"color.palette.brown-dark-1": { property: "--hm-sem-palette-brown-dark-1", defaultValue: runtimeThemeDefaults["--hm-sem-palette-brown-dark-1"], type: "color" },
|
||||
"color.palette.brown-light-4": { property: "--hm-sem-palette-brown-light-4", defaultValue: runtimeThemeDefaults["--hm-sem-palette-brown-light-4"], type: "color" },
|
||||
"color.palette.orange-light-4": { property: "--hm-sem-palette-orange-light-4", defaultValue: runtimeThemeDefaults["--hm-sem-palette-orange-light-4"], type: "color" },
|
||||
"color.palette.orange-default": { property: "--hm-sem-palette-orange-default", defaultValue: runtimeThemeDefaults["--hm-sem-palette-orange-default"], type: "color" },
|
||||
"color.palette.warm-neutral-light": { property: "--hm-sem-palette-warm-neutral-light", defaultValue: runtimeThemeDefaults["--hm-sem-palette-warm-neutral-light"], type: "color" },
|
||||
"color.palette.warm-neutral-mid": { property: "--hm-sem-palette-warm-neutral-mid", defaultValue: runtimeThemeDefaults["--hm-sem-palette-warm-neutral-mid"], type: "color" },
|
||||
"color.palette.warm-neutral-dark": { property: "--hm-sem-palette-warm-neutral-dark", defaultValue: runtimeThemeDefaults["--hm-sem-palette-warm-neutral-dark"], type: "color" },
|
||||
"color.palette.beige-light": { property: "--hm-sem-palette-beige-light", defaultValue: runtimeThemeDefaults["--hm-sem-palette-beige-light"], type: "color" },
|
||||
"color.palette.red-danger": { property: "--hm-sem-palette-red-danger", defaultValue: runtimeThemeDefaults["--hm-sem-palette-red-danger"], type: "color" },
|
||||
"color.palette.white": { property: "--hm-sem-palette-white", defaultValue: runtimeThemeDefaults["--hm-sem-palette-white"], type: "color" },
|
||||
"color.palette.black": { property: "--hm-sem-palette-black", defaultValue: runtimeThemeDefaults["--hm-sem-palette-black"], type: "color" },
|
||||
"color.palette.neutral-gray-500": { property: "--hm-sem-palette-neutral-gray-500", defaultValue: runtimeThemeDefaults["--hm-sem-palette-neutral-gray-500"], type: "color" },
|
||||
"color.palette.green-tone-1": { property: "--hm-sem-palette-green-tone-1", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-tone-1"], type: "color" },
|
||||
"color.palette.green-tone-2": { property: "--hm-sem-palette-green-tone-2", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-tone-2"], type: "color" },
|
||||
"color.palette.green-tone-3": { property: "--hm-sem-palette-green-tone-3", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-tone-3"], type: "color" },
|
||||
"color.palette.green-tone-4": { property: "--hm-sem-palette-green-tone-4", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-tone-4"], type: "color" },
|
||||
"color.palette.warm-neutral-pale": { property: "--hm-sem-palette-warm-neutral-pale", defaultValue: runtimeThemeDefaults["--hm-sem-palette-warm-neutral-pale"], type: "color" },
|
||||
"color.palette.warm-neutral-secondary": { property: "--hm-sem-palette-warm-neutral-secondary", defaultValue: runtimeThemeDefaults["--hm-sem-palette-warm-neutral-secondary"], type: "color" },
|
||||
"color.palette.warm-neutral-muted": { property: "--hm-sem-palette-warm-neutral-muted", defaultValue: runtimeThemeDefaults["--hm-sem-palette-warm-neutral-muted"], type: "color" },
|
||||
"color.palette.green-tint": { property: "--hm-sem-palette-green-tint", defaultValue: runtimeThemeDefaults["--hm-sem-palette-green-tint"], type: "color" },
|
||||
"color.palette.yellow-accent": { property: "--hm-sem-palette-yellow-accent", defaultValue: runtimeThemeDefaults["--hm-sem-palette-yellow-accent"], type: "color" },
|
||||
"color.palette.brown-deep": { property: "--hm-sem-palette-brown-deep", defaultValue: runtimeThemeDefaults["--hm-sem-palette-brown-deep"], type: "color" },
|
||||
"color.palette.brown-highlight": { property: "--hm-sem-palette-brown-highlight", defaultValue: runtimeThemeDefaults["--hm-sem-palette-brown-highlight"], type: "color" },
|
||||
"color.palette.orange-light": { property: "--hm-sem-palette-orange-light", defaultValue: runtimeThemeDefaults["--hm-sem-palette-orange-light"], type: "color" },
|
||||
"color.palette.orange-mid": { property: "--hm-sem-palette-orange-mid", defaultValue: runtimeThemeDefaults["--hm-sem-palette-orange-mid"], type: "color" },
|
||||
"color.palette.orange-dark": { property: "--hm-sem-palette-orange-dark", defaultValue: runtimeThemeDefaults["--hm-sem-palette-orange-dark"], type: "color" },
|
||||
"color.palette.orange-deep": { property: "--hm-sem-palette-orange-deep", defaultValue: runtimeThemeDefaults["--hm-sem-palette-orange-deep"], type: "color" },
|
||||
"color.palette.orange-border": { property: "--hm-sem-palette-orange-border", defaultValue: runtimeThemeDefaults["--hm-sem-palette-orange-border"], type: "color" },
|
||||
"color.palette.orange-border-deep": { property: "--hm-sem-palette-orange-border-deep", defaultValue: runtimeThemeDefaults["--hm-sem-palette-orange-border-deep"], type: "color" },
|
||||
"color.palette.gray": { property: "--hm-sem-palette-gray", defaultValue: runtimeThemeDefaults["--hm-sem-palette-gray"], type: "color" },
|
||||
"color.palette.gray-dark": { property: "--hm-sem-palette-gray-dark", defaultValue: runtimeThemeDefaults["--hm-sem-palette-gray-dark"], type: "color" },
|
||||
"color.palette.indicator": { property: "--hm-sem-palette-indicator", defaultValue: runtimeThemeDefaults["--hm-sem-palette-indicator"], type: "color" },
|
||||
} as const
|
||||
|
||||
export type RuntimeThemeToken = keyof typeof runtimeThemeContract
|
||||
export type RuntimeTheme = { schemaVersion: 1; tokens: Partial<Record<RuntimeThemeToken, string>> }
|
||||
export const defaultRuntimeTheme: RuntimeTheme = {
|
||||
schemaVersion: 1,
|
||||
tokens: Object.fromEntries(Object.entries(runtimeThemeContract).map(([key, value]) => [key, value.defaultValue])),
|
||||
}
|
||||
|
||||
export const runtimeGradientGroups = [
|
||||
[
|
||||
"color.bg.brand.stop.0",
|
||||
"color.bg.brand.stop.12",
|
||||
"color.bg.brand.stop.42",
|
||||
"color.bg.brand.stop.78",
|
||||
"color.bg.brand.stop.100"
|
||||
],
|
||||
[
|
||||
"surface.action.primary.stop.0",
|
||||
"surface.action.primary.stop.30",
|
||||
"surface.action.primary.stop.65",
|
||||
"surface.action.primary.stop.100"
|
||||
],
|
||||
[
|
||||
"surface.action.primary.border.stop.0",
|
||||
"surface.action.primary.border.stop.50",
|
||||
"surface.action.primary.border.stop.100"
|
||||
]
|
||||
] as const
|
||||
@@ -0,0 +1,149 @@
|
||||
/* Dashboard Mode owns its internal navigation and scroll frame. No GNB/LNB CSS variable or responsive dependency. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-dashboard-height: 100dvh;
|
||||
--hm-dashboard-background: var(--hm-sem-palette-green-light-2);
|
||||
--hm-dashboard-content-background: var(--hm-sem-bg-content);
|
||||
--hm-dashboard-content-top-radius: 12px;
|
||||
--hm-dashboard-content-side-radius: 18px;
|
||||
--hm-dashboard-sidebar-width: 272px;
|
||||
--hm-dashboard-min-width: 960px;
|
||||
--hm-dashboard-header-background: linear-gradient(180deg, var(--hm-sem-action-surface-stop-0) 0%, var(--hm-sem-action-surface-stop-30) 30%, var(--hm-sem-action-surface-stop-65) 65%, var(--hm-sem-action-surface-stop-100) 100%);
|
||||
--hm-dashboard-nav-text: var(--hm-sem-on-action-primary);
|
||||
--hm-dashboard-nav-text-secondary: var(--hm-sem-palette-green-tint);
|
||||
--hm-dashboard-nav-text-stroke-width: 1px;
|
||||
--hm-dashboard-nav-text-stroke-color: color-mix(in srgb, var(--hm-sem-palette-black) 30%, transparent);
|
||||
--hm-dashboard-nav-focus-outline: 2px solid var(--hm-sem-on-action-primary);
|
||||
--hm-dashboard-padding: 34px;
|
||||
--hm-dashboard-gap: 24px;
|
||||
--hm-dashboard-content-shadow-top: 0 -8px 18px color-mix(in srgb, var(--hm-sem-palette-green-dark-2) 18%, transparent);
|
||||
--hm-dashboard-content-shadow-side: -8px 0 18px color-mix(in srgb, var(--hm-sem-palette-green-dark-2) 18%, transparent);
|
||||
}
|
||||
|
||||
[data-slot="dashboard-scroll"] {
|
||||
box-sizing: border-box;
|
||||
height: var(--hm-dashboard-height);
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
[data-slot="dashboard-layout"] {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
/* 4 × 200px KPI cards + 3 × 24px gaps + 2 × 34px padding + 20px scrollbar allowance.
|
||||
Also fits the measured 549px compact status bar, its wings and the 125px brand without overlap. */
|
||||
min-width: var(--hm-dashboard-min-width, 960px);
|
||||
}
|
||||
[data-slot="dashboard-layout"][data-sidebar] {
|
||||
min-width: calc(var(--hm-dashboard-min-width) + var(--hm-dashboard-sidebar-width));
|
||||
}
|
||||
[data-slot="dashboard-body"] {
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
background: var(--hm-dashboard-background);
|
||||
}
|
||||
[data-slot="dashboard-header"] {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
height: 48px;
|
||||
padding: 0 20px;
|
||||
background: var(--hm-dashboard-header-background);
|
||||
color: var(--hm-dashboard-nav-text);
|
||||
}
|
||||
[data-slot="dashboard-body"] > [data-slot="dashboard-sidebar"] {
|
||||
flex: none;
|
||||
width: var(--hm-dashboard-sidebar-width);
|
||||
min-height: 0;
|
||||
padding: 28px 20px 22px;
|
||||
gap: 22px;
|
||||
background: linear-gradient(90deg, var(--hm-sem-action-primary) 0%, var(--hm-sem-palette-green-light-1) 30%, var(--hm-sem-palette-green-light-2) 50%, var(--hm-sem-palette-green-light-3) 90%, var(--hm-sem-palette-green-light-2) 100%);
|
||||
color: var(--hm-dashboard-nav-text);
|
||||
}
|
||||
[data-slot="dashboard-sidebar"] > nav {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
[data-slot="dashboard-sidebar"] [data-slot="button"] {
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
height: auto;
|
||||
white-space: normal;
|
||||
color: var(--hm-dashboard-nav-text);
|
||||
}
|
||||
[data-slot="dashboard-sidebar"] [data-slot="button"]:hover {
|
||||
background: color-mix(in srgb, var(--hm-sem-palette-black) 10%, transparent);
|
||||
}
|
||||
[data-slot="dashboard-sidebar"] [data-slot="button"][aria-expanded="true"] {
|
||||
background: color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
|
||||
}
|
||||
[data-slot="dashboard-sidebar"] [data-slot="collapsible"][data-current] > [data-slot="button"] {
|
||||
color: var(--hm-sem-palette-yellow-accent);
|
||||
}
|
||||
[data-slot="dashboard-sidebar"] [data-slot="button"][aria-expanded="true"] > svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
[data-slot="dashboard-sidebar"] [aria-current="page"],
|
||||
[data-slot="dashboard-sidebar"] [aria-current="page"]:hover {
|
||||
background: var(--hm-dashboard-content-background);
|
||||
color: var(--hm-sem-action-primary);
|
||||
}
|
||||
[data-slot="dashboard-sidebar"] [data-slot="button"]:focus-visible {
|
||||
outline: var(--hm-dashboard-nav-focus-outline);
|
||||
}
|
||||
[data-slot="dashboard-sidebar"] [aria-current="page"]:focus-visible {
|
||||
outline-color: var(--hm-sem-action-primary);
|
||||
}
|
||||
|
||||
/* Content surface and vertical scroll container, positioned after Dashboard's internal navigation. */
|
||||
[data-slot="dashboard-main"] {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
padding: var(--hm-dashboard-padding);
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
/* Let horizontal wheel/touch gestures chain to the outer Dashboard frame. */
|
||||
overscroll-behavior-y: contain;
|
||||
background: var(--hm-dashboard-content-background);
|
||||
}
|
||||
/* Dashboard header and sidebar use Dashboard-owned radii. */
|
||||
[data-slot="dashboard-layout"][data-header] [data-slot="dashboard-main"] {
|
||||
border-radius: var(--hm-dashboard-content-top-radius) var(--hm-dashboard-content-top-radius) 0 0;
|
||||
box-shadow: var(--hm-dashboard-content-shadow-top);
|
||||
}
|
||||
|
||||
[data-slot="dashboard-content"] {
|
||||
gap: var(--hm-dashboard-gap);
|
||||
min-width: 0;
|
||||
}
|
||||
[data-slot="dashboard-content"] > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Preserve the desktop filter row inside the minimum-width frame, independent of the viewport's mobile Filter Bar rules. */
|
||||
[data-slot="dashboard-layout"] [data-slot="filter-bar"] {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
[data-slot="dashboard-layout"] [data-slot="filter-bar-search"] {
|
||||
flex: 0 0 var(--hm-filter-bar-search-width);
|
||||
}
|
||||
[data-slot="dashboard-layout"] [data-slot="filter-bar"] > :is([data-slot="select-trigger"], [data-slot="combobox-trigger"], [data-slot="date-picker-trigger"], [data-slot="input"]) {
|
||||
flex: 0 0 var(--hm-filter-bar-control-width);
|
||||
width: var(--hm-filter-bar-control-width);
|
||||
}
|
||||
[data-slot="dashboard-layout"] [data-slot="filter-bar-actions"] {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/* The sidebar remains inside the Dashboard minimum-width frame at every viewport width. */
|
||||
[data-slot="dashboard-layout"][data-sidebar] [data-slot="dashboard-main"] {
|
||||
border-radius: var(--hm-dashboard-content-side-radius) 0 0 var(--hm-dashboard-content-side-radius);
|
||||
box-shadow: var(--hm-dashboard-content-shadow-side);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/* Data Table pattern: wrappers around the Table primitive. Header / cell / row looks are src/theme/table.css; Checkbox, Pagination, Select,
|
||||
Dropdown Menu, Button and Skeleton keep their own tokens. Only the footer layout, summary text, sort button, empty and loading cells are here. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-data-table-footer-gap: 12px;
|
||||
--hm-data-table-footer-padding-top: 12px;
|
||||
--hm-data-table-summary-color: var(--hm-sem-fg-secondary);
|
||||
--hm-data-table-summary-font-size: 13px;
|
||||
--hm-data-table-sort-gap: 4px;
|
||||
--hm-data-table-sort-icon-size: 14px;
|
||||
--hm-data-table-sort-active-color: var(--hm-sem-palette-green-light-3);
|
||||
--hm-data-table-empty-padding-y: 48px;
|
||||
--hm-data-table-empty-color: var(--hm-sem-fg-secondary);
|
||||
--hm-data-table-skeleton-height: 16px;
|
||||
}
|
||||
|
||||
/* min-width 0 lets the Table container scroll sideways inside a grid / flex parent instead of widening the page. */
|
||||
[data-slot="data-table"] {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-slot="data-table-footer"] {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--hm-data-table-footer-gap);
|
||||
padding-top: var(--hm-data-table-footer-padding-top);
|
||||
}
|
||||
[data-slot="data-table-summary"] {
|
||||
margin: 0;
|
||||
margin-right: auto;
|
||||
color: var(--hm-data-table-summary-color);
|
||||
font-size: var(--hm-data-table-summary-font-size);
|
||||
}
|
||||
[data-slot="data-table-page-size"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--hm-data-table-summary-color);
|
||||
font-size: var(--hm-data-table-summary-font-size);
|
||||
}
|
||||
[data-slot="data-table-footer"] [data-slot="data-table-pagination"] {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
[data-slot="data-table-sort"] {
|
||||
gap: var(--hm-data-table-sort-gap);
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="data-table-sort"] svg {
|
||||
width: var(--hm-data-table-sort-icon-size);
|
||||
height: var(--hm-data-table-sort-icon-size);
|
||||
opacity: 0.6;
|
||||
}
|
||||
[data-slot="data-table-sort"][data-sorted] svg {
|
||||
opacity: 1;
|
||||
color: var(--hm-data-table-sort-active-color);
|
||||
}
|
||||
[data-slot="data-table-sort"]:focus-visible {
|
||||
outline: 2px solid var(--hm-sem-stroke-input-focus);
|
||||
outline-offset: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
[data-slot="data-table-empty"] {
|
||||
padding-top: var(--hm-data-table-empty-padding-y);
|
||||
padding-bottom: var(--hm-data-table-empty-padding-y);
|
||||
text-align: center;
|
||||
color: var(--hm-data-table-empty-color);
|
||||
}
|
||||
[data-slot="data-table-skeleton"] {
|
||||
height: var(--hm-data-table-skeleton-height);
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/* Date Picker mapping. ui-viz lists Date Picker by name only (no spec), so these are current HM values built from the HM Semantic /
|
||||
Primitive tokens. Date Picker is a composition (Popover + Calendar): the popup surface comes from Popover and the calendar from
|
||||
Calendar, so only the trigger (a field, in the two Select sizes) is styled here. The Popover padding / width are overridden per
|
||||
instance through an inline style in the component (the popup hugs the Calendar, which has its own padding).
|
||||
The trigger look is in `@layer components` so a consumer's utility classes (width, height) still win. Nothing here references
|
||||
Button / Input / Select / Popover tokens. */
|
||||
:root, [data-hm-theme] {
|
||||
/* Inner heights; the 1px border is added around them (outer 34 / 44), like the Select trigger. */
|
||||
--hm-date-picker-height-md: 32px;
|
||||
--hm-date-picker-height-lg: 42px;
|
||||
--hm-date-picker-border-width: 1px;
|
||||
--hm-date-picker-padding-x: 12px;
|
||||
--hm-date-picker-gap: 12px;
|
||||
--hm-date-picker-radius: var(--hm-radius-control-md);
|
||||
--hm-date-picker-font-size-md: 14px;
|
||||
--hm-date-picker-line-height-md: 20px;
|
||||
--hm-date-picker-font-size-lg: 16px;
|
||||
--hm-date-picker-line-height-lg: 24px;
|
||||
|
||||
--hm-date-picker-background: var(--hm-sem-bg-surface);
|
||||
--hm-date-picker-text: var(--hm-sem-fg-primary);
|
||||
--hm-date-picker-placeholder: var(--hm-sem-fg-placeholder);
|
||||
--hm-date-picker-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-date-picker-focus-border-color: var(--hm-sem-stroke-input-focus);
|
||||
--hm-date-picker-icon-size: 18px;
|
||||
--hm-date-picker-icon-color: var(--hm-sem-fg-secondary);
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-date-picker-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
[data-slot="date-picker-trigger"] {
|
||||
--hm-date-picker-height: var(--hm-date-picker-height-lg);
|
||||
--hm-date-picker-font-size: var(--hm-date-picker-font-size-lg);
|
||||
--hm-date-picker-line-height: var(--hm-date-picker-line-height-lg);
|
||||
box-sizing: border-box;
|
||||
min-width: 0; /* lets a long value truncate instead of widening a grid / flex parent */
|
||||
max-width: 100%;
|
||||
min-height: calc(var(--hm-date-picker-height) + 2 * var(--hm-date-picker-border-width));
|
||||
gap: var(--hm-date-picker-gap);
|
||||
padding: 0 var(--hm-date-picker-padding-x);
|
||||
border: var(--hm-date-picker-border-width) solid var(--hm-date-picker-border-color);
|
||||
border-radius: var(--hm-date-picker-radius);
|
||||
background: var(--hm-date-picker-background);
|
||||
color: var(--hm-date-picker-text);
|
||||
font-size: var(--hm-date-picker-font-size);
|
||||
line-height: var(--hm-date-picker-line-height);
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="date-picker-trigger"][data-size="medium"] {
|
||||
--hm-date-picker-height: var(--hm-date-picker-height-md);
|
||||
--hm-date-picker-font-size: var(--hm-date-picker-font-size-md);
|
||||
--hm-date-picker-line-height: var(--hm-date-picker-line-height-md);
|
||||
}
|
||||
[data-slot="date-picker-trigger"][data-empty] [data-slot="date-picker-value"] {
|
||||
color: var(--hm-date-picker-placeholder);
|
||||
}
|
||||
[data-slot="date-picker-value"] {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[data-slot="date-picker-icon"] {
|
||||
flex-shrink: 0;
|
||||
width: var(--hm-date-picker-icon-size);
|
||||
height: var(--hm-date-picker-icon-size);
|
||||
color: var(--hm-date-picker-icon-color);
|
||||
}
|
||||
/* Focus and open: the whole outline turns to the focus color (no ring, like the Input / Select focus). */
|
||||
[data-slot="date-picker-trigger"]:focus-visible,
|
||||
[data-slot="date-picker-trigger"][data-state="open"] {
|
||||
border-color: var(--hm-date-picker-focus-border-color);
|
||||
}
|
||||
/* Existing native aria-invalid is the source; Calendar selection/focus remains independent. */
|
||||
[data-slot="date-picker-trigger"][aria-invalid="true"] {
|
||||
border-color: var(--hm-sem-action-destructive);
|
||||
}
|
||||
[data-slot="date-picker-trigger"][aria-invalid="true"]:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
[data-slot="date-picker-trigger"]:disabled {
|
||||
opacity: var(--hm-date-picker-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Generated by npm run generate:theme-defaults. Do not edit.
|
||||
// Colors: themes/default.json; geometry/aliases: scripts/theme-templates.
|
||||
export const runtimeThemeDefaults = {
|
||||
"--hm-sem-bg-content": "#f5f0e5",
|
||||
"--hm-sem-bg-surface": "#ffffff",
|
||||
"--hm-sem-fg-primary": "#0a221d",
|
||||
"--hm-sem-fg-secondary": "#6e6759",
|
||||
"--hm-sem-fg-placeholder": "#999999",
|
||||
"--hm-sem-action-primary": "#123328",
|
||||
"--hm-sem-action-cancel": "#6e6759",
|
||||
"--hm-sem-action-destructive": "#d32f2f",
|
||||
"--hm-sem-stroke-default": "#9a9284",
|
||||
"--hm-sem-stroke-input-default": "#dedbd2",
|
||||
"--hm-sem-stroke-input-focus": "#266651",
|
||||
"--hm-sem-brand-bg-stop-0": "#0a221d",
|
||||
"--hm-sem-brand-bg-stop-12": "#071e18",
|
||||
"--hm-sem-brand-bg-stop-42": "#123328",
|
||||
"--hm-sem-brand-bg-stop-78": "#266651",
|
||||
"--hm-sem-brand-bg-stop-100": "#205241",
|
||||
"--hm-sem-action-surface-stop-0": "#1f8265",
|
||||
"--hm-sem-action-surface-stop-30": "#165c47",
|
||||
"--hm-sem-action-surface-stop-65": "#114838",
|
||||
"--hm-sem-action-surface-stop-100": "#08241c",
|
||||
"--hm-sem-action-border-stop-0": "#0a221d",
|
||||
"--hm-sem-action-border-stop-50": "#34daaa",
|
||||
"--hm-sem-action-border-stop-100": "#08241c",
|
||||
"--hm-radius-input": "4px",
|
||||
"--hm-radius-container": "4px",
|
||||
"--hm-sem-state-active": "#266651",
|
||||
"--hm-sem-on-state-active": "#ffffff",
|
||||
"--hm-sem-on-action-primary": "#ffffff",
|
||||
"--hm-sem-bg-subtle": "#f4f1ea",
|
||||
"--hm-sem-accent": "#bddfd4",
|
||||
"--hm-sem-on-accent": "#123328",
|
||||
"--hm-sem-palette-green-light-4": "#34daaa",
|
||||
"--hm-sem-palette-green-light-3": "#249473",
|
||||
"--hm-sem-palette-green-light-2": "#266651",
|
||||
"--hm-sem-palette-green-light-1": "#205241",
|
||||
"--hm-sem-palette-green-default": "#123328",
|
||||
"--hm-sem-palette-green-dark-1": "#0a221d",
|
||||
"--hm-sem-palette-green-dark-2": "#071e18",
|
||||
"--hm-sem-palette-brown-light-2": "#897445",
|
||||
"--hm-sem-palette-brown-light-1": "#604f32",
|
||||
"--hm-sem-palette-brown-default": "#3e3523",
|
||||
"--hm-sem-palette-brown-dark-1": "#352d1d",
|
||||
"--hm-sem-palette-brown-light-4": "#ecd4a2",
|
||||
"--hm-sem-palette-orange-light-4": "#feba88",
|
||||
"--hm-sem-palette-orange-default": "#cc5200",
|
||||
"--hm-sem-palette-warm-neutral-light": "#e0dace",
|
||||
"--hm-sem-palette-warm-neutral-mid": "#9a9284",
|
||||
"--hm-sem-palette-warm-neutral-dark": "#6e6759",
|
||||
"--hm-sem-palette-beige-light": "#f5f0e5",
|
||||
"--hm-sem-palette-red-danger": "#d32f2f",
|
||||
"--hm-sem-palette-white": "#ffffff",
|
||||
"--hm-sem-palette-black": "#000000",
|
||||
"--hm-sem-palette-neutral-gray-500": "#999999",
|
||||
"--hm-sem-palette-green-tone-1": "#1f8265",
|
||||
"--hm-sem-palette-green-tone-2": "#165c47",
|
||||
"--hm-sem-palette-green-tone-3": "#114838",
|
||||
"--hm-sem-palette-green-tone-4": "#08241c",
|
||||
"--hm-sem-palette-warm-neutral-pale": "#dedbd2",
|
||||
"--hm-sem-palette-warm-neutral-secondary": "#ede8db",
|
||||
"--hm-sem-palette-warm-neutral-muted": "#f4f1ea",
|
||||
"--hm-sem-palette-green-tint": "#bddfd4",
|
||||
"--hm-sem-palette-yellow-accent": "#ffc600",
|
||||
"--hm-sem-palette-brown-deep": "#221d13",
|
||||
"--hm-sem-palette-brown-highlight": "#a98d53",
|
||||
"--hm-sem-palette-orange-light": "#d35a00",
|
||||
"--hm-sem-palette-orange-mid": "#ce5600",
|
||||
"--hm-sem-palette-orange-dark": "#b44b00",
|
||||
"--hm-sem-palette-orange-deep": "#783402",
|
||||
"--hm-sem-palette-orange-border": "#bc4c00",
|
||||
"--hm-sem-palette-orange-border-deep": "#5e2902",
|
||||
"--hm-sem-palette-gray": "#767676",
|
||||
"--hm-sem-palette-gray-dark": "#5a5a5a",
|
||||
"--hm-sem-palette-indicator": "#2d2d2d"
|
||||
} as const
|
||||
@@ -0,0 +1,203 @@
|
||||
/* Generated from themes/default.json + scripts/theme-templates/default.css. Do not edit. */
|
||||
@import "./primitives.css";
|
||||
|
||||
/* Derived aliases and geometry. Values are generated from themes/default.json. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-color-primary: var(--hm-sem-palette-green-default);
|
||||
--hm-color-primary-hover: color-mix(in srgb, var(--hm-sem-action-primary) 85%, var(--hm-sem-palette-black));
|
||||
--hm-color-primary-active: color-mix(in srgb, var(--hm-sem-action-primary) 70%, var(--hm-sem-palette-black));
|
||||
--hm-color-surface-default: var(--hm-sem-palette-white);
|
||||
--hm-color-surface-content: var(--hm-sem-palette-beige-light);
|
||||
--hm-color-surface-subtle: var(--hm-sem-palette-warm-neutral-muted);
|
||||
--hm-color-text-primary: var(--hm-sem-palette-green-dark-1);
|
||||
--hm-color-text-secondary: var(--hm-sem-palette-warm-neutral-dark);
|
||||
--hm-color-text-inverse: var(--hm-sem-palette-white);
|
||||
--hm-color-text-placeholder: var(--hm-sem-palette-neutral-gray-500);
|
||||
--hm-color-border-default: var(--hm-sem-palette-warm-neutral-mid);
|
||||
--hm-color-border-control: var(--hm-sem-palette-warm-neutral-pale);
|
||||
/* Common Focus color. Trial value: Light 2 (#266651), pending user confirmation. Was --hm-color-primary. */
|
||||
--hm-color-border-focus: var(--hm-sem-palette-green-light-2);
|
||||
--hm-color-action-cancel: var(--hm-sem-palette-warm-neutral-dark);
|
||||
--hm-color-action-destructive: var(--hm-sem-palette-red-danger);
|
||||
--hm-color-action-destructive-hover: color-mix(in srgb, var(--hm-sem-action-destructive) 8%, var(--hm-sem-bg-surface));
|
||||
--hm-color-action-overlay-hover: color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
|
||||
--hm-color-action-overlay-active: color-mix(in srgb, var(--hm-sem-palette-black) 30%, transparent);
|
||||
--hm-color-action-shadow-light: color-mix(in srgb, var(--hm-sem-palette-white) 10%, transparent);
|
||||
--hm-color-action-shadow-dark: color-mix(in srgb, var(--hm-sem-palette-black) 25%, transparent);
|
||||
|
||||
--hm-radius-control-md: 4px;
|
||||
--hm-control-height-md: 34px;
|
||||
--hm-font-family-base: "Noto Sans KR", "Malgun Gothic", sans-serif;
|
||||
--hm-font-size-button-md: 13px;
|
||||
--hm-font-weight-medium: 500;
|
||||
--hm-font-weight-bold: 700;
|
||||
|
||||
/* Existing semantic names remain the shadcn and preview adapter. */
|
||||
--hm-sem-bg-content: var(--hm-color-surface-content);
|
||||
--hm-sem-bg-surface: var(--hm-color-surface-default);
|
||||
--hm-sem-fg-primary: var(--hm-color-text-primary);
|
||||
--hm-sem-fg-secondary: var(--hm-color-text-secondary);
|
||||
--hm-sem-fg-placeholder: var(--hm-color-text-placeholder);
|
||||
--hm-sem-action-primary: var(--hm-color-primary);
|
||||
--hm-sem-action-cancel: var(--hm-color-action-cancel);
|
||||
--hm-sem-action-destructive: var(--hm-color-action-destructive);
|
||||
--hm-sem-stroke-default: var(--hm-color-border-default);
|
||||
--hm-sem-stroke-input-default: var(--hm-color-border-control);
|
||||
--hm-sem-stroke-input-focus: var(--hm-color-border-focus);
|
||||
--hm-sem-on-action-primary: var(--hm-color-text-inverse);
|
||||
/* Active state fill: checked / selected / on (Checkbox, Radio, Switch, Slider, Progress, Calendar).
|
||||
Sibling of the Focus chain (--hm-color-border-focus): same Light 2 today, but neither references the other. */
|
||||
--hm-sem-state-active: var(--hm-sem-palette-green-light-2);
|
||||
--hm-sem-on-state-active: var(--hm-color-text-inverse);
|
||||
/* Public gradient stop groups; geometry remains internal. */
|
||||
--hm-sem-brand-bg-stop-0: var(--hm-sem-palette-green-dark-1);
|
||||
--hm-sem-brand-bg-stop-12: var(--hm-sem-palette-green-dark-2);
|
||||
--hm-sem-brand-bg-stop-42: var(--hm-sem-palette-green-default);
|
||||
--hm-sem-brand-bg-stop-78: var(--hm-sem-palette-green-light-2);
|
||||
--hm-sem-brand-bg-stop-100: var(--hm-sem-palette-green-light-1);
|
||||
--hm-sem-bg-brand: linear-gradient(to bottom,
|
||||
var(--hm-sem-brand-bg-stop-0) 0%, var(--hm-sem-brand-bg-stop-12) 12%,
|
||||
var(--hm-sem-brand-bg-stop-42) 42%, var(--hm-sem-brand-bg-stop-78) 78%,
|
||||
var(--hm-sem-brand-bg-stop-100) 100%);
|
||||
|
||||
--hm-sem-action-surface-stop-0: var(--hm-sem-palette-green-tone-1);
|
||||
--hm-sem-action-surface-stop-30: var(--hm-sem-palette-green-tone-2);
|
||||
--hm-sem-action-surface-stop-65: var(--hm-sem-palette-green-tone-3);
|
||||
--hm-sem-action-surface-stop-100: var(--hm-sem-palette-green-tone-4);
|
||||
--hm-sem-surface-action-primary: linear-gradient(180deg,
|
||||
var(--hm-sem-action-surface-stop-0) 0%, var(--hm-sem-action-surface-stop-30) 30%,
|
||||
var(--hm-sem-action-surface-stop-65) 65%, var(--hm-sem-action-surface-stop-100) 100%);
|
||||
--hm-sem-action-border-stop-0: var(--hm-sem-palette-green-dark-1);
|
||||
--hm-sem-action-border-stop-50: var(--hm-sem-palette-green-light-4);
|
||||
--hm-sem-action-border-stop-100: var(--hm-sem-palette-green-tone-4);
|
||||
--hm-sem-border-action-primary: linear-gradient(180deg,
|
||||
var(--hm-sem-action-border-stop-0) 0%, var(--hm-sem-action-border-stop-50) 50%,
|
||||
var(--hm-sem-action-border-stop-100) 100%);
|
||||
|
||||
--hm-radius-button: var(--hm-radius-control-md);
|
||||
--hm-radius-input: var(--hm-radius-control-md);
|
||||
--hm-radius-select: var(--hm-radius-control-md);
|
||||
--hm-radius-container: var(--hm-radius-control-md);
|
||||
|
||||
/* Floating surface: anchored panels (Dropdown Menu, Context Menu, Popover, Hover Card, Menubar content, Navigation Menu panel).
|
||||
Not Tooltip (inverse face), Select, Card or the modal surfaces. Component tokens keep their own names and point here. */
|
||||
--hm-sem-floating-background: var(--hm-sem-bg-surface);
|
||||
--hm-sem-floating-border-width: 1px;
|
||||
--hm-sem-floating-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-sem-floating-radius: var(--hm-radius-container);
|
||||
--hm-sem-floating-shadow: 0 0 12px color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
|
||||
|
||||
--hm-sem-bg-subtle: var(--hm-color-surface-subtle);
|
||||
|
||||
--hm-sem-accent: var(--hm-sem-palette-green-tint);
|
||||
|
||||
--hm-sem-on-accent: #123328;
|
||||
|
||||
--hm-sem-palette-green-light-4: var(--hm-green-light-4);
|
||||
|
||||
--hm-sem-palette-green-light-3: var(--hm-green-light-3);
|
||||
|
||||
--hm-sem-palette-green-light-2: var(--hm-green-light-2);
|
||||
|
||||
--hm-sem-palette-green-light-1: var(--hm-green-light-1);
|
||||
|
||||
--hm-sem-palette-green-default: var(--hm-green-default);
|
||||
|
||||
--hm-sem-palette-green-dark-1: var(--hm-green-dark-1);
|
||||
|
||||
--hm-sem-palette-green-dark-2: var(--hm-green-dark-2);
|
||||
|
||||
--hm-sem-palette-brown-light-2: var(--hm-brown-light-2);
|
||||
|
||||
--hm-sem-palette-brown-light-1: var(--hm-brown-light-1);
|
||||
|
||||
--hm-sem-palette-brown-default: var(--hm-brown-default);
|
||||
|
||||
--hm-sem-palette-brown-dark-1: var(--hm-brown-dark-1);
|
||||
|
||||
--hm-sem-palette-brown-light-4: var(--hm-brown-light-4);
|
||||
|
||||
--hm-sem-palette-orange-light-4: var(--hm-orange-light-4);
|
||||
|
||||
--hm-sem-palette-orange-default: var(--hm-orange-default);
|
||||
|
||||
--hm-sem-palette-warm-neutral-light: var(--hm-warm-neutral-light);
|
||||
|
||||
--hm-sem-palette-warm-neutral-mid: var(--hm-warm-neutral-mid);
|
||||
|
||||
--hm-sem-palette-warm-neutral-dark: var(--hm-warm-neutral-dark);
|
||||
|
||||
--hm-sem-palette-beige-light: var(--hm-beige-light);
|
||||
|
||||
--hm-sem-palette-red-danger: var(--hm-red-danger);
|
||||
|
||||
--hm-sem-palette-white: var(--hm-white);
|
||||
|
||||
--hm-sem-palette-black: var(--hm-black);
|
||||
|
||||
--hm-sem-palette-neutral-gray-500: var(--hm-neutral-gray-500);
|
||||
|
||||
--hm-sem-palette-green-tone-1: var(--hm-green-tone-1);
|
||||
|
||||
--hm-sem-palette-green-tone-2: var(--hm-green-tone-2);
|
||||
|
||||
--hm-sem-palette-green-tone-3: var(--hm-green-tone-3);
|
||||
|
||||
--hm-sem-palette-green-tone-4: var(--hm-green-tone-4);
|
||||
|
||||
--hm-sem-palette-warm-neutral-pale: var(--hm-warm-neutral-pale);
|
||||
|
||||
--hm-sem-palette-warm-neutral-secondary: var(--hm-warm-neutral-secondary);
|
||||
|
||||
--hm-sem-palette-warm-neutral-muted: var(--hm-warm-neutral-muted);
|
||||
|
||||
--hm-sem-palette-green-tint: var(--hm-green-tint);
|
||||
|
||||
--hm-sem-palette-yellow-accent: var(--hm-yellow-accent);
|
||||
|
||||
--hm-sem-palette-brown-deep: #221d13;
|
||||
|
||||
--hm-sem-palette-brown-highlight: #a98d53;
|
||||
|
||||
--hm-sem-palette-orange-light: #d35a00;
|
||||
|
||||
--hm-sem-palette-orange-mid: #ce5600;
|
||||
|
||||
--hm-sem-palette-orange-dark: #b44b00;
|
||||
|
||||
--hm-sem-palette-orange-deep: #783402;
|
||||
|
||||
--hm-sem-palette-orange-border: #bc4c00;
|
||||
|
||||
--hm-sem-palette-orange-border-deep: #5e2902;
|
||||
|
||||
--hm-sem-palette-gray: #767676;
|
||||
|
||||
--hm-sem-palette-gray-dark: #5a5a5a;
|
||||
|
||||
--hm-sem-palette-indicator: #2d2d2d;
|
||||
|
||||
--hm-sem-overlay-background: color-mix(in srgb, var(--hm-sem-palette-black) 10%, transparent);
|
||||
--hm-sem-surface-highlight: color-mix(in srgb, var(--hm-sem-accent) 45%, transparent);
|
||||
|
||||
/* Adapter for shadcn/Tailwind. The public contract.ts allowlist limits runtime overrides. */
|
||||
--background: var(--hm-sem-bg-content);
|
||||
--foreground: var(--hm-sem-fg-primary);
|
||||
--card: var(--hm-sem-bg-surface);
|
||||
--card-foreground: var(--hm-sem-fg-primary);
|
||||
--popover: var(--hm-sem-bg-surface);
|
||||
--popover-foreground: var(--hm-sem-fg-primary);
|
||||
--primary: var(--hm-sem-action-primary);
|
||||
--primary-foreground: var(--hm-sem-on-action-primary);
|
||||
--secondary: var(--hm-sem-palette-warm-neutral-secondary);
|
||||
--secondary-foreground: var(--hm-sem-palette-brown-default);
|
||||
--muted: var(--hm-sem-bg-subtle);
|
||||
--muted-foreground: var(--hm-sem-fg-secondary);
|
||||
--accent: var(--hm-sem-accent);
|
||||
--accent-foreground: var(--hm-sem-on-accent);
|
||||
--destructive: var(--hm-sem-action-destructive);
|
||||
--border: var(--hm-sem-stroke-default);
|
||||
--input: var(--hm-sem-stroke-input-default);
|
||||
--ring: var(--hm-sem-stroke-input-focus);
|
||||
--radius: var(--hm-radius-container);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/* Dialog mapping. ui_viz: docs/components/dialog.md (Dialog / Modal), §1.1, §2, §4, §9. Radix keeps Portal, focus trap, ARIA, Escape, focus return and scroll lock.
|
||||
Only the look is set here: container, regions (Header / Body / Footer) and text. The Overlay, motion and Close button keep the shadcn defaults. */
|
||||
:root, [data-hm-theme] {
|
||||
/* ui_viz: dialog.md §2, §8.1 — Radius 8px (Dialog-local, USER_DECISION) and shadow 0 0 12px rgba(0,0,0,.2) (D-093 D-088) — Confirmed. Dialog-local, not Foundation tokens. */
|
||||
--hm-dialog-radius: 8px;
|
||||
--hm-dialog-shadow: 0 0 12px color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
|
||||
/* ui_viz: dialog.md §2 — Surface color.bg.surface / foreground color.fg.primary — Confirmed reuse */
|
||||
--hm-dialog-background: var(--hm-sem-bg-surface);
|
||||
--hm-dialog-text: var(--hm-sem-fg-primary);
|
||||
/* ui_viz: dialog.md §2 — outer 1px; color #DEDBD2 is Temporary (Token relation TBD, DIALOG-10 FOLLOW_UP). Same value as the Header divider. */
|
||||
--hm-dialog-border-width: 1px;
|
||||
--hm-dialog-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-dialog-divider-color: var(--hm-sem-stroke-input-default);
|
||||
|
||||
/* ui_viz: dialog.md §9 — Header 24/24/16, Title<->Description 8, Body 24 all sides, Actions 0/24/24, Action gap 8 — Confirmed (D-093, COMPONENT_SCOPED) */
|
||||
--hm-dialog-padding: 24px;
|
||||
--hm-dialog-header-padding-bottom: 16px;
|
||||
--hm-dialog-header-gap: 8px;
|
||||
--hm-dialog-actions-gap: 8px;
|
||||
--hm-dialog-footer-padding-top: 16px;
|
||||
|
||||
/* ui_viz: dialog.md §1.1 — Title heading-md 20/28/700 (Dialog-local), Description body-sm 14/20/400, Body body-md 16/24/400 */
|
||||
--hm-dialog-title-font-size: 20px;
|
||||
--hm-dialog-title-line-height: 28px;
|
||||
--hm-dialog-title-font-weight: 700;
|
||||
--hm-dialog-description-font-size: 14px;
|
||||
--hm-dialog-description-line-height: 20px;
|
||||
--hm-dialog-body-font-size: 16px;
|
||||
--hm-dialog-body-line-height: 24px;
|
||||
/* ui_viz: dialog.md §1.2 — Description foreground color.fg.secondary is REFERENCE_ONLY / PARTIAL (current style kept, not a Confirmed value) */
|
||||
--hm-dialog-description-color: var(--hm-sem-fg-secondary);
|
||||
|
||||
/* ui_viz: dialog.md §2, §9 — size is fit-content; max width = viewport - 2 x edge; max height also subtracts the app common header / footer.
|
||||
edge 16px is REVIEW (existing reference value, not a global spacing token). The two app heights are Web Mapping names the consuming screen may set (default 0). */
|
||||
--hm-dialog-edge: 16px;
|
||||
--hm-dialog-app-header-height: 0px;
|
||||
--hm-dialog-app-footer-height: 0px;
|
||||
}
|
||||
|
||||
[data-slot="dialog-content"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
/* Content size up to the max width. Not fit-content: with the shadcn `left: 50%` placement, fit-content only gets half the viewport
|
||||
as available width, so a text Dialog stopped at 50vw (195px at 390px) instead of 100vw - 2 x edge. max-content + max-width gives the same min(content, max). */
|
||||
width: max-content;
|
||||
max-width: calc(100vw - 2 * var(--hm-dialog-edge));
|
||||
max-height: calc(100dvh - var(--hm-dialog-app-header-height) - var(--hm-dialog-app-footer-height) - 2 * var(--hm-dialog-edge));
|
||||
/* Centered in the area left by the app header / footer. */
|
||||
top: calc(var(--hm-dialog-app-header-height) + (100dvh - var(--hm-dialog-app-header-height) - var(--hm-dialog-app-footer-height)) / 2);
|
||||
padding: 0;
|
||||
border: var(--hm-dialog-border-width) solid var(--hm-dialog-border-color);
|
||||
border-radius: var(--hm-dialog-radius);
|
||||
background: var(--hm-dialog-background);
|
||||
color: var(--hm-dialog-text);
|
||||
box-shadow: var(--hm-dialog-shadow);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Header: Title (and optional Description) with the inset divider below it (24px each side, not edge to edge).
|
||||
The divider is the last pixel of the 16px bottom inset (ui_viz draws it inside the padding), so the border width comes out of the padding. */
|
||||
[data-slot="dialog-header"] {
|
||||
flex: none;
|
||||
gap: var(--hm-dialog-header-gap);
|
||||
margin-inline: var(--hm-dialog-padding);
|
||||
padding: var(--hm-dialog-padding) 0 calc(var(--hm-dialog-header-padding-bottom) - var(--hm-dialog-border-width));
|
||||
border-bottom: var(--hm-dialog-border-width) solid var(--hm-dialog-divider-color);
|
||||
}
|
||||
[data-slot="dialog-title"] {
|
||||
font-size: var(--hm-dialog-title-font-size);
|
||||
line-height: var(--hm-dialog-title-line-height);
|
||||
font-weight: var(--hm-dialog-title-font-weight);
|
||||
color: var(--hm-dialog-text);
|
||||
}
|
||||
[data-slot="dialog-description"] {
|
||||
font-size: var(--hm-dialog-description-font-size);
|
||||
line-height: var(--hm-dialog-description-line-height);
|
||||
font-weight: 400;
|
||||
color: var(--hm-dialog-description-color);
|
||||
}
|
||||
|
||||
/* Body: the only region that scrolls. Wide content (tables) scrolls sideways inside it, never the page. */
|
||||
[data-slot="dialog-body"] {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: var(--hm-dialog-padding);
|
||||
font-size: var(--hm-dialog-body-font-size);
|
||||
line-height: var(--hm-dialog-body-line-height);
|
||||
font-weight: 400;
|
||||
}
|
||||
[data-slot="dialog-body"] > :first-child { margin-top: 0; }
|
||||
[data-slot="dialog-body"] > :last-child { margin-bottom: 0; }
|
||||
|
||||
/* Footer (Actions): trailing edge, cancel first and the action last, no divider above. Stacking on a narrow screen is TBD in ui_viz (shadcn stacking kept). */
|
||||
[data-slot="dialog-footer"] {
|
||||
flex: none;
|
||||
gap: var(--hm-dialog-actions-gap);
|
||||
margin: 0;
|
||||
padding: var(--hm-dialog-footer-padding-top) var(--hm-dialog-padding) var(--hm-dialog-padding);
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/* Drawer mapping. ui-viz has no Drawer spec, so these are current HM values built from the HM Semantic tokens.
|
||||
vaul keeps drag-to-dismiss, snap points, scroll / drag arbitration and body scroll lock; Radix keeps Portal, focus trap,
|
||||
Escape and ARIA. The handle is vaul's drag affordance (bottom direction); only its look is set here. The Overlay keeps the
|
||||
shadcn default. No --hm-sheet-* or --hm-dialog-* token is referenced. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-drawer-background: var(--hm-sem-bg-surface);
|
||||
--hm-drawer-text: var(--hm-sem-fg-primary);
|
||||
--hm-drawer-border-width: 1px;
|
||||
--hm-drawer-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-drawer-radius: 8px;
|
||||
--hm-drawer-shadow: 0 0 12px color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
|
||||
|
||||
/* Bottom / top: full width, height by content up to max-height. Left / right: fixed width. */
|
||||
--hm-drawer-max-height: 85dvh;
|
||||
--hm-drawer-width: 400px;
|
||||
--hm-drawer-edge-gap: 48px;
|
||||
|
||||
--hm-drawer-padding: 24px;
|
||||
--hm-drawer-header-gap: 8px;
|
||||
--hm-drawer-footer-gap: 8px;
|
||||
|
||||
--hm-drawer-handle-width: 48px;
|
||||
--hm-drawer-handle-height: 4px;
|
||||
--hm-drawer-handle-color: var(--hm-sem-stroke-default);
|
||||
--hm-drawer-handle-margin-top: 12px;
|
||||
|
||||
--hm-drawer-title-font-size: 18px;
|
||||
--hm-drawer-title-line-height: 28px;
|
||||
--hm-drawer-title-font-weight: 700;
|
||||
--hm-drawer-description-font-size: 14px;
|
||||
--hm-drawer-description-line-height: 20px;
|
||||
--hm-drawer-description-color: var(--hm-sem-fg-secondary);
|
||||
--hm-drawer-body-font-size: 14px;
|
||||
--hm-drawer-body-line-height: 20px;
|
||||
}
|
||||
|
||||
[data-slot="drawer-content"] {
|
||||
box-sizing: border-box;
|
||||
background: var(--hm-drawer-background);
|
||||
color: var(--hm-drawer-text);
|
||||
box-shadow: var(--hm-drawer-shadow);
|
||||
font-size: var(--hm-drawer-body-font-size);
|
||||
line-height: var(--hm-drawer-body-line-height);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-slot="drawer-content"][data-vaul-drawer-direction="bottom"] {
|
||||
max-height: var(--hm-drawer-max-height);
|
||||
border-top: var(--hm-drawer-border-width) solid var(--hm-drawer-border-color);
|
||||
border-radius: var(--hm-drawer-radius) var(--hm-drawer-radius) 0 0;
|
||||
}
|
||||
[data-slot="drawer-content"][data-vaul-drawer-direction="top"] {
|
||||
max-height: var(--hm-drawer-max-height);
|
||||
border-bottom: var(--hm-drawer-border-width) solid var(--hm-drawer-border-color);
|
||||
border-radius: 0 0 var(--hm-drawer-radius) var(--hm-drawer-radius);
|
||||
}
|
||||
[data-slot="drawer-content"][data-vaul-drawer-direction="left"],
|
||||
[data-slot="drawer-content"][data-vaul-drawer-direction="right"] {
|
||||
height: 100%;
|
||||
width: min(var(--hm-drawer-width), calc(100vw - var(--hm-drawer-edge-gap)));
|
||||
}
|
||||
[data-slot="drawer-content"][data-vaul-drawer-direction="left"] {
|
||||
border-right: var(--hm-drawer-border-width) solid var(--hm-drawer-border-color);
|
||||
border-radius: 0 var(--hm-drawer-radius) var(--hm-drawer-radius) 0;
|
||||
}
|
||||
[data-slot="drawer-content"][data-vaul-drawer-direction="right"] {
|
||||
border-left: var(--hm-drawer-border-width) solid var(--hm-drawer-border-color);
|
||||
border-radius: var(--hm-drawer-radius) 0 0 var(--hm-drawer-radius);
|
||||
}
|
||||
|
||||
[data-slot="drawer-handle"] {
|
||||
width: var(--hm-drawer-handle-width);
|
||||
height: var(--hm-drawer-handle-height);
|
||||
margin-top: var(--hm-drawer-handle-margin-top);
|
||||
border-radius: 9999px;
|
||||
background: var(--hm-drawer-handle-color);
|
||||
}
|
||||
|
||||
[data-slot="drawer-header"] {
|
||||
flex: none;
|
||||
gap: var(--hm-drawer-header-gap);
|
||||
padding: var(--hm-drawer-padding) var(--hm-drawer-padding) 16px;
|
||||
}
|
||||
[data-slot="drawer-title"] {
|
||||
font-size: var(--hm-drawer-title-font-size);
|
||||
line-height: var(--hm-drawer-title-line-height);
|
||||
font-weight: var(--hm-drawer-title-font-weight);
|
||||
color: var(--hm-drawer-text);
|
||||
}
|
||||
[data-slot="drawer-description"] {
|
||||
font-size: var(--hm-drawer-description-font-size);
|
||||
line-height: var(--hm-drawer-description-line-height);
|
||||
font-weight: 400;
|
||||
color: var(--hm-drawer-description-color);
|
||||
}
|
||||
|
||||
/* Body: the only region that scrolls; Header and Footer stay in view. */
|
||||
[data-slot="drawer-body"] {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 0 var(--hm-drawer-padding);
|
||||
}
|
||||
|
||||
/* Footer: actions stack full width (touch targets on a narrow screen), the primary action first as in shadcn. */
|
||||
[data-slot="drawer-footer"] {
|
||||
flex: none;
|
||||
gap: var(--hm-drawer-footer-gap);
|
||||
padding: 16px var(--hm-drawer-padding) var(--hm-drawer-padding);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/* Dropdown Menu mapping. ui-viz has no Dropdown Menu spec (inventory: design pending), so these are current HM values built from
|
||||
the HM Semantic tokens. Radix state attributes (data-highlighted, data-disabled, data-state) drive the states; no HM state attributes.
|
||||
Floating surface values repeat in Popover / Tooltip / Hover Card, but each component keeps its own tokens. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-dropdown-menu-background: var(--hm-sem-floating-background);
|
||||
--hm-dropdown-menu-text: var(--hm-sem-fg-primary);
|
||||
--hm-dropdown-menu-border-width: var(--hm-sem-floating-border-width);
|
||||
--hm-dropdown-menu-border-color: var(--hm-sem-floating-border-color);
|
||||
--hm-dropdown-menu-radius: var(--hm-sem-floating-radius);
|
||||
--hm-dropdown-menu-shadow: var(--hm-sem-floating-shadow);
|
||||
--hm-dropdown-menu-padding: 4px;
|
||||
|
||||
--hm-dropdown-menu-item-height: 36px;
|
||||
--hm-dropdown-menu-item-padding-x: 12px;
|
||||
--hm-dropdown-menu-item-gap: 8px;
|
||||
--hm-dropdown-menu-item-radius: 2px;
|
||||
--hm-dropdown-menu-font-size: 14px;
|
||||
--hm-dropdown-menu-line-height: 20px;
|
||||
--hm-dropdown-menu-font-weight: 400;
|
||||
|
||||
/* Highlighted (hover and keyboard) uses the same face as the Select option: the shadcn accent. */
|
||||
--hm-dropdown-menu-highlight-background: var(--hm-sem-surface-highlight);
|
||||
--hm-dropdown-menu-highlight-text: var(--accent-foreground);
|
||||
--hm-dropdown-menu-destructive-text: var(--hm-sem-action-destructive);
|
||||
--hm-dropdown-menu-destructive-highlight-background: var(--hm-color-action-destructive-hover);
|
||||
|
||||
--hm-dropdown-menu-label-color: var(--hm-sem-fg-secondary);
|
||||
--hm-dropdown-menu-label-font-size: 12px;
|
||||
--hm-dropdown-menu-label-line-height: 18px;
|
||||
--hm-dropdown-menu-label-font-weight: 500;
|
||||
--hm-dropdown-menu-shortcut-color: var(--hm-sem-fg-secondary);
|
||||
--hm-dropdown-menu-shortcut-font-size: 12px;
|
||||
--hm-dropdown-menu-separator-color: var(--hm-sem-stroke-input-default);
|
||||
|
||||
/* Check / radio indicator column at the start of the item. */
|
||||
--hm-dropdown-menu-indicator-size: 16px;
|
||||
--hm-dropdown-menu-radio-dot-size: 8px;
|
||||
}
|
||||
|
||||
[data-slot="dropdown-menu-content"],
|
||||
[data-slot="dropdown-menu-sub-content"] {
|
||||
box-sizing: border-box;
|
||||
padding: var(--hm-dropdown-menu-padding);
|
||||
border: var(--hm-dropdown-menu-border-width) solid var(--hm-dropdown-menu-border-color);
|
||||
border-radius: var(--hm-dropdown-menu-radius);
|
||||
background: var(--hm-dropdown-menu-background);
|
||||
color: var(--hm-dropdown-menu-text);
|
||||
box-shadow: var(--hm-dropdown-menu-shadow);
|
||||
font-size: var(--hm-dropdown-menu-font-size);
|
||||
line-height: var(--hm-dropdown-menu-line-height);
|
||||
font-weight: var(--hm-dropdown-menu-font-weight);
|
||||
}
|
||||
|
||||
[data-slot="dropdown-menu-item"],
|
||||
[data-slot="dropdown-menu-checkbox-item"],
|
||||
[data-slot="dropdown-menu-radio-item"],
|
||||
[data-slot="dropdown-menu-sub-trigger"] {
|
||||
box-sizing: border-box;
|
||||
min-height: var(--hm-dropdown-menu-item-height);
|
||||
gap: var(--hm-dropdown-menu-item-gap);
|
||||
padding: 0 var(--hm-dropdown-menu-item-padding-x);
|
||||
border-radius: var(--hm-dropdown-menu-item-radius);
|
||||
}
|
||||
/* Checkbox / radio items reserve the indicator column; `inset` aligns plain items to the same text start. */
|
||||
[data-slot="dropdown-menu-checkbox-item"],
|
||||
[data-slot="dropdown-menu-radio-item"],
|
||||
[data-slot="dropdown-menu-item"][data-inset],
|
||||
[data-slot="dropdown-menu-sub-trigger"][data-inset] {
|
||||
padding-left: calc(var(--hm-dropdown-menu-item-padding-x) + var(--hm-dropdown-menu-indicator-size) + var(--hm-dropdown-menu-item-gap));
|
||||
}
|
||||
[data-slot="dropdown-menu-item-indicator"] {
|
||||
left: var(--hm-dropdown-menu-item-padding-x);
|
||||
width: var(--hm-dropdown-menu-indicator-size);
|
||||
height: var(--hm-dropdown-menu-indicator-size);
|
||||
}
|
||||
[data-slot="dropdown-menu-radio-dot"] {
|
||||
width: var(--hm-dropdown-menu-radio-dot-size);
|
||||
height: var(--hm-dropdown-menu-radio-dot-size);
|
||||
}
|
||||
|
||||
/* Highlighted: Radix sets data-highlighted for pointer hover and keyboard focus alike. An open sub menu keeps its trigger highlighted. */
|
||||
[data-slot="dropdown-menu-item"][data-highlighted],
|
||||
[data-slot="dropdown-menu-checkbox-item"][data-highlighted],
|
||||
[data-slot="dropdown-menu-radio-item"][data-highlighted],
|
||||
[data-slot="dropdown-menu-sub-trigger"][data-highlighted],
|
||||
[data-slot="dropdown-menu-sub-trigger"][data-state="open"] {
|
||||
background: var(--hm-dropdown-menu-highlight-background);
|
||||
color: var(--hm-dropdown-menu-highlight-text);
|
||||
}
|
||||
[data-slot="dropdown-menu-item"][data-variant="destructive"] {
|
||||
color: var(--hm-dropdown-menu-destructive-text);
|
||||
}
|
||||
[data-slot="dropdown-menu-item"][data-variant="destructive"][data-highlighted] {
|
||||
background: var(--hm-dropdown-menu-destructive-highlight-background);
|
||||
color: var(--hm-dropdown-menu-destructive-text);
|
||||
}
|
||||
|
||||
[data-slot="dropdown-menu-label"] {
|
||||
padding: 6px var(--hm-dropdown-menu-item-padding-x);
|
||||
color: var(--hm-dropdown-menu-label-color);
|
||||
font-size: var(--hm-dropdown-menu-label-font-size);
|
||||
line-height: var(--hm-dropdown-menu-label-line-height);
|
||||
font-weight: var(--hm-dropdown-menu-label-font-weight);
|
||||
}
|
||||
[data-slot="dropdown-menu-label"][data-inset] {
|
||||
padding-left: calc(var(--hm-dropdown-menu-item-padding-x) + var(--hm-dropdown-menu-indicator-size) + var(--hm-dropdown-menu-item-gap));
|
||||
}
|
||||
[data-slot="dropdown-menu-separator"] {
|
||||
margin: var(--hm-dropdown-menu-padding) calc(-1 * var(--hm-dropdown-menu-padding));
|
||||
background: var(--hm-dropdown-menu-separator-color);
|
||||
}
|
||||
[data-slot="dropdown-menu-shortcut"] {
|
||||
color: var(--hm-dropdown-menu-shortcut-color);
|
||||
font-size: var(--hm-dropdown-menu-shortcut-font-size);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
[data-slot="dropdown-menu-item"][data-highlighted] [data-slot="dropdown-menu-shortcut"],
|
||||
[data-slot="dropdown-menu-checkbox-item"][data-highlighted] [data-slot="dropdown-menu-shortcut"],
|
||||
[data-slot="dropdown-menu-radio-item"][data-highlighted] [data-slot="dropdown-menu-shortcut"] {
|
||||
color: inherit;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/* Filter Bar pattern: layout only. The controls inside (Input, Select, Combobox, Date Picker, Button) keep their own component tokens.
|
||||
Spacing values are pattern-local (no global spacing scale exists yet). */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-filter-bar-gap: 8px;
|
||||
--hm-filter-bar-control-width: 180px;
|
||||
--hm-filter-bar-search-width: 280px;
|
||||
--hm-filter-bar-search-icon-size: 16px;
|
||||
--hm-filter-bar-search-icon-inset: 12px;
|
||||
--hm-filter-bar-search-icon-gap: 8px;
|
||||
--hm-filter-bar-search-text-end: 32px; /* clear button 24px + its 6px right inset + 2px */
|
||||
--hm-filter-bar-search-icon-color: var(--hm-sem-fg-placeholder);
|
||||
--hm-filter-bar-search-clear-color: var(--hm-sem-fg-secondary);
|
||||
--hm-filter-bar-narrow-breakpoint: 640px;
|
||||
}
|
||||
|
||||
[data-slot="filter-bar"] {
|
||||
gap: var(--hm-filter-bar-gap);
|
||||
}
|
||||
/* Select / Combobox / Date Picker triggers and a bare Input are w-fit or w-full by themselves; in the bar they get one control width. */
|
||||
[data-slot="filter-bar"] > :is([data-slot="select-trigger"], [data-slot="combobox-trigger"], [data-slot="date-picker-trigger"], [data-slot="input"]) {
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-filter-bar-control-width);
|
||||
max-width: 100%;
|
||||
}
|
||||
[data-slot="filter-bar-search"] {
|
||||
flex: 0 1 var(--hm-filter-bar-search-width);
|
||||
min-width: 0;
|
||||
}
|
||||
[data-slot="filter-bar-search-icon"] {
|
||||
top: 50%;
|
||||
left: var(--hm-filter-bar-search-icon-inset);
|
||||
width: var(--hm-filter-bar-search-icon-size);
|
||||
height: var(--hm-filter-bar-search-icon-size);
|
||||
transform: translateY(-50%);
|
||||
color: var(--hm-filter-bar-search-icon-color);
|
||||
}
|
||||
/* The text starts after the icon (inset + size + gap) and ends before the clear button. Input's Focus / Error / Readonly rules reset
|
||||
padding-inline at the same specificity and load later, so the search re-applies its insets for those states: Focus / Error give
|
||||
back the extra 0.5px border exactly as input.css does (the text does not move), and Readonly (no border) keeps the icon clear. */
|
||||
[data-slot="filter-bar-search"] {
|
||||
--hm-filter-bar-search-text-start: calc(var(--hm-filter-bar-search-icon-inset) + var(--hm-filter-bar-search-icon-size) + var(--hm-filter-bar-search-icon-gap));
|
||||
}
|
||||
[data-slot="filter-bar-search"] [data-slot="input"] {
|
||||
padding-left: var(--hm-filter-bar-search-text-start);
|
||||
padding-right: var(--hm-filter-bar-search-text-end);
|
||||
}
|
||||
[data-slot="filter-bar-search"] [data-slot="input"]:is(:focus-visible, [aria-invalid="true"]) {
|
||||
padding-left: calc(var(--hm-filter-bar-search-text-start) - (var(--hm-input-state-border-width) - var(--hm-input-border-width)));
|
||||
padding-right: calc(var(--hm-filter-bar-search-text-end) - (var(--hm-input-state-border-width) - var(--hm-input-border-width)));
|
||||
}
|
||||
[data-slot="filter-bar-search"] [data-slot="input"][readonly],
|
||||
[data-slot="filter-bar-search"] [data-slot="input"][readonly]:focus-visible {
|
||||
padding-left: var(--hm-filter-bar-search-text-start);
|
||||
padding-right: var(--hm-filter-bar-search-text-end);
|
||||
}
|
||||
[data-slot="filter-bar-search"] [data-slot="input"]::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
[data-slot="filter-bar-search-clear"] {
|
||||
top: 50%;
|
||||
right: 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translateY(-50%);
|
||||
color: var(--hm-filter-bar-search-clear-color);
|
||||
}
|
||||
[data-slot="filter-bar-search-clear"] svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
[data-slot="filter-bar-actions"] {
|
||||
gap: var(--hm-filter-bar-gap);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Narrow screen: search takes a full row, the controls share rows, the actions take their own row. */
|
||||
@media (max-width: 640px) {
|
||||
[data-slot="filter-bar-search"] {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
[data-slot="filter-bar"] > :is([data-slot="select-trigger"], [data-slot="combobox-trigger"], [data-slot="date-picker-trigger"], [data-slot="input"]) {
|
||||
flex: 1 1 calc(50% - var(--hm-filter-bar-gap));
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
[data-slot="filter-bar-actions"] {
|
||||
flex: 1 1 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,573 @@
|
||||
/* GNB pattern (layout visual reference: D:\컴포넌트\ui-viz Patterns/GNB, implementation: current @hm-design/ui primitives).
|
||||
The bar is the ui-viz green top header: 70px, vertical brand gradient, brand left, menu centered on the whole bar, text-only profile
|
||||
right, Beige "current" item joined to the content by two concave corners, White 2-depth panel. The Navigation Menu, Dropdown Menu,
|
||||
Sheet, Button and Avatar keep their own markup, Radix behavior and tokens; here they are only re-colored INSIDE the GNB scope
|
||||
(`[data-slot="gnb"] …`), so their own Stories do not change. Spacing values are pattern-local (no global spacing scale exists).
|
||||
Navigation must use `<NavigationMenu viewport={false}>` so each panel opens under its own trigger at the trigger's width. */
|
||||
:root, [data-hm-theme] {
|
||||
/* Exposed for GNB Mode content layouts: the bar height and the content join below it. Mobile uses a shorter bar. */
|
||||
--hm-gnb-height: 70px;
|
||||
--hm-gnb-join: 12px;
|
||||
/* Same stops as the shared Semantic action surface gradient family (tone 1-4); painted over bar + join as one gradient. */
|
||||
--hm-gnb-background: linear-gradient(180deg, var(--hm-sem-action-surface-stop-0) 0%, var(--hm-sem-action-surface-stop-30) 30%, var(--hm-sem-action-surface-stop-65) 65%, var(--hm-sem-action-surface-stop-100) 100%);
|
||||
--hm-gnb-padding-x: 28px;
|
||||
--hm-gnb-column-gap: 24px;
|
||||
/* Below the z-50 of Popover / Dropdown / Sheet / Dialog (portaled). Applies when `sticky`. */
|
||||
--hm-gnb-z-index: 40;
|
||||
|
||||
--hm-gnb-text: var(--hm-sem-on-action-primary);
|
||||
--hm-gnb-text-secondary: var(--hm-sem-palette-green-tint);
|
||||
--hm-gnb-text-stroke-color: color-mix(in srgb, var(--hm-sem-palette-black) 30%, transparent);
|
||||
--hm-gnb-text-stroke-width: 1px;
|
||||
--hm-gnb-hover-background: color-mix(in srgb, var(--hm-sem-palette-black) 10%, transparent);
|
||||
--hm-gnb-focus-outline: 2px solid var(--hm-sem-on-action-primary);
|
||||
|
||||
--hm-gnb-brand-gap: 12px;
|
||||
--hm-gnb-brand-padding-end: 28px;
|
||||
--hm-gnb-brand-eyebrow-size: 11px;
|
||||
--hm-gnb-brand-eyebrow-spacing: 0.12em;
|
||||
--hm-gnb-brand-name-size: 16px;
|
||||
--hm-gnb-brand-text-gap: 5px;
|
||||
--hm-gnb-logo-max-height: 100%;
|
||||
|
||||
/* Primary menu: 4px above the item, item face 66px = bar 70; first/last item keeps room for the current join curves. */
|
||||
--hm-gnb-menu-inset-top: 4px;
|
||||
--hm-gnb-menu-join: 12px;
|
||||
--hm-gnb-menu-min-width: 112px;
|
||||
--hm-gnb-menu-padding-x: 16px;
|
||||
--hm-gnb-menu-gap: 12px;
|
||||
--hm-gnb-menu-radius: 4px 4px 0 0;
|
||||
--hm-gnb-menu-font-size: 16px;
|
||||
--hm-gnb-menu-font-weight: 700;
|
||||
--hm-gnb-current-background: var(--hm-sem-bg-content);
|
||||
--hm-gnb-current-text: var(--hm-sem-action-primary);
|
||||
--hm-gnb-open-background: var(--hm-sem-bg-surface);
|
||||
--hm-gnb-open-text: var(--hm-sem-action-primary);
|
||||
|
||||
/* 2-depth panel: parent width, White, items centered. Radius and shadow are review values in ui-viz. */
|
||||
--hm-gnb-panel-background: var(--hm-sem-bg-surface);
|
||||
--hm-gnb-panel-radius: 0 0 8px 8px;
|
||||
--hm-gnb-panel-shadow: 0 6px 16px color-mix(in srgb, var(--hm-sem-palette-black) 10%, transparent);
|
||||
--hm-gnb-panel-item-min-height: 56px;
|
||||
--hm-gnb-panel-item-padding: 16px;
|
||||
--hm-gnb-panel-item-font-size: 16px;
|
||||
--hm-gnb-panel-item-line-height: 24px;
|
||||
--hm-gnb-panel-item-text: var(--hm-sem-fg-primary);
|
||||
--hm-gnb-panel-item-hover-background: var(--hm-sem-bg-content);
|
||||
--hm-gnb-panel-item-hover-text: var(--hm-sem-action-primary);
|
||||
|
||||
/* Profile (Avatar default size + name and role; the text is hidden on the mobile bar, the Avatar stays). */
|
||||
--hm-gnb-user-gap: 3px;
|
||||
--hm-gnb-user-radius: var(--hm-radius-control-md);
|
||||
--hm-gnb-user-name-size: 16px;
|
||||
--hm-gnb-user-description-size: 12px;
|
||||
--hm-gnb-user-text-max-width: 220px;
|
||||
|
||||
/* Mobile menu content (inside the HM Sheet, white face). */
|
||||
--hm-gnb-mobile-link-height: 44px;
|
||||
--hm-gnb-mobile-link-padding-x: 12px;
|
||||
--hm-gnb-mobile-link-radius: var(--hm-radius-control-md);
|
||||
--hm-gnb-mobile-link-font-size: 14px;
|
||||
--hm-gnb-mobile-link-line-height: 20px;
|
||||
--hm-gnb-mobile-link-font-weight: 500;
|
||||
--hm-gnb-mobile-link-text: var(--hm-sem-fg-primary);
|
||||
--hm-gnb-mobile-link-hover-background: var(--accent);
|
||||
--hm-gnb-mobile-link-hover-text: var(--accent-foreground);
|
||||
--hm-gnb-mobile-link-current-background: var(--hm-sem-bg-content);
|
||||
--hm-gnb-mobile-link-current-text: var(--hm-sem-action-primary);
|
||||
--hm-gnb-mobile-link-current-font-weight: 700;
|
||||
--hm-gnb-mobile-group-label-color: var(--hm-sem-fg-secondary);
|
||||
--hm-gnb-mobile-group-gap: 16px;
|
||||
--hm-gnb-mobile-nested-gap: 8px;
|
||||
--hm-gnb-mobile-nested-indent: var(--hm-gnb-mobile-link-padding-x);
|
||||
--hm-gnb-mobile-focus-ring: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
|
||||
/* ── Bar ─────────────────────────────────────────────────────────────────────────────────────── */
|
||||
[data-slot="gnb"] {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, auto) minmax(0, 1fr);
|
||||
column-gap: var(--hm-gnb-column-gap);
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
height: var(--hm-gnb-height);
|
||||
padding: 0 var(--hm-gnb-padding-x);
|
||||
background: var(--hm-gnb-background) left top / 100% calc(var(--hm-gnb-height) + var(--hm-gnb-join)) no-repeat;
|
||||
color: var(--hm-gnb-text);
|
||||
font-size: 16px;
|
||||
}
|
||||
/* Continue the gradient only outside the content's rounded top corners. A full strip would cover the content when the sticky
|
||||
header creates a higher stacking context. Static and sticky share this geometry (Dashboard Layout contract). */
|
||||
[data-slot="gnb"]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--hm-gnb-join);
|
||||
background: var(--hm-gnb-background) 0 calc(-1 * var(--hm-gnb-height)) / 100% calc(var(--hm-gnb-height) + var(--hm-gnb-join)) no-repeat;
|
||||
mask:
|
||||
radial-gradient(circle at bottom right, transparent var(--hm-gnb-join), #000 var(--hm-gnb-join)) left top / var(--hm-gnb-join) 100% no-repeat,
|
||||
radial-gradient(circle at bottom left, transparent var(--hm-gnb-join), #000 var(--hm-gnb-join)) right top / var(--hm-gnb-join) 100% no-repeat;
|
||||
pointer-events: none;
|
||||
}
|
||||
[data-slot="gnb"][data-sticky] {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--hm-gnb-z-index);
|
||||
}
|
||||
|
||||
/* ── Brand: horizontal; logo (img / svg) only when supplied, then the name. Eyebrow + name are optional. ───────── */
|
||||
[data-slot="gnb-brand"] {
|
||||
grid-column: 1;
|
||||
justify-self: start;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
height: calc(var(--hm-gnb-height) - 2px);
|
||||
padding-right: var(--hm-gnb-brand-padding-end);
|
||||
gap: var(--hm-gnb-brand-gap);
|
||||
color: var(--hm-gnb-text);
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="gnb-brand"]:focus-visible {
|
||||
outline: var(--hm-gnb-focus-outline);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
[data-slot="gnb-brand"] > :is(img, svg) {
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
max-height: var(--hm-gnb-logo-max-height);
|
||||
}
|
||||
[data-slot="gnb-brand"] > span {
|
||||
display: grid;
|
||||
gap: var(--hm-gnb-brand-text-gap);
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
-webkit-text-stroke: var(--hm-gnb-text-stroke-width) var(--hm-gnb-text-stroke-color);
|
||||
paint-order: stroke fill;
|
||||
}
|
||||
[data-slot="gnb-brand"] :is(small, strong) {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[data-slot="gnb-brand"] small {
|
||||
color: var(--hm-gnb-text-secondary);
|
||||
font-size: var(--hm-gnb-brand-eyebrow-size);
|
||||
font-weight: 700;
|
||||
letter-spacing: var(--hm-gnb-brand-eyebrow-spacing);
|
||||
line-height: 1.2;
|
||||
}
|
||||
[data-slot="gnb-brand"] strong {
|
||||
font-size: var(--hm-gnb-brand-name-size);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ── Primary navigation: centered on the whole bar by the equal side columns ────────────────────────────────── */
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] {
|
||||
grid-column: 2;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
height: var(--hm-gnb-height);
|
||||
/* `clip` (unlike `hidden`) leaves the vertical axis visible so the open panel below the bar is not cut; too-long labels are cut here. */
|
||||
overflow-x: clip;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu"] {
|
||||
flex: 0 1 auto;
|
||||
align-items: flex-start;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-list"] {
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
height: 100%;
|
||||
padding-inline: var(--hm-gnb-menu-join);
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-item"] {
|
||||
height: 100%;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] :is([data-slot="navigation-menu-trigger"], [data-slot="navigation-menu-link"]) {
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
gap: var(--hm-gnb-menu-gap);
|
||||
min-width: var(--hm-gnb-menu-min-width);
|
||||
height: calc(var(--hm-gnb-height) - var(--hm-gnb-menu-inset-top));
|
||||
margin-top: var(--hm-gnb-menu-inset-top);
|
||||
padding: 0 var(--hm-gnb-menu-padding-x);
|
||||
border-radius: var(--hm-gnb-menu-radius);
|
||||
background: transparent;
|
||||
color: var(--hm-gnb-text);
|
||||
font-size: var(--hm-gnb-menu-font-size);
|
||||
font-weight: var(--hm-gnb-menu-font-weight);
|
||||
white-space: nowrap;
|
||||
box-shadow: none;
|
||||
}
|
||||
/* Arrow optical correction: (12px SVG box - 7px painted width) / 4 = 1.25px.
|
||||
Keep the total padding, intrinsic trigger width, gap and icon margin unchanged. */
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-trigger"]:has(> [data-slot="navigation-menu-trigger-icon"]) {
|
||||
padding-left: calc(var(--hm-gnb-menu-padding-x) + 1.25px);
|
||||
padding-right: calc(var(--hm-gnb-menu-padding-x) - 1.25px);
|
||||
}
|
||||
/* Hover / focus (not the current item, not an open parent): Black 10% over the green. */
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] :is([data-slot="navigation-menu-trigger"], [data-slot="navigation-menu-link"]):is(:hover, :focus-visible):not([data-active], [aria-current="page"], [data-state="open"]) {
|
||||
background: var(--hm-gnb-hover-background);
|
||||
color: var(--hm-gnb-text);
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] :is([data-slot="navigation-menu-trigger"], [data-slot="navigation-menu-link"]):focus-visible {
|
||||
outline: var(--hm-gnb-focus-outline);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
/* Current location: Beige face joined to the content below by two concave corners. Hover does not repaint it. Radix `active` on a
|
||||
Link (`data-active`, `aria-current="page"`); a Trigger carries `aria-current="page"` from the consumer. */
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] :is([data-slot="navigation-menu-trigger"], [data-slot="navigation-menu-link"]):is([data-active], [aria-current="page"]) {
|
||||
z-index: 1;
|
||||
background: var(--hm-gnb-current-background);
|
||||
color: var(--hm-gnb-current-text);
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] :is([data-slot="navigation-menu-trigger"], [data-slot="navigation-menu-link"]):is([data-active], [aria-current="page"]):focus-visible {
|
||||
outline-color: var(--hm-gnb-current-text);
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] :is([data-slot="navigation-menu-trigger"], [data-slot="navigation-menu-link"]):is([data-active], [aria-current="page"])::before,
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] :is([data-slot="navigation-menu-trigger"], [data-slot="navigation-menu-link"]):is([data-active], [aria-current="page"])::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: var(--hm-gnb-menu-join);
|
||||
height: var(--hm-gnb-menu-join);
|
||||
pointer-events: none;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] :is([data-slot="navigation-menu-trigger"], [data-slot="navigation-menu-link"]):is([data-active], [aria-current="page"])::before {
|
||||
right: 100%;
|
||||
background: radial-gradient(circle at top left, transparent calc(var(--hm-gnb-menu-join) - 0.5px), var(--hm-gnb-current-background) var(--hm-gnb-menu-join));
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] :is([data-slot="navigation-menu-trigger"], [data-slot="navigation-menu-link"]):is([data-active], [aria-current="page"])::after {
|
||||
left: 100%;
|
||||
background: radial-gradient(circle at top right, transparent calc(var(--hm-gnb-menu-join) - 0.5px), var(--hm-gnb-current-background) var(--hm-gnb-menu-join));
|
||||
}
|
||||
/* Open parent: reuse the current concave joins with the panel's surface.
|
||||
alignPanels supplies each side independently after viewport clamping. */
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-trigger"][data-state="open"] {
|
||||
z-index: 3;
|
||||
background: var(--hm-gnb-open-background);
|
||||
color: var(--hm-gnb-open-text);
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-trigger"][data-state="open"]::before,
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-trigger"][data-state="open"]::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: var(--hm-gnb-menu-join);
|
||||
pointer-events: none;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-trigger"][data-state="open"]::before {
|
||||
right: 100%;
|
||||
width: min(var(--hm-gnb-panel-left-extension, 0px), var(--hm-gnb-menu-join));
|
||||
background: radial-gradient(circle at top left, transparent calc(var(--hm-gnb-menu-join) - 0.5px), var(--hm-gnb-panel-background) var(--hm-gnb-menu-join)) right bottom / var(--hm-gnb-menu-join) var(--hm-gnb-menu-join) no-repeat;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-trigger"][data-state="open"]::after {
|
||||
left: 100%;
|
||||
width: min(var(--hm-gnb-panel-right-extension, 0px), var(--hm-gnb-menu-join));
|
||||
background: radial-gradient(circle at top right, transparent calc(var(--hm-gnb-menu-join) - 0.5px), var(--hm-gnb-panel-background) var(--hm-gnb-menu-join)) left bottom / var(--hm-gnb-menu-join) var(--hm-gnb-menu-join) no-repeat;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-trigger"][data-state="open"]:focus-visible {
|
||||
outline-color: var(--hm-gnb-open-text);
|
||||
}
|
||||
|
||||
/* ── 2-depth panel (viewport={false}): under its own trigger, same width, White, centered items ─────────────── */
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-content"] {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: var(--hm-gnb-panel-radius);
|
||||
background: var(--hm-gnb-panel-background);
|
||||
color: var(--hm-gnb-panel-item-text);
|
||||
box-shadow: var(--hm-gnb-panel-shadow);
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-content"] > ul {
|
||||
display: grid;
|
||||
width: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
list-style: none;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-content"] [data-slot="navigation-menu-link"] {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: var(--hm-gnb-panel-item-min-height);
|
||||
margin: 0;
|
||||
padding: var(--hm-gnb-panel-item-padding);
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: var(--hm-gnb-panel-item-text);
|
||||
font-size: var(--hm-gnb-panel-item-font-size);
|
||||
line-height: var(--hm-gnb-panel-item-line-height);
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
white-space: normal;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-content"] [data-slot="navigation-menu-link"]:is(:hover, :focus-visible, [data-active]) {
|
||||
background: var(--hm-gnb-panel-item-hover-background);
|
||||
color: var(--hm-gnb-panel-item-hover-text);
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-content"] [data-slot="navigation-menu-link"]:focus-visible {
|
||||
outline: 2px solid var(--hm-gnb-panel-item-hover-text);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* ── Utilities and profile ──────────────────────────────────────────────────────────────────────────────── */
|
||||
[data-slot="gnb-utilities"] {
|
||||
grid-column: 3;
|
||||
justify-self: end;
|
||||
min-width: 0;
|
||||
height: calc(var(--hm-gnb-height) - 2px);
|
||||
gap: 4px;
|
||||
}
|
||||
/* Optional icon actions (search, notification...) are HM ghost Buttons; on the green bar they are white with the Black 10% hover. */
|
||||
[data-slot="gnb-utilities"] [data-slot="button"][data-variant="ghost"] {
|
||||
color: var(--hm-gnb-text);
|
||||
}
|
||||
[data-slot="gnb-utilities"] [data-slot="button"][data-variant="ghost"]:is(:hover, [aria-expanded="true"]) {
|
||||
background: var(--hm-gnb-hover-background);
|
||||
color: var(--hm-gnb-text);
|
||||
}
|
||||
[data-slot="gnb-utilities"] [data-slot="button"][data-variant="ghost"]:focus-visible {
|
||||
border-color: transparent;
|
||||
outline: var(--hm-gnb-focus-outline);
|
||||
outline-offset: -2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-slot="gnb-user"] {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
padding: 0 8px;
|
||||
gap: 10px;
|
||||
border: 0;
|
||||
border-radius: var(--hm-gnb-user-radius);
|
||||
background: transparent;
|
||||
color: var(--hm-gnb-text);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="gnb-user"]:is(:hover, [data-state="open"]) {
|
||||
background: var(--hm-gnb-hover-background);
|
||||
}
|
||||
[data-slot="gnb-user"]:focus-visible {
|
||||
outline: var(--hm-gnb-focus-outline);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
[data-slot="gnb-user-text"] {
|
||||
display: grid;
|
||||
gap: var(--hm-gnb-user-gap);
|
||||
min-width: 0;
|
||||
max-width: var(--hm-gnb-user-text-max-width);
|
||||
padding-block: 4px;
|
||||
}
|
||||
[data-slot="gnb-user-name"],
|
||||
[data-slot="gnb-user-description"] {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
-webkit-text-stroke: var(--hm-gnb-text-stroke-width) var(--hm-gnb-text-stroke-color);
|
||||
paint-order: stroke fill;
|
||||
}
|
||||
[data-slot="gnb-user-name"] {
|
||||
font-size: var(--hm-gnb-user-name-size);
|
||||
font-weight: 700;
|
||||
}
|
||||
[data-slot="gnb-user-description"] {
|
||||
color: var(--hm-gnb-text-secondary);
|
||||
font-size: var(--hm-gnb-user-description-size);
|
||||
}
|
||||
|
||||
/* ── Mobile menu content (inside the HM Sheet): destinations as a plain link list ─────────────────────────── */
|
||||
[data-slot="gnb-mobile-nav"] {
|
||||
display: grid;
|
||||
gap: var(--hm-gnb-mobile-group-gap);
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
[data-slot="gnb-mobile-group-label"] {
|
||||
margin: 0;
|
||||
padding: 0 var(--hm-gnb-mobile-link-padding-x) 4px;
|
||||
color: var(--hm-gnb-mobile-group-label-color);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
/* A group inside a group (a deeper local level, e.g. the LNB 3-depth): one link padding further in, with room above its title. */
|
||||
[data-slot="gnb-mobile-group"] [data-slot="gnb-mobile-group"] {
|
||||
margin-top: var(--hm-gnb-mobile-nested-gap);
|
||||
padding-left: var(--hm-gnb-mobile-nested-indent);
|
||||
}
|
||||
[data-slot="gnb-mobile-link"] {
|
||||
box-sizing: border-box;
|
||||
min-height: var(--hm-gnb-mobile-link-height);
|
||||
padding: 0 var(--hm-gnb-mobile-link-padding-x);
|
||||
border-radius: var(--hm-gnb-mobile-link-radius);
|
||||
color: var(--hm-gnb-mobile-link-text);
|
||||
font-size: var(--hm-gnb-mobile-link-font-size);
|
||||
line-height: var(--hm-gnb-mobile-link-line-height);
|
||||
font-weight: var(--hm-gnb-mobile-link-font-weight);
|
||||
text-decoration: none;
|
||||
overflow-wrap: anywhere;
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="gnb-mobile-link"]:is(:hover, :focus-visible) {
|
||||
background: var(--hm-gnb-mobile-link-hover-background);
|
||||
color: var(--hm-gnb-mobile-link-hover-text);
|
||||
}
|
||||
[data-slot="gnb-mobile-link"]:focus-visible {
|
||||
box-shadow: var(--hm-gnb-mobile-focus-ring);
|
||||
}
|
||||
[data-slot="gnb-mobile-link"][aria-current="page"] {
|
||||
background: var(--hm-gnb-mobile-link-current-background);
|
||||
color: var(--hm-gnb-mobile-link-current-text);
|
||||
font-weight: var(--hm-gnb-mobile-link-current-font-weight);
|
||||
}
|
||||
|
||||
/* Desktop panels grow to their longest label. The GNB region measures only the
|
||||
viewport-edge correction; extreme labels scroll within the bounded panel. */
|
||||
@media (min-width: 1024px) {
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] {
|
||||
overflow-x: visible;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-content"] {
|
||||
left: var(--hm-gnb-panel-offset-x, 0px);
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
max-width: calc(100vw - 2 * var(--hm-gnb-padding-x));
|
||||
overflow-x: auto;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-content"] > ul {
|
||||
min-width: max-content;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-content"] [data-slot="navigation-menu-link"] {
|
||||
white-space: nowrap;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
/* The concave current-page joins belong to 1Depth only, not panel rows. */
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-content"] [data-slot="navigation-menu-link"]::before,
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] [data-slot="navigation-menu-content"] [data-slot="navigation-menu-link"]::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Mobile bar: below 1024px (Tailwind `lg`). Menu → hamburger + Sheet; profile → Avatar only. Keep this block last. ──────── */
|
||||
@media (max-width: 1023px) {
|
||||
:root, [data-hm-theme] {
|
||||
--hm-gnb-height: 56px;
|
||||
--hm-gnb-padding-x: 16px;
|
||||
}
|
||||
[data-slot="gnb"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
[data-slot="gnb"] [data-slot="gnb-nav"] {
|
||||
display: none;
|
||||
}
|
||||
[data-slot="gnb-brand"] {
|
||||
flex: 0 1 auto;
|
||||
height: auto;
|
||||
padding-right: 0;
|
||||
}
|
||||
[data-slot="gnb-brand"] small {
|
||||
display: none;
|
||||
}
|
||||
[data-slot="gnb-utilities"] {
|
||||
flex: none;
|
||||
height: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
[data-slot="gnb-user"] {
|
||||
padding: 0 4px;
|
||||
}
|
||||
[data-slot="gnb-user-text"] {
|
||||
display: none;
|
||||
}
|
||||
/* The hamburger is a ghost Button: white on the green bar. */
|
||||
[data-slot="gnb"] > [data-slot="button"][data-variant="ghost"] {
|
||||
color: var(--hm-gnb-text);
|
||||
}
|
||||
[data-slot="gnb"] > [data-slot="button"][data-variant="ghost"]:is(:hover, [aria-expanded="true"]) {
|
||||
background: var(--hm-gnb-hover-background);
|
||||
color: var(--hm-gnb-text);
|
||||
}
|
||||
[data-slot="gnb"] > [data-slot="button"][data-variant="ghost"]:focus-visible {
|
||||
border-color: transparent;
|
||||
outline: var(--hm-gnb-focus-outline);
|
||||
outline-offset: -2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Opt-in GNB ONLY composition. Reuse LNB's DOM, tokens and responsive menu unchanged.
|
||||
These layout hooks ship with @hm-design/ui/theme.css; no Story-only stylesheet is required.
|
||||
The consumer provides the frame height and content padding. */
|
||||
[data-hm-gnb-layout="sidebar"] {
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
[data-hm-gnb-layout="sidebar"] > [data-slot="gnb"] {
|
||||
z-index: var(--hm-gnb-z-index);
|
||||
}
|
||||
[data-hm-gnb-layout="sidebar"] > [data-hm-gnb-body] {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: var(--hm-lnb-width) minmax(0, 1fr);
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
background: var(--hm-sem-palette-green-light-2);
|
||||
border-radius: var(--hm-gnb-join) var(--hm-gnb-join) 0 0;
|
||||
}
|
||||
[data-hm-gnb-layout="sidebar"] > [data-hm-gnb-body] > [data-slot="lnb"][data-hm-gnb-sidebar] {
|
||||
--hm-lnb-background: var(--hm-sem-palette-green-light-2);
|
||||
}
|
||||
[data-hm-gnb-layout="sidebar"] > [data-hm-gnb-body] > [data-hm-gnb-main] {
|
||||
position: relative;
|
||||
grid-column: 2;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
background: var(--hm-sem-bg-content);
|
||||
border-radius: var(--hm-lnb-join) var(--hm-gnb-join) 0 var(--hm-lnb-join);
|
||||
}
|
||||
@media (max-width: 1023px) {
|
||||
[data-hm-gnb-layout="sidebar"] > [data-hm-gnb-body] {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
[data-hm-gnb-layout="sidebar"] > [data-hm-gnb-body] > [data-hm-gnb-main] {
|
||||
grid-column: 1;
|
||||
border-radius: var(--hm-gnb-join) var(--hm-gnb-join) 0 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* Hover Card mapping. ui-viz has no Hover Card spec, so these are current HM values from the HM Semantic tokens.
|
||||
A richer preview than Tooltip (title / description / metadata); it is wider than Popover so a preview reads in one block. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-hover-card-background: var(--hm-sem-floating-background);
|
||||
--hm-hover-card-text: var(--hm-sem-fg-primary);
|
||||
--hm-hover-card-border-width: var(--hm-sem-floating-border-width);
|
||||
--hm-hover-card-border-color: var(--hm-sem-floating-border-color);
|
||||
--hm-hover-card-radius: var(--hm-sem-floating-radius);
|
||||
--hm-hover-card-shadow: var(--hm-sem-floating-shadow);
|
||||
--hm-hover-card-padding: 16px;
|
||||
--hm-hover-card-width: 320px;
|
||||
--hm-hover-card-font-size: 14px;
|
||||
--hm-hover-card-line-height: 20px;
|
||||
--hm-hover-card-font-weight: 400;
|
||||
}
|
||||
|
||||
[data-slot="hover-card-content"] {
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-hover-card-width);
|
||||
max-width: calc(100vw - 16px);
|
||||
padding: var(--hm-hover-card-padding);
|
||||
border: var(--hm-hover-card-border-width) solid var(--hm-hover-card-border-color);
|
||||
border-radius: var(--hm-hover-card-radius);
|
||||
background: var(--hm-hover-card-background);
|
||||
color: var(--hm-hover-card-text);
|
||||
box-shadow: var(--hm-hover-card-shadow);
|
||||
font-size: var(--hm-hover-card-font-size);
|
||||
line-height: var(--hm-hover-card-line-height);
|
||||
font-weight: var(--hm-hover-card-font-weight);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export { runtimeThemeContract, defaultRuntimeTheme } from "./contract"
|
||||
export type { RuntimeTheme, RuntimeThemeToken } from "./contract"
|
||||
export { applyRuntimeTheme, resetRuntimeTheme, isRuntimeTheme } from "./runtime"
|
||||
export { fetchRuntimeTheme, resolveRuntimeTheme, loadRuntimeTheme } from "./loader"
|
||||
export type { RuntimeThemeLoadOptions } from "./loader"
|
||||
@@ -0,0 +1,107 @@
|
||||
/* Input mapping: the control only. Label, Required star and Helper/Error message are consumer composition, not part of Input.
|
||||
States use native selectors (:focus-visible, [aria-invalid="true"], :disabled, [readonly]); size uses data-size. */
|
||||
:root, [data-hm-theme] {
|
||||
/* ui_viz: docs/components/input.md §24, §16 — Confirmed (size, padding-x). Not aliased to Button or the global control height. */
|
||||
--hm-input-height-sm: 28px;
|
||||
--hm-input-height-md: 34px;
|
||||
--hm-input-height-lg: 42px;
|
||||
--hm-input-padding-x-sm: 10px;
|
||||
--hm-input-padding-x-md: 12px;
|
||||
--hm-input-padding-x-lg: 12px;
|
||||
|
||||
/* ui_viz: docs/components/input.md §9, §21 INPUT_VALUE_ROLE_FOLLOWS_SIZE — owner decision (value and placeholder per size) */
|
||||
--hm-input-font-size-sm: 12px;
|
||||
--hm-input-line-height-sm: 18px;
|
||||
--hm-input-font-size-md: 14px;
|
||||
--hm-input-line-height-md: 20px;
|
||||
--hm-input-font-size-lg: 16px;
|
||||
--hm-input-line-height-lg: 24px;
|
||||
--hm-input-font-weight: 400;
|
||||
|
||||
/* ui_viz: docs/components/input.md §10, §11, §1.1 D-005 — Confirmed */
|
||||
--hm-input-radius: var(--hm-radius-input);
|
||||
--hm-input-border-width: 1px;
|
||||
--hm-input-state-border-width: 1.5px;
|
||||
--hm-input-background: var(--hm-sem-bg-surface);
|
||||
--hm-input-text: var(--hm-sem-fg-primary);
|
||||
--hm-input-placeholder: var(--hm-sem-fg-placeholder);
|
||||
--hm-input-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-input-focus-border-color: var(--hm-sem-stroke-input-focus);
|
||||
--hm-input-error-border-color: var(--hm-sem-action-destructive);
|
||||
}
|
||||
|
||||
/* Large is the default when size is omitted. Height is a min-height, so the control keeps one line of text centered. */
|
||||
[data-slot="input"] {
|
||||
--hm-input-height: var(--hm-input-height-lg);
|
||||
--hm-input-padding-x: var(--hm-input-padding-x-lg);
|
||||
--hm-input-font-size: var(--hm-input-font-size-lg);
|
||||
--hm-input-line-height: var(--hm-input-line-height-lg);
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
min-height: var(--hm-input-height);
|
||||
padding: 0 var(--hm-input-padding-x);
|
||||
border: var(--hm-input-border-width) solid var(--hm-input-border-color);
|
||||
border-radius: var(--hm-input-radius);
|
||||
background: var(--hm-input-background);
|
||||
color: var(--hm-input-text);
|
||||
font-size: var(--hm-input-font-size);
|
||||
line-height: var(--hm-input-line-height);
|
||||
font-weight: var(--hm-input-font-weight);
|
||||
box-shadow: none;
|
||||
}
|
||||
[data-slot="input"][data-size="medium"] {
|
||||
--hm-input-height: var(--hm-input-height-md);
|
||||
--hm-input-padding-x: var(--hm-input-padding-x-md);
|
||||
--hm-input-font-size: var(--hm-input-font-size-md);
|
||||
--hm-input-line-height: var(--hm-input-line-height-md);
|
||||
}
|
||||
[data-slot="input"][data-size="small"] {
|
||||
--hm-input-height: var(--hm-input-height-sm);
|
||||
--hm-input-padding-x: var(--hm-input-padding-x-sm);
|
||||
--hm-input-font-size: var(--hm-input-font-size-sm);
|
||||
--hm-input-line-height: var(--hm-input-line-height-sm);
|
||||
}
|
||||
[data-slot="input"]::placeholder {
|
||||
color: var(--hm-input-placeholder);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Focus: the border alone marks it (1.5px, no shadow or glow). Padding gives back the extra 0.5px so the value does not move. */
|
||||
[data-slot="input"]:focus-visible {
|
||||
outline: none;
|
||||
border-width: var(--hm-input-state-border-width);
|
||||
border-color: var(--hm-input-focus-border-color);
|
||||
padding-inline: calc(var(--hm-input-padding-x) - (var(--hm-input-state-border-width) - var(--hm-input-border-width)));
|
||||
}
|
||||
|
||||
/* Invalid owns the border; keyboard focus adds the HM 3px / 50% semantic ring independently. */
|
||||
[data-slot="input"][aria-invalid="true"] {
|
||||
border-width: var(--hm-input-state-border-width);
|
||||
border-color: var(--hm-input-error-border-color);
|
||||
padding-inline: calc(var(--hm-input-padding-x) - (var(--hm-input-state-border-width) - var(--hm-input-border-width)));
|
||||
}
|
||||
[data-slot="input"][aria-invalid="true"]:focus-visible {
|
||||
border-color: var(--hm-input-error-border-color);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
|
||||
/* Disabled keeps the white face. Dimming (opacity 0.6) is set in ui/input.tsx: 2026-10-02 user decision, Input and Select only, not a global Disabled token. */
|
||||
[data-slot="input"]:disabled {
|
||||
background: var(--hm-input-background);
|
||||
}
|
||||
|
||||
/* Readonly: no box, text only, not dimmed. Always Large metrics, whatever the size. */
|
||||
[data-slot="input"][readonly] {
|
||||
--hm-input-height: var(--hm-input-height-lg);
|
||||
--hm-input-font-size: var(--hm-input-font-size-lg);
|
||||
--hm-input-line-height: var(--hm-input-line-height-lg);
|
||||
border-width: 0;
|
||||
padding-inline: 0;
|
||||
background: transparent;
|
||||
}
|
||||
[data-slot="input"][readonly]:focus-visible {
|
||||
border-width: 0;
|
||||
padding-inline: 0;
|
||||
outline: revert;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* Label mapping: typography and color only. Required star, Helper and Error message are consumer composition, not part of Label.
|
||||
ui_viz has no standalone Label spec; the Field Label role is owned by Input / Textarea (docs/components/input.md §9, §10). */
|
||||
:root, [data-hm-theme] {
|
||||
/* ui_viz: docs/components/input.md §9 Field Label → label-md 14/20/500 (CANONICAL_OVERRIDE of the 13/700 Source); textarea.md §2 */
|
||||
--hm-label-font-size: 14px;
|
||||
--hm-label-line-height: 20px;
|
||||
--hm-label-font-weight: 500;
|
||||
/* ui_viz: docs/components/input.md §10, §16 — color.fg.secondary, Confirmed */
|
||||
--hm-label-text: var(--hm-sem-fg-secondary);
|
||||
}
|
||||
|
||||
[data-slot="label"] {
|
||||
color: var(--hm-label-text);
|
||||
font-size: var(--hm-label-font-size);
|
||||
line-height: var(--hm-label-line-height);
|
||||
font-weight: var(--hm-label-font-weight);
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
/* LNB pattern (layout visual reference: D:\컴포넌트\ui-viz Patterns/LNB, implementation: HM Collapsible + plain elements).
|
||||
A 272px green column: horizontal 5-stop surface, brand on top, menu in the middle, text-only profile at the bottom. The current
|
||||
item is a Beige face (left corners rounded) that runs to the content edge and joins it through two concave corners; hover is
|
||||
Black 10% to the same edge; an open group is a Black 20% face with a 1px green line. Spacing values are pattern-local. */
|
||||
:root, [data-hm-theme] {
|
||||
/* Exposed for the layout: grid column width and the surface that continues behind the content's rounded corners. */
|
||||
--hm-lnb-width: 272px;
|
||||
--hm-lnb-join: 18px;
|
||||
--hm-lnb-background: linear-gradient(90deg, var(--hm-sem-action-primary) 0%, var(--hm-sem-palette-green-light-1) 30%, var(--hm-sem-palette-green-light-2) 50%, var(--hm-sem-palette-green-light-3) 90%, var(--hm-sem-palette-green-light-2) 100%);
|
||||
--hm-lnb-background-end: var(--hm-sem-palette-green-light-2);
|
||||
--hm-lnb-padding-top: 28px;
|
||||
--hm-lnb-padding-bottom: 22px;
|
||||
--hm-lnb-inset-start: 20px;
|
||||
--hm-lnb-inset-end: 20px;
|
||||
/* Menu top (ui-viz 20 under the brand). Padding inside the scroll region, so the scroll starts right under the brand; keep it at
|
||||
least `--hm-lnb-join` so the curve above a current first item is not clipped (the bottom padding is the join for the same reason). */
|
||||
--hm-lnb-nav-padding-top: 20px;
|
||||
|
||||
--hm-lnb-text: var(--hm-sem-on-action-primary);
|
||||
--hm-lnb-text-secondary: var(--hm-sem-palette-green-tint);
|
||||
--hm-lnb-text-stroke-color: color-mix(in srgb, var(--hm-sem-palette-black) 30%, transparent);
|
||||
--hm-lnb-focus-outline: 2px solid var(--hm-sem-on-action-primary);
|
||||
|
||||
--hm-lnb-brand-padding-x: 30px;
|
||||
--hm-lnb-brand-padding-bottom: 22px;
|
||||
--hm-lnb-brand-gap: 12px;
|
||||
--hm-lnb-brand-eyebrow-size: 11px;
|
||||
--hm-lnb-brand-eyebrow-spacing: 0.12em;
|
||||
--hm-lnb-brand-name-size: 17px;
|
||||
--hm-lnb-brand-text-gap: 5px;
|
||||
|
||||
--hm-lnb-item-min-height: 56px;
|
||||
--hm-lnb-item-padding-start: 18px;
|
||||
--hm-lnb-item-gap: 12px;
|
||||
--hm-lnb-item-font-size: 18px;
|
||||
--hm-lnb-item-line-height: 28px;
|
||||
--hm-lnb-item-font-weight: 700;
|
||||
--hm-lnb-item-radius: 4px 0 0 4px;
|
||||
--hm-lnb-item-icon-size: 20px;
|
||||
--hm-lnb-item-icon-opacity: 0.5;
|
||||
--hm-lnb-item-icon-stroke: 1.7;
|
||||
--hm-lnb-item-text-stroke-width: 1.5px;
|
||||
--hm-lnb-item-text-shadow: 1px 2px 1px var(--hm-lnb-text-stroke-color);
|
||||
--hm-lnb-hover-background: color-mix(in srgb, var(--hm-sem-palette-black) 10%, transparent);
|
||||
|
||||
--hm-lnb-current-background: var(--hm-sem-bg-content);
|
||||
--hm-lnb-current-text: var(--hm-sem-action-primary);
|
||||
--hm-lnb-current-radius: 8px 0 0 8px;
|
||||
/* Inset depth of the current face: left edge, top, bottom; the last quarter fades so the content side stays open. */
|
||||
--hm-lnb-current-depth:
|
||||
linear-gradient(to right, color-mix(in srgb, var(--hm-sem-palette-black) 30%, transparent), transparent 6px) 0 0 / 75% 100% no-repeat,
|
||||
linear-gradient(to bottom, color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent), transparent 3px) 0 0 / 75% 100% no-repeat,
|
||||
linear-gradient(to top, color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent), transparent 3px) 0 0 / 75% 100% no-repeat;
|
||||
|
||||
--hm-lnb-group-open-background: color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
|
||||
--hm-lnb-group-open-line: 1px solid var(--hm-sem-palette-green-light-2);
|
||||
--hm-lnb-group-radius: 8px 0 0 8px;
|
||||
--hm-lnb-group-current-text: var(--hm-sem-palette-yellow-accent);
|
||||
--hm-lnb-group-arrow-size: 18px;
|
||||
--hm-lnb-subitem-padding-start: 68px;
|
||||
/* Align the 2-depth link surface with the 1-depth group text; retain its existing text inset. */
|
||||
--hm-lnb-subitem-surface-inset: calc(var(--hm-lnb-item-padding-start) + var(--hm-lnb-group-arrow-size) + var(--hm-lnb-item-gap));
|
||||
--hm-lnb-group-padding-bottom: 8px;
|
||||
|
||||
/* 3-depth: the items of an `LNBGroup` nested in a group. Same 18px; the hierarchy is the extra 20px indent and the connector
|
||||
(a vertical line at the 2-depth text start, a 12px branch to each row's middle, 8px to the text; it ends at the last row). */
|
||||
--hm-lnb-third-min-height: 48px;
|
||||
--hm-lnb-third-padding-block: 12px;
|
||||
--hm-lnb-third-padding-start: 88px;
|
||||
--hm-lnb-third-line-height: 24px;
|
||||
--hm-lnb-third-font-weight: 500;
|
||||
--hm-lnb-third-current-font-weight: 700;
|
||||
--hm-lnb-third-connector-x: var(--hm-lnb-subitem-padding-start);
|
||||
--hm-lnb-third-connector-length: 12px;
|
||||
--hm-lnb-third-connector-width: 1px;
|
||||
--hm-lnb-third-connector-color: color-mix(in srgb, var(--hm-sem-palette-green-tint) 40%, transparent);
|
||||
--hm-lnb-third-connector-current-color: color-mix(in srgb, var(--hm-sem-palette-green-light-2) 40%, transparent);
|
||||
|
||||
--hm-lnb-profile-padding-x: 30px;
|
||||
--hm-lnb-profile-padding-top: 22px;
|
||||
--hm-lnb-profile-gap: 12px;
|
||||
--hm-lnb-profile-name-size: 16px;
|
||||
--hm-lnb-profile-description-size: 12px;
|
||||
}
|
||||
|
||||
[data-slot="lnb"] {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-lnb-width);
|
||||
min-height: 100%;
|
||||
padding: var(--hm-lnb-padding-top) 0 var(--hm-lnb-padding-bottom);
|
||||
background: var(--hm-lnb-background);
|
||||
color: var(--hm-lnb-text);
|
||||
}
|
||||
/* The surface keeps going for `--hm-lnb-join` behind the rounded left corners of the content next to the column. */
|
||||
[data-slot="lnb"]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 100%;
|
||||
width: var(--hm-lnb-join);
|
||||
background: var(--hm-lnb-background-end);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Brand (vertical) and profile: text with the dark text border. */
|
||||
[data-slot="lnb-brand"] {
|
||||
flex: none;
|
||||
gap: var(--hm-lnb-brand-gap);
|
||||
align-items: flex-start;
|
||||
padding: 0 var(--hm-lnb-brand-padding-x) var(--hm-lnb-brand-padding-bottom);
|
||||
}
|
||||
[data-slot="lnb-brand"] > :is(img, svg) {
|
||||
flex: 0 0 auto;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
[data-slot="lnb-brand"] > span {
|
||||
display: grid;
|
||||
gap: var(--hm-lnb-brand-text-gap);
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
[data-slot="lnb-brand"] :is(small, strong),
|
||||
[data-slot="lnb-profile"] :is(strong, p) {
|
||||
-webkit-text-stroke: 1px var(--hm-lnb-text-stroke-color);
|
||||
paint-order: stroke fill;
|
||||
}
|
||||
[data-slot="lnb-brand"] small {
|
||||
color: var(--hm-lnb-text-secondary);
|
||||
font-size: var(--hm-lnb-brand-eyebrow-size);
|
||||
font-weight: 700;
|
||||
letter-spacing: var(--hm-lnb-brand-eyebrow-spacing);
|
||||
line-height: 1.2;
|
||||
}
|
||||
[data-slot="lnb-brand"] strong {
|
||||
font-size: var(--hm-lnb-brand-name-size);
|
||||
font-weight: 700;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-slot="lnb"] > [data-slot="lnb-profile"] {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
gap: var(--hm-lnb-profile-gap);
|
||||
align-self: flex-start;
|
||||
width: fit-content;
|
||||
/* GNB profile's 8px inner hit padding; the existing 30px inset still places the Avatar. */
|
||||
max-width: calc(100% - 2 * (var(--hm-lnb-profile-padding-x) - 8px));
|
||||
margin-top: auto;
|
||||
margin-inline: calc(var(--hm-lnb-profile-padding-x) - 8px);
|
||||
min-height: var(--hm-lnb-third-min-height);
|
||||
padding: 0 8px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="lnb"] > [data-slot="lnb-profile"]:is(:hover, [data-state="open"]) {
|
||||
background: var(--hm-lnb-hover-background);
|
||||
}
|
||||
[data-slot="lnb"] > [data-slot="lnb-profile"]:focus-visible {
|
||||
outline: var(--hm-lnb-focus-outline);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
[data-slot="lnb-profile-text"] {
|
||||
min-width: 0;
|
||||
}
|
||||
[data-slot="lnb-profile"] strong {
|
||||
display: block;
|
||||
font-size: var(--hm-lnb-profile-name-size);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-slot="lnb-profile"] p {
|
||||
margin: 4px 0 0;
|
||||
color: var(--hm-lnb-text-secondary);
|
||||
font-size: var(--hm-lnb-profile-description-size);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Menu region and the only scroll container of the column (brand and profile are `flex: none`; the column itself never scrolls). In a
|
||||
column of fixed height it fills the space between them (`min-height: 0` lets it shrink below the menu height), so it starts at the
|
||||
brand bottom and ends at the profile top. It is `inset-end + 1px` wider than the column's inner area so a current item can overlap
|
||||
the content by 1px; vertical padding keeps the join curves of the first / last item from being clipped. `direction: rtl` only moves
|
||||
the native scrollbar to the left edge, so it never takes width from the right side where the current item joins the content; the
|
||||
menu itself is LTR again (direct children, see "Menu rows" below). */
|
||||
[data-slot="lnb-nav"] {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
direction: rtl;
|
||||
margin: 0 -1px 0 0;
|
||||
padding: var(--hm-lnb-nav-padding-top) 0 var(--hm-lnb-join) var(--hm-lnb-inset-start);
|
||||
overflow-x: clip;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: color-mix(in srgb, var(--hm-sem-palette-white) 35%, transparent) transparent;
|
||||
}
|
||||
/* Layout owns the old top spacing only when this column owns a Profile. */
|
||||
[data-slot="lnb"]:has(> [data-slot="lnb-profile"]) > [data-slot="lnb-nav"] {
|
||||
margin-bottom: var(--hm-lnb-profile-padding-top);
|
||||
}
|
||||
|
||||
/* ── Item ───────────────────────────────────────────────────────────────────────────────────────────── */
|
||||
[data-slot="lnb-item"],
|
||||
[data-slot="lnb-group-trigger"] {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
gap: var(--hm-lnb-item-gap);
|
||||
width: calc(100% - 1px);
|
||||
min-height: var(--hm-lnb-item-min-height);
|
||||
padding: 14px calc(var(--hm-lnb-inset-end) + 12px) 14px var(--hm-lnb-item-padding-start);
|
||||
border: 0;
|
||||
border-radius: var(--hm-lnb-item-radius);
|
||||
background: transparent;
|
||||
color: var(--hm-lnb-text);
|
||||
font-family: inherit;
|
||||
font-size: var(--hm-lnb-item-font-size);
|
||||
line-height: var(--hm-lnb-item-line-height);
|
||||
font-weight: var(--hm-lnb-item-font-weight);
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: anywhere;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
-webkit-text-stroke: var(--hm-lnb-item-text-stroke-width) var(--hm-lnb-text-stroke-color);
|
||||
paint-order: stroke fill;
|
||||
text-shadow: var(--hm-lnb-item-text-shadow);
|
||||
}
|
||||
[data-slot="lnb-item"] > svg:not([data-slot]) {
|
||||
flex: 0 0 auto;
|
||||
width: var(--hm-lnb-item-icon-size);
|
||||
height: var(--hm-lnb-item-icon-size);
|
||||
opacity: var(--hm-lnb-item-icon-opacity);
|
||||
stroke-width: var(--hm-lnb-item-icon-stroke);
|
||||
}
|
||||
[data-slot="lnb-item"]:not([aria-current="page"]):hover,
|
||||
[data-slot="lnb-group-trigger"]:hover {
|
||||
background: var(--hm-lnb-hover-background);
|
||||
}
|
||||
[data-slot="lnb-item"]:focus-visible,
|
||||
[data-slot="lnb-group-trigger"]:focus-visible {
|
||||
outline: var(--hm-lnb-focus-outline);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* Current: Beige face, left corners 8, runs 1px into the content, concave corners above and below, inset depth. No text effects. */
|
||||
[data-slot="lnb-item"][aria-current="page"] {
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
background: var(--hm-lnb-current-depth), var(--hm-lnb-current-background);
|
||||
border-radius: var(--hm-lnb-current-radius);
|
||||
color: var(--hm-lnb-current-text);
|
||||
-webkit-text-stroke: 0;
|
||||
text-shadow: none;
|
||||
}
|
||||
[data-slot="lnb-item"][aria-current="page"]:focus-visible {
|
||||
outline-color: var(--hm-lnb-current-text);
|
||||
}
|
||||
[data-slot="lnb-item"][aria-current="page"]::before,
|
||||
[data-slot="lnb-item"][aria-current="page"]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
width: var(--hm-lnb-join);
|
||||
height: var(--hm-lnb-join);
|
||||
pointer-events: none;
|
||||
}
|
||||
[data-slot="lnb-item"][aria-current="page"]::before {
|
||||
bottom: 100%;
|
||||
background: radial-gradient(circle at top left, transparent calc(var(--hm-lnb-join) - 0.5px), var(--hm-lnb-current-background) var(--hm-lnb-join));
|
||||
}
|
||||
[data-slot="lnb-item"][aria-current="page"]::after {
|
||||
top: 100%;
|
||||
background: radial-gradient(circle at bottom left, transparent calc(var(--hm-lnb-join) - 0.5px), var(--hm-lnb-current-background) var(--hm-lnb-join));
|
||||
}
|
||||
|
||||
/* ── Group (HM Collapsible) ─────────────────────────────────────────────────────────────────────────── */
|
||||
[data-slot="lnb-group"] {
|
||||
position: relative;
|
||||
width: calc(100% - 1px);
|
||||
border-radius: var(--hm-lnb-group-radius);
|
||||
}
|
||||
[data-slot="lnb-group"][data-state="open"] {
|
||||
background: var(--hm-lnb-group-open-background);
|
||||
}
|
||||
[data-slot="lnb-group"][data-state="open"]::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: var(--hm-lnb-group-open-line);
|
||||
border-right: 0;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
}
|
||||
[data-slot="lnb-group-trigger"] {
|
||||
width: 100%;
|
||||
border-radius: var(--hm-lnb-group-radius);
|
||||
}
|
||||
[data-slot="lnb-group"][data-state="open"] > [data-slot="lnb-group-trigger"]:hover {
|
||||
border-radius: 8px 0 0 0;
|
||||
}
|
||||
[data-slot="lnb-group"][data-current] > [data-slot="lnb-group-trigger"] {
|
||||
color: var(--hm-lnb-group-current-text);
|
||||
}
|
||||
[data-slot="lnb-group-arrow"] {
|
||||
flex: 0 0 auto;
|
||||
width: var(--hm-lnb-group-arrow-size);
|
||||
height: var(--hm-lnb-group-arrow-size);
|
||||
stroke-width: 2;
|
||||
filter: drop-shadow(var(--hm-lnb-item-text-shadow));
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
[data-slot="lnb-group-trigger"][data-state="open"] > [data-slot="lnb-group-arrow"] {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
[data-slot="lnb-group-content"][data-state="open"] {
|
||||
display: grid;
|
||||
padding-bottom: var(--hm-lnb-group-padding-bottom);
|
||||
}
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-item"] {
|
||||
width: 100%;
|
||||
padding-left: var(--hm-lnb-subitem-padding-start);
|
||||
border-radius: 0;
|
||||
}
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-item"]:not([aria-current="page"]):hover {
|
||||
border-radius: var(--hm-lnb-item-radius);
|
||||
}
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-item"][aria-current="page"] {
|
||||
width: calc(100% + 1px);
|
||||
margin-right: -1px;
|
||||
border-radius: var(--hm-lnb-current-radius);
|
||||
}
|
||||
/* Only 2-depth destination links: the group keeps its full layout width, while hover, current, focus and click belong to the
|
||||
inset link. Subtract the same inset from text padding and width to preserve text wrapping and the right content join. */
|
||||
[data-slot="lnb-nav"] > [data-slot="lnb-group"] > [data-slot="lnb-group-content"] > [data-slot="lnb-item"] {
|
||||
width: calc(100% - var(--hm-lnb-subitem-surface-inset));
|
||||
margin-left: var(--hm-lnb-subitem-surface-inset);
|
||||
padding-left: calc(var(--hm-lnb-subitem-padding-start) - var(--hm-lnb-subitem-surface-inset));
|
||||
}
|
||||
[data-slot="lnb-nav"] > [data-slot="lnb-group"] > [data-slot="lnb-group-content"] > [data-slot="lnb-item"][aria-current="page"] {
|
||||
width: calc(100% + 1px - var(--hm-lnb-subitem-surface-inset));
|
||||
}
|
||||
|
||||
/* ── Menu rows (direct children of the menu region) ─────────────────────────────────────────────────── */
|
||||
/* Back to LTR. A left scrollbar narrows the content box from the left, so each row starts that much (the scrollbar width, 0 without
|
||||
one) to the left again: `100%` is the content box, the region is `--hm-lnb-width + 1px` wide with `--hm-lnb-inset-start` padding. The
|
||||
scrollbar sits in that padding and the rows keep x 20 → 272 (current 273) with or without it. */
|
||||
[data-slot="lnb-nav"] > * {
|
||||
direction: ltr;
|
||||
margin-left: calc(100% + var(--hm-lnb-inset-start) - var(--hm-lnb-width) - 1px);
|
||||
}
|
||||
[data-slot="lnb-nav"] > :is([data-slot="lnb-item"], [data-slot="lnb-group"]) {
|
||||
width: auto;
|
||||
margin-right: 1px;
|
||||
}
|
||||
[data-slot="lnb-nav"] > [data-slot="lnb-item"][aria-current="page"] {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* ── Nested group (2-depth group inside a group) ────────────────────────────────────────────────────── */
|
||||
/* The outer group already draws the open face and line, so a nested group adds no face, line, padding or width loss; its name
|
||||
lines up with the 2-depth item text. */
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-group"] {
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-group"][data-state="open"] {
|
||||
background: none;
|
||||
}
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-group"][data-state="open"]::before {
|
||||
content: none;
|
||||
}
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-group-trigger"] {
|
||||
padding-left: calc(var(--hm-lnb-subitem-padding-start) - var(--hm-lnb-group-arrow-size) - var(--hm-lnb-item-gap));
|
||||
border-radius: 0;
|
||||
}
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-group"] > [data-slot="lnb-group-trigger"]:hover {
|
||||
border-radius: var(--hm-lnb-item-radius);
|
||||
}
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-group-content"][data-state="open"] {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* 3-depth only: the row owns the existing connector gradients; the link begins at the branch end.
|
||||
Decoration follows the row's real middle when text wraps and never owns pointer interaction. */
|
||||
[data-slot="lnb-third-row"] {
|
||||
--hm-lnb-third-line: var(--hm-lnb-third-connector-color);
|
||||
--hm-lnb-third-line-end: 100%;
|
||||
--hm-lnb-third-connector:
|
||||
linear-gradient(var(--hm-lnb-third-line), var(--hm-lnb-third-line)) var(--hm-lnb-third-connector-x) 0 / var(--hm-lnb-third-connector-width) var(--hm-lnb-third-line-end) no-repeat,
|
||||
linear-gradient(var(--hm-lnb-third-line), var(--hm-lnb-third-line)) var(--hm-lnb-third-connector-x) calc(50% - var(--hm-lnb-third-connector-width) / 2) / var(--hm-lnb-third-connector-length) var(--hm-lnb-third-connector-width) no-repeat;
|
||||
position: relative;
|
||||
padding-left: calc(var(--hm-lnb-third-connector-x) + var(--hm-lnb-third-connector-length));
|
||||
}
|
||||
[data-slot="lnb-third-row"]::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--hm-lnb-third-connector);
|
||||
pointer-events: none;
|
||||
}
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-third-row"] > [data-slot="lnb-item"] {
|
||||
min-height: var(--hm-lnb-third-min-height);
|
||||
padding-top: var(--hm-lnb-third-padding-block);
|
||||
padding-bottom: var(--hm-lnb-third-padding-block);
|
||||
padding-left: calc(var(--hm-lnb-third-padding-start) - var(--hm-lnb-third-connector-x) - var(--hm-lnb-third-connector-length));
|
||||
line-height: var(--hm-lnb-third-line-height);
|
||||
font-weight: var(--hm-lnb-third-font-weight);
|
||||
-webkit-text-stroke: 0;
|
||||
text-shadow: none;
|
||||
}
|
||||
/* The vertical line ends at the middle of the last row. */
|
||||
[data-slot="lnb-third-row"]:last-child {
|
||||
--hm-lnb-third-line-end: 50%;
|
||||
}
|
||||
[data-slot="lnb-group-content"] [data-slot="lnb-third-row"] > [data-slot="lnb-item"][aria-current="page"] {
|
||||
font-weight: var(--hm-lnb-third-current-font-weight);
|
||||
}
|
||||
|
||||
/* Desktop only: below 1024px the column is gone and the layout column collapses with it. */
|
||||
@media (max-width: 1023px) {
|
||||
:root, [data-hm-theme] {
|
||||
--hm-lnb-width: 0px;
|
||||
}
|
||||
[data-slot="lnb"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* LNB Mobile Header is the mobile representation of LNB Mode, independent of GNB/Dashboard. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-lnb-mobile-height: 56px;
|
||||
--hm-lnb-mobile-join: 12px;
|
||||
--hm-lnb-mobile-background: linear-gradient(180deg, var(--hm-sem-action-surface-stop-0) 0%, var(--hm-sem-action-surface-stop-30) 30%, var(--hm-sem-action-surface-stop-65) 65%, var(--hm-sem-action-surface-stop-100) 100%);
|
||||
/* Mobile menu content (inside the HM Sheet, white face). */
|
||||
--hm-lnb-mobile-link-height: 44px;
|
||||
--hm-lnb-mobile-link-padding-x: 12px;
|
||||
--hm-lnb-mobile-link-radius: var(--hm-radius-control-md);
|
||||
--hm-lnb-mobile-link-font-size: 14px;
|
||||
--hm-lnb-mobile-link-line-height: 20px;
|
||||
--hm-lnb-mobile-link-font-weight: 500;
|
||||
--hm-lnb-mobile-link-text: var(--hm-sem-fg-primary);
|
||||
--hm-lnb-mobile-link-hover-background: var(--accent);
|
||||
--hm-lnb-mobile-link-hover-text: var(--accent-foreground);
|
||||
--hm-lnb-mobile-link-current-background: var(--hm-sem-bg-content);
|
||||
--hm-lnb-mobile-link-current-text: var(--hm-sem-action-primary);
|
||||
--hm-lnb-mobile-link-current-font-weight: 700;
|
||||
--hm-lnb-mobile-group-label-color: var(--hm-sem-fg-secondary);
|
||||
--hm-lnb-mobile-group-gap: 16px;
|
||||
--hm-lnb-mobile-nested-gap: 8px;
|
||||
--hm-lnb-mobile-nested-indent: var(--hm-lnb-mobile-link-padding-x);
|
||||
--hm-lnb-mobile-focus-ring: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
[data-slot="lnb-mobile-header"] { display: none; }
|
||||
[data-slot="lnb-mobile-nav"] {
|
||||
display: grid;
|
||||
gap: var(--hm-lnb-mobile-group-gap);
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
[data-slot="lnb-mobile-group-label"] {
|
||||
margin: 0;
|
||||
padding: 0 var(--hm-lnb-mobile-link-padding-x) 4px;
|
||||
color: var(--hm-lnb-mobile-group-label-color);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
/* A group inside a group (a deeper local level, e.g. the LNB 3-depth): one link padding further in, with room above its title. */
|
||||
[data-slot="lnb-mobile-group"] [data-slot="lnb-mobile-group"] {
|
||||
margin-top: var(--hm-lnb-mobile-nested-gap);
|
||||
padding-left: var(--hm-lnb-mobile-nested-indent);
|
||||
}
|
||||
[data-slot="lnb-mobile-link"] {
|
||||
box-sizing: border-box;
|
||||
min-height: var(--hm-lnb-mobile-link-height);
|
||||
padding: 0 var(--hm-lnb-mobile-link-padding-x);
|
||||
border-radius: var(--hm-lnb-mobile-link-radius);
|
||||
color: var(--hm-lnb-mobile-link-text);
|
||||
font-size: var(--hm-lnb-mobile-link-font-size);
|
||||
line-height: var(--hm-lnb-mobile-link-line-height);
|
||||
font-weight: var(--hm-lnb-mobile-link-font-weight);
|
||||
text-decoration: none;
|
||||
overflow-wrap: anywhere;
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="lnb-mobile-link"]:is(:hover, :focus-visible) {
|
||||
background: var(--hm-lnb-mobile-link-hover-background);
|
||||
color: var(--hm-lnb-mobile-link-hover-text);
|
||||
}
|
||||
[data-slot="lnb-mobile-link"]:focus-visible {
|
||||
box-shadow: var(--hm-lnb-mobile-focus-ring);
|
||||
}
|
||||
[data-slot="lnb-mobile-link"][aria-current="page"] {
|
||||
background: var(--hm-lnb-mobile-link-current-background);
|
||||
color: var(--hm-lnb-mobile-link-current-text);
|
||||
font-weight: var(--hm-lnb-mobile-link-current-font-weight);
|
||||
}
|
||||
|
||||
|
||||
[data-slot="lnb-mobile-nav"] [data-slot="lnb-profile"] {
|
||||
display: flex; align-items: center; gap: 12px; width: 100%; padding: 8px;
|
||||
border-radius: var(--hm-radius-control-md); color: var(--hm-sem-fg-primary); text-align: left; cursor: pointer;
|
||||
}
|
||||
[data-slot="lnb-mobile-nav"] [data-slot="lnb-profile"]:hover { background: var(--accent); }
|
||||
[data-slot="lnb-mobile-nav"] [data-slot="lnb-profile"]:focus-visible { outline: 2px solid var(--ring); }
|
||||
[data-slot="lnb-mobile-nav"] [data-slot="lnb-profile"] p { color: var(--hm-sem-fg-secondary); }
|
||||
@media (max-width: 1023px) {
|
||||
[data-slot="lnb-mobile-header"] {
|
||||
position: relative; display: flex; flex: none; align-items: center; gap: 8px;
|
||||
height: var(--hm-lnb-mobile-height); padding: 0 16px;
|
||||
background: var(--hm-lnb-mobile-background) left top / 100% calc(var(--hm-lnb-mobile-height) + var(--hm-lnb-mobile-join)) no-repeat;
|
||||
color: var(--hm-sem-on-action-primary);
|
||||
}
|
||||
[data-slot="lnb-mobile-header"]::after {
|
||||
content: ""; position: absolute; inset: 100% 0 auto; height: var(--hm-lnb-mobile-join);
|
||||
background: var(--hm-lnb-mobile-background) left bottom / 100% calc(var(--hm-lnb-mobile-height) + var(--hm-lnb-mobile-join)) no-repeat;
|
||||
pointer-events: none;
|
||||
}
|
||||
[data-slot="lnb-mobile-brand"] { min-width: 0; gap: 12px; text-decoration: none; }
|
||||
[data-slot="lnb-mobile-brand"] :is(span, strong) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
[data-slot="lnb-mobile-brand"] strong { font-size: 16px; font-weight: 700; }
|
||||
[data-slot="lnb-mobile-brand"] small { display: none; }
|
||||
[data-slot="lnb-mobile-brand"] > :is(img, svg) { max-height: 32px; width: auto; }
|
||||
[data-slot="lnb-mobile-header"] > [data-slot="button"] { color: var(--hm-sem-on-action-primary); }
|
||||
[data-slot="lnb-mobile-header"] > [data-slot="button"]:hover { background: var(--hm-lnb-hover-background); }
|
||||
[data-slot="lnb-mobile-header"] > [data-slot="button"]:focus-visible { outline: var(--hm-lnb-focus-outline); outline-offset: -2px; box-shadow: none; }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { defaultRuntimeTheme, type RuntimeTheme } from "./contract"
|
||||
import { applyRuntimeTheme, isRuntimeTheme, resetRuntimeTheme } from "./runtime"
|
||||
import { beginThemeRequest, isCurrentThemeRequest } from "./requests"
|
||||
|
||||
/** Fetch/validate only. No DOM access, fixed URL, storage or framework dependency. */
|
||||
export async function fetchRuntimeTheme(url: string | URL, request?: RequestInit): Promise<RuntimeTheme> {
|
||||
const response = await fetch(url, request)
|
||||
if (!response.ok) throw new Error(`Theme request failed: HTTP ${response.status}`)
|
||||
const theme: unknown = await response.json()
|
||||
if (!isRuntimeTheme(theme)) throw new Error("Invalid Runtime Theme v1 document")
|
||||
return { schemaVersion: 1, tokens: { ...theme.tokens } }
|
||||
}
|
||||
|
||||
/** Resolve a supplied default + partial override using the existing v1 contract. */
|
||||
export function resolveRuntimeTheme(theme: unknown, defaults: unknown = defaultRuntimeTheme): RuntimeTheme {
|
||||
if (!isRuntimeTheme(defaults) || !isRuntimeTheme(theme)) throw new Error("Invalid Runtime Theme v1 document")
|
||||
return { schemaVersion: 1, tokens: { ...defaultRuntimeTheme.tokens, ...defaults.tokens, ...theme.tokens } }
|
||||
}
|
||||
|
||||
export type RuntimeThemeLoadOptions = { request?: RequestInit; defaults?: RuntimeTheme }
|
||||
|
||||
/** Convenience path: CDN/settings URL -> validate -> optional merge -> apply.
|
||||
* Failure releases this API's override to the host/HM CSS fallback.
|
||||
* Superseded requests never apply or reset the newer owner's theme.
|
||||
*/
|
||||
export async function loadRuntimeTheme(root: HTMLElement, url: string | URL, options: RuntimeThemeLoadOptions = {}): Promise<boolean> {
|
||||
const request = beginThemeRequest(root)
|
||||
try {
|
||||
const theme = await fetchRuntimeTheme(url, options.request)
|
||||
if (!isCurrentThemeRequest(root, request)) return false
|
||||
return applyRuntimeTheme(root, options.defaults ? resolveRuntimeTheme(theme, options.defaults) : theme)
|
||||
} catch {
|
||||
if (isCurrentThemeRequest(root, request)) resetRuntimeTheme(root)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/* Loading Screen pattern (visual reference: ui-viz Dashboard LoadingScreen, dashboard-composition.md §18). Pattern-local values.
|
||||
The surface uses the HM brand Green (ui-viz local #0a3322 is not an HM Primitive). The spinner never rotates: each of the 24 bars fades
|
||||
1 → .08 over 1.2s linear, started 0.05s apart clockwise. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-loading-screen-background: var(--hm-sem-action-primary);
|
||||
--hm-loading-screen-foreground: var(--hm-sem-on-action-primary);
|
||||
--hm-loading-screen-inline-padding-block: 36px;
|
||||
--hm-loading-screen-inline-padding-inline: 24px;
|
||||
--hm-loading-screen-inline-radius: 6px;
|
||||
--hm-loading-screen-inline-shadow: inset 0 0 24px color-mix(in srgb, var(--hm-sem-palette-black) 45%, transparent);
|
||||
--hm-loading-screen-gap: 12px;
|
||||
--hm-loading-screen-z-index: 9999;
|
||||
--hm-loading-spinner-size: 44px;
|
||||
--hm-loading-spinner-bar-width: 2.5px;
|
||||
--hm-loading-spinner-bar-height: 10px;
|
||||
--hm-loading-spinner-bar-count: 24;
|
||||
--hm-loading-spinner-bar-color: var(--hm-loading-screen-foreground);
|
||||
--hm-loading-spinner-bar-min-opacity: 0.08;
|
||||
--hm-loading-spinner-duration: 1.2s;
|
||||
--hm-loading-spinner-stagger: 0.05s;
|
||||
--hm-loading-text-font-size: 14px;
|
||||
--hm-loading-text-font-weight: 500;
|
||||
--hm-loading-text-letter-spacing: -0.02em;
|
||||
--hm-loading-text-color: color-mix(in srgb, var(--hm-loading-screen-foreground) 60%, transparent);
|
||||
}
|
||||
|
||||
[data-slot="loading-screen"] {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--hm-loading-screen-background);
|
||||
color: var(--hm-loading-screen-foreground);
|
||||
text-align: center;
|
||||
}
|
||||
[data-slot="loading-screen"][data-variant="inline"] {
|
||||
padding: var(--hm-loading-screen-inline-padding-block) var(--hm-loading-screen-inline-padding-inline);
|
||||
border-radius: var(--hm-loading-screen-inline-radius);
|
||||
box-shadow: var(--hm-loading-screen-inline-shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Fullscreen: the spinner + text group is centered in the viewport, same column / gap as inline (spinner and text share one center axis;
|
||||
ui-viz's block assembly put the spinner at the group's leading edge, which read as misaligned). */
|
||||
[data-slot="loading-screen"][data-variant="fullscreen"] {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: var(--hm-loading-screen-z-index);
|
||||
}
|
||||
[data-slot="loading-screen-content"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--hm-loading-screen-gap);
|
||||
}
|
||||
|
||||
[data-slot="loading-spinner"] {
|
||||
position: relative;
|
||||
width: var(--hm-loading-spinner-size);
|
||||
height: var(--hm-loading-spinner-size);
|
||||
}
|
||||
[data-slot="loading-spinner"] > span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(50% - var(--hm-loading-spinner-bar-width) / 2);
|
||||
width: var(--hm-loading-spinner-bar-width);
|
||||
height: var(--hm-loading-spinner-bar-height);
|
||||
border-radius: 9999px;
|
||||
background: var(--hm-loading-spinner-bar-color);
|
||||
transform-origin: center calc(var(--hm-loading-spinner-size) / 2);
|
||||
transform: rotate(calc(var(--hm-loading-index) * 360deg / var(--hm-loading-spinner-bar-count)));
|
||||
opacity: var(--hm-loading-spinner-bar-min-opacity);
|
||||
animation: hm-loading-fade var(--hm-loading-spinner-duration) linear infinite;
|
||||
animation-delay: calc(var(--hm-loading-index) * var(--hm-loading-spinner-stagger));
|
||||
}
|
||||
@keyframes hm-loading-fade {
|
||||
from { opacity: 1; }
|
||||
to { opacity: var(--hm-loading-spinner-bar-min-opacity); }
|
||||
}
|
||||
/* Reduced motion: no animation; the bars stay as a static comet (bright start fading clockwise). */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
[data-slot="loading-spinner"] > span {
|
||||
animation: none;
|
||||
opacity: calc(1 - var(--hm-loading-index) / (var(--hm-loading-spinner-bar-count) - 1) * (1 - var(--hm-loading-spinner-bar-min-opacity)));
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="loading-text"] {
|
||||
margin: 0;
|
||||
font-family: var(--hm-font-family-base);
|
||||
font-size: var(--hm-loading-text-font-size);
|
||||
font-weight: var(--hm-loading-text-font-weight);
|
||||
letter-spacing: var(--hm-loading-text-letter-spacing);
|
||||
color: var(--hm-loading-text-color);
|
||||
}
|
||||
[data-slot="loading-text"]::after {
|
||||
content: "...";
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/* Menubar mapping. ui-viz has no Menubar spec, so these are current HM values built from the HM Semantic tokens. A desktop menu bar:
|
||||
a bordered bar of text triggers whose open / focused trigger is highlighted, and menu content with the same item states as
|
||||
other Radix menus. Radix state attributes (data-highlighted, data-disabled, data-state) drive the states; no HM state attributes.
|
||||
No --hm-dropdown-menu-* token is referenced. */
|
||||
:root, [data-hm-theme] {
|
||||
/* Bar */
|
||||
--hm-menubar-background: var(--hm-sem-bg-surface);
|
||||
--hm-menubar-text: var(--hm-sem-fg-primary);
|
||||
--hm-menubar-border-width: 1px;
|
||||
--hm-menubar-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-menubar-radius: var(--hm-radius-container);
|
||||
--hm-menubar-height: 38px;
|
||||
--hm-menubar-padding: 2px;
|
||||
--hm-menubar-gap: 2px;
|
||||
|
||||
/* Top-level trigger */
|
||||
--hm-menubar-trigger-padding-x: 12px;
|
||||
--hm-menubar-trigger-radius: 2px;
|
||||
--hm-menubar-trigger-font-size: 14px;
|
||||
--hm-menubar-trigger-line-height: 20px;
|
||||
--hm-menubar-trigger-font-weight: 500;
|
||||
|
||||
/* Content */
|
||||
--hm-menubar-content-background: var(--hm-sem-floating-background);
|
||||
--hm-menubar-content-border-width: var(--hm-sem-floating-border-width);
|
||||
--hm-menubar-content-border-color: var(--hm-sem-floating-border-color);
|
||||
--hm-menubar-content-radius: var(--hm-sem-floating-radius);
|
||||
--hm-menubar-content-shadow: var(--hm-sem-floating-shadow);
|
||||
--hm-menubar-content-padding: 4px;
|
||||
|
||||
--hm-menubar-item-height: 36px;
|
||||
--hm-menubar-item-padding-x: 12px;
|
||||
--hm-menubar-item-gap: 8px;
|
||||
--hm-menubar-item-radius: 2px;
|
||||
--hm-menubar-font-size: 14px;
|
||||
--hm-menubar-line-height: 20px;
|
||||
|
||||
--hm-menubar-highlight-background: var(--hm-sem-surface-highlight);
|
||||
/* Top-level trigger highlight/open keeps the full accent; keyboard focus adds a separate ring. */
|
||||
--hm-menubar-trigger-highlight-background: var(--accent);
|
||||
--hm-menubar-highlight-text: var(--accent-foreground);
|
||||
--hm-menubar-destructive-text: var(--hm-sem-action-destructive);
|
||||
--hm-menubar-destructive-highlight-background: var(--hm-color-action-destructive-hover);
|
||||
|
||||
--hm-menubar-label-color: var(--hm-sem-fg-secondary);
|
||||
--hm-menubar-label-font-size: 12px;
|
||||
--hm-menubar-label-line-height: 18px;
|
||||
--hm-menubar-label-font-weight: 500;
|
||||
--hm-menubar-shortcut-color: var(--hm-sem-fg-secondary);
|
||||
--hm-menubar-shortcut-font-size: 12px;
|
||||
--hm-menubar-separator-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-menubar-indicator-size: 16px;
|
||||
--hm-menubar-radio-dot-size: 8px;
|
||||
}
|
||||
|
||||
[data-slot="menubar"] {
|
||||
box-sizing: border-box;
|
||||
width: fit-content;
|
||||
height: var(--hm-menubar-height);
|
||||
gap: var(--hm-menubar-gap);
|
||||
padding: var(--hm-menubar-padding);
|
||||
border: var(--hm-menubar-border-width) solid var(--hm-menubar-border-color);
|
||||
border-radius: var(--hm-menubar-radius);
|
||||
background: var(--hm-menubar-background);
|
||||
color: var(--hm-menubar-text);
|
||||
}
|
||||
[data-slot="menubar-trigger"] {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
padding: 0 var(--hm-menubar-trigger-padding-x);
|
||||
border-radius: var(--hm-menubar-trigger-radius);
|
||||
font-size: var(--hm-menubar-trigger-font-size);
|
||||
line-height: var(--hm-menubar-trigger-line-height);
|
||||
font-weight: var(--hm-menubar-trigger-font-weight);
|
||||
cursor: default;
|
||||
}
|
||||
/* Radix sets data-highlighted on the focused top-level trigger and data-state="open" on the open one. */
|
||||
[data-slot="menubar-trigger"][data-highlighted],
|
||||
[data-slot="menubar-trigger"][data-state="open"] {
|
||||
background: var(--hm-menubar-trigger-highlight-background);
|
||||
color: var(--hm-menubar-highlight-text);
|
||||
}
|
||||
[data-slot="menubar-trigger"]:focus-visible:not([data-disabled]) {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
[data-slot="menubar-trigger"][data-disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-slot="menubar-content"],
|
||||
[data-slot="menubar-sub-content"] {
|
||||
box-sizing: border-box;
|
||||
padding: var(--hm-menubar-content-padding);
|
||||
border: var(--hm-menubar-content-border-width) solid var(--hm-menubar-content-border-color);
|
||||
border-radius: var(--hm-menubar-content-radius);
|
||||
background: var(--hm-menubar-content-background);
|
||||
color: var(--hm-menubar-text);
|
||||
box-shadow: var(--hm-menubar-content-shadow);
|
||||
font-size: var(--hm-menubar-font-size);
|
||||
line-height: var(--hm-menubar-line-height);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
[data-slot="menubar-item"],
|
||||
[data-slot="menubar-checkbox-item"],
|
||||
[data-slot="menubar-radio-item"],
|
||||
[data-slot="menubar-sub-trigger"] {
|
||||
box-sizing: border-box;
|
||||
min-height: var(--hm-menubar-item-height);
|
||||
gap: var(--hm-menubar-item-gap);
|
||||
padding: 0 var(--hm-menubar-item-padding-x);
|
||||
border-radius: var(--hm-menubar-item-radius);
|
||||
}
|
||||
[data-slot="menubar-checkbox-item"],
|
||||
[data-slot="menubar-radio-item"],
|
||||
[data-slot="menubar-item"][data-inset],
|
||||
[data-slot="menubar-sub-trigger"][data-inset] {
|
||||
padding-left: calc(var(--hm-menubar-item-padding-x) + var(--hm-menubar-indicator-size) + var(--hm-menubar-item-gap));
|
||||
}
|
||||
[data-slot="menubar-item-indicator"] {
|
||||
left: var(--hm-menubar-item-padding-x);
|
||||
width: var(--hm-menubar-indicator-size);
|
||||
height: var(--hm-menubar-indicator-size);
|
||||
}
|
||||
[data-slot="menubar-radio-dot"] {
|
||||
width: var(--hm-menubar-radio-dot-size);
|
||||
height: var(--hm-menubar-radio-dot-size);
|
||||
}
|
||||
|
||||
[data-slot="menubar-item"][data-highlighted],
|
||||
[data-slot="menubar-checkbox-item"][data-highlighted],
|
||||
[data-slot="menubar-radio-item"][data-highlighted],
|
||||
[data-slot="menubar-sub-trigger"][data-highlighted],
|
||||
[data-slot="menubar-sub-trigger"][data-state="open"] {
|
||||
background: var(--hm-menubar-highlight-background);
|
||||
color: var(--hm-menubar-highlight-text);
|
||||
}
|
||||
[data-slot="menubar-item"][data-variant="destructive"] {
|
||||
color: var(--hm-menubar-destructive-text);
|
||||
}
|
||||
[data-slot="menubar-item"][data-variant="destructive"][data-highlighted] {
|
||||
background: var(--hm-menubar-destructive-highlight-background);
|
||||
color: var(--hm-menubar-destructive-text);
|
||||
}
|
||||
|
||||
[data-slot="menubar-label"] {
|
||||
padding: 6px var(--hm-menubar-item-padding-x);
|
||||
color: var(--hm-menubar-label-color);
|
||||
font-size: var(--hm-menubar-label-font-size);
|
||||
line-height: var(--hm-menubar-label-line-height);
|
||||
font-weight: var(--hm-menubar-label-font-weight);
|
||||
}
|
||||
[data-slot="menubar-label"][data-inset] {
|
||||
padding-left: calc(var(--hm-menubar-item-padding-x) + var(--hm-menubar-indicator-size) + var(--hm-menubar-item-gap));
|
||||
}
|
||||
[data-slot="menubar-separator"] {
|
||||
margin: var(--hm-menubar-content-padding) calc(-1 * var(--hm-menubar-content-padding));
|
||||
background: var(--hm-menubar-separator-color);
|
||||
}
|
||||
[data-slot="menubar-shortcut"] {
|
||||
color: var(--hm-menubar-shortcut-color);
|
||||
font-size: var(--hm-menubar-shortcut-font-size);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
[data-slot="menubar-item"][data-highlighted] [data-slot="menubar-shortcut"],
|
||||
[data-slot="menubar-checkbox-item"][data-highlighted] [data-slot="menubar-shortcut"],
|
||||
[data-slot="menubar-radio-item"][data-highlighted] [data-slot="menubar-shortcut"] {
|
||||
color: inherit;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/* Navigation Menu mapping. ui-viz has no Navigation Menu spec, so these are current HM values built from the HM Semantic tokens.
|
||||
It is for navigation destinations: top-level triggers and links share one text-button look, the current location (Radix
|
||||
`data-active`) is bold and tinted, and the content / viewport is a floating panel that can hold links with descriptions.
|
||||
Radix attributes drive the states (data-state, data-active, data-motion). No --hm-dropdown-menu-* token is referenced. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-navigation-menu-height: 34px;
|
||||
--hm-navigation-menu-padding-x: 12px;
|
||||
--hm-navigation-menu-gap: 4px;
|
||||
--hm-navigation-menu-radius: var(--hm-radius-control-md);
|
||||
--hm-navigation-menu-font-size: 14px;
|
||||
--hm-navigation-menu-line-height: 20px;
|
||||
--hm-navigation-menu-font-weight: 500;
|
||||
--hm-navigation-menu-text: var(--hm-sem-fg-primary);
|
||||
--hm-navigation-menu-icon-size: 12px;
|
||||
|
||||
--hm-navigation-menu-hover-background: var(--accent);
|
||||
--hm-navigation-menu-hover-text: var(--accent-foreground);
|
||||
/* Current location: same family as the Tabs / Select highlight (green tint), softer than hover. */
|
||||
--hm-navigation-menu-active-background: var(--hm-sem-surface-highlight);
|
||||
--hm-navigation-menu-active-text: var(--hm-sem-action-primary);
|
||||
--hm-navigation-menu-active-font-weight: 700;
|
||||
|
||||
/* Content / viewport surface */
|
||||
--hm-navigation-menu-background: var(--hm-sem-floating-background);
|
||||
--hm-navigation-menu-border-width: var(--hm-sem-floating-border-width);
|
||||
--hm-navigation-menu-border-color: var(--hm-sem-floating-border-color);
|
||||
--hm-navigation-menu-surface-radius: var(--hm-sem-floating-radius);
|
||||
--hm-navigation-menu-shadow: var(--hm-sem-floating-shadow);
|
||||
--hm-navigation-menu-content-padding: 8px;
|
||||
--hm-navigation-menu-content-min-width: 240px;
|
||||
|
||||
/* Link inside the content (destination with an optional description) */
|
||||
--hm-navigation-menu-item-padding: 8px 12px;
|
||||
--hm-navigation-menu-item-gap: 2px;
|
||||
--hm-navigation-menu-item-radius: 2px;
|
||||
--hm-navigation-menu-description-color: var(--hm-sem-fg-secondary);
|
||||
--hm-navigation-menu-description-font-size: 12px;
|
||||
--hm-navigation-menu-description-line-height: 18px;
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-navigation-menu-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-slot="navigation-menu-list"] {
|
||||
gap: var(--hm-navigation-menu-gap);
|
||||
}
|
||||
|
||||
/* Top-level trigger and link: the same text-button look. */
|
||||
[data-slot="navigation-menu-trigger"],
|
||||
[data-slot="navigation-menu-link"] {
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
height: var(--hm-navigation-menu-height);
|
||||
padding: 0 var(--hm-navigation-menu-padding-x);
|
||||
border-radius: var(--hm-navigation-menu-radius);
|
||||
background: transparent;
|
||||
color: var(--hm-navigation-menu-text);
|
||||
font-size: var(--hm-navigation-menu-font-size);
|
||||
line-height: var(--hm-navigation-menu-line-height);
|
||||
font-weight: var(--hm-navigation-menu-font-weight);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="navigation-menu-trigger"]:hover,
|
||||
[data-slot="navigation-menu-trigger"]:focus-visible,
|
||||
[data-slot="navigation-menu-trigger"][data-state="open"],
|
||||
[data-slot="navigation-menu-link"]:hover,
|
||||
[data-slot="navigation-menu-link"]:focus-visible {
|
||||
background: var(--hm-navigation-menu-hover-background);
|
||||
color: var(--hm-navigation-menu-hover-text);
|
||||
}
|
||||
[data-slot="navigation-menu-trigger"]:focus-visible,
|
||||
[data-slot="navigation-menu-link"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
[data-slot="navigation-menu-link"][data-active] {
|
||||
background: var(--hm-navigation-menu-active-background);
|
||||
color: var(--hm-navigation-menu-active-text);
|
||||
font-weight: var(--hm-navigation-menu-active-font-weight);
|
||||
}
|
||||
[data-slot="navigation-menu-link"][data-active]:hover,
|
||||
[data-slot="navigation-menu-link"][data-active]:focus-visible {
|
||||
background: var(--hm-navigation-menu-hover-background);
|
||||
}
|
||||
[data-slot="navigation-menu-trigger"]:disabled {
|
||||
opacity: var(--hm-navigation-menu-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
[data-slot="navigation-menu-trigger-icon"] {
|
||||
width: var(--hm-navigation-menu-icon-size);
|
||||
height: var(--hm-navigation-menu-icon-size);
|
||||
}
|
||||
|
||||
/* Floating panel: the viewport carries the surface (viewport mode) or the content does (viewport={false}). */
|
||||
[data-slot="navigation-menu-viewport"],
|
||||
[data-viewport="false"] [data-slot="navigation-menu-content"] {
|
||||
border: var(--hm-navigation-menu-border-width) solid var(--hm-navigation-menu-border-color);
|
||||
border-radius: var(--hm-navigation-menu-surface-radius);
|
||||
background: var(--hm-navigation-menu-background);
|
||||
color: var(--hm-navigation-menu-text);
|
||||
box-shadow: var(--hm-navigation-menu-shadow);
|
||||
}
|
||||
[data-slot="navigation-menu-content"] {
|
||||
box-sizing: border-box;
|
||||
min-width: var(--hm-navigation-menu-content-min-width);
|
||||
padding: var(--hm-navigation-menu-content-padding);
|
||||
}
|
||||
[data-slot="navigation-menu-indicator-arrow"] {
|
||||
background: var(--hm-navigation-menu-border-color);
|
||||
}
|
||||
|
||||
/* Links inside the content are destinations: block items, optional title + description children. */
|
||||
[data-slot="navigation-menu-content"] [data-slot="navigation-menu-link"] {
|
||||
height: auto;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--hm-navigation-menu-item-gap);
|
||||
padding: var(--hm-navigation-menu-item-padding);
|
||||
border-radius: var(--hm-navigation-menu-item-radius);
|
||||
}
|
||||
[data-slot="navigation-menu-content"] [data-slot="navigation-menu-link"] [data-description] {
|
||||
color: var(--hm-navigation-menu-description-color);
|
||||
font-size: var(--hm-navigation-menu-description-font-size);
|
||||
line-height: var(--hm-navigation-menu-description-line-height);
|
||||
font-weight: 400;
|
||||
}
|
||||
[data-slot="navigation-menu-content"] [data-slot="navigation-menu-link"]:hover [data-description],
|
||||
[data-slot="navigation-menu-content"] [data-slot="navigation-menu-link"]:focus-visible [data-description] {
|
||||
color: inherit;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/* The existing shadcn 10% overlay, now theme-derived. */
|
||||
[data-slot="dialog-overlay"], [data-slot="alert-dialog-overlay"],
|
||||
[data-slot="sheet-overlay"], [data-slot="drawer-overlay"] {
|
||||
background-color: var(--hm-sem-overlay-background);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/* Page Header pattern (layout visual reference: ui-viz page heading, dashboard-composition.md §8): space-between row, aligned to the
|
||||
bottom, 24px gap, 20px to the first card. The Breadcrumb is the HM Breadcrumb (its own 14px look, ui-viz had static 12px text).
|
||||
Spacing values are pattern-local. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-page-header-gap: 24px;
|
||||
--hm-page-header-margin-bottom: 20px;
|
||||
--hm-page-header-breadcrumb-gap: 10px;
|
||||
--hm-page-header-title-font-size: 28px;
|
||||
--hm-page-header-title-line-height: 36px;
|
||||
--hm-page-header-title-font-weight: 700;
|
||||
--hm-page-header-title-color: var(--hm-sem-fg-primary);
|
||||
--hm-page-header-description-gap: 8px;
|
||||
--hm-page-header-description-font-size: 14px;
|
||||
--hm-page-header-description-line-height: 20px;
|
||||
--hm-page-header-description-color: var(--hm-sem-fg-secondary);
|
||||
--hm-page-header-actions-gap: 8px;
|
||||
}
|
||||
|
||||
[data-slot="page-header"] {
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--hm-page-header-gap);
|
||||
min-width: 0;
|
||||
margin-bottom: var(--hm-page-header-margin-bottom);
|
||||
}
|
||||
[data-slot="page-header-content"] {
|
||||
flex: 1 1 320px;
|
||||
min-width: 0;
|
||||
}
|
||||
[data-slot="page-header-content"] > [data-slot="breadcrumb"] {
|
||||
margin-bottom: var(--hm-page-header-breadcrumb-gap);
|
||||
}
|
||||
[data-slot="page-header-title"] {
|
||||
margin: 0;
|
||||
color: var(--hm-page-header-title-color);
|
||||
font-size: var(--hm-page-header-title-font-size);
|
||||
line-height: var(--hm-page-header-title-line-height);
|
||||
font-weight: var(--hm-page-header-title-font-weight);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-slot="page-header-description"] {
|
||||
margin: var(--hm-page-header-description-gap) 0 0;
|
||||
color: var(--hm-page-header-description-color);
|
||||
font-size: var(--hm-page-header-description-font-size);
|
||||
line-height: var(--hm-page-header-description-line-height);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-slot="page-header-actions"] {
|
||||
gap: var(--hm-page-header-actions-gap);
|
||||
margin-left: auto;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/* Pagination mapping. ui-viz lists Pagination only as a component name (no spec), so these are current HM values built from the HM
|
||||
Semantic tokens. Links are independent anchor styles (not the Button tokens): a square page link, a padded Previous / Next link,
|
||||
a filled current page, and a dimmed aria-disabled link. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-pagination-gap: 4px;
|
||||
--hm-pagination-height: 34px;
|
||||
--hm-pagination-min-width: 34px;
|
||||
--hm-pagination-padding-x: 12px;
|
||||
--hm-pagination-radius: var(--hm-radius-control-md);
|
||||
--hm-pagination-font-size: 14px;
|
||||
--hm-pagination-line-height: 20px;
|
||||
--hm-pagination-font-weight: 500;
|
||||
--hm-pagination-icon-size: 16px;
|
||||
|
||||
--hm-pagination-text: var(--hm-sem-fg-primary);
|
||||
--hm-pagination-hover-background: var(--hm-sem-surface-highlight);
|
||||
--hm-pagination-hover-text: var(--accent-foreground);
|
||||
--hm-pagination-active-background: var(--hm-sem-action-primary);
|
||||
--hm-pagination-active-text: var(--hm-sem-on-action-primary);
|
||||
--hm-pagination-active-font-weight: 700;
|
||||
--hm-pagination-ellipsis-color: var(--hm-sem-fg-secondary);
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-pagination-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-slot="pagination-content"] {
|
||||
gap: var(--hm-pagination-gap);
|
||||
}
|
||||
[data-slot="pagination-link"] {
|
||||
box-sizing: border-box;
|
||||
height: var(--hm-pagination-height);
|
||||
min-width: var(--hm-pagination-min-width);
|
||||
padding: 0;
|
||||
border-radius: var(--hm-pagination-radius);
|
||||
color: var(--hm-pagination-text);
|
||||
font-size: var(--hm-pagination-font-size);
|
||||
line-height: var(--hm-pagination-line-height);
|
||||
font-weight: var(--hm-pagination-font-weight);
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="pagination-link"][data-size="default"] {
|
||||
padding: 0 var(--hm-pagination-padding-x);
|
||||
}
|
||||
[data-slot="pagination-link"] > svg {
|
||||
width: var(--hm-pagination-icon-size);
|
||||
height: var(--hm-pagination-icon-size);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
[data-slot="pagination-link"]:hover {
|
||||
background: var(--hm-pagination-hover-background);
|
||||
color: var(--hm-pagination-hover-text);
|
||||
}
|
||||
[data-slot="pagination-link"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
/* Current page: filled, stays filled on hover. */
|
||||
[data-slot="pagination-link"][aria-current="page"] {
|
||||
background: var(--hm-pagination-active-background);
|
||||
color: var(--hm-pagination-active-text);
|
||||
font-weight: var(--hm-pagination-active-font-weight);
|
||||
}
|
||||
[data-slot="pagination-link"][aria-disabled="true"] {
|
||||
opacity: var(--hm-pagination-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
[data-slot="pagination-link"][aria-disabled="true"]:hover {
|
||||
background: transparent;
|
||||
color: var(--hm-pagination-text);
|
||||
}
|
||||
[data-slot="pagination-ellipsis"] {
|
||||
width: var(--hm-pagination-min-width);
|
||||
height: var(--hm-pagination-height);
|
||||
color: var(--hm-pagination-ellipsis-color);
|
||||
}
|
||||
[data-slot="pagination-ellipsis"] > svg {
|
||||
width: var(--hm-pagination-icon-size);
|
||||
height: var(--hm-pagination-icon-size);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* Popover mapping. ui-viz has no Popover spec (inventory: design pending), so these are current HM values from the HM Semantic tokens.
|
||||
Popover is a non-modal floating panel: no overlay, no focus trap. Radix owns Escape / outside close and focus return. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-popover-background: var(--hm-sem-floating-background);
|
||||
--hm-popover-text: var(--hm-sem-fg-primary);
|
||||
--hm-popover-border-width: var(--hm-sem-floating-border-width);
|
||||
--hm-popover-border-color: var(--hm-sem-floating-border-color);
|
||||
--hm-popover-radius: var(--hm-sem-floating-radius);
|
||||
--hm-popover-shadow: var(--hm-sem-floating-shadow);
|
||||
--hm-popover-padding: 16px;
|
||||
--hm-popover-width: 288px;
|
||||
--hm-popover-font-size: 14px;
|
||||
--hm-popover-line-height: 20px;
|
||||
--hm-popover-font-weight: 400;
|
||||
}
|
||||
|
||||
[data-slot="popover-content"] {
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-popover-width);
|
||||
max-width: calc(100vw - 16px);
|
||||
padding: var(--hm-popover-padding);
|
||||
border: var(--hm-popover-border-width) solid var(--hm-popover-border-color);
|
||||
border-radius: var(--hm-popover-radius);
|
||||
background: var(--hm-popover-background);
|
||||
color: var(--hm-popover-text);
|
||||
box-shadow: var(--hm-popover-shadow);
|
||||
font-size: var(--hm-popover-font-size);
|
||||
line-height: var(--hm-popover-line-height);
|
||||
font-weight: var(--hm-popover-font-weight);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/* Generated from themes/default.json + scripts/theme-templates/primitives.css. Do not edit. */
|
||||
/* HM color palette. Primitive names describe colors, not UI roles. */
|
||||
:root {
|
||||
--hm-green-light-4: #34daaa;
|
||||
--hm-green-light-3: #249473;
|
||||
--hm-green-light-2: #266651;
|
||||
--hm-green-light-1: #205241;
|
||||
--hm-green-default: #123328;
|
||||
--hm-green-dark-1: #0a221d;
|
||||
--hm-green-dark-2: #071e18;
|
||||
|
||||
--hm-brown-light-2: #897445;
|
||||
--hm-brown-light-1: #604f32;
|
||||
--hm-brown-default: #3e3523;
|
||||
--hm-brown-dark-1: #352d1d;
|
||||
|
||||
--hm-brown-light-4: #ecd4a2;
|
||||
|
||||
--hm-orange-light-4: #feba88;
|
||||
--hm-orange-default: #cc5200;
|
||||
|
||||
--hm-warm-neutral-light: #e0dace;
|
||||
--hm-warm-neutral-mid: #9a9284;
|
||||
--hm-warm-neutral-dark: #6e6759;
|
||||
--hm-beige-light: #f5f0e5;
|
||||
|
||||
--hm-red-danger: #d32f2f;
|
||||
--hm-white: #ffffff;
|
||||
--hm-black: #000000;
|
||||
--hm-neutral-gray-500: #999999;
|
||||
|
||||
/* Existing preview values without an approved palette alias. */
|
||||
--hm-green-tone-1: #1f8265;
|
||||
--hm-green-tone-2: #165c47;
|
||||
--hm-green-tone-3: #114838;
|
||||
--hm-green-tone-4: #08241c;
|
||||
--hm-warm-neutral-pale: #dedbd2;
|
||||
--hm-warm-neutral-secondary: #ede8db;
|
||||
--hm-warm-neutral-muted: #f4f1ea;
|
||||
--hm-green-tint: #bddfd4;
|
||||
}
|
||||
|
||||
/* Accent for the current parent group on the green LNB surface (ui-viz color.action.accent-on-green). */
|
||||
:root {
|
||||
--hm-yellow-accent: #ffc600;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* Progress mapping. ui-viz has no Progress spec (inventory name only), so these are current HM values from the HM Semantic /
|
||||
Primitive tokens. One default look: a pale track and a primary indicator. Status colors (success / warning / error) are not defined
|
||||
and are not created here. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-progress-height: 8px;
|
||||
--hm-progress-radius: 9999px;
|
||||
--hm-progress-track-background: var(--hm-sem-palette-warm-neutral-light);
|
||||
/* Shares the Active Semantic while the Progress and Active color policies are the same; split later if they diverge. */
|
||||
--hm-progress-indicator-background: var(--hm-sem-state-active);
|
||||
}
|
||||
|
||||
[data-slot="progress"] {
|
||||
height: var(--hm-progress-height);
|
||||
border-radius: var(--hm-progress-radius);
|
||||
background: var(--hm-progress-track-background);
|
||||
}
|
||||
[data-slot="progress-indicator"] {
|
||||
background: var(--hm-progress-indicator-background);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/* Radio Group mapping: current HM values built from the shared Semantic tokens (ui-viz has no Radio spec; reference only).
|
||||
States use Radix attributes: [data-state="checked"|"unchecked"], :hover, :focus-visible, :disabled, [aria-invalid="true"]; layout uses data-orientation. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-radio-size: 18px;
|
||||
--hm-radio-border-width: 1px;
|
||||
--hm-radio-dot-size: 8px;
|
||||
--hm-radio-background: var(--hm-sem-bg-surface);
|
||||
--hm-radio-border-color: var(--hm-sem-stroke-default);
|
||||
--hm-radio-hover-border-color: var(--hm-sem-action-primary);
|
||||
--hm-radio-checked-border-color: var(--hm-sem-state-active);
|
||||
--hm-radio-dot-color: var(--hm-sem-state-active);
|
||||
--hm-radio-error-border-color: var(--hm-sem-action-destructive);
|
||||
--hm-radio-group-gap: 12px;
|
||||
--hm-radio-group-gap-horizontal: 16px;
|
||||
}
|
||||
|
||||
[data-slot="radio-group"] {
|
||||
gap: var(--hm-radio-group-gap);
|
||||
}
|
||||
[data-slot="radio-group"][data-orientation="horizontal"] {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--hm-radio-group-gap-horizontal);
|
||||
}
|
||||
|
||||
[data-slot="radio-group-item"] {
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-radio-size);
|
||||
height: var(--hm-radio-size);
|
||||
padding: 0;
|
||||
border: var(--hm-radio-border-width) solid var(--hm-radio-border-color);
|
||||
border-radius: 50%;
|
||||
background: var(--hm-radio-background);
|
||||
color: var(--hm-radio-dot-color);
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="radio-group-item"] svg {
|
||||
width: var(--hm-radio-dot-size);
|
||||
height: var(--hm-radio-dot-size);
|
||||
}
|
||||
[data-slot="radio-group-item"]:hover:not(:disabled) {
|
||||
border-color: var(--hm-radio-hover-border-color);
|
||||
}
|
||||
[data-slot="radio-group-item"][data-state="checked"] {
|
||||
border-color: var(--hm-radio-checked-border-color);
|
||||
}
|
||||
/* Focus level is the shadcn ring (ui_viz global focus is TBD); keep the ring variables because the theme sets box-shadow: none. */
|
||||
[data-slot="radio-group-item"]:focus-visible {
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000);
|
||||
}
|
||||
[data-slot="radio-group-item"][aria-invalid="true"] {
|
||||
border-color: var(--hm-radio-error-border-color);
|
||||
}
|
||||
[data-slot="radio-group-item"]:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// One owner per root. A newer load/apply/reset supersedes an in-flight load.
|
||||
const requests = new WeakMap<HTMLElement, object>()
|
||||
export function beginThemeRequest(root: HTMLElement) { const request = {}; requests.set(root, request); return request }
|
||||
export function isCurrentThemeRequest(root: HTMLElement, request: object) { return requests.get(root) === request }
|
||||
export function invalidateThemeRequest(root: HTMLElement) { requests.delete(root) }
|
||||
@@ -0,0 +1,74 @@
|
||||
/* Resizable mapping. ui-viz has no Resizable spec, so these are current HM values built from the HM Semantic / Primitive tokens.
|
||||
react-resizable-panels v4 owns the drag, constraints, keyboard and the pointer target (it expands the target around the thin
|
||||
separator); here only the divider and the optional grip are styled. The separator state is the library's `data-separator`
|
||||
("inactive" | "hover" | "active") and `:focus-visible`. Panels and Group keep the library's inline sizing.
|
||||
The divider look is in `@layer components` so a consumer's utility classes still win. Nothing here references Separator tokens. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-resizable-divider-width: 1px;
|
||||
--hm-resizable-divider-color: var(--hm-sem-stroke-input-default);
|
||||
/* Hover: a stronger neutral; dragging and keyboard focus: the primary color. The divider widens by a halo, not by layout. */
|
||||
--hm-resizable-hover-color: var(--hm-sem-stroke-default);
|
||||
--hm-resizable-active-color: var(--hm-sem-action-primary);
|
||||
--hm-resizable-focus-color: var(--hm-sem-stroke-input-focus);
|
||||
--hm-resizable-halo-width: 1px;
|
||||
|
||||
--hm-resizable-grip-width: 12px;
|
||||
--hm-resizable-grip-height: 16px;
|
||||
--hm-resizable-grip-radius: 2px;
|
||||
--hm-resizable-grip-background: var(--hm-sem-bg-surface);
|
||||
--hm-resizable-grip-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-resizable-grip-icon-size: 10px;
|
||||
--hm-resizable-grip-icon-color: var(--hm-sem-fg-secondary);
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-resizable-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
[data-slot="resizable-handle"] {
|
||||
flex-shrink: 0;
|
||||
background: var(--hm-resizable-divider-color);
|
||||
}
|
||||
/* A separator between side-by-side panels is vertical; between stacked panels it is horizontal. */
|
||||
[data-slot="resizable-handle"][aria-orientation="vertical"] {
|
||||
width: var(--hm-resizable-divider-width);
|
||||
}
|
||||
[data-slot="resizable-handle"][aria-orientation="horizontal"] {
|
||||
height: var(--hm-resizable-divider-width);
|
||||
}
|
||||
|
||||
[data-slot="resizable-handle"][data-separator="hover"] {
|
||||
background: var(--hm-resizable-hover-color);
|
||||
box-shadow: 0 0 0 var(--hm-resizable-halo-width) var(--hm-resizable-hover-color);
|
||||
}
|
||||
[data-slot="resizable-handle"][data-separator="active"] {
|
||||
background: var(--hm-resizable-active-color);
|
||||
box-shadow: 0 0 0 var(--hm-resizable-halo-width) var(--hm-resizable-active-color);
|
||||
}
|
||||
[data-slot="resizable-handle"]:focus-visible {
|
||||
background: var(--hm-resizable-focus-color);
|
||||
box-shadow: 0 0 0 var(--hm-resizable-halo-width) var(--hm-resizable-focus-color);
|
||||
}
|
||||
[data-slot="resizable-handle"][data-disabled],
|
||||
[data-slot="resizable-handle"][aria-disabled="true"] {
|
||||
opacity: var(--hm-resizable-disabled-opacity);
|
||||
}
|
||||
|
||||
[data-slot="resizable-handle-grip"] {
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-resizable-grip-width);
|
||||
height: var(--hm-resizable-grip-height);
|
||||
border: 1px solid var(--hm-resizable-grip-border-color);
|
||||
border-radius: var(--hm-resizable-grip-radius);
|
||||
background: var(--hm-resizable-grip-background);
|
||||
color: var(--hm-resizable-grip-icon-color);
|
||||
}
|
||||
[data-slot="resizable-handle-grip"] > svg {
|
||||
width: var(--hm-resizable-grip-icon-size);
|
||||
height: var(--hm-resizable-grip-icon-size);
|
||||
}
|
||||
/* Between stacked panels the grip lies down. */
|
||||
[data-slot="resizable-handle"][aria-orientation="horizontal"] [data-slot="resizable-handle-grip"] {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { runtimeThemeContract, runtimeGradientGroups, type RuntimeThemeToken } from "./contract"
|
||||
import { invalidateThemeRequest } from "./requests"
|
||||
|
||||
type SavedProperty = { property: string; value: string; priority: string }
|
||||
const applied = new WeakMap<HTMLElement, { properties: SavedProperty[]; scope: string | null }>()
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
|
||||
/** Restore the host's previous inline values and scope. */
|
||||
export function resetRuntimeTheme(root: HTMLElement) {
|
||||
invalidateThemeRequest(root)
|
||||
const previous = applied.get(root)
|
||||
if (!previous) return
|
||||
for (const { property, value, priority } of previous.properties) {
|
||||
if (value) root.style.setProperty(property, value, priority)
|
||||
else root.style.removeProperty(property)
|
||||
}
|
||||
if (previous.scope === null) root.removeAttribute("data-hm-theme")
|
||||
else root.setAttribute("data-hm-theme", previous.scope)
|
||||
applied.delete(root)
|
||||
}
|
||||
|
||||
/** Validate without touching the DOM (editors/loaders can preserve their last valid value). */
|
||||
export function isRuntimeTheme(theme: unknown): theme is import("./contract").RuntimeTheme {
|
||||
if (!isRecord(theme) || theme.schemaVersion !== 1 || !isRecord(theme.tokens)) return false
|
||||
const tokens = theme.tokens
|
||||
for (const group of runtimeGradientGroups) {
|
||||
const count = group.filter(key => Object.hasOwn(tokens, key)).length
|
||||
if (count !== 0 && count !== group.length) return false
|
||||
}
|
||||
for (const [key, value] of Object.entries(tokens)) {
|
||||
if (!Object.hasOwn(runtimeThemeContract, key) || typeof value !== "string") return false
|
||||
const definition = runtimeThemeContract[key as RuntimeThemeToken]
|
||||
const valid = definition.type === "radius"
|
||||
? /^(?:[2-9]|1[0-2])px$/.test(value)
|
||||
: /^#[0-9a-fA-F]{6}$/.test(value)
|
||||
if (!valid) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Atomic validation. Invalid documents release the previous override to host/default. */
|
||||
export function applyRuntimeTheme(root: HTMLElement, theme: unknown): boolean {
|
||||
if (!isRuntimeTheme(theme)) { resetRuntimeTheme(root); return false }
|
||||
const values = Object.entries(theme.tokens).map(([key, value]) =>
|
||||
[runtimeThemeContract[key as RuntimeThemeToken].property, value!] as const)
|
||||
resetRuntimeTheme(root)
|
||||
const scope = root.getAttribute("data-hm-theme")
|
||||
const properties = values.map(([property]) => ({
|
||||
property, value: root.style.getPropertyValue(property), priority: root.style.getPropertyPriority(property),
|
||||
}))
|
||||
root.setAttribute("data-hm-theme", "runtime")
|
||||
for (const [property, value] of values) root.style.setProperty(property, value)
|
||||
applied.set(root, { properties, scope })
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/* Scroll Area mapping. ui-viz has no Scroll Area spec, so these are current HM values. Radix keeps the scroll behavior and the
|
||||
default scrollbar `type="hover"` (the bar appears while hovering or scrolling); only the scrollbar chrome is styled. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-scroll-area-scrollbar-size: 10px;
|
||||
--hm-scroll-area-thumb-background: var(--hm-sem-stroke-default);
|
||||
--hm-scroll-area-thumb-hover-background: var(--hm-sem-fg-secondary);
|
||||
--hm-scroll-area-thumb-radius: 9999px;
|
||||
}
|
||||
|
||||
[data-slot="scroll-area-scrollbar"][data-orientation="vertical"] {
|
||||
width: var(--hm-scroll-area-scrollbar-size);
|
||||
}
|
||||
[data-slot="scroll-area-scrollbar"][data-orientation="horizontal"] {
|
||||
height: var(--hm-scroll-area-scrollbar-size);
|
||||
}
|
||||
[data-slot="scroll-area-thumb"] {
|
||||
border-radius: var(--hm-scroll-area-thumb-radius);
|
||||
background: var(--hm-scroll-area-thumb-background);
|
||||
}
|
||||
[data-slot="scroll-area-thumb"]:hover,
|
||||
[data-slot="scroll-area-scrollbar"][data-state="visible"]:active [data-slot="scroll-area-thumb"] {
|
||||
background: var(--hm-scroll-area-thumb-hover-background);
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/* Select mapping: the trigger and its connected list. Label and Helper/Error text are consumer composition.
|
||||
States use the Radix attributes (data-state, data-placeholder, data-highlighted, data-side) and :disabled; size uses data-size. */
|
||||
:root, [data-hm-theme] {
|
||||
/* ui_viz: docs/components/select.md §1.1 D-004, §8 — Confirmed. Control height is the inner height: the 1px border is added
|
||||
around it (outer 34 / 44). Not aliased to Input or Button. */
|
||||
--hm-select-control-height-md: 32px;
|
||||
--hm-select-control-height-lg: 42px;
|
||||
--hm-select-border-width: 1px;
|
||||
/* ui_viz: docs/components/select.md §8, §17 — BASELINE_VALUE (current applied contract) */
|
||||
--hm-select-padding-x: 12px;
|
||||
--hm-select-gap: 12px;
|
||||
/* ui_viz select.md §8: Option min-height 42 for both sizes (no medium value defined). Kept as one token per size so a
|
||||
later Medium decision changes only this line. */
|
||||
--hm-select-option-height-md: 42px;
|
||||
--hm-select-option-height-lg: 42px;
|
||||
--hm-select-radius: var(--hm-radius-input);
|
||||
|
||||
/* ui_viz: docs/components/select.md §9 — large body-md 16/24/400, medium trigger body-sm 14/20/400, option body-md in both sizes */
|
||||
--hm-select-font-size-md: 14px;
|
||||
--hm-select-line-height-md: 20px;
|
||||
--hm-select-font-size-lg: 16px;
|
||||
--hm-select-line-height-lg: 24px;
|
||||
/* Option typography follows the Select size (Trigger and list share one size). ui_viz left the Medium Option Role open
|
||||
(TBD, medium Trigger body-sm vs Option body-md); the Medium Option takes the Medium Trigger value 14/20/400. */
|
||||
--hm-select-option-font-size-md: var(--hm-select-font-size-md);
|
||||
--hm-select-option-line-height-md: var(--hm-select-line-height-md);
|
||||
--hm-select-option-font-size-lg: var(--hm-select-font-size-lg);
|
||||
--hm-select-option-line-height-lg: var(--hm-select-line-height-lg);
|
||||
--hm-select-font-weight: 400;
|
||||
|
||||
/* ui_viz: docs/components/select.md §10, §1.3 — Confirmed (D-005, M06) */
|
||||
--hm-select-background: var(--hm-sem-bg-surface);
|
||||
--hm-select-text: var(--hm-sem-fg-primary);
|
||||
--hm-select-placeholder: var(--hm-sem-fg-placeholder);
|
||||
--hm-select-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-select-focus-border-color: var(--hm-sem-stroke-input-focus);
|
||||
--hm-select-seam-color: var(--hm-sem-stroke-input-default);
|
||||
/* Open list face measured from the target Open screen: #e1f1ec on the white list = 45% of --accent (#bddfd4).
|
||||
Same mix the Toggle/Calendar hover faces use. The list shadow is the target's soft, downward-offset one (not the floating-surface shadow). */
|
||||
--hm-select-option-state-background: var(--hm-sem-surface-highlight);
|
||||
--hm-select-option-state-text: var(--accent-foreground);
|
||||
--hm-select-content-shadow: 0 2px 24px color-mix(in srgb, var(--hm-sem-palette-black) 12%, transparent);
|
||||
|
||||
/* ui_viz: docs/components/select.md §1.2 — user-confirmed Disabled: opacity .6, same white face and #DEDBD2 border */
|
||||
--hm-select-disabled-opacity: 0.6;
|
||||
/* ui_viz: docs/components/select.md §7 — indicator glyph stroke (user-provided Source) */
|
||||
--hm-select-indicator-color: var(--hm-sem-palette-indicator);
|
||||
}
|
||||
|
||||
/* Trigger. Large is the default when size is omitted. */
|
||||
[data-slot="select-trigger"] {
|
||||
--hm-select-control-height: var(--hm-select-control-height-lg);
|
||||
--hm-select-font-size: var(--hm-select-font-size-lg);
|
||||
--hm-select-line-height: var(--hm-select-line-height-lg);
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
height: auto;
|
||||
min-height: calc(var(--hm-select-control-height) + 2 * var(--hm-select-border-width));
|
||||
gap: var(--hm-select-gap);
|
||||
padding: 0 var(--hm-select-padding-x);
|
||||
border: var(--hm-select-border-width) solid var(--hm-select-border-color);
|
||||
border-radius: var(--hm-select-radius);
|
||||
background: var(--hm-select-background);
|
||||
color: var(--hm-select-text);
|
||||
font-size: var(--hm-select-font-size);
|
||||
line-height: var(--hm-select-line-height);
|
||||
font-weight: var(--hm-select-font-weight);
|
||||
}
|
||||
[data-slot="select-trigger"] > [data-slot="select-value"] {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[data-slot="select-trigger"][data-size="medium"] {
|
||||
--hm-select-control-height: var(--hm-select-control-height-md);
|
||||
--hm-select-font-size: var(--hm-select-font-size-md);
|
||||
--hm-select-line-height: var(--hm-select-line-height-md);
|
||||
}
|
||||
[data-slot="select-trigger"][data-placeholder] {
|
||||
color: var(--hm-select-placeholder);
|
||||
}
|
||||
[data-slot="select-icon"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--hm-select-indicator-color);
|
||||
}
|
||||
|
||||
/* Focus and Open retain the focus-colored outer outline. The connected edge follows Content's actual side. */
|
||||
[data-slot="select-trigger"]:focus {
|
||||
border-color: var(--hm-select-focus-border-color);
|
||||
}
|
||||
[data-slot="select-trigger"][data-state="open"] {
|
||||
border-color: var(--hm-select-focus-border-color);
|
||||
}
|
||||
|
||||
/* Direction is mirrored from the actual Radix Content through its ARIA relationship. */
|
||||
[data-slot="select-trigger"][data-state="open"][data-side="bottom"] {
|
||||
border-radius: var(--hm-select-radius) var(--hm-select-radius) 0 0;
|
||||
border-bottom-color: var(--hm-select-seam-color);
|
||||
}
|
||||
[data-slot="select-trigger"][data-state="open"][data-side="top"] {
|
||||
border-radius: 0 0 var(--hm-select-radius) var(--hm-select-radius);
|
||||
border-top-color: var(--hm-select-seam-color);
|
||||
}
|
||||
|
||||
/* Disabled keeps the Default paint and dims the whole control. */
|
||||
[data-slot="select-trigger"]:disabled {
|
||||
border-color: var(--hm-select-border-color);
|
||||
background: var(--hm-select-background);
|
||||
opacity: var(--hm-select-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Invalid belongs to the trigger border, never the popup surface or its neutral seam. */
|
||||
[data-slot="select-trigger"][aria-invalid="true"] {
|
||||
border-color: var(--hm-sem-action-destructive);
|
||||
}
|
||||
[data-slot="select-trigger"][aria-invalid="true"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
[data-slot="select-trigger"][aria-invalid="true"][data-state="open"][data-side="bottom"] {
|
||||
border-bottom-color: var(--hm-select-seam-color);
|
||||
}
|
||||
[data-slot="select-trigger"][aria-invalid="true"][data-state="open"][data-side="top"] {
|
||||
border-top-color: var(--hm-select-seam-color);
|
||||
}
|
||||
|
||||
/* List: same outer width as the trigger, border in the focus color, rounded only at the bottom (connected outline). */
|
||||
[data-slot="select-content"] {
|
||||
box-sizing: border-box;
|
||||
border: var(--hm-select-border-width) solid var(--hm-select-focus-border-color);
|
||||
border-radius: var(--hm-select-radius);
|
||||
background: var(--hm-select-background);
|
||||
color: var(--hm-select-text);
|
||||
box-shadow: var(--hm-select-content-shadow);
|
||||
}
|
||||
[data-slot="select-content"]:not([data-align-trigger="true"]) {
|
||||
width: var(--radix-select-trigger-width);
|
||||
min-width: var(--radix-select-trigger-width);
|
||||
}
|
||||
[data-slot="select-content"][data-side="bottom"] {
|
||||
border-radius: 0 0 var(--hm-select-radius) var(--hm-select-radius);
|
||||
/* sideOffset=-1 puts this top border on the trigger's bottom border, so it is the one seam line between the two.
|
||||
Only the seam is the quiet control border; the outer outline stays the focus color. */
|
||||
border-top-color: var(--hm-select-seam-color);
|
||||
}
|
||||
|
||||
[data-slot="select-content"][data-side="top"] {
|
||||
border-radius: var(--hm-select-radius) var(--hm-select-radius) 0 0;
|
||||
border-bottom-color: var(--hm-select-seam-color);
|
||||
}
|
||||
|
||||
/* Content uses the trigger's outer width. Its Viewport lives inside that bordered box, so it must fill the
|
||||
available inner width instead of applying the trigger's outer width as another minimum. */
|
||||
[data-slot="select-content"] [data-position="popper"] {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Option profile follows the Select size: SelectContent carries data-size (from the Trigger, through context), the Portal
|
||||
keeps it out of the Trigger subtree. Width is not size-dependent (Content = Trigger outer width).
|
||||
Hover, Selected and Keyboard Active use the same face (set by the shadcn state classes). */
|
||||
[data-slot="select-content"] {
|
||||
--hm-select-option-height: var(--hm-select-option-height-lg);
|
||||
--hm-select-option-font-size: var(--hm-select-option-font-size-lg);
|
||||
--hm-select-option-line-height: var(--hm-select-option-line-height-lg);
|
||||
}
|
||||
[data-slot="select-content"][data-size="medium"] {
|
||||
--hm-select-option-height: var(--hm-select-option-height-md);
|
||||
--hm-select-option-font-size: var(--hm-select-option-font-size-md);
|
||||
--hm-select-option-line-height: var(--hm-select-option-line-height-md);
|
||||
}
|
||||
[data-slot="select-item"] {
|
||||
box-sizing: border-box;
|
||||
min-height: var(--hm-select-option-height);
|
||||
padding: 0 var(--hm-select-padding-x);
|
||||
border-radius: 0;
|
||||
font-size: var(--hm-select-option-font-size);
|
||||
line-height: var(--hm-select-option-line-height);
|
||||
font-weight: var(--hm-select-font-weight);
|
||||
}
|
||||
|
||||
/* ItemText: a flex item defaults to min-width:auto, so an unbreakable label kept its full width and pushed the row past the
|
||||
list (Item scrollWidth > Viewport clientWidth). Let it shrink to the row and truncate, like the trigger value. */
|
||||
[data-slot="select-item"] > span {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Radix keeps selection and the current pointer/keyboard highlight as separate states. ui_viz gives both
|
||||
the same option face, so use the actual Radix attributes rather than DOM position or focus alone. */
|
||||
[data-slot="select-item"][data-state="checked"],
|
||||
[data-slot="select-item"][data-highlighted] {
|
||||
background: var(--hm-select-option-state-background);
|
||||
color: var(--hm-select-option-state-text);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* Separator mapping — current HM values. ui-viz has no standalone Separator (only per-component divider values), so this is a plain 1px line.
|
||||
The color is a divider tone (the same pale warm neutral the HM input / Dialog outlines use) referenced from the Primitive; no Semantic exists for it yet. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-separator-thickness: 1px;
|
||||
--hm-separator-color: var(--hm-sem-stroke-input-default);
|
||||
}
|
||||
|
||||
[data-slot="separator"] {
|
||||
background: var(--hm-separator-color);
|
||||
}
|
||||
[data-slot="separator"][data-orientation="horizontal"] {
|
||||
height: var(--hm-separator-thickness);
|
||||
}
|
||||
[data-slot="separator"][data-orientation="vertical"] {
|
||||
width: var(--hm-separator-thickness);
|
||||
min-height: 1em;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/* Sheet mapping. ui-viz has no Sheet spec (the Dialog doc lists "side sheet" as out of scope), so these are current HM values built
|
||||
from the HM Semantic tokens. Modal panel attached to a viewport edge; Radix keeps Portal, focus trap, Escape and focus return.
|
||||
Position and slide animation stay the shadcn classes; the Overlay keeps the shadcn default. Values repeat Dialog's look in
|
||||
meaning only: no --hm-dialog-* token is referenced. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-sheet-background: var(--hm-sem-bg-surface);
|
||||
--hm-sheet-text: var(--hm-sem-fg-primary);
|
||||
--hm-sheet-border-width: 1px;
|
||||
--hm-sheet-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-sheet-shadow: 0 0 12px color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
|
||||
|
||||
/* Left / right: fixed width, never wider than the viewport minus a strip of Overlay. Top / bottom: full width, height by content. */
|
||||
--hm-sheet-width: 400px;
|
||||
--hm-sheet-edge-gap: 48px;
|
||||
|
||||
--hm-sheet-padding: 24px;
|
||||
--hm-sheet-header-gap: 8px;
|
||||
--hm-sheet-footer-gap: 8px;
|
||||
/* Space reserved at the Header end for the Close button (28px button + 8px gap). */
|
||||
--hm-sheet-close-clearance: 36px;
|
||||
|
||||
--hm-sheet-title-font-size: 20px;
|
||||
--hm-sheet-title-line-height: 28px;
|
||||
--hm-sheet-title-font-weight: 700;
|
||||
--hm-sheet-description-font-size: 14px;
|
||||
--hm-sheet-description-line-height: 20px;
|
||||
--hm-sheet-description-color: var(--hm-sem-fg-secondary);
|
||||
--hm-sheet-body-font-size: 14px;
|
||||
--hm-sheet-body-line-height: 20px;
|
||||
}
|
||||
|
||||
[data-slot="sheet-content"] {
|
||||
box-sizing: border-box;
|
||||
background: var(--hm-sheet-background);
|
||||
color: var(--hm-sheet-text);
|
||||
box-shadow: var(--hm-sheet-shadow);
|
||||
font-size: var(--hm-sheet-body-font-size);
|
||||
line-height: var(--hm-sheet-body-line-height);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-slot="sheet-content"][data-side="right"],
|
||||
[data-slot="sheet-content"][data-side="left"] {
|
||||
height: 100%;
|
||||
width: min(var(--hm-sheet-width), calc(100vw - var(--hm-sheet-edge-gap)));
|
||||
}
|
||||
[data-slot="sheet-content"][data-side="top"],
|
||||
[data-slot="sheet-content"][data-side="bottom"] {
|
||||
width: 100%;
|
||||
max-height: calc(100dvh - var(--hm-sheet-edge-gap));
|
||||
}
|
||||
/* Border only on the edge that faces the page. */
|
||||
[data-slot="sheet-content"][data-side="right"] { border-left: var(--hm-sheet-border-width) solid var(--hm-sheet-border-color); }
|
||||
[data-slot="sheet-content"][data-side="left"] { border-right: var(--hm-sheet-border-width) solid var(--hm-sheet-border-color); }
|
||||
[data-slot="sheet-content"][data-side="top"] { border-bottom: var(--hm-sheet-border-width) solid var(--hm-sheet-border-color); }
|
||||
[data-slot="sheet-content"][data-side="bottom"] { border-top: var(--hm-sheet-border-width) solid var(--hm-sheet-border-color); }
|
||||
|
||||
[data-slot="sheet-header"] {
|
||||
flex: none;
|
||||
gap: var(--hm-sheet-header-gap);
|
||||
padding: var(--hm-sheet-padding) calc(var(--hm-sheet-padding) + var(--hm-sheet-close-clearance)) 16px var(--hm-sheet-padding);
|
||||
}
|
||||
[data-slot="sheet-title"] {
|
||||
font-size: var(--hm-sheet-title-font-size);
|
||||
line-height: var(--hm-sheet-title-line-height);
|
||||
font-weight: var(--hm-sheet-title-font-weight);
|
||||
color: var(--hm-sheet-text);
|
||||
}
|
||||
[data-slot="sheet-description"] {
|
||||
font-size: var(--hm-sheet-description-font-size);
|
||||
line-height: var(--hm-sheet-description-line-height);
|
||||
font-weight: 400;
|
||||
color: var(--hm-sheet-description-color);
|
||||
}
|
||||
|
||||
/* Body: the only region that scrolls; Header and Footer stay in view. */
|
||||
[data-slot="sheet-body"] {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 0 var(--hm-sheet-padding);
|
||||
}
|
||||
|
||||
[data-slot="sheet-footer"] {
|
||||
flex: none;
|
||||
gap: var(--hm-sheet-footer-gap);
|
||||
padding: 16px var(--hm-sheet-padding) var(--hm-sheet-padding);
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
/* Skeleton mapping. ui-viz has no Skeleton spec (inventory name only), so these are current HM values. The pulse is the shadcn
|
||||
`animate-pulse` (no HM Motion rule exists); reduced-motion users get a still placeholder.
|
||||
The default look lives in `@layer components` so a consumer's utility classes (`rounded-full`, `bg-*`) still override it:
|
||||
Skeleton shape is meant to be set through `className`. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-skeleton-background: var(--hm-sem-palette-warm-neutral-light);
|
||||
--hm-skeleton-radius: var(--hm-radius-control-md);
|
||||
}
|
||||
|
||||
@layer components {
|
||||
[data-slot="skeleton"] {
|
||||
background: var(--hm-skeleton-background);
|
||||
border-radius: var(--hm-skeleton-radius);
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
[data-slot="skeleton"] {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/* Slider mapping. ui-viz has no Slider spec, so these are current HM values built from the HM Semantic / Primitive tokens.
|
||||
Track, Range and Thumb are internal parts, so they are styled here; the Slider's own width / height stay `className` territory.
|
||||
States: Radix attributes ([data-disabled], [data-orientation]) and :hover / :active / :focus-visible on the thumb. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-slider-track-size: 6px;
|
||||
--hm-slider-track-background: var(--hm-sem-palette-warm-neutral-light);
|
||||
--hm-slider-range-background: var(--hm-sem-state-active);
|
||||
|
||||
--hm-slider-thumb-size: 18px;
|
||||
--hm-slider-thumb-background: var(--hm-sem-bg-surface);
|
||||
--hm-slider-thumb-border-width: 2px;
|
||||
--hm-slider-thumb-border-color: var(--hm-sem-state-active);
|
||||
/* Interaction halo: hover shows it, dragging widens it. Keyboard focus has its own semantic color. */
|
||||
--hm-slider-thumb-halo-color: color-mix(in srgb, var(--hm-sem-action-primary) 20%, transparent);
|
||||
--hm-slider-thumb-focus-halo-color: color-mix(in srgb, var(--hm-sem-stroke-input-focus) 20%, transparent);
|
||||
--hm-slider-thumb-halo-size: 4px;
|
||||
--hm-slider-thumb-halo-size-active: 6px;
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-slider-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-slot="slider-track"] {
|
||||
border-radius: 9999px;
|
||||
background: var(--hm-slider-track-background);
|
||||
}
|
||||
[data-slot="slider"][data-orientation="horizontal"] [data-slot="slider-track"] {
|
||||
height: var(--hm-slider-track-size);
|
||||
width: 100%;
|
||||
}
|
||||
[data-slot="slider"][data-orientation="vertical"] [data-slot="slider-track"] {
|
||||
width: var(--hm-slider-track-size);
|
||||
height: 100%;
|
||||
}
|
||||
[data-slot="slider-range"] {
|
||||
background: var(--hm-slider-range-background);
|
||||
}
|
||||
[data-slot="slider"][data-orientation="horizontal"] [data-slot="slider-range"] {
|
||||
height: 100%;
|
||||
}
|
||||
[data-slot="slider"][data-orientation="vertical"] [data-slot="slider-range"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[data-slot="slider-thumb"] {
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-slider-thumb-size);
|
||||
height: var(--hm-slider-thumb-size);
|
||||
border: var(--hm-slider-thumb-border-width) solid var(--hm-slider-thumb-border-color);
|
||||
border-radius: 9999px;
|
||||
background: var(--hm-slider-thumb-background);
|
||||
cursor: grab;
|
||||
}
|
||||
[data-slot="slider-thumb"]:hover {
|
||||
box-shadow: 0 0 0 var(--hm-slider-thumb-halo-size) var(--hm-slider-thumb-halo-color);
|
||||
}
|
||||
[data-slot="slider-thumb"]:active {
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 0 0 var(--hm-slider-thumb-halo-size-active) var(--hm-slider-thumb-halo-color);
|
||||
}
|
||||
/* Focus wins the color when states overlap; drag keeps its 6px size and grabbing cursor. */
|
||||
[data-slot="slider-thumb"]:focus-visible {
|
||||
box-shadow: 0 0 0 var(--hm-slider-thumb-halo-size) var(--hm-slider-thumb-focus-halo-color);
|
||||
}
|
||||
[data-slot="slider-thumb"]:focus-visible:active {
|
||||
box-shadow: 0 0 0 var(--hm-slider-thumb-halo-size-active) var(--hm-slider-thumb-focus-halo-color);
|
||||
}
|
||||
|
||||
[data-slot="slider"][data-disabled] {
|
||||
opacity: var(--hm-slider-disabled-opacity);
|
||||
}
|
||||
[data-slot="slider"][data-disabled] [data-slot="slider-thumb"] {
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/* Toast (Sonner) mapping. Notification surface, not an anchored floating panel, so it does NOT use --hm-sem-floating-*; its values are
|
||||
its own (the visual values currently equal the floating surface and are a Notification Semantic candidate, see docs/components/sonner.md).
|
||||
ui-viz has no Toast spec and its success / warning / information status colors are TBD, so no status palette is invented:
|
||||
Default, Success, Info, Warning and Loading share the neutral surface (the icon tells the type) and only Error uses the destructive color,
|
||||
the same stance as Alert. Sonner's own data attributes ([data-sonner-toast], [data-type], [data-button] ...) are the state hooks. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-toast-width: 356px;
|
||||
--hm-toast-background: var(--hm-sem-bg-surface);
|
||||
--hm-toast-text: var(--hm-sem-fg-primary);
|
||||
--hm-toast-border-width: 1px;
|
||||
--hm-toast-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-toast-radius: var(--hm-radius-container);
|
||||
--hm-toast-shadow: 0 0 12px color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
|
||||
--hm-toast-padding: 16px;
|
||||
--hm-toast-gap: 12px;
|
||||
--hm-toast-font-family: var(--hm-font-family-base);
|
||||
--hm-toast-title-font-size: 14px;
|
||||
--hm-toast-title-line-height: 20px;
|
||||
--hm-toast-title-font-weight: 500;
|
||||
--hm-toast-description-color: var(--hm-sem-fg-secondary);
|
||||
--hm-toast-description-font-size: 13px;
|
||||
--hm-toast-description-line-height: 18px;
|
||||
--hm-toast-description-font-weight: 400;
|
||||
--hm-toast-icon-size: 16px;
|
||||
--hm-toast-icon-color: var(--hm-sem-fg-primary);
|
||||
|
||||
--hm-toast-error-border-color: var(--hm-sem-action-destructive);
|
||||
--hm-toast-error-text: var(--hm-sem-action-destructive);
|
||||
|
||||
--hm-toast-action-height: 28px;
|
||||
--hm-toast-action-padding-x: 12px;
|
||||
--hm-toast-action-radius: var(--hm-radius-control-md);
|
||||
--hm-toast-action-font-size: 12px;
|
||||
--hm-toast-action-font-weight: 500;
|
||||
--hm-toast-action-background: var(--hm-sem-action-primary);
|
||||
--hm-toast-action-text: var(--hm-sem-on-action-primary);
|
||||
--hm-toast-cancel-background: var(--hm-sem-bg-subtle);
|
||||
--hm-toast-cancel-text: var(--hm-sem-fg-secondary);
|
||||
|
||||
--hm-toast-close-background: var(--hm-toast-background);
|
||||
--hm-toast-close-text: var(--hm-sem-fg-secondary);
|
||||
--hm-toast-close-border-color: var(--hm-toast-border-color);
|
||||
}
|
||||
|
||||
/* Sonner's own rules use up to three attributes, so the Toaster root repeats [dir] (always set) to win regardless of injection order. */
|
||||
[data-sonner-toaster][data-sonner-theme][dir] {
|
||||
--width: var(--hm-toast-width);
|
||||
--border-radius: var(--hm-toast-radius);
|
||||
--normal-bg: var(--hm-toast-background);
|
||||
--normal-border: var(--hm-toast-border-color);
|
||||
--normal-text: var(--hm-toast-text);
|
||||
font-family: var(--hm-toast-font-family);
|
||||
}
|
||||
|
||||
[data-sonner-toaster] [data-sonner-toast][data-styled="true"] {
|
||||
box-sizing: border-box;
|
||||
gap: var(--hm-toast-gap);
|
||||
padding: var(--hm-toast-padding);
|
||||
border-width: var(--hm-toast-border-width);
|
||||
box-shadow: var(--hm-toast-shadow);
|
||||
font-size: var(--hm-toast-description-font-size);
|
||||
line-height: var(--hm-toast-description-line-height);
|
||||
}
|
||||
[data-sonner-toaster] [data-sonner-toast][data-styled="true"] [data-title] {
|
||||
font-size: var(--hm-toast-title-font-size);
|
||||
line-height: var(--hm-toast-title-line-height);
|
||||
font-weight: var(--hm-toast-title-font-weight);
|
||||
}
|
||||
[data-sonner-toaster] [data-sonner-toast][data-styled="true"] [data-description] {
|
||||
color: var(--hm-toast-description-color);
|
||||
font-size: var(--hm-toast-description-font-size);
|
||||
line-height: var(--hm-toast-description-line-height);
|
||||
font-weight: var(--hm-toast-description-font-weight);
|
||||
}
|
||||
[data-sonner-toaster] [data-sonner-toast][data-styled="true"] [data-icon] {
|
||||
width: var(--hm-toast-icon-size);
|
||||
height: var(--hm-toast-icon-size);
|
||||
color: var(--hm-toast-icon-color);
|
||||
}
|
||||
|
||||
/* Error is the only status with its own color (destructive); other types stay neutral. */
|
||||
[data-sonner-toaster] [data-sonner-toast][data-type="error"] {
|
||||
border-color: var(--hm-toast-error-border-color);
|
||||
}
|
||||
[data-sonner-toaster] [data-sonner-toast][data-type="error"] [data-icon],
|
||||
[data-sonner-toaster] [data-sonner-toast][data-type="error"] [data-title] {
|
||||
color: var(--hm-toast-error-text);
|
||||
}
|
||||
|
||||
[data-sonner-toaster] [data-sonner-toast][data-styled="true"] [data-button] {
|
||||
height: var(--hm-toast-action-height);
|
||||
padding: 0 var(--hm-toast-action-padding-x);
|
||||
border-radius: var(--hm-toast-action-radius);
|
||||
background: var(--hm-toast-action-background);
|
||||
color: var(--hm-toast-action-text);
|
||||
font-size: var(--hm-toast-action-font-size);
|
||||
font-weight: var(--hm-toast-action-font-weight);
|
||||
}
|
||||
[data-sonner-toaster] [data-sonner-toast][data-styled="true"] [data-cancel] {
|
||||
background: var(--hm-toast-cancel-background);
|
||||
color: var(--hm-toast-cancel-text);
|
||||
}
|
||||
[data-sonner-toaster] [data-sonner-toast][data-styled="true"] [data-close-button] {
|
||||
background: var(--hm-toast-close-background);
|
||||
color: var(--hm-toast-close-text);
|
||||
border-color: var(--hm-toast-close-border-color);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/* Switch mapping — current HM values built from the shared Semantic tokens (ui-viz has no Switch spec; it was only a candidate there).
|
||||
States use Radix attributes: [data-state="checked"|"unchecked"], :hover, :focus-visible, :disabled. Motion is the shadcn default. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-switch-width: 36px;
|
||||
--hm-switch-height: 20px;
|
||||
--hm-switch-thumb-size: 16px;
|
||||
--hm-switch-thumb-offset: 2px;
|
||||
--hm-switch-off-background: var(--hm-sem-stroke-default);
|
||||
--hm-switch-on-background: var(--hm-sem-state-active);
|
||||
--hm-switch-thumb-color: var(--hm-sem-bg-surface);
|
||||
--hm-switch-hover-overlay: var(--hm-color-action-overlay-hover);
|
||||
}
|
||||
|
||||
[data-slot="switch"] {
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-switch-width);
|
||||
height: var(--hm-switch-height);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 9999px;
|
||||
background-color: var(--hm-switch-off-background);
|
||||
background-image: none;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="switch"][data-state="checked"] {
|
||||
background-color: var(--hm-switch-on-background);
|
||||
}
|
||||
[data-slot="switch"]:hover:not(:disabled) {
|
||||
background-image: linear-gradient(var(--hm-switch-hover-overlay), var(--hm-switch-hover-overlay));
|
||||
}
|
||||
[data-slot="switch"]:focus-visible {
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000);
|
||||
}
|
||||
[data-slot="switch"]:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
[data-slot="switch-thumb"] {
|
||||
width: var(--hm-switch-thumb-size);
|
||||
height: var(--hm-switch-thumb-size);
|
||||
border-radius: 9999px;
|
||||
background: var(--hm-switch-thumb-color);
|
||||
transform: translateX(var(--hm-switch-thumb-offset));
|
||||
}
|
||||
[data-slot="switch-thumb"][data-state="checked"] {
|
||||
transform: translateX(calc(var(--hm-switch-width) - var(--hm-switch-thumb-size) - var(--hm-switch-thumb-offset)));
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/* Table mapping. Reference: ui-viz docs/components/table.md (an earlier test spec: green header strip, 1px row divider, 14px/12px cell
|
||||
padding, 520px default minimum width, no radius, hover face only). Current HM values built from the HM Semantic / Primitive tokens;
|
||||
the selected row and the footer are not defined there, so they are new here. Static display only: no sorting / filtering / paging. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-table-min-width: 520px;
|
||||
--hm-table-background: var(--hm-sem-bg-surface);
|
||||
--hm-table-text: var(--hm-sem-fg-primary);
|
||||
--hm-table-font-size: 14px;
|
||||
--hm-table-line-height: 20px;
|
||||
--hm-table-font-weight: 400;
|
||||
|
||||
--hm-table-cell-padding-y: 14px;
|
||||
--hm-table-cell-padding-x: 12px;
|
||||
--hm-table-divider-width: 1px;
|
||||
--hm-table-divider-color: var(--hm-sem-stroke-input-default);
|
||||
|
||||
/* Header strip: a pale green face with a green top edge and a soft bottom edge. */
|
||||
--hm-table-header-background: color-mix(in srgb, var(--hm-sem-palette-green-tint) 30%, var(--hm-sem-palette-white));
|
||||
--hm-table-header-text: var(--hm-sem-palette-green-light-1);
|
||||
--hm-table-header-font-size: 13px;
|
||||
--hm-table-header-font-weight: 700;
|
||||
--hm-table-header-border-top: var(--hm-sem-palette-green-light-2);
|
||||
--hm-table-header-border-bottom: color-mix(in srgb, var(--hm-sem-palette-green-light-2) 20%, transparent);
|
||||
|
||||
/* Rows are a wide soft surface: hover, selected and selected + hover all use Accent 45% (not the Active State Semantic). */
|
||||
--hm-table-row-hover-background: var(--hm-sem-surface-highlight);
|
||||
--hm-table-row-selected-background: var(--hm-sem-surface-highlight);
|
||||
|
||||
--hm-table-footer-background: var(--hm-sem-bg-subtle);
|
||||
--hm-table-footer-font-weight: 500;
|
||||
|
||||
--hm-table-caption-color: var(--hm-sem-fg-secondary);
|
||||
--hm-table-caption-font-size: 12px;
|
||||
--hm-table-caption-line-height: 18px;
|
||||
--hm-table-caption-gap: 12px;
|
||||
}
|
||||
|
||||
[data-slot="table"] {
|
||||
min-width: var(--hm-table-min-width);
|
||||
border-collapse: collapse;
|
||||
background: var(--hm-table-background);
|
||||
color: var(--hm-table-text);
|
||||
font-size: var(--hm-table-font-size);
|
||||
line-height: var(--hm-table-line-height);
|
||||
font-weight: var(--hm-table-font-weight);
|
||||
}
|
||||
|
||||
[data-slot="table-head"],
|
||||
[data-slot="table-cell"] {
|
||||
padding: var(--hm-table-cell-padding-y) var(--hm-table-cell-padding-x);
|
||||
overflow-wrap: anywhere;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
[data-slot="table-head"] {
|
||||
background: var(--hm-table-header-background);
|
||||
color: var(--hm-table-header-text);
|
||||
font-size: var(--hm-table-header-font-size);
|
||||
font-weight: var(--hm-table-header-font-weight);
|
||||
border-top: var(--hm-table-divider-width) solid var(--hm-table-header-border-top);
|
||||
border-bottom: var(--hm-table-divider-width) solid var(--hm-table-header-border-bottom);
|
||||
}
|
||||
[data-slot="table-cell"] {
|
||||
border-top: var(--hm-table-divider-width) solid var(--hm-table-divider-color);
|
||||
}
|
||||
/* The header edge replaces the divider of the first body row. */
|
||||
[data-slot="table-header"] + [data-slot="table-body"] > [data-slot="table-row"]:first-child > [data-slot="table-cell"] {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
/* Rows: hover colors the face only (body rows, not the header). */
|
||||
[data-slot="table-body"] > [data-slot="table-row"]:hover {
|
||||
background: var(--hm-table-row-hover-background);
|
||||
}
|
||||
[data-slot="table-row"][data-state="selected"],
|
||||
[data-slot="table-body"] > [data-slot="table-row"][data-state="selected"]:hover {
|
||||
background: var(--hm-table-row-selected-background);
|
||||
}
|
||||
|
||||
[data-slot="table-footer"] {
|
||||
background: var(--hm-table-footer-background);
|
||||
font-weight: var(--hm-table-footer-font-weight);
|
||||
}
|
||||
[data-slot="table-footer"] [data-slot="table-cell"] {
|
||||
border-top: var(--hm-table-divider-width) solid var(--hm-table-divider-color);
|
||||
}
|
||||
|
||||
[data-slot="table-caption"] {
|
||||
margin-top: var(--hm-table-caption-gap);
|
||||
text-align: left;
|
||||
color: var(--hm-table-caption-color);
|
||||
font-size: var(--hm-table-caption-font-size);
|
||||
line-height: var(--hm-table-caption-line-height);
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/* Tabs mapping: default Primary Fill (content), Secondary Fill (surface), Legacy non-fill and overflow move buttons (D-090). Horizontal, text-only, no size/variant.
|
||||
States use Radix attributes: [data-state="active"|"inactive"], :hover, :focus-visible. */
|
||||
:root, [data-hm-theme] {
|
||||
/* ui_viz: docs/components/tabs.md §8, §16 — SOURCE_ADOPTED / COMPONENT_SCOPED (owner 2026-09-18). Not aliased to Button or radius/border tokens. */
|
||||
--hm-tab-height: 34px;
|
||||
--hm-tab-padding-x: 16px;
|
||||
--hm-tab-radius-top: 4px;
|
||||
--hm-tab-border-width: 1px;
|
||||
|
||||
/* ui_viz: docs/components/tabs.md §9 — Navigation Label 16/24/700, same weight for Selected */
|
||||
--hm-tab-font-size: 16px;
|
||||
--hm-tab-line-height: 24px;
|
||||
--hm-tab-font-weight: 700;
|
||||
|
||||
/* ui_viz: docs/components/tabs.md §10 — SOURCE_ADOPTED. Default face and border are also marked TEMPORARY there (alpha composites of
|
||||
Green tint / Green Light 2). Selected border is #DEDBD2 (color.stroke.tab.selected); no Semantic exists in HM, so the Primitive is used here. */
|
||||
--hm-tab-background: color-mix(in srgb, var(--hm-sem-palette-green-tint) 45%, transparent);
|
||||
--hm-tab-border-color: color-mix(in srgb, var(--hm-sem-palette-green-light-2) 35%, transparent);
|
||||
--hm-tab-text: var(--hm-sem-palette-green-light-2);
|
||||
--hm-tab-selected-background: var(--hm-sem-bg-surface);
|
||||
--hm-tab-selected-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-tab-selected-text: var(--hm-sem-action-primary);
|
||||
/* Hover (inactive only): Black 20% overlay — value Confirmed (tab.md 6, 8); foreground SOURCE_ADOPTED */
|
||||
--hm-tab-hover-overlay: color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent);
|
||||
--hm-tab-hover-text: var(--hm-sem-action-primary);
|
||||
--hm-tab-transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
/* Frame holds the Bar (and the move buttons when the tabs overflow). The whole Bar overlaps the content boundary by 1px; Selected paints its bottom stroke in the surface color. */
|
||||
[data-slot="tabs-frame"] {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
width: 100%;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
/* Bar: no surface of its own, gap 0, scrolls horizontally with the native scrollbar hidden. */
|
||||
[data-slot="tabs-list"] {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex: 0 1 auto;
|
||||
gap: 0;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
[data-slot="tabs-trigger"] {
|
||||
box-sizing: border-box;
|
||||
height: var(--hm-tab-height);
|
||||
min-height: var(--hm-tab-height);
|
||||
padding: 0 var(--hm-tab-padding-x);
|
||||
border: var(--hm-tab-border-width) solid var(--hm-tab-border-color);
|
||||
border-bottom-color: var(--hm-tab-selected-border-color);
|
||||
border-radius: var(--hm-tab-radius-top) var(--hm-tab-radius-top) 0 0;
|
||||
background-color: var(--hm-tab-background);
|
||||
background-image: none;
|
||||
color: var(--hm-tab-text);
|
||||
font-size: var(--hm-tab-font-size);
|
||||
line-height: var(--hm-tab-line-height);
|
||||
font-weight: var(--hm-tab-font-weight);
|
||||
box-shadow: none;
|
||||
transition: color var(--hm-tab-transition-duration) ease, background-color var(--hm-tab-transition-duration) ease;
|
||||
}
|
||||
/* One shared border between neighbours. Must come before the Selected rule. */
|
||||
[data-slot="tabs-trigger"] + [data-slot="tabs-trigger"] {
|
||||
border-left-width: 0;
|
||||
}
|
||||
[data-slot="tabs-trigger"][data-state="active"] {
|
||||
z-index: 2;
|
||||
border-color: var(--hm-tab-selected-border-color);
|
||||
border-bottom-color: var(--hm-tab-selected-background);
|
||||
background-color: var(--hm-tab-selected-background);
|
||||
color: var(--hm-tab-selected-text);
|
||||
}
|
||||
[data-slot="tabs-trigger"][data-state="inactive"]:hover:not(:disabled) {
|
||||
background-image: linear-gradient(var(--hm-tab-hover-overlay), var(--hm-tab-hover-overlay));
|
||||
color: var(--hm-tab-hover-text);
|
||||
}
|
||||
/* Focus is a global TBD in ui_viz: the shadcn focus ring from ui/tabs.tsx stays. The theme box-shadow is none, so keep the ring variables. */
|
||||
[data-slot="tabs-trigger"]:focus-visible {
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000);
|
||||
}
|
||||
|
||||
/* ---- Overflow move buttons (ui_viz tabs.md §24 D-090, CURRENT 2026-10-01). Outside the tablist, shown only when the tabs overflow. ---- */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-tab-arrow-size: 34px;
|
||||
--hm-tab-arrow-icon-size: 18px;
|
||||
--hm-tab-arrow-background: var(--hm-sem-palette-green-light-1);
|
||||
--hm-tab-arrow-border-color: var(--hm-sem-palette-green-dark-1);
|
||||
--hm-tab-arrow-icon-color: var(--hm-sem-on-action-primary);
|
||||
--hm-tab-arrow-disabled-opacity: 0.6;
|
||||
}
|
||||
[data-slot="tabs-frame"][data-overflow="true"] > [data-slot="tabs-list"] {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
[data-slot="tabs-arrow"] {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: var(--hm-tab-arrow-size);
|
||||
height: var(--hm-tab-arrow-size);
|
||||
padding: 0;
|
||||
border: var(--hm-tab-border-width) solid var(--hm-tab-arrow-border-color);
|
||||
background-color: var(--hm-tab-arrow-background);
|
||||
background-image: none;
|
||||
color: var(--hm-tab-arrow-icon-color);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
[data-slot="tabs-arrow"][data-direction="previous"] { border-radius: var(--hm-tab-radius-top) 0 0 0; }
|
||||
[data-slot="tabs-arrow"][data-direction="next"] { border-radius: 0 var(--hm-tab-radius-top) 0 0; }
|
||||
[data-slot="tabs-arrow"] svg { width: var(--hm-tab-arrow-icon-size); height: var(--hm-tab-arrow-icon-size); }
|
||||
[data-slot="tabs-arrow"]:hover:not(:disabled) {
|
||||
background-image: linear-gradient(var(--hm-tab-hover-overlay), var(--hm-tab-hover-overlay));
|
||||
}
|
||||
[data-slot="tabs-arrow"]:disabled {
|
||||
opacity: var(--hm-tab-arrow-disabled-opacity);
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* Focus value is global TBD: shadcn ring level kept (ring variables come from the tailwind focus-visible utilities). */
|
||||
[data-slot="tabs-arrow"]:focus-visible {
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
/* In the overflow state every item keeps its own label width (no equal split). */
|
||||
[data-slot="tabs-frame"][data-overflow="true"] [data-slot="tabs-trigger"] {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/* ---- Fill profile (ui_viz tabs.md §24 D-071~075): default Primary content; explicit surface is Secondary. ---- */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-tab-fill-line-color: var(--hm-sem-palette-green-light-3);
|
||||
/* REVIEW VALUE (ui_viz tabs.md §24: curve radius 8px, BORDER FOLLOW-UP). The curve itself is Confirmed, the radius is not. */
|
||||
--hm-tab-fill-curve-radius: 8px;
|
||||
}
|
||||
[data-slot="tabs-frame"][data-fill="true"] {
|
||||
--hm-tab-fill-panel: var(--hm-sem-bg-surface);
|
||||
--hm-tab-fill-face: linear-gradient(var(--hm-sem-bg-surface), var(--hm-sem-bg-surface));
|
||||
}
|
||||
[data-slot="tabs-frame"][data-fill="true"][data-panel-surface="content"] {
|
||||
--hm-tab-fill-panel: var(--hm-sem-bg-content);
|
||||
/* Non-white panel: the Selected face runs from White (top) to the panel color (bottom); the panel itself gets no gradient. */
|
||||
--hm-tab-fill-face: linear-gradient(to bottom, var(--hm-sem-palette-white) 0%, var(--hm-sem-bg-content) 100%);
|
||||
}
|
||||
[data-slot="tabs-frame"][data-fill="true"] > [data-slot="tabs-list"] {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
}
|
||||
[data-slot="tabs-frame"][data-fill="true"][data-overflow="false"] [data-slot="tabs-trigger"] {
|
||||
flex: 1 1 0;
|
||||
min-width: auto;
|
||||
}
|
||||
/* Inactive: only the bottom line is Green Light 3; face and top / vertical borders keep the basic profile values. */
|
||||
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"] {
|
||||
border-bottom-color: var(--hm-tab-fill-line-color);
|
||||
}
|
||||
/* Every fill neighbour restores its left border and overlaps it by 1px, matching the tested ui-viz geometry.
|
||||
The active tab's z-index keeps its Green edge above the inactive border without producing a double-width line. */
|
||||
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"] + [data-slot="tabs-trigger"] {
|
||||
margin-left: calc(-1 * var(--hm-tab-border-width));
|
||||
border-left-width: var(--hm-tab-border-width);
|
||||
}
|
||||
/* Selected: Green Light 3 top / left / right, bottom open in the panel color. */
|
||||
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"][data-state="active"] {
|
||||
border-color: var(--hm-tab-fill-line-color);
|
||||
border-bottom-color: var(--hm-tab-fill-panel);
|
||||
background-color: transparent;
|
||||
background-image: var(--hm-tab-fill-face);
|
||||
}
|
||||
/* Curves: a quarter arc of the vertical Green line toward each neighbour, 1px Green. Inside the arc is transparent (the neighbour shows),
|
||||
outside is the panel color. Decoration only: no pointer input. */
|
||||
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"][data-state="active"]:not(:last-child)::after,
|
||||
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"][data-state="active"]:not(:first-child)::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: calc(-1 * var(--hm-tab-border-width));
|
||||
width: var(--hm-tab-fill-curve-radius);
|
||||
height: var(--hm-tab-fill-curve-radius);
|
||||
pointer-events: none;
|
||||
}
|
||||
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"][data-state="active"]:not(:last-child)::after {
|
||||
right: calc(-1 * var(--hm-tab-fill-curve-radius));
|
||||
background: radial-gradient(circle at 100% 0, transparent calc(var(--hm-tab-fill-curve-radius) - var(--hm-tab-border-width)),
|
||||
var(--hm-tab-fill-line-color) calc(var(--hm-tab-fill-curve-radius) - var(--hm-tab-border-width)),
|
||||
var(--hm-tab-fill-line-color) var(--hm-tab-fill-curve-radius), var(--hm-tab-fill-panel) var(--hm-tab-fill-curve-radius));
|
||||
}
|
||||
[data-slot="tabs-frame"][data-fill="true"] [data-slot="tabs-trigger"][data-state="active"]:not(:first-child)::before {
|
||||
left: calc(-1 * var(--hm-tab-fill-curve-radius));
|
||||
background: radial-gradient(circle at 0 0, transparent calc(var(--hm-tab-fill-curve-radius) - var(--hm-tab-border-width)),
|
||||
var(--hm-tab-fill-line-color) calc(var(--hm-tab-fill-curve-radius) - var(--hm-tab-border-width)),
|
||||
var(--hm-tab-fill-line-color) var(--hm-tab-fill-curve-radius), var(--hm-tab-fill-panel) var(--hm-tab-fill-curve-radius));
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/* Textarea mapping: the control only. Label, Required star and Helper/Error message are consumer composition, not part of Textarea.
|
||||
States use native selectors (:focus-visible, [aria-invalid="true"], :disabled, [readonly]); resize uses data-resize.
|
||||
Input and Textarea values are separate Tokens even where the numbers match (ui_viz keeps them as separate component owners). */
|
||||
:root, [data-hm-theme] {
|
||||
/* ui_viz: docs/components/textarea.md §8.1 TEXTAREA-01/02 — SCOPED_DESIGN_APPROVAL (2026-09-17 owner, Textarea component-local).
|
||||
Not D-093 Confirmed; Canonical min-height and inset stay TBD in ui_viz. Not promoted to size.* or space.* scale. */
|
||||
--hm-textarea-min-height: 112px;
|
||||
--hm-textarea-padding-block: 8px;
|
||||
--hm-textarea-padding-inline: 12px;
|
||||
|
||||
/* ui_viz: docs/components/textarea.md §2 Typography — Value 16/24/400 (existing role) */
|
||||
--hm-textarea-font-size: 16px;
|
||||
--hm-textarea-line-height: 24px;
|
||||
--hm-textarea-font-weight: 400;
|
||||
|
||||
/* ui_viz: docs/components/textarea.md §2, §2.2, §3.3 — Confirmed (border 1px, radius 4px, surface, colors, Focus/Error 1.5px) */
|
||||
--hm-textarea-radius: var(--hm-radius-input);
|
||||
--hm-textarea-border-width: 1px;
|
||||
--hm-textarea-state-border-width: 1.5px;
|
||||
--hm-textarea-background: var(--hm-sem-bg-surface);
|
||||
--hm-textarea-text: var(--hm-sem-fg-primary);
|
||||
--hm-textarea-placeholder: var(--hm-sem-fg-placeholder);
|
||||
--hm-textarea-border-color: var(--hm-sem-stroke-input-default);
|
||||
--hm-textarea-focus-border-color: var(--hm-sem-stroke-input-focus);
|
||||
--hm-textarea-error-border-color: var(--hm-sem-action-destructive);
|
||||
}
|
||||
|
||||
[data-slot="textarea"] {
|
||||
box-sizing: border-box;
|
||||
min-height: var(--hm-textarea-min-height);
|
||||
padding: var(--hm-textarea-padding-block) var(--hm-textarea-padding-inline);
|
||||
border: var(--hm-textarea-border-width) solid var(--hm-textarea-border-color);
|
||||
border-radius: var(--hm-textarea-radius);
|
||||
background: var(--hm-textarea-background);
|
||||
color: var(--hm-textarea-text);
|
||||
font-size: var(--hm-textarea-font-size);
|
||||
line-height: var(--hm-textarea-line-height);
|
||||
font-weight: var(--hm-textarea-font-weight);
|
||||
box-shadow: none;
|
||||
resize: vertical;
|
||||
}
|
||||
[data-slot="textarea"][data-resize="none"] { resize: none; }
|
||||
[data-slot="textarea"][data-resize="both"] { resize: both; }
|
||||
[data-slot="textarea"]::placeholder {
|
||||
color: var(--hm-textarea-placeholder);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Hover is identical to Default (textarea.md §3.3, TEXTAREA-05 user decision): no hover rule. */
|
||||
|
||||
/* Focus: the border alone marks it (1.5px, no shadow or glow). Padding gives back the extra 0.5px so the value does not move. */
|
||||
[data-slot="textarea"]:focus-visible {
|
||||
outline: none;
|
||||
border-width: var(--hm-textarea-state-border-width);
|
||||
border-color: var(--hm-textarea-focus-border-color);
|
||||
padding: calc(var(--hm-textarea-padding-block) - (var(--hm-textarea-state-border-width) - var(--hm-textarea-border-width)))
|
||||
calc(var(--hm-textarea-padding-inline) - (var(--hm-textarea-state-border-width) - var(--hm-textarea-border-width)));
|
||||
}
|
||||
|
||||
/* Invalid owns the border; keyboard focus adds the same HM 3px / 50% semantic ring as Input. */
|
||||
[data-slot="textarea"][aria-invalid="true"] {
|
||||
border-width: var(--hm-textarea-state-border-width);
|
||||
border-color: var(--hm-textarea-error-border-color);
|
||||
padding: calc(var(--hm-textarea-padding-block) - (var(--hm-textarea-state-border-width) - var(--hm-textarea-border-width)))
|
||||
calc(var(--hm-textarea-padding-inline) - (var(--hm-textarea-state-border-width) - var(--hm-textarea-border-width)));
|
||||
}
|
||||
[data-slot="textarea"][aria-invalid="true"]:focus-visible {
|
||||
border-color: var(--hm-textarea-error-border-color);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
}
|
||||
|
||||
/* Disabled keeps the surface face. Opacity 0.6 is set in ui/textarea.tsx: ui_viz `.6` is Temporary; 2026-10-02 user decision extends it to Textarea. */
|
||||
[data-slot="textarea"]:disabled {
|
||||
background: var(--hm-textarea-background);
|
||||
}
|
||||
|
||||
/* Readonly (textarea.md §3.1): keeps the Default surface, border and typography, not dimmed, no resize affordance whatever `resize` says. */
|
||||
[data-slot="textarea"][readonly] {
|
||||
resize: none;
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/* Toggle Group mapping. ui-viz has no Toggle Group spec, so these are current HM values built from the HM Semantic / Primitive
|
||||
tokens. The items share the same selection language as Toggle (full Accent face when on) but use their own --hm-toggle-group-* tokens
|
||||
and do not depend on Toggle's CSS. spacing={0} (default) connects the items into a segmented bar; spacing > 0 separates them
|
||||
with a gap. Radix sets data-orientation only when `orientation` is given, so "horizontal" is selected as :not([data-orientation="vertical"]).
|
||||
States use Radix attributes ([data-state="on"|"off"], :disabled) and :hover.
|
||||
The look is in `@layer components` so a consumer's utility classes still win. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-toggle-group-height-sm: 28px;
|
||||
--hm-toggle-group-height-md: 34px;
|
||||
--hm-toggle-group-height-lg: 42px;
|
||||
--hm-toggle-group-padding-x-sm: 8px;
|
||||
--hm-toggle-group-padding-x-md: 12px;
|
||||
--hm-toggle-group-padding-x-lg: 16px;
|
||||
--hm-toggle-group-radius: var(--hm-radius-control-md);
|
||||
--hm-toggle-group-gap: 8px;
|
||||
--hm-toggle-group-font-size: 14px;
|
||||
--hm-toggle-group-line-height: 20px;
|
||||
--hm-toggle-group-font-weight: 500;
|
||||
|
||||
--hm-toggle-group-off-background: transparent;
|
||||
--hm-toggle-group-off-text: var(--hm-sem-fg-primary);
|
||||
--hm-toggle-group-hover-background: var(--hm-sem-surface-highlight);
|
||||
--hm-toggle-group-on-background: var(--accent);
|
||||
--hm-toggle-group-on-text: var(--accent-foreground);
|
||||
|
||||
--hm-toggle-group-border-width: 1px;
|
||||
--hm-toggle-group-border-color: var(--hm-sem-stroke-input-default);
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-toggle-group-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
[data-slot="toggle-group-item"] {
|
||||
--hm-toggle-group-item-height: var(--hm-toggle-group-height-md);
|
||||
--hm-toggle-group-item-padding-x: var(--hm-toggle-group-padding-x-md);
|
||||
box-sizing: border-box;
|
||||
gap: 8px;
|
||||
height: var(--hm-toggle-group-item-height);
|
||||
min-width: var(--hm-toggle-group-item-height);
|
||||
padding: 0 var(--hm-toggle-group-item-padding-x);
|
||||
border: var(--hm-toggle-group-border-width) solid transparent;
|
||||
border-radius: var(--hm-toggle-group-radius);
|
||||
background: var(--hm-toggle-group-off-background);
|
||||
color: var(--hm-toggle-group-off-text);
|
||||
font-size: var(--hm-toggle-group-font-size);
|
||||
line-height: var(--hm-toggle-group-line-height);
|
||||
font-weight: var(--hm-toggle-group-font-weight);
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="toggle-group-item"][data-size="sm"] {
|
||||
--hm-toggle-group-item-height: var(--hm-toggle-group-height-sm);
|
||||
--hm-toggle-group-item-padding-x: var(--hm-toggle-group-padding-x-sm);
|
||||
}
|
||||
[data-slot="toggle-group-item"][data-size="lg"] {
|
||||
--hm-toggle-group-item-height: var(--hm-toggle-group-height-lg);
|
||||
--hm-toggle-group-item-padding-x: var(--hm-toggle-group-padding-x-lg);
|
||||
}
|
||||
[data-slot="toggle-group-item"][data-icon-only]:has(> svg:only-child) {
|
||||
padding: 0;
|
||||
}
|
||||
[data-slot="toggle-group-item"][data-variant="outline"] {
|
||||
border-color: var(--hm-toggle-group-border-color);
|
||||
}
|
||||
[data-slot="toggle-group-item"]:hover:not(:disabled) {
|
||||
background: var(--hm-toggle-group-hover-background);
|
||||
}
|
||||
[data-slot="toggle-group-item"][data-state="on"],
|
||||
[data-slot="toggle-group-item"][data-state="on"]:hover:not(:disabled) {
|
||||
background: var(--hm-toggle-group-on-background);
|
||||
color: var(--hm-toggle-group-on-text);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
[data-slot="toggle-group-item"]:disabled {
|
||||
opacity: var(--hm-toggle-group-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Gap between separated items (spacing > 0). */
|
||||
[data-slot="toggle-group"] {
|
||||
gap: var(--hm-toggle-group-gap);
|
||||
}
|
||||
|
||||
/* Connected (spacing 0): square inner corners, round outer corners, outline borders overlap by one border width. */
|
||||
[data-slot="toggle-group"][data-spacing="0"] [data-slot="toggle-group-item"] {
|
||||
border-radius: 0;
|
||||
}
|
||||
[data-slot="toggle-group"][data-spacing="0"]:not([data-orientation="vertical"]) [data-slot="toggle-group-item"]:first-child {
|
||||
border-top-left-radius: var(--hm-toggle-group-radius);
|
||||
border-bottom-left-radius: var(--hm-toggle-group-radius);
|
||||
}
|
||||
[data-slot="toggle-group"][data-spacing="0"]:not([data-orientation="vertical"]) [data-slot="toggle-group-item"]:last-child {
|
||||
border-top-right-radius: var(--hm-toggle-group-radius);
|
||||
border-bottom-right-radius: var(--hm-toggle-group-radius);
|
||||
}
|
||||
[data-slot="toggle-group"][data-spacing="0"][data-orientation="vertical"] [data-slot="toggle-group-item"]:first-child {
|
||||
border-top-left-radius: var(--hm-toggle-group-radius);
|
||||
border-top-right-radius: var(--hm-toggle-group-radius);
|
||||
}
|
||||
[data-slot="toggle-group"][data-spacing="0"][data-orientation="vertical"] [data-slot="toggle-group-item"]:last-child {
|
||||
border-bottom-left-radius: var(--hm-toggle-group-radius);
|
||||
border-bottom-right-radius: var(--hm-toggle-group-radius);
|
||||
}
|
||||
[data-slot="toggle-group"][data-spacing="0"]:not([data-orientation="vertical"]) [data-slot="toggle-group-item"][data-variant="outline"]:not(:first-child) {
|
||||
margin-left: calc(-1 * var(--hm-toggle-group-border-width));
|
||||
}
|
||||
[data-slot="toggle-group"][data-spacing="0"][data-orientation="vertical"] [data-slot="toggle-group-item"][data-variant="outline"]:not(:first-child) {
|
||||
margin-top: calc(-1 * var(--hm-toggle-group-border-width));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/* Toggle mapping. ui-viz has no Toggle spec, so these are current HM values built from the HM Semantic / Primitive tokens.
|
||||
Toggle is a persistent pressed-state control, not an action button: it has its own look and does not reference any
|
||||
--hm-button-* token or the Button roles. States use the Radix attributes ([data-state="on"|"off"], :disabled) and :hover.
|
||||
The default look is in `@layer components` so a consumer's utility classes (width, height, rounded, layout) still win. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-toggle-height-sm: 28px;
|
||||
--hm-toggle-height-md: 34px;
|
||||
--hm-toggle-height-lg: 42px;
|
||||
--hm-toggle-padding-x-sm: 8px;
|
||||
--hm-toggle-padding-x-md: 12px;
|
||||
--hm-toggle-padding-x-lg: 16px;
|
||||
--hm-toggle-radius: var(--hm-radius-control-md);
|
||||
--hm-toggle-gap: 8px;
|
||||
--hm-toggle-font-size: 14px;
|
||||
--hm-toggle-line-height: 20px;
|
||||
--hm-toggle-font-weight: 500;
|
||||
|
||||
--hm-toggle-off-background: transparent;
|
||||
--hm-toggle-off-text: var(--hm-sem-fg-primary);
|
||||
--hm-toggle-hover-background: var(--hm-sem-surface-highlight);
|
||||
/* On = the persistent selected surface (full Accent), independent of Active and Focus. */
|
||||
--hm-toggle-on-background: var(--accent);
|
||||
--hm-toggle-on-text: var(--accent-foreground);
|
||||
|
||||
--hm-toggle-outline-border-width: 1px;
|
||||
--hm-toggle-outline-border-color: var(--hm-sem-stroke-input-default);
|
||||
|
||||
/* Current HM value for this component only; not a global Disabled rule. */
|
||||
--hm-toggle-disabled-opacity: 0.6;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
[data-slot="toggle"] {
|
||||
--hm-toggle-height: var(--hm-toggle-height-md);
|
||||
--hm-toggle-padding-x: var(--hm-toggle-padding-x-md);
|
||||
box-sizing: border-box;
|
||||
gap: var(--hm-toggle-gap);
|
||||
height: var(--hm-toggle-height);
|
||||
min-width: var(--hm-toggle-height);
|
||||
padding: 0 var(--hm-toggle-padding-x);
|
||||
border: var(--hm-toggle-outline-border-width) solid transparent;
|
||||
border-radius: var(--hm-toggle-radius);
|
||||
background: var(--hm-toggle-off-background);
|
||||
color: var(--hm-toggle-off-text);
|
||||
font-size: var(--hm-toggle-font-size);
|
||||
line-height: var(--hm-toggle-line-height);
|
||||
font-weight: var(--hm-toggle-font-weight);
|
||||
cursor: pointer;
|
||||
}
|
||||
[data-slot="toggle"][data-size="sm"] {
|
||||
--hm-toggle-height: var(--hm-toggle-height-sm);
|
||||
--hm-toggle-padding-x: var(--hm-toggle-padding-x-sm);
|
||||
}
|
||||
[data-slot="toggle"][data-size="lg"] {
|
||||
--hm-toggle-height: var(--hm-toggle-height-lg);
|
||||
--hm-toggle-padding-x: var(--hm-toggle-padding-x-lg);
|
||||
}
|
||||
/* Icon-only: a square of the control height. */
|
||||
[data-slot="toggle"][data-icon-only]:has(> svg:only-child) {
|
||||
padding: 0;
|
||||
}
|
||||
[data-slot="toggle"][data-variant="outline"] {
|
||||
border-color: var(--hm-toggle-outline-border-color);
|
||||
}
|
||||
[data-slot="toggle"]:hover:not(:disabled) {
|
||||
background: var(--hm-toggle-hover-background);
|
||||
}
|
||||
[data-slot="toggle"][data-state="on"],
|
||||
[data-slot="toggle"][data-state="on"]:hover:not(:disabled) {
|
||||
background: var(--hm-toggle-on-background);
|
||||
color: var(--hm-toggle-on-text);
|
||||
}
|
||||
[data-slot="toggle"]:disabled {
|
||||
opacity: var(--hm-toggle-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* Tooltip mapping. ui-viz has no Tooltip spec (inventory: behavior and design pending), so these are current HM values from the HM
|
||||
Semantic tokens. Short, non-interactive text on the inverse (dark) face; the arrow shares the face color. */
|
||||
:root, [data-hm-theme] {
|
||||
--hm-tooltip-background: var(--hm-sem-fg-primary);
|
||||
--hm-tooltip-text: var(--hm-sem-on-action-primary);
|
||||
--hm-tooltip-radius: var(--hm-radius-container);
|
||||
--hm-tooltip-padding-x: 10px;
|
||||
--hm-tooltip-padding-y: 6px;
|
||||
--hm-tooltip-max-width: 240px;
|
||||
--hm-tooltip-font-size: 12px;
|
||||
--hm-tooltip-line-height: 18px;
|
||||
--hm-tooltip-font-weight: 400;
|
||||
}
|
||||
|
||||
[data-slot="tooltip-content"] {
|
||||
box-sizing: border-box;
|
||||
max-width: var(--hm-tooltip-max-width);
|
||||
padding: var(--hm-tooltip-padding-y) var(--hm-tooltip-padding-x);
|
||||
border-radius: var(--hm-tooltip-radius);
|
||||
background: var(--hm-tooltip-background);
|
||||
color: var(--hm-tooltip-text);
|
||||
font-size: var(--hm-tooltip-font-size);
|
||||
line-height: var(--hm-tooltip-line-height);
|
||||
font-weight: var(--hm-tooltip-font-weight);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
[data-slot="tooltip-arrow"] {
|
||||
background: var(--hm-tooltip-background);
|
||||
fill: var(--hm-tooltip-background);
|
||||
}
|
||||
Reference in new issue
Block a user