@godxjp/ui 19.5.0 → 20.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 (142) hide show
  1. package/dist/components/data-display/card.d.ts +2 -2
  2. package/dist/components/data-display/collapsible.d.ts +23 -4
  3. package/dist/components/data-display/collapsible.js +119 -4
  4. package/dist/components/data-display/index.d.ts +3 -1
  5. package/dist/components/data-display/index.js +2 -0
  6. package/dist/components/data-display/legend.d.ts +15 -0
  7. package/dist/components/data-display/legend.js +11 -0
  8. package/dist/components/data-display/list-row.js +1 -1
  9. package/dist/components/data-display/popover.d.ts +58 -5
  10. package/dist/components/data-display/popover.js +206 -27
  11. package/dist/components/data-display/progress.d.ts +50 -2
  12. package/dist/components/data-display/progress.js +57 -9
  13. package/dist/components/data-entry/calendar.d.ts +1 -1
  14. package/dist/components/data-entry/calendar.js +67 -32
  15. package/dist/components/data-entry/checkbox.d.ts +19 -6
  16. package/dist/components/data-entry/checkbox.js +55 -16
  17. package/dist/components/data-entry/choice-option.d.ts +1 -1
  18. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  19. package/dist/components/data-entry/field.js +0 -1
  20. package/dist/components/data-entry/form.js +1 -1
  21. package/dist/components/data-entry/label.d.ts +3 -2
  22. package/dist/components/data-entry/label.js +23 -10
  23. package/dist/components/data-entry/month-picker.d.ts +1 -1
  24. package/dist/components/data-entry/month-range-picker.d.ts +1 -1
  25. package/dist/components/data-entry/transfer.js +16 -6
  26. package/dist/components/data-entry/tree-select-strategy.d.ts +1 -1
  27. package/dist/components/data-entry/tree-select.js +29 -12
  28. package/dist/components/data-entry/tree-utils.d.ts +1 -1
  29. package/dist/components/data-entry/upload.js +7 -2
  30. package/dist/components/feedback/dialog.d.ts +105 -38
  31. package/dist/components/feedback/dialog.js +272 -194
  32. package/dist/components/feedback/overlay-close-focus.d.ts +31 -0
  33. package/dist/components/feedback/overlay-close-focus.js +31 -0
  34. package/dist/components/feedback/overlay-header-tone.d.ts +1 -1
  35. package/dist/components/feedback/sheet.d.ts +53 -11
  36. package/dist/components/feedback/sheet.js +150 -81
  37. package/dist/components/feedback/tooltip.d.ts +51 -7
  38. package/dist/components/feedback/tooltip.js +107 -25
  39. package/dist/components/general/button.js +2 -2
  40. package/dist/components/general/logo.d.ts +17 -0
  41. package/dist/components/general/logo.js +22 -16
  42. package/dist/components/general/typography.d.ts +7 -1
  43. package/dist/components/general/typography.js +20 -5
  44. package/dist/components/layout/app-shell.d.ts +1 -1
  45. package/dist/components/layout/app-shell.js +52 -8
  46. package/dist/components/layout/aspect-ratio.js +0 -1
  47. package/dist/components/layout/auth-divider.js +0 -1
  48. package/dist/components/layout/flex.d.ts +1 -1
  49. package/dist/components/layout/flex.js +19 -2
  50. package/dist/components/layout/nav-surface.d.ts +26 -0
  51. package/dist/components/layout/nav-surface.js +17 -0
  52. package/dist/components/layout/org-switcher.d.ts +5 -1
  53. package/dist/components/layout/org-switcher.js +5 -3
  54. package/dist/components/layout/separator.js +0 -1
  55. package/dist/components/layout/sidebar.js +4 -1
  56. package/dist/components/layout/split-pane.d.ts +14 -1
  57. package/dist/components/layout/split-pane.js +26 -13
  58. package/dist/components/layout/topbar-item.js +1 -2
  59. package/dist/components/navigation/app-setting-picker.js +21 -2
  60. package/dist/components/navigation/app-setting-toggle.d.ts +16 -0
  61. package/dist/components/navigation/app-setting-toggle.js +96 -0
  62. package/dist/components/navigation/dropdown-menu.d.ts +199 -18
  63. package/dist/components/navigation/dropdown-menu.js +344 -117
  64. package/dist/components/navigation/index.d.ts +2 -0
  65. package/dist/components/navigation/index.js +2 -0
  66. package/dist/components/navigation/pagination-utils.d.ts +2 -1
  67. package/dist/components/navigation/tabs.d.ts +47 -6
  68. package/dist/components/navigation/tabs.js +152 -90
  69. package/dist/components/ui/accordion.d.ts +50 -5
  70. package/dist/components/ui/accordion.js +239 -33
  71. package/dist/components/ui/aspect-ratio.d.ts +23 -2
  72. package/dist/components/ui/aspect-ratio.js +15 -13
  73. package/dist/components/ui/avatar.d.ts +29 -4
  74. package/dist/components/ui/avatar.js +111 -25
  75. package/dist/components/ui/hover-card.d.ts +42 -4
  76. package/dist/components/ui/hover-card.js +183 -27
  77. package/dist/components/ui/label.js +0 -1
  78. package/dist/components/ui/segmented.d.ts +3 -3
  79. package/dist/components/ui/separator.d.ts +8 -2
  80. package/dist/components/ui/separator.js +17 -9
  81. package/dist/components/ui/toggle-group.d.ts +50 -5
  82. package/dist/components/ui/toggle-group.js +79 -20
  83. package/dist/components/ui/toggle.d.ts +31 -5
  84. package/dist/components/ui/toggle.js +42 -3
  85. package/dist/i18n/messages/en.json +8 -0
  86. package/dist/i18n/messages/ja.json +8 -0
  87. package/dist/i18n/messages/vi.json +8 -0
  88. package/dist/lib/control-styles.d.ts +1 -1
  89. package/dist/lib/control-styles.js +1 -1
  90. package/dist/lib/slot.d.ts +32 -0
  91. package/dist/lib/slot.js +22 -0
  92. package/dist/lib/variants.d.ts +22 -3
  93. package/dist/lib/variants.js +56 -1
  94. package/dist/props/components/app.prop.d.ts +25 -1
  95. package/dist/props/components/data-display.prop.d.ts +17 -0
  96. package/dist/props/components/data-entry.prop.d.ts +32 -4
  97. package/dist/props/components/general.prop.d.ts +31 -1
  98. package/dist/props/components/index.d.ts +1 -1
  99. package/dist/props/components/layout.prop.d.ts +118 -5
  100. package/dist/props/components/navigation.prop.d.ts +1 -1
  101. package/dist/props/registry.d.ts +22 -2
  102. package/dist/props/registry.js +33 -2
  103. package/dist/props/vocabulary/content.prop.d.ts +1 -1
  104. package/dist/props/vocabulary/index.d.ts +2 -2
  105. package/dist/props/vocabulary/interaction.prop.d.ts +17 -1
  106. package/dist/props/vocabulary/layout.prop.d.ts +57 -1
  107. package/dist/styles/control.css +142 -1
  108. package/dist/styles/core.css +5 -2
  109. package/dist/styles/data-display-layout.css +72 -0
  110. package/dist/styles/focus-ring.css +13 -1
  111. package/dist/styles/index.css +5 -2
  112. package/dist/styles/layout.css +106 -0
  113. package/dist/styles/shell-layout.css +140 -8
  114. package/dist/styles/text-layout.css +16 -4
  115. package/dist/tokens/base.css +2 -1
  116. package/dist/tokens/components/data-display.css +11 -0
  117. package/dist/tokens/components/shell.css +3 -1
  118. package/dist/tokens/components/text.css +7 -0
  119. package/dist/tokens/foundation.css +2 -0
  120. package/docs/COMPONENTS.md +9 -3
  121. package/docs/DESIGN-AUTHORITY.md +96 -71
  122. package/docs/FRAME-COVERAGE-REPORT.md +5 -3
  123. package/docs/README.md +14 -14
  124. package/docs/WHAT-BELONGS-HERE.md +179 -0
  125. package/docs/data-display/legend.tsx +145 -0
  126. package/docs/data-display/progress.tsx +32 -0
  127. package/docs/data-entry/segmented.tsx +1 -1
  128. package/docs/data-entry/select.tsx +2 -2
  129. package/docs/feedback/sheet.tsx +1 -1
  130. package/docs/general/typography.tsx +84 -1
  131. package/docs/layout/app-shell-arrangements.tsx +225 -0
  132. package/docs/layout/aspect-ratio.tsx +1 -1
  133. package/docs/navigation/app-setting-picker.tsx +26 -1
  134. package/docs/navigation/app-setting-toggle.tsx +111 -0
  135. package/package.json +11 -8
  136. package/scripts/_agent-setup.mjs +165 -3
  137. package/scripts/consumer-rule.md +98 -0
  138. package/scripts/guinea-pig-skill.md +322 -0
  139. package/scripts/init-guinea-pig.mjs +62 -0
  140. package/scripts/postinstall.mjs +13 -2
  141. package/scripts/ui-audit.mjs +115 -14
  142. /package/dist/tokens/{antd.generated.css → derived.css} +0 -0
@@ -1,5 +1,6 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
+ import { Slot } from "../../lib/slot.js";
3
4
  import { cn } from "../../lib/utils.js";
4
5
  const X_HEIGHT_TOP = /^[acegmnopqrsuvwxyz]+$/;
5
6
  const LATIN_DESCENDER = /[gjpqy]/;
@@ -83,42 +84,47 @@ const Logo = React.forwardRef(
83
84
  tone = "primary",
84
85
  wordmark,
85
86
  label,
87
+ asChild,
88
+ children,
86
89
  className,
87
90
  ...props
88
91
  }, ref) => {
89
92
  const hasWordmark = wordmark !== void 0 && wordmark !== null && wordmark !== false;
90
93
  const identity = { "data-mark": mark, "data-size": size, "data-tone": tone };
94
+ const renderRoot = (rootProps, content) => {
95
+ if (!asChild) {
96
+ return /* @__PURE__ */ jsx("span", { ref, ...rootProps, children: content });
97
+ }
98
+ const borrowed = React.Children.only(children);
99
+ return /* @__PURE__ */ jsx(Slot, { ...rootProps, ref, children: React.cloneElement(borrowed, void 0, content) });
100
+ };
91
101
  if (hasWordmark) {
92
- return /* @__PURE__ */ jsxs(
93
- "span",
102
+ return renderRoot(
94
103
  {
95
- ref,
96
104
  "data-slot": "logo-lockup",
97
105
  ...identity,
98
106
  className: cn("ui-logo-lockup", className),
99
107
  role: label ? "img" : void 0,
100
108
  "aria-label": label,
101
- ...props,
102
- children: [
103
- /* @__PURE__ */ jsx("span", { "data-slot": "logo", ...identity, className: "ui-logo", "aria-hidden": "true", children: /* @__PURE__ */ jsx(MarkArtwork, { mark, glyph }) }),
104
- /* @__PURE__ */ jsx("span", { "data-slot": "logo-wordmark", "data-logotype": true, className: "ui-logo-wordmark", children: wordmark })
105
- ]
106
- }
109
+ ...props
110
+ },
111
+ /* @__PURE__ */ jsxs(Fragment, { children: [
112
+ /* @__PURE__ */ jsx("span", { "data-slot": "logo", ...identity, className: "ui-logo", "aria-hidden": "true", children: /* @__PURE__ */ jsx(MarkArtwork, { mark, glyph }) }),
113
+ /* @__PURE__ */ jsx("span", { "data-slot": "logo-wordmark", "data-logotype": true, className: "ui-logo-wordmark", children: wordmark })
114
+ ] })
107
115
  );
108
116
  }
109
- return /* @__PURE__ */ jsx(
110
- "span",
117
+ return renderRoot(
111
118
  {
112
- ref,
113
119
  "data-slot": "logo",
114
120
  ...identity,
115
121
  className: cn("ui-logo", className),
116
122
  role: label ? "img" : void 0,
117
123
  "aria-label": label,
118
124
  "aria-hidden": label ? void 0 : true,
119
- ...props,
120
- children: /* @__PURE__ */ jsx(MarkArtwork, { mark, glyph })
121
- }
125
+ ...props
126
+ },
127
+ /* @__PURE__ */ jsx(MarkArtwork, { mark, glyph })
122
128
  );
123
129
  }
124
130
  );
@@ -5,7 +5,9 @@ export type { TextProp, TextProp as TextProps, HeadingProp, HeadingProp as Headi
5
5
  * font-medium text-muted-foreground">`.
6
6
  */
7
7
  export declare const Text: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLElement>, "color"> & {
8
- as?: "span" | "p" | "div" | "label" | "strong" | "em" | "small" | "code" | "kbd" | "dt" | "dd" | "caption" | "abbr";
8
+ as?: "span" | "p" | "div" | "a" | "label" | "strong" | "em" | "small" | "code" | "kbd" | "dt" | "dd" | "caption" | "abbr";
9
+ asChild?: import("../../props/index.js").AsChildProp;
10
+ link?: boolean;
9
11
  size?: import("../../props/index.js").TextSizeProp;
10
12
  tone?: import("../../props/index.js").TextToneProp;
11
13
  weight?: import("../../props/index.js").FontWeightProp;
@@ -15,6 +17,10 @@ export declare const Text: React.ForwardRefExoticComponent<Omit<React.HTMLAttrib
15
17
  tabular?: boolean;
16
18
  mono?: boolean;
17
19
  htmlFor?: string;
20
+ href?: string;
21
+ target?: React.HTMLAttributeAnchorTarget;
22
+ rel?: string;
23
+ download?: React.AnchorHTMLAttributes<HTMLAnchorElement>["download"];
18
24
  } & React.RefAttributes<HTMLElement>>;
19
25
  /**
20
26
  * Heading — h1..h4 sized from the `--heading-h*` tokens. `level` sets both the size token and the
@@ -3,16 +3,21 @@ import { cn } from "../../lib/utils.js";
3
3
  const Text = React.forwardRef(
4
4
  ({
5
5
  as = "span",
6
+ asChild = false,
6
7
  size = "sm",
7
- tone = "default",
8
+ // `link` is an affordance, not a colour: it only moves the DEFAULT tone, so
9
+ // `link tone="destructive"` is a destructive link rather than an argument between two rules.
10
+ tone,
8
11
  weight = "regular",
9
12
  align,
10
13
  truncate,
11
14
  clamp,
12
15
  tabular,
13
16
  mono,
17
+ link,
14
18
  className,
15
19
  style,
20
+ children,
16
21
  ...props
17
22
  }, ref) => {
18
23
  const clampLines = typeof clamp === "number" && Number.isFinite(clamp) && clamp >= 1 ? Math.floor(clamp) : void 0;
@@ -28,11 +33,11 @@ const Text = React.forwardRef(
28
33
  );
29
34
  }
30
35
  }
31
- return React.createElement(as, {
32
- ref,
36
+ const typography = {
33
37
  "data-slot": "text",
34
38
  "data-size": size,
35
- "data-tone": tone,
39
+ "data-tone": tone ?? (link ? "primary" : "default"),
40
+ "data-link": link ? "" : void 0,
36
41
  "data-weight": weight,
37
42
  "data-align": align,
38
43
  "data-truncate": truncate && clampLines === void 0 ? "" : void 0,
@@ -42,7 +47,17 @@ const Text = React.forwardRef(
42
47
  "data-mono": mono ? "" : void 0,
43
48
  className: cn("ui-text", className),
44
49
  ...props
45
- });
50
+ };
51
+ if (asChild) {
52
+ const child = React.Children.only(children);
53
+ return React.cloneElement(child, {
54
+ ...typography,
55
+ ...child.props,
56
+ ref,
57
+ className: cn(typography.className, child.props.className)
58
+ });
59
+ }
60
+ return React.createElement(as, { ref, ...typography }, children);
46
61
  }
47
62
  );
48
63
  Text.displayName = "Text";
@@ -1,4 +1,4 @@
1
1
  import * as React from "react";
2
2
  import type { AppShellProp } from "../../props/components/layout.prop.js";
3
3
  export type { AppShellProp, AppShellProp as AppShellProps, } from "../../props/components/layout.prop.js";
4
- export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, mobileNav, mobileNavLabel, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
4
+ export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailLabel, mobileNav, mobileNavLabel, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
@@ -1,10 +1,11 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Menu } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { Button } from "../general/button.js";
7
7
  import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../feedback/sheet.js";
8
+ import { NavSurfaceProvider } from "./nav-surface.js";
8
9
  function AppShell({
9
10
  sidebar,
10
11
  topbar,
@@ -17,14 +18,20 @@ function AppShell({
17
18
  sidebarCollapsed = false,
18
19
  responsiveNavigation = "drawer",
19
20
  topbarSpan = "content",
21
+ navRail,
22
+ navRailLabel,
20
23
  mobileNav,
21
24
  mobileNavLabel,
22
25
  mobileNavOpen,
23
26
  onMobileNavOpenChange
24
27
  }) {
25
28
  const { t } = useTranslation();
26
- const drawerNav = mobileNav !== void 0 ? mobileNav : sidebar;
29
+ const drawerNav = mobileNav !== void 0 ? mobileNav : navRail !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
30
+ navRail,
31
+ sidebar
32
+ ] }) : sidebar;
27
33
  const hasDrawer = responsiveNavigation === "drawer" && drawerNav != null;
34
+ const railInDrawer = mobileNav === void 0 && navRail !== void 0;
28
35
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
29
36
  const drawerOpen = mobileNavOpen ?? uncontrolledOpen;
30
37
  const setDrawerOpen = onMobileNavOpenChange ?? setUncontrolledOpen;
@@ -36,13 +43,39 @@ function AppShell({
36
43
  }
37
44
  };
38
45
  const hasTopbarContent = topbar !== void 0 || topbarLeft !== void 0 || topbarRight !== void 0 || logo !== void 0;
39
- const resolvedTopbar = topbar !== void 0 ? topbar : /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
40
- logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: logo }),
46
+ if (process.env.NODE_ENV !== "production" && topbar !== void 0) {
47
+ const ignored = [
48
+ topbarLeft !== void 0 && "topbarLeft",
49
+ topbarRight !== void 0 && "topbarRight"
50
+ ].filter(Boolean);
51
+ if (ignored.length > 0) {
52
+ console.warn(
53
+ `AppShell: \`topbar\` \u0111\u01B0\u1EE3c truy\u1EC1n, n\xEAn ${ignored.join(", ")} b\u1ECB b\u1ECF qua v\xE0 KH\xD4NG \u0111\u01B0\u1EE3c v\u1EBD. Ho\u1EB7c b\u1ECF \`topbar\` \u0111\u1EC3 AppShell t\u1EF1 d\u1EF1ng d\u1EA3i t\u1EEB c\xE1c khe kia, ho\u1EB7c \u0111\u1EB7t n\u1ED9i dung \u0111\xF3 v\xE0o trong ch\xEDnh \`topbar\`. (\`logo\` KH\xD4NG n\u1EB1m trong s\u1ED1 n\xE0y \u2014 n\xF3 lu\xF4n \u0111\u01B0\u1EE3c v\u1EBD.)`
54
+ );
55
+ }
56
+ }
57
+ const logoInRail = logo !== void 0 && topbarSpan !== "full";
58
+ const resolvedTopbar = topbar !== void 0 ? /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
59
+ !logoInRail && logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: logo }),
60
+ /* @__PURE__ */ jsx("div", { className: "app-topbar-custom", children: topbar })
61
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
62
+ !logoInRail && logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: logo }),
41
63
  topbarLeft !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-left", children: topbarLeft }),
42
64
  /* @__PURE__ */ jsx("div", { className: "app-topbar-spacer" }),
43
65
  topbarRight !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-right", children: topbarRight })
44
66
  ] });
45
- const rail = /* @__PURE__ */ jsx("aside", { className: "app-sidebar", "aria-label": t("layout.appShell.sidebarLabel"), children: sidebar });
67
+ const rail = /* @__PURE__ */ jsxs("aside", { className: "app-sidebar", "aria-label": t("layout.appShell.sidebarLabel"), children: [
68
+ logoInRail && /* @__PURE__ */ jsx("div", { className: "app-sidebar-logo", children: logo }),
69
+ sidebar
70
+ ] });
71
+ const navRailRegion = navRail === void 0 ? null : /* @__PURE__ */ jsx(
72
+ "aside",
73
+ {
74
+ className: "app-nav-rail",
75
+ "aria-label": navRailLabel ?? t("layout.appShell.navRailLabel"),
76
+ children: navRail
77
+ }
78
+ );
46
79
  const bar = !hasTopbarContent && !hasDrawer ? null : /* @__PURE__ */ jsxs("header", { className: "app-topbar ui-scale-fixed", "aria-label": t("layout.appShell.headerLabel"), children: [
47
80
  hasDrawer && /* @__PURE__ */ jsxs(Sheet, { open: drawerOpen, onOpenChange: setDrawerOpen, children: [
48
81
  /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
@@ -71,7 +104,10 @@ function AppShell({
71
104
  {
72
105
  className: "app-mobile-nav-body px-[var(--app-shell-mobile-nav-inset)]",
73
106
  onClick: handleDrawerClick,
74
- children: drawerNav
107
+ children: railInDrawer ? /* @__PURE__ */ jsxs("div", { className: "app-mobile-nav-columns", children: [
108
+ /* @__PURE__ */ jsx("div", { className: "app-mobile-nav-rail", children: navRail }),
109
+ /* @__PURE__ */ jsx(NavSurfaceProvider, { surface: "drawer", children: /* @__PURE__ */ jsx("div", { className: "app-mobile-nav-sections", children: sidebar }) })
110
+ ] }) : /* @__PURE__ */ jsx(NavSurfaceProvider, { surface: "drawer", children: drawerNav })
75
111
  }
76
112
  )
77
113
  ]
@@ -88,9 +124,17 @@ function AppShell({
88
124
  "data-responsive-navigation": responsiveNavigation,
89
125
  "data-topbar": hasTopbarContent ? void 0 : "none",
90
126
  "data-topbar-span": topbarSpan === "full" ? "full" : void 0,
127
+ "data-nav-rail": navRail !== void 0 ? "" : void 0,
91
128
  children: [
92
- topbarSpan === "full" ? bar : rail,
93
- topbarSpan === "full" ? rail : bar,
129
+ topbarSpan === "full" ? /* @__PURE__ */ jsxs(Fragment, { children: [
130
+ bar,
131
+ navRailRegion,
132
+ rail
133
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
134
+ navRailRegion,
135
+ rail,
136
+ bar
137
+ ] }),
94
138
  /* @__PURE__ */ jsxs("main", { className: "app-main", "aria-label": t("layout.appShell.mainLabel"), tabIndex: 0, children: [
95
139
  breadcrumb !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-breadcrumb", children: breadcrumb }),
96
140
  children
@@ -1,4 +1,3 @@
1
- "use client";
2
1
  import { AspectRatio } from "../ui/aspect-ratio.js";
3
2
  export {
4
3
  AspectRatio
@@ -1,4 +1,3 @@
1
- "use client";
2
1
  import { jsx } from "react/jsx-runtime";
3
2
  import { cn } from "../../lib/utils.js";
4
3
  import { Separator } from "../ui/separator.js";
@@ -1,3 +1,3 @@
1
1
  import type { FlexProp } from "../../props/components/layout.prop.js";
2
2
  export type { FlexAlignProp, FlexDirectionProp, FlexJustifyProp, FlexProp, FlexProp as FlexProps, } from "../../props/components/layout.prop.js";
3
- export declare function Flex({ as: Element, direction, gap, align, justify, wrap, hideBelow, hideFrom, className, children, ...props }: FlexProp): import("react").JSX.Element;
3
+ export declare function Flex({ as: Element, direction, gap, gapRaw, pad, padRaw, align, justify, wrap, hideBelow, hideFrom, fill, width, className, style, children, ...props }: FlexProp): import("react").JSX.Element;
@@ -2,17 +2,24 @@
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { mergeAriaIds } from "../../lib/field-a11y.js";
4
4
  import { cn } from "../../lib/utils.js";
5
- import { flexGapClass } from "../../lib/variants.js";
5
+ import { flexGapClass, padStyle } from "../../lib/variants.js";
6
+ const toCssLength = (value) => typeof value === "number" ? `${value}px` : value;
6
7
  function Flex({
7
8
  as: Element = "div",
8
9
  direction = "row",
9
10
  gap = "md",
11
+ gapRaw,
12
+ pad,
13
+ padRaw,
10
14
  align,
11
15
  justify,
12
16
  wrap = false,
13
17
  hideBelow,
14
18
  hideFrom,
19
+ fill = false,
20
+ width,
15
21
  className,
22
+ style,
16
23
  children,
17
24
  ...props
18
25
  }) {
@@ -39,7 +46,17 @@ function Flex({
39
46
  "data-wrap": wrap ? "true" : void 0,
40
47
  "data-hide-below": hideBelow,
41
48
  "data-hide-from": hideFrom,
42
- className: cn("ui-flex", flexGapClass[gap], className),
49
+ "data-fill": fill ? "" : void 0,
50
+ "data-width-raw": width === void 0 ? void 0 : "",
51
+ "data-gap-raw": gapRaw,
52
+ className: cn("ui-flex", gapRaw === void 0 ? flexGapClass[gap] : void 0, className),
53
+ "data-pad-raw": padRaw === void 0 ? void 0 : "",
54
+ style: {
55
+ ...style,
56
+ ...gapRaw === void 0 ? void 0 : { gap: `${gapRaw}px` },
57
+ ...width === void 0 ? void 0 : { inlineSize: toCssLength(width) },
58
+ ...padStyle(pad, padRaw)
59
+ },
43
60
  ...domProps,
44
61
  children
45
62
  }
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ /**
3
+ * WHICH SURFACE a navigation component is rendering into.
4
+ *
5
+ * `AppShell` reuses the very same `sidebar` / `navRail` nodes for the mobile drawer that it docks
6
+ * on desktop — that is the point of the default, since a consumer should not have to build its
7
+ * navigation twice. But those nodes carry `collapsed`, and collapse is a DESKTOP answer: it trades
8
+ * labels for horizontal room in a docked column. A drawer has no such pressure, and below the
9
+ * breakpoint it is the ONLY navigation there is.
10
+ *
11
+ * Shipping both together produced a drawer of anonymous icons: a consumer collapsed its sidebar at
12
+ * 1280px, resized to 393px, opened the drawer, and got the organization mark plus five unlabelled
13
+ * glyphs — captured in that repo's own browser test, which is named "desktop sidebar collapse
14
+ * stays independent from the mobile navigation drawer" and had been asserting exactly this.
15
+ *
16
+ * The node cannot be un-collapsed from the outside once it is built, so the surface announces
17
+ * itself instead and the navigation components read it. Not exported from the public barrel: this
18
+ * is a contract between `AppShell` and the navigation it hosts, not a consumer knob.
19
+ */
20
+ export type NavSurface = "docked" | "drawer";
21
+ export declare function NavSurfaceProvider({ surface, children, }: {
22
+ surface: NavSurface;
23
+ children: React.ReactNode;
24
+ }): React.JSX.Element;
25
+ /** `"docked"` outside an AppShell drawer — so a standalone `Sidebar` keeps its own `collapsed`. */
26
+ export declare function useNavSurface(): NavSurface;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ const NavSurfaceContext = React.createContext("docked");
5
+ function NavSurfaceProvider({
6
+ surface,
7
+ children
8
+ }) {
9
+ return /* @__PURE__ */ jsx(NavSurfaceContext.Provider, { value: surface, children });
10
+ }
11
+ function useNavSurface() {
12
+ return React.useContext(NavSurfaceContext);
13
+ }
14
+ export {
15
+ NavSurfaceProvider,
16
+ useNavSurface
17
+ };
@@ -1,9 +1,13 @@
1
1
  import * as React from "react";
2
2
  import type { OrgSwitcherProp } from "../../props/components/layout.prop.js";
3
3
  export type { OrgSwitcherLabels, OrgSwitcherOrganization, OrgSwitcherProp, OrgSwitcherProp as OrgSwitcherProps, } from "../../props/components/layout.prop.js";
4
+ /** Arbitrary `data-*` hooks — the one escape hatch this closed component keeps open. */
5
+ type DataAttributes = {
6
+ [key: `data-${string}`]: string | number | boolean | undefined;
7
+ };
4
8
  /**
5
9
  * Organization switcher with one stable shell contract: canonical trigger geometry, an optional
6
10
  * status badge, a searchable desktop popover, a focus-trapped bottom Sheet at/below the shared
7
11
  * `--sheet-responsive-breakpoint-width`, and explicit loading/empty/error states.
8
12
  */
9
- export declare function OrgSwitcher({ organizations, value, onValueChange, collapsed, disabled, loading, error, onRetry, labels, responsive, open, onOpenChange, className, }: OrgSwitcherProp): React.JSX.Element;
13
+ export declare function OrgSwitcher({ organizations, value, onValueChange, collapsed, disabled, loading, error, onRetry, labels, responsive, open, onOpenChange, className, ...rest }: OrgSwitcherProp & Pick<React.ComponentPropsWithoutRef<"button">, "id"> & DataAttributes): React.JSX.Element;
@@ -46,7 +46,7 @@ const OrgSwitcherTrigger = React.forwardRef(
46
46
  ref,
47
47
  type: "button",
48
48
  variant: "ghost",
49
- className: "ui-org-switcher-trigger",
49
+ className: cn("ui-org-switcher-trigger", "h-[length:var(--org-switcher-trigger-height)]"),
50
50
  "data-collapsed": collapsed ? "true" : void 0,
51
51
  disabled,
52
52
  "aria-label": label,
@@ -147,7 +147,8 @@ function OrgSwitcher({
147
147
  responsive = "auto",
148
148
  open,
149
149
  onOpenChange,
150
- className
150
+ className,
151
+ ...rest
151
152
  }) {
152
153
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
153
154
  const controlled = open !== void 0;
@@ -173,7 +174,8 @@ function OrgSwitcher({
173
174
  organization: current,
174
175
  collapsed,
175
176
  disabled: triggerDisabled,
176
- label: triggerLabel
177
+ label: triggerLabel,
178
+ ...rest
177
179
  }
178
180
  );
179
181
  const panel = /* @__PURE__ */ jsx(
@@ -1,4 +1,3 @@
1
- "use client";
2
1
  import { Separator } from "../ui/separator.js";
3
2
  export {
4
3
  Separator
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Slot } from "@radix-ui/react-slot";
4
+ import { Slot } from "../../lib/slot.js";
5
5
  import { ChevronDown } from "lucide-react";
6
6
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../data-display/collapsible.js";
7
7
  import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
8
8
  import { Tooltip, TooltipContent, TooltipTrigger } from "../feedback/tooltip.js";
9
+ import { useNavSurface } from "./nav-surface.js";
9
10
  import { useTranslation } from "../../i18n/use-translation.js";
10
11
  import { cn } from "../../lib/utils.js";
11
12
  function SidebarHeader({ children, className, ...props }) {
@@ -301,6 +302,8 @@ function Sidebar({
301
302
  }) {
302
303
  const { t } = useTranslation();
303
304
  const resolvedSections = sections ?? [];
305
+ const surface = useNavSurface();
306
+ collapsed = surface === "drawer" ? false : collapsed;
304
307
  return /* @__PURE__ */ jsxs("div", { className: "sb-root", "data-collapsed": collapsed ? "true" : void 0, children: [
305
308
  brand !== void 0 ? /* @__PURE__ */ jsx(SidebarHeader, { children: brand }) : product ? (() => {
306
309
  const interactive = onProductClick != null;
@@ -15,5 +15,18 @@ export type SplitPaneProps = {
15
15
  asideWidth?: "sm" | "md" | "lg";
16
16
  /** Accessible complementary landmark name; required when multiple panes share a document. */
17
17
  asideLabel?: string;
18
+ /**
19
+ * Fill the remaining height of the parent instead of growing with the content.
20
+ *
21
+ * Default `false` keeps the pane content-height — the right answer for a pane that sits in a
22
+ * scrolling page. Set it for an app-shell surface whose columns own their own scrolling (a chat
23
+ * transcript with a pinned composer, a full-height table beside a detail rail): the pane and
24
+ * both columns get a DEFINITE height, so `overflow` inside a column scrolls that column rather
25
+ * than pushing the page taller.
26
+ *
27
+ * The parent still decides how much height there is to fill — `fill` claims 100% of it, it does
28
+ * not invent it. Inside a flex column give the wrapper `flex: 1; min-height: 0`.
29
+ */
30
+ fill?: boolean;
18
31
  };
19
- export declare function SplitPane({ children, aside, asideWidth, asideLabel }: SplitPaneProps): import("react").JSX.Element;
32
+ export declare function SplitPane({ children, aside, asideWidth, asideLabel, fill, }: SplitPaneProps): import("react").JSX.Element;
@@ -1,18 +1,31 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- function SplitPane({ children, aside, asideWidth = "md", asideLabel }) {
2
+ function SplitPane({
3
+ children,
4
+ aside,
5
+ asideWidth = "md",
6
+ asideLabel,
7
+ fill = false
8
+ }) {
3
9
  const closed = aside === null || aside === void 0;
4
- return /* @__PURE__ */ jsx("div", { className: "ui-split-pane-scope", children: /* @__PURE__ */ jsxs(
5
- "div",
6
- {
7
- className: "ui-split-pane",
8
- "data-aside-width": asideWidth,
9
- "data-aside": closed ? "closed" : void 0,
10
- children: [
11
- /* @__PURE__ */ jsx("div", { className: "ui-split-pane-main", children }),
12
- closed ? null : /* @__PURE__ */ jsx("aside", { className: "ui-split-pane-aside", "aria-label": asideLabel, children: aside })
13
- ]
14
- }
15
- ) });
10
+ return (
11
+ // `data-fill` is published on BOTH wrappers, not just the grid: the scope element is the one
12
+ // the consumer's own box actually contains, so without it the height stops at the scope and
13
+ // the grid has nothing definite to fill. Two attributes here are what keeps the consumer from
14
+ // reaching past its own child with `[&>*>*]` to say the same thing.
15
+ /* @__PURE__ */ jsx("div", { className: "ui-split-pane-scope", "data-fill": fill ? "true" : void 0, children: /* @__PURE__ */ jsxs(
16
+ "div",
17
+ {
18
+ className: "ui-split-pane",
19
+ "data-aside-width": asideWidth,
20
+ "data-aside": closed ? "closed" : void 0,
21
+ "data-fill": fill ? "true" : void 0,
22
+ children: [
23
+ /* @__PURE__ */ jsx("div", { className: "ui-split-pane-main", children }),
24
+ closed ? null : /* @__PURE__ */ jsx("aside", { className: "ui-split-pane-aside", "aria-label": asideLabel, children: aside })
25
+ ]
26
+ }
27
+ ) })
28
+ );
16
29
  }
17
30
  export {
18
31
  SplitPane
@@ -1,7 +1,6 @@
1
- "use client";
2
1
  import { jsx } from "react/jsx-runtime";
3
2
  import * as React from "react";
4
- import { Slot } from "@radix-ui/react-slot";
3
+ import { Slot } from "../../lib/slot.js";
5
4
  import { cn } from "../../lib/utils.js";
6
5
  const TopbarItem = React.forwardRef(function TopbarItem2({ asChild = false, className, type, ...props }, ref) {
7
6
  const Comp = asChild ? Slot : "button";
@@ -110,7 +110,8 @@ const AppSettingPicker = React.forwardRef(
110
110
  }, [kind, ctx?.timezoneOptions, current, t, locale, fallbackLocale]);
111
111
  const unbound = current === void 0 || !handleChange;
112
112
  const Icon = ICON[kind];
113
- const iconOnly = resolvedAppearance === "icon";
113
+ const bar = resolvedAppearance === "bar";
114
+ const iconOnly = bar || resolvedAppearance === "icon";
114
115
  const inline = resolvedAppearance === "inline";
115
116
  const isCompact = compact && !inline;
116
117
  return /* @__PURE__ */ jsxs(
@@ -164,7 +165,24 @@ const AppSettingPicker = React.forwardRef(
164
165
  // thua lớp utilities mà SelectTrigger phát ra.
165
166
  cn(
166
167
  "ui-app-setting-picker-icon hover:bg-accent hover:text-accent-foreground",
167
- "w-[length:var(--control-height)] shrink-0 justify-center"
168
+ "w-[length:var(--control-height)] shrink-0 justify-center",
169
+ // `bar` — a CELL of the bar, not a control in it. Both overrides are UTILITIES
170
+ // for the reason the block above already records twice: SelectTrigger emits
171
+ // `rounded-[var(--control-radius)]` from controlTriggerClass and takes its
172
+ // height from `.ui-control`, and a rule in @layer components loses to both.
173
+ //
174
+ // HEIGHT IS `self-stretch` + `h-auto`, NOT A LENGTH. The bar's height comes
175
+ // from a grid row in AppShell, from --topbar-height standalone, or from the
176
+ // coarse-pointer override on touch — a cell that named any one of those would
177
+ // be wrong in the other two. `h-auto` is what releases `.ui-control`'s height
178
+ // so the stretch can take effect; without it the box stays 32px in a 48px bar
179
+ // and the hover paints a pill, which is the whole defect this value fixes.
180
+ // `min-h` keeps the touch target when the bar is SHORTER than --control-height.
181
+ // The radius reads --topbar-item-radius, the SAME knob `TopbarItem` uses, so a
182
+ // service that wants softer bar cells retunes one token and both follow. A
183
+ // `rounded-none` literal here would put the shape out of a theme's reach
184
+ // (rules #44/#45) and let the two bar cells drift apart.
185
+ bar && "h-auto min-h-[length:var(--control-height)] self-stretch rounded-[var(--topbar-item-radius)]"
168
186
  )
169
187
  ) : (
170
188
  // Labeled: sized to a per-kind width from `sm` up; below `sm` it hugs its content and
@@ -180,6 +198,7 @@ const AppSettingPicker = React.forwardRef(
180
198
  className
181
199
  ),
182
200
  "data-kind": kind,
201
+ "data-appearance": resolvedAppearance,
183
202
  "aria-label": t(ARIA_KEY[kind]),
184
203
  children: [
185
204
  inline ? null : /* @__PURE__ */ jsx(
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import type { AppSettingToggleProp } from "../../props/components/app.prop.js";
3
+ export type { AppSettingToggleKind, AppSettingToggleProp, AppSettingToggleProp as AppSettingToggleProps, } from "../../props/components/app.prop.js";
4
+ /**
5
+ * AppSettingToggle — ONE button that steps a single AppProvider setting to its next value.
6
+ *
7
+ * The no-menu counterpart to {@link AppSettingPicker}: same binding contract, same option order,
8
+ * one tap instead of open-then-choose. Reach for it when the value set is closed and short (theme,
9
+ * density, font size, clock format) and the control lives in a top bar, where a dropdown for three
10
+ * values is a menu nobody wanted to open.
11
+ *
12
+ * The accessible name carries BOTH the setting and its current value ("Theme: Dark"), because the
13
+ * glyph is the only visible state and a screen-reader user would otherwise be told only which
14
+ * setting they are standing on, never where it currently is.
15
+ */
16
+ export declare const AppSettingToggle: React.ForwardRefExoticComponent<AppSettingToggleProp & React.RefAttributes<HTMLButtonElement>>;