@godxjp/ui 18.15.4 → 19.1.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 (154) hide show
  1. package/dist/app/app-provider.d.ts +1 -1
  2. package/dist/app/app-provider.js +3 -0
  3. package/dist/app/date-format-labels.d.ts +1 -1
  4. package/dist/app/time-format-labels.d.ts +1 -1
  5. package/dist/components/data-display/badge.d.ts +1 -1
  6. package/dist/components/data-display/card.d.ts +4 -13
  7. package/dist/components/data-display/card.js +2 -15
  8. package/dist/components/data-display/data-table.d.ts +3 -8
  9. package/dist/components/data-display/data-table.js +106 -68
  10. package/dist/components/data-display/descriptions.js +34 -5
  11. package/dist/components/data-display/empty-state.js +1 -1
  12. package/dist/components/data-display/permission-matrix.js +7 -7
  13. package/dist/components/data-display/popover.js +2 -9
  14. package/dist/components/data-display/qr-code.d.ts +1 -1
  15. package/dist/components/data-display/scroll-area.d.ts +3 -1
  16. package/dist/components/data-display/scroll-area.js +150 -16
  17. package/dist/components/data-display/table.js +12 -3
  18. package/dist/components/data-entry/branch-scope-picker.js +4 -4
  19. package/dist/components/data-entry/calendar.js +15 -24
  20. package/dist/components/data-entry/cascader.js +70 -72
  21. package/dist/components/data-entry/checkbox-group.js +12 -5
  22. package/dist/components/data-entry/checkbox.js +30 -13
  23. package/dist/components/data-entry/command.d.ts +6 -6
  24. package/dist/components/data-entry/date-picker.js +34 -17
  25. package/dist/components/data-entry/date-range-picker.js +19 -13
  26. package/dist/components/data-entry/form-field.d.ts +1 -1
  27. package/dist/components/data-entry/form-field.js +22 -5
  28. package/dist/components/data-entry/index.d.ts +2 -1
  29. package/dist/components/data-entry/input.js +18 -22
  30. package/dist/components/data-entry/label.js +4 -2
  31. package/dist/components/data-entry/month-picker.js +20 -17
  32. package/dist/components/data-entry/month-range-picker.js +26 -19
  33. package/dist/components/data-entry/radio.js +25 -6
  34. package/dist/components/data-entry/search-select.d.ts +1 -1
  35. package/dist/components/data-entry/search-select.js +37 -31
  36. package/dist/components/data-entry/select.d.ts +8 -0
  37. package/dist/components/data-entry/select.js +68 -26
  38. package/dist/components/data-entry/switch.js +16 -2
  39. package/dist/components/data-entry/textarea.d.ts +56 -0
  40. package/dist/components/data-entry/textarea.js +110 -40
  41. package/dist/components/data-entry/time-picker.js +52 -52
  42. package/dist/components/data-entry/toggle-group.d.ts +1 -0
  43. package/dist/components/data-entry/toggle.d.ts +1 -1
  44. package/dist/components/data-entry/transfer.js +14 -15
  45. package/dist/components/data-entry/tree-select.js +19 -23
  46. package/dist/components/data-entry/upload-crop-dialog.js +4 -4
  47. package/dist/components/data-entry/upload.d.ts +1 -1
  48. package/dist/components/data-entry/upload.js +46 -46
  49. package/dist/components/feedback/alert.js +7 -14
  50. package/dist/components/feedback/dialog.d.ts +23 -32
  51. package/dist/components/feedback/dialog.js +16 -13
  52. package/dist/components/feedback/sheet.d.ts +1 -1
  53. package/dist/components/feedback/sheet.js +14 -14
  54. package/dist/components/feedback/skeleton.js +13 -10
  55. package/dist/components/feedback/sonner.d.ts +1 -1
  56. package/dist/components/feedback/sonner.js +17 -8
  57. package/dist/components/feedback/tooltip.js +1 -1
  58. package/dist/components/general/activity.d.ts +44 -0
  59. package/dist/components/general/activity.js +65 -0
  60. package/dist/components/general/button.d.ts +2 -2
  61. package/dist/components/general/button.js +11 -24
  62. package/dist/components/general/index.d.ts +2 -0
  63. package/dist/components/general/index.js +2 -0
  64. package/dist/components/layout/app-shell.js +5 -3
  65. package/dist/components/layout/auth-divider.d.ts +9 -2
  66. package/dist/components/layout/auth-divider.js +7 -11
  67. package/dist/components/layout/index.d.ts +1 -0
  68. package/dist/components/layout/page-container.d.ts +1 -1
  69. package/dist/components/layout/page-container.js +32 -20
  70. package/dist/components/layout/resizable.d.ts +12 -12
  71. package/dist/components/layout/separator.d.ts +1 -0
  72. package/dist/components/layout/service-role-panel.js +47 -42
  73. package/dist/components/layout/sidebar.js +13 -1
  74. package/dist/components/layout/split-pane.d.ts +12 -1
  75. package/dist/components/layout/split-pane.js +13 -4
  76. package/dist/components/navigation/app-setting-picker.js +5 -14
  77. package/dist/components/navigation/dropdown-menu.js +10 -16
  78. package/dist/components/navigation/menubar.d.ts +3 -6
  79. package/dist/components/navigation/pagination-utils.d.ts +15 -2
  80. package/dist/components/navigation/pagination-utils.js +27 -21
  81. package/dist/components/navigation/pagination.d.ts +4 -4
  82. package/dist/components/navigation/pagination.js +2 -2
  83. package/dist/components/navigation/steps.js +31 -67
  84. package/dist/components/navigation/tabs.js +17 -5
  85. package/dist/components/query/infinite-query-state.js +2 -2
  86. package/dist/components/ui/accordion.d.ts +1 -1
  87. package/dist/components/ui/avatar.d.ts +1 -1
  88. package/dist/components/ui/avatar.js +29 -10
  89. package/dist/components/ui/input-otp.d.ts +8 -6
  90. package/dist/components/ui/rating.js +1 -1
  91. package/dist/components/ui/separator.d.ts +27 -2
  92. package/dist/components/ui/separator.js +42 -11
  93. package/dist/components/ui/toggle-group.d.ts +15 -5
  94. package/dist/components/ui/toggle-group.js +43 -18
  95. package/dist/components/ui/toggle.d.ts +66 -5
  96. package/dist/components/ui/toggle.js +64 -9
  97. package/dist/i18n/messages/en.json +8 -0
  98. package/dist/i18n/messages/ja.json +8 -0
  99. package/dist/i18n/messages/vi.json +8 -0
  100. package/dist/lib/control-styles.d.ts +14 -0
  101. package/dist/lib/control-styles.js +2 -0
  102. package/dist/lib/field-a11y.d.ts +52 -0
  103. package/dist/lib/field-a11y.js +17 -2
  104. package/dist/props/components/app.prop.d.ts +15 -0
  105. package/dist/props/components/data-display.prop.d.ts +85 -3
  106. package/dist/props/components/data-entry.prop.d.ts +35 -2
  107. package/dist/props/components/general.prop.d.ts +52 -1
  108. package/dist/props/components/layout.prop.d.ts +137 -2
  109. package/dist/props/registry.d.ts +116 -5
  110. package/dist/props/registry.js +165 -4
  111. package/dist/props/vocabulary/index.d.ts +2 -2
  112. package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
  113. package/dist/props/vocabulary/layout.prop.d.ts +8 -0
  114. package/dist/styles/alert-layout.css +69 -3
  115. package/dist/styles/badge-layout.css +2 -2
  116. package/dist/styles/base.css +6 -0
  117. package/dist/styles/card-layout.css +25 -8
  118. package/dist/styles/control.css +1150 -29
  119. package/dist/styles/data-display-layout.css +185 -11
  120. package/dist/styles/data-entry-layout.css +352 -1
  121. package/dist/styles/density.css +2 -2
  122. package/dist/styles/dialog-layout.css +203 -4
  123. package/dist/styles/focus-ring.css +155 -0
  124. package/dist/styles/form-layout.css +50 -5
  125. package/dist/styles/index.css +1 -0
  126. package/dist/styles/layout.css +412 -23
  127. package/dist/styles/motion.css +178 -0
  128. package/dist/styles/navigation-layout.css +398 -22
  129. package/dist/styles/shell-layout.css +137 -39
  130. package/dist/styles/table-layout.css +153 -22
  131. package/dist/styles/toggle.css +59 -0
  132. package/dist/tokens/base.css +5 -0
  133. package/dist/tokens/components/activity.css +64 -0
  134. package/dist/tokens/components/badge.css +7 -0
  135. package/dist/tokens/components/card.css +14 -10
  136. package/dist/tokens/components/chart.css +1 -1
  137. package/dist/tokens/components/control.css +358 -8
  138. package/dist/tokens/components/data-display.css +75 -0
  139. package/dist/tokens/components/data-entry.css +25 -0
  140. package/dist/tokens/components/descriptions.css +22 -0
  141. package/dist/tokens/components/feedback.css +143 -4
  142. package/dist/tokens/components/form.css +16 -0
  143. package/dist/tokens/components/legal-document.css +5 -1
  144. package/dist/tokens/components/navigation.css +109 -0
  145. package/dist/tokens/components/scroll-area.css +21 -0
  146. package/dist/tokens/components/separator.css +52 -0
  147. package/dist/tokens/components/shell.css +132 -20
  148. package/dist/tokens/components/table.css +52 -3
  149. package/dist/tokens/components/toggle.css +82 -0
  150. package/dist/tokens/components/upload.css +107 -0
  151. package/dist/tokens/foundation.css +171 -10
  152. package/dist/tokens/semantic/layout.css +120 -1
  153. package/package.json +85 -68
  154. package/scripts/ui-audit.mjs +51 -2
@@ -2,7 +2,7 @@
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "../../lib/utils.js";
5
- import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
5
+ import { pickGroupFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
6
6
  import { Checkbox } from "./checkbox.js";
7
7
  import { Field } from "./field.js";
8
8
  import { choiceGroupClassName } from "./choice-option.js";
@@ -33,6 +33,10 @@ function CheckboxGroup({
33
33
  const reactId = React.useId();
34
34
  const [value, setValue] = useControllableArray(controlledValue, defaultValue);
35
35
  const groupA11y = pickGroupFieldA11y(ariaProps);
36
+ const identity = useFieldIdentity({ id, name, "data-field": groupA11y["data-field"] });
37
+ const resolvedName = name ?? identity.name;
38
+ const resolvedField = groupA11y["data-field"] ?? identity["data-field"];
39
+ const optionDomId = (optionValue, index) => id ? `${id}-${optionValue}` : `${reactId}-${optionValue}-${index}`;
36
40
  const toggle = (optionValue) => {
37
41
  const next = value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue];
38
42
  setValue(next);
@@ -45,17 +49,19 @@ function CheckboxGroup({
45
49
  role: "group",
46
50
  id,
47
51
  ...groupA11y,
52
+ "data-field": resolvedField,
48
53
  "aria-disabled": disabled ? true : void 0,
49
54
  "data-orientation": orientation,
50
55
  className: choiceGroupClassName(orientation, className),
51
56
  children: options.map((opt, index) => {
52
- const id2 = `${reactId}-${opt.value}-${index}`;
57
+ const optionId = optionDomId(opt.value, index);
53
58
  const checked = value.includes(opt.value);
54
- return /* @__PURE__ */ jsx(Field, { id: id2, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(
59
+ return /* @__PURE__ */ jsx(Field, { id: optionId, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(
55
60
  Checkbox,
56
61
  {
57
- id: id2,
58
- name,
62
+ id: optionId,
63
+ name: resolvedName,
64
+ "data-field": resolvedField,
59
65
  value: opt.value,
60
66
  checked,
61
67
  disabled: Boolean(disabled) || Boolean(opt.disabled),
@@ -74,6 +80,7 @@ function CheckboxGroup({
74
80
  role: "group",
75
81
  id,
76
82
  ...groupA11y,
83
+ "data-field": resolvedField,
77
84
  "aria-disabled": disabled ? true : void 0,
78
85
  "data-orientation": orientation,
79
86
  className: cn(choiceGroupClassName(orientation), className),
@@ -4,20 +4,37 @@ import * as React from "react";
4
4
  import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
5
5
  import { Check } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
+ import { useFieldIdentity } from "../../lib/field-a11y.js";
7
8
  import { CheckboxGroup } from "./checkbox-group.js";
8
- const CheckboxRoot = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
9
- CheckboxPrimitive.Root,
10
- {
11
- ref,
12
- "data-slot": "checkbox",
13
- className: cn(
14
- "peer ui-checkbox focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground shrink-0 shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
15
- className
16
- ),
17
- ...props,
18
- children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "ui-choice-indicator", children: /* @__PURE__ */ jsx(Check, { className: "ui-checkbox-icon", "aria-hidden": "true" }) })
19
- }
20
- ));
9
+ const CheckboxRoot = React.forwardRef(({ className, ...props }, ref) => {
10
+ const identity = useFieldIdentity({
11
+ id: props.id,
12
+ name: props.name,
13
+ "data-field": props["data-field"]
14
+ });
15
+ return /* @__PURE__ */ jsx(
16
+ CheckboxPrimitive.Root,
17
+ {
18
+ ref,
19
+ "data-slot": "checkbox",
20
+ className: cn(
21
+ // `disabled:cursor-not-allowed disabled:opacity-50` and the checked fill are DELETED, not
22
+ // moved (#319): `.ui-checkbox:disabled, .ui-checkbox[data-disabled]` and
23
+ // `.ui-checkbox[data-state="checked"]` in styles/control.css already declare both, reading
24
+ // --disabled-opacity and --checkbox-checked-background. Utilities are layered AFTER
25
+ // components in Tailwind v4, so these literals were silently OUTRANKING those knobs — a
26
+ // service overriding --checkbox-checked-background got no fill change at all. Radix sets
27
+ // `data-state`/`data-disabled` on the root, so the CSS rules match. Rendering is unchanged:
28
+ // both knobs default to exactly the values these utilities hard-coded.
29
+ "peer ui-checkbox aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground shrink-0 shadow-xs transition-shadow outline-none",
30
+ className
31
+ ),
32
+ ...props,
33
+ ...identity,
34
+ children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "ui-choice-indicator", children: /* @__PURE__ */ jsx(Check, { className: "ui-checkbox-icon", "aria-hidden": "true" }) })
35
+ }
36
+ );
37
+ });
21
38
  CheckboxRoot.displayName = CheckboxPrimitive.Root.displayName;
22
39
  const Checkbox = Object.assign(CheckboxRoot, {
23
40
  Group: CheckboxGroup
@@ -5,7 +5,7 @@ export declare const Command: React.ForwardRefExoticComponent<Omit<{
5
5
  ref?: React.Ref<HTMLDivElement>;
6
6
  } & {
7
7
  asChild?: boolean;
8
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
8
+ }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
9
9
  label?: string;
10
10
  shouldFilter?: boolean;
11
11
  filter?: (value: string, search: string, keywords?: string[]) => number;
@@ -20,7 +20,7 @@ export declare const CommandInput: React.ForwardRefExoticComponent<Omit<Omit<Pic
20
20
  ref?: React.Ref<HTMLInputElement>;
21
21
  } & {
22
22
  asChild?: boolean;
23
- }, "key" | "asChild" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "onChange" | "value" | "type"> & {
23
+ }, "asChild" | "key" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "onChange" | "type" | "value"> & {
24
24
  value?: string;
25
25
  onValueChange?: (search: string) => void;
26
26
  } & React.RefAttributes<HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
@@ -30,7 +30,7 @@ export declare const CommandList: React.ForwardRefExoticComponent<Omit<{
30
30
  ref?: React.Ref<HTMLDivElement>;
31
31
  } & {
32
32
  asChild?: boolean;
33
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
33
+ }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
34
34
  label?: string;
35
35
  } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
36
36
  export declare const CommandEmpty: React.ForwardRefExoticComponent<Omit<{
@@ -39,14 +39,14 @@ export declare const CommandEmpty: React.ForwardRefExoticComponent<Omit<{
39
39
  ref?: React.Ref<HTMLDivElement>;
40
40
  } & {
41
41
  asChild?: boolean;
42
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
42
+ }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
43
43
  export declare const CommandGroup: React.ForwardRefExoticComponent<Omit<{
44
44
  children?: React.ReactNode;
45
45
  } & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
46
46
  ref?: React.Ref<HTMLDivElement>;
47
47
  } & {
48
48
  asChild?: boolean;
49
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild">, "value" | "heading"> & {
49
+ }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "heading" | "value"> & {
50
50
  heading?: React.ReactNode;
51
51
  value?: string;
52
52
  forceMount?: boolean;
@@ -57,7 +57,7 @@ export declare const CommandItem: React.ForwardRefExoticComponent<Omit<{
57
57
  ref?: React.Ref<HTMLDivElement>;
58
58
  } & {
59
59
  asChild?: boolean;
60
- }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild">, "onSelect" | "value" | "disabled"> & {
60
+ }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "disabled" | "onSelect" | "value"> & {
61
61
  disabled?: boolean;
62
62
  onSelect?: (value: string) => void;
63
63
  value?: string;
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { CalendarIcon } from "lucide-react";
4
+ import { CalendarIcon, X } from "lucide-react";
5
5
  import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
6
6
  import { parseDateInput, toIsoDate } from "../../lib/datetime/parse.js";
7
7
  import { useControlledLatch } from "../../lib/hooks.js";
8
- import { pickFieldA11y } from "../../lib/field-a11y.js";
8
+ import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
9
9
  import { cn } from "../../lib/utils.js";
10
10
  import { Input } from "./input.js";
11
11
  import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
@@ -30,6 +30,9 @@ function DatePicker({
30
30
  const { dayPickerLocale } = usePickerLocales(localeProp);
31
31
  const [open, setOpen] = React.useState(false);
32
32
  const fieldA11y = pickFieldA11y(ariaProps);
33
+ const identity = useFieldIdentity({ id, name, "data-field": fieldA11y["data-field"] });
34
+ const resolvedName = name ?? identity.name;
35
+ const resolvedField = fieldA11y["data-field"] ?? identity["data-field"];
33
36
  const reactId = React.useId();
34
37
  const dialogId = `${id ?? reactId}-dialog`;
35
38
  const isControlled = useControlledLatch(valueProp !== void 0);
@@ -48,6 +51,7 @@ function DatePicker({
48
51
  emit(void 0);
49
52
  setText("");
50
53
  };
54
+ const showClear = allowClear && text !== "" && !disabled;
51
55
  const commit = (raw) => {
52
56
  const trimmed = raw.trim();
53
57
  if (trimmed === "") {
@@ -65,7 +69,8 @@ function DatePicker({
65
69
  Input,
66
70
  {
67
71
  id,
68
- name,
72
+ name: resolvedName,
73
+ "data-field": resolvedField,
69
74
  value: text,
70
75
  disabled,
71
76
  placeholder: resolvedPlaceholder,
@@ -76,19 +81,31 @@ function DatePicker({
76
81
  "aria-haspopup": "dialog",
77
82
  "aria-controls": open ? dialogId : void 0,
78
83
  ...fieldA11y,
79
- allowClear,
80
- onClear: clear,
81
- trailingIcon: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
82
- "button",
83
- {
84
- type: "button",
85
- disabled,
86
- tabIndex: -1,
87
- "aria-label": t("dataEntry.datePicker.openCalendar") ?? "Open calendar",
88
- className: "text-muted-foreground hover:text-foreground inline-flex size-5 items-center justify-center rounded-sm opacity-70 transition-opacity hover:opacity-100",
89
- children: /* @__PURE__ */ jsx(CalendarIcon, { className: "size-4", "aria-hidden": "true" })
90
- }
91
- ) }),
84
+ className: showClear ? "ui-control-inline-affix-pair-affixed" : void 0,
85
+ trailingIcon: /* @__PURE__ */ jsxs("span", { className: "ui-time-picker-affix", children: [
86
+ showClear ? /* @__PURE__ */ jsx(
87
+ "button",
88
+ {
89
+ type: "button",
90
+ tabIndex: -1,
91
+ "aria-label": t("common.clear") ?? "Clear",
92
+ onClick: clear,
93
+ className: "ui-control-inline-affix-action",
94
+ children: /* @__PURE__ */ jsx(X, { className: "ui-control-inline-affix-icon", "aria-hidden": "true" })
95
+ }
96
+ ) : null,
97
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
98
+ "button",
99
+ {
100
+ type: "button",
101
+ disabled,
102
+ tabIndex: -1,
103
+ "aria-label": t("dataEntry.datePicker.openCalendar") ?? "Open calendar",
104
+ className: "ui-control-inline-affix-action",
105
+ children: /* @__PURE__ */ jsx(CalendarIcon, { className: "ui-control-inline-affix-icon", "aria-hidden": "true" })
106
+ }
107
+ ) })
108
+ ] }),
92
109
  onClick: () => {
93
110
  if (!disabled) setOpen(true);
94
111
  },
@@ -116,7 +133,7 @@ function DatePicker({
116
133
  id: dialogId,
117
134
  role: "dialog",
118
135
  "aria-label": t("dataEntry.datePicker.openCalendar") ?? "Calendar",
119
- className: "w-auto p-0",
136
+ className: "ui-control-panel-flush",
120
137
  align: "start",
121
138
  onOpenAutoFocus: (event) => event.preventDefault(),
122
139
  children: /* @__PURE__ */ jsx(
@@ -5,7 +5,7 @@ import { ArrowRight, CalendarIcon, X } from "lucide-react";
5
5
  import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
6
6
  import { parseDateInput, toIsoDate } from "../../lib/datetime/index.js";
7
7
  import { useControlledLatch } from "../../lib/hooks.js";
8
- import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
8
+ import { pickGroupFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
9
9
  import { cn } from "../../lib/utils.js";
10
10
  import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
11
11
  import { Calendar } from "./calendar.js";
@@ -33,6 +33,9 @@ function DateRangePicker({
33
33
  const groupId = id ?? autoId;
34
34
  const fromId = `${groupId}-from`;
35
35
  const toId = `${groupId}-to`;
36
+ const identity = useFieldIdentity({ id: groupId, name, "data-field": groupA11y["data-field"] });
37
+ const rangeField = groupA11y["data-field"] ?? identity["data-field"];
38
+ const rangeName = name ?? identity.name;
36
39
  const isControlled = useControlledLatch(valueProp !== void 0);
37
40
  const [internalValue, setInternalValue] = React.useState(defaultValue);
38
41
  const value = isControlled ? valueProp : internalValue;
@@ -70,21 +73,21 @@ function DateRangePicker({
70
73
  }
71
74
  }
72
75
  };
73
- const innerInputClass = "min-w-0 flex-1 bg-transparent tabular-nums outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed";
76
+ const innerInputClass = "ui-month-picker-input";
74
77
  return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
75
78
  "div",
76
79
  {
77
80
  role: "group",
78
81
  id: groupId,
79
82
  ...groupA11y,
83
+ "data-field": rangeField,
80
84
  "aria-disabled": disabled ? true : void 0,
81
85
  className: cn(
82
- // One input-styled shell for the whole range — mirrors Input's control
83
- // tokens (border/radius/ring) so it reads as a single form field.
84
- "ui-control border-input bg-background flex w-full min-w-0 items-center gap-2 rounded-[var(--control-radius)] transition-[color,box-shadow] outline-none",
86
+ // One input-styled shell for the whole range — the shared composite-field box, so
87
+ // this and the month range picker cannot drift into two slightly different fields.
88
+ "ui-control ui-control-composite-field",
85
89
  "focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]",
86
90
  open && "border-ring ring-ring/50 ring-[3px]",
87
- disabled && "pointer-events-none cursor-not-allowed opacity-50",
88
91
  className
89
92
  ),
90
93
  onClick: () => {
@@ -95,7 +98,8 @@ function DateRangePicker({
95
98
  "input",
96
99
  {
97
100
  id: fromId,
98
- name: name ? `${name}_from` : void 0,
101
+ "data-field": rangeField ? `${rangeField}_from` : void 0,
102
+ name: rangeName ? `${rangeName}_from` : void 0,
99
103
  value: fromText,
100
104
  disabled,
101
105
  placeholder: resolvedPlaceholder,
@@ -114,12 +118,13 @@ function DateRangePicker({
114
118
  }
115
119
  }
116
120
  ),
117
- /* @__PURE__ */ jsx(ArrowRight, { className: "text-muted-foreground size-3.5 shrink-0", "aria-hidden": "true" }),
121
+ /* @__PURE__ */ jsx(ArrowRight, { className: "ui-month-picker-separator-icon", "aria-hidden": "true" }),
118
122
  /* @__PURE__ */ jsx(
119
123
  "input",
120
124
  {
121
125
  id: toId,
122
- name: name ? `${name}_to` : void 0,
126
+ "data-field": rangeField ? `${rangeField}_to` : void 0,
127
+ name: rangeName ? `${rangeName}_to` : void 0,
123
128
  value: toText,
124
129
  disabled,
125
130
  placeholder: resolvedPlaceholder,
@@ -149,9 +154,10 @@ function DateRangePicker({
149
154
  event.stopPropagation();
150
155
  clear();
151
156
  },
152
- children: /* @__PURE__ */ jsx(X, { className: "size-4 shrink-0", "aria-hidden": "true" })
157
+ children: /* @__PURE__ */ jsx(X, { className: "ui-month-picker-icon", "aria-hidden": "true" })
153
158
  }
154
- ) : /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
159
+ ) : null,
160
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
155
161
  "button",
156
162
  {
157
163
  type: "button",
@@ -159,13 +165,13 @@ function DateRangePicker({
159
165
  tabIndex: -1,
160
166
  "aria-label": t("dataEntry.dateRangePicker.openCalendar") ?? "Open calendar",
161
167
  className: "text-muted-foreground hover:text-foreground shrink-0",
162
- children: /* @__PURE__ */ jsx(CalendarIcon, { className: "size-4 shrink-0", "aria-hidden": "true" })
168
+ children: /* @__PURE__ */ jsx(CalendarIcon, { className: "ui-month-picker-icon", "aria-hidden": "true" })
163
169
  }
164
170
  ) }),
165
171
  /* @__PURE__ */ jsx(
166
172
  PopoverContent,
167
173
  {
168
- className: "w-auto p-0",
174
+ className: "ui-control-panel-flush",
169
175
  align: "start",
170
176
  onOpenAutoFocus: (event) => event.preventDefault(),
171
177
  children: /* @__PURE__ */ jsx(
@@ -1,4 +1,4 @@
1
1
  import * as React from "react";
2
2
  import type { FormFieldProp } from "../../props/components/data-entry.prop.js";
3
3
  export type { FormFieldProp, FormFieldProp as FormFieldProps, } from "../../props/components/data-entry.prop.js";
4
- export declare function FormField({ id, name, label, required, helper, error: errorProp, labelAddon, layout: layoutProp, labelWidth: labelWidthProp, controlWidth: controlWidthProp, colSpan, className, children, staticText, }: FormFieldProp): React.JSX.Element;
4
+ export declare function FormField({ id, name, field, label, required, helper, error: errorProp, labelAddon, layout: layoutProp, labelWidth: labelWidthProp, controlWidth: controlWidthProp, colSpan, className, children, staticText, }: FormFieldProp): React.JSX.Element;
@@ -3,7 +3,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Label } from "../data-entry/label.js";
5
5
  import { cn } from "../../lib/utils.js";
6
- import { FieldNameContext, mergeAriaIds } from "../../lib/field-a11y.js";
6
+ import { FieldIdentityContext, FieldNameContext, mergeAriaIds } from "../../lib/field-a11y.js";
7
+ import { useOptionalAppContext } from "../../app/app-provider.js";
7
8
  import { useFormLayout } from "./form.js";
8
9
  import { firstBagMessage, useClaimErrorKey, useFormErrorsRegistry } from "./form-errors.js";
9
10
  const toCssLength = (v) => typeof v === "number" ? `${v}px` : v;
@@ -11,6 +12,7 @@ const FOCUSABLE_SELECTOR = 'input:not([type="hidden"]), select, textarea, button
11
12
  function FormField({
12
13
  id,
13
14
  name,
15
+ field,
14
16
  label,
15
17
  required,
16
18
  helper,
@@ -38,6 +40,8 @@ function FormField({
38
40
  const labelId = `${resolvedId}-label`;
39
41
  const helperId = helper ? `${resolvedId}-helper` : void 0;
40
42
  const errorId = error ? `${resolvedId}-error` : void 0;
43
+ const fieldKey = field ?? name ?? id;
44
+ const emitFieldNames = useOptionalAppContext()?.emitFieldNames ?? false;
41
45
  const isStatic = staticText !== void 0;
42
46
  if (!isStatic && typeof process !== "undefined" && process.env?.NODE_ENV !== "production" && !React.isValidElement(children)) {
43
47
  console.warn(
@@ -48,17 +52,30 @@ function FormField({
48
52
  () => ({ labelId, label: typeof label === "string" ? label : void 0 }),
49
53
  [labelId, label]
50
54
  );
55
+ const fieldIdentityContext = React.useMemo(
56
+ () => fieldKey === void 0 ? null : { emitName: emitFieldNames },
57
+ [fieldKey, emitFieldNames]
58
+ );
51
59
  const childProps = React.isValidElement(children) ? children.props : void 0;
52
60
  const mergeIds = mergeAriaIds;
53
61
  const childWithA11y = isStatic ? (
54
62
  // Byte-for-byte the same value typography as `Descriptions.Item`'s `dd` (gh#294), so a
55
- // read-only FormField row and a Descriptions value are indistinguishable when mixed.
56
- /* @__PURE__ */ jsx("span", { className: "text-sm break-all", children: staticText })
63
+ // read-only FormField row and a Descriptions value are indistinguishable when mixed. That
64
+ // used to be two copies of the literal `text-sm` kept in sync by this comment; both call sites
65
+ // now read the SAME token pair (#319), so a service retunes both from one place and they
66
+ // cannot drift. --descriptions-value-line-height is the mandatory companion: `text-sm` also
67
+ // set Tailwind's `--text-sm--line-height`, which the theme never remaps (the gh#260 bug).
68
+ /* @__PURE__ */ jsx("span", { className: "text-[length:var(--descriptions-value-font-size)] leading-[var(--descriptions-value-line-height)] break-all", children: staticText })
57
69
  ) : React.isValidElement(children) ? React.cloneElement(children, {
58
70
  // The label is associated via aria-labelledby (not <label for>): composite
59
71
  // controls (Radio.Group, checkbox lists, range pairs) have no labelable root,
60
72
  // and a dangling `for` triggers Chrome's "Incorrect use of <label>" issue.
61
73
  id: childProps?.id ?? resolvedId,
74
+ // gh#337 — the machine key. Read the child's own value first in BOTH cases: cloneElement
75
+ // overwrites every key present in the config bag, `undefined` included, so a bare
76
+ // `"data-field": fieldKey` would erase a value the control set for itself.
77
+ "data-field": childProps?.["data-field"] ?? fieldKey,
78
+ ...emitFieldNames ? { name: childProps?.name ?? fieldKey } : void 0,
62
79
  "aria-labelledby": childProps?.["aria-labelledby"] ?? labelId,
63
80
  // Redundant `aria-label` fallback (belt-and-suspenders): the accessible name is the
64
81
  // SAME string as the visible label, just reachable even if an aria-labelledby lookup
@@ -83,7 +100,7 @@ function FormField({
83
100
  style["--form-label-width"] = toCssLength(labelWidth);
84
101
  if (controlWidth != null)
85
102
  style["--form-control-width"] = toCssLength(controlWidth);
86
- if (colSpan != null) style.gridColumn = `span ${colSpan}`;
103
+ if (colSpan != null) style["--form-field-col-span"] = String(colSpan);
87
104
  return /* @__PURE__ */ jsxs(
88
105
  "div",
89
106
  {
@@ -121,7 +138,7 @@ function FormField({
121
138
  labelAddon
122
139
  ] }),
123
140
  /* @__PURE__ */ jsxs("div", { "data-slot": "form-field-control", className: "ui-form-field-control", children: [
124
- isStatic ? childWithA11y : /* @__PURE__ */ jsx(FieldNameContext.Provider, { value: fieldNameContext, children: childWithA11y }),
141
+ isStatic ? childWithA11y : /* @__PURE__ */ jsx(FieldNameContext.Provider, { value: fieldNameContext, children: /* @__PURE__ */ jsx(FieldIdentityContext.Provider, { value: fieldIdentityContext, children: childWithA11y }) }),
125
142
  helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "text-muted-foreground text-xs", children: helper }) : null,
126
143
  error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-destructive text-xs", children: error }) : null
127
144
  ] })
@@ -21,8 +21,9 @@ export { SearchInput } from "./search-input.js";
21
21
  export { Switch } from "./switch.js";
22
22
  export type { SwitchProps } from "./switch.js";
23
23
  export { Toggle } from "./toggle.js";
24
- export type { ToggleProps } from "./toggle.js";
24
+ export type { ToggleProp, ToggleProps } from "./toggle.js";
25
25
  export { ToggleGroup, ToggleGroupItem } from "./toggle-group.js";
26
+ export type { ToggleGroupItemProp, ToggleGroupItemProps } from "./toggle-group.js";
26
27
  export { Slider } from "./slider.js";
27
28
  export type { SliderProps } from "./slider.js";
28
29
  export { Calendar } from "./calendar.js";
@@ -3,15 +3,12 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
- import { useFieldNameFallback } from "../../lib/field-a11y.js";
6
+ import { useFieldIdentity, useFieldNameFallback } from "../../lib/field-a11y.js";
7
7
  import { cn } from "../../lib/utils.js";
8
8
  const inputBaseClass = [
9
- "ui-control border-input bg-background w-full min-w-0 rounded-[var(--control-radius)] transition-[color,box-shadow] outline-none",
9
+ "ui-control ui-input border-input bg-background w-full rounded-[var(--control-radius)] transition-[color,box-shadow] outline-none",
10
10
  "selection:bg-primary selection:text-primary-foreground",
11
- "file:text-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium",
12
11
  "placeholder:text-muted-foreground",
13
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
14
- "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
15
12
  "aria-invalid:border-destructive aria-invalid:ring-destructive/20"
16
13
  ];
17
14
  const Input = React.forwardRef(
@@ -32,6 +29,11 @@ const Input = React.forwardRef(
32
29
  "aria-label": props["aria-label"],
33
30
  "aria-labelledby": props["aria-labelledby"]
34
31
  });
32
+ const identity = useFieldIdentity({
33
+ id: props.id,
34
+ name: props.name,
35
+ "data-field": props["data-field"]
36
+ });
35
37
  const innerRef = React.useRef(null);
36
38
  const setRefs = React.useCallback(
37
39
  (node) => {
@@ -77,7 +79,8 @@ const Input = React.forwardRef(
77
79
  onChange,
78
80
  className: cn(inputBaseClass, className),
79
81
  ...props,
80
- ...nameFallback
82
+ ...nameFallback,
83
+ ...identity
81
84
  }
82
85
  );
83
86
  }
@@ -89,20 +92,12 @@ const Input = React.forwardRef(
89
92
  tabIndex: -1,
90
93
  "aria-label": t("common.clear") ?? "Clear",
91
94
  onClick: clear,
92
- className: "text-muted-foreground hover:text-foreground inline-flex size-5 items-center justify-center rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:opacity-100",
93
- children: /* @__PURE__ */ jsx(X, { className: "size-4", "aria-hidden": "true" })
95
+ className: "ui-control-inline-affix-action",
96
+ children: /* @__PURE__ */ jsx(X, { className: "ui-control-inline-affix-icon", "aria-hidden": "true" })
94
97
  }
95
98
  ) : trailingIcon;
96
- return /* @__PURE__ */ jsxs("span", { "data-slot": "input-affix-wrapper", className: "relative inline-flex w-full items-center", children: [
97
- leadingIcon != null ? /* @__PURE__ */ jsx(
98
- "span",
99
- {
100
- "data-slot": "input-leading",
101
- "aria-hidden": "true",
102
- className: "text-muted-foreground pointer-events-none absolute inset-y-0 start-2 inline-flex items-center",
103
- children: leadingIcon
104
- }
105
- ) : null,
99
+ return /* @__PURE__ */ jsxs("span", { "data-slot": "input-affix-wrapper", className: "ui-input-affix-wrapper", children: [
100
+ leadingIcon != null ? /* @__PURE__ */ jsx("span", { "data-slot": "input-leading", "aria-hidden": "true", className: "ui-input-leading", children: leadingIcon }) : null,
106
101
  /* @__PURE__ */ jsx(
107
102
  "input",
108
103
  {
@@ -114,15 +109,16 @@ const Input = React.forwardRef(
114
109
  onChange: handleChange,
115
110
  className: cn(
116
111
  inputBaseClass,
117
- leadingIcon != null && "ps-9",
118
- (showClear || trailingIcon != null) && "pe-9",
112
+ leadingIcon != null && "ui-input--leading-affix",
113
+ (showClear || trailingIcon != null) && "ui-input--trailing-affix",
119
114
  className
120
115
  ),
121
116
  ...props,
122
- ...nameFallback
117
+ ...nameFallback,
118
+ ...identity
123
119
  }
124
120
  ),
125
- trailing != null ? /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 end-2 inline-flex items-center", children: trailing }) : null
121
+ trailing != null ? /* @__PURE__ */ jsx("span", { className: "ui-input-trailing", children: trailing }) : null
126
122
  ] });
127
123
  }
128
124
  );
@@ -5,7 +5,7 @@ import * as LabelPrimitive from "@radix-ui/react-label";
5
5
  import { cva } from "class-variance-authority";
6
6
  import { cn } from "../../lib/utils.js";
7
7
  const labelVariants = cva(
8
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
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
10
  const Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
11
11
  LabelPrimitive.Root,
@@ -13,7 +13,9 @@ const Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
13
13
  ref,
14
14
  "data-slot": "label",
15
15
  className: cn(
16
- "flex items-center gap-2 select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
16
+ // A label inside a disabled group reads at the SYSTEM disabled alpha (--disabled-opacity),
17
+ // the same one .ui-checkbox/.ui-radio/.ui-switch:disabled use — one knob, not a second one.
18
+ "ui-label group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-[var(--disabled-opacity)]",
17
19
  labelVariants(),
18
20
  className
19
21
  ),