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}
>