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,253 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ChevronDownIcon } from "lucide-react"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import {
8
+ Collapsible,
9
+ CollapsiblePanel,
10
+ CollapsibleTrigger,
11
+ } from "@/components/ui/collapsible"
12
+ import { ShimmerText } from "@/components/ui/shimmer-text"
13
+ import { useControllableState } from "@/hooks/use-controllable-state"
14
+
15
+ import "./thinking-reasoning.css"
16
+
17
+ type ThinkingReasoningStatus = "thinking" | "done"
18
+
19
+ type ThinkingReasoningLabels = {
20
+ /** Header label while the agent is still reasoning. */
21
+ thinking: string
22
+ /**
23
+ * Verb shown in the summary once reasoning ends, for example "Thought".
24
+ * With the duration it also names the summary toggle: "Thought for 5s".
25
+ */
26
+ thought: string
27
+ /** Duration suffix rendered after the verb, for example "for 5s". */
28
+ duration: (seconds: number) => React.ReactNode
29
+ }
30
+
31
+ type ThinkingReasoningProps = Omit<React.ComponentProps<"div">, "children"> & {
32
+ defaultOpen?: boolean
33
+ /** Elapsed seconds for the summary. Measured from mount when omitted. */
34
+ duration?: number
35
+ labels?: Partial<ThinkingReasoningLabels>
36
+ onOpenChange?: (open: boolean) => void
37
+ /** Controls whether the finished reasoning is unfolded. */
38
+ open?: boolean
39
+ /**
40
+ * Keeps the block height stable while the reasoning folds into its summary
41
+ * so content below does not jump. Off by default for chat threads.
42
+ */
43
+ reserveSpace?: boolean
44
+ status?: ThinkingReasoningStatus
45
+ /** Reasoning sentences in arrival order. Append to stream new ones. */
46
+ steps: readonly string[]
47
+ }
48
+
49
+ const defaultLabels: ThinkingReasoningLabels = {
50
+ duration: (seconds) => ` for ${seconds}s`,
51
+ thinking: "Thinking…",
52
+ thought: "Thought",
53
+ }
54
+
55
+ type ViewportMetrics = {
56
+ contentHeight: number
57
+ fade: number
58
+ maxHeight: number
59
+ }
60
+
61
+ function readLength(element: HTMLElement, property: string) {
62
+ const value = window.getComputedStyle(element).getPropertyValue(property)
63
+ const parsed = Number.parseFloat(value)
64
+ return Number.isFinite(parsed) ? parsed : 0
65
+ }
66
+
67
+ /**
68
+ * Collapsible reasoning block. While thinking, sentences reveal in sequence
69
+ * and auto-scroll behind a soft fade; once done the header folds into a
70
+ * "Thought for Ns" summary that unfolds the full, scrollable reasoning.
71
+ */
72
+ const ThinkingReasoning = React.forwardRef<HTMLDivElement, ThinkingReasoningProps>(
73
+ function ThinkingReasoning(
74
+ {
75
+ className,
76
+ defaultOpen = false,
77
+ duration,
78
+ labels: labelOverrides,
79
+ onOpenChange,
80
+ open: controlledOpen,
81
+ reserveSpace = false,
82
+ status = "thinking",
83
+ steps,
84
+ ...props
85
+ },
86
+ ref
87
+ ) {
88
+ const labels = { ...defaultLabels, ...labelOverrides }
89
+ const done = status === "done"
90
+ const [open, setOpenState] = useControllableState({
91
+ defaultValue: defaultOpen,
92
+ onChange: onOpenChange,
93
+ value: controlledOpen,
94
+ })
95
+ const expanded = done ? open : true
96
+ const scrollable = done && open
97
+
98
+ // Measured from the first thinking render. A block that mounts already
99
+ // done has nothing to measure, so its summary omits the duration.
100
+ const startedAt = React.useRef<number | null>(null)
101
+ const [measuredSeconds, setMeasuredSeconds] = React.useState<number>()
102
+ React.useEffect(() => {
103
+ if (done) {
104
+ if (startedAt.current !== null) {
105
+ setMeasuredSeconds(
106
+ Math.max(1, Math.round((Date.now() - startedAt.current) / 1000))
107
+ )
108
+ }
109
+ return
110
+ }
111
+ startedAt.current = Date.now()
112
+ }, [done])
113
+ const seconds = duration ?? measuredSeconds
114
+
115
+ const viewportRef = React.useRef<HTMLDivElement>(null)
116
+ const streamRef = React.useRef<HTMLDivElement>(null)
117
+ const [metrics, setMetrics] = React.useState<ViewportMetrics>({
118
+ contentHeight: 0,
119
+ fade: 0,
120
+ maxHeight: 0,
121
+ })
122
+ const [scrollFade, setScrollFade] = React.useState({
123
+ bottom: true,
124
+ top: false,
125
+ })
126
+
127
+ const measure = React.useCallback(() => {
128
+ const viewport = viewportRef.current
129
+ const stream = streamRef.current
130
+ if (!viewport || !stream) return
131
+ setMetrics({
132
+ contentHeight: stream.offsetHeight,
133
+ fade: readLength(viewport, "--thinking-reasoning-fade"),
134
+ maxHeight: readLength(viewport, "--thinking-reasoning-max-height"),
135
+ })
136
+ }, [])
137
+
138
+ // A folded block unmounts its content, so measuring and observing start
139
+ // again whenever it unfolds.
140
+ React.useLayoutEffect(measure, [expanded, measure, steps, status])
141
+ React.useEffect(() => {
142
+ const stream = streamRef.current
143
+ if (!stream || typeof ResizeObserver === "undefined") return
144
+ const observer = new ResizeObserver(measure)
145
+ observer.observe(stream)
146
+ return () => observer.disconnect()
147
+ }, [expanded, measure])
148
+
149
+ const capped =
150
+ metrics.maxHeight > 0 && metrics.contentHeight > metrics.maxHeight
151
+ const viewportHeight = capped ? metrics.maxHeight : metrics.contentHeight
152
+ const translate =
153
+ scrollable || !capped
154
+ ? 0
155
+ : metrics.maxHeight - metrics.fade - metrics.contentHeight
156
+ const fadeTop = scrollable ? scrollFade.top : capped
157
+ const fadeBottom = scrollable ? scrollFade.bottom : capped
158
+
159
+ const setOpen = (next: boolean) => {
160
+ if (next) {
161
+ if (viewportRef.current) viewportRef.current.scrollTop = 0
162
+ setScrollFade({ bottom: true, top: false })
163
+ }
164
+ setOpenState(next)
165
+ }
166
+
167
+ const handleScroll = () => {
168
+ const viewport = viewportRef.current
169
+ if (!viewport) return
170
+ setScrollFade({
171
+ bottom:
172
+ viewport.scrollTop + viewport.clientHeight <
173
+ viewport.scrollHeight - 1,
174
+ top: viewport.scrollTop > 1,
175
+ })
176
+ }
177
+
178
+ return (
179
+ <Collapsible
180
+ asChild
181
+ disabled={!done}
182
+ onOpenChange={setOpen}
183
+ open={expanded}
184
+ >
185
+ <div
186
+ data-open={expanded ? "true" : "false"}
187
+ data-reserve-space={reserveSpace ? "true" : "false"}
188
+ data-slot="thinking-reasoning"
189
+ data-state={status}
190
+ ref={ref}
191
+ className={cn("flex min-w-0 flex-col items-start", className)}
192
+ {...props}
193
+ >
194
+ {/* No aria-label: the label and summary it shows are its name. */}
195
+ <CollapsibleTrigger asChild>
196
+ <button data-slot="thinking-reasoning-trigger" type="button">
197
+ {done ? (
198
+ <span data-slot="thinking-reasoning-label">
199
+ <span data-slot="thinking-reasoning-verb">{labels.thought}</span>
200
+ {seconds !== undefined ? labels.duration(seconds) : null}
201
+ </span>
202
+ ) : (
203
+ <span aria-live="polite" data-slot="thinking-reasoning-label">
204
+ <ShimmerText>{labels.thinking}</ShimmerText>
205
+ </span>
206
+ )}
207
+ {done ? (
208
+ <ChevronDownIcon
209
+ aria-hidden="true"
210
+ data-slot="thinking-reasoning-chevron"
211
+ strokeWidth="var(--bh-icon-stroke-180)"
212
+ />
213
+ ) : null}
214
+ </button>
215
+ </CollapsibleTrigger>
216
+
217
+ <CollapsiblePanel data-slot="thinking-reasoning-collapsible">
218
+ <div
219
+ data-capped={capped ? "true" : "false"}
220
+ data-fade-bottom={fadeBottom ? "true" : "false"}
221
+ data-fade-top={fadeTop ? "true" : "false"}
222
+ data-scrollable={scrollable ? "true" : "false"}
223
+ data-slot="thinking-reasoning-viewport"
224
+ onScroll={scrollable ? handleScroll : undefined}
225
+ ref={viewportRef}
226
+ style={{ height: `${viewportHeight}px` }}
227
+ tabIndex={scrollable ? 0 : undefined}
228
+ >
229
+ <div
230
+ data-slot="thinking-reasoning-stream"
231
+ ref={streamRef}
232
+ style={{ transform: `translateY(${translate}px)` }}
233
+ >
234
+ {steps.map((step, index) => (
235
+ <p data-slot="thinking-reasoning-step" key={index}>
236
+ {step}
237
+ </p>
238
+ ))}
239
+ </div>
240
+ </div>
241
+ </CollapsiblePanel>
242
+ </div>
243
+ </Collapsible>
244
+ )
245
+ }
246
+ )
247
+
248
+ export { ThinkingReasoning }
249
+ export type {
250
+ ThinkingReasoningLabels,
251
+ ThinkingReasoningProps,
252
+ ThinkingReasoningStatus,
253
+ }
@@ -0,0 +1,8 @@
1
+ [data-slot="thinking-state"] {
2
+ color: var(--bh-content-subtle);
3
+ font-family: var(--bh-font-family);
4
+ font-size: var(--bh-text-body-xs-medium-font-size);
5
+ font-weight: var(--bh-text-body-xs-medium-font-weight);
6
+ letter-spacing: var(--bh-text-body-xs-medium-letter-spacing);
7
+ line-height: var(--bh-text-body-xs-medium-line-height);
8
+ }
@@ -0,0 +1,46 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+ import { ShimmerText, type ShimmerTextTone } from "@/components/ui/shimmer-text"
7
+
8
+ import "./thinking-state.css"
9
+
10
+ type ThinkingStateStatus = "thinking" | "done"
11
+
12
+ type ThinkingStateProps = React.ComponentProps<"span"> & {
13
+ /** `done` stops the sweep and leaves the label as static text. */
14
+ status?: ThinkingStateStatus
15
+ tone?: ShimmerTextTone
16
+ }
17
+
18
+ /**
19
+ * Minimal "Thinking" status: a shimmering label that signals the agent is
20
+ * working before its first token arrives. Pass localized `children`.
21
+ */
22
+ const ThinkingState = React.forwardRef<HTMLSpanElement, ThinkingStateProps>(
23
+ function ThinkingState(
24
+ { children = "Thinking", className, status = "thinking", tone, ...props },
25
+ ref
26
+ ) {
27
+ return (
28
+ <span
29
+ aria-live="polite"
30
+ data-slot="thinking-state"
31
+ data-state={status}
32
+ ref={ref}
33
+ role="status"
34
+ className={cn("inline-flex min-w-0 items-center", className)}
35
+ {...props}
36
+ >
37
+ <ShimmerText animated={status === "thinking"} tone={tone}>
38
+ {children}
39
+ </ShimmerText>
40
+ </span>
41
+ )
42
+ }
43
+ )
44
+
45
+ export { ThinkingState }
46
+ export type { ThinkingStateProps, ThinkingStateStatus }
@@ -36,7 +36,7 @@ const intentClassNames: Record<ToastIntent, string> = {
36
36
  }
37
37
 
38
38
  const toastClassName = [
39
- "border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-raised)] text-[var(--bh-content-default)] shadow-[var(--shadow-component-default)]",
39
+ "border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-raised)] text-[var(--bh-content-default)] shadow-[var(--elevation-component-default)]",
40
40
  "rounded-[var(--bh-radius-xl-10)] font-[var(--bh-font-family)]",
41
41
  "[--normal-bg:var(--bh-bg-raised)] [--normal-border:var(--bh-border-subtle)] [--normal-text:var(--bh-content-default)]",
42
42
  "[--success-bg:var(--bh-bg-raised)] [--success-border:var(--bh-border-success-subtle)] [--success-text:var(--bh-content-default)]",
@@ -13,7 +13,7 @@ type ToggleFieldControlPosition = "start" | "end"
13
13
  const toggleVariants = cva(
14
14
  [
15
15
  "peer group/toggle inline-flex shrink-0 items-center rounded-[var(--bh-radius-full)] bg-[var(--bh-interactive-switch-default)] p-[var(--bh-menu-switch-padding)] outline-none transition-[background-color,box-shadow]",
16
- "focus-visible:shadow-[var(--shadow-social-button-focus-ring)]",
16
+ "focus-visible:shadow-[var(--elevation-social-button-focus-ring)]",
17
17
  "disabled:pointer-events-none data-[disabled]:cursor-not-allowed",
18
18
  "data-[state=unchecked]:hover:bg-[var(--bh-interactive-switch-hover)]",
19
19
  "data-[state=checked]:bg-[var(--bh-interactive-switch-active)] data-[state=checked]:hover:bg-[var(--bh-interactive-switch-active-hover)]",
@@ -78,7 +78,7 @@ const Toggle = React.forwardRef<
78
78
  >
79
79
  <SwitchPrimitive.Thumb
80
80
  data-slot="toggle-thumb"
81
- className="pointer-events-none flex size-[var(--bh-toggle-handle-size)] translate-x-0 items-center justify-center rounded-[var(--bh-radius-full)] bg-[var(--bh-interactive-switch-handle)] text-[var(--bh-content-on-light)] shadow-[var(--shadow-md)] transition-[background-color,transform] group-data-[disabled]/toggle:bg-[var(--bh-interactive-switch-handle-disabled)] group-data-[state=checked]/toggle:translate-x-[var(--bh-toggle-offset)] rtl:group-data-[state=checked]/toggle:-translate-x-[var(--bh-toggle-offset)]"
81
+ className="pointer-events-none flex size-[var(--bh-toggle-handle-size)] translate-x-0 items-center justify-center rounded-[var(--bh-radius-full)] bg-[var(--bh-interactive-switch-handle)] text-[var(--bh-content-on-light)] shadow-[var(--elevation-03)] transition-[background-color,transform] group-data-[disabled]/toggle:bg-[var(--bh-interactive-switch-handle-disabled)] group-data-[state=checked]/toggle:translate-x-[var(--bh-toggle-offset)] rtl:group-data-[state=checked]/toggle:-translate-x-[var(--bh-toggle-offset)]"
82
82
  >
83
83
  {showIcon ? (
84
84
  <span
@@ -71,13 +71,13 @@ const toolbarSectionVariants = cva(
71
71
  )
72
72
 
73
73
  const toolbarButtonVariants = cva(
74
- "group/toolbar-button inline-flex min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-control-default)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:shadow-[var(--shadow-button-focus)] disabled:pointer-events-none disabled:shadow-none aria-invalid:border-[length:var(--bh-border-width-default)] aria-invalid:border-[var(--bh-border-danger-strong)] aria-invalid:shadow-[var(--shadow-button-danger-focus)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[var(--bh-button-icon-size)] [&_svg:not([class*='size-'])]:m-[calc((var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] [&_svg[data-icon='inline-start']]:ms-[calc(var(--bh-button-icon-offset)+(var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] [&_svg[data-icon='inline-end']]:me-[calc(var(--bh-button-icon-offset)+(var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
74
+ "group/toolbar-button inline-flex min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-control-default)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:shadow-[var(--elevation-button-focus)] disabled:pointer-events-none disabled:shadow-none aria-invalid:border-[length:var(--bh-border-width-default)] aria-invalid:border-[var(--bh-border-danger-strong)] aria-invalid:shadow-[var(--elevation-button-danger-focus)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[var(--bh-button-icon-size)] [&_svg:not([class*='size-'])]:m-[calc((var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] [&_svg[data-icon='inline-start']]:ms-[calc(var(--bh-button-icon-offset)+(var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] [&_svg[data-icon='inline-end']]:me-[calc(var(--bh-button-icon-offset)+(var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
75
75
  {
76
76
  variants: {
77
77
  variant: {
78
78
  default:
79
- "bg-[var(--bh-interactive-brand-default)] text-[var(--bh-content-on-brand)] shadow-[var(--shadow-button-raised)] hover:bg-[var(--bh-interactive-brand-hover)] active:bg-[var(--bh-interactive-brand-pressed)] disabled:bg-[var(--bh-interactive-brand-disabled)] disabled:text-[var(--bh-content-disabled)]",
80
- soft: "border-[length:var(--bh-border-width-default)] border-transparent bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-default)] shadow-[var(--shadow-button-soft)] hover:bg-[var(--bh-interactive-soft-hover)] active:bg-[var(--bh-interactive-soft-pressed)] focus-visible:border-[var(--bh-border-brand-strong)] disabled:bg-[var(--bh-interactive-soft-disabled)] disabled:text-[var(--bh-content-disabled)]",
79
+ "bg-[var(--bh-interactive-brand-default)] text-[var(--bh-content-on-brand)] shadow-[var(--elevation-button-raised)] hover:bg-[var(--bh-interactive-brand-hover)] active:bg-[var(--bh-interactive-brand-pressed)] disabled:bg-[var(--bh-interactive-brand-disabled)] disabled:text-[var(--bh-content-disabled)]",
80
+ soft: "border-[length:var(--bh-border-width-default)] border-transparent bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-default)] shadow-[var(--elevation-button-soft)] hover:bg-[var(--bh-interactive-soft-hover)] active:bg-[var(--bh-interactive-soft-pressed)] focus-visible:border-[var(--bh-border-brand-strong)] disabled:bg-[var(--bh-interactive-soft-disabled)] disabled:text-[var(--bh-content-disabled)]",
81
81
  outline:
82
82
  "border-[length:var(--bh-border-width-default)] border-[var(--bh-border-default)] bg-[var(--bh-interactive-outlined-default)] text-[var(--bh-content-default)] hover:bg-[var(--bh-interactive-outlined-hover)] active:bg-[var(--bh-interactive-outlined-pressed)] focus-visible:border-[var(--bh-border-brand-strong)] disabled:border-[var(--bh-border-disabled)] disabled:bg-[var(--bh-interactive-outlined-disabled)] disabled:text-[var(--bh-content-disabled)]",
83
83
  ghost:
@@ -101,7 +101,7 @@ const toolbarButtonVariants = cva(
101
101
  )
102
102
 
103
103
  const toolbarSearchVariants = cva(
104
- "flex max-w-full min-w-0 items-center gap-[var(--bh-input-content-gap)] rounded-[var(--bh-input-radius)] bg-[var(--bh-interactive-input-default)] text-[var(--bh-content-default)] shadow-[var(--shadow-input)] transition-[background-color,box-shadow] focus-within:shadow-[var(--shadow-input-focus-ring)] data-[disabled=true]:pointer-events-none data-[disabled=true]:bg-[var(--bh-interactive-input-disabled)] data-[disabled=true]:text-[var(--bh-content-disabled)]",
104
+ "flex max-w-full min-w-0 items-center gap-[var(--bh-input-content-gap)] rounded-[var(--bh-input-radius)] bg-[var(--bh-interactive-input-default)] text-[var(--bh-content-default)] shadow-[var(--elevation-input)] transition-[background-color,box-shadow] focus-within:shadow-[var(--elevation-input-focus-ring)] data-[disabled=true]:pointer-events-none data-[disabled=true]:bg-[var(--bh-interactive-input-disabled)] data-[disabled=true]:text-[var(--bh-content-disabled)]",
105
105
  {
106
106
  variants: {
107
107
  density: {
@@ -124,7 +124,7 @@ const toolbarSearchVariants = cva(
124
124
  )
125
125
 
126
126
  const toolbarSelectVariants = cva(
127
- "inline-flex max-w-full min-w-0 items-center justify-between gap-[var(--bh-space-md-8)] rounded-[var(--bh-input-radius)] bg-[var(--bh-interactive-input-default)] px-[var(--bh-select-trigger-padding-x)] text-start text-[length:var(--bh-text-body-md-regular-font-size)] font-[var(--bh-text-body-md-regular-font-weight)] leading-[var(--bh-text-body-md-regular-line-height)] tracking-[var(--bh-text-body-md-regular-letter-spacing)] text-[var(--bh-content-default)] shadow-[var(--shadow-select-trigger)] outline-none transition-[background-color,box-shadow] hover:bg-[var(--bh-bg-default-hover)] focus-visible:shadow-[var(--shadow-input-focus-ring)] disabled:pointer-events-none disabled:bg-[var(--bh-interactive-input-disabled)] disabled:text-[var(--bh-content-disabled)]",
127
+ "inline-flex max-w-full min-w-0 items-center justify-between gap-[var(--bh-space-md-8)] rounded-[var(--bh-input-radius)] bg-[var(--bh-interactive-input-default)] px-[var(--bh-select-trigger-padding-x)] text-start text-[length:var(--bh-text-body-md-regular-font-size)] font-[var(--bh-text-body-md-regular-font-weight)] leading-[var(--bh-text-body-md-regular-line-height)] tracking-[var(--bh-text-body-md-regular-letter-spacing)] text-[var(--bh-content-default)] shadow-[var(--elevation-select-trigger)] outline-none transition-[background-color,box-shadow] hover:bg-[var(--bh-bg-default-hover)] focus-visible:shadow-[var(--elevation-input-focus-ring)] disabled:pointer-events-none disabled:bg-[var(--bh-interactive-input-disabled)] disabled:text-[var(--bh-content-disabled)]",
128
128
  {
129
129
  variants: {
130
130
  density: {
@@ -167,7 +167,7 @@ type ToolbarProps = React.ComponentProps<"div"> &
167
167
  VariantProps<typeof toolbarVariants>
168
168
 
169
169
  const Toolbar = React.forwardRef<HTMLDivElement, ToolbarProps>(function Toolbar(
170
- { className, layout, wrap, role = "toolbar", ...props },
170
+ { className, layout, wrap, role = "group", ...props },
171
171
  ref
172
172
  ) {
173
173
  return (
@@ -29,16 +29,16 @@ const TOOLTIP_COLLISION_PADDING_PX = 8
29
29
 
30
30
  const tooltipContentVariants = cva(
31
31
  [
32
- "z-[var(--bh-z-overlay)] overflow-visible bg-[var(--bh-tooltip-bg)] text-[var(--bh-tooltip-fg)] shadow-[var(--bh-tooltip-shadow)] outline-none",
32
+ "z-[var(--bh-z-overlay)] overflow-visible bg-[var(--bh-tooltip-bg)] text-[var(--bh-tooltip-fg)] shadow-[var(--bh-tooltip-elevation)] outline-none",
33
33
  "font-[var(--bh-font-family)] tracking-[var(--bh-text-base-letter-spacing)]",
34
34
  ],
35
35
  {
36
36
  variants: {
37
37
  variant: {
38
38
  dark:
39
- "[--bh-tooltip-bg:var(--bh-bg-always-dark)] [--bh-tooltip-border:transparent] [--bh-tooltip-close-fg:var(--bh-content-on-color)] [--bh-tooltip-fg:var(--bh-content-on-color)] [--bh-tooltip-shadow:none] [--bh-tooltip-shortcut-bg:var(--bh-bg-neutral-soft)] [--bh-tooltip-shortcut-border:var(--bh-border-inverse-subtle)]",
39
+ "[--bh-tooltip-bg:var(--bh-bg-always-dark)] [--bh-tooltip-border:transparent] [--bh-tooltip-close-fg:var(--bh-content-on-color)] [--bh-tooltip-fg:var(--bh-content-on-color)] [--bh-tooltip-elevation:none] [--bh-tooltip-shortcut-bg:var(--bh-bg-neutral-soft)] [--bh-tooltip-shortcut-border:var(--bh-border-inverse-subtle)]",
40
40
  default:
41
- "[--bh-tooltip-bg:var(--bh-bg-raised)] [--bh-tooltip-border:var(--bh-border-subtle)] [--bh-tooltip-close-fg:var(--bh-content-subtle)] [--bh-tooltip-fg:var(--bh-content-default)] [--bh-tooltip-shadow:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-tooltip-border),var(--shadow-lg)] [--bh-tooltip-shortcut-bg:var(--bh-bg-neutral-subtle)] [--bh-tooltip-shortcut-border:var(--bh-border-subtle)]",
41
+ "[--bh-tooltip-bg:var(--bh-bg-raised)] [--bh-tooltip-border:var(--bh-border-subtle)] [--bh-tooltip-close-fg:var(--bh-content-subtle)] [--bh-tooltip-fg:var(--bh-content-default)] [--bh-tooltip-elevation:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-tooltip-border),var(--elevation-04)] [--bh-tooltip-shortcut-bg:var(--bh-bg-neutral-subtle)] [--bh-tooltip-shortcut-border:var(--bh-border-subtle)]",
42
42
  },
43
43
  size: {
44
44
  sm:
@@ -244,11 +244,13 @@ function TooltipLargeContent({
244
244
  function TooltipShortcut({
245
245
  children = "/",
246
246
  className,
247
+ dir = "ltr",
247
248
  ...props
248
249
  }: TooltipShortcutProps) {
249
250
  return (
250
251
  <span
251
252
  data-slot="tooltip-shortcut"
253
+ dir={dir}
252
254
  className={cn(
253
255
  "flex h-[var(--bh-tooltip-shortcut-height)] min-w-[var(--bh-tooltip-shortcut-min-width)] shrink-0 items-center justify-center rounded-[var(--bh-radius-sm-4)] border border-[var(--bh-tooltip-shortcut-border)] bg-[var(--bh-tooltip-shortcut-bg)] px-[var(--bh-space-xs-4)] text-center text-[length:var(--bh-text-body-2xs-medium-font-size)] font-[var(--bh-text-body-2xs-medium-font-weight)] leading-[var(--bh-text-body-2xs-medium-line-height)] tracking-[var(--bh-text-body-2xs-medium-letter-spacing)]",
254
256
  className
@@ -272,7 +274,7 @@ function TooltipCloseButton({
272
274
  data-slot="tooltip-close"
273
275
  type={type || "button"}
274
276
  className={cn(
275
- "inline-flex size-[var(--bh-tooltip-close-size)] shrink-0 items-center justify-center rounded-[var(--bh-radius-full)] border-0 bg-transparent p-0 text-[var(--bh-tooltip-close-fg)] outline-none transition-[background-color,box-shadow] hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:shadow-[var(--shadow-button-focus)]",
277
+ "inline-flex size-[var(--bh-tooltip-close-size)] shrink-0 items-center justify-center rounded-[var(--bh-radius-full)] border-0 bg-transparent p-0 text-[var(--bh-tooltip-close-fg)] outline-none transition-[background-color,box-shadow] hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:shadow-[var(--elevation-button-focus)]",
276
278
  className
277
279
  )}
278
280
  {...props}
@@ -0,0 +1,90 @@
1
+ import * as React from "react"
2
+ import {
3
+ BotIcon,
4
+ BrainIcon,
5
+ FileSearchIcon,
6
+ GlobeIcon,
7
+ ListCollapseIcon,
8
+ SparklesIcon,
9
+ } from "lucide-react"
10
+
11
+ import {
12
+ AgentInput,
13
+ type AgentInputModel,
14
+ type AgentInputPayload,
15
+ type AgentInputSkill,
16
+ } from "@/components/ui/agent-input"
17
+
18
+ const models: AgentInputModel[] = [
19
+ {
20
+ description: "Most capable for complex, multi-step reasoning.",
21
+ icon: <SparklesIcon />,
22
+ id: "reasoning-xl",
23
+ meta: "200k context",
24
+ name: "Reasoning XL",
25
+ },
26
+ {
27
+ description: "Fast all-rounder with strong tool use.",
28
+ icon: <BotIcon />,
29
+ id: "general-pro",
30
+ meta: "400k context",
31
+ name: "General Pro",
32
+ },
33
+ {
34
+ description: "Long-context model for large documents and codebases.",
35
+ icon: <BrainIcon />,
36
+ id: "long-context",
37
+ meta: "1M context",
38
+ name: "Long Context",
39
+ },
40
+ ]
41
+
42
+ const skills: AgentInputSkill[] = [
43
+ { icon: <FileSearchIcon />, id: "deep-research", name: "Deep research" },
44
+ { icon: <ListCollapseIcon />, id: "code-review", name: "Code review" },
45
+ { icon: <GlobeIcon />, id: "web-search", name: "Web search" },
46
+ { icon: <SparklesIcon />, id: "summarize", name: "Summarize" },
47
+ ]
48
+
49
+ const enhanced =
50
+ "Rewrite this prompt to be clear and specific: state the goal, add the relevant context and constraints, define the expected output format and tone, and note any assumptions. Ask a clarifying question first if key details are missing."
51
+
52
+ /**
53
+ * Integration seam: replace the timeout with a request to your model API and
54
+ * honour the abort signal so an in-flight enhance can be cancelled.
55
+ */
56
+ async function mockEnhance(_prompt: string, signal: AbortSignal) {
57
+ await new Promise((resolve) => setTimeout(resolve, 2500))
58
+ if (signal.aborted) throw new DOMException("Aborted", "AbortError")
59
+ return enhanced
60
+ }
61
+
62
+ export function AgentInputDemo() {
63
+ const [sent, setSent] = React.useState<AgentInputPayload | null>(null)
64
+ // Integration seam: picked attachments carry their File. Upload them here,
65
+ // or as soon as they arrive through onAttachmentsChange.
66
+ const files = sent?.attachments.flatMap((attachment) => attachment.file ?? []) ?? []
67
+
68
+ return (
69
+ <div className="grid w-full max-w-[var(--bh-space-19xl-384)] gap-[var(--bh-space-xl-12)]">
70
+ <AgentInput
71
+ models={models}
72
+ onEnhance={mockEnhance}
73
+ onSend={setSent}
74
+ skills={skills}
75
+ />
76
+
77
+ {sent ? (
78
+ <p
79
+ aria-live="polite"
80
+ className="text-[length:var(--bh-text-body-xs-regular-font-size)] leading-[var(--bh-text-body-xs-regular-line-height)] text-[var(--bh-content-subtle)]"
81
+ >
82
+ Sent to {sent.model}: {sent.text}
83
+ {files.length > 0
84
+ ? ` (${files.length} ${files.length === 1 ? "file" : "files"} ready to upload)`
85
+ : null}
86
+ </p>
87
+ ) : null}
88
+ </div>
89
+ )
90
+ }
@@ -0,0 +1,40 @@
1
+ import { BellIcon } from "lucide-react"
2
+
3
+ import {
4
+ AppHeader,
5
+ AppHeaderAction,
6
+ AppHeaderEnd,
7
+ AppHeaderSearch,
8
+ AppHeaderStart,
9
+ } from "@/components/ui/app-header"
10
+ import { Breadcrumbs } from "@/components/ui/expanded/Breadcrumbs"
11
+ import { Input } from "@/components/ui/input"
12
+
13
+ export function AppHeaderDemo() {
14
+ return (
15
+ <AppHeader>
16
+ <AppHeaderStart>
17
+ <Breadcrumbs
18
+ aria-label="Breadcrumb"
19
+ items={[{ label: "Recruiting", href: "#overview" }, { label: "Overview" }]}
20
+ size="sm"
21
+ />
22
+ </AppHeaderStart>
23
+ <AppHeaderEnd>
24
+ <AppHeaderSearch onSubmit={(event) => event.preventDefault()}>
25
+ <Input
26
+ aria-label="Search workspace"
27
+ hasHelperText={false}
28
+ hasLabel={false}
29
+ placeholder="Search workspace"
30
+ type="search"
31
+ />
32
+ </AppHeaderSearch>
33
+ <AppHeaderAction aria-label="Notifications" icon={<BellIcon />} />
34
+ <AppHeaderAction aria-label="EN, switch language to Arabic" size="sm">
35
+ EN
36
+ </AppHeaderAction>
37
+ </AppHeaderEnd>
38
+ </AppHeader>
39
+ )
40
+ }
@@ -0,0 +1,83 @@
1
+ import * as React from "react"
2
+
3
+ import {
4
+ ApprovalCommand,
5
+ ApprovalPlan,
6
+ type ApprovalPlanStep,
7
+ type ApprovalQuestion,
8
+ ApprovalQuestions,
9
+ } from "@/components/ui/approval-card"
10
+
11
+ const questions: ApprovalQuestion[] = [
12
+ {
13
+ id: "auth",
14
+ options: ["Session cookies", "JWT bearer", "OAuth only"],
15
+ prompt: "Which auth approach should we use?",
16
+ },
17
+ {
18
+ id: "secrets",
19
+ options: [".env.local", "Vault / secrets manager", "CI only"],
20
+ prompt: "Where should secrets live?",
21
+ },
22
+ {
23
+ id: "flag",
24
+ options: ["Yes, gradual rollout", "No, full release"],
25
+ prompt: "Ship behind a feature flag?",
26
+ },
27
+ ]
28
+
29
+ const plan: ApprovalPlanStep[] = [
30
+ { id: "p1", title: "Add migration for sessions table" },
31
+ { id: "p2", title: "Wire auth middleware" },
32
+ { id: "p3", title: "Update login flow and tests" },
33
+ { id: "p4", title: "Add account settings page" },
34
+ { id: "p5", title: "Tighten CSRF and rate limits" },
35
+ { id: "p6", title: "Write rollout notes" },
36
+ ]
37
+
38
+ export function ApprovalCardDemo() {
39
+ const [status, setStatus] = React.useState<string | null>(null)
40
+
41
+ return (
42
+ <div className="grid w-full max-w-[var(--bh-space-19xl-384)] gap-[var(--bh-space-3xl-16)]">
43
+ <ApprovalQuestions
44
+ onApprove={({ answers }) =>
45
+ setStatus(`Answers: ${JSON.stringify(answers)}`)
46
+ }
47
+ onReject={() => setStatus("Questions skipped")}
48
+ questions={questions}
49
+ />
50
+
51
+ <ApprovalCommand
52
+ command="pnpm db:migrate && pnpm build"
53
+ cwd="~/acme"
54
+ onApprove={() => setStatus("Command approved")}
55
+ onReject={() => setStatus("Command skipped")}
56
+ />
57
+
58
+ <ApprovalPlan
59
+ autoApproveSeconds={30}
60
+ onApprove={({ source }) =>
61
+ setStatus(source === "timeout" ? "Plan auto-approved" : "Plan approved")
62
+ }
63
+ onDownload={() => setStatus("Plan downloaded")}
64
+ onExpand={() => setStatus("Plan expanded")}
65
+ onViewPlan={() => setStatus("Plan opened")}
66
+ plan={plan}
67
+ planSummary={
68
+ "Ship cookie-based sessions with middleware and tests.\nIncludes a safe rollout path for production."
69
+ }
70
+ planTitle="Session auth migration"
71
+ />
72
+
73
+ {status ? (
74
+ <p
75
+ aria-live="polite"
76
+ className="text-[length:var(--bh-text-body-xs-regular-font-size)] leading-[var(--bh-text-body-xs-regular-line-height)] text-[var(--bh-content-subtle)]"
77
+ >
78
+ {status}
79
+ </p>
80
+ ) : null}
81
+ </div>
82
+ )
83
+ }
@@ -16,6 +16,9 @@ export function ButtonDemo() {
16
16
  <Button variant="danger">Danger</Button>
17
17
  <Button variant="soft-danger">Soft Danger</Button>
18
18
  <Button variant="white">White</Button>
19
+ <Button variant="success">Success</Button>
20
+ <Button variant="warning">Warning</Button>
21
+ <Button variant="link">Link</Button>
19
22
  </div>
20
23
 
21
24
  <div className="flex flex-wrap items-center gap-[var(--bh-space-xl-12)]">
@@ -0,0 +1,5 @@
1
+ import { ChartBlock } from "@/components/ui/chart-block"
2
+
3
+ export function ChartBlockDemo() {
4
+ return <ChartBlock block="latency" />
5
+ }