@godxjp/ui 20.2.1 → 22.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 (141) hide show
  1. package/dist/app/app-provider.js +2 -1
  2. package/dist/components/data-display/badge.d.ts +28 -1
  3. package/dist/components/data-display/badge.js +2 -0
  4. package/dist/components/data-display/chat-bubble.d.ts +30 -0
  5. package/dist/components/data-display/chat-bubble.js +229 -0
  6. package/dist/components/data-display/data-table.d.ts +18 -2
  7. package/dist/components/data-display/data-table.js +4 -0
  8. package/dist/components/data-display/descriptions.js +4 -1
  9. package/dist/components/data-display/index.d.ts +5 -3
  10. package/dist/components/data-display/index.js +5 -2
  11. package/dist/components/data-display/popover.d.ts +19 -1
  12. package/dist/components/data-display/popover.js +12 -3
  13. package/dist/components/data-display/progress.d.ts +37 -0
  14. package/dist/components/data-display/progress.js +57 -5
  15. package/dist/components/data-display/scroll-area.js +26 -1
  16. package/dist/components/data-display/tree.d.ts +8 -0
  17. package/dist/components/data-display/tree.js +426 -0
  18. package/dist/components/data-entry/chat-composer.d.ts +50 -0
  19. package/dist/components/data-entry/chat-composer.js +163 -0
  20. package/dist/components/data-entry/chat-suggestion.d.ts +28 -0
  21. package/dist/components/data-entry/chat-suggestion.js +285 -0
  22. package/dist/components/data-entry/date-picker.d.ts +10 -3
  23. package/dist/components/data-entry/date-picker.js +503 -238
  24. package/dist/components/data-entry/form-field.d.ts +1 -1
  25. package/dist/components/data-entry/form-field.js +4 -1
  26. package/dist/components/data-entry/form.js +10 -1
  27. package/dist/components/data-entry/index.d.ts +4 -6
  28. package/dist/components/data-entry/index.js +4 -6
  29. package/dist/components/data-entry/label.d.ts +14 -1
  30. package/dist/components/data-entry/label.js +6 -1
  31. package/dist/components/data-entry/radio.d.ts +27 -6
  32. package/dist/components/data-entry/radio.js +88 -43
  33. package/dist/components/data-entry/search-select.js +8 -1
  34. package/dist/components/data-entry/select.js +2 -0
  35. package/dist/components/data-entry/switch.d.ts +6 -3
  36. package/dist/components/data-entry/switch.js +34 -11
  37. package/dist/components/data-entry/textarea.js +3 -1
  38. package/dist/components/data-entry/tree-utils.d.ts +10 -48
  39. package/dist/components/data-entry/tree-utils.js +1 -154
  40. package/dist/components/layout/mobile-shell.d.ts +1 -1
  41. package/dist/components/layout/mobile-shell.js +2 -0
  42. package/dist/components/navigation/menubar.d.ts +10 -3
  43. package/dist/components/navigation/steps.d.ts +1 -1
  44. package/dist/components/navigation/steps.js +3 -1
  45. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  46. package/dist/components/navigation/tabs-scroll.js +52 -1
  47. package/dist/components/navigation/tabs.d.ts +2 -2
  48. package/dist/components/navigation/tabs.js +88 -23
  49. package/dist/components/ui/index.d.ts +0 -1
  50. package/dist/components/ui/index.js +0 -1
  51. package/dist/components/ui/segmented.d.ts +15 -1
  52. package/dist/components/ui/segmented.js +13 -6
  53. package/dist/i18n/messages/en.json +48 -2
  54. package/dist/i18n/messages/ja.json +46 -2
  55. package/dist/i18n/messages/vi.json +47 -2
  56. package/dist/lib/datetime/picker-format.d.ts +18 -0
  57. package/dist/lib/datetime/picker-format.js +35 -1
  58. package/dist/lib/field-a11y.d.ts +12 -0
  59. package/dist/lib/field-a11y.js +14 -1
  60. package/dist/lib/tree.d.ts +53 -0
  61. package/dist/lib/tree.js +155 -0
  62. package/dist/props/components/data-display.prop.d.ts +194 -1
  63. package/dist/props/components/data-entry.prop.d.ts +307 -99
  64. package/dist/props/components/index.d.ts +1 -1
  65. package/dist/props/components/layout.prop.d.ts +12 -1
  66. package/dist/props/components/navigation.prop.d.ts +76 -1
  67. package/dist/props/registry.d.ts +156 -19
  68. package/dist/props/registry.js +218 -18
  69. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  70. package/dist/props/vocabulary/index.d.ts +2 -2
  71. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  72. package/dist/styles/badge-layout.css +4 -0
  73. package/dist/styles/card-layout.css +6 -6
  74. package/dist/styles/control.css +22 -6
  75. package/dist/styles/data-display-layout.css +385 -68
  76. package/dist/styles/data-entry-layout.css +64 -0
  77. package/dist/styles/focus-ring.css +4 -2
  78. package/dist/styles/navigation-layout.css +25 -0
  79. package/dist/styles/shell-layout.css +7 -1
  80. package/dist/styles/table-layout.css +34 -0
  81. package/dist/tokens/base.css +3 -0
  82. package/dist/tokens/components/chat-bubble.css +36 -0
  83. package/dist/tokens/components/chat-composer.css +19 -0
  84. package/dist/tokens/components/data-display.css +9 -6
  85. package/dist/tokens/components/descriptions.css +4 -0
  86. package/dist/tokens/components/navigation.css +6 -0
  87. package/dist/tokens/components/segmented.css +0 -2
  88. package/dist/tokens/components/shell.css +3 -0
  89. package/dist/tokens/components/table.css +3 -0
  90. package/dist/tokens/components/tree.css +27 -0
  91. package/dist/tokens/foundation.css +7 -0
  92. package/docs/COMPONENTS.md +14 -14
  93. package/docs/CONSUMER-RULES.md +24 -1
  94. package/docs/FRAME-COVERAGE-LEDGER.md +1 -1
  95. package/docs/FRAME-COVERAGE-REPORT.md +9 -7
  96. package/docs/TOKENS.md +65 -0
  97. package/docs/data-display/badge.tsx +37 -0
  98. package/docs/data-display/chat-bubble.tsx +397 -0
  99. package/docs/data-display/data-table/index.tsx +18 -0
  100. package/docs/data-display/popover.tsx +41 -1
  101. package/docs/data-display/progress.tsx +28 -0
  102. package/docs/data-display/timeline.tsx +46 -0
  103. package/docs/data-display/tree.tsx +394 -0
  104. package/docs/data-entry/calendar.tsx +2 -2
  105. package/docs/data-entry/chat-composer.tsx +464 -0
  106. package/docs/data-entry/chat-suggestion.tsx +301 -0
  107. package/docs/data-entry/date-picker.tsx +143 -3
  108. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  109. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  110. package/docs/data-entry/form.tsx +4 -4
  111. package/docs/data-entry/number-input.tsx +31 -0
  112. package/docs/data-entry/segmented.tsx +103 -1
  113. package/docs/layout/mobile-shell.tsx +5 -0
  114. package/docs/navigation/tabs.tsx +149 -0
  115. package/docs/roadmap/ai-chat-components.md +207 -0
  116. package/docs/roadmap/antd-parity.md +155 -0
  117. package/docs/roadmap/badge-tag-chip-count.md +172 -0
  118. package/docs/roadmap/list-masonry.md +159 -0
  119. package/docs/roadmap/parity-audit-data-display-feedback.md +567 -0
  120. package/docs/roadmap/parity-audit-data-entry.md +357 -0
  121. package/docs/roadmap/parity-audit-layout-navigation-general.md +464 -0
  122. package/docs/roadmap/parity-backlog.md +78 -0
  123. package/docs/roadmap/tree-components.md +151 -0
  124. package/docs/showcase/acme-portal.tsx +7 -0
  125. package/docs/showcase/table-tree-rows.tsx +4 -4
  126. package/package.json +13 -23
  127. package/scripts/ui-audit.mjs +17 -4
  128. package/dist/components/data-display/tree-list.d.ts +0 -13
  129. package/dist/components/data-display/tree-list.js +0 -26
  130. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  131. package/dist/components/data-entry/date-range-picker.js +0 -349
  132. package/dist/components/data-entry/month-picker.d.ts +0 -10
  133. package/dist/components/data-entry/month-picker.js +0 -241
  134. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  135. package/dist/components/data-entry/month-range-picker.js +0 -301
  136. package/dist/components/ui/date-range-picker.d.ts +0 -1
  137. package/dist/components/ui/date-range-picker.js +0 -2
  138. package/docs/data-display/tree-list.tsx +0 -107
  139. package/docs/data-entry/date-range-picker.tsx +0 -175
  140. package/docs/data-entry/month-picker.tsx +0 -101
  141. package/docs/data-entry/month-range-picker.tsx +0 -115
@@ -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, field, label, required, helper, error: errorProp, validateStatus, hasFeedback, feedback, 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, helperPlacement, error: errorProp, validateStatus, hasFeedback, feedback, labelAddon, layout: layoutProp, labelWidth: labelWidthProp, controlWidth: controlWidthProp, colSpan, className, children, staticText, }: FormFieldProp): React.JSX.Element;
@@ -19,6 +19,7 @@ function FormField({
19
19
  label,
20
20
  required,
21
21
  helper,
22
+ helperPlacement = "after",
22
23
  error: errorProp,
23
24
  validateStatus,
24
25
  hasFeedback,
@@ -46,6 +47,7 @@ function FormField({
46
47
  const resolvedId = id ?? autoId;
47
48
  const labelId = `${resolvedId}-label`;
48
49
  const helperId = helper ? `${resolvedId}-helper` : void 0;
50
+ const helperNode = helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "text-muted-foreground text-xs", children: helper }) : null;
49
51
  const validationStatus = error ? "error" : validateStatus;
50
52
  const feedbackId = hasFeedback && validationStatus ? `${resolvedId}-feedback` : void 0;
51
53
  const FeedbackIcon = validationStatus === "validating" ? LoaderCircle : validationStatus === "error" ? CircleAlert : validationStatus === "warning" ? TriangleAlert : CheckCircle;
@@ -151,6 +153,7 @@ function FormField({
151
153
  labelAddon
152
154
  ] }),
153
155
  /* @__PURE__ */ jsxs("div", { "data-slot": "form-field-control", className: "ui-form-field-control", children: [
156
+ helperPlacement === "before" ? helperNode : null,
154
157
  isStatic ? childWithA11y : /* @__PURE__ */ jsx(FieldNameContext.Provider, { value: fieldNameContext, children: /* @__PURE__ */ jsx(FieldIdentityContext.Provider, { value: fieldIdentityContext, children: /* @__PURE__ */ jsx(
155
158
  "fieldset",
156
159
  {
@@ -170,7 +173,7 @@ function FormField({
170
173
  ),
171
174
  t(`dataEntry.form.${validationStatus}`)
172
175
  ] }) : null,
173
- helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "text-muted-foreground text-xs", children: helper }) : null,
176
+ helperPlacement === "after" ? helperNode : null,
174
177
  error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-destructive text-xs", children: error }) : null
175
178
  ] })
176
179
  ]
@@ -9,6 +9,15 @@ const FormLayoutContext = React.createContext(null);
9
9
  function useFormLayout() {
10
10
  return React.useContext(FormLayoutContext);
11
11
  }
12
+ function asChildContent(children, columns) {
13
+ if (columns == null) return children;
14
+ const child = React.Children.only(children);
15
+ return React.cloneElement(
16
+ child,
17
+ void 0,
18
+ /* @__PURE__ */ jsx(ResponsiveGrid, { columns, children: child.props.children })
19
+ );
20
+ }
12
21
  const Form = React.forwardRef(function Form2({
13
22
  layout = "vertical",
14
23
  disabled,
@@ -41,7 +50,7 @@ const Form = React.forwardRef(function Form2({
41
50
  "data-layout": layout,
42
51
  className: cn("ui-form", density && `ui-density-${density}`, className),
43
52
  ...props,
44
- children
53
+ children: asChildContent(children, columns)
45
54
  }
46
55
  )
47
56
  ) });
@@ -7,6 +7,10 @@ export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScro
7
7
  export { Checkbox } from "./checkbox.js";
8
8
  export { CheckboxGroup } from "./checkbox-group.js";
9
9
  export { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
10
+ export { ChatComposer } from "./chat-composer.js";
11
+ export type { ChatComposerProp, ChatComposerProps, ChatComposerSubmitTypeProp, } from "./chat-composer.js";
12
+ export { ChatSuggestion } from "./chat-suggestion.js";
13
+ export type { ChatSuggestionProp, ChatSuggestionProps, ChatSuggestionItemProp, ChatSuggestionRenderProp, } from "./chat-suggestion.js";
10
14
  export { Textarea } from "./textarea.js";
11
15
  export type { TextareaProps } from "./textarea.js";
12
16
  export { Form, useFormLayout, type FormLayoutContextValue } from "./form.js";
@@ -28,14 +32,8 @@ export { Slider } from "./slider.js";
28
32
  export type { SliderProps } from "./slider.js";
29
33
  export { Calendar } from "./calendar.js";
30
34
  export type { CalendarProps } from "./calendar.js";
31
- export { MonthPicker } from "./month-picker.js";
32
- export type { MonthPickerProps } from "./month-picker.js";
33
- export { MonthRangePicker } from "./month-range-picker.js";
34
- export type { MonthRangePickerProps } from "./month-range-picker.js";
35
35
  export { DatePicker } from "./date-picker.js";
36
36
  export type { DatePickerProps } from "./date-picker.js";
37
- export { DateRangePicker } from "./date-range-picker.js";
38
- export type { DateRangePickerProps } from "./date-range-picker.js";
39
37
  export { TimePicker } from "./time-picker.js";
40
38
  export type { TimePickerProps } from "./time-picker.js";
41
39
  export { ColorPicker } from "./color-picker.js";
@@ -16,6 +16,8 @@ import {
16
16
  import { Checkbox } from "./checkbox.js";
17
17
  import { CheckboxGroup } from "./checkbox-group.js";
18
18
  import { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
19
+ import { ChatComposer } from "./chat-composer.js";
20
+ import { ChatSuggestion } from "./chat-suggestion.js";
19
21
  import { Textarea } from "./textarea.js";
20
22
  import { Form, useFormLayout } from "./form.js";
21
23
  import { FormField } from "./form-field.js";
@@ -27,10 +29,7 @@ import { Toggle } from "./toggle.js";
27
29
  import { ToggleGroup, ToggleGroupItem } from "./toggle-group.js";
28
30
  import { Slider } from "./slider.js";
29
31
  import { Calendar } from "./calendar.js";
30
- import { MonthPicker } from "./month-picker.js";
31
- import { MonthRangePicker } from "./month-range-picker.js";
32
32
  import { DatePicker } from "./date-picker.js";
33
- import { DateRangePicker } from "./date-range-picker.js";
34
33
  import { TimePicker } from "./time-picker.js";
35
34
  import { ColorPicker } from "./color-picker.js";
36
35
  import {
@@ -64,6 +63,8 @@ export {
64
63
  BranchScopePicker,
65
64
  Calendar,
66
65
  Cascader,
66
+ ChatComposer,
67
+ ChatSuggestion,
67
68
  Checkbox,
68
69
  CheckboxGroup,
69
70
  ColorPicker,
@@ -75,7 +76,6 @@ export {
75
76
  CommandList,
76
77
  CommandPalette,
77
78
  DatePicker,
78
- DateRangePicker,
79
79
  Field,
80
80
  Form,
81
81
  FormErrors,
@@ -87,8 +87,6 @@ export {
87
87
  InputOTPSeparator,
88
88
  InputOTPSlot,
89
89
  Label,
90
- MonthPicker,
91
- MonthRangePicker,
92
90
  NumberInput,
93
91
  PasswordInput,
94
92
  PasswordStrength,
@@ -1,5 +1,18 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
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> & {
3
+ export declare const Label: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>, "ref">, "slot"> & VariantProps<(props?: import("class-variance-authority/types").ClassProp | undefined) => string> & {
4
4
  asChild?: boolean;
5
+ /**
6
+ * react-aria's slot channel, DEFAULTED OFF.
7
+ *
8
+ * A `Label` rendered inside a react-aria collection (a `RadioGroup`, say) is otherwise
9
+ * claimed by that collection as the GROUP's label: it comes out as a `<span>`, the `htmlFor`
10
+ * this library passed is dropped on the floor, and the control it was meant to name ends up
11
+ * with no accessible name at all — a silent failure, measured on `Radio.Group` the moment
12
+ * that group moved to react-aria. `<Label htmlFor="x">` means one thing in this library and
13
+ * a surrounding primitive does not get to redefine it, so the default is react-aria's own
14
+ * opt-out: "an explicit `null` value indicates that the local props completely override all
15
+ * props received from a parent". Pass a slot name to opt back in.
16
+ */
17
+ slot?: string | null;
5
18
  } & React.RefAttributes<HTMLLabelElement>>;
@@ -7,8 +7,13 @@ 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, onMouseDown, asChild = false, ...props }, ref) => {
10
+ const Label = React.forwardRef(({ className, onMouseDown, asChild = false, slot = null, ...props }, ref) => {
11
11
  const shared = {
12
+ // `string | null`, cast because react-aria's own `LabelProps` extends `LabelHTMLAttributes`
13
+ // and so re-types `slot` as `string | undefined` — narrower than the `SlotProps` contract the
14
+ // rest of the library publishes, and narrower than `useSlottedContext`, which branches on
15
+ // `slot === null` explicitly. React drops a `null` attribute, so nothing reaches the DOM.
16
+ slot,
12
17
  // `...props` đứng TRƯỚC, không phải sau: giữ đúng thứ tự thuộc tính mà
13
18
  // @radix-ui/react-label phát ra (`for` → `data-slot` → `class`), nên phép so
14
19
  // `outerHTML` trong __tests__/label-checkbox-rac.test.tsx khớp từng ký tự.
@@ -1,14 +1,35 @@
1
1
  import * as React from "react";
2
- import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
3
2
  import type { RadioGroupProp } from "../../props/components/data-entry.prop.js";
4
3
  export type { RadioGroupProp, RadioGroupProp as RadioGroupProps, } from "../../props/components/data-entry.prop.js";
5
- declare const RadioGroupRoot: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
6
- declare const RadioItem: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
4
+ declare const RadioGroupRoot: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "defaultValue" | "dir" | "onChange"> & {
5
+ value?: string;
6
+ defaultValue?: string;
7
+ onValueChange?: (value: string) => void;
8
+ disabled?: boolean;
9
+ required?: boolean;
10
+ name?: string;
11
+ orientation?: "horizontal" | "vertical";
12
+ } & React.RefAttributes<HTMLDivElement>>;
13
+ declare const RadioItem: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref">, "value"> & {
14
+ value: string;
15
+ } & React.RefAttributes<HTMLLabelElement>>;
7
16
  declare function RadioGroupOptions({ value, defaultValue, onValueChange, options, orientation, optionType, buttonStyle, disabled, name, id, className, children, ...ariaProps }: RadioGroupProp): React.JSX.Element;
8
17
  /** Single radio — use inside `Radio.Group` / `RadioGroupRoot`, or via `options` API. */
9
- export declare const Radio: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>> & {
10
- Root: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
18
+ export declare const Radio: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref">, "value"> & {
19
+ value: string;
20
+ } & React.RefAttributes<HTMLLabelElement>> & {
21
+ Root: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "defaultValue" | "dir" | "onChange"> & {
22
+ value?: string;
23
+ defaultValue?: string;
24
+ onValueChange?: (value: string) => void;
25
+ disabled?: boolean;
26
+ required?: boolean;
27
+ name?: string;
28
+ orientation?: "horizontal" | "vertical";
29
+ } & React.RefAttributes<HTMLDivElement>>;
11
30
  Group: typeof RadioGroupOptions;
12
- Item: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
31
+ Item: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref">, "value"> & {
32
+ value: string;
33
+ } & React.RefAttributes<HTMLLabelElement>>;
13
34
  };
14
35
  export { RadioGroupRoot, RadioItem, RadioGroupOptions as RadioGroup };
@@ -1,64 +1,108 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
4
+ import { Radio as AriaRadio, RadioGroup as AriaRadioGroup } from "react-aria-components";
5
5
  import { Circle } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
- import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
7
+ import { pickFieldA11y, useFieldIdentity, useMirroredInputAttributes } from "../../lib/field-a11y.js";
8
8
  import { Field } from "./field.js";
9
9
  import { choiceGroupClassName } from "./choice-option.js";
10
- const RadioGroupRoot = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
11
- RadioGroupPrimitive.Root,
12
- {
13
- ref,
14
- "data-slot": "radio-group",
15
- className: cn("ui-choice-group", className),
16
- ...props
10
+ function checkedState(isSelected) {
11
+ return isSelected ? "checked" : "unchecked";
12
+ }
13
+ function useFieldKeyedInput(fieldKey) {
14
+ const inputRef = React.useRef(null);
15
+ useMirroredInputAttributes(inputRef, { "data-field": fieldKey });
16
+ return inputRef;
17
+ }
18
+ const RadioGroupRoot = React.forwardRef(
19
+ ({ className, value, defaultValue, onValueChange, disabled, required, orientation, ...props }, ref) => {
20
+ const invalid = props["aria-invalid"];
21
+ const { "aria-label": ariaLabel, ...rest } = props;
22
+ return /* @__PURE__ */ jsx(
23
+ AriaRadioGroup,
24
+ {
25
+ ref,
26
+ "data-slot": "radio-group",
27
+ className: cn("ui-choice-group", className),
28
+ value,
29
+ defaultValue,
30
+ onChange: onValueChange,
31
+ isDisabled: disabled,
32
+ isRequired: required,
33
+ isInvalid: invalid !== void 0 && invalid !== false && invalid !== "false",
34
+ orientation,
35
+ "aria-label": props["aria-labelledby"] ? void 0 : ariaLabel,
36
+ ...rest
37
+ }
38
+ );
17
39
  }
18
- ));
19
- RadioGroupRoot.displayName = RadioGroupPrimitive.Root.displayName;
20
- const RadioItem = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
21
- RadioGroupPrimitive.Item,
22
- {
23
- ref,
24
- "data-slot": "radio-group-item",
25
- className: cn(
26
- // `.ui-radio:disabled, .ui-radio[data-disabled]` in styles/control.css already declares both
27
- // and reads --disabled-opacity. The utility was layered after components, so it silently
28
- // outranked that token — a service theme's --disabled-opacity never reached a radio.
29
- // Byte-identical: --disabled-opacity defaults to 0.5.
30
- "ui-radio aria-invalid:border-destructive shrink-0 shadow-xs transition-shadow outline-none",
31
- className
32
- ),
33
- ...props,
34
- children: /* @__PURE__ */ jsx(
35
- RadioGroupPrimitive.Indicator,
40
+ );
41
+ RadioGroupRoot.displayName = "RadioGroup";
42
+ const RadioItem = React.forwardRef(
43
+ ({ className, disabled, value, ...props }, ref) => {
44
+ const { "data-field": fieldKey } = props;
45
+ const inputRef = useFieldKeyedInput(fieldKey);
46
+ return /* @__PURE__ */ jsx(
47
+ AriaRadio,
36
48
  {
37
- "data-slot": "radio-group-indicator",
38
- className: "ui-choice-indicator",
39
- children: /* @__PURE__ */ jsx(Circle, { className: "ui-radio-icon", "aria-hidden": "true" })
49
+ ref,
50
+ inputRef,
51
+ value,
52
+ isDisabled: disabled,
53
+ "data-slot": "radio-group-item",
54
+ className: cn(
55
+ // `.ui-radio:disabled, .ui-radio[data-disabled]` in styles/control.css already declares both
56
+ // and reads --disabled-opacity. The utility was layered after components, so it silently
57
+ // outranked that token — a service theme's --disabled-opacity never reached a radio.
58
+ // Byte-identical: --disabled-opacity defaults to 0.5.
59
+ //
60
+ // `inline-flex items-center justify-center` is load-bearing, for the reason Checkbox
61
+ // records: react-aria's root is a `<label>`, which is `display:inline`, so without it the
62
+ // 16px dot collapses to nothing. `data-[invalid]` replaces `aria-invalid:` because
63
+ // react-aria puts `aria-invalid` on the `<input>` and `data-invalid` on this box.
64
+ "ui-radio data-[invalid]:border-destructive inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow outline-none",
65
+ className
66
+ ),
67
+ ...props,
68
+ render: (domProps, state) => /* @__PURE__ */ jsx("label", { ...domProps, "data-state": checkedState(state.isSelected) }),
69
+ children: /* @__PURE__ */ jsx("span", { "data-slot": "radio-group-indicator", className: "ui-choice-indicator", children: /* @__PURE__ */ jsx(Circle, { className: "ui-radio-icon", "aria-hidden": "true" }) })
40
70
  }
41
- )
71
+ );
42
72
  }
43
- ));
44
- RadioItem.displayName = RadioGroupPrimitive.Item.displayName;
73
+ );
74
+ RadioItem.displayName = "Radio";
75
+ function RadioBarButton({
76
+ id,
77
+ option,
78
+ resolvedField
79
+ }) {
80
+ const inputRef = useFieldKeyedInput(resolvedField);
81
+ return /* @__PURE__ */ jsx(
82
+ AriaRadio,
83
+ {
84
+ id,
85
+ inputRef,
86
+ value: option.value,
87
+ isDisabled: option.disabled,
88
+ "data-slot": "radio-button",
89
+ className: "ui-radio-button ui-focus-ring",
90
+ render: (domProps, state) => /* @__PURE__ */ jsx("label", { ...domProps, "data-state": checkedState(state.isSelected) }),
91
+ children: /* @__PURE__ */ jsx("span", { className: "ui-radio-button-label", children: option.label })
92
+ }
93
+ );
94
+ }
45
95
  function RadioButtonBar({
46
96
  options,
47
- value,
48
97
  optionDomId,
49
98
  resolvedField
50
99
  }) {
51
100
  return /* @__PURE__ */ jsx(Fragment, { children: options.map((opt, index) => /* @__PURE__ */ jsx(
52
- RadioGroupPrimitive.Item,
101
+ RadioBarButton,
53
102
  {
54
103
  id: optionDomId(opt.value, index),
55
- value: opt.value,
56
- disabled: opt.disabled,
57
- "data-slot": "radio-button",
58
- "data-field": resolvedField,
59
- "data-state": value === opt.value ? "checked" : "unchecked",
60
- className: "ui-radio-button ui-focus-ring",
61
- children: /* @__PURE__ */ jsx("span", { className: "ui-radio-button-label", children: opt.label })
104
+ option: opt,
105
+ resolvedField
62
106
  },
63
107
  opt.value
64
108
  )) });
@@ -94,6 +138,7 @@ function RadioGroupOptions({
94
138
  disabled,
95
139
  name: resolvedName,
96
140
  id,
141
+ orientation,
97
142
  ...groupA11y,
98
143
  "data-field": resolvedField,
99
144
  "data-orientation": orientation,
@@ -108,7 +153,6 @@ function RadioGroupOptions({
108
153
  RadioButtonBar,
109
154
  {
110
155
  options,
111
- value,
112
156
  optionDomId,
113
157
  resolvedField
114
158
  }
@@ -147,6 +191,7 @@ function RadioGroupOptions({
147
191
  disabled,
148
192
  name: resolvedName,
149
193
  id,
194
+ orientation,
150
195
  ...groupA11y,
151
196
  "data-field": resolvedField,
152
197
  "data-orientation": orientation,
@@ -36,6 +36,7 @@ function SearchSelect(props) {
36
36
  size,
37
37
  status,
38
38
  variant,
39
+ width = "full",
39
40
  loading: loadingProp = false,
40
41
  open: openProp,
41
42
  defaultOpen = false,
@@ -334,9 +335,15 @@ function SearchSelect(props) {
334
335
  "data-field": resolvedField,
335
336
  "data-value": (multiple ? values.join(",") : value) || void 0,
336
337
  "data-mode": multiple ? "multiple" : void 0,
338
+ "data-width": width,
337
339
  className: cn(
338
340
  controlSurfaceTriggerClass,
339
- "w-full justify-start",
341
+ "justify-start",
342
+ // `bounded` deliberately emits NO width utility — its width is owned by the
343
+ // `[data-width="bounded"]` rule in control.css, and a utility here would win the
344
+ // layer order and make that token dead. Same reasoning as SelectTrigger.
345
+ width === "auto" && "w-auto",
346
+ width === "full" && "w-full",
340
347
  // Reserve trailing room for the single clear-or-chevron overlay rendered below.
341
348
  "ui-control-trigger-affixed"
342
349
  ),
@@ -298,6 +298,7 @@ function DataSelect(props) {
298
298
  size,
299
299
  status,
300
300
  variant,
301
+ width,
301
302
  loading,
302
303
  open,
303
304
  defaultOpen,
@@ -372,6 +373,7 @@ function DataSelect(props) {
372
373
  size,
373
374
  variant,
374
375
  status,
376
+ width,
375
377
  "aria-busy": loading || void 0,
376
378
  className: cn(
377
379
  (showClear || loading) && "ui-control-trigger-affixed",
@@ -1,9 +1,12 @@
1
1
  import * as React from "react";
2
- import * as SwitchPrimitive from "@radix-ui/react-switch";
3
2
  export type { SwitchProp, SwitchProp as SwitchProps } from "../../props/components/data-entry.prop.js";
4
- export declare const Switch: React.ForwardRefExoticComponent<Omit<SwitchPrimitive.SwitchProps & React.RefAttributes<HTMLButtonElement>, "ref"> & {
3
+ export declare const Switch: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref">, "checked" | "defaultChecked" | "onChange" | "value"> & {
4
+ checked?: boolean;
5
+ defaultChecked?: boolean;
6
+ onCheckedChange?: (checked: boolean) => void;
7
+ required?: boolean;
5
8
  size?: "sm" | "md";
6
9
  loading?: boolean;
7
10
  checkedChildren?: React.ReactNode;
8
11
  unCheckedChildren?: React.ReactNode;
9
- } & React.RefAttributes<HTMLButtonElement>>;
12
+ } & React.RefAttributes<HTMLLabelElement>>;
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import * as SwitchPrimitive from "@radix-ui/react-switch";
4
+ import { Switch as AriaSwitch } from "react-aria-components";
5
5
  import { Loader2 } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
- import { useFieldIdentity } from "../../lib/field-a11y.js";
7
+ import { useFieldIdentity, useMirroredInputAttributes } from "../../lib/field-a11y.js";
8
8
  const Switch = React.forwardRef(
9
9
  ({
10
10
  className,
@@ -16,6 +16,8 @@ const Switch = React.forwardRef(
16
16
  loading = false,
17
17
  checkedChildren,
18
18
  unCheckedChildren,
19
+ disabled,
20
+ required,
19
21
  ...props
20
22
  }, ref) => {
21
23
  const identity = useFieldIdentity({
@@ -34,19 +36,42 @@ const Switch = React.forwardRef(
34
36
  }
35
37
  onCheckedChange?.(next);
36
38
  };
39
+ const ownField = props["data-field"];
40
+ const fieldKey = ownField ?? identity["data-field"];
41
+ const invalid = props["aria-invalid"];
42
+ const isInvalid = invalid !== void 0 && invalid !== false && invalid !== "false";
43
+ const inputRef = React.useRef(null);
44
+ useMirroredInputAttributes(inputRef, {
45
+ "aria-invalid": isInvalid ? "true" : void 0,
46
+ "aria-busy": loading ? "true" : void 0,
47
+ "aria-disabled": loading ? "true" : void 0,
48
+ // react-aria's `Switch` Omits `isRequired`, and a switch is never the target of native
49
+ // constraint validation in this library — the form layer owns that. The prop is accepted
50
+ // for shape and announced through `aria-required`.
51
+ "aria-required": required ? "true" : void 0,
52
+ "data-field": fieldKey
53
+ });
54
+ const state = isChecked ? "checked" : "unchecked";
37
55
  return /* @__PURE__ */ jsxs(Fragment, { children: [
38
56
  resolvedName ? /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value: isChecked ? "1" : "0", readOnly: true }) : null,
39
57
  /* @__PURE__ */ jsxs(
40
- SwitchPrimitive.Root,
58
+ AriaSwitch,
41
59
  {
60
+ ...props,
42
61
  ref,
62
+ inputRef,
43
63
  "data-slot": "switch",
44
64
  "data-size": size,
65
+ "data-state": state,
45
66
  "data-loading": loading ? "true" : void 0,
46
- "aria-busy": loading || void 0,
47
- "aria-disabled": loading || void 0,
48
- checked: isChecked,
49
- onCheckedChange: handleCheckedChange,
67
+ isSelected: isChecked,
68
+ isDisabled: disabled,
69
+ onChange: handleCheckedChange,
70
+ onKeyDown: (event) => {
71
+ if (event.key !== "Enter") return;
72
+ event.preventDefault();
73
+ handleCheckedChange(!isChecked);
74
+ },
50
75
  className: cn(
51
76
  // `.ui-switch:disabled, .ui-switch[data-disabled]` in styles/control.css already
52
77
  // declares both and reads --disabled-opacity. The utility was layered after
@@ -56,11 +81,9 @@ const Switch = React.forwardRef(
56
81
  "peer ui-switch shadow-xs transition-all outline-none",
57
82
  className
58
83
  ),
59
- ...props,
60
- "data-field": identity["data-field"] ?? props["data-field"],
61
84
  children: [
62
85
  checkedChildren != null || unCheckedChildren != null ? /* @__PURE__ */ jsx("span", { "data-slot": "switch-content", className: "ui-switch-content", "aria-hidden": "true", children: isChecked ? checkedChildren : unCheckedChildren }) : null,
63
- /* @__PURE__ */ jsx(SwitchPrimitive.Thumb, { "data-slot": "switch-thumb", className: "ui-switch-thumb", children: loading ? /* @__PURE__ */ jsx(
86
+ /* @__PURE__ */ jsx("span", { "data-slot": "switch-thumb", "data-state": state, className: "ui-switch-thumb", children: loading ? /* @__PURE__ */ jsx(
64
87
  Loader2,
65
88
  {
66
89
  "data-slot": "switch-spinner",
@@ -74,7 +97,7 @@ const Switch = React.forwardRef(
74
97
  ] });
75
98
  }
76
99
  );
77
- Switch.displayName = SwitchPrimitive.Root.displayName;
100
+ Switch.displayName = "Switch";
78
101
  export {
79
102
  Switch
80
103
  };
@@ -158,11 +158,13 @@ const Textarea = React.forwardRef(
158
158
  }
159
159
  );
160
160
  if (!needsWrapper) return field;
161
+ const placeholderText = typeof props.placeholder === "string" ? props.placeholder : void 0;
162
+ const mirrorText = mirror.length > 0 ? mirror : placeholderText ?? "";
161
163
  return /* @__PURE__ */ jsxs(
162
164
  "span",
163
165
  {
164
166
  "data-slot": "textarea-affix-wrapper",
165
- "data-autogrow-value": growing ? mirror : void 0,
167
+ "data-autogrow-value": growing ? mirrorText : void 0,
166
168
  style: { ...autoGrowVars, ...mirrorInset },
167
169
  className: cn(
168
170
  "relative w-full",
@@ -1,48 +1,10 @@
1
- import type * as React from "react";
2
- /** Normalized tree node — the conventional `treeData` / Cascader `options` shape. */
3
- export type TreeOption = {
4
- value: string;
5
- label: React.ReactNode;
6
- disabled?: boolean;
7
- disableCheckbox?: boolean;
8
- /** When false with `loadData`, shows expand affordance */
9
- isLeaf?: boolean;
10
- children?: TreeOption[];
11
- };
12
- export type TreeFieldNames = {
13
- label?: string;
14
- value?: string;
15
- children?: string;
16
- };
17
- export type NormalizedTreeOption = TreeOption & {
18
- children?: NormalizedTreeOption[];
19
- };
20
- type RawTreeNode = Record<string, unknown>;
21
- export declare function reactNodeText(value: React.ReactNode): string;
22
- export declare function normalizeTreeOptions(nodes: RawTreeNode[] | undefined, fieldNames?: TreeFieldNames): NormalizedTreeOption[];
23
- export declare function getNodeByPath(options: NormalizedTreeOption[], path: string[]): NormalizedTreeOption[];
24
- export declare function getOptionsAtPath(options: NormalizedTreeOption[], path: string[]): NormalizedTreeOption[];
25
- export declare function formatPathLabels(chain: NormalizedTreeOption[], separator?: string): string;
26
- export type TreePath = {
27
- path: string[];
28
- labels: string[];
29
- };
30
- export declare function collectLeafPaths(options: NormalizedTreeOption[], prefix?: string[], root?: NormalizedTreeOption[]): TreePath[];
31
- export declare function collectAllPaths(options: NormalizedTreeOption[], prefix?: string[], root?: NormalizedTreeOption[]): TreePath[];
32
- export declare function pathKey(path: string[]): string;
33
- export declare function pathsEqual(a: string[], b: string[]): boolean;
34
- export declare function filterTreeOptions(options: NormalizedTreeOption[], query: string, filter?: (query: string, path: NormalizedTreeOption[]) => boolean): TreePath[];
35
- export declare function getDescendantValues(node: NormalizedTreeOption): string[];
36
- export declare function flattenVisibleTree(options: NormalizedTreeOption[], expandedKeys: Set<string>, depth?: number): {
37
- node: NormalizedTreeOption;
38
- depth: number;
39
- hasChildren: boolean;
40
- }[];
41
- export declare function filterVisibleTree(options: NormalizedTreeOption[], query: string): {
42
- node: NormalizedTreeOption;
43
- depth: number;
44
- hasChildren: boolean;
45
- }[];
46
- export declare function collectAllExpandableKeys(options: NormalizedTreeOption[]): string[];
47
- export declare function findNodeByValue(options: NormalizedTreeOption[], value: string): NormalizedTreeOption | undefined;
48
- export {};
1
+ /**
2
+ * RE-EXPORT SHIM — the tree model moved to `src/lib/tree.ts`.
3
+ *
4
+ * `Tree` (data-display) and `TreeSelect` (data-entry) are two SURFACES over ONE model. Forking the
5
+ * traversal would let a page tree and a dropdown tree disagree about what "expanded", "a leaf" or
6
+ * "every descendant" means — so the normalizer, the visible-row flattener and the descendant walk
7
+ * live in `src/lib/`, which both groups may import, and this path stays valid so nothing that
8
+ * already imports `./tree-utils` has to change.
9
+ */
10
+ export * from "../../lib/tree.js";