feat: integrate latest HMDesign design system updates

This commit is contained in:
DESKTOP-KPCP6AL\ㅅㅎ committed 2026-10-11 11:09:03 +09:00
1 parent 99faec189e
commit 1c4e0bb9a1
209 files changed
+8845 -552

No files matched your search

+5 -2
View File
@@ -5,7 +5,7 @@ import { CheckIcon, MinusIcon } from "lucide-react"
// Radix Checkbox structure, data-state and ARIA are kept (ui-viz has no Checkbox spec; reference only); the look comes from
// src/theme/checkbox.css. Indeterminate is `checked="indeterminate"` (Radix); the shadcn check icon is kept and a minus icon is shown for it.
function Checkbox({ className, form, asChild, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
function Checkbox({ className, form, asChild, children, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
// Radix's reset listener uses the trigger's native form, while Root only passes `form` to its input.
const useFormButton = !!form && !asChild
const indicator = (
@@ -26,7 +26,10 @@ function Checkbox({ className, form, asChild, ...props }: React.ComponentProps<t
)}
{...props}
>
{useFormButton ? <button type="button" form={form}>{indicator}</button> : indicator}
{asChild
? React.cloneElement(React.Children.only(children) as React.ReactElement<{ children?: React.ReactNode }>, undefined,
(children as React.ReactElement<{ children?: React.ReactNode }>).props.children, indicator)
: useFormButton ? <button type="button" form={form}>{children}{indicator}</button> : <>{children}{indicator}</>}
</CheckboxPrimitive.Root>
)
}
+12 -4
View File
@@ -63,6 +63,9 @@ function Combobox({
const value = controlled ? valueProp : inner
const selected = options.find((option) => option.value === value)
const popupRef = useConnectedPopupRef()
const generatedId = React.useId()
const triggerId = props.id ?? `combobox-${generatedId}`
const popupId = `combobox-popup-${generatedId}`
const select = (next: string) => {
if (!controlled) setInner(next)
@@ -76,7 +79,7 @@ function Combobox({
<button
type="button"
role="combobox"
aria-haspopup="listbox"
aria-haspopup="dialog"
aria-expanded={open}
data-slot="combobox-trigger"
data-size={size}
@@ -84,6 +87,8 @@ function Combobox({
disabled={disabled}
className={cn("flex w-full items-center justify-between text-left outline-none disabled:cursor-not-allowed", className)}
{...props}
id={triggerId}
aria-controls={open ? popupId : undefined}
>
<span data-slot="combobox-value">{selected ? selected.label : placeholder}</span>
<ChevronDownIcon data-slot="combobox-icon" aria-hidden="true" />
@@ -91,6 +96,9 @@ function Combobox({
</PopoverTrigger>
<PopoverContent
ref={popupRef}
id={popupId}
aria-label={props["aria-label"]}
aria-labelledby={props["aria-labelledby"] ?? (props["aria-label"] ? undefined : triggerId)}
data-combobox-popup=""
data-size={size}
align="start"
@@ -102,9 +110,9 @@ function Combobox({
} as React.CSSProperties
}
>
<Command>
<CommandInput placeholder={searchPlaceholder} />
<CommandList>
<Command label={props["aria-label"] ?? placeholder}>
<CommandInput aria-label={searchPlaceholder} placeholder={searchPlaceholder} />
<CommandList label={props["aria-label"] ?? placeholder} aria-labelledby={props["aria-labelledby"]}>
<CommandEmpty>{emptyText}</CommandEmpty>
{options.map((option) => (
<CommandItem
@@ -54,6 +54,9 @@ function DatePicker({
const [inner, setInner] = React.useState<Date | undefined>(defaultValue)
const controlled = controlledProp ?? (valueProp !== undefined)
const value = controlled ? valueProp : inner
const generatedId = React.useId()
const triggerId = props.id ?? `date-picker-${generatedId}`
const popupId = `date-picker-popup-${generatedId}`
const select = (date: Date | undefined) => {
if (!controlled) setInner(date)
@@ -72,12 +75,17 @@ function DatePicker({
disabled={disabled}
className={cn("flex w-full items-center justify-between text-left outline-none disabled:cursor-not-allowed", className)}
{...props}
id={triggerId}
aria-controls={open ? popupId : undefined}
>
<span data-slot="date-picker-value">{value ? format(value) : placeholder}</span>
<CalendarIcon data-slot="date-picker-icon" aria-hidden="true" />
</button>
</PopoverTrigger>
<PopoverContent
id={popupId}
aria-label={props["aria-label"]}
aria-labelledby={props["aria-labelledby"] ?? (props["aria-label"] ? undefined : triggerId)}
align="start"
style={{ "--hm-popover-padding": "0px", "--hm-popover-width": "auto" } as React.CSSProperties}
// Radix focuses the first tabbable (the "previous month" icon button). Land on the day the Calendar marks as its tab stop
+4 -1
View File
@@ -73,7 +73,10 @@ function MenubarContent({
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-36 origin-(--radix-menubar-content-transform-origin) overflow-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
// Unmount the previous menu immediately on switching. Keeping its
// dismissable layer alive for an exit animation closes the next menu
// when that menu receives focus (Radix non-modal Menubar).
"z-50 min-w-36 origin-(--radix-menubar-content-transform-origin) overflow-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95",
className
)}
{...props}
+12 -5
View File
@@ -10,6 +10,8 @@ function Progress({
className,
value,
max,
asChild,
children,
...props
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
// Use the same valid range/default as Radix so the visual fill agrees with its ARIA value.
@@ -17,19 +19,24 @@ function Progress({
const percentage = typeof value === "number" && value >= 0 && value <= effectiveMax
? (value / effectiveMax) * 100
: 0
const indicator = <ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="size-full flex-1 transition-all"
style={{ transform: `translateX(${percentage - 100}%)` }}
/>
return (
<ProgressPrimitive.Root
data-slot="progress"
className={cn("relative w-full overflow-hidden", className)}
value={value}
max={max}
asChild={asChild}
{...props}
>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="size-full flex-1 transition-all"
style={{ transform: `translateX(${percentage - 100}%)` }}
/>
{asChild
? React.cloneElement(React.Children.only(children) as React.ReactElement<{ children?: React.ReactNode }>, undefined,
(children as React.ReactElement<{ children?: React.ReactNode }>).props.children, indicator)
: <>{children}{indicator}</>}
</ProgressPrimitive.Root>
)
}
@@ -27,6 +27,7 @@ function ToggleGroup({
size,
spacing = 0,
children,
style,
...props
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants> & {
@@ -38,7 +39,7 @@ function ToggleGroup({
data-variant={variant}
data-size={size}
data-spacing={spacing}
style={{ "--hm-toggle-group-gap": `${spacing * 4}px` } as React.CSSProperties}
style={{ "--hm-toggle-group-gap": `${spacing * 4}px`, ...style } as React.CSSProperties}
className={cn("group/toggle-group flex w-fit flex-row items-center data-vertical:flex-col data-vertical:items-stretch", className)}
{...props}
>
+1 -1
View File
@@ -258,7 +258,7 @@ export {
export type { DataTableColumnDef, DataTableInstance, DataTableOptions, DataTableProps } from "./patterns/data-table"
export { FilterBar, FilterBarActions, FilterBarSearch } from "./patterns/filter-bar"
export type { FilterBarSearchProps } from "./patterns/filter-bar/filter-bar"
export type { FilterBarProps, FilterBarSearchProps } from "./patterns/filter-bar/filter-bar"
export {
countDashboardFilter,
@@ -189,6 +189,7 @@ function DataTablePagination<TData extends RowData>({
className?: string
}) {
const { pageIndex, pageSize } = table.state.pagination
const pageSizeLabelId = React.useId()
const pageCount = Math.max(table.getPageCount(), 1)
const page = pageIndex + 1
const selected = table.getFilteredSelectedRowModel().rows.length
@@ -203,9 +204,9 @@ function DataTablePagination<TData extends RowData>({
{selected > 0 ? `${selected}개 선택됨 · 전체 ${total}개` : `전체 ${total}개`}
</p>
<div data-slot="data-table-page-size">
<span id="data-table-page-size-label">페이지당</span>
<span id={pageSizeLabelId}>페이지당</span>
<Select value={String(pageSize)} onValueChange={(value) => table.setPageSize(Number(value))}>
<SelectTrigger size="medium" aria-labelledby="data-table-page-size-label">
<SelectTrigger size="medium" aria-labelledby={pageSizeLabelId}>
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -14,8 +14,12 @@ import { Input, type InputSize } from "../../components/ui/input"
// <Select>…</Select> <Combobox … /> <DatePicker … />
// <FilterBarActions><Button onClick={reset}>초기화</Button></FilterBarActions>
// </FilterBar>
function FilterBar({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="filter-bar" className={cn("flex flex-wrap items-center", className)} {...props} />
type FilterBarProps = React.ComponentProps<"div"> & {
/** Opt-in publisher list composition. Omitted preserves the general layout. */
layout?: "default" | "list-basic" | "list-extended"
}
function FilterBar({ className, layout = "default", ...props }: FilterBarProps) {
return <div data-slot="filter-bar" data-layout={layout === "default" ? undefined : layout} className={cn("flex flex-wrap items-center", className)} {...props} />
}
type FilterBarSearchProps = Omit<React.ComponentProps<"input">, "size" | "value" | "defaultValue" | "onChange" | "type"> & {
@@ -32,22 +36,30 @@ function FilterBarSearch({
size = "medium",
placeholder = "검색",
"aria-label": ariaLabel,
readOnly,
disabled,
ref,
...props
}: FilterBarSearchProps) {
const inputRef = React.useRef<HTMLInputElement>(null)
React.useImperativeHandle(ref, () => inputRef.current!, [])
return (
<div data-slot="filter-bar-search" className={cn("relative", className)}>
<SearchIcon aria-hidden="true" data-slot="filter-bar-search-icon" className="pointer-events-none absolute" />
<Input
ref={inputRef}
type="search"
size={size}
value={value}
placeholder={placeholder}
aria-label={ariaLabel ?? placeholder}
readOnly={readOnly}
disabled={disabled}
onChange={(event) => onValueChange(event.target.value)}
{...props}
/>
{value ? (
<button type="button" data-slot="filter-bar-search-clear" aria-label="검색어 지우기" className="absolute" onClick={() => onValueChange("")}>
{value && !readOnly && !disabled ? (
<button type="button" data-slot="filter-bar-search-clear" aria-label="검색어 지우기" className="absolute" onClick={() => { inputRef.current?.focus(); onValueChange("") }}>
<XIcon aria-hidden="true" />
</button>
) : null}
@@ -61,4 +73,4 @@ function FilterBarActions({ className, ...props }: React.ComponentProps<"div">)
}
export { FilterBar, FilterBarActions, FilterBarSearch }
export type { FilterBarSearchProps }
export type { FilterBarProps, FilterBarSearchProps }
+3 -3
View File
@@ -115,14 +115,14 @@ type GNBUserProps = Omit<React.ComponentProps<"button">, "children"> & {
// Trigger button of the user menu: name / role text on the desktop bar; the Avatar (children) shows on the mobile bar only, where the
// text is hidden (ui-viz's profile is text-only, so the desktop bar has no image). Because of that the
// accessible name defaults to "<name> 사용자 메뉴". Use it as `<DropdownMenuTrigger asChild>`; the data-slot comes after the
// accessible name includes the visible name and description, followed by "사용자 메뉴". Use it as `<DropdownMenuTrigger asChild>`; the data-slot comes after the
// spread so the trigger's own slot does not replace it (same rule as Button).
function GNBUser({ className, name, description, children, "aria-label": ariaLabel, ...props }: GNBUserProps) {
return (
<button type="button" aria-label={ariaLabel ?? `${name} 사용자 메뉴`} className={cn("flex items-center", className)} {...props} data-slot="gnb-user">
<button type="button" aria-label={ariaLabel ?? `${name}${description ? ` ${description}` : ""} 사용자 메뉴`} className={cn("flex items-center", className)} {...props} data-slot="gnb-user">
{children}
<span data-slot="gnb-user-text">
<span data-slot="gnb-user-name">{name}</span>
<span data-slot="gnb-user-name">{name}{description ? " " : null}</span>
{description ? <span data-slot="gnb-user-description">{description}</span> : null}
</span>
</button>
+2 -2
View File
@@ -99,10 +99,10 @@ type LNBProfileProps = Omit<React.ComponentProps<"button">, "children"> & {
// it composes directly with `DropdownMenuTrigger asChild`; Radix owns Enter / Space, arrow navigation, Escape and focus return.
function LNBProfile({ className, name, description, children, "aria-label": ariaLabel, ...props }: LNBProfileProps) {
return (
<button type="button" aria-label={ariaLabel ?? `${name} 사용자 메뉴`} className={cn(className)} {...props} data-slot="lnb-profile">
<button type="button" aria-label={ariaLabel ?? `${name}${description ? ` ${description}` : ""} 사용자 메뉴`} className={cn(className)} {...props} data-slot="lnb-profile">
{children}
<div data-slot="lnb-profile-text">
<strong>{name}</strong>
<strong>{name}{description ? " " : null}</strong>
{description ? <p>{description}</p> : null}
</div>
</button>
+1 -1
View File
@@ -45,7 +45,7 @@
cursor: pointer;
}
[data-slot="accordion-trigger"]:hover:not(:disabled) {
background: transparent;
background: color-mix(in srgb, var(--hm-sem-palette-black) 5%, transparent);
}
[data-slot="accordion-trigger"]:disabled {
opacity: var(--hm-accordion-disabled-opacity);
+5 -1
View File
@@ -43,6 +43,8 @@
--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);
--hm-button-solid-red-background: var(--hm-sem-action-destructive);
--hm-button-solid-red-border: var(--hm-sem-action-destructive);
/* 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);
@@ -79,9 +81,11 @@
[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);
--hm-button-outline-ink: var(--hm-button-cancel-runtime-ink, var(--hm-sem-palette-gray));
}
[data-slot="button"][data-variant="destructive"] {
--hm-button-solid-background: var(--hm-button-solid-red-background);
--hm-button-solid-border: var(--hm-button-solid-red-border);
--hm-button-outline-ink: var(--hm-button-outline-red-ink);
}
+2 -1
View File
@@ -19,7 +19,8 @@
--hm-combobox-background: var(--hm-sem-bg-surface);
--hm-combobox-text: var(--hm-sem-fg-primary);
--hm-combobox-placeholder: var(--hm-sem-fg-placeholder);
/* Readable empty-value guidance on the white trigger; preserve shared tokens. */
--hm-combobox-placeholder: var(--hm-sem-fg-secondary);
--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);
@@ -135,6 +135,7 @@
[data-slot="dashboard-layout"] [data-slot="filter-bar"] {
flex-wrap: nowrap;
}
[data-slot="dashboard-layout"] [data-slot="filter-bar"][data-layout] { flex-wrap: wrap; }
[data-slot="dashboard-layout"] [data-slot="filter-bar-search"] {
flex: 0 0 var(--hm-filter-bar-search-width);
}
+2 -1
View File
@@ -19,7 +19,8 @@
--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);
/* Readable empty-value guidance on the white trigger; preserve shared tokens. */
--hm-date-picker-placeholder: var(--hm-sem-fg-secondary);
--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;
+7 -1
View File
@@ -101,9 +101,15 @@
padding: 0 var(--hm-drawer-padding);
}
/* Footer: actions stack full width (touch targets on a narrow screen), the primary action first as in shadcn. */
/* DOM order: Cancel → Execute. Mobile reverses the visual stack. */
[data-slot="drawer-footer"] {
flex: none;
flex-direction: row;
justify-content: flex-end;
gap: var(--hm-drawer-footer-gap);
padding: 16px var(--hm-drawer-padding) var(--hm-drawer-padding);
}
@media (max-width: 640px) {
[data-slot="drawer-footer"] { flex-direction: column-reverse; align-items: stretch; }
[data-slot="drawer-footer"] > * { width: 100%; }
}
+6
View File
@@ -76,6 +76,12 @@
margin-left: auto;
}
/* Explicit publisher list composition; default consumers keep the original layout. */
[data-slot="filter-bar"][data-layout="list-basic"] { justify-content: center; }
[data-slot="filter-bar"][data-layout="list-extended"] { justify-content: flex-start; }
[data-slot="filter-bar"][data-layout] { flex-wrap: wrap; }
[data-slot="filter-bar"][data-layout] > [data-slot="filter-bar-actions"] { margin-left: 0; }
/* 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"] {
+25 -3
View File
@@ -547,12 +547,34 @@
grid-template-columns: var(--hm-lnb-width) minmax(0, 1fr);
min-width: 0;
min-height: 0;
background: var(--hm-sem-palette-green-light-2);
background: var(--hm-sem-bg-content);
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);
--hm-gnb-content-menu-color: var(--hm-sem-action-primary);
--hm-lnb-background: var(--hm-sem-bg-content);
--hm-lnb-background-end: var(--hm-sem-bg-content);
--hm-lnb-text: var(--hm-gnb-content-menu-color);
--hm-lnb-text-secondary: var(--hm-gnb-content-menu-color);
--hm-lnb-item-text-stroke-width: 0px;
--hm-lnb-item-text-shadow: none;
--hm-lnb-hover-background: color-mix(in srgb, var(--hm-gnb-content-menu-color) 8%, transparent);
--hm-lnb-current-background: color-mix(in srgb, var(--hm-gnb-content-menu-color) 20%, transparent);
--hm-lnb-current-text: var(--hm-gnb-content-menu-color);
--hm-lnb-current-depth: none;
--hm-lnb-group-current-text: var(--hm-gnb-content-menu-color);
--hm-lnb-group-open-background: transparent;
--hm-lnb-group-open-line: 0;
--hm-lnb-focus-outline: 2px solid var(--hm-gnb-content-menu-color);
border-right: 1px solid var(--hm-sem-stroke-default);
}
[data-hm-gnb-layout="sidebar"][data-hm-color="brown"] > [data-hm-gnb-body] > [data-hm-gnb-sidebar] {
--hm-gnb-content-menu-color: var(--hm-sem-palette-brown-default);
}
/* Content navigation uses one straight divider, without the green LNB join. */
[data-hm-gnb-layout="sidebar"] [data-hm-gnb-sidebar]::after,
[data-hm-gnb-layout="sidebar"] [data-hm-gnb-sidebar] [data-slot="lnb-item"][aria-current="page"]::before,
[data-hm-gnb-layout="sidebar"] [data-hm-gnb-sidebar] [data-slot="lnb-item"][aria-current="page"]::after { content: none; }
[data-hm-gnb-layout="sidebar"] > [data-hm-gnb-body] > [data-hm-gnb-main] {
position: relative;
grid-column: 2;
@@ -560,7 +582,7 @@
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);
border-radius: 0 var(--hm-gnb-join) 0 0;
}
@media (max-width: 1023px) {
[data-hm-gnb-layout="sidebar"] > [data-hm-gnb-body] {
+2 -2
View File
@@ -1,7 +1,7 @@
/* 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. */
Black 10% to the same edge; an open group is a Black 8% 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;
@@ -52,7 +52,7 @@
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-background: color-mix(in srgb, var(--hm-sem-palette-black) 8%, 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);
+4
View File
@@ -15,5 +15,9 @@
background: var(--hm-progress-track-background);
}
[data-slot="progress-indicator"] {
/* Consumer children in an asChild root must not displace the filled bar. */
position: absolute;
inset: 0;
border-radius: var(--hm-progress-radius);
background: var(--hm-progress-indicator-background);
}
+6 -1
View File
@@ -42,8 +42,13 @@ export function isRuntimeTheme(theme: unknown): theme is import("./contract").Ru
/** 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]) =>
const values: (readonly [string, string])[] = Object.entries(theme.tokens).map(([key, value]) =>
[runtimeThemeContract[key as RuntimeThemeToken].property, value!] as const)
// Reviewed Gray default comes from the existing palette. Retain custom cancel
// overrides without changing the public token or the full Default preset.
const cancel = theme.tokens["color.action.cancel"]
if (cancel && cancel.toLowerCase() !== runtimeThemeContract["color.action.cancel"].defaultValue)
values.push(["--hm-button-cancel-runtime-ink", cancel])
resetRuntimeTheme(root)
const scope = root.getAttribute("data-hm-theme")
const properties = values.map(([property]) => ({
+6
View File
@@ -4,6 +4,8 @@
meaning only: no --hm-dialog-* token is referenced. */
:root, [data-hm-theme] {
--hm-sheet-background: var(--hm-sem-bg-surface);
/* Existing control token reused provisionally; no new approved radius value. */
--hm-sheet-radius: var(--hm-radius-control-md);
--hm-sheet-text: var(--hm-sem-fg-primary);
--hm-sheet-border-width: 1px;
--hm-sheet-border-color: var(--hm-sem-stroke-input-default);
@@ -53,6 +55,10 @@
[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-content"][data-side="right"] { border-radius: var(--hm-sheet-radius) 0 0 var(--hm-sheet-radius); }
[data-slot="sheet-content"][data-side="left"] { border-radius: 0 var(--hm-sheet-radius) var(--hm-sheet-radius) 0; }
[data-slot="sheet-content"][data-side="top"] { border-radius: 0 0 var(--hm-sheet-radius) var(--hm-sheet-radius); }
[data-slot="sheet-content"][data-side="bottom"] { border-radius: var(--hm-sheet-radius) var(--hm-sheet-radius) 0 0; }
[data-slot="sheet-header"] {
flex: none;
+3 -3
View File
@@ -22,9 +22,9 @@
--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);
/* Publisher review: table-only Accent 20%; other highlight mappings stay unchanged. */
--hm-table-row-hover-background: color-mix(in srgb, var(--hm-sem-accent) 20%, transparent);
--hm-table-row-selected-background: var(--hm-table-row-hover-background);
--hm-table-footer-background: var(--hm-sem-bg-subtle);
--hm-table-footer-font-weight: 500;
+2 -2
View File
@@ -147,8 +147,8 @@
}
[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%);
/* D-02 (2026-10-09): only Primary's selected face starts at White alpha .8; the content stop, direction and geometry stay unchanged. */
--hm-tab-fill-face: linear-gradient(to bottom, color-mix(in srgb, var(--hm-sem-palette-white) 80%, transparent) 0%, var(--hm-sem-bg-content) 100%);
}
[data-slot="tabs-frame"][data-fill="true"] > [data-slot="tabs-list"] {
flex: 1 1 auto;