@godxjp/ui 19.6.0 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/components/data-display/card.d.ts +2 -2
  2. package/dist/components/data-display/collapsible.d.ts +23 -4
  3. package/dist/components/data-display/collapsible.js +119 -4
  4. package/dist/components/data-display/index.d.ts +3 -1
  5. package/dist/components/data-display/index.js +2 -0
  6. package/dist/components/data-display/legend.d.ts +15 -0
  7. package/dist/components/data-display/legend.js +11 -0
  8. package/dist/components/data-display/list-row.js +1 -1
  9. package/dist/components/data-display/popover.d.ts +58 -5
  10. package/dist/components/data-display/popover.js +206 -27
  11. package/dist/components/data-display/progress.d.ts +50 -2
  12. package/dist/components/data-display/progress.js +57 -9
  13. package/dist/components/data-entry/calendar.d.ts +1 -1
  14. package/dist/components/data-entry/calendar.js +67 -32
  15. package/dist/components/data-entry/checkbox.d.ts +19 -6
  16. package/dist/components/data-entry/checkbox.js +55 -16
  17. package/dist/components/data-entry/choice-option.d.ts +1 -1
  18. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  19. package/dist/components/data-entry/field.js +0 -1
  20. package/dist/components/data-entry/form.js +1 -1
  21. package/dist/components/data-entry/label.d.ts +3 -2
  22. package/dist/components/data-entry/label.js +23 -10
  23. package/dist/components/data-entry/month-picker.d.ts +1 -1
  24. package/dist/components/data-entry/month-range-picker.d.ts +1 -1
  25. package/dist/components/data-entry/transfer.js +16 -6
  26. package/dist/components/data-entry/tree-select-strategy.d.ts +1 -1
  27. package/dist/components/data-entry/tree-select.js +29 -12
  28. package/dist/components/data-entry/tree-utils.d.ts +1 -1
  29. package/dist/components/data-entry/upload.js +7 -2
  30. package/dist/components/feedback/dialog.d.ts +105 -38
  31. package/dist/components/feedback/dialog.js +272 -194
  32. package/dist/components/feedback/overlay-close-focus.d.ts +31 -0
  33. package/dist/components/feedback/overlay-close-focus.js +31 -0
  34. package/dist/components/feedback/overlay-header-tone.d.ts +1 -1
  35. package/dist/components/feedback/sheet.d.ts +53 -11
  36. package/dist/components/feedback/sheet.js +150 -81
  37. package/dist/components/feedback/tooltip.d.ts +51 -7
  38. package/dist/components/feedback/tooltip.js +107 -25
  39. package/dist/components/general/button.js +2 -2
  40. package/dist/components/general/logo.d.ts +17 -0
  41. package/dist/components/general/logo.js +22 -16
  42. package/dist/components/layout/app-shell.d.ts +1 -1
  43. package/dist/components/layout/app-shell.js +52 -8
  44. package/dist/components/layout/aspect-ratio.js +0 -1
  45. package/dist/components/layout/auth-divider.js +0 -1
  46. package/dist/components/layout/flex.d.ts +1 -1
  47. package/dist/components/layout/flex.js +19 -2
  48. package/dist/components/layout/nav-surface.d.ts +26 -0
  49. package/dist/components/layout/nav-surface.js +17 -0
  50. package/dist/components/layout/org-switcher.d.ts +5 -1
  51. package/dist/components/layout/org-switcher.js +5 -3
  52. package/dist/components/layout/separator.js +0 -1
  53. package/dist/components/layout/sidebar.js +4 -1
  54. package/dist/components/layout/split-pane.d.ts +14 -1
  55. package/dist/components/layout/split-pane.js +26 -13
  56. package/dist/components/layout/topbar-item.js +1 -2
  57. package/dist/components/navigation/app-setting-picker.js +21 -2
  58. package/dist/components/navigation/app-setting-toggle.d.ts +16 -0
  59. package/dist/components/navigation/app-setting-toggle.js +96 -0
  60. package/dist/components/navigation/dropdown-menu.d.ts +199 -18
  61. package/dist/components/navigation/dropdown-menu.js +344 -117
  62. package/dist/components/navigation/index.d.ts +2 -0
  63. package/dist/components/navigation/index.js +2 -0
  64. package/dist/components/navigation/pagination-utils.d.ts +2 -1
  65. package/dist/components/navigation/tabs.d.ts +47 -6
  66. package/dist/components/navigation/tabs.js +152 -90
  67. package/dist/components/ui/accordion.d.ts +50 -5
  68. package/dist/components/ui/accordion.js +239 -33
  69. package/dist/components/ui/aspect-ratio.d.ts +23 -2
  70. package/dist/components/ui/aspect-ratio.js +15 -13
  71. package/dist/components/ui/avatar.d.ts +29 -4
  72. package/dist/components/ui/avatar.js +111 -25
  73. package/dist/components/ui/hover-card.d.ts +42 -4
  74. package/dist/components/ui/hover-card.js +183 -27
  75. package/dist/components/ui/label.js +0 -1
  76. package/dist/components/ui/segmented.d.ts +3 -3
  77. package/dist/components/ui/separator.d.ts +8 -2
  78. package/dist/components/ui/separator.js +17 -9
  79. package/dist/components/ui/toggle-group.d.ts +50 -5
  80. package/dist/components/ui/toggle-group.js +79 -20
  81. package/dist/components/ui/toggle.d.ts +31 -5
  82. package/dist/components/ui/toggle.js +42 -3
  83. package/dist/i18n/messages/en.json +8 -0
  84. package/dist/i18n/messages/ja.json +8 -0
  85. package/dist/i18n/messages/vi.json +8 -0
  86. package/dist/lib/control-styles.d.ts +1 -1
  87. package/dist/lib/control-styles.js +1 -1
  88. package/dist/lib/slot.d.ts +32 -0
  89. package/dist/lib/slot.js +22 -0
  90. package/dist/lib/variants.d.ts +22 -3
  91. package/dist/lib/variants.js +56 -1
  92. package/dist/props/components/app.prop.d.ts +25 -1
  93. package/dist/props/components/data-display.prop.d.ts +17 -0
  94. package/dist/props/components/data-entry.prop.d.ts +32 -4
  95. package/dist/props/components/index.d.ts +1 -1
  96. package/dist/props/components/layout.prop.d.ts +118 -5
  97. package/dist/props/components/navigation.prop.d.ts +1 -1
  98. package/dist/props/registry.d.ts +22 -2
  99. package/dist/props/registry.js +33 -2
  100. package/dist/props/vocabulary/content.prop.d.ts +1 -1
  101. package/dist/props/vocabulary/index.d.ts +2 -2
  102. package/dist/props/vocabulary/interaction.prop.d.ts +17 -1
  103. package/dist/props/vocabulary/layout.prop.d.ts +57 -1
  104. package/dist/styles/control.css +142 -1
  105. package/dist/styles/core.css +5 -2
  106. package/dist/styles/data-display-layout.css +72 -0
  107. package/dist/styles/focus-ring.css +4 -0
  108. package/dist/styles/index.css +5 -2
  109. package/dist/styles/layout.css +106 -0
  110. package/dist/styles/shell-layout.css +140 -8
  111. package/dist/styles/text-layout.css +5 -4
  112. package/dist/tokens/base.css +1 -1
  113. package/dist/tokens/components/data-display.css +11 -0
  114. package/dist/tokens/components/shell.css +3 -1
  115. package/dist/tokens/foundation.css +2 -0
  116. package/docs/COMPONENTS.md +9 -3
  117. package/docs/DESIGN-AUTHORITY.md +96 -71
  118. package/docs/FRAME-COVERAGE-REPORT.md +5 -3
  119. package/docs/README.md +14 -14
  120. package/docs/WHAT-BELONGS-HERE.md +179 -0
  121. package/docs/data-display/legend.tsx +145 -0
  122. package/docs/data-display/progress.tsx +32 -0
  123. package/docs/data-entry/segmented.tsx +1 -1
  124. package/docs/data-entry/select.tsx +2 -2
  125. package/docs/feedback/sheet.tsx +1 -1
  126. package/docs/layout/app-shell-arrangements.tsx +225 -0
  127. package/docs/layout/aspect-ratio.tsx +1 -1
  128. package/docs/navigation/app-setting-picker.tsx +26 -1
  129. package/docs/navigation/app-setting-toggle.tsx +111 -0
  130. package/package.json +11 -8
  131. package/scripts/_agent-setup.mjs +165 -3
  132. package/scripts/consumer-rule.md +98 -0
  133. package/scripts/guinea-pig-skill.md +322 -0
  134. package/scripts/init-guinea-pig.mjs +62 -0
  135. package/scripts/postinstall.mjs +13 -2
  136. package/scripts/ui-audit.mjs +115 -14
  137. /package/dist/tokens/{antd.generated.css → derived.css} +0 -0
@@ -1,27 +1,75 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- function Progress({ value, label, tone, over = false }) {
5
- const isOver = over && value > 100;
6
- const boundedValue = Math.max(0, Math.min(100, value));
7
- const effectiveTone = tone ?? (isOver ? "destructive" : "success");
4
+ import { useTranslation } from "../../i18n/use-translation.js";
5
+ import { cn } from "../../lib/utils.js";
6
+ function Progress(props) {
7
+ const { t } = useTranslation();
8
8
  const labelId = React.useId();
9
+ const {
10
+ label,
11
+ className,
12
+ segments,
13
+ value,
14
+ tone,
15
+ over = false,
16
+ "aria-label": ariaLabel,
17
+ "aria-labelledby": ariaLabelledBy,
18
+ ...domProps
19
+ } = props;
20
+ const named = ariaLabel !== void 0 || ariaLabelledBy !== void 0;
21
+ const labelledBy = ariaLabelledBy ?? (label !== void 0 ? labelId : void 0);
22
+ const caption = label !== void 0 ? /* @__PURE__ */ jsx("div", { className: "ui-progress-label", id: labelId, children: label }) : null;
23
+ if (segments !== void 0) {
24
+ const amounts = segments.map((segment) => Math.max(0, segment.value));
25
+ const total = amounts.reduce((sum, amount) => sum + amount, 0);
26
+ const separator = t("dataDisplay.progress.breakdownSeparator");
27
+ const spoken = segments.map((segment, index) => `${segment.label} ${amounts[index]}`).join(separator);
28
+ return /* @__PURE__ */ jsxs(
29
+ "div",
30
+ {
31
+ className: cn("ui-progress", className),
32
+ "data-breakdown": "",
33
+ role: "img",
34
+ "aria-labelledby": ariaLabelledBy,
35
+ "aria-label": ariaLabelledBy !== void 0 ? void 0 : ariaLabel !== void 0 ? `${ariaLabel}${separator}${spoken}` : label !== void 0 ? `${label}${separator}${spoken}` : spoken,
36
+ ...domProps,
37
+ children: [
38
+ /* @__PURE__ */ jsx("div", { className: "ui-progress-track", children: segments.map((segment, index) => /* @__PURE__ */ jsx(
39
+ "div",
40
+ {
41
+ className: "ui-progress-segment",
42
+ "data-tone": segment.tone,
43
+ style: { inlineSize: total === 0 ? "0%" : `${amounts[index] / total * 100}%` }
44
+ },
45
+ `${segment.label}-${index}`
46
+ )) }),
47
+ caption
48
+ ]
49
+ }
50
+ );
51
+ }
52
+ const meterValue = value ?? 0;
53
+ const isOver = over && meterValue > 100;
54
+ const boundedValue = Math.max(0, Math.min(100, meterValue));
55
+ const effectiveTone = tone ?? (isOver ? "destructive" : "success");
9
56
  return /* @__PURE__ */ jsxs(
10
57
  "div",
11
58
  {
12
- className: "ui-progress",
59
+ className: cn("ui-progress", className),
13
60
  "data-tone": effectiveTone,
14
61
  "data-over": isOver ? "" : void 0,
15
62
  role: "progressbar",
16
63
  "aria-valuenow": boundedValue,
17
64
  "aria-valuemin": 0,
18
65
  "aria-valuemax": 100,
19
- "aria-valuetext": `${isOver ? Math.round(value) : boundedValue}%`,
20
- "aria-labelledby": label ? labelId : void 0,
21
- "aria-label": label ? void 0 : "Progress",
66
+ "aria-valuetext": `${isOver ? Math.round(meterValue) : boundedValue}%`,
67
+ "aria-labelledby": labelledBy,
68
+ "aria-label": labelledBy !== void 0 ? void 0 : named ? ariaLabel : t("dataDisplay.progress.ariaLabel"),
69
+ ...domProps,
22
70
  children: [
23
71
  /* @__PURE__ */ jsx("div", { className: "ui-progress-track", children: /* @__PURE__ */ jsx("div", { className: "ui-progress-bar", style: { width: `${boundedValue}%` } }) }),
24
- label ? /* @__PURE__ */ jsx("div", { className: "ui-progress-label", id: labelId, children: label }) : null
72
+ caption
25
73
  ]
26
74
  }
27
75
  );
@@ -1,4 +1,4 @@
1
1
  import * as React from "react";
2
2
  import type { CalendarProp } from "../../props/components/data-entry.prop.js";
3
3
  export type { CalendarProp, CalendarProp as CalendarProps, CalendarFooterProp, } from "../../props/components/data-entry.prop.js";
4
- export declare function Calendar({ className, classNames, showOutsideDays, "aria-label": ariaLabel, labels, showToday, showClose, onClose, footer, month: monthProp, onMonthChange, ...props }: CalendarProp): React.JSX.Element;
4
+ export declare function Calendar({ className, classNames, showOutsideDays, "aria-label": ariaLabel, labels, showToday, showClose, onClose, footer, width, bordered, month: monthProp, onMonthChange, ...props }: CalendarProp): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { ChevronLeft, ChevronRight } from "lucide-react";
4
+ import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from "lucide-react";
5
5
  import { DayPicker, dateMatchModifiers } from "react-day-picker";
6
6
  import { useTranslation } from "../../i18n/use-translation.js";
7
7
  import { cn } from "../../lib/utils.js";
@@ -21,6 +21,8 @@ function Calendar({
21
21
  showClose = false,
22
22
  onClose,
23
23
  footer,
24
+ width = "auto",
25
+ bordered = false,
24
26
  month: monthProp,
25
27
  onMonthChange,
26
28
  ...props
@@ -78,6 +80,8 @@ function Calendar({
78
80
  labelNav: labels?.labelNav ?? (() => `${ariaLabel ?? "Calendar"} navigation`)
79
81
  },
80
82
  className: cn("ui-calendar", className),
83
+ "data-width": width === "full" ? "full" : void 0,
84
+ "data-bordered": bordered ? "true" : void 0,
81
85
  classNames: {
82
86
  months: cn("ui-calendar-months", classNames?.months),
83
87
  month: cn("ui-calendar-month", classNames?.month),
@@ -100,51 +104,82 @@ function Calendar({
100
104
  classNames?.button_next
101
105
  ),
102
106
  month_grid: cn("ui-calendar-grid", classNames?.month_grid),
103
- weekdays: cn("flex", classNames?.weekdays),
107
+ /*
108
+ * The weekday row needs a DS class of its own. It only ever carried `flex`, so any rule
109
+ * written for it — the ruled grid's end edge, the header tint — matched nothing and failed
110
+ * silently while looking correct in the stylesheet.
111
+ */
112
+ weekdays: cn("ui-calendar-weekdays flex", classNames?.weekdays),
113
+ /*
114
+ * Named for the same reason the weekday row is: a slot with no DS class cannot be styled
115
+ * from this package, and a rule written for one fails silently. These two only render with
116
+ * `showWeekNumber`, which is exactly why the gap went unnoticed.
117
+ */
118
+ week_number: cn("ui-calendar-week-number", classNames?.week_number),
119
+ week_number_header: cn("ui-calendar-week-number-header", classNames?.week_number_header),
104
120
  weekday: cn("ui-calendar-weekday", classNames?.weekday),
105
121
  week: cn("ui-calendar-week", classNames?.week),
106
- day: cn(
107
- "ui-calendar-day",
108
- "[&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50",
109
- "[&:has([aria-selected].day-range-end)]:rounded-e-md",
110
- classNames?.day
111
- ),
122
+ /*
123
+ * ONE ELEMENT OWNS THE DAY SURFACE, and it is the `<button>`.
124
+ *
125
+ * It used to be split: hover painted the button, selection painted the `<td>`. Measured on
126
+ * a real page — the selected day came out a SHARP blue square (td, border-radius 0) while
127
+ * hover was a rounded grey (button, 3.71px). Same for `outside`: `text-muted-foreground`
128
+ * sat on the td while the ghost button set its own colour underneath, so days from the
129
+ * neighbouring month were not greyed at all. Two symptoms, one cause.
130
+ *
131
+ * So the `td` keeps ONLY grid duties — column width, the row it sits in — and every state
132
+ * (hover, selected, today, outside, disabled, range) is expressed on the button, where the
133
+ * radius and the padding already live. Adding a state later means adding it in one place;
134
+ * that was the part that kept going wrong.
135
+ */
136
+ day: cn("ui-calendar-day", classNames?.day),
112
137
  day_button: cn(
113
138
  buttonVariants({ variant: "ghost" }),
114
139
  "ui-calendar-day-button",
115
140
  classNames?.day_button
116
141
  ),
117
- range_start: cn("day-range-start rounded-s-md", classNames?.range_start),
118
- range_end: cn("day-range-end rounded-e-md", classNames?.range_end),
119
- range_middle: cn(
120
- "aria-selected:bg-accent aria-selected:text-accent-foreground",
121
- classNames?.range_middle
122
- ),
123
- selected: cn(
124
- "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
125
- // The day is a ghost <button> that sets its OWN (dark --foreground) text colour, which
126
- // overrides the cell's text-primary-foreground and fails contrast on the blue fill
127
- // (axe color-contrast). Force the button label to primary-foreground through hover/focus.
128
- "[&>button]:text-primary-foreground [&>button:hover]:bg-primary [&>button:hover]:text-primary-foreground [&>button:focus]:text-primary-foreground",
129
- classNames?.selected
130
- ),
131
- today: cn("bg-accent text-accent-foreground", classNames?.today),
132
- outside: cn(
133
- "day-outside text-muted-foreground aria-selected:text-muted-foreground",
134
- classNames?.outside
135
- ),
142
+ range_start: cn("day-range-start", classNames?.range_start),
143
+ range_end: cn("day-range-end", classNames?.range_end),
144
+ range_middle: cn("day-range-middle", classNames?.range_middle),
145
+ selected: cn("day-selected", classNames?.selected),
146
+ today: cn("day-today", classNames?.today),
147
+ outside: cn("day-outside", classNames?.outside),
136
148
  disabled: cn("ui-calendar-day-disabled", classNames?.disabled),
137
149
  hidden: cn("invisible", classNames?.hidden),
138
- footer: cn("ui-calendar-footer", classNames?.footer),
139
- ...classNames
150
+ footer: cn("ui-calendar-footer", classNames?.footer)
151
+ /*
152
+ * NO `...classNames` here. Spreading the consumer's object last REPLACED each slot that it
153
+ * names, so `classNames={{ day: "my-class" }}` dropped `.ui-calendar-day` — and with it
154
+ * every state selector, because they all key on that class. The slots above already merge
155
+ * the consumer's value through `cn()`; this line only ever un-merged them.
156
+ *
157
+ * A slot this component does not name still passes straight through: react-day-picker
158
+ * reads its own defaults for anything absent from this object.
159
+ */
140
160
  },
161
+ ...props,
141
162
  components: {
163
+ /*
164
+ * FOUR orientations, not two. `Nav` asks for left/right, but `Dropdown` asks for "down" —
165
+ * and mapping everything that is not "left" to ChevronRight pointed the month and year
166
+ * selects sideways. react-day-picker's own Chevron handles all four; ours has to as well.
167
+ */
142
168
  Chevron: ({ orientation, className: chevronClassName }) => {
143
- const Icon = orientation === "left" ? ChevronLeft : ChevronRight;
169
+ const Icon = orientation === "left" ? ChevronLeft : orientation === "up" ? ChevronUp : orientation === "down" ? ChevronDown : ChevronRight;
144
170
  return /* @__PURE__ */ jsx(Icon, { className: cn("ui-calendar-chevron", chevronClassName), "aria-hidden": "true" });
145
- }
171
+ },
172
+ /*
173
+ * A consumer's `components` MERGE with ours; they must not replace the object.
174
+ *
175
+ * `{...props}` used to sit after this block, so `components={{ DayButton: Custom }}` — the
176
+ * documented way to put a marker on a day — silently deleted our `Chevron` too. Measured
177
+ * on a live page: the nav arrows came back as react-day-picker's own polygon chevron,
178
+ * which needs a `fill` and therefore picked up `--rdp-accent-color: blue`. The consumer
179
+ * replaced one component and lost an unrelated one, with no error.
180
+ */
181
+ ...props.components
146
182
  },
147
- ...props,
148
183
  ...rangeDefaults
149
184
  }
150
185
  );
@@ -1,11 +1,10 @@
1
1
  import * as React from "react";
2
- import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
3
2
  import { CheckboxGroup } from "./checkbox-group.js";
4
3
  /**
5
4
  * Decorative checkbox glyph — a non-interactive `<span>`, NOT the real {@link Checkbox} (which is a
6
- * `<button>`). Used where the row itself is the interactive element and a nested `<button>` would
7
- * be invalid HTML (Cascader's option rows). Shares {@link CheckboxGlyph} so "partial" can never
8
- * drift apart from the real control's rendering again.
5
+ * real `<input>` wrapped by react-aria-components). Used where the row itself is the interactive
6
+ * element and a nested control would be invalid HTML (Cascader's option rows). Shares
7
+ * {@link CheckboxGlyph} so "partial" can never drift apart from the real control's rendering again.
9
8
  */
10
9
  export declare function CheckboxVisual({ checked, indeterminate, disabled, className, }: {
11
10
  checked: boolean;
@@ -13,7 +12,21 @@ export declare function CheckboxVisual({ checked, indeterminate, disabled, class
13
12
  disabled?: boolean;
14
13
  className?: string;
15
14
  }): React.JSX.Element;
16
- /** Checkbox — dùng standalone hoặc `Checkbox.Group` với `options` (Ant Design style). */
17
- export declare const Checkbox: React.ForwardRefExoticComponent<Omit<CheckboxPrimitive.CheckboxProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>> & {
15
+ /**
16
+ * The PUBLIC prop shape, unchanged from the @radix-ui/react-checkbox era: `checked` /
17
+ * `defaultChecked` accept the tri-state `"indeterminate"`, `onCheckedChange` reports it back, and
18
+ * `disabled` / `required` keep their HTML spelling. react-aria-components spells the same four
19
+ * `isSelected` + `isIndeterminate` / `defaultSelected` / `onChange` / `isDisabled` / `isRequired`;
20
+ * that translation happens INSIDE this component and none of those names reach a consumer.
21
+ */
22
+ interface CheckboxRootProps extends Omit<React.ComponentPropsWithoutRef<"button">, "checked" | "defaultChecked" | "onChange"> {
23
+ checked?: boolean | "indeterminate";
24
+ defaultChecked?: boolean | "indeterminate";
25
+ onCheckedChange?: (checked: boolean | "indeterminate") => void;
26
+ required?: boolean;
27
+ }
28
+ /** Checkbox — dùng standalone hoặc `Checkbox.Group` với `options` (theo quy ước phổ biến). */
29
+ export declare const Checkbox: React.ForwardRefExoticComponent<CheckboxRootProps & React.RefAttributes<HTMLLabelElement>> & {
18
30
  Group: typeof CheckboxGroup;
19
31
  };
32
+ export {};
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
4
+ import { Checkbox as AriaCheckbox } from "react-aria-components";
5
5
  import { Check, Minus } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
7
  import { useFieldIdentity } from "../../lib/field-a11y.js";
@@ -28,44 +28,83 @@ function CheckboxVisual({
28
28
  }
29
29
  );
30
30
  }
31
- const CheckboxRoot = React.forwardRef(({ className, ...props }, ref) => {
31
+ const CheckboxRoot = React.forwardRef((props, ref) => {
32
+ const {
33
+ className,
34
+ checked,
35
+ defaultChecked,
36
+ onCheckedChange,
37
+ disabled,
38
+ required,
39
+ value,
40
+ tabIndex,
41
+ "data-field": ownField,
42
+ ...rest
43
+ } = props;
32
44
  const identity = useFieldIdentity({
33
45
  id: props.id,
34
46
  name: props.name,
35
- "data-field": props["data-field"]
47
+ "data-field": ownField
36
48
  });
49
+ const fieldKey = ownField ?? identity["data-field"];
50
+ const inputRef = React.useRef(null);
51
+ React.useLayoutEffect(() => {
52
+ const input = inputRef.current;
53
+ if (!input) return;
54
+ if (fieldKey === void 0) {
55
+ input.removeAttribute("data-field");
56
+ } else {
57
+ input.setAttribute("data-field", fieldKey);
58
+ }
59
+ }, [fieldKey]);
37
60
  const [uncontrolled, setUncontrolled] = React.useState(
38
- props.defaultChecked ?? false
61
+ defaultChecked ?? false
39
62
  );
40
- const state = props.checked ?? uncontrolled;
63
+ const state = checked ?? uncontrolled;
64
+ const invalid = props["aria-invalid"];
41
65
  return /* @__PURE__ */ jsx(
42
- CheckboxPrimitive.Root,
66
+ AriaCheckbox,
43
67
  {
68
+ ...rest,
44
69
  ref,
70
+ inputRef,
45
71
  "data-slot": "checkbox",
72
+ "data-state": state === "indeterminate" ? "indeterminate" : state ? "checked" : "unchecked",
73
+ name: identity.name ?? props.name,
74
+ value,
75
+ isSelected: state === true,
76
+ isIndeterminate: state === "indeterminate",
77
+ isDisabled: disabled,
78
+ isRequired: required,
79
+ isInvalid: invalid !== void 0 && invalid !== false && invalid !== "false",
80
+ excludeFromTabOrder: tabIndex !== void 0 && tabIndex < 0,
46
81
  className: cn(
47
82
  // `disabled:cursor-not-allowed disabled:opacity-50` and the checked fill are DELETED, not
48
83
  // moved: `.ui-checkbox:disabled, .ui-checkbox[data-disabled]` and
49
84
  // `.ui-checkbox[data-state="checked"]` in styles/control.css already declare both, reading
50
85
  // --disabled-opacity and --checkbox-checked-background. Utilities are layered AFTER
51
86
  // components in Tailwind v4, so these literals were silently OUTRANKING those knobs — a
52
- // service overriding --checkbox-checked-background got no fill change at all. Radix sets
53
- // `data-state`/`data-disabled` on the root, so the CSS rules match. Rendering is unchanged:
54
- // both knobs default to exactly the values these utilities hard-coded.
55
- "peer ui-checkbox aria-invalid:border-destructive data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground shrink-0 shadow-xs transition-shadow outline-none",
87
+ // service overriding --checkbox-checked-background got no fill change at all. `data-state`
88
+ // above and react-aria's own `data-disabled` keep those CSS rules matching. Rendering is
89
+ // unchanged: both knobs default to exactly the values these utilities hard-coded.
90
+ //
91
+ // `inline-flex items-center justify-center` is NEW and load-bearing: the root used to be a
92
+ // `<button>` (inline-block, so `.ui-checkbox`'s width/height applied and the glyph centred
93
+ // itself). react-aria's root is a `<label>`, which is `display:inline` — without this the
94
+ // 16px box collapses to nothing. CheckboxVisual already carries the same three for the
95
+ // same reason.
96
+ "peer ui-checkbox data-[invalid]:border-destructive data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow outline-none",
56
97
  className
57
98
  ),
58
- ...props,
59
- ...identity,
60
- onCheckedChange: (next) => {
99
+ onChange: (next) => {
61
100
  setUncontrolled(next);
62
- props.onCheckedChange?.(next);
101
+ onCheckedChange?.(next);
63
102
  },
64
- children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "ui-choice-indicator", children: /* @__PURE__ */ jsx(CheckboxGlyph, { state }) })
103
+ children: /* @__PURE__ */ jsx("span", { "data-slot": "checkbox-indicator", className: "ui-choice-indicator", children: state ? /* @__PURE__ */ jsx(CheckboxGlyph, { state }) : null })
65
104
  }
66
105
  );
67
106
  });
68
- CheckboxRoot.displayName = CheckboxPrimitive.Root.displayName;
107
+ CheckboxRoot.displayName = "Checkbox";
69
108
  const Checkbox = Object.assign(CheckboxRoot, {
70
109
  Group: CheckboxGroup
71
110
  });
@@ -1,5 +1,5 @@
1
1
  import type * as React from "react";
2
- /** Shared option shape — Ant Design `CheckboxOptionType` / `Radio` options. */
2
+ /** Shared option shape — the conventional `CheckboxOptionType` / `Radio` options entry. */
3
3
  export type ChoiceOption = {
4
4
  label: React.ReactNode;
5
5
  value: string;
@@ -3,7 +3,7 @@ import type { DateRangePickerProp } from "../../props/components/data-entry.prop
3
3
  export type { DateRangePickerProp, DateRangePickerProp as DateRangePickerProps, } from "../../props/components/data-entry.prop.js";
4
4
  /**
5
5
  * DateRangePicker — WAI-ARIA date-range combobox rendered as ONE input-styled control
6
- * (Ant Design RangePicker convention): `[ from → to ✕ 📅 ]`. The two inner fields stay
6
+ * (the established RangePicker convention): `[ from → to ✕ 📅 ]`. The two inner fields stay
7
7
  * real, typeable ISO `yyyy-MM-dd` inputs (form-submittable via `${name}_from` /
8
8
  * `${name}_to`, screen-reader friendly, e2e-testable by filling either input); the
9
9
  * range calendar is the visual affordance.
@@ -1,4 +1,3 @@
1
- "use client";
2
1
  import { jsx, jsxs } from "react/jsx-runtime";
3
2
  import { cn } from "../../lib/utils.js";
4
3
  import { Label } from "./label.js";
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { Slot } from "@radix-ui/react-slot";
3
+ import { Slot } from "../../lib/slot.js";
4
4
  import * as React from "react";
5
5
  import { cn } from "../../lib/utils.js";
6
6
  import { FormErrorsProvider } from "./form-errors.js";
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
- import * as LabelPrimitive from "@radix-ui/react-label";
3
2
  import { type VariantProps } from "class-variance-authority";
4
- export declare const Label: React.ForwardRefExoticComponent<Omit<LabelPrimitive.LabelProps & React.RefAttributes<HTMLLabelElement>, "ref"> & VariantProps<(props?: import("class-variance-authority/types").ClassProp | undefined) => string> & React.RefAttributes<HTMLLabelElement>>;
3
+ export declare const Label: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>, "ref"> & VariantProps<(props?: import("class-variance-authority/types").ClassProp | undefined) => string> & {
4
+ asChild?: boolean;
5
+ } & React.RefAttributes<HTMLLabelElement>>;
@@ -1,16 +1,19 @@
1
- "use client";
2
1
  import { jsx } from "react/jsx-runtime";
3
2
  import * as React from "react";
4
- import * as LabelPrimitive from "@radix-ui/react-label";
3
+ import { Label as AriaLabel } from "react-aria-components";
5
4
  import { cva } from "class-variance-authority";
6
5
  import { cn } from "../../lib/utils.js";
6
+ import { Slot } from "../../lib/slot.js";
7
7
  const labelVariants = cva(
8
8
  "font-medium leading-[var(--control-label-line-height)] peer-disabled:cursor-not-allowed peer-disabled:opacity-[var(--control-label-disabled-alpha)]"
9
9
  );
10
- const Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
11
- LabelPrimitive.Root,
12
- {
13
- ref,
10
+ const Label = React.forwardRef(({ className, onMouseDown, asChild = false, ...props }, ref) => {
11
+ const shared = {
12
+ // `...props` đứng TRƯỚC, không phải sau: giữ đúng thứ tự thuộc tính mà
13
+ // @radix-ui/react-label phát ra (`for` → `data-slot` → `class`), nên phép so
14
+ // `outerHTML` trong __tests__/label-checkbox-rac.test.tsx khớp từng ký tự.
15
+ // `className` / `onMouseDown` / `asChild` đã được tách ra nên không bị ghi đè.
16
+ ...props,
14
17
  "data-slot": "label",
15
18
  className: cn(
16
19
  // A label inside a disabled group reads at the SYSTEM disabled alpha (--disabled-opacity),
@@ -19,10 +22,20 @@ const Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
19
22
  labelVariants(),
20
23
  className
21
24
  ),
22
- ...props
23
- }
24
- ));
25
- Label.displayName = LabelPrimitive.Root.displayName;
25
+ // Carried over BY HAND from @radix-ui/react-label, which react-aria-components' Label does not
26
+ // do: without it a double click on the label text selects the surrounding paragraph instead of
27
+ // just toggling the control twice. A press that started ON the control is left alone — cancelling
28
+ // that one would swallow the control's own activation.
29
+ onMouseDown: (event) => {
30
+ const target = event.target;
31
+ if (target.closest("button, input, select")) return;
32
+ onMouseDown?.(event);
33
+ if (!event.defaultPrevented && event.detail > 1) event.preventDefault();
34
+ }
35
+ };
36
+ return asChild ? /* @__PURE__ */ jsx(Slot, { ref, ...shared }) : /* @__PURE__ */ jsx(AriaLabel, { ref, ...shared });
37
+ });
38
+ Label.displayName = "Label";
26
39
  export {
27
40
  Label
28
41
  };
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import type { MonthPickerProp } from "../../props/components/data-entry.prop.js";
3
3
  export type { MonthPickerProp, MonthPickerProp as MonthPickerProps, } from "../../props/components/data-entry.prop.js";
4
4
  /**
5
- * MonthPicker — year/month (`yyyy/MM`) input with an Ant-Design-style month grid
5
+ * MonthPicker — year/month (`yyyy/MM`) input with a 12-cell month grid
6
6
  * popover: a year header with chevrons over a 3×4 grid of the twelve months.
7
7
  * The field stays a real, typeable input (form-submittable via `name` as
8
8
  * `yyyy-MM`); the grid is the visual affordance.
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import type { MonthRangePickerProp } from "../../props/components/data-entry.prop.js";
3
3
  export type { MonthRangePickerProp, MonthRangePickerProp as MonthRangePickerProps, } from "../../props/components/data-entry.prop.js";
4
4
  /**
5
- * MonthRangePicker — year/month (`yyyy/MM`) RANGE rendered as ONE input-styled control (Ant Design
5
+ * MonthRangePicker — year/month (`yyyy/MM`) RANGE rendered as ONE input-styled control (the
6
6
  * RangePicker convention): `[ from → to ✕ 📅 ]`. The two inner fields stay real, typeable
7
7
  * `yyyy/MM` inputs (form-submittable via `${name}_from` / `${name}_to`); the Ant-style month grid
8
8
  * is the visual affordance.
@@ -42,7 +42,7 @@ function TransferPanel({
42
42
  };
43
43
  return /* @__PURE__ */ jsxs("div", { className: "ui-transfer-pane", children: [
44
44
  /* @__PURE__ */ jsxs("div", { className: "ui-transfer-pane-header", children: [
45
- /* @__PURE__ */ jsxs("label", { className: "ui-transfer-pane-check", children: [
45
+ /* @__PURE__ */ jsxs("div", { className: "ui-transfer-pane-check", children: [
46
46
  /* @__PURE__ */ jsx(
47
47
  Checkbox,
48
48
  {
@@ -70,7 +70,7 @@ function TransferPanel({
70
70
  }
71
71
  ) }),
72
72
  /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsx("ul", { className: "ui-transfer-list", "aria-labelledby": titleId, children: filtered.length === 0 ? /* @__PURE__ */ jsx("li", { className: "ui-transfer-empty", children: emptyText }) : filtered.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
73
- "label",
73
+ "div",
74
74
  {
75
75
  className: cn(
76
76
  "ui-transfer-row",
@@ -81,16 +81,26 @@ function TransferPanel({
81
81
  /* @__PURE__ */ jsx(
82
82
  Checkbox,
83
83
  {
84
+ id: `${titleId}-${item.key}-box`,
84
85
  checked: selectedKeys.includes(item.key),
85
86
  disabled: Boolean(disabled) || Boolean(item.disabled),
86
87
  onCheckedChange: (v) => toggleKey(item.key, v === true),
88
+ "aria-labelledby": `${titleId}-${item.key}`,
87
89
  className: "ui-transfer-row-check"
88
90
  }
89
91
  ),
90
- /* @__PURE__ */ jsxs("span", { className: "ui-transfer-row-body", children: [
91
- /* @__PURE__ */ jsx("span", { className: "block truncate font-medium", children: item.title }),
92
- item.description && /* @__PURE__ */ jsx("span", { className: "ui-transfer-row-description text-muted-foreground block truncate", children: item.description })
93
- ] })
92
+ /* @__PURE__ */ jsxs(
93
+ "label",
94
+ {
95
+ className: "ui-transfer-row-body",
96
+ id: `${titleId}-${item.key}`,
97
+ htmlFor: `${titleId}-${item.key}-box`,
98
+ children: [
99
+ /* @__PURE__ */ jsx("span", { className: "block truncate font-medium", children: item.title }),
100
+ item.description && /* @__PURE__ */ jsx("span", { className: "ui-transfer-row-description text-muted-foreground block truncate", children: item.description })
101
+ ]
102
+ }
103
+ )
94
104
  ]
95
105
  }
96
106
  ) }, item.key)) }) })
@@ -1,4 +1,4 @@
1
- /** Ant Design `showCheckedStrategy` equivalents for TreeSelect. */
1
+ /** `showCheckedStrategy` equivalents for TreeSelect — the conventional enterprise names. */
2
2
  export declare const SHOW_CHILD: "SHOW_CHILD";
3
3
  export declare const SHOW_PARENT: "SHOW_PARENT";
4
4
  export declare const SHOW_ALL: "SHOW_ALL";
@@ -259,18 +259,35 @@ function TreeSelectRoot({
259
259
  children: expanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "ui-tree-select-toggle-icon", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "ui-tree-select-toggle-icon", "aria-hidden": "true" })
260
260
  }
261
261
  ),
262
- checkable ? /* @__PURE__ */ jsxs("label", { className: "ui-tree-select-label", children: [
263
- /* @__PURE__ */ jsx(
264
- Checkbox,
265
- {
266
- checked: isSelected,
267
- tabIndex: -1,
268
- disabled: Boolean(node.disabled) || Boolean(node.disableCheckbox),
269
- onCheckedChange: () => toggleSelect(node)
270
- }
271
- ),
272
- /* @__PURE__ */ jsx("span", { className: "truncate", children: node.label })
273
- ] }) : /* @__PURE__ */ jsx(
262
+ checkable ? (
263
+ // The row must NOT WRAP the Checkbox in a `<label>`: the Checkbox is itself a
264
+ // `<label>` around a real `<input>`, and a label nested in a label is
265
+ // invalid HTML — the browser resolves neither, so the box loses its
266
+ // accessible name. The node text sits BESIDE the box as a `for=`-associated
267
+ // label instead, which both names it and keeps the text clickable.
268
+ /* @__PURE__ */ jsxs("div", { className: "ui-tree-select-label", children: [
269
+ /* @__PURE__ */ jsx(
270
+ Checkbox,
271
+ {
272
+ id: `${treeId}-${node.value}-box`,
273
+ checked: isSelected,
274
+ tabIndex: -1,
275
+ disabled: Boolean(node.disabled) || Boolean(node.disableCheckbox),
276
+ onCheckedChange: () => toggleSelect(node),
277
+ "aria-labelledby": `${treeId}-${node.value}-label`
278
+ }
279
+ ),
280
+ /* @__PURE__ */ jsx(
281
+ "label",
282
+ {
283
+ className: "truncate",
284
+ id: `${treeId}-${node.value}-label`,
285
+ htmlFor: `${treeId}-${node.value}-box`,
286
+ children: node.label
287
+ }
288
+ )
289
+ ] })
290
+ ) : /* @__PURE__ */ jsx(
274
291
  "button",
275
292
  {
276
293
  type: "button",
@@ -1,5 +1,5 @@
1
1
  import type * as React from "react";
2
- /** Normalized tree node — Ant Design `treeData` / Cascader `options`. */
2
+ /** Normalized tree node — the conventional `treeData` / Cascader `options` shape. */
3
3
  export type TreeOption = {
4
4
  value: string;
5
5
  label: React.ReactNode;