@godxjp/ui 27.8.0 → 27.10.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 (53) hide show
  1. package/dist/components/data-display/range-timeline.d.ts +19 -0
  2. package/dist/components/data-display/range-timeline.js +2 -0
  3. package/dist/components/data-display/tree.js +2 -0
  4. package/dist/components/data-entry/upload.d.ts +1 -1
  5. package/dist/components/data-entry/upload.js +19 -16
  6. package/dist/components/general/button.d.ts +1 -0
  7. package/dist/components/general/button.js +10 -2
  8. package/dist/components/layout/app-shell.d.ts +1 -1
  9. package/dist/components/layout/app-shell.js +8 -2
  10. package/dist/components/layout/page-container.d.ts +1 -1
  11. package/dist/components/layout/page-container.js +12 -2
  12. package/dist/components/layout/topbar.d.ts +1 -1
  13. package/dist/components/layout/topbar.js +2 -0
  14. package/dist/components/ui/toggle-group.d.ts +4 -0
  15. package/dist/components/ui/toggle-group.js +10 -2
  16. package/dist/components/ui/toggle.d.ts +4 -2
  17. package/dist/components/ui/toggle.js +22 -3
  18. package/dist/contracts/measurement.json +1 -1
  19. package/dist/props/components/data-display.prop.d.ts +11 -0
  20. package/dist/props/components/data-entry.prop.d.ts +22 -3
  21. package/dist/props/components/general.prop.d.ts +12 -0
  22. package/dist/props/components/index.d.ts +1 -1
  23. package/dist/props/components/layout.prop.d.ts +60 -1
  24. package/dist/props/registry.d.ts +27 -6
  25. package/dist/props/registry.js +42 -3
  26. package/dist/styles/control.css +17 -7
  27. package/dist/styles/data-display-layout.css +25 -7
  28. package/dist/styles/data-entry-layout.css +13 -0
  29. package/dist/styles/layout.css +8 -0
  30. package/dist/styles/shell-layout.css +43 -1
  31. package/dist/styles/toggle.css +5 -0
  32. package/dist/tokens/components/control.css +5 -2
  33. package/dist/tokens/components/data-display.css +10 -1
  34. package/dist/tokens/components/shell.css +3 -0
  35. package/dist/tokens/components/toggle.css +7 -0
  36. package/dist/tokens/components/tree.css +2 -0
  37. package/docs/DESIGN-AUTHORITY.md +20 -0
  38. package/docs/FRAME-COVERAGE-REPORT.md +1 -1
  39. package/docs/data-display/timeline.tsx +77 -1
  40. package/docs/data-display/tree.tsx +94 -0
  41. package/docs/data-entry/calendar.tsx +3 -2
  42. package/docs/data-entry/toggle-count.tsx +51 -0
  43. package/docs/data-entry/toggle-group.tsx +40 -2
  44. package/docs/data-entry/toggle.tsx +54 -2
  45. package/docs/data-entry/upload.tsx +44 -0
  46. package/docs/general/button/index.tsx +18 -6
  47. package/docs/general/icon.tsx +3 -1
  48. package/docs/layout/app-shell-narrow-bar.tsx +205 -0
  49. package/docs/layout/app-shell.tsx +10 -0
  50. package/docs/layout/page-container.tsx +67 -1
  51. package/docs/layout/topbar.tsx +11 -1
  52. package/package.json +2 -2
  53. package/scripts/cli.mjs +3 -0
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import type { DensityProp } from "../../props/vocabulary/index.js";
2
3
  export type RangeTimelineRow = {
3
4
  id: string;
4
5
  label: React.ReactNode;
@@ -34,6 +35,15 @@ export type RangeTimelineProps = React.HTMLAttributes<HTMLElement> & {
34
35
  * the header-only ruling. Colour `--range-timeline-grid-color`, weight `--range-timeline-grid-width`.
35
36
  */
36
37
  bordered?: boolean;
38
+ /**
39
+ * How wide ONE axis unit is — the canonical density vocabulary, the same three steps
40
+ * `DataTable density` uses. `default` (56px/day) is the shipped width, so an existing Gantt
41
+ * does not move; `compact` (42px/day) fits a third more days on the same screen; `comfortable`
42
+ * (70px/day) spreads them out. It moves the column width ONLY — row height, bar height and the
43
+ * label column are identical at every step. Retune a step with
44
+ * `--range-timeline-unit-width-{compact,default,comfortable}`.
45
+ */
46
+ density?: DensityProp;
37
47
  /**
38
48
  * Controlled ids of the EXPANDED parent rows (same spelling as `Tree expandedValues`). A folded
39
49
  * parent hides every descendant row — label and bar.
@@ -66,6 +76,15 @@ export declare const RangeTimeline: React.ForwardRefExoticComponent<React.HTMLAt
66
76
  * the header-only ruling. Colour `--range-timeline-grid-color`, weight `--range-timeline-grid-width`.
67
77
  */
68
78
  bordered?: boolean;
79
+ /**
80
+ * How wide ONE axis unit is — the canonical density vocabulary, the same three steps
81
+ * `DataTable density` uses. `default` (56px/day) is the shipped width, so an existing Gantt
82
+ * does not move; `compact` (42px/day) fits a third more days on the same screen; `comfortable`
83
+ * (70px/day) spreads them out. It moves the column width ONLY — row height, bar height and the
84
+ * label column are identical at every step. Retune a step with
85
+ * `--range-timeline-unit-width-{compact,default,comfortable}`.
86
+ */
87
+ density?: DensityProp;
69
88
  /**
70
89
  * Controlled ids of the EXPANDED parent rows (same spelling as `Tree expandedValues`). A folded
71
90
  * parent hides every descendant row — label and bar.
@@ -15,6 +15,7 @@ const RangeTimeline = React.forwardRef(
15
15
  today,
16
16
  onRangeChange,
17
17
  bordered = true,
18
+ density = "default",
18
19
  expandedValues,
19
20
  defaultExpandedValues,
20
21
  onExpandedValuesChange,
@@ -92,6 +93,7 @@ const RangeTimeline = React.forwardRef(
92
93
  ref,
93
94
  className: cn("ui-range-timeline", className),
94
95
  "data-bordered": bordered ? "true" : void 0,
96
+ "data-density": density === "default" ? void 0 : density,
95
97
  "aria-label": label,
96
98
  tabIndex: 0,
97
99
  children: /* @__PURE__ */ jsxs(
@@ -64,6 +64,7 @@ function TreeRoot({
64
64
  titleRender,
65
65
  showLine = false,
66
66
  showIcon = false,
67
+ divided = false,
67
68
  variant = "default",
68
69
  size = "md",
69
70
  disabled = false,
@@ -411,6 +412,7 @@ function TreeRoot({
411
412
  "data-size": size,
412
413
  "data-variant": variant,
413
414
  "data-show-line": showLine ? "true" : void 0,
415
+ "data-divided": divided ? "true" : void 0,
414
416
  "data-empty": isEmpty ? "true" : void 0,
415
417
  className: cn("ui-tree", className),
416
418
  children: isEmpty ? null : renderNodes(options, 0)
@@ -4,4 +4,4 @@ export type { UploadProp, UploadProp as UploadProps, UploadFileItemProp, UploadV
4
4
  export type { UploadFileItem, UploadVariant, UploadCommitAction } from "./upload-types.js";
5
5
  export { collectUploadCommitActions, createUploadItem, UPLOAD_LIST_IGNORE } from "./upload-types.js";
6
6
  export { useUploadDraft } from "./use-upload-draft.js";
7
- export declare function Upload({ variant, listType, triggerSize, triggerVariant, value, defaultValue, onValueChange, accept: acceptProp, multiple: multipleProp, maxCount: maxCountProp, maxSizeBytes, disabled: disabledProp, readOnly, directory, pastable, openFileDialogOnClick, name, action, method, headers, data, withCredentials, beforeUpload, onReject, onRemove, onPreview, onDownload, previewFile, onDrop, showUploadList, itemRender, removable, onUpload, id, className, children, ...ariaProps }: UploadProp): React.JSX.Element;
7
+ export declare function Upload({ variant, listType, triggerSize, triggerVariant, triggerIcon: TriggerIcon, value, defaultValue, onValueChange, accept: acceptProp, multiple: multipleProp, maxCount: maxCountProp, maxSizeBytes, disabled: disabledProp, readOnly, directory, pastable, openFileDialogOnClick, name, action, method, headers, data, withCredentials, beforeUpload, onReject, onRemove, onPreview, onDownload, previewFile, onDrop, showUploadList, itemRender, removable, onUpload, id, className, children, ...ariaProps }: UploadProp): React.JSX.Element;
@@ -70,6 +70,7 @@ function Upload({
70
70
  listType,
71
71
  triggerSize,
72
72
  triggerVariant = "outline",
73
+ triggerIcon: TriggerIcon = UploadIcon,
73
74
  value,
74
75
  defaultValue,
75
76
  onValueChange,
@@ -498,7 +499,7 @@ function Upload({
498
499
  "aria-label": triggerAriaLabel,
499
500
  children: [
500
501
  /* @__PURE__ */ jsx(
501
- UploadIcon,
502
+ TriggerIcon,
502
503
  {
503
504
  className: "ui-upload-trigger-icon",
504
505
  "data-with-label": iconOnly ? void 0 : "",
@@ -772,21 +773,23 @@ function UploadFileList({
772
773
  ] }),
773
774
  item.status === "uploading" && /* @__PURE__ */ jsx(Progress, { value: item.percent ?? 0, "aria-label": t("dataEntry.upload.uploading") })
774
775
  ] }),
775
- onPreview && (hasCustomPreview || item.previewUrl) && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onPreview(item), children: t("dataEntry.upload.preview") }),
776
- onDownload && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onDownload(item), children: t("dataEntry.upload.download") }),
777
- onStart && item.file && item.status !== "uploading" && item.status !== "done" && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onStart(item), children: t(item.status === "error" ? "dataEntry.upload.retry" : "dataEntry.upload.start") }),
778
- onCancel && item.status === "uploading" && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onCancel(item.uid), children: t("dataEntry.upload.cancel") }),
779
- onRemove && /* @__PURE__ */ jsx(
780
- Button,
781
- {
782
- type: "button",
783
- size: "sm",
784
- variant: "ghost",
785
- "aria-label": t("dataEntry.upload.removeFile", { name: item.name }),
786
- onClick: () => onRemove(item.uid),
787
- children: /* @__PURE__ */ jsx(X, { className: "ui-upload-row-icon", "aria-hidden": "true" })
788
- }
789
- )
776
+ /* @__PURE__ */ jsxs("div", { className: "ui-upload-row-actions", children: [
777
+ onPreview && (hasCustomPreview || item.previewUrl) && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onPreview(item), children: t("dataEntry.upload.preview") }),
778
+ onDownload && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onDownload(item), children: t("dataEntry.upload.download") }),
779
+ onStart && item.file && item.status !== "uploading" && item.status !== "done" && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onStart(item), children: t(item.status === "error" ? "dataEntry.upload.retry" : "dataEntry.upload.start") }),
780
+ onCancel && item.status === "uploading" && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => onCancel(item.uid), children: t("dataEntry.upload.cancel") }),
781
+ onRemove && /* @__PURE__ */ jsx(
782
+ Button,
783
+ {
784
+ type: "button",
785
+ size: "sm",
786
+ variant: "ghost",
787
+ "aria-label": t("dataEntry.upload.removeFile", { name: item.name }),
788
+ onClick: () => onRemove(item.uid),
789
+ children: /* @__PURE__ */ jsx(X, { className: "ui-upload-row-icon", "aria-hidden": "true" })
790
+ }
791
+ )
792
+ ] })
790
793
  ] });
791
794
  return /* @__PURE__ */ jsx("li", { children: itemRender ? itemRender(node, item, items, {
792
795
  remove: () => onRemove?.(item.uid),
@@ -21,5 +21,6 @@ export declare const Button: React.ForwardRefExoticComponent<React.ButtonHTMLAtt
21
21
  count?: number;
22
22
  overflowCount?: number;
23
23
  showZero?: boolean;
24
+ countLabel?: string;
24
25
  } & React.RefAttributes<HTMLButtonElement>>;
25
26
  export { buttonVariants };
@@ -71,6 +71,7 @@ const Button = React.forwardRef(
71
71
  count,
72
72
  overflowCount = 99,
73
73
  showZero = true,
74
+ countLabel,
74
75
  disabled,
75
76
  type,
76
77
  children,
@@ -84,8 +85,14 @@ const Button = React.forwardRef(
84
85
  loadingText ?? children
85
86
  ] }) : children;
86
87
  const showCount = !asChild && count != null && (count !== 0 || showZero);
87
- const countLabel = showCount && count != null && count > overflowCount ? `${numberFormat(locale).format(overflowCount)}+` : count != null ? numberFormat(locale).format(count) : "";
88
- const countNode = showCount ? /* @__PURE__ */ jsx("span", { "data-slot": "button-count", className: "ui-button-count", children: countLabel }) : null;
88
+ const countText = showCount && count != null && count > overflowCount ? `${numberFormat(locale).format(overflowCount)}+` : count != null ? numberFormat(locale).format(count) : "";
89
+ const spokenCount = countLabel ? `${countText} ${countLabel}` : countText;
90
+ const ariaLabel = props["aria-label"];
91
+ const foldedAriaLabel = showCount && ariaLabel != null ? { "aria-label": `${ariaLabel}, ${spokenCount}` } : null;
92
+ const countNode = showCount ? /* @__PURE__ */ jsxs(Fragment, { children: [
93
+ /* @__PURE__ */ jsx("span", { "data-slot": "button-count", className: "ui-button-count", "aria-hidden": "true", children: countText }),
94
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: `, ${spokenCount}` })
95
+ ] }) : null;
89
96
  return /* @__PURE__ */ jsx(
90
97
  Comp,
91
98
  {
@@ -109,6 +116,7 @@ const Button = React.forwardRef(
109
116
  ),
110
117
  ref,
111
118
  ...props,
119
+ ...foldedAriaLabel,
112
120
  children: asChild ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
113
121
  content,
114
122
  countNode
@@ -3,4 +3,4 @@ import type { AppShellProp } from "../../props/components/layout.prop.js";
3
3
  export type { AppShellProp, AppShellProp as AppShellProps, } from "../../props/components/layout.prop.js";
4
4
  /** Same rail boundary as shell-layout.css; SSR starts in docked mode. */
5
5
  export declare function useAppShellNavigationMode(): "drawer" | "docked";
6
- export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailPosition, navRailEnd, navRailLabel, mobileNav, mobileNavLabel, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
6
+ export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, logoCompact, logoCompactBelow, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailPosition, navRailEnd, navRailLabel, mobileNav, mobileNavLabel, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
@@ -17,6 +17,8 @@ function AppShell({
17
17
  topbarLeft,
18
18
  topbarRight,
19
19
  logo,
20
+ logoCompact,
21
+ logoCompactBelow = "sm",
20
22
  breadcrumb,
21
23
  footer,
22
24
  children,
@@ -72,11 +74,15 @@ function AppShell({
72
74
  }
73
75
  }
74
76
  const logoInRail = hasSidebar && logo !== void 0 && topbarSpan !== "full";
77
+ const brand = logoCompact === void 0 ? logo : /* @__PURE__ */ jsxs(Fragment, { children: [
78
+ /* @__PURE__ */ jsx("span", { className: "app-topbar-brand", "data-hide-below": logoCompactBelow, children: logo }),
79
+ /* @__PURE__ */ jsx("span", { className: "app-topbar-brand", "data-hide-from": logoCompactBelow, children: logoCompact })
80
+ ] });
75
81
  const resolvedTopbar = topbar !== void 0 ? /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
76
- !logoInRail && logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: logo }),
82
+ !logoInRail && logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: brand }),
77
83
  /* @__PURE__ */ jsx("div", { className: "app-topbar-custom", children: topbar })
78
84
  ] }) : /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
79
- !logoInRail && logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: logo }),
85
+ !logoInRail && logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: brand }),
80
86
  topbarLeft !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-left", children: topbarLeft }),
81
87
  /* @__PURE__ */ jsx("div", { className: "app-topbar-spacer" }),
82
88
  topbarRight !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-right", children: topbarRight })
@@ -1,5 +1,5 @@
1
1
  import type { PageContainerProp, PageInsetProp } from "../../props/components/layout.prop.js";
2
- export type { PageContainerProp, PageContainerProp as PageContainerProps, } from "../../props/components/layout.prop.js";
2
+ export type { PageContainerProp, PageContainerProp as PageContainerProps, PageContainerExtraProp, } from "../../props/components/layout.prop.js";
3
3
  export type { BreadcrumbItemProp, BreadcrumbItemProp as BreadcrumbItem, } from "../../props/vocabulary/navigation.prop.js";
4
4
  export declare function PageContainerInset({ className, children, ...props }: PageInsetProp): import("react").JSX.Element;
5
5
  declare function PageContainerRoot({ title, subtitle, status, extra, toolbar, footer, toolbarPad, footerPad, breadcrumb, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, density, variant, preset, headerLayout, headerScale, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect, useRef, useState } from "react";
3
+ import { isValidElement, useEffect, useRef, useState } from "react";
4
4
  import { ChevronRight } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { cn } from "../../lib/utils.js";
@@ -33,6 +33,12 @@ function useFooterReveal(enabled) {
33
33
  function PageContainerInset({ className, children, ...props }) {
34
34
  return /* @__PURE__ */ jsx("div", { className: cn("ui-page-container-inset", className), ...props, children });
35
35
  }
36
+ function resolvePageExtra(extra) {
37
+ if (extra === void 0 || extra === null || extra === false) return {};
38
+ const isSlotMap = typeof extra === "object" && !isValidElement(extra) && !Array.isArray(extra);
39
+ if (isSlotMap) return extra;
40
+ return { start: extra };
41
+ }
36
42
  function footerPadStyle(footerPad) {
37
43
  const style = padStyle(footerPad, void 0);
38
44
  const end = style?.paddingInlineEnd ?? style?.padding;
@@ -68,6 +74,7 @@ function PageContainerRoot({
68
74
  const reveal = stickyFooter && footer != null && footerReveal === "onScroll";
69
75
  const { headerRef, revealed } = useFooterReveal(reveal);
70
76
  const { t } = useTranslation();
77
+ const { start: extraStart, end: extraEnd } = resolvePageExtra(extra);
71
78
  return /* @__PURE__ */ jsxs(
72
79
  "div",
73
80
  {
@@ -149,7 +156,10 @@ function PageContainerRoot({
149
156
  )
150
157
  ) : subtitle && /* @__PURE__ */ jsx("p", { className: "ui-page-subtitle", children: subtitle })
151
158
  ] }),
152
- extra && /* @__PURE__ */ jsx("div", { className: "ui-page-header-extra", children: extra })
159
+ (extraStart != null || extraEnd != null) && /* @__PURE__ */ jsxs("div", { className: "ui-page-header-extra", children: [
160
+ extraStart,
161
+ extraEnd
162
+ ] })
153
163
  ] })
154
164
  ]
155
165
  }
@@ -4,4 +4,4 @@ export type { TopbarProp, TopbarProp as TopbarProps } from "../../props/componen
4
4
  * Topbar — a PURE SLOT bar. It positions three clusters (`start` / `center` / `end`) and owns the
5
5
  * bar's flex layout; it does NOT bake any chrome.
6
6
  */
7
- export declare function Topbar({ start, center, end, className, children, height, pad, style, ...props }: TopbarProp): import("react").JSX.Element;
7
+ export declare function Topbar({ start, center, end, className, children, height, overflow, pad, style, ...props }: TopbarProp): import("react").JSX.Element;
@@ -8,6 +8,7 @@ function Topbar({
8
8
  className,
9
9
  children,
10
10
  height,
11
+ overflow = "scroll",
11
12
  pad,
12
13
  style,
13
14
  ...props
@@ -16,6 +17,7 @@ function Topbar({
16
17
  "div",
17
18
  {
18
19
  "data-height": height,
20
+ "data-overflow": overflow,
19
21
  style: { ...style, ...padStyle(pad, void 0) },
20
22
  "data-slot": "topbar",
21
23
  className: cn("ui-topbar", className),
@@ -2,9 +2,11 @@ import * as React from "react";
2
2
  import { type ToggleCountFields, type ToggleProp } from "./toggle.js";
3
3
  type ToggleGroupVariant = ToggleProp["variant"];
4
4
  type ToggleGroupSize = ToggleProp["size"];
5
+ type ToggleGroupShape = ToggleProp["shape"];
5
6
  type ToggleGroupBaseProp = Omit<React.ComponentPropsWithoutRef<"div">, "defaultValue" | "dir"> & {
6
7
  variant?: ToggleGroupVariant;
7
8
  size?: ToggleGroupSize;
9
+ shape?: ToggleGroupShape;
8
10
  /** Disable every item at once — RAC `isDisabled`. */
9
11
  disabled?: boolean;
10
12
  /** Which way the arrow keys walk the group. */
@@ -51,6 +53,7 @@ export type ToggleGroupItemProp = Omit<React.ComponentPropsWithoutRef<"button">,
51
53
  value: string;
52
54
  variant?: ToggleGroupVariant;
53
55
  size?: ToggleGroupSize;
56
+ shape?: ToggleGroupShape;
54
57
  };
55
58
  export type ToggleGroupItemProps = ToggleGroupItemProp;
56
59
  export declare const ToggleGroupItem: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref">, "value"> & ToggleCountFields & {
@@ -61,5 +64,6 @@ export declare const ToggleGroupItem: React.ForwardRefExoticComponent<Omit<Omit<
61
64
  value: string;
62
65
  variant?: ToggleGroupVariant;
63
66
  size?: ToggleGroupSize;
67
+ shape?: ToggleGroupShape;
64
68
  } & React.RefAttributes<HTMLButtonElement>>;
65
69
  export {};
@@ -31,6 +31,7 @@ const ToggleGroup = React.forwardRef(
31
31
  className,
32
32
  variant,
33
33
  size,
34
+ shape,
34
35
  children,
35
36
  type,
36
37
  value,
@@ -40,7 +41,7 @@ const ToggleGroup = React.forwardRef(
40
41
  loop: _loop,
41
42
  ...props
42
43
  }, ref) => {
43
- const context = React.useMemo(() => ({ variant, size }), [variant, size]);
44
+ const context = React.useMemo(() => ({ variant, size, shape }), [variant, size, shape]);
44
45
  const selectedKeys = React.useMemo(() => toSelectedKeys(value), [value]);
45
46
  const defaultSelectedKeys = React.useMemo(() => toSelectedKeys(defaultValue), [defaultValue]);
46
47
  const handleSelectionChange = (keys) => {
@@ -58,6 +59,7 @@ const ToggleGroup = React.forwardRef(
58
59
  "data-slot": "toggle-group",
59
60
  "data-variant": variant,
60
61
  "data-size": size,
62
+ "data-shape": shape,
61
63
  className: cn("ui-toggle-group", className),
62
64
  ...props,
63
65
  selectionMode: type,
@@ -77,6 +79,7 @@ const ToggleGroupItem = React.forwardRef(
77
79
  className,
78
80
  variant,
79
81
  size,
82
+ shape,
80
83
  count,
81
84
  overflowCount,
82
85
  showZero,
@@ -90,6 +93,7 @@ const ToggleGroupItem = React.forwardRef(
90
93
  const context = React.useContext(ToggleGroupContext);
91
94
  const resolvedVariant = variant ?? context.variant;
92
95
  const resolvedSize = size ?? context.size;
96
+ const resolvedShape = shape ?? context.shape;
93
97
  const { pill, resolvedAriaLabel } = useCounterPill({
94
98
  count,
95
99
  overflowCount,
@@ -107,8 +111,12 @@ const ToggleGroupItem = React.forwardRef(
107
111
  "data-state": isPressed ? "on" : "off",
108
112
  "data-variant": resolvedVariant,
109
113
  "data-size": resolvedSize,
114
+ "data-shape": resolvedShape,
110
115
  "aria-label": resolvedAriaLabel,
111
- className: cn(toggleVariants({ variant: resolvedVariant, size: resolvedSize }), className),
116
+ className: cn(
117
+ toggleVariants({ variant: resolvedVariant, size: resolvedSize, shape: resolvedShape }),
118
+ className
119
+ ),
112
120
  ...props,
113
121
  id: value,
114
122
  isDisabled: disabled,
@@ -1,8 +1,9 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const toggleVariants: (props?: ({
4
- variant?: "default" | "outline" | null | undefined;
4
+ variant?: "default" | "outline" | "soft" | null | undefined;
5
5
  size?: "lg" | "md" | "sm" | "xs" | null | undefined;
6
+ shape?: "default" | "pill" | "sharp" | null | undefined;
6
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
8
  /**
8
9
  * Not exported on its own — it is folded into `ToggleProp` and `ToggleGroupItemProp` so there is
@@ -68,8 +69,9 @@ declare function restoreDomProps<P extends {
68
69
  export type ToggleProp = React.ComponentPropsWithoutRef<"button"> & TogglePressedFields & VariantProps<typeof toggleVariants> & ToggleCountFields;
69
70
  export type ToggleProps = ToggleProp;
70
71
  export declare const Toggle: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & TogglePressedFields & VariantProps<(props?: ({
71
- variant?: "default" | "outline" | null | undefined;
72
+ variant?: "default" | "outline" | "soft" | null | undefined;
72
73
  size?: "lg" | "md" | "sm" | "xs" | null | undefined;
74
+ shape?: "default" | "pill" | "sharp" | null | undefined;
73
75
  } & import("class-variance-authority/types").ClassProp) | undefined) => string> & ToggleCountFields & React.RefAttributes<HTMLButtonElement>>;
74
76
  export { toggleVariants, useCounterPill, restoreDomProps };
75
77
  export type { ToggleCountFields, TogglePressedFields };
@@ -10,7 +10,15 @@ const toggleVariants = cva("ui-toggle", {
10
10
  variants: {
11
11
  variant: {
12
12
  default: "ui-toggle-default",
13
- outline: "ui-toggle-outline"
13
+ outline: "ui-toggle-outline",
14
+ // The REST FILL (gh#734). `default` is a 1px TRANSPARENT border and `outline` a hairline on
15
+ // `--background`, so neither draws a chip that reads as filled before it is pressed — which
16
+ // is why a consumer building a tag-filter panel reported its tags "look transparent" and
17
+ // fell back to `Button variant="secondary" shape="pill" aria-pressed`, a control that paints
18
+ // no pressed state at all. `soft` is antd `Tag`'s resting surface, on the SAME
19
+ // `hsl(var(--secondary))` fill `Badge variant="secondary"` and `Button variant="secondary"`
20
+ // already paint, so a chip, a badge and a button on one row are one family.
21
+ soft: "ui-toggle-soft"
14
22
  },
15
23
  size: {
16
24
  // The fourth step of the shared control ladder (gh#716): 24px, `--control-height-xs`, the
@@ -20,11 +28,21 @@ const toggleVariants = cva("ui-toggle", {
20
28
  sm: "ui-toggle-sm",
21
29
  md: "ui-toggle-default-size",
22
30
  lg: "ui-toggle-lg"
31
+ },
32
+ // Corner shape at parity with `Button` and `Badge` — the same three values mapped to the same
33
+ // two radius tokens, so `shape="pill"` means one thing across the library. `default` is empty
34
+ // on purpose: it leaves `.ui-toggle`'s own `--radius-md` in place instead of restating it,
35
+ // exactly as `badgeVariants` does.
36
+ shape: {
37
+ default: "",
38
+ pill: "rounded-[var(--radius-pill)]",
39
+ sharp: "rounded-[var(--radius-sharp)]"
23
40
  }
24
41
  },
25
42
  defaultVariants: {
26
43
  variant: "default",
27
- size: "md"
44
+ size: "md",
45
+ shape: "default"
28
46
  }
29
47
  });
30
48
  function useCounterPill({
@@ -82,6 +100,7 @@ const Toggle = React.forwardRef(
82
100
  className,
83
101
  variant,
84
102
  size,
103
+ shape,
85
104
  count,
86
105
  overflowCount,
87
106
  showZero,
@@ -113,7 +132,7 @@ const Toggle = React.forwardRef(
113
132
  "data-slot": "toggle",
114
133
  "data-state": isPressed ? "on" : "off",
115
134
  "aria-label": resolvedAriaLabel,
116
- className: cn(toggleVariants({ variant, size }), className),
135
+ className: cn(toggleVariants({ variant, size, shape }), className),
117
136
  ...props,
118
137
  isSelected: isPressed,
119
138
  onChange: onSelectionChange,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "27.8.0",
3
+ "version": "27.10.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -700,6 +700,17 @@ export type TreeProp = {
700
700
  titleRender?: (node: TreeNodeProp) => React.ReactNode;
701
701
  /** Draw the connector rails between a parent and its children (antd `showLine`). */
702
702
  showLine?: boolean;
703
+ /**
704
+ * Rule between the rows: a hairline on every node's block-start except the outline's first, so a
705
+ * tree used as navigation inside a Card reads as a list instead of one block (gh#732). The rule
706
+ * runs the FULL width of the row at every depth — it is never indented per level, which would
707
+ * make the children read as a nested table. Colour is `--tree-divider-color`.
708
+ *
709
+ * Off by default: a rule is chrome, and this component's chrome defaults quiet (cardinal rule
710
+ * #44 — the same reason `showLine` ships off). Not antd's name — antd's `Tree` has no such
711
+ * capability; see docs/DESIGN-AUTHORITY.md.
712
+ */
713
+ divided?: boolean;
703
714
  /** Draw each node's `icon` (antd `showIcon`). `variant="directory"` supplies folder/file glyphs. */
704
715
  showIcon?: boolean;
705
716
  /** `directory` is antd's `<DirectoryTree>`: folder/file glyphs and a full-row selected band. */
@@ -745,9 +745,12 @@ export type CalendarProp = DayPickerProps & CalendarFooterProp & {
745
745
  *
746
746
  * Default `true` — the owner's call, from a real screen: with no ruling the month read as a
747
747
  * cloud of numbers and was "very hard to read". Pass `bordered={false}` for the floating-button
748
- * look. The line colour is `--calendar-grid-border-color` (default `--input` at 0.5 alpha,
749
- * 1.74:1 light / 1.95:1 dark on the popover; the full `--input` read too dark, and the
750
- * decorative `--border` measured 1.15:1 and was invisible as a 1px ruling).
748
+ * look. The line colour is `--calendar-grid-border-color`, default `hsl(var(--border))` — the
749
+ * same tier as the popover edge, a Card edge and a DataTable row rule, so the ruling can never
750
+ * out-weigh the surface it is drawn on (L* 93.80 / 1.149:1 light, L* 20.76 / 1.270:1 dark).
751
+ * It was `--input` at 0.5 alpha (L* 78.67 / 1.738:1) through 27.6.0 and was reported darker
752
+ * than every card and table in the system (gh#730); the full `--input` (3.463:1) was reported
753
+ * too dark before that. Set `--calendar-grid-border-color` to retint.
751
754
  */
752
755
  bordered?: boolean;
753
756
  /** Decorate a day cell — 祝日, a booked day, a deadline. @see CalendarCellRenderProp */
@@ -1543,6 +1546,22 @@ export type UploadProp = FieldA11yProps & {
1543
1546
  * @see Button — same variant scale
1544
1547
  */
1545
1548
  triggerVariant?: "default" | "destructive" | "outline" | "dashed" | "secondary" | "ghost" | "link";
1549
+ /**
1550
+ * `variant="button"` only — the glyph COMPONENT the trigger draws, `triggerIcon={Plus}` rather
1551
+ * than `<Plus />`. Defaults to the upload arrow.
1552
+ *
1553
+ * It exists because the glyph was hard-coded and `triggerVariant` only moves emphasis, so a
1554
+ * "create new" action that HAPPENS to be an upload could not carry a plus and had to announce
1555
+ * itself as an upload (gh#734). The component is rendered onto the `<svg>` itself, at the same
1556
+ * `--upload-row-icon-size` the built-in glyph uses, so a swapped icon cannot change the
1557
+ * trigger's metrics.
1558
+ *
1559
+ * Typed as the widened glyph component (SVG props + ref) rather than the narrower `IconProp`,
1560
+ * for the reason `IconGlyphProp.as` already records: the library hands the glyph its class, its
1561
+ * `aria-hidden` and its label-spacing attribute, none of which `IconProp` can accept. Every
1562
+ * `lucide-react` icon and every `Icon`-compatible component qualifies.
1563
+ */
1564
+ triggerIcon?: React.ComponentType<React.SVGProps<SVGSVGElement> & React.RefAttributes<SVGSVGElement>>;
1546
1565
  className?: ClassNameProp;
1547
1566
  children?: React.ReactNode;
1548
1567
  };
@@ -269,6 +269,18 @@ export type ButtonProp = React.ButtonHTMLAttributes<HTMLButtonElement> & {
269
269
  * `true` (a `0` pill shows); pass `false` to hide the pill at zero.
270
270
  */
271
271
  showZero?: boolean;
272
+ /**
273
+ * Localized description of what the count MEANS, folded into the accessible name so the control
274
+ * never announces as a bare digit run. The SAME prop and the same contract `Toggle` has carried
275
+ * since gh#312 — without it a counted button measured `"Git3"` in the accessibility tree, not
276
+ * "Git, 3 pages" (gh#734).
277
+ *
278
+ * The pill itself is `aria-hidden` and the spoken form is an `sr-only` sibling, so the name
279
+ * reads `"<label>, <count> <countLabel>"` from the contents; with an explicit `aria-label`
280
+ * (an icon-only counted button) the same clause is folded into that instead. Pass a
281
+ * `t()`-resolved string — the library does not own this wording.
282
+ */
283
+ countLabel?: string;
272
284
  };
273
285
  /**
274
286
  * @see Reveal — entrance-motion primitive (staggered fade-up). Wraps content in a real element
@@ -1,4 +1,4 @@
1
- export type { AppLauncherApp, AppLauncherGroup, AppLauncherLabels, AppLauncherProp, AppShellProp, AuthFooterProp, AuthIdentityProp, OrgSwitcherLabels, OrgSwitcherOrganization, OrgSwitcherProp, DragAxisProp, DragBoundsProp, DraggablePanelLabels, DraggablePanelPlacementProp, DraggablePanelPositionProp, DraggablePanelProp, PageContainerProp, PageInsetProp, SidebarItemProp, SidebarProductProp, SidebarProp, SidebarSectionProp, TopbarProp, } from "./layout.prop.js";
1
+ export type { AppLauncherApp, AppLauncherGroup, AppLauncherLabels, AppLauncherProp, AppShellProp, AuthFooterProp, AuthIdentityProp, OrgSwitcherLabels, OrgSwitcherOrganization, OrgSwitcherProp, DragAxisProp, DragBoundsProp, DraggablePanelLabels, DraggablePanelPlacementProp, DraggablePanelPositionProp, DraggablePanelProp, PageContainerExtraProp, PageContainerProp, PageInsetProp, SidebarItemProp, SidebarProductProp, SidebarProp, SidebarSectionProp, TopbarProp, } from "./layout.prop.js";
2
2
  export type { ButtonProp } from "./general.prop.js";
3
3
  export type { CommandProp, InputProp, TextareaProp, FormFieldProp, SearchInputProp, CheckboxProp, CheckboxGroupProp, ChoiceOptionProp, RadioProp, RadioGroupProp, SwitchProp, SliderProp, CalendarProp, DatePickerProp, TimePickerProp, ColorPickerProp, UploadProp, UploadFileItemProp, UploadVariantProp, TreeOptionProp, TreeFieldNamesProp, CascaderProp, TreeSelectProp, ShowCheckedStrategyProp, TransferProp, TransferItemProp, } from "./data-entry.prop.js";
4
4
  export type { AvatarProp, EmptyStateProp, DescriptionsProp, DescriptionsItemProp, BadgeProp, DataTableProp, QrCodeProp, } from "./data-display.prop.js";
@@ -22,6 +22,24 @@ export type PageContainerMeasureProp = "default" | "narrow" | "medium";
22
22
  * `document` (default) — the row is the page's TITLE: a record, a form, a collection, a report.
23
23
  */
24
24
  export type PageContainerHeaderScaleProp = "document" | "chrome";
25
+ /**
26
+ * @see PageContainer — the header's trailing slot, one node or two named sub-slots.
27
+ *
28
+ * The bare node is the shape every page already passes and stays the whole API for a single
29
+ * action cluster. `{ start, end }` splits that cluster in two so a page can put something AFTER
30
+ * the actions: the convention a consumer's four record screens share is identity → actions →
31
+ * pager LAST, and with one slot the pager had to live in a second header band inside the body,
32
+ * so the app carried two header shapes (gh#734).
33
+ *
34
+ * The shape is `TabsExtraProp`'s, deliberately — `Tabs.extra` already accepts exactly this union
35
+ * for exactly this reason, and a second spelling of one axis is what `check:prop-vocabulary`
36
+ * exists to prevent. Logical inline names (`start`/`end`, never `left`/`right`), so the slots
37
+ * swap sides under `dir="rtl"` with no second code path.
38
+ */
39
+ export type PageContainerExtraProp = ExtraProp | {
40
+ start?: ReactNode;
41
+ end?: ReactNode;
42
+ };
25
43
  /** @see PageContainer */
26
44
  export type PageContainerProp = {
27
45
  toolbarPad?: PadProp;
@@ -40,7 +58,8 @@ export type PageContainerProp = {
40
58
  * violation) so the page's heading outline never disappears mid-load.
41
59
  */
42
60
  headerLoading?: boolean;
43
- extra?: ExtraProp;
61
+ /** @see PageContainerExtraProp */
62
+ extra?: PageContainerExtraProp;
44
63
  /**
45
64
  * FIXED chrome band between the page header and the scrolling body — a filter strip, a status
46
65
  * bar, a "channel workflow" rail. It is a first-class page-chrome slot precisely because the
@@ -353,6 +372,28 @@ export type AppShellProp = {
353
372
  topbarLeft?: ReactNode;
354
373
  topbarRight?: ReactNode;
355
374
  logo?: ReactNode;
375
+ /**
376
+ * The brand node a NARROW bar gets instead of `logo` — a mark without the wordmark, a shorter
377
+ * lockup, a different `viewBox` (gh#728).
378
+ *
379
+ * The brand cell shrinks without this prop: below the `sm` step it is capped at
380
+ * `--app-shell-brand-compact-max-inline-size` and anything past the cap is cropped from the
381
+ * inline-end. A CROP is all a stylesheet can do to artwork it did not author — `viewBox` is an
382
+ * attribute, which is why consumers were re-cropping their own SVG through a media-query hook.
383
+ * Passing a node here is how you CHOOSE what the narrow bar shows instead.
384
+ *
385
+ * Both nodes are rendered; the breakpoint drops one with `display: none`, so exactly one brand
386
+ * is ever in the accessibility tree. Applies to the brand IN THE BAR only: under the default
387
+ * `topbarSpan="content"` with a sidebar the brand sits at the head of the rail, which the
388
+ * responsive shell has already replaced with the drawer at that width.
389
+ */
390
+ logoCompact?: ReactNode;
391
+ /**
392
+ * The step at which `logoCompact` takes over — `Flex hideBelow`'s own vocabulary and the same
393
+ * canonical scale (sm 40rem · md 48rem · lg 64rem · xl 80rem). Defaults to `"sm"`, the step the
394
+ * brand cap uses. Ignored without `logoCompact`.
395
+ */
396
+ logoCompactBelow?: BreakpointProp;
356
397
  breadcrumb?: ReactNode;
357
398
  footer?: ReactNode;
358
399
  sidebarCollapsed?: boolean;
@@ -1318,6 +1359,24 @@ export type TopbarProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children">
1318
1359
  center?: ReactNode;
1319
1360
  /** Inline-end cluster — settings pickers, notifications, the user menu. */
1320
1361
  end?: ReactNode;
1362
+ /**
1363
+ * What a bar that does not fit does with the cells that do not fit (gh#728).
1364
+ *
1365
+ * - `"scroll"` (default) — the BAR scrolls on the inline axis while the `start` and `end`
1366
+ * clusters keep their cells whole (`start` floors at one cell, `end` at its content), so what
1367
+ * does not fit is off-port rather than half-painted. Every cell stays in the DOM and in the
1368
+ * accessibility tree, Tab reaches each one in order, and the browser scrolls a focused cell
1369
+ * fully into view; the scrollbar is suppressed (a horizontal bar inside a 48px chrome row
1370
+ * would eat the height that caused the deficit) so the pointer affordance is drag/wheel.
1371
+ * - `"clip"` — the pre-gh#728 behaviour: a cell past the cluster's edge is simply not painted.
1372
+ * Still focusable, still announced, invisible and unreachable by pointer. Choose it only for a
1373
+ * bar you have measured as never overflowing.
1374
+ *
1375
+ * Independent of `TopbarItem hideBelow`, which REMOVES a cell at a step: dropping a cell is the
1376
+ * consumer saying it does not belong on a phone, and that remains the way to spend the budget
1377
+ * deliberately. This prop is only about what happens once there is no budget left.
1378
+ */
1379
+ overflow?: "scroll" | "clip";
1321
1380
  /** Escape hatch — render fully custom bar content instead of the three slots. */
1322
1381
  children?: ReactNode;
1323
1382
  };