@godxjp/ui 18.15.4 → 19.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 (145) hide show
  1. package/dist/app/date-format-labels.d.ts +1 -1
  2. package/dist/app/time-format-labels.d.ts +1 -1
  3. package/dist/components/data-display/badge.d.ts +1 -1
  4. package/dist/components/data-display/card.d.ts +4 -13
  5. package/dist/components/data-display/card.js +2 -15
  6. package/dist/components/data-display/data-table.d.ts +3 -8
  7. package/dist/components/data-display/data-table.js +106 -68
  8. package/dist/components/data-display/descriptions.js +34 -5
  9. package/dist/components/data-display/empty-state.js +1 -1
  10. package/dist/components/data-display/permission-matrix.js +7 -7
  11. package/dist/components/data-display/popover.js +2 -9
  12. package/dist/components/data-display/qr-code.d.ts +1 -1
  13. package/dist/components/data-display/scroll-area.d.ts +3 -1
  14. package/dist/components/data-display/scroll-area.js +150 -16
  15. package/dist/components/data-display/table.js +12 -3
  16. package/dist/components/data-entry/branch-scope-picker.js +4 -4
  17. package/dist/components/data-entry/calendar.js +15 -24
  18. package/dist/components/data-entry/cascader.js +67 -71
  19. package/dist/components/data-entry/checkbox.js +9 -1
  20. package/dist/components/data-entry/command.d.ts +6 -6
  21. package/dist/components/data-entry/date-picker.js +28 -15
  22. package/dist/components/data-entry/date-range-picker.js +10 -10
  23. package/dist/components/data-entry/form-field.js +7 -3
  24. package/dist/components/data-entry/index.d.ts +2 -1
  25. package/dist/components/data-entry/input.js +8 -19
  26. package/dist/components/data-entry/label.js +4 -2
  27. package/dist/components/data-entry/month-picker.js +16 -15
  28. package/dist/components/data-entry/month-range-picker.js +17 -16
  29. package/dist/components/data-entry/radio.js +6 -1
  30. package/dist/components/data-entry/search-select.js +29 -29
  31. package/dist/components/data-entry/select.js +15 -18
  32. package/dist/components/data-entry/switch.js +7 -1
  33. package/dist/components/data-entry/textarea.d.ts +56 -0
  34. package/dist/components/data-entry/textarea.js +103 -40
  35. package/dist/components/data-entry/time-picker.js +52 -52
  36. package/dist/components/data-entry/toggle-group.d.ts +1 -0
  37. package/dist/components/data-entry/toggle.d.ts +1 -1
  38. package/dist/components/data-entry/transfer.js +14 -15
  39. package/dist/components/data-entry/tree-select.js +16 -22
  40. package/dist/components/data-entry/upload-crop-dialog.js +4 -4
  41. package/dist/components/data-entry/upload.d.ts +1 -1
  42. package/dist/components/data-entry/upload.js +46 -46
  43. package/dist/components/feedback/alert.js +7 -14
  44. package/dist/components/feedback/dialog.d.ts +23 -32
  45. package/dist/components/feedback/dialog.js +16 -13
  46. package/dist/components/feedback/sheet.d.ts +1 -1
  47. package/dist/components/feedback/sheet.js +14 -14
  48. package/dist/components/feedback/skeleton.js +13 -10
  49. package/dist/components/feedback/sonner.d.ts +1 -1
  50. package/dist/components/feedback/sonner.js +17 -8
  51. package/dist/components/feedback/tooltip.js +1 -1
  52. package/dist/components/general/activity.d.ts +44 -0
  53. package/dist/components/general/activity.js +65 -0
  54. package/dist/components/general/button.d.ts +2 -2
  55. package/dist/components/general/button.js +11 -24
  56. package/dist/components/general/index.d.ts +2 -0
  57. package/dist/components/general/index.js +2 -0
  58. package/dist/components/layout/app-shell.js +5 -3
  59. package/dist/components/layout/auth-divider.d.ts +9 -2
  60. package/dist/components/layout/auth-divider.js +7 -11
  61. package/dist/components/layout/index.d.ts +1 -0
  62. package/dist/components/layout/page-container.d.ts +1 -1
  63. package/dist/components/layout/page-container.js +32 -20
  64. package/dist/components/layout/resizable.d.ts +12 -12
  65. package/dist/components/layout/separator.d.ts +1 -0
  66. package/dist/components/layout/service-role-panel.js +47 -42
  67. package/dist/components/layout/sidebar.js +13 -1
  68. package/dist/components/layout/split-pane.d.ts +12 -1
  69. package/dist/components/layout/split-pane.js +13 -4
  70. package/dist/components/navigation/app-setting-picker.js +5 -14
  71. package/dist/components/navigation/dropdown-menu.js +10 -16
  72. package/dist/components/navigation/menubar.d.ts +3 -6
  73. package/dist/components/navigation/pagination-utils.d.ts +15 -2
  74. package/dist/components/navigation/pagination-utils.js +27 -21
  75. package/dist/components/navigation/pagination.d.ts +4 -4
  76. package/dist/components/navigation/pagination.js +2 -2
  77. package/dist/components/navigation/steps.js +31 -67
  78. package/dist/components/navigation/tabs.js +17 -5
  79. package/dist/components/query/infinite-query-state.js +2 -2
  80. package/dist/components/ui/accordion.d.ts +1 -1
  81. package/dist/components/ui/avatar.d.ts +1 -1
  82. package/dist/components/ui/avatar.js +29 -10
  83. package/dist/components/ui/input-otp.d.ts +8 -6
  84. package/dist/components/ui/rating.js +1 -1
  85. package/dist/components/ui/separator.d.ts +27 -2
  86. package/dist/components/ui/separator.js +42 -11
  87. package/dist/components/ui/toggle-group.d.ts +15 -5
  88. package/dist/components/ui/toggle-group.js +43 -18
  89. package/dist/components/ui/toggle.d.ts +66 -5
  90. package/dist/components/ui/toggle.js +64 -9
  91. package/dist/i18n/messages/en.json +8 -0
  92. package/dist/i18n/messages/ja.json +8 -0
  93. package/dist/i18n/messages/vi.json +8 -0
  94. package/dist/lib/control-styles.d.ts +14 -0
  95. package/dist/lib/control-styles.js +2 -0
  96. package/dist/props/components/data-display.prop.d.ts +85 -3
  97. package/dist/props/components/data-entry.prop.d.ts +17 -0
  98. package/dist/props/components/general.prop.d.ts +52 -1
  99. package/dist/props/components/layout.prop.d.ts +137 -2
  100. package/dist/props/registry.d.ts +116 -5
  101. package/dist/props/registry.js +165 -4
  102. package/dist/props/vocabulary/index.d.ts +2 -2
  103. package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
  104. package/dist/props/vocabulary/layout.prop.d.ts +8 -0
  105. package/dist/styles/alert-layout.css +69 -3
  106. package/dist/styles/badge-layout.css +2 -2
  107. package/dist/styles/base.css +6 -0
  108. package/dist/styles/card-layout.css +25 -8
  109. package/dist/styles/control.css +1150 -29
  110. package/dist/styles/data-display-layout.css +185 -11
  111. package/dist/styles/data-entry-layout.css +352 -1
  112. package/dist/styles/density.css +2 -2
  113. package/dist/styles/dialog-layout.css +203 -4
  114. package/dist/styles/focus-ring.css +155 -0
  115. package/dist/styles/form-layout.css +50 -5
  116. package/dist/styles/index.css +1 -0
  117. package/dist/styles/layout.css +412 -23
  118. package/dist/styles/motion.css +178 -0
  119. package/dist/styles/navigation-layout.css +398 -22
  120. package/dist/styles/shell-layout.css +137 -39
  121. package/dist/styles/table-layout.css +153 -22
  122. package/dist/styles/toggle.css +59 -0
  123. package/dist/tokens/base.css +5 -0
  124. package/dist/tokens/components/activity.css +64 -0
  125. package/dist/tokens/components/badge.css +7 -0
  126. package/dist/tokens/components/card.css +14 -10
  127. package/dist/tokens/components/chart.css +1 -1
  128. package/dist/tokens/components/control.css +358 -8
  129. package/dist/tokens/components/data-display.css +75 -0
  130. package/dist/tokens/components/data-entry.css +25 -0
  131. package/dist/tokens/components/descriptions.css +22 -0
  132. package/dist/tokens/components/feedback.css +143 -4
  133. package/dist/tokens/components/form.css +16 -0
  134. package/dist/tokens/components/legal-document.css +5 -1
  135. package/dist/tokens/components/navigation.css +109 -0
  136. package/dist/tokens/components/scroll-area.css +21 -0
  137. package/dist/tokens/components/separator.css +52 -0
  138. package/dist/tokens/components/shell.css +132 -20
  139. package/dist/tokens/components/table.css +52 -3
  140. package/dist/tokens/components/toggle.css +82 -0
  141. package/dist/tokens/components/upload.css +107 -0
  142. package/dist/tokens/foundation.css +171 -10
  143. package/dist/tokens/semantic/layout.css +120 -1
  144. package/package.json +84 -68
  145. package/scripts/ui-audit.mjs +51 -2
@@ -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,
@@ -1,6 +1,6 @@
1
1
  /** Data Display component prop types — @see docs/COMPONENTS.md#data-display */
2
2
  import type * as React from "react";
3
- import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, TablePresetProp, BreakpointProp, DensityProp, ChildrenProp, ToneProp, AvatarShapeProp, HeadingLevelProp, HandlerProp, SizeProp, LabelProp, IdProp } from "../vocabulary/index.js";
3
+ import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, TablePresetProp, BreakpointProp, DensityProp, ChildrenProp, ToneProp, AvatarShapeProp, HeadingLevelProp, HandlerProp, SizeProp, LabelProp, IdProp, DescriptionsLayoutProp } from "../vocabulary/index.js";
4
4
  /** @see EmptyState */
5
5
  /**
6
6
  * Semantic intent of the EmptyState icon medallion — a subset of the shared `ToneProp` vocabulary
@@ -39,10 +39,19 @@ export type EmptyStateProp = {
39
39
  titleAs?: "h1" | "h2" | "h3" | "h4" | "p" | "div";
40
40
  className?: ClassNameProp;
41
41
  };
42
- /** @see Descriptions */
42
+ /**
43
+ * @see Descriptions — composed with `Descriptions.Item` CHILDREN, not an `items` array.
44
+ * This type had drifted from the component (it described a long-gone items-based API and was
45
+ * missing `layout`/`labelAlign` entirely, which the generated manifest already listed).
46
+ */
43
47
  export type DescriptionsProp = {
44
- items: DescriptionsItemProp[];
48
+ /** `Descriptions.Item` children — one label/value pair each. */
49
+ children: React.ReactNode;
45
50
  columns?: 1 | 2 | 3;
51
+ /** Label placement within each item. Default `vertical` (label over value). */
52
+ layout?: DescriptionsLayoutProp;
53
+ /** Label text alignment inside the label column. Applies only to `layout="horizontal"`. */
54
+ labelAlign?: "start" | "end";
46
55
  className?: ClassNameProp;
47
56
  };
48
57
  export type DescriptionsItemProp = {
@@ -63,9 +72,45 @@ export type DescriptionsItemProp = {
63
72
  export type AvatarProp = React.ComponentPropsWithoutRef<"span"> & {
64
73
  shape?: AvatarShapeProp;
65
74
  appearance?: AvatarAppearanceProp;
75
+ /**
76
+ * Presence — WHO is reachable right now, drawn as an indicator at the block-end/inline-end
77
+ * corner of the mark with a localized `sr-only` label folded into the avatar's accessible text.
78
+ * Never colour alone (WCAG 1.4.1): each value also has its own silhouette (filled · half-filled ·
79
+ * barred · hollow).
80
+ *
81
+ * OMIT the prop entirely for an entity that has no presence concept (an organization mark, a
82
+ * capability medallion) — an absent prop emits no node and no attribute, so the DOM stays
83
+ * byte-identical to today's. Pass `"offline"` for a person KNOWN to be unreachable; that is a
84
+ * different statement, exactly as `ListRow`'s omitted vs `false` `unread` is.
85
+ */
86
+ presence?: AvatarPresenceProp;
87
+ /**
88
+ * Override the localized presence text (`t("dataDisplay.avatar.presence.online")` …) when the
89
+ * product has a more precise phrasing ("In a meeting until 15:00"). Visually hidden either way —
90
+ * a presence dot never carries visible text; that is `Badge status`.
91
+ */
92
+ presenceLabel?: LabelProp;
66
93
  className?: ClassNameProp;
67
94
  children?: ChildrenProp;
68
95
  };
96
+ /**
97
+ * Avatar presence status — a person's realtime reachability.
98
+ *
99
+ * A DELIBERATELY separate vocabulary from the lifecycle `BadgeStatusProp`: presence is volatile,
100
+ * per-person and pushed over a socket, while a lifecycle status is a record's state and renders as
101
+ * a labelled chip. Each value is encoded twice over — a semantic role colour AND a shape — so the
102
+ * four are told apart in greyscale, by a deuteranope and under forced colors:
103
+ *
104
+ * - `"online"` — filled disc (`--success`).
105
+ * - `"away"` — half-filled disc (`--warning`).
106
+ * - `"busy"` — filled disc cut by a horizontal bar, the do-not-disturb mark (`--destructive`).
107
+ * - `"offline"` — hollow ring (`--muted-foreground`).
108
+ *
109
+ * Retune every constant with `--avatar-presence-*`.
110
+ *
111
+ * @see Avatar
112
+ */
113
+ export type AvatarPresenceProp = "online" | "away" | "busy" | "offline";
69
114
  /**
70
115
  * Avatar fill treatment.
71
116
  *
@@ -295,3 +340,40 @@ export type PermissionMatrixProp = {
295
340
  className?: ClassNameProp;
296
341
  id?: IdProp;
297
342
  };
343
+ /**
344
+ * Edge the ScrollArea viewport sticks to as its content grows (gh#311).
345
+ *
346
+ * - `none` — the scroll offset is left entirely alone. This is the default and is exactly the
347
+ * behaviour a ScrollArea has always had.
348
+ * - `bottom` — a live stream (chat, log tail, streaming response, activity feed). While the reader
349
+ * is within `anchorOffset` of the bottom, arriving content keeps the newest item in view; once
350
+ * they scroll away to read history, growth NEVER moves them, and content inserted ABOVE the
351
+ * read position is compensated so the item under their eyes stays put.
352
+ */
353
+ export type ScrollAreaAnchorProp = "none" | "bottom";
354
+ /** @see ScrollArea */
355
+ export type ScrollAreaProp = {
356
+ /**
357
+ * Ref to the element that actually SCROLLS — the Radix viewport — not the root. The root is
358
+ * `overflow: hidden` and never scrolls, so the component's own `ref` cannot serve. Use this to
359
+ * read `scrollTop`/`scrollHeight`, call `scrollTo()`, restore a saved position, or drive a
360
+ * "jump to newest" button. It is the typed, supported alternative to querying the Radix-internal
361
+ * `[data-radix-scroll-area-viewport]` attribute, which is not a public contract and is ambiguous
362
+ * the moment two ScrollAreas nest.
363
+ */
364
+ viewportRef?: React.Ref<HTMLDivElement>;
365
+ /** Edge the viewport sticks to as content grows. Default `none` (inert). */
366
+ anchor?: ScrollAreaAnchorProp;
367
+ /**
368
+ * Distance in px from the bottom edge inside which the reader still counts as "at the bottom"
369
+ * for `anchor="bottom"`. Defaults to the `--scroll-area-anchor-offset` token (3rem), read off
370
+ * the element at mount so a theme — or a `[data-tenant]` scope — moves it globally.
371
+ */
372
+ anchorOffset?: number;
373
+ /**
374
+ * Fires when the pinned state flips: `false` when the reader scrolls away from the bottom,
375
+ * `true` when they come back inside `anchorOffset`. Render a focusable "jump to newest" button
376
+ * from it — anchoring must never be the only route back to new content.
377
+ */
378
+ onAnchoredChange?: (anchored: boolean) => void;
379
+ };
@@ -40,6 +40,14 @@ export type TextareaProp = React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
40
40
  allowClear?: boolean;
41
41
  /** Called after the field is cleared via the inline ✕. */
42
42
  onClear?: () => void;
43
+ /** `ghost` drops the field's own border/background/ring for a textarea inside a surface that already draws the box. */
44
+ variant?: "default" | "ghost";
45
+ /** Grow the box with its content between `minRows` and `maxRows` instead of a fixed `rows` height (default false). */
46
+ autoGrow?: boolean;
47
+ /** Floor in text rows while `autoGrow`; never undercuts the `--control-height` tier. */
48
+ minRows?: number;
49
+ /** Ceiling in text rows while `autoGrow` — past it the control scrolls internally. `0` = unbounded. */
50
+ maxRows?: number;
43
51
  };
44
52
  /**
45
53
  * @see NumberInput — localized numeric spinbutton (composes `Input` + step `Button`s).
@@ -529,6 +537,15 @@ export type UploadProp = FieldA11yProps & {
529
537
  * @see Button — same size scale
530
538
  */
531
539
  triggerSize?: "default" | "md" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg";
540
+ /**
541
+ * `variant="button"` only — the visual weight of the visible trigger, forwarded to Button.
542
+ * Defaults to `outline`, which is right for a standalone form field. Pass `ghost` when the
543
+ * trigger sits in a toolbar row beside other icon buttons — inside a chat composer, say —
544
+ * where a bordered square reads as the odd one out.
545
+ *
546
+ * @see Button — same variant scale
547
+ */
548
+ triggerVariant?: "default" | "destructive" | "outline" | "dashed" | "secondary" | "ghost" | "link";
532
549
  className?: ClassNameProp;
533
550
  children?: React.ReactNode;
534
551
  };
@@ -1,6 +1,6 @@
1
1
  /** Foundation component prop types — @see docs/COMPONENTS.md#foundation */
2
2
  import type * as React from "react";
3
- import type { AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DisabledProp, FontWeightProp, HeadingLevelProp, OnClickProp, PendingProp, RevealDelayProp, ShapeProp, TextAlignProp, TextSizeProp, TextToneProp } from "../vocabulary/index.js";
3
+ import type { ActivityAnnounceProp, ActivityVariantProp, AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DisabledProp, FontWeightProp, HeadingLevelProp, LabelProp, OnClickProp, PendingProp, RevealDelayProp, ShapeProp, SizeProp, TextAlignProp, TextSizeProp, TextToneProp } from "../vocabulary/index.js";
4
4
  /** @see Text — typographic primitive; replaces hand-rolled `<span className="text-[13px] …">`. */
5
5
  export type TextProp = Omit<React.HTMLAttributes<HTMLElement>, "color"> & {
6
6
  /** Render element. Default `span`. Covers inline/block text + description-list + code/caption. */
@@ -100,3 +100,54 @@ export type RevealProp = React.HTMLAttributes<HTMLDivElement> & {
100
100
  asChild?: AsChildProp;
101
101
  className?: ClassNameProp;
102
102
  };
103
+ /**
104
+ * @see Activity — the official AMBIENT-motion primitive (a continuous, unbounded "in progress"),
105
+ * the LOOP counterpart to `Reveal`'s one-shot entrance. Reads the DS motion tokens
106
+ * (`--activity-interval`, `--activity-stagger-step`, `--ease-standard`, `--activity-mark-offset`)
107
+ * so a consumer never hand-rolls a looping `@keyframes`. Under `prefers-reduced-motion` the loop is
108
+ * dropped and the mark renders in a static, fully-visible resting state — three solid dots, a solid
109
+ * pulse mark, or a bar segment parked at the reading-start — with no layout shift, the same
110
+ * guarantee `Reveal` gives.
111
+ *
112
+ * NOT `Skeleton` (content is loading — `aria-busy` + an unconditional live region), NOT
113
+ * `Button loading` (this action is in flight). `Activity` means: something is happening,
114
+ * indefinitely, elsewhere — someone typing, a sync running, a response streaming, a recording live.
115
+ *
116
+ * There is deliberately no `asChild`: unlike `Reveal` (which owns no DOM), `Activity` renders its
117
+ * own mark structure, so there is no single child to merge onto.
118
+ */
119
+ export type ActivityProp = Omit<React.HTMLAttributes<HTMLSpanElement>, "color"> & {
120
+ /**
121
+ * The mark. Default `dots` — three dots rising in sequence, the ellipsis convention.
122
+ * `pulse` is a single breathing mark (live / recording); `bar` an indeterminate sweep (syncing).
123
+ */
124
+ variant?: ActivityVariantProp;
125
+ /**
126
+ * Size step, the standard ladder. Default `sm` — an ambient mark is never the loudest thing on
127
+ * screen. Scales the mark AND the label together (the mark is `em`-based).
128
+ */
129
+ size?: SizeProp;
130
+ /** Semantic colour intent. Default `muted` — ambient, not an alert. */
131
+ tone?: TextToneProp;
132
+ /**
133
+ * Localized description of WHAT is happening ("Hưng đang nhập…", "同期中…"). Rendered as visible
134
+ * `Text` beside the mark when `children` are absent; when `children` ARE present it becomes an
135
+ * `sr-only` description instead, so the indicator is never animation-only. Consumer-owned copy —
136
+ * the library never invents it, and the plural form of "N people are typing" must come from the
137
+ * consumer's `Intl.PluralRules`.
138
+ */
139
+ label?: LabelProp;
140
+ /**
141
+ * Richer visible content in place of `label` (a name in a `<strong>`, a `Badge`, …). The mark
142
+ * stays `aria-hidden`; pass `label` alongside for the sr-only description.
143
+ */
144
+ children?: ChildrenProp;
145
+ /**
146
+ * Announce the label to assistive technology. Default `false` — the DELIBERATE default, because
147
+ * an ambient indicator that fires a live region on every socket event is a screen-reader flood.
148
+ * With `false` NO live region is emitted at all. `"polite"` wraps ONLY the label in a single
149
+ * `aria-live="polite" aria-atomic="true"` region; the mark stays outside it and `aria-hidden`.
150
+ */
151
+ announce?: ActivityAnnounceProp;
152
+ className?: ClassNameProp;
153
+ };
@@ -1,7 +1,7 @@
1
1
  /** Layout component prop types — @see docs/COMPONENTS.md#layout */
2
2
  import type * as React from "react";
3
3
  import type { ComponentType, ReactNode, SVGProps } from "react";
4
- import type { BreadcrumbProp, TitleProp, SubtitleProp, StatusProp, ExtraProp, FooterProp, PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, BreakpointProp, GapProp, ClassNameProp, ChildrenProp, IdProp, DisabledProp, DescriptionProp, ActionProp, IconProp, HeadingLevelProp } from "../vocabulary/index.js";
4
+ import type { BreadcrumbProp, TitleProp, SubtitleProp, StatusProp, ExtraProp, FooterProp, PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, OrientationProp, TextAlignProp, TextToneProp, BreakpointProp, GapProp, ClassNameProp, ChildrenProp, IdProp, DisabledProp, DescriptionProp, ActionProp, IconProp, HeadingLevelProp, ToneProp } from "../vocabulary/index.js";
5
5
  import type { EmptyStateToneProp } from "./data-display.prop.js";
6
6
  /**
7
7
  * Arrangement of the page header's title band and its `extra` slot below the 640px step.
@@ -24,6 +24,22 @@ export type PageContainerPresetProp = "default" | "admin-collection";
24
24
  * fluid at the compact gutter.
25
25
  */
26
26
  export type PageContainerMeasureProp = "default" | "narrow" | "medium";
27
+ /**
28
+ * What the page's top row IS — the question that decides its type step, not how big you want it.
29
+ *
30
+ * `document` (default) — the row is the page's TITLE: a record, a form, a collection, a report.
31
+ * The `<h1>` takes `--page-title-font-size` and the existing responsive step down at 720px.
32
+ *
33
+ * `chrome` — the row is the surface's own furniture: a chat channel name, a mail subject line, an
34
+ * IDE tab, a conversation header. It names the thing you are already inside rather than announcing
35
+ * a document, so it takes the body type step (`--page-title-font-size-chrome`) and the header band
36
+ * stops competing with the content underneath. Orthogonal to `PageContainerVariantProp`: `ghost`
37
+ * owns the page's chrome WEIGHT (no divider, no header bottom pad), this owns what the title MEANS
38
+ * — a chat page usually wants both, a quiet document feed wants only `ghost`.
39
+ *
40
+ * The `<h1>` stays an `<h1>` either way; only the type step moves, and only via a token.
41
+ */
42
+ export type PageContainerHeaderScaleProp = "document" | "chrome";
27
43
  /** @see PageContainer */
28
44
  export type PageContainerProp = {
29
45
  title: TitleProp;
@@ -47,6 +63,18 @@ export type PageContainerProp = {
47
63
  */
48
64
  headerLoading?: boolean;
49
65
  extra?: ExtraProp;
66
+ /**
67
+ * FIXED chrome band between the page header and the scrolling body — a filter strip, a status
68
+ * bar, a "channel workflow" rail. It is a first-class page-chrome slot precisely because the
69
+ * only alternative was hand-laying `position: sticky` at the call site (which the design system
70
+ * forbids) or putting the strip inside the body, where it scrolls away. Under `fill` the body
71
+ * IS the scroll viewport, so this band is a plain `flex: none` sibling OUTSIDE it — content can
72
+ * never travel underneath it the way it does under a sticky box. Shares the page gutters and
73
+ * the `measure` cap with the header and the body, so the three bands line up on both edges; its
74
+ * inset and bottom rule are token-owned (`--page-toolbar-pad-block` / `--page-toolbar-divider`).
75
+ * Omit it and NOTHING is rendered — no wrapper element, no gap.
76
+ */
77
+ toolbar?: ReactNode;
50
78
  footer?: FooterProp;
51
79
  breadcrumb?: BreadcrumbProp;
52
80
  /**
@@ -75,6 +103,37 @@ export type PageContainerProp = {
75
103
  * `--page-header-extra-measure`. At >=640px both arrangements are identical.
76
104
  */
77
105
  headerLayout?: PageContainerHeaderLayoutProp;
106
+ /**
107
+ * Whether the page's top row is a DOCUMENT TITLE or the surface's own CHROME. Defaults to
108
+ * `document` — the historical page, byte-identical (no attribute is emitted at all). Pass
109
+ * `chrome` when the row names the thing the user is already inside rather than announcing a
110
+ * document: a chat channel, a mail thread, an IDE tab. The `<h1>` then takes the body type step
111
+ * (`--page-title-font-size-chrome`) at EVERY width — including below 720px, where the
112
+ * document-scale responsive step would otherwise pull it back UP — so the header band stops
113
+ * eating the height the conversation needs (measured in a consumer chat page: a 61px band with a
114
+ * 24px name, against a design that wanted ~40px at the `sm` step).
115
+ *
116
+ * The same answer also puts the band ON the frame's edge: the container's block-start padding
117
+ * becomes `--page-pad-block-start-chrome` (0) instead of `--space-page-active-y`, because a
118
+ * document title needs air above it and a channel head IS the top edge (measured on a consumer
119
+ * chat screen as 24px that pushed the head off y=0 and came off the transcript viewport). The
120
+ * page's bottom edge is untouched, that being `stickyFooter`'s.
121
+ *
122
+ * It carries two more consequences of the same fact. The SUBTITLE drops to
123
+ * `--page-subtitle-font-size-chrome` (`--font-size-2xs`, ≈11px): a caption on chrome, not a
124
+ * document's standfirst, and at the document step it was rendering at the very same size as the
125
+ * chrome title, which is not a hierarchy. And the `extra` cluster CENTRES on the bar
126
+ * (`align-self: center`) wherever the header row is a row (>=640px), because top-packing actions
127
+ * is a document behaviour — they belong on the first line of a tall `<h1>` — and a bar has no
128
+ * tall heading to align to. Measured on a consumer chat screen, that was 8.65px: 28px icon
129
+ * buttons pinned at y=14 inside a 45.3px row whose title block centred at y=22.65.
130
+ *
131
+ * The heading stays an `<h1>` throughout, so the screen-reader outline is unchanged. Compose it
132
+ * with `variant="ghost"` for the full quiet chrome header (ghost drops the divider and the
133
+ * header's bottom pad); the two are separate props because chrome WEIGHT and what the title
134
+ * MEANS are separate questions.
135
+ */
136
+ headerScale?: PageContainerHeaderScaleProp;
78
137
  /**
79
138
  * Bounded page measure shared by the header and the body. Defaults to `default` — no cap, the
80
139
  * historical fluid page. `narrow` (624px surface) / `medium` (720px surface) cap BOTH bands to
@@ -314,7 +373,52 @@ export type AuthShellProp = {
314
373
  density?: "comfortable" | "compact";
315
374
  className?: ClassNameProp;
316
375
  };
317
- /** @see AuthDivider */
376
+ /**
377
+ * @see Separator — the tokenized rule, optionally INTERRUPTED by a localized label.
378
+ *
379
+ * With no `label` the DOM, the `data-slot="separator"` and the `.ui-separator` class are exactly
380
+ * what they have always been: an inert Radix rule, `decorative` by default, nothing announced.
381
+ * With a `label` the root becomes the three-cell grid `rule · label · rule` and, because
382
+ * "new messages" is CONTENT rather than decoration, a real `role="separator"` carrying the label
383
+ * as its accessible name (gh#308). Every constant — rule weight, label gap, label inset, label
384
+ * type ramp, rule and label colour per tone — is a `--separator-*` component token (rules #44/#45),
385
+ * so a service retunes a day divider or an unread watermark from its theme and never forks CSS.
386
+ */
387
+ export type SeparatorProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
388
+ /** Rule axis. Default `"horizontal"`. */
389
+ orientation?: OrientationProp;
390
+ /**
391
+ * Localized text that INTERRUPTS the rule — a day divider, a "new messages" watermark, an auth
392
+ * conjunction. The rule splits into two halves around it and the grid keeps the label optically
393
+ * placed regardless of translation length. `horizontal` only: with `orientation="vertical"` the
394
+ * label is ignored and dev builds warn. Omit for a plain rule.
395
+ *
396
+ * A `string`, not the `LabelProp` ReactNode, because this text IS the separator's accessible
397
+ * name — a node cannot be announced. The library never invents the copy: the consumer passes a
398
+ * `t()` string, and a date is formatted with `Intl.DateTimeFormat` on the active locale.
399
+ */
400
+ label?: string;
401
+ /**
402
+ * Where the label sits on the rule. Default `"center"` (the classic conjunction). `"start"` is
403
+ * the Slack/Mattermost stream convention — the label hugs the inline-start edge with the long
404
+ * half of the rule running to the inline-end. Logical, so it flips under `dir="rtl"`.
405
+ */
406
+ labelAlign?: TextAlignProp;
407
+ /**
408
+ * Semantic emphasis of the label AND the rule together — `"default"` is the quiet chrome (#44);
409
+ * a semantic role marks an attention rule such as an unread watermark. Never colour-only: the
410
+ * tone re-points both halves, so the distinction survives forced-colors. Default `"default"`.
411
+ */
412
+ tone?: TextToneProp;
413
+ /**
414
+ * `true` (the default for an UNLABELLED rule) keeps Radix's decorative behaviour — `role="none"`,
415
+ * nothing announced. A `label` flips the default to `false`, so the rule becomes a real
416
+ * `role="separator"` named by the label. Pass it explicitly to override either default.
417
+ */
418
+ decorative?: boolean;
419
+ className?: ClassNameProp;
420
+ };
421
+ /** @see AuthDivider — the auth-scoped preset over `Separator label` (gh#308). */
318
422
  export type AuthDividerProp = {
319
423
  /** Short localized conjunction rendered between the two separator rules (for example, "or"). */
320
424
  label: string;
@@ -528,6 +632,22 @@ export type SidebarProductProp = {
528
632
  role?: string;
529
633
  color?: string;
530
634
  };
635
+ /**
636
+ * What a nav row's count MEANS — a subset of the shared `ToneProp` vocabulary, not a palette.
637
+ *
638
+ * `neutral` (default) — a plain count: unread items, pending rows, queued jobs. The pill keeps the
639
+ * quiet `--sidebar-badge-background` / `-foreground` pair it always had.
640
+ *
641
+ * `destructive` — the count is ADDRESSED TO THE USER and the rail should pull the eye: an
642
+ * @mention, a direct message, a failing job awaiting them. Reads the
643
+ * `--sidebar-badge-destructive-*` pair.
644
+ *
645
+ * Deliberately TWO values, not the whole `ToneProp` union: a navigation rail answers one question
646
+ * about a count — "does this need me personally?" — and a five-colour rail is decoration, not
647
+ * information. Colour ONLY: the pill's geometry is shared, so a mention row and an unread row stay
648
+ * aligned in the same column.
649
+ */
650
+ export type SidebarBadgeToneProp = Extract<ToneProp, "neutral" | "destructive">;
531
651
  /** @see Sidebar */
532
652
  export type SidebarItemProp = {
533
653
  id: string;
@@ -540,7 +660,22 @@ export type SidebarItemProp = {
540
660
  * `--sidebar-nav-icon-foreground` (see {@link SidebarProp}).
541
661
  */
542
662
  icon: ComponentType<SVGProps<SVGSVGElement>>;
663
+ /**
664
+ * Count/status affix rendered in the row's `.sb-badge` pill. Pass the CONTENT ONLY — a number, a
665
+ * string, `"9+"`. Never a `<Badge>`: the row already IS a badge, so nesting one produces two
666
+ * stacked pills (measured: a 37.11×19.14 `.sb-badge` wrapping a 25.11×19.14 `<Badge>` with its
667
+ * own border). To change what the count MEANS, use {@link SidebarItemProp.badgeTone}.
668
+ */
543
669
  badge?: ReactNode;
670
+ /**
671
+ * Emphasis of `badge`. Defaults to `neutral` — the historical pill, byte-identical (no attribute
672
+ * is emitted at all). Pass `destructive` when the count is addressed to the user rather than
673
+ * merely unread: an @mention, a direct message, a failure waiting on them. It moves two colour
674
+ * tokens and nothing else, so mention rows and unread rows still line up.
675
+ *
676
+ * Ignored when `badge` is absent, and on the collapsed rail (which hides `.sb-badge` entirely).
677
+ */
678
+ badgeTone?: SidebarBadgeToneProp;
544
679
  disabled?: boolean;
545
680
  /**
546
681
  * Destination of the row. It is the SOLE interactive element (no nested `<button>`), so