@ctrl-ui/react 0.9.6 → 0.9.8

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 (119) hide show
  1. package/README.md +38 -2
  2. package/dist/activity.d.ts +3 -1
  3. package/dist/activity.js +2 -1
  4. package/dist/blocks/agent-run-sidebar.js +11 -10
  5. package/dist/blocks/agent-run.d.ts +3 -1
  6. package/dist/blocks/agent-run.js +21 -13
  7. package/dist/blocks/coding-agent.js +50 -69
  8. package/dist/blocks/design-canvas-inspector.js +1 -1
  9. package/dist/blocks/design-canvas.js +41 -34
  10. package/dist/blocks/discussion.js +1 -1
  11. package/dist/blocks/file-explorer.js +66 -47
  12. package/dist/blocks/settings.js +37 -41
  13. package/dist/blocks/team-chat.js +2 -1
  14. package/dist/blocks/theme-toggle.d.ts +2 -1
  15. package/dist/blocks/theme-toggle.js +13 -4
  16. package/dist/hooks/use-mobile.d.ts +1 -1
  17. package/dist/hooks/use-mobile.js +1 -1
  18. package/dist/knob-contracts/badge-knobs.d.ts +1 -1
  19. package/dist/knob-contracts/badge-knobs.js +3 -1
  20. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  21. package/dist/knob-contracts/button-knobs.js +1 -0
  22. package/dist/knob-contracts/calendar-knobs.d.ts +1 -1
  23. package/dist/knob-contracts/calendar-knobs.js +9 -1
  24. package/dist/knob-contracts/card-knobs.d.ts +1 -1
  25. package/dist/knob-contracts/card-knobs.js +5 -1
  26. package/dist/knob-contracts/choice-knobs.d.ts +1 -1
  27. package/dist/knob-contracts/choice-knobs.js +1 -0
  28. package/dist/knob-contracts/collapsible-knobs.d.ts +1 -1
  29. package/dist/knob-contracts/collapsible-knobs.js +5 -1
  30. package/dist/knob-contracts/emoji-picker-knobs.d.ts +1 -1
  31. package/dist/knob-contracts/emoji-picker-knobs.js +3 -1
  32. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  33. package/dist/knob-contracts/field-knobs.js +17 -1
  34. package/dist/knob-contracts/item-knobs.d.ts +1 -1
  35. package/dist/knob-contracts/item-knobs.js +5 -1
  36. package/dist/knob-contracts/kbd-knobs.d.ts +1 -1
  37. package/dist/knob-contracts/kbd-knobs.js +4 -1
  38. package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
  39. package/dist/knob-contracts/page-layout-knobs.js +1 -0
  40. package/dist/knob-contracts/pagination-knobs.d.ts +1 -1
  41. package/dist/knob-contracts/pagination-knobs.js +2 -1
  42. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  43. package/dist/knob-contracts/popup-knobs.js +9 -0
  44. package/dist/knob-contracts/range-knobs.d.ts +1 -1
  45. package/dist/knob-contracts/range-knobs.js +2 -0
  46. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  47. package/dist/knob-contracts/sidebar-knobs.js +2 -0
  48. package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
  49. package/dist/knob-contracts/tabs-knobs.js +3 -0
  50. package/dist/knob-contracts/timeline-knobs.d.ts +1 -1
  51. package/dist/knob-contracts/timeline-knobs.js +4 -1
  52. package/dist/knob-contracts/tree-knobs.d.ts +1 -1
  53. package/dist/knob-contracts/tree-knobs.js +3 -1
  54. package/dist/skin.d.ts +2 -0
  55. package/dist/styles/index.css +2 -3
  56. package/dist/styles/recipes/activity.css +4 -0
  57. package/dist/styles/recipes/avatar.css +1 -1
  58. package/dist/styles/recipes/badge.css +17 -0
  59. package/dist/styles/recipes/button.css +8 -1
  60. package/dist/styles/recipes/calendar.css +82 -6
  61. package/dist/styles/recipes/card.css +40 -5
  62. package/dist/styles/recipes/choice.css +9 -2
  63. package/dist/styles/recipes/collapsible.css +8 -1
  64. package/dist/styles/recipes/color-picker-values.css +4 -0
  65. package/dist/styles/recipes/emoji-picker.css +29 -8
  66. package/dist/styles/recipes/field-parts.css +80 -5
  67. package/dist/styles/recipes/field.css +113 -40
  68. package/dist/styles/recipes/item.css +33 -5
  69. package/dist/styles/recipes/kbd.css +26 -6
  70. package/dist/styles/recipes/morphing-panel.css +1 -1
  71. package/dist/styles/recipes/page-layout.css +18 -2
  72. package/dist/styles/recipes/pagination.css +8 -0
  73. package/dist/styles/recipes/popup-controls.css +9 -0
  74. package/dist/styles/recipes/popup-drawer.css +1 -0
  75. package/dist/styles/recipes/popup-lists.css +55 -6
  76. package/dist/styles/recipes/popup-motion.css +7 -1
  77. package/dist/styles/recipes/popup.css +10 -0
  78. package/dist/styles/recipes/range.css +16 -2
  79. package/dist/styles/recipes/sidebar-mobile.css +1 -1
  80. package/dist/styles/recipes/sidebar-parts.css +17 -1
  81. package/dist/styles/recipes/sidebar.css +11 -0
  82. package/dist/styles/recipes/tabs.css +41 -8
  83. package/dist/styles/recipes/timeline.css +25 -2
  84. package/dist/styles/recipes/tree.css +33 -0
  85. package/dist/styles/theme.css +11 -1
  86. package/dist/ui/alert-dialog.js +1 -1
  87. package/dist/ui/badge.js +1 -1
  88. package/dist/ui/calendar.js +9 -4
  89. package/dist/ui/card.d.ts +3 -1
  90. package/dist/ui/card.js +2 -1
  91. package/dist/ui/dialog.js +1 -1
  92. package/dist/ui/dropdown-menu.d.ts +1 -1
  93. package/dist/ui/dropdown-menu.js +23 -6
  94. package/dist/ui/dropzone.js +10 -11
  95. package/dist/ui/field.d.ts +6 -2
  96. package/dist/ui/field.js +4 -2
  97. package/dist/ui/morphing-panel.d.ts +5 -1
  98. package/dist/ui/morphing-panel.js +4 -2
  99. package/dist/ui/select.js +2 -2
  100. package/dist/ui/sidebar-mobile.js +1 -1
  101. package/dist/ui/sidebar-provider.d.ts +3 -1
  102. package/dist/ui/sidebar-provider.js +3 -2
  103. package/dist/ui/sidebar-resize-rail.js +1 -1
  104. package/dist/ui/sidebar.d.ts +6 -5
  105. package/dist/ui/sidebar.js +10 -9
  106. package/dist/ui/tabs.d.ts +1 -1
  107. package/dist/ui/tabs.js +2 -13
  108. package/dist/ui/timeline.js +1 -1
  109. package/dist/ui/typography.d.ts +11 -5
  110. package/dist/ui/typography.js +18 -6
  111. package/lint/biome-preset.json +3 -0
  112. package/lint/class-policy.mjs +17 -0
  113. package/lint/eslint-plugin.mjs +87 -0
  114. package/lint/grit-rules.mjs +40 -0
  115. package/lint/no-component-style-classes.grit +16 -0
  116. package/lint/no-raw-type-classes.grit +2 -3
  117. package/lint/write-grit-rules.mjs +19 -0
  118. package/package.json +11 -2
  119. package/dist/styles/type-scale.css +0 -72
package/README.md CHANGED
@@ -47,10 +47,46 @@ const twMerge = extendTailwindMerge({ extend: { classGroups: { "font-size": [{ t
47
47
 
48
48
  Controls size their own text: pick the control's `size`, or set its `--cui-*-font-size` knob in your theme, rather than wrapping the label in a `text-*` span.
49
49
 
50
- With Biome, the shipped GritQL plugin errors on text sizes in `className`: the type rungs, Tailwind's `text-xs`…`text-9xl`, and arbitrary `text-[13px]`. Adopt it folder by folder through `overrides` while you migrate:
50
+ ## Lint rules
51
+
52
+ Two rules keep call sites honest, shipped for Biome, ESLint, and oxlint from one policy (`lint/class-policy.mjs`):
53
+
54
+ - `no-component-style-classes` errors when `className` on a component imported from `@ctrl-ui/react` (or a `components/control-ui` directory) sets what the component owns: colors, type, padding, borders, radius, shadows, state styles such as `hover:` and `data-[…]:`, or `!important`. Layout classes (`flex`, `gap-*`, widths, heights, margins, placement) stay allowed. Restyle through the component's props or its `--cui-*` knobs instead.
55
+ - `no-raw-type-classes` errors on text sizes in any `className`: the type rungs, Tailwind's `text-xs`…`text-9xl`, and arbitrary `text-[13px]`. It reaches beyond Control UI, so adopt it folder by folder while you migrate.
56
+
57
+ Biome, extending the shipped preset so rules added later arrive with the package update:
58
+
59
+ ```json
60
+ {
61
+ "extends": ["@ctrl-ui/react/biome"],
62
+ "overrides": [{ "includes": ["src/features/**"], "plugins": ["./node_modules/@ctrl-ui/react/lint/no-raw-type-classes.grit"] }]
63
+ }
64
+ ```
65
+
66
+ ESLint flat config:
67
+
68
+ ```js
69
+ import controlUi from "@ctrl-ui/react/eslint-plugin";
70
+
71
+ export default [{ plugins: { "control-ui": controlUi }, rules: { "control-ui/no-component-style-classes": "error" } }];
72
+ ```
73
+
74
+ oxlint, through its JS plugin support:
75
+
76
+ ```json
77
+ { "jsPlugins": ["@ctrl-ui/react/eslint-plugin"], "rules": { "control-ui/no-component-style-classes": "error" } }
78
+ ```
79
+
80
+ When your app imports components through its own wrapper package or folder, declare that path so the rule follows those imports too:
81
+
82
+ ```js
83
+ rules: { "control-ui/no-component-style-classes": ["error", { importSources: ["@acme/ui/components/"] }] }
84
+ ```
85
+
86
+ Biome's Grit plugins take no options, so the package ships `control-ui-grit` to write the rules with your paths. Run it on install so a package update regenerates them, gitignore `.control-ui`, and list `./.control-ui/no-component-style-classes.grit` under `plugins` in place of the preset:
51
87
 
52
88
  ```json
53
- { "overrides": [{ "includes": ["src/features/**"], "plugins": ["./node_modules/@ctrl-ui/react/lint/no-raw-type-classes.grit"] }] }
89
+ { "scripts": { "postinstall": "control-ui-grit --import-source=@acme/ui/components/ --out=.control-ui" } }
54
90
  ```
55
91
 
56
92
  ## Customize
@@ -45,8 +45,10 @@ export type ActivityDetailProps = ComponentProps<"div"> & {
45
45
  export declare function ActivityDetail({ className, ...props }: ActivityDetailProps): import("react").JSX.Element;
46
46
  export type ActivityDetailLabelProps = ActivityStyleProps<ComponentProps<"div">, ActivityKnobStyle>;
47
47
  export declare function ActivityDetailLabel({ className, ...props }: ActivityDetailLabelProps): import("react").JSX.Element;
48
+ export type ActivityDetailTone = "default" | "destructive";
48
49
  export type ActivityDetailContentProps = ActivityStyleProps<ComponentProps<"div">, ActivityKnobStyle> & {
49
50
  format?: ActivityDetailFormat;
51
+ tone?: ActivityDetailTone;
50
52
  };
51
- export declare function ActivityDetailContent({ format, className, ...props }: ActivityDetailContentProps): import("react").JSX.Element;
53
+ export declare function ActivityDetailContent({ format, tone, className, ...props }: ActivityDetailContentProps): import("react").JSX.Element;
52
54
  //#endregion
package/dist/activity.js CHANGED
@@ -168,12 +168,13 @@ function ActivityDetailLabel({ className, ...props }) {
168
168
  className: cn("uppercase", className)
169
169
  });
170
170
  }
171
- function ActivityDetailContent({ format = "text", className, ...props }) {
171
+ function ActivityDetailContent({ format = "text", tone = "default", className, ...props }) {
172
172
  return /* @__PURE__ */ jsx("div", {
173
173
  "data-control-ui": "activity",
174
174
  "data-control-family": "activity",
175
175
  "data-slot": "detail-content",
176
176
  "data-format": format,
177
+ "data-tone": tone,
177
178
  ...props,
178
179
  className: cn("min-w-0 whitespace-pre-wrap wrap-anywhere", format === "code" && "w-fit max-w-full", className)
179
180
  });
@@ -5,30 +5,30 @@ import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, Sideba
5
5
  import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarRail } from "../ui/sidebar.js";
6
6
  import { agentRunStatusLabels } from "./agent-run-status.js";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
- import { BotIcon, Check, CircleAlert, CircleDashed, CirclePause, CircleSlash, LoaderCircle, PlusIcon } from "lucide-react";
8
+ import { BotIcon, CheckIcon, CircleAlertIcon, CircleDashedIcon, CirclePauseIcon, CircleSlashIcon, LoaderCircleIcon, PlusIcon } from "lucide-react";
9
9
  //#region src/blocks/agent-run-sidebar.tsx
10
10
  const runStatusIcons = {
11
- running: /* @__PURE__ */ jsx(LoaderCircle, {
11
+ running: /* @__PURE__ */ jsx(LoaderCircleIcon, {
12
12
  className: "size-3.5 shrink-0 motion-safe:animate-spin",
13
13
  "aria-hidden": "true"
14
14
  }),
15
- waiting: /* @__PURE__ */ jsx(CirclePause, {
15
+ waiting: /* @__PURE__ */ jsx(CirclePauseIcon, {
16
16
  className: "size-3.5 shrink-0 text-warning-text",
17
17
  "aria-hidden": "true"
18
18
  }),
19
- success: /* @__PURE__ */ jsx(Check, {
19
+ success: /* @__PURE__ */ jsx(CheckIcon, {
20
20
  className: "size-3.5 shrink-0 text-success-text",
21
21
  "aria-hidden": "true"
22
22
  }),
23
- error: /* @__PURE__ */ jsx(CircleAlert, {
23
+ error: /* @__PURE__ */ jsx(CircleAlertIcon, {
24
24
  className: "size-3.5 shrink-0 text-destructive-text",
25
25
  "aria-hidden": "true"
26
26
  }),
27
- cancelled: /* @__PURE__ */ jsx(CircleSlash, {
27
+ cancelled: /* @__PURE__ */ jsx(CircleSlashIcon, {
28
28
  className: "size-3.5 shrink-0 text-muted-foreground",
29
29
  "aria-hidden": "true"
30
30
  }),
31
- pending: /* @__PURE__ */ jsx(CircleDashed, {
31
+ pending: /* @__PURE__ */ jsx(CircleDashedIcon, {
32
32
  className: "size-3.5 shrink-0 text-muted-foreground",
33
33
  "aria-hidden": "true"
34
34
  })
@@ -39,7 +39,8 @@ function AgentRunSidebar({ appName, brand, runs, activeRunId, sidebarFooter, onR
39
39
  collapsible: "offcanvas",
40
40
  children: [
41
41
  /* @__PURE__ */ jsxs(SidebarHeader, {
42
- className: "gap-1.5 border-b border-sidebar-border px-2 pb-2 pt-3",
42
+ divider: true,
43
+ className: "gap-1.5",
43
44
  children: [/* @__PURE__ */ jsxs("div", {
44
45
  className: "flex min-w-0 items-center gap-2 px-1 py-1",
45
46
  children: [brand ?? /* @__PURE__ */ jsx("span", {
@@ -65,11 +66,11 @@ function AgentRunSidebar({ appName, brand, runs, activeRunId, sidebarFooter, onR
65
66
  }) }) }) : null]
66
67
  }),
67
68
  /* @__PURE__ */ jsx(SidebarContent, { children: /* @__PURE__ */ jsxs(SidebarGroup, {
68
- className: "gap-1 pt-1",
69
+ className: "gap-1",
69
70
  children: [/* @__PURE__ */ jsx(SidebarGroupLabel, { children: "Runs" }), runs.length === 0 ? /* @__PURE__ */ jsx(Text, {
70
71
  size: "caption",
71
72
  tone: "muted",
72
- className: "px-2",
73
+ className: "mx-2",
73
74
  children: "No runs yet"
74
75
  }) : /* @__PURE__ */ jsx(SidebarMenu, { children: runs.map((run) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
75
76
  size: "sm",
@@ -4,6 +4,7 @@ import { Card } from "../ui/card.js";
4
4
  import { SidebarProviderProps } from "../ui/sidebar-provider.js";
5
5
  import "../ui/sidebar.js";
6
6
  import { TimelineItemProps } from "../ui/timeline.js";
7
+ import { TypeSize } from "../ui/typography.js";
7
8
  import { ComponentProps, ReactNode } from "react";
8
9
  //#region src/blocks/agent-run.d.ts
9
10
  type AgentRunLayout = "viewport" | "contained";
@@ -22,8 +23,9 @@ export declare function AgentRunBlock({ appName, brand, runs, activeRunId, onRun
22
23
  export type AgentRunElapsedProps = Omit<ComponentProps<"time">, "dateTime" | "children"> & {
23
24
  startedAt: number;
24
25
  endedAt?: number;
26
+ size?: TypeSize;
25
27
  };
26
- export declare function AgentRunElapsed({ startedAt, endedAt, className, ...props }: AgentRunElapsedProps): import("react").JSX.Element;
28
+ export declare function AgentRunElapsed({ startedAt, endedAt, size, className, ...props }: AgentRunElapsedProps): import("react").JSX.Element;
27
29
  export type AgentRunHeaderProps = Omit<ComponentProps<"header">, "title"> & {
28
30
  title: string;
29
31
  status: AgentRunStatus;
@@ -32,7 +32,8 @@ function AgentRunBlock({ appName = "Agents", brand, runs, activeRunId, onRunSele
32
32
  };
33
33
  return /* @__PURE__ */ jsxs(SidebarProvider, {
34
34
  layout,
35
- className: cn("bg-background text-foreground", layout === "viewport" && "h-svh overflow-hidden", className),
35
+ surface: "background",
36
+ className: cn(layout === "viewport" && "h-svh overflow-hidden", className),
36
37
  style: providerStyle,
37
38
  ...props,
38
39
  children: [/* @__PURE__ */ jsx(AgentRunSidebar, {
@@ -52,15 +53,20 @@ function AgentRunBlock({ appName = "Agents", brand, runs, activeRunId, onRunSele
52
53
  })]
53
54
  });
54
55
  }
55
- function AgentRunElapsed({ startedAt, endedAt, className, ...props }) {
56
+ function AgentRunElapsed({ startedAt, endedAt, size = "label", className, ...props }) {
56
57
  const now = useRunClock(endedAt === void 0);
57
58
  const end = endedAt ?? now;
58
59
  const ms = end === null ? null : Math.max(0, end - startedAt);
59
- return /* @__PURE__ */ jsx("time", {
60
- dateTime: ms === null ? void 0 : elapsedIsoDuration(ms),
61
- className: cn("text-label tabular-nums text-muted-foreground", className),
62
- ...props,
63
- children: ms === null ? "" : formatElapsed(ms)
60
+ return /* @__PURE__ */ jsx(Text, {
61
+ size,
62
+ tone: "muted",
63
+ numeric: true,
64
+ className,
65
+ children: /* @__PURE__ */ jsx("time", {
66
+ dateTime: ms === null ? void 0 : elapsedIsoDuration(ms),
67
+ ...props,
68
+ children: ms === null ? "" : formatElapsed(ms)
69
+ })
64
70
  });
65
71
  }
66
72
  function AgentRunHeader({ title, status, startedAt, endedAt, actions, className, ...props }) {
@@ -89,7 +95,7 @@ function AgentRunHeader({ title, status, startedAt, endedAt, actions, className,
89
95
  startedAt === void 0 ? null : /* @__PURE__ */ jsx(AgentRunElapsed, {
90
96
  startedAt,
91
97
  endedAt,
92
- className: "text-body"
98
+ size: "body"
93
99
  }),
94
100
  /* @__PURE__ */ jsx("div", {
95
101
  className: "ms-auto flex shrink-0 items-center gap-1",
@@ -205,7 +211,7 @@ function AgentRunToolCall({ name, summary, icon, input, output, errorText, start
205
211
  children: formatToolValue(output)
206
212
  })] }),
207
213
  errorText === void 0 ? null : /* @__PURE__ */ jsxs(ActivityDetail, { children: [/* @__PURE__ */ jsx(ActivityDetailLabel, { children: "Error" }), /* @__PURE__ */ jsx(ActivityDetailContent, {
208
- className: "text-destructive-text",
214
+ tone: "destructive",
209
215
  children: errorText
210
216
  })] })
211
217
  ] }) : null]
@@ -236,13 +242,15 @@ function AgentRunDetails({ title = "Details", items, ...props }) {
236
242
  title,
237
243
  ...props,
238
244
  children: /* @__PURE__ */ jsx("dl", {
239
- className: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-2 text-body",
245
+ className: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-2",
240
246
  children: items.map((item) => /* @__PURE__ */ jsxs("div", {
241
247
  className: "contents",
242
- children: [/* @__PURE__ */ jsx("dt", {
243
- className: "text-muted-foreground",
248
+ children: [/* @__PURE__ */ jsx(Text, {
249
+ as: "dt",
250
+ tone: "muted",
244
251
  children: item.label
245
- }), /* @__PURE__ */ jsx("dd", {
252
+ }), /* @__PURE__ */ jsx(Text, {
253
+ as: "dd",
246
254
  className: "min-w-0 break-words",
247
255
  children: item.value
248
256
  })]
@@ -5,8 +5,9 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/colla
5
5
  import { Heading, Text } from "../ui/typography.js";
6
6
  import { ChatLayout, ChatThread } from "../chat-layout.js";
7
7
  import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
8
- import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
8
+ import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub } from "../ui/sidebar-menu.js";
9
9
  import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInset, SidebarRail, SidebarTrigger } from "../ui/sidebar.js";
10
+ import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from "../ui/item.js";
10
11
  import { jsx, jsxs } from "react/jsx-runtime";
11
12
  import { BotIcon, ChevronRightIcon, CircleIcon, FolderIcon, PlusIcon, SearchIcon } from "lucide-react";
12
13
  //#region src/blocks/coding-agent.tsx
@@ -17,7 +18,8 @@ function CodingAgentBlock({ appName = "Agent", brand, sidebarTop, navigation = [
17
18
  };
18
19
  return /* @__PURE__ */ jsxs(SidebarProvider, {
19
20
  layout,
20
- className: cn("bg-background text-foreground", layout === "viewport" && "h-svh overflow-hidden", className),
21
+ surface: "background",
22
+ className: cn(layout === "viewport" && "h-svh overflow-hidden", className),
21
23
  style: providerStyle,
22
24
  ...props,
23
25
  children: [/* @__PURE__ */ jsx(CodingAgentSidebar, {
@@ -78,7 +80,8 @@ function CodingAgentSidebar({ appName, brand, sidebarTop, navigation, projects,
78
80
  collapsible: "offcanvas",
79
81
  children: [
80
82
  /* @__PURE__ */ jsxs(SidebarHeader, {
81
- className: "gap-1.5 border-b border-sidebar-border px-2 pb-2 pt-3",
83
+ divider: true,
84
+ className: "gap-1.5",
82
85
  children: [
83
86
  sidebarTop,
84
87
  /* @__PURE__ */ jsxs("div", {
@@ -117,25 +120,22 @@ function CodingAgentSidebar({ appName, brand, sidebarTop, navigation, projects,
117
120
  }) }) })
118
121
  ]
119
122
  }),
120
- /* @__PURE__ */ jsxs(SidebarContent, { children: [navigation.length > 0 ? /* @__PURE__ */ jsx(SidebarGroup, {
121
- className: "pb-0",
122
- children: /* @__PURE__ */ jsx(SidebarMenu, { children: navigation.map((item) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
123
- size: "sm",
124
- onClick: () => {
125
- onNavigationSelect?.(item);
126
- finishMobileNavigation();
127
- },
128
- children: [
129
- item.icon,
130
- /* @__PURE__ */ jsx("span", { children: item.label }),
131
- item.trailing ? /* @__PURE__ */ jsx("span", {
132
- className: "ms-auto shrink-0",
133
- children: item.trailing
134
- }) : null
135
- ]
136
- }) }, item.id)) })
137
- }) : null, /* @__PURE__ */ jsxs(SidebarGroup, {
138
- className: "gap-1 pt-1",
123
+ /* @__PURE__ */ jsxs(SidebarContent, { children: [navigation.length > 0 ? /* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: navigation.map((item) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
124
+ size: "sm",
125
+ onClick: () => {
126
+ onNavigationSelect?.(item);
127
+ finishMobileNavigation();
128
+ },
129
+ children: [
130
+ item.icon,
131
+ /* @__PURE__ */ jsx("span", { children: item.label }),
132
+ item.trailing ? /* @__PURE__ */ jsx("span", {
133
+ className: "ms-auto shrink-0",
134
+ children: item.trailing
135
+ }) : null
136
+ ]
137
+ }) }, item.id)) }) }) : null, /* @__PURE__ */ jsxs(SidebarGroup, {
138
+ className: "gap-1",
139
139
  children: [/* @__PURE__ */ jsx(SidebarGroupLabel, { children: "Projects" }), /* @__PURE__ */ jsx("div", {
140
140
  className: "grid gap-1",
141
141
  children: projects.map((project) => /* @__PURE__ */ jsxs(Collapsible, {
@@ -157,28 +157,25 @@ function CodingAgentSidebar({ appName, brand, sidebarTop, navigation, projects,
157
157
  className: "truncate",
158
158
  children: project.name
159
159
  })
160
- ] }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenu, {
161
- className: "mt-1 ps-5",
162
- children: project.tasks.map((task) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
163
- size: "sm",
164
- isActive: task.id === activeTaskId,
165
- onClick: () => {
166
- onTaskSelect?.(task, project);
167
- finishMobileNavigation();
168
- },
169
- children: [
170
- /* @__PURE__ */ jsx(CircleIcon, {
171
- className: "size-1.5 fill-current opacity-35",
172
- "aria-hidden": "true"
173
- }),
174
- /* @__PURE__ */ jsx("span", { children: task.title }),
175
- task.trailing ? /* @__PURE__ */ jsx("span", {
176
- className: "ms-auto shrink-0",
177
- children: task.trailing
178
- }) : null
179
- ]
180
- }) }, task.id))
181
- }) })]
160
+ ] }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { children: project.tasks.map((task) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
161
+ size: "sm",
162
+ isActive: task.id === activeTaskId,
163
+ onClick: () => {
164
+ onTaskSelect?.(task, project);
165
+ finishMobileNavigation();
166
+ },
167
+ children: [
168
+ /* @__PURE__ */ jsx(CircleIcon, {
169
+ className: "size-1.5 fill-current opacity-35",
170
+ "aria-hidden": "true"
171
+ }),
172
+ /* @__PURE__ */ jsx("span", { children: task.title }),
173
+ task.trailing ? /* @__PURE__ */ jsx("span", {
174
+ className: "ms-auto shrink-0",
175
+ children: task.trailing
176
+ }) : null
177
+ ]
178
+ }) }, task.id)) }) })]
182
179
  }, project.id))
183
180
  })]
184
181
  })] }),
@@ -228,31 +225,15 @@ function CodingAgentEmptyState({ title = "What should we build?", description, i
228
225
  }) : null,
229
226
  suggestions.length > 0 ? /* @__PURE__ */ jsx("div", {
230
227
  className: "mt-8 grid w-full grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4",
231
- children: suggestions.map((suggestion) => /* @__PURE__ */ jsx(Button, {
232
- variant: "surface",
233
- size: "lg",
234
- className: "h-auto min-h-28 w-full items-start justify-start whitespace-normal rounded-[var(--radius-panel)] px-4 py-4 text-start",
235
- onClick: () => onSuggestionSelect?.(suggestion.prompt ?? suggestion.title, suggestion),
236
- children: /* @__PURE__ */ jsxs("span", {
237
- className: "flex min-w-0 flex-col items-start gap-3",
238
- children: [
239
- /* @__PURE__ */ jsx("span", {
240
- className: "text-primary-text",
241
- children: suggestion.icon
242
- }),
243
- /* @__PURE__ */ jsx(Text, {
244
- weight: "medium",
245
- tone: "foreground",
246
- children: suggestion.title
247
- }),
248
- suggestion.description ? /* @__PURE__ */ jsx(Text, {
249
- size: "caption",
250
- tone: "muted",
251
- className: "leading-5",
252
- children: suggestion.description
253
- }) : null
254
- ]
255
- })
228
+ children: suggestions.map((suggestion) => /* @__PURE__ */ jsxs(Item, {
229
+ variant: "outline",
230
+ render: /* @__PURE__ */ jsx("button", {
231
+ type: "button",
232
+ onClick: () => onSuggestionSelect?.(suggestion.prompt ?? suggestion.title, suggestion)
233
+ }),
234
+ className: "min-h-28 w-full flex-col items-start text-start",
235
+ style: { "--cui-item-media-foreground": "var(--primary-text)" },
236
+ children: [/* @__PURE__ */ jsx(ItemMedia, { children: suggestion.icon }), /* @__PURE__ */ jsxs(ItemContent, { children: [/* @__PURE__ */ jsx(ItemTitle, { children: suggestion.title }), suggestion.description ? /* @__PURE__ */ jsx(ItemDescription, { children: suggestion.description }) : null] })]
256
237
  }, suggestion.id))
257
238
  }) : null
258
239
  ]
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { Button } from "../ui/button.js";
3
3
  import { Heading } from "../ui/typography.js";
4
+ import { Separator } from "../ui/separator.js";
4
5
  import { resizeDesignCanvasLayer } from "./design-canvas-data.js";
5
6
  import { ColorPickerArea } from "../ui/color-picker-area.js";
6
7
  import { ColorPickerHue } from "../ui/color-picker-sliders.js";
@@ -9,7 +10,6 @@ import { NumberField, NumberFieldGroup, NumberFieldInput, NumberFieldScrubArea }
9
10
  import { ColorPickerEyeDropper, ColorPickerInput } from "../ui/color-picker-values.js";
10
11
  import { ColorPicker, ColorPickerContent, ColorPickerTrigger } from "../ui/color-picker.js";
11
12
  import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "../ui/empty.js";
12
- import { Separator } from "../ui/separator.js";
13
13
  import { Toggle } from "../ui/toggle.js";
14
14
  import { useState } from "react";
15
15
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -11,7 +11,7 @@ import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator } from "../ui/to
11
11
  import { InfiniteCanvas, InfiniteCanvasContent, InfiniteCanvasControls, InfiniteCanvasItem } from "../ui/infinite-canvas.js";
12
12
  import { ResizableFloatingPanel } from "../ui/resizable.js";
13
13
  import { useRef, useState } from "react";
14
- import { jsx, jsxs } from "react/jsx-runtime";
14
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
15
15
  import { CircleIcon, FrameIcon, HandIcon, LayersIcon, MousePointer2Icon, SlidersHorizontalIcon, SquareIcon, TypeIcon } from "lucide-react";
16
16
  //#region src/blocks/design-canvas.tsx
17
17
  const TOOLS = [
@@ -53,10 +53,10 @@ const TOOLS = [
53
53
  }
54
54
  ];
55
55
  const LAYER_ICONS = {
56
- frame: /* @__PURE__ */ jsx(FrameIcon, {}),
57
- rectangle: /* @__PURE__ */ jsx(SquareIcon, {}),
58
- ellipse: /* @__PURE__ */ jsx(CircleIcon, {}),
59
- text: /* @__PURE__ */ jsx(TypeIcon, {})
56
+ frame: /* @__PURE__ */ jsx(FrameIcon, { className: "text-muted-foreground" }),
57
+ rectangle: /* @__PURE__ */ jsx(SquareIcon, { className: "text-muted-foreground" }),
58
+ ellipse: /* @__PURE__ */ jsx(CircleIcon, { className: "text-muted-foreground" }),
59
+ text: /* @__PURE__ */ jsx(TypeIcon, { className: "text-muted-foreground" })
60
60
  };
61
61
  const NUDGE_DIRECTIONS = {
62
62
  ArrowLeft: {
@@ -246,7 +246,7 @@ function DesignCanvasBlock({ layers, defaultLayers = [], onLayersChange, selecte
246
246
  onPointerCancel: cancelDraft,
247
247
  onKeyDown: handleShortcut,
248
248
  children: [/* @__PURE__ */ jsxs(InfiniteCanvasContent, {
249
- className: cn(tool !== "move" && "[&>*]:pointer-events-none"),
249
+ className: cn(tool !== "move" && "pointer-events-none"),
250
250
  children: [currentLayers.map((layer) => /* @__PURE__ */ jsx(DesignCanvasLayerNode, {
251
251
  layer,
252
252
  selected: layer.id === currentSelectedLayerId,
@@ -288,7 +288,6 @@ function DesignCanvasBlock({ layers, defaultLayers = [], onLayersChange, selecte
288
288
  onOpenChange: setLayersOpen,
289
289
  children: /* @__PURE__ */ jsx(DrawerContent, {
290
290
  side: "left",
291
- className: "gap-0 p-0",
292
291
  children: /* @__PURE__ */ jsx(DesignCanvasPanel, {
293
292
  label: layersLabel,
294
293
  title: layersLabel,
@@ -303,7 +302,6 @@ function DesignCanvasBlock({ layers, defaultLayers = [], onLayersChange, selecte
303
302
  onOpenChange: setPropertiesOpen,
304
303
  children: /* @__PURE__ */ jsx(DrawerContent, {
305
304
  side: "right",
306
- className: "gap-0 p-0",
307
305
  children: /* @__PURE__ */ jsx(DesignCanvasPanel, {
308
306
  label: propertiesLabel,
309
307
  title: propertiesTitle,
@@ -353,24 +351,27 @@ function DesignCanvasBlock({ layers, defaultLayers = [], onLayersChange, selecte
353
351
  });
354
352
  }
355
353
  function DesignCanvasPanel({ label, title, inDrawer = false, children }) {
356
- const titleClassName = "flex h-11 shrink-0 items-center border-b px-3";
357
- const titleText = /* @__PURE__ */ jsx("span", {
358
- className: "truncate",
359
- title,
360
- children: title
361
- });
362
354
  return /* @__PURE__ */ jsxs("aside", {
363
355
  "aria-label": label,
364
356
  className: "flex min-h-0 flex-1 flex-col",
365
- children: [inDrawer ? /* @__PURE__ */ jsx(DrawerTitle, {
366
- className: `${titleClassName} text-caption font-medium`,
367
- children: titleText
368
- }) : /* @__PURE__ */ jsx(Heading, {
369
- level: 2,
370
- size: "caption",
371
- weight: "medium",
372
- className: titleClassName,
373
- children: titleText
357
+ children: [/* @__PURE__ */ jsx("div", {
358
+ className: "flex h-11 shrink-0 items-center border-b px-3",
359
+ children: inDrawer ? /* @__PURE__ */ jsx(DrawerTitle, {
360
+ className: "truncate",
361
+ title,
362
+ style: {
363
+ "--cui-popup-title-font-size": "var(--text-caption)",
364
+ "--cui-popup-title-font-weight": "var(--font-weight-medium)"
365
+ },
366
+ children: title
367
+ }) : /* @__PURE__ */ jsx(Heading, {
368
+ level: 2,
369
+ size: "caption",
370
+ weight: "medium",
371
+ className: "truncate",
372
+ title,
373
+ children: title
374
+ })
374
375
  }), /* @__PURE__ */ jsx(ScrollArea, {
375
376
  className: "min-h-0 flex-1",
376
377
  children
@@ -385,7 +386,8 @@ function DesignCanvasLayerList({ layers, selectedLayerId, onSelect }) {
385
386
  size: "sm",
386
387
  active: layer.id === selectedLayerId,
387
388
  "aria-pressed": layer.id === selectedLayerId,
388
- className: "w-full justify-start [&_svg]:size-3.5 [&_svg]:text-muted-foreground",
389
+ className: "w-full justify-start",
390
+ style: { "--cui-button-icon": "calc(var(--spacing) * 3.5)" },
389
391
  onClick: () => onSelect(layer.id),
390
392
  children: [LAYER_ICONS[layer.kind], /* @__PURE__ */ jsx("span", {
391
393
  className: "truncate",
@@ -407,7 +409,6 @@ function DesignCanvasLayerNode({ layer, selected, onSelect, onKeyDown, onLayerCh
407
409
  "aria-pressed": interactive ? selected : void 0,
408
410
  "aria-keyshortcuts": interactive ? LAYER_KEY_SHORTCUTS : void 0,
409
411
  "data-selected": selected || void 0,
410
- className: "outline-primary data-selected:outline-2",
411
412
  style: {
412
413
  width: layer.width,
413
414
  height: layer.height,
@@ -442,15 +443,21 @@ function DesignCanvasLayerNode({ layer, selected, onSelect, onKeyDown, onLayerCh
442
443
  borderRadius: layer.kind === "ellipse" ? "50%" : layer.cornerRadius
443
444
  }
444
445
  }),
445
- selected ? /* @__PURE__ */ jsxs(Text, {
446
- size: "micro",
447
- className: "absolute inset-x-0 top-full mx-auto mt-1.5 w-max rounded-sm bg-primary px-1 whitespace-nowrap text-primary-foreground tabular-nums",
448
- children: [
449
- layer.width,
450
- " × ",
451
- layer.height
452
- ]
453
- }) : null
446
+ selected ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
447
+ "aria-hidden": "true",
448
+ className: "absolute inset-0 outline-2 outline-primary"
449
+ }), /* @__PURE__ */ jsx("span", {
450
+ className: "absolute inset-x-0 top-full mx-auto mt-1.5 flex w-max rounded-sm bg-primary px-1 whitespace-nowrap text-primary-foreground",
451
+ children: /* @__PURE__ */ jsxs(Text, {
452
+ size: "micro",
453
+ numeric: true,
454
+ children: [
455
+ layer.width,
456
+ " × ",
457
+ layer.height
458
+ ]
459
+ })
460
+ })] }) : null
454
461
  ]
455
462
  });
456
463
  }
@@ -70,7 +70,7 @@ function DiscussionComposer({ label, placeholder, submitLabel = "Post", secondar
70
70
  error ? /* @__PURE__ */ jsx(Text, {
71
71
  as: "p",
72
72
  role: "alert",
73
- className: "px-3",
73
+ className: "mx-3",
74
74
  children: error
75
75
  }) : null,
76
76
  /* @__PURE__ */ jsxs(ChatComposerToolbar, { children: [/* @__PURE__ */ jsx(Text, {