banhaten 0.4.0 → 0.6.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 (214) hide show
  1. package/CHANGELOG.md +78 -1
  2. package/CONTRIBUTING.md +6 -4
  3. package/README.md +4 -2
  4. package/docs/design-spacing.md +13 -0
  5. package/docs/design-system/README.md +2 -0
  6. package/docs/design-system/appearances/default.md +1 -1
  7. package/docs/design-system/appearances/rounded.md +1 -1
  8. package/docs/design-system/appearances/sharp.md +1 -1
  9. package/docs/design-system/component-showcase-consistency-report.md +5 -5
  10. package/docs/design-system/components/README.md +15 -0
  11. package/docs/design-system/components/accordion.md +1 -1
  12. package/docs/design-system/components/activity-feed.md +1 -1
  13. package/docs/design-system/components/activity-heatmap.md +1 -1
  14. package/docs/design-system/components/agent-input.md +85 -0
  15. package/docs/design-system/components/alert-dialog.md +1 -1
  16. package/docs/design-system/components/alert.md +10 -4
  17. package/docs/design-system/components/app-header.md +97 -0
  18. package/docs/design-system/components/approval-card.md +134 -0
  19. package/docs/design-system/components/attribute.md +14 -2
  20. package/docs/design-system/components/avatar.md +1 -1
  21. package/docs/design-system/components/badge.md +1 -1
  22. package/docs/design-system/components/banner.md +1 -1
  23. package/docs/design-system/components/breadcrumbs.md +8 -3
  24. package/docs/design-system/components/button-group.md +1 -1
  25. package/docs/design-system/components/button.md +8 -5
  26. package/docs/design-system/components/card.md +15 -1
  27. package/docs/design-system/components/catalog-components.md +1 -1
  28. package/docs/design-system/components/chart.md +95 -24
  29. package/docs/design-system/components/checkbox.md +1 -1
  30. package/docs/design-system/components/collapsible.md +20 -6
  31. package/docs/design-system/components/command.md +1 -1
  32. package/docs/design-system/components/context-menu.md +1 -1
  33. package/docs/design-system/components/date-picker.md +1 -1
  34. package/docs/design-system/components/file-upload.md +1 -1
  35. package/docs/design-system/components/hover-card.md +1 -1
  36. package/docs/design-system/components/inline-citations.md +93 -0
  37. package/docs/design-system/components/input-group.md +1 -1
  38. package/docs/design-system/components/input.md +59 -3
  39. package/docs/design-system/components/item.md +1 -1
  40. package/docs/design-system/components/kbd.md +6 -4
  41. package/docs/design-system/components/menu.md +35 -4
  42. package/docs/design-system/components/menubar.md +1 -1
  43. package/docs/design-system/components/modal.md +2 -2
  44. package/docs/design-system/components/native-select.md +1 -1
  45. package/docs/design-system/components/navigation-menu.md +1 -1
  46. package/docs/design-system/components/onboarding-step-list-item.md +1 -1
  47. package/docs/design-system/components/page-header.md +58 -17
  48. package/docs/design-system/components/pagination.md +2 -2
  49. package/docs/design-system/components/popover.md +1 -1
  50. package/docs/design-system/components/progress-slider.md +1 -1
  51. package/docs/design-system/components/radio-card.md +1 -1
  52. package/docs/design-system/components/radio-group.md +1 -1
  53. package/docs/design-system/components/resizable.md +1 -1
  54. package/docs/design-system/components/rolling-count.md +61 -0
  55. package/docs/design-system/components/scroll-area.md +1 -1
  56. package/docs/design-system/components/select.md +12 -2
  57. package/docs/design-system/components/sheet.md +1 -1
  58. package/docs/design-system/components/shimmer-text.md +67 -0
  59. package/docs/design-system/components/sidebar.md +75 -8
  60. package/docs/design-system/components/skeleton.md +2 -2
  61. package/docs/design-system/components/slideout.md +1 -1
  62. package/docs/design-system/components/slider.md +2 -2
  63. package/docs/design-system/components/social-button.md +2 -2
  64. package/docs/design-system/components/streaming-text.md +82 -0
  65. package/docs/design-system/components/table.md +103 -21
  66. package/docs/design-system/components/tabs.md +25 -6
  67. package/docs/design-system/components/tag.md +1 -1
  68. package/docs/design-system/components/task-list.md +95 -0
  69. package/docs/design-system/components/textarea.md +1 -1
  70. package/docs/design-system/components/thinking-reasoning.md +72 -0
  71. package/docs/design-system/components/thinking-state.md +67 -0
  72. package/docs/design-system/components/timeline.md +1 -1
  73. package/docs/design-system/components/toast.md +1 -1
  74. package/docs/design-system/components/toggle.md +2 -2
  75. package/docs/design-system/components/toolbar.md +11 -6
  76. package/docs/design-system/components/tooltip.md +6 -4
  77. package/docs/design-system/density-and-elevation.md +16 -16
  78. package/docs/design-system/design-system-test-missing-items.md +8 -8
  79. package/docs/design-system/installation.md +2 -0
  80. package/docs/design-system/tabs.md +2 -1
  81. package/docs/design-system/template-gap-audit.md +32 -0
  82. package/docs/design-system/templates.md +59 -0
  83. package/docs/design-system/token-governance.md +15 -2
  84. package/package.json +2 -2
  85. package/registry/components/accordion.tsx +2 -2
  86. package/registry/components/agent-input-parts.tsx +552 -0
  87. package/registry/components/agent-input-state.ts +156 -0
  88. package/registry/components/agent-input.css +307 -0
  89. package/registry/components/agent-input.tsx +382 -0
  90. package/registry/components/alert-dialog.tsx +2 -2
  91. package/registry/components/alert.tsx +23 -7
  92. package/registry/components/app-header.tsx +151 -0
  93. package/registry/components/approval-card.css +409 -0
  94. package/registry/components/approval-card.tsx +401 -0
  95. package/registry/components/approval-frame.tsx +108 -0
  96. package/registry/components/approval-questions.tsx +559 -0
  97. package/registry/components/attribute.tsx +48 -13
  98. package/registry/components/autocomplete.tsx +15 -68
  99. package/registry/components/avatar.tsx +2 -2
  100. package/registry/components/badge.tsx +8 -7
  101. package/registry/components/button-group.tsx +2 -2
  102. package/registry/components/button.tsx +19 -14
  103. package/registry/components/card.tsx +18 -5
  104. package/registry/components/chart-block-data.ts +177 -0
  105. package/registry/components/chart-block.css +171 -0
  106. package/registry/components/chart-block.tsx +260 -0
  107. package/registry/components/chart-interactions.ts +123 -0
  108. package/registry/components/chart-options.cartesian.ts +162 -0
  109. package/registry/components/chart-options.paint.ts +99 -0
  110. package/registry/components/chart-options.parts.ts +72 -0
  111. package/registry/components/chart-options.radial.ts +109 -0
  112. package/registry/components/chart-options.ts +44 -0
  113. package/registry/components/chart-options.types.ts +67 -0
  114. package/registry/components/chart.tsx +1 -1
  115. package/registry/components/checkbox.tsx +6 -6
  116. package/registry/components/collapsible.tsx +43 -3
  117. package/registry/components/command.tsx +1 -1
  118. package/registry/components/context-menu.tsx +1 -1
  119. package/registry/components/data-chart.tsx +40 -0
  120. package/registry/components/date-picker.tsx +9 -9
  121. package/registry/components/echart-theme.ts +246 -0
  122. package/registry/components/echart.css +49 -0
  123. package/registry/components/echart.tsx +178 -0
  124. package/registry/components/expanded/Breadcrumbs.tsx +5 -1
  125. package/registry/components/expanded/PageHeader.tsx +67 -24
  126. package/registry/components/expanded/Table.tsx +60 -22
  127. package/registry/components/expanded/activityFeed.css +2 -2
  128. package/registry/components/expanded/activityHeatmap.css +1 -1
  129. package/registry/components/expanded/banner.css +2 -2
  130. package/registry/components/expanded/breadcrumbs.css +19 -2
  131. package/registry/components/expanded/catalogComponentsShowcase.css +1 -1
  132. package/registry/components/expanded/fileUpload.css +7 -7
  133. package/registry/components/expanded/onboardingStepListItem.css +2 -2
  134. package/registry/components/expanded/pageHeader.css +38 -44
  135. package/registry/components/expanded/slideout.css +4 -4
  136. package/registry/components/expanded/table.css +58 -9
  137. package/registry/components/expanded/timeline.css +1 -1
  138. package/registry/components/hover-card.tsx +1 -1
  139. package/registry/components/inline-citations.css +126 -0
  140. package/registry/components/inline-citations.tsx +292 -0
  141. package/registry/components/input-group.tsx +4 -4
  142. package/registry/components/input.tsx +150 -137
  143. package/registry/components/item.tsx +1 -1
  144. package/registry/components/kbd.tsx +3 -2
  145. package/registry/components/menu.tsx +117 -12
  146. package/registry/components/menubar.tsx +2 -2
  147. package/registry/components/modal.tsx +3 -3
  148. package/registry/components/native-select.tsx +4 -4
  149. package/registry/components/navigation-menu.tsx +2 -2
  150. package/registry/components/pagination.tsx +4 -4
  151. package/registry/components/popover.tsx +3 -3
  152. package/registry/components/progress-slider.tsx +1 -1
  153. package/registry/components/radio-card.tsx +7 -7
  154. package/registry/components/radio-group.tsx +2 -2
  155. package/registry/components/resizable.tsx +2 -2
  156. package/registry/components/rolling-count.css +37 -0
  157. package/registry/components/rolling-count.tsx +110 -0
  158. package/registry/components/scroll-area.tsx +1 -1
  159. package/registry/components/select.tsx +15 -10
  160. package/registry/components/sheet.tsx +2 -2
  161. package/registry/components/shimmer-text.css +69 -0
  162. package/registry/components/shimmer-text.tsx +43 -0
  163. package/registry/components/sidebar.tsx +528 -223
  164. package/registry/components/skeleton.tsx +1 -1
  165. package/registry/components/slider.tsx +2 -2
  166. package/registry/components/social-button.tsx +1 -1
  167. package/registry/components/streaming-text.css +45 -0
  168. package/registry/components/streaming-text.tsx +165 -0
  169. package/registry/components/table-elements.tsx +164 -5
  170. package/registry/components/tabs.css +32 -4
  171. package/registry/components/tabs.tsx +5 -1
  172. package/registry/components/tag.tsx +1 -1
  173. package/registry/components/task-list.css +328 -0
  174. package/registry/components/task-list.tsx +429 -0
  175. package/registry/components/textarea.tsx +4 -4
  176. package/registry/components/thinking-reasoning.css +178 -0
  177. package/registry/components/thinking-reasoning.tsx +253 -0
  178. package/registry/components/thinking-state.css +8 -0
  179. package/registry/components/thinking-state.tsx +46 -0
  180. package/registry/components/toast.tsx +1 -1
  181. package/registry/components/toggle.tsx +2 -2
  182. package/registry/components/toolbar.tsx +6 -6
  183. package/registry/components/tooltip.tsx +6 -4
  184. package/registry/examples/agent-input-demo.tsx +90 -0
  185. package/registry/examples/app-header-demo.tsx +40 -0
  186. package/registry/examples/approval-card-demo.tsx +83 -0
  187. package/registry/examples/button-demo.tsx +3 -0
  188. package/registry/examples/chart-block-demo.tsx +5 -0
  189. package/registry/examples/chart-demo.tsx +6 -21
  190. package/registry/examples/collection-filter-bar-demo.tsx +1 -1
  191. package/registry/examples/control-density-contract-demo.tsx +5 -1
  192. package/registry/examples/inline-citations-demo.tsx +31 -0
  193. package/registry/examples/input-actions-demo.tsx +36 -0
  194. package/registry/examples/menu-demo.tsx +1 -1
  195. package/registry/examples/popover-demo.tsx +1 -0
  196. package/registry/examples/rolling-count-demo.tsx +39 -0
  197. package/registry/examples/shimmer-text-demo.tsx +20 -0
  198. package/registry/examples/streaming-text-demo.tsx +28 -0
  199. package/registry/examples/task-list-demo.tsx +74 -0
  200. package/registry/examples/thinking-reasoning-demo.tsx +74 -0
  201. package/registry/examples/thinking-state-demo.tsx +13 -0
  202. package/registry/guidance/charts.LICENSE.txt +24 -0
  203. package/registry/hooks/use-controllable-state.ts +33 -0
  204. package/registry/hooks/use-listbox-navigation.ts +73 -0
  205. package/registry/hooks/use-motion-end.ts +55 -0
  206. package/registry/hooks/use-reduced-motion.ts +28 -0
  207. package/registry/index.json +1808 -190
  208. package/registry/styles/globals.css +992 -1088
  209. package/registry.json +385 -10
  210. package/schema/registry.schema.json +5 -1
  211. package/schema/tokens.schema.json +8 -4
  212. package/src/cli/file-transaction.js +15 -8
  213. package/src/cli/index.js +22 -29
  214. package/tokens/banhaten.tokens.json +490 -192
@@ -0,0 +1,429 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ ChevronDownIcon,
6
+ CircleArrowRightIcon,
7
+ CircleCheckIcon,
8
+ ListChecksIcon,
9
+ } from "lucide-react"
10
+
11
+ import { cn } from "@/lib/utils"
12
+ import {
13
+ Collapsible,
14
+ CollapsiblePanel,
15
+ CollapsibleTrigger,
16
+ } from "@/components/ui/collapsible"
17
+ import { RollingCount } from "@/components/ui/rolling-count"
18
+ import { ShimmerText } from "@/components/ui/shimmer-text"
19
+ import { useControllableState } from "@/hooks/use-controllable-state"
20
+
21
+ import "./task-list.css"
22
+
23
+ type TaskStatus = "pending" | "active" | "done"
24
+
25
+ type TaskListItem = {
26
+ id: string
27
+ label: React.ReactNode
28
+ status?: TaskStatus
29
+ }
30
+
31
+ type TaskListSurface = "card" | "well" | "plain"
32
+
33
+ type TaskListLabels = {
34
+ /**
35
+ * Accessible summary of the counter, for example "2 of 5 done". The toggle
36
+ * reads it after the title, so its name is "To-dos 2 of 5 done".
37
+ */
38
+ count: (done: number, total: number) => string
39
+ }
40
+
41
+ type TaskListPreviewLabels = {
42
+ /** Label of the control that reveals the remaining items. */
43
+ more: (count: number) => string
44
+ /** Label of the control that hides the remaining items again. */
45
+ less: string
46
+ }
47
+
48
+ type TaskListRootProps = React.ComponentProps<"div"> & {
49
+ surface?: TaskListSurface
50
+ }
51
+
52
+ type TaskListHeaderProps = React.ComponentProps<"div"> & {
53
+ /** Shown at the end of the header, for example a total. */
54
+ count?: React.ReactNode
55
+ /**
56
+ * Rows the header icon summarizes: a list before work starts, a progress pie
57
+ * while it runs, and a check once every row is done.
58
+ */
59
+ items?: readonly TaskListItem[]
60
+ }
61
+
62
+ type TaskListItemsProps = Omit<React.ComponentProps<"ul">, "children"> & {
63
+ items: readonly TaskListItem[]
64
+ }
65
+
66
+ type TaskListPreviewProps = Omit<React.ComponentProps<"div">, "children"> & {
67
+ items: readonly TaskListItem[]
68
+ labels?: Partial<TaskListPreviewLabels>
69
+ /** Rows shown before the rest fold behind a "more" control. */
70
+ visibleCount?: number
71
+ }
72
+
73
+ type TaskListProps = Omit<React.ComponentProps<"div">, "children" | "title"> & {
74
+ defaultOpen?: boolean
75
+ items: readonly TaskListItem[]
76
+ labels?: Partial<TaskListLabels>
77
+ onOpenChange?: (open: boolean) => void
78
+ open?: boolean
79
+ surface?: TaskListSurface
80
+ title?: React.ReactNode
81
+ }
82
+
83
+ const defaultLabels: TaskListLabels = {
84
+ count: (done, total) => `${done} of ${total} done`,
85
+ }
86
+
87
+ const defaultPreviewLabels: TaskListPreviewLabels = {
88
+ less: "Show less",
89
+ more: (count) => `${count} more`,
90
+ }
91
+
92
+ const noItems: readonly TaskListItem[] = []
93
+
94
+ type TaskListState = "idle" | "running" | "done"
95
+
96
+ function summarize(items: readonly TaskListItem[]) {
97
+ const total = items.length
98
+ const done = items.filter((item) => item.status === "done").length
99
+ const running = items.some((item) => item.status === "active")
100
+ const state: TaskListState =
101
+ total > 0 && done === total ? "done" : running || done > 0 ? "running" : "idle"
102
+
103
+ return {
104
+ done,
105
+ progress: total > 0 ? Math.round((done / total) * 100) : 0,
106
+ state,
107
+ total,
108
+ }
109
+ }
110
+
111
+ type TaskListContextValue = {
112
+ setTitleId: (id: string | undefined) => void
113
+ titleId: string | undefined
114
+ }
115
+
116
+ /** Lets the rows of a list take their name from its header. */
117
+ const TaskListContext = React.createContext<TaskListContextValue | null>(null)
118
+
119
+ /** Ten even dashes: pathLength makes the dash period divide the circle exactly,
120
+ * and the ring starts at 12 o'clock with a dash centered there, so it stays
121
+ * symmetric (and unchanged when mirrored in RTL). */
122
+ function DashedCircleIcon(props: React.ComponentProps<"svg">) {
123
+ return (
124
+ <svg aria-hidden="true" fill="none" viewBox="0 0 24 24" {...props}>
125
+ <circle
126
+ cx="12"
127
+ cy="12"
128
+ pathLength="54"
129
+ r="9"
130
+ stroke="currentColor"
131
+ strokeDasharray="1.8 3.6"
132
+ strokeDashoffset="0.9"
133
+ strokeLinecap="round"
134
+ strokeWidth="var(--bh-icon-stroke-180)"
135
+ transform="rotate(-90 12 12)"
136
+ />
137
+ </svg>
138
+ )
139
+ }
140
+
141
+ function FilledCheckIcon(props: React.ComponentProps<"svg">) {
142
+ return (
143
+ <svg aria-hidden="true" viewBox="0 0 24 24" {...props}>
144
+ <path
145
+ clipRule="evenodd"
146
+ d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
147
+ fill="currentColor"
148
+ fillRule="evenodd"
149
+ />
150
+ </svg>
151
+ )
152
+ }
153
+
154
+ type TaskListHeaderContentProps = {
155
+ chevron?: boolean
156
+ count?: React.ReactNode
157
+ items: readonly TaskListItem[]
158
+ title: React.ReactNode
159
+ }
160
+
161
+ /** Icon, title, and count shared by the toggle header and the static one. */
162
+ function TaskListHeaderContent({
163
+ chevron = false,
164
+ count,
165
+ items,
166
+ title,
167
+ }: TaskListHeaderContentProps) {
168
+ const { progress, state } = summarize(items)
169
+ const titleId = React.useId()
170
+ const register = React.useContext(TaskListContext)?.setTitleId
171
+
172
+ React.useEffect(() => {
173
+ if (!register) return
174
+ register(titleId)
175
+ return () => register(undefined)
176
+ }, [register, titleId])
177
+
178
+ return (
179
+ <>
180
+ <span
181
+ aria-hidden="true"
182
+ data-slot="task-list-header-icon"
183
+ data-state={state}
184
+ >
185
+ <ListChecksIcon data-icon="list" strokeWidth="var(--bh-icon-stroke-200)" />
186
+ <span
187
+ data-icon="progress"
188
+ data-slot="task-list-progress"
189
+ style={{ "--task-list-progress": `${progress}%` } as React.CSSProperties}
190
+ >
191
+ <DashedCircleIcon data-slot="task-list-progress-ring" />
192
+ </span>
193
+ <FilledCheckIcon data-icon="done" />
194
+ {chevron ? (
195
+ <ChevronDownIcon
196
+ data-icon="chevron"
197
+ strokeWidth="var(--bh-icon-stroke-180)"
198
+ />
199
+ ) : null}
200
+ </span>
201
+ <span data-slot="task-list-title" dir="auto" id={titleId}>
202
+ {title}
203
+ </span>
204
+ {count === undefined || count === null ? null : (
205
+ <>
206
+ {/* Keeps title and count apart in the toggle's accessible name.
207
+ Flex layout drops the space, so nothing moves on screen. */}{" "}
208
+ <span data-slot="task-list-count">{count}</span>
209
+ </>
210
+ )}
211
+ </>
212
+ )
213
+ }
214
+
215
+ type TaskListRowProps = {
216
+ index: number
217
+ item: TaskListItem
218
+ }
219
+
220
+ function TaskListRow({ index, item }: TaskListRowProps) {
221
+ const status = item.status ?? "pending"
222
+
223
+ return (
224
+ <li
225
+ data-slot="task-list-item"
226
+ data-status={status}
227
+ style={{ "--task-list-index": index } as React.CSSProperties}
228
+ >
229
+ <span data-slot="task-list-item-icon">
230
+ <DashedCircleIcon data-icon="pending" />
231
+ <CircleArrowRightIcon
232
+ aria-hidden="true"
233
+ data-icon="active"
234
+ strokeWidth="var(--bh-icon-stroke-180)"
235
+ />
236
+ <CircleCheckIcon
237
+ aria-hidden="true"
238
+ data-icon="done"
239
+ strokeWidth="var(--bh-icon-stroke-180)"
240
+ />
241
+ </span>
242
+ <span data-slot="task-list-label" dir="auto">
243
+ <span data-slot="task-list-label-text">{item.label}</span>
244
+ <span aria-hidden="true" data-slot="task-list-label-shine">
245
+ <ShimmerText animated={status === "active"} tone="default">
246
+ {item.label}
247
+ </ShimmerText>
248
+ </span>
249
+ </span>
250
+ </li>
251
+ )
252
+ }
253
+
254
+ /** Surface of a composed list. Its header names the rows inside it. */
255
+ const TaskListRoot = React.forwardRef<HTMLDivElement, TaskListRootProps>(
256
+ function TaskListRoot({ className, surface = "card", ...props }, ref) {
257
+ const [titleId, setTitleId] = React.useState<string>()
258
+ const context = React.useMemo(() => ({ setTitleId, titleId }), [titleId])
259
+
260
+ return (
261
+ <TaskListContext.Provider value={context}>
262
+ <div
263
+ data-slot="task-list"
264
+ data-surface={surface}
265
+ ref={ref}
266
+ className={cn("min-w-0 max-w-full", className)}
267
+ {...props}
268
+ />
269
+ </TaskListContext.Provider>
270
+ )
271
+ }
272
+ )
273
+
274
+ /** Static header: status icon, title, and an optional count. */
275
+ const TaskListHeader = React.forwardRef<HTMLDivElement, TaskListHeaderProps>(
276
+ function TaskListHeader({ children, count, items = noItems, ...props }, ref) {
277
+ return (
278
+ <div data-slot="task-list-header" ref={ref} {...props}>
279
+ <TaskListHeaderContent count={count} items={items} title={children} />
280
+ </div>
281
+ )
282
+ }
283
+ )
284
+
285
+ /** Rows moving between pending, active, and done. */
286
+ const TaskListItems = React.forwardRef<HTMLUListElement, TaskListItemsProps>(
287
+ function TaskListItems({ "aria-labelledby": labelledBy, items, ...props }, ref) {
288
+ const titleId = React.useContext(TaskListContext)?.titleId
289
+
290
+ return (
291
+ <ul
292
+ aria-labelledby={labelledBy ?? titleId}
293
+ data-slot="task-list-items"
294
+ ref={ref}
295
+ {...props}
296
+ >
297
+ {items.map((item, index) => (
298
+ <TaskListRow index={index} item={item} key={item.id} />
299
+ ))}
300
+ </ul>
301
+ )
302
+ }
303
+ )
304
+
305
+ /** Shows the first rows and folds the rest behind a "more" control. */
306
+ const TaskListPreview = React.forwardRef<HTMLDivElement, TaskListPreviewProps>(
307
+ function TaskListPreview(
308
+ { items, labels: labelOverrides, visibleCount = 3, ...props },
309
+ ref
310
+ ) {
311
+ const labels = { ...defaultPreviewLabels, ...labelOverrides }
312
+ const [expanded, setExpanded] = React.useState(false)
313
+ const split = Math.max(0, visibleCount)
314
+ const rest = items.slice(split)
315
+
316
+ return (
317
+ <div data-slot="task-list-preview" ref={ref} {...props}>
318
+ <TaskListItems items={items.slice(0, split)} />
319
+ {rest.length > 0 ? (
320
+ <Collapsible onOpenChange={setExpanded} open={expanded}>
321
+ <CollapsiblePanel data-slot="task-list-collapsible">
322
+ <TaskListItems data-rest="true" items={rest} />
323
+ </CollapsiblePanel>
324
+ <CollapsibleTrigger asChild>
325
+ <button data-slot="task-list-more" type="button">
326
+ <span aria-hidden="true" data-slot="task-list-more-icon">
327
+ <svg viewBox="0 0 24 24">
328
+ {expanded ? (
329
+ <rect
330
+ fill="currentColor"
331
+ height="1.5"
332
+ rx="0.75"
333
+ width="14.5"
334
+ x="4.75"
335
+ y="11.25"
336
+ />
337
+ ) : (
338
+ <>
339
+ <circle cx="6" cy="12" fill="currentColor" r="1.25" />
340
+ <circle cx="12" cy="12" fill="currentColor" r="1.25" />
341
+ <circle cx="18" cy="12" fill="currentColor" r="1.25" />
342
+ </>
343
+ )}
344
+ </svg>
345
+ </span>
346
+ {expanded ? labels.less : labels.more(rest.length)}
347
+ </button>
348
+ </CollapsibleTrigger>
349
+ </Collapsible>
350
+ ) : null}
351
+ </div>
352
+ )
353
+ }
354
+ )
355
+
356
+ /**
357
+ * Agent progress tracker: a collapsible to-do list whose header icon, counter,
358
+ * and rows move between pending, active, and done as work completes. Compose
359
+ * the parts instead for a static or folded list.
360
+ */
361
+ const TaskList = React.forwardRef<HTMLDivElement, TaskListProps>(
362
+ function TaskList(
363
+ {
364
+ defaultOpen = true,
365
+ items,
366
+ labels: labelOverrides,
367
+ onOpenChange,
368
+ open: controlledOpen,
369
+ title = "To-dos",
370
+ ...props
371
+ },
372
+ ref
373
+ ) {
374
+ const labels = { ...defaultLabels, ...labelOverrides }
375
+ const [open, setOpen] = useControllableState({
376
+ defaultValue: defaultOpen,
377
+ onChange: onOpenChange,
378
+ value: controlledOpen,
379
+ })
380
+ const { done, state, total } = summarize(items)
381
+
382
+ // Collapsible wires the toggle's ARIA and unmounts folded rows once they
383
+ // have animated away, so nothing hidden stays focusable. The toggle is
384
+ // named by what it shows: the title, then the progress summary.
385
+ return (
386
+ <Collapsible asChild onOpenChange={setOpen} open={open}>
387
+ <TaskListRoot
388
+ data-open={open ? "true" : "false"}
389
+ data-state={state}
390
+ ref={ref}
391
+ {...props}
392
+ >
393
+ <CollapsibleTrigger asChild>
394
+ <button data-slot="task-list-header" type="button">
395
+ <TaskListHeaderContent
396
+ chevron
397
+ count={
398
+ <>
399
+ <span className="sr-only">{labels.count(done, total)}</span>
400
+ <RollingCount aria-hidden="true" value={`${done}/${total}`} />
401
+ </>
402
+ }
403
+ items={items}
404
+ title={title}
405
+ />
406
+ </button>
407
+ </CollapsibleTrigger>
408
+ <CollapsiblePanel data-slot="task-list-collapsible">
409
+ <TaskListItems items={items} />
410
+ </CollapsiblePanel>
411
+ </TaskListRoot>
412
+ </Collapsible>
413
+ )
414
+ }
415
+ )
416
+
417
+ export { TaskList, TaskListHeader, TaskListItems, TaskListPreview, TaskListRoot }
418
+ export type {
419
+ TaskListHeaderProps,
420
+ TaskListItem,
421
+ TaskListItemsProps,
422
+ TaskListLabels,
423
+ TaskListPreviewLabels,
424
+ TaskListPreviewProps,
425
+ TaskListProps,
426
+ TaskListRootProps,
427
+ TaskListSurface,
428
+ TaskStatus,
429
+ }
@@ -53,7 +53,7 @@ const textareaSurface = cva(
53
53
  "relative flex w-full rounded-[var(--bh-textarea-radius)]",
54
54
  "min-h-[var(--bh-textarea-min-height)] p-[var(--bh-textarea-padding)]",
55
55
  "transition-[background-color,box-shadow]",
56
- "[--bh-textarea-border:var(--bh-border-input)] [--shadow-textarea:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-textarea-border,var(--bh-border-input)),var(--shadow-component-default)]",
56
+ "[--bh-textarea-border:var(--bh-border-input)] [--elevation-textarea:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-textarea-border,var(--bh-border-input)),var(--elevation-component-default)]",
57
57
  "after:pointer-events-none after:absolute after:inset-[var(--bh-textarea-focus-ring-inset)] after:rounded-[var(--bh-textarea-radius)] after:border-[length:var(--bh-textarea-focus-ring-width)] after:border-[var(--bh-border-focus)] after:opacity-[var(--bh-opacity-0)] after:content-['']",
58
58
  "focus-within:after:opacity-[var(--bh-opacity-30)]",
59
59
  ],
@@ -65,15 +65,15 @@ const textareaSurface = cva(
65
65
  },
66
66
  state: {
67
67
  placeholder:
68
- "bg-[var(--bh-interactive-input-default)] [box-shadow:var(--shadow-textarea)]",
68
+ "bg-[var(--bh-interactive-input-default)] [box-shadow:var(--elevation-textarea)]",
69
69
  filled:
70
- "bg-[var(--bh-interactive-input-default)] [box-shadow:var(--shadow-textarea)]",
70
+ "bg-[var(--bh-interactive-input-default)] [box-shadow:var(--elevation-textarea)]",
71
71
  disabled:
72
72
  "bg-[var(--bh-interactive-input-disabled)] [box-shadow:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-disabled)] after:hidden",
73
73
  },
74
74
  invalid: {
75
75
  true:
76
- "[--bh-textarea-border:var(--bh-border-danger-strong)] [box-shadow:var(--shadow-textarea)] focus-within:after:hidden",
76
+ "[--bh-textarea-border:var(--bh-border-danger-strong)] [box-shadow:var(--elevation-textarea)] focus-within:after:hidden",
77
77
  false: "",
78
78
  },
79
79
  },
@@ -0,0 +1,178 @@
1
+ [data-slot="thinking-reasoning"] {
2
+ --thinking-reasoning-fade: var(--bh-thinking-reasoning-fade);
3
+ --thinking-reasoning-max-height: var(--bh-thinking-reasoning-max-height);
4
+ --thinking-reasoning-step-height: calc(
5
+ 2 * var(--bh-text-body-xs-regular-line-height)
6
+ );
7
+
8
+ animation: bh-thinking-reasoning-in var(--bh-duration-slow)
9
+ var(--bh-easing-emphasized) both;
10
+ color: var(--bh-content-subtle);
11
+ font-family: var(--bh-font-family);
12
+ inline-size: min(var(--bh-thinking-reasoning-width), 100%);
13
+ }
14
+
15
+ /* Anchor the header: label line + viewport gap + capped viewport. */
16
+ [data-slot="thinking-reasoning"][data-reserve-space="true"] {
17
+ min-block-size: calc(
18
+ var(--bh-text-body-xs-medium-line-height) + var(--bh-space-sm-6) +
19
+ var(--thinking-reasoning-max-height)
20
+ );
21
+ }
22
+
23
+ [data-slot="thinking-reasoning-trigger"] {
24
+ align-items: center;
25
+ background: transparent;
26
+ border: 0;
27
+ border-radius: var(--bh-radius-sm-4);
28
+ color: inherit;
29
+ cursor: default;
30
+ display: inline-flex;
31
+ font: inherit;
32
+ gap: var(--bh-space-sm-6);
33
+ margin: 0;
34
+ min-block-size: var(--bh-text-body-xs-medium-line-height);
35
+ outline: none;
36
+ padding: 0;
37
+ }
38
+
39
+ [data-slot="thinking-reasoning-trigger"]:enabled {
40
+ cursor: pointer;
41
+ }
42
+
43
+ [data-slot="thinking-reasoning-trigger"]:focus-visible {
44
+ box-shadow: var(--elevation-component-focus-ring);
45
+ }
46
+
47
+ [data-slot="thinking-reasoning-label"] {
48
+ color: var(--bh-content-muted);
49
+ font-size: var(--bh-text-body-xs-medium-font-size);
50
+ font-weight: var(--bh-text-body-xs-medium-font-weight);
51
+ letter-spacing: var(--bh-text-body-xs-medium-letter-spacing);
52
+ line-height: var(--bh-text-body-xs-medium-line-height);
53
+ }
54
+
55
+ [data-slot="thinking-reasoning-verb"] {
56
+ color: var(--bh-content-subtle);
57
+ }
58
+
59
+ [data-slot="thinking-reasoning-chevron"] {
60
+ block-size: var(--bh-space-xl-12);
61
+ color: var(--bh-content-muted);
62
+ flex: none;
63
+ inline-size: var(--bh-space-xl-12);
64
+ transition: color var(--bh-duration-fast) var(--bh-easing-standard),
65
+ transform var(--bh-duration-moderate) var(--bh-easing-emphasized);
66
+ }
67
+
68
+ [data-slot="thinking-reasoning-trigger"][aria-expanded="true"]
69
+ [data-slot="thinking-reasoning-chevron"] {
70
+ transform: rotate(180deg);
71
+ }
72
+
73
+ [data-slot="thinking-reasoning-trigger"]:enabled:hover
74
+ [data-slot="thinking-reasoning-chevron"] {
75
+ color: var(--bh-content-subtle);
76
+ }
77
+
78
+ /* The body is a Collapsible panel; this tunes its fold motion. */
79
+ [data-slot="thinking-reasoning-collapsible"] {
80
+ --collapsible-panel-duration: var(--bh-duration-slow);
81
+ --collapsible-panel-easing: var(--bh-easing-emphasized);
82
+
83
+ inline-size: 100%;
84
+ }
85
+
86
+ /* Grows with the content until capped; then the stream scrolls behind fades. */
87
+ [data-slot="thinking-reasoning-viewport"] {
88
+ --thinking-reasoning-fade-top: 0px;
89
+ --thinking-reasoning-fade-bottom: 0px;
90
+
91
+ margin-block-start: var(--bh-space-sm-6);
92
+ outline: none;
93
+ overflow: hidden;
94
+ transition: height var(--bh-duration-slow) var(--bh-easing-emphasized);
95
+ }
96
+
97
+ [data-slot="thinking-reasoning-viewport"][data-capped="true"] {
98
+ mask-image: linear-gradient(
99
+ to bottom,
100
+ transparent 0,
101
+ black var(--thinking-reasoning-fade-top),
102
+ black calc(100% - var(--thinking-reasoning-fade-bottom)),
103
+ transparent 100%
104
+ );
105
+ -webkit-mask-image: linear-gradient(
106
+ to bottom,
107
+ transparent 0,
108
+ black var(--thinking-reasoning-fade-top),
109
+ black calc(100% - var(--thinking-reasoning-fade-bottom)),
110
+ transparent 100%
111
+ );
112
+ }
113
+
114
+ [data-slot="thinking-reasoning-viewport"][data-fade-top="true"] {
115
+ --thinking-reasoning-fade-top: var(--thinking-reasoning-fade);
116
+ }
117
+
118
+ [data-slot="thinking-reasoning-viewport"][data-fade-bottom="true"] {
119
+ --thinking-reasoning-fade-bottom: var(--thinking-reasoning-fade);
120
+ }
121
+
122
+ [data-slot="thinking-reasoning-viewport"][data-scrollable="true"] {
123
+ overflow-y: auto;
124
+ scrollbar-width: none;
125
+ }
126
+
127
+ [data-slot="thinking-reasoning-viewport"][data-scrollable="true"]::-webkit-scrollbar {
128
+ display: none;
129
+ }
130
+
131
+ [data-slot="thinking-reasoning-viewport"][data-scrollable="true"]:focus-visible {
132
+ box-shadow: var(--elevation-component-focus-ring);
133
+ }
134
+
135
+ [data-slot="thinking-reasoning-stream"] {
136
+ display: flex;
137
+ flex-direction: column;
138
+ gap: var(--bh-space-xs-4);
139
+ transition: transform var(--bh-duration-slower)
140
+ var(--bh-easing-emphasized);
141
+ will-change: transform;
142
+ }
143
+
144
+ [data-slot="thinking-reasoning-step"] {
145
+ animation: bh-thinking-reasoning-step-in var(--bh-duration-slow)
146
+ var(--bh-easing-emphasized) both;
147
+ block-size: var(--thinking-reasoning-step-height);
148
+ -webkit-box-orient: vertical;
149
+ color: var(--bh-content-subtle);
150
+ display: -webkit-box;
151
+ font-size: var(--bh-text-body-xs-regular-font-size);
152
+ font-weight: var(--bh-text-body-xs-regular-font-weight);
153
+ letter-spacing: var(--bh-text-body-xs-regular-letter-spacing);
154
+ -webkit-line-clamp: 2;
155
+ line-height: var(--bh-text-body-xs-regular-line-height);
156
+ margin: 0;
157
+ overflow: hidden;
158
+ }
159
+
160
+ @keyframes bh-thinking-reasoning-in {
161
+ from {
162
+ opacity: var(--bh-opacity-0);
163
+ }
164
+
165
+ to {
166
+ opacity: var(--bh-opacity-100);
167
+ }
168
+ }
169
+
170
+ @keyframes bh-thinking-reasoning-step-in {
171
+ from {
172
+ opacity: var(--bh-opacity-0);
173
+ }
174
+
175
+ to {
176
+ opacity: var(--bh-opacity-100);
177
+ }
178
+ }