@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
@@ -1,21 +1,52 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import * as SeparatorPrimitive from "@radix-ui/react-separator";
5
5
  import { cn } from "../../lib/utils.js";
6
- const Separator = React.forwardRef(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => /* @__PURE__ */ jsx(
7
- SeparatorPrimitive.Root,
8
- {
9
- ref,
10
- "data-slot": "separator",
11
- "data-orientation": orientation,
12
- orientation,
6
+ const Separator = React.forwardRef(
7
+ ({
8
+ className,
9
+ orientation = "horizontal",
13
10
  decorative,
14
- className: cn("ui-separator", className),
11
+ label,
12
+ labelAlign = "center",
13
+ tone = "default",
15
14
  ...props
15
+ }, ref) => {
16
+ const hasLabel = typeof label === "string" && label.trim() !== "";
17
+ const labelled = hasLabel && orientation === "horizontal";
18
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
19
+ if (hasLabel && orientation === "vertical") {
20
+ console.warn(
21
+ 'Separator: `label` is rendered on orientation="horizontal" only; it was ignored. Use a horizontal rule for a day divider / unread watermark.'
22
+ );
23
+ }
24
+ }
25
+ const isDecorative = decorative ?? !labelled;
26
+ return /* @__PURE__ */ jsx(
27
+ SeparatorPrimitive.Root,
28
+ {
29
+ ref,
30
+ "data-slot": "separator",
31
+ "data-orientation": orientation,
32
+ "data-tone": tone,
33
+ "data-labelled": labelled ? "" : void 0,
34
+ "data-label-align": labelled ? labelAlign : void 0,
35
+ orientation,
36
+ decorative: isDecorative,
37
+ "aria-label": labelled && !isDecorative ? label : void 0,
38
+ className: cn("ui-separator", className),
39
+ ...props,
40
+ children: labelled ? /* @__PURE__ */ jsxs(Fragment, { children: [
41
+ /* @__PURE__ */ jsx("span", { className: "ui-separator-rule", "aria-hidden": "true" }),
42
+ /* @__PURE__ */ jsx("span", { className: "ui-separator-label", "aria-hidden": isDecorative ? void 0 : true, children: label }),
43
+ /* @__PURE__ */ jsx("span", { className: "ui-separator-rule", "aria-hidden": "true" })
44
+ ] }) : null
45
+ }
46
+ );
16
47
  }
17
- ));
18
- Separator.displayName = SeparatorPrimitive.Root.displayName;
48
+ );
49
+ Separator.displayName = "Separator";
19
50
  export {
20
51
  Separator
21
52
  };
@@ -1,13 +1,23 @@
1
1
  import * as React from "react";
2
2
  import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
3
- import { type ToggleProps } from "./toggle.js";
4
- type ToggleGroupVariant = ToggleProps["variant"];
5
- type ToggleGroupSize = ToggleProps["size"];
6
- export declare const ToggleGroup: React.ForwardRefExoticComponent<((Omit<ToggleGroupPrimitive.ToggleGroupSingleProps & React.RefAttributes<HTMLDivElement>, "ref"> | Omit<ToggleGroupPrimitive.ToggleGroupMultipleProps & React.RefAttributes<HTMLDivElement>, "ref">) & {
3
+ import { type ToggleCountFields, type ToggleProp } from "./toggle.js";
4
+ type ToggleGroupVariant = ToggleProp["variant"];
5
+ type ToggleGroupSize = ToggleProp["size"];
6
+ export declare const ToggleGroup: React.ForwardRefExoticComponent<((Omit<ToggleGroupPrimitive.ToggleGroupMultipleProps & React.RefAttributes<HTMLDivElement>, "ref"> | Omit<ToggleGroupPrimitive.ToggleGroupSingleProps & React.RefAttributes<HTMLDivElement>, "ref">) & {
7
7
  variant?: ToggleGroupVariant;
8
8
  size?: ToggleGroupSize;
9
9
  }) & React.RefAttributes<HTMLDivElement>>;
10
- export declare const ToggleGroupItem: React.ForwardRefExoticComponent<Omit<ToggleGroupPrimitive.ToggleGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & {
10
+ /**
11
+ * `variant`/`size` come from the group through context; the COUNT stays per item, because the
12
+ * number is per-item data (gh#312). Same vocabulary as `Toggle` and `Button` — one counter pill
13
+ * for the whole library.
14
+ */
15
+ export type ToggleGroupItemProp = React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> & ToggleCountFields & {
16
+ variant?: ToggleGroupVariant;
17
+ size?: ToggleGroupSize;
18
+ };
19
+ export type ToggleGroupItemProps = ToggleGroupItemProp;
20
+ export declare const ToggleGroupItem: React.ForwardRefExoticComponent<Omit<ToggleGroupPrimitive.ToggleGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & ToggleCountFields & {
11
21
  variant?: ToggleGroupVariant;
12
22
  size?: ToggleGroupSize;
13
23
  } & React.RefAttributes<HTMLButtonElement>>;
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
5
5
  import { cn } from "../../lib/utils.js";
6
- import { toggleVariants } from "./toggle.js";
6
+ import { toggleVariants, useCounterPill } from "./toggle.js";
7
7
  const ToggleGroupContext = React.createContext({});
8
8
  const ToggleGroup = React.forwardRef(({ className, variant, size, children, ...props }, ref) => {
9
9
  const context = React.useMemo(() => ({ variant, size }), [variant, size]);
@@ -21,22 +21,47 @@ const ToggleGroup = React.forwardRef(({ className, variant, size, children, ...p
21
21
  );
22
22
  });
23
23
  ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
24
- const ToggleGroupItem = React.forwardRef(({ className, variant, size, ...props }, ref) => {
25
- const context = React.useContext(ToggleGroupContext);
26
- const resolvedVariant = variant ?? context.variant;
27
- const resolvedSize = size ?? context.size;
28
- return /* @__PURE__ */ jsx(
29
- ToggleGroupPrimitive.Item,
30
- {
31
- ref,
32
- "data-slot": "toggle-group-item",
33
- "data-variant": resolvedVariant,
34
- "data-size": resolvedSize,
35
- className: cn(toggleVariants({ variant: resolvedVariant, size: resolvedSize }), className),
36
- ...props
37
- }
38
- );
39
- });
24
+ const ToggleGroupItem = React.forwardRef(
25
+ ({
26
+ className,
27
+ variant,
28
+ size,
29
+ count,
30
+ overflowCount,
31
+ showZero,
32
+ countLabel,
33
+ children,
34
+ "aria-label": ariaLabel,
35
+ ...props
36
+ }, ref) => {
37
+ const context = React.useContext(ToggleGroupContext);
38
+ const resolvedVariant = variant ?? context.variant;
39
+ const resolvedSize = size ?? context.size;
40
+ const { pill, resolvedAriaLabel } = useCounterPill({
41
+ count,
42
+ overflowCount,
43
+ showZero,
44
+ countLabel,
45
+ ariaLabel
46
+ });
47
+ return /* @__PURE__ */ jsxs(
48
+ ToggleGroupPrimitive.Item,
49
+ {
50
+ ref,
51
+ "data-slot": "toggle-group-item",
52
+ "data-variant": resolvedVariant,
53
+ "data-size": resolvedSize,
54
+ "aria-label": resolvedAriaLabel,
55
+ className: cn(toggleVariants({ variant: resolvedVariant, size: resolvedSize }), className),
56
+ ...props,
57
+ children: [
58
+ children,
59
+ pill
60
+ ]
61
+ }
62
+ );
63
+ }
64
+ );
40
65
  ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
41
66
  export {
42
67
  ToggleGroup,
@@ -3,11 +3,72 @@ import * as TogglePrimitive from "@radix-ui/react-toggle";
3
3
  import { type VariantProps } from "class-variance-authority";
4
4
  declare const toggleVariants: (props?: ({
5
5
  variant?: "default" | "outline" | null | undefined;
6
- size?: "sm" | "md" | "lg" | null | undefined;
6
+ size?: "lg" | "md" | "sm" | null | undefined;
7
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
- export type ToggleProps = React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>;
8
+ /**
9
+ * The counter-pill vocabulary, lifted VERBATIM from `Button` (`count` / `overflowCount` /
10
+ * `showZero`) so a counted filter tab and a counted, pressed filter chip are one vocabulary rather
11
+ * than two that drift (gh#312). Not exported on its own — it is folded into `ToggleProp` and
12
+ * `ToggleGroupItemProp` so there is one public name per component.
13
+ */
14
+ type ToggleCountFields = {
15
+ /**
16
+ * Optional numeric count rendered as a borderless counter pill after the label — the SAME
17
+ * vocabulary `Button` already defines, so a counted segmented toggle and a counted filter tab
18
+ * read identically. Formatted with `Intl.NumberFormat` in the active locale (never `String(n)`,
19
+ * never a hand-rolled thousands separator). Never nest a `Badge` inside a Toggle for this: a
20
+ * Badge is a status chip with its own surface, so it double-borders the chip and puts two boxes
21
+ * where there is one control. The pill takes its colour from the toggle's OWN pressed state, so
22
+ * pressed and unpressed chips differ without reading the number.
23
+ */
24
+ count?: number;
25
+ /** Cap for `count` — beyond it the pill shows `{overflowCount}+` (e.g. `99+`). Default `99`. */
26
+ overflowCount?: number;
27
+ /** Render the pill when `count` is 0. Default `true`, matching Button. */
28
+ showZero?: boolean;
29
+ /**
30
+ * Localized description of what the count MEANS, folded into the accessible name so the control
31
+ * never announces as a bare number ("👍 3" → "thumbs up, 3 reactions"). Supply it whenever the
32
+ * visible label is an icon or an emoji; with a text label the label itself already carries the
33
+ * meaning. Pass a `t()`-resolved string — the library does not own this wording.
34
+ */
35
+ countLabel?: string;
36
+ };
37
+ /**
38
+ * Counter pill shared by `Toggle` and `ToggleGroupItem`.
39
+ *
40
+ * ACCESSIBLE NAME. The digits live INSIDE the same `button[aria-pressed]` — one tab stop, one
41
+ * focus ring, one name. The name always comes out as "<label>, <count> <unit>" ("Unread, 12",
42
+ * "Thích, 3 lượt"), by one of two routes:
43
+ *
44
+ * • Name from CONTENTS (the usual text chip). The visible digits are `aria-hidden`, and an
45
+ * `sr-only` sibling carries ", 12 unit". The separator is explicit on purpose: the accessible
46
+ * name is a CONCATENATION of descendant text, and whether a browser inserts a space between
47
+ * two inline boxes is a layout detail no test can rely on — without it the control can
48
+ * announce "Unread12".
49
+ * • Name from `aria-label` (an icon or emoji chip). Contents are ignored for the name, so the
50
+ * same ", 12 unit" tail is folded into the label instead.
51
+ *
52
+ * Either way the count is announced exactly ONCE — the visible digits are hidden from the a11y
53
+ * tree precisely so they cannot be counted twice — and there is deliberately no `aria-live`: a
54
+ * count that ticks up as other people react is not this control's status, and a live region here
55
+ * would fire on every socket message. A product that wants the change announced owns its own
56
+ * live region.
57
+ */
58
+ declare function useCounterPill({ count, overflowCount, showZero, countLabel, ariaLabel, }: ToggleCountFields & {
59
+ ariaLabel?: string;
60
+ }): {
61
+ pill: null;
62
+ resolvedAriaLabel: string | undefined;
63
+ } | {
64
+ pill: React.JSX.Element;
65
+ resolvedAriaLabel: string | undefined;
66
+ };
67
+ export type ToggleProp = React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants> & ToggleCountFields;
68
+ export type ToggleProps = ToggleProp;
9
69
  export declare const Toggle: React.ForwardRefExoticComponent<Omit<TogglePrimitive.ToggleProps & React.RefAttributes<HTMLButtonElement>, "ref"> & VariantProps<(props?: ({
10
70
  variant?: "default" | "outline" | null | undefined;
11
- size?: "sm" | "md" | "lg" | null | undefined;
12
- } & import("class-variance-authority/types").ClassProp) | undefined) => string> & React.RefAttributes<HTMLButtonElement>>;
13
- export { toggleVariants };
71
+ size?: "lg" | "md" | "sm" | null | undefined;
72
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string> & ToggleCountFields & React.RefAttributes<HTMLButtonElement>>;
73
+ export { toggleVariants, useCounterPill };
74
+ export type { ToggleCountFields };
@@ -1,9 +1,10 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import * as TogglePrimitive from "@radix-ui/react-toggle";
5
5
  import { cva } from "class-variance-authority";
6
6
  import { cn } from "../../lib/utils.js";
7
+ import { useTranslation } from "../../i18n/use-translation.js";
7
8
  const toggleVariants = cva("ui-toggle", {
8
9
  variants: {
9
10
  variant: {
@@ -21,17 +22,71 @@ const toggleVariants = cva("ui-toggle", {
21
22
  size: "md"
22
23
  }
23
24
  });
24
- const Toggle = React.forwardRef(({ className, variant, size, ...props }, ref) => /* @__PURE__ */ jsx(
25
- TogglePrimitive.Root,
26
- {
27
- ref,
28
- "data-slot": "toggle",
29
- className: cn(toggleVariants({ variant, size }), className),
25
+ function useCounterPill({
26
+ count,
27
+ overflowCount = 99,
28
+ showZero = true,
29
+ countLabel,
30
+ ariaLabel
31
+ }) {
32
+ const { locale } = useTranslation();
33
+ const visible = count != null && (count !== 0 || showZero);
34
+ const formatted = React.useMemo(() => {
35
+ if (count == null) return "";
36
+ const format = new Intl.NumberFormat(locale);
37
+ return count > overflowCount ? `${format.format(overflowCount)}+` : format.format(count);
38
+ }, [count, locale, overflowCount]);
39
+ if (!visible) {
40
+ return { pill: null, resolvedAriaLabel: ariaLabel };
41
+ }
42
+ const spoken = countLabel ? `${formatted} ${countLabel}` : formatted;
43
+ return {
44
+ pill: /* @__PURE__ */ jsxs(Fragment, { children: [
45
+ /* @__PURE__ */ jsx("span", { "data-slot": "toggle-count", className: "ui-toggle-count", "aria-hidden": "true", children: formatted }),
46
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: `, ${spoken}` })
47
+ ] }),
48
+ resolvedAriaLabel: ariaLabel == null ? ariaLabel : `${ariaLabel}, ${spoken}`
49
+ };
50
+ }
51
+ const Toggle = React.forwardRef(
52
+ ({
53
+ className,
54
+ variant,
55
+ size,
56
+ count,
57
+ overflowCount,
58
+ showZero,
59
+ countLabel,
60
+ children,
61
+ "aria-label": ariaLabel,
30
62
  ...props
63
+ }, ref) => {
64
+ const { pill, resolvedAriaLabel } = useCounterPill({
65
+ count,
66
+ overflowCount,
67
+ showZero,
68
+ countLabel,
69
+ ariaLabel
70
+ });
71
+ return /* @__PURE__ */ jsxs(
72
+ TogglePrimitive.Root,
73
+ {
74
+ ref,
75
+ "data-slot": "toggle",
76
+ "aria-label": resolvedAriaLabel,
77
+ className: cn(toggleVariants({ variant, size }), className),
78
+ ...props,
79
+ children: [
80
+ children,
81
+ pill
82
+ ]
83
+ }
84
+ );
31
85
  }
32
- ));
86
+ );
33
87
  Toggle.displayName = TogglePrimitive.Root.displayName;
34
88
  export {
35
89
  Toggle,
36
- toggleVariants
90
+ toggleVariants,
91
+ useCounterPill
37
92
  };
@@ -213,6 +213,14 @@
213
213
  }
214
214
  },
215
215
  "dataDisplay": {
216
+ "avatar": {
217
+ "presence": {
218
+ "online": "Online",
219
+ "away": "Away",
220
+ "busy": "Busy",
221
+ "offline": "Offline"
222
+ }
223
+ },
216
224
  "carousel": {
217
225
  "roleDescription": "carousel",
218
226
  "ariaLabel": "Carousel",
@@ -207,6 +207,14 @@
207
207
  }
208
208
  },
209
209
  "dataDisplay": {
210
+ "avatar": {
211
+ "presence": {
212
+ "online": "オンライン",
213
+ "away": "離席中",
214
+ "busy": "取り込み中",
215
+ "offline": "オフライン"
216
+ }
217
+ },
210
218
  "carousel": {
211
219
  "roleDescription": "カルーセル",
212
220
  "ariaLabel": "カルーセル",
@@ -207,6 +207,14 @@
207
207
  }
208
208
  },
209
209
  "dataDisplay": {
210
+ "avatar": {
211
+ "presence": {
212
+ "online": "Trực tuyến",
213
+ "away": "Vắng mặt",
214
+ "busy": "Bận",
215
+ "offline": "Ngoại tuyến"
216
+ }
217
+ },
210
218
  "carousel": {
211
219
  "roleDescription": "băng chuyền",
212
220
  "ariaLabel": "Băng chuyền",
@@ -1,6 +1,20 @@
1
1
  /** Shared control sizing — reads `--control-height`, `--font-size-*` from density / theme. */
2
2
  export declare const controlFieldClass = "ui-control w-full rounded-[var(--control-radius)] border border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50";
3
3
  export declare const controlMultilineClass = "ui-control-multiline w-full rounded-[var(--control-radius)] border border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50";
4
+ /**
5
+ * Multiline control with its own chrome removed, for a textarea EMBEDDED in a surface that
6
+ * already draws the box — a chat composer inside a Card, an inline edit cell, a comment box in
7
+ * a panel. Two nested rounded borders is the tell that this was missing.
8
+ *
9
+ * These are utilities, not tokens, ON PURPOSE. `.ui-control-multiline` reads
10
+ * `--control-border-width` / `--control-shadow` from `@layer components`, but the default class
11
+ * ALSO carries Tailwind's `border` in `@layer utilities`, which wins whatever the token says —
12
+ * the same structural inertness gh#260 found on Badge's font size. So the variant cannot be a
13
+ * token override by a consumer; it has to drop the utilities here.
14
+ *
15
+ * The focus ring goes with them: the surface owns focus, via `focus-within` on the wrapper.
16
+ */
17
+ export declare const controlMultilineGhostClass = "ui-control-multiline w-full min-h-0 border-0 bg-transparent shadow-none placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50";
4
18
  /**
5
19
  * Open-state ring for popup-style triggers (Select / Cascader / TreeSelect / SearchSelect).
6
20
  * Radix moves focus INTO the popup on open, so the trigger loses `:focus-visible` and — without
@@ -1,5 +1,6 @@
1
1
  const controlFieldClass = "ui-control w-full rounded-[var(--control-radius)] border border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50";
2
2
  const controlMultilineClass = "ui-control-multiline w-full rounded-[var(--control-radius)] border border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50";
3
+ const controlMultilineGhostClass = "ui-control-multiline w-full min-h-0 border-0 bg-transparent shadow-none placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50";
3
4
  const controlOpenRingClass = "data-[state=open]:border-ring data-[state=open]:ring-ring/50 data-[state=open]:ring-[3px]";
4
5
  const controlTriggerClass = "ui-control flex w-full items-center justify-between gap-2 whitespace-nowrap rounded-[var(--control-radius)] border border-input bg-background shadow-sm transition-[color,box-shadow] focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 data-[state=open]:border-ring data-[state=open]:ring-ring/50 data-[state=open]:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1";
5
6
  const controlIconClass = "size-[length:var(--control-height)] shrink-0";
@@ -21,6 +22,7 @@ export {
21
22
  controlIconLeadingClass,
22
23
  controlIconSmClass,
23
24
  controlMultilineClass,
25
+ controlMultilineGhostClass,
24
26
  controlOpenRingClass,
25
27
  controlTriggerClass,
26
28
  tableCellPaddingClass,
@@ -13,6 +13,12 @@ import * as React from "react";
13
13
  * - `aria-errormessage` + `aria-invalid` — the validation message, announced when invalid.
14
14
  * - `aria-required` — required-field semantics.
15
15
  * - `aria-label` — a name supplied directly (used when there is no visible label element).
16
+ *
17
+ * `data-field` travels the SAME route (gh#337) and is therefore part of this bag even though it is
18
+ * not an ARIA attribute. It is the field's stable machine key — the `data-testid` role, standardised
19
+ * on one attribute so a control never has to be found by a generated id or by its visible Japanese
20
+ * label. It is inert (read-only metadata) and must land on the same semantic focus target the aria
21
+ * relationships do; routing it through a second, parallel mechanism is how a control gets missed.
16
22
  */
17
23
  export interface FieldA11yProps {
18
24
  "aria-label"?: string;
@@ -21,6 +27,8 @@ export interface FieldA11yProps {
21
27
  "aria-errormessage"?: string;
22
28
  "aria-invalid"?: React.AriaAttributes["aria-invalid"];
23
29
  "aria-required"?: React.AriaAttributes["aria-required"];
30
+ /** Stable machine key of the field (gh#337) — see the interface doc above. */
31
+ "data-field"?: string;
24
32
  }
25
33
  /**
26
34
  * Merge one or more space-separated id-reference lists (`aria-describedby`, `aria-labelledby`,
@@ -81,6 +89,49 @@ export declare const FieldNameContext: React.Context<FieldNameContextValue | nul
81
89
  * inside a FormField AND still nameless after its own props and the cloned contract are applied.
82
90
  */
83
91
  export declare function useFieldNameFallback(name: Pick<FieldA11yProps, "aria-label" | "aria-labelledby">): Pick<FieldA11yProps, "aria-label" | "aria-labelledby">;
92
+ /**
93
+ * The enclosing `FormField`, made reachable by NESTED controls (gh#337).
94
+ *
95
+ * `cloneElement` reaches FormField's single DIRECT child only. In the real screens this library
96
+ * serves, 322 of 1,410 controls (23%) sit one level deeper — the direct child is a `Flex` holding a
97
+ * from/to pair, a 年/月 combo, or a value + 「不明」 checkbox — so the machine key stopped on the
98
+ * wrapper `div` and the controls inside stayed anonymous. The customer's acceptance condition is
99
+ * 「画面に見えている入力欄の100%に付与されていること」; 77% does not pass.
100
+ *
101
+ * The key for such a control is its OWN `id`, and that is a finding, not a convention invented
102
+ * here: of those 322, **250 already carry a static id** and the ambiguous case the wrapper creates
103
+ * — two controls under one field — is already distinguished at the call site
104
+ * (`search_billing_date_from` / `..._to`, `fax` / `fax_unknown`). So a nested control names itself
105
+ * and no two controls can ever end up sharing a key. The remaining 72 (48 computed ids, 24 with no
106
+ * id at all) get NOTHING: a fabricated key is worse than a missing one, because automation would
107
+ * bind to it and break silently on the next render.
108
+ */
109
+ export interface FieldIdentityContextValue {
110
+ /** Whether the app opted into a native `name` (@see AppProviderProp.emitFieldNames). */
111
+ emitName: boolean;
112
+ }
113
+ export declare const FieldIdentityContext: React.Context<FieldIdentityContextValue | null>;
114
+ /**
115
+ * Resolve `data-field` / `name` for a control nested under a `FormField` (see
116
+ * {@link FieldIdentityContext}). Returns `{}` — adding nothing — in every case but the one it
117
+ * exists for, which is what keeps it safe to call from every control:
118
+ *
119
+ * - **outside a FormField** → `{}`. A control elsewhere on the page is untouched.
120
+ * - **`data-field` already present** → `{}`. Either `FormField` cloned it onto its direct child, or
121
+ * a composite (DatePicker, Select) already resolved this field and is passing it down. Whoever
122
+ * owns the field owns BOTH attributes, so `name` is not second-guessed here either — that is what
123
+ * keeps DatePicker's ISO mirror (`name` belongs to the hidden `yyyy-MM-dd` input, not the visible
124
+ * `yyyy/MM/dd` one) and Select's native `<select>` intact.
125
+ * - **no `id` of its own** → `{}`. Nothing to derive a stable key from; see the interface doc.
126
+ */
127
+ export declare function useFieldIdentity(own: {
128
+ id?: string;
129
+ name?: string;
130
+ "data-field"?: string;
131
+ }): {
132
+ "data-field"?: string;
133
+ name?: string;
134
+ };
84
135
  /**
85
136
  * The field-a11y attributes valid on a **group container** (`role="group"`), used by composite
86
137
  * controls that have no single labelable focus target — CheckboxGroup, and the two-input range
@@ -98,4 +149,5 @@ export declare function useFieldNameFallback(name: Pick<FieldA11yProps, "aria-la
98
149
  export declare function pickGroupFieldA11y(props: FieldA11yProps): {
99
150
  "aria-labelledby"?: string;
100
151
  "aria-describedby"?: string;
152
+ "data-field"?: string;
101
153
  };
@@ -6,7 +6,8 @@ const FIELD_A11Y_KEYS = [
6
6
  "aria-describedby",
7
7
  "aria-errormessage",
8
8
  "aria-invalid",
9
- "aria-required"
9
+ "aria-required",
10
+ "data-field"
10
11
  ];
11
12
  function mergeAriaIds(...values) {
12
13
  return Array.from(new Set(values.flatMap((value) => value?.split(/\s+/).filter(Boolean) ?? []))).join(
@@ -47,19 +48,33 @@ function useFieldNameFallback(name) {
47
48
  ...field.label !== void 0 ? { "aria-label": field.label } : {}
48
49
  };
49
50
  }
51
+ const FieldIdentityContext = React.createContext(null);
52
+ function useFieldIdentity(own) {
53
+ const ctx = React.useContext(FieldIdentityContext);
54
+ if (!ctx || own["data-field"] !== void 0 || own.id === void 0) return {};
55
+ return {
56
+ "data-field": own.id,
57
+ ...ctx.emitName && own.name === void 0 ? { name: own.id } : {}
58
+ };
59
+ }
50
60
  function pickGroupFieldA11y(props) {
51
61
  const describedBy = mergeAriaIds(props["aria-describedby"], props["aria-errormessage"]);
52
62
  return {
53
63
  ...props["aria-labelledby"] !== void 0 ? { "aria-labelledby": props["aria-labelledby"] } : {},
54
- ...describedBy !== void 0 ? { "aria-describedby": describedBy } : {}
64
+ ...describedBy !== void 0 ? { "aria-describedby": describedBy } : {},
65
+ // Not an ARIA attribute and so not subject to the role="group" restriction above — a group
66
+ // still has to be findable by its field key (gh#337).
67
+ ...props["data-field"] !== void 0 ? { "data-field": props["data-field"] } : {}
55
68
  };
56
69
  }
57
70
  export {
71
+ FieldIdentityContext,
58
72
  FieldNameContext,
59
73
  mergeAriaIds,
60
74
  omitFieldA11y,
61
75
  pickFieldA11y,
62
76
  pickGroupFieldA11y,
63
77
  resolveFieldA11y,
78
+ useFieldIdentity,
64
79
  useFieldNameFallback
65
80
  };
@@ -49,6 +49,19 @@ export type AppProviderProp = {
49
49
  * number (e.g. `0.95`) overrides it. Type is a separate axis — not scaled.
50
50
  */
51
51
  scaling?: number | null;
52
+ /**
53
+ * Emit a native `name` on every control a `FormField` wraps, taken from the field's key
54
+ * (`field` → `name` → `id`). Default `false`, and deliberately opt-in (gh#337).
55
+ *
56
+ * `data-field` is inert metadata and is always emitted; `name` is NOT — it changes what a
57
+ * native `<form>` submit sends. An app whose controls have never carried a `name` would start
58
+ * posting extra keys to its backend the moment it upgraded the library. That is a behaviour
59
+ * change no shared package may make by default, so the app that WANTS it (a screen-automation /
60
+ * RPA contract, native form posts) turns it on here, once, and every FormField in it obeys.
61
+ *
62
+ * A `name` written on the control itself always wins over the injected one.
63
+ */
64
+ emitFieldNames?: boolean;
52
65
  onLocaleChange?: (locale: AppLocale) => void;
53
66
  onTimezoneChange?: (timezone: AppTimezone) => void;
54
67
  onTimeFormatChange?: (timeFormat: AppTimeFormat) => void;
@@ -117,6 +130,8 @@ export type AppContextValue = {
117
130
  density: AppDensity;
118
131
  fontSize: AppFontSize;
119
132
  scaling: number | null;
133
+ /** Whether `FormField` injects a native `name` onto its control (gh#337). Default `false`. */
134
+ emitFieldNames: boolean;
120
135
  setLocale: (locale: AppLocale) => void;
121
136
  setTimezone: (timezone: AppTimezone) => void;
122
137
  setTimeFormat: (timeFormat: AppTimeFormat) => void;