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.
- package/CHANGELOG.md +78 -1
- package/CONTRIBUTING.md +6 -4
- package/README.md +4 -2
- package/docs/design-spacing.md +13 -0
- package/docs/design-system/README.md +2 -0
- package/docs/design-system/appearances/default.md +1 -1
- package/docs/design-system/appearances/rounded.md +1 -1
- package/docs/design-system/appearances/sharp.md +1 -1
- package/docs/design-system/component-showcase-consistency-report.md +5 -5
- package/docs/design-system/components/README.md +15 -0
- package/docs/design-system/components/accordion.md +1 -1
- package/docs/design-system/components/activity-feed.md +1 -1
- package/docs/design-system/components/activity-heatmap.md +1 -1
- package/docs/design-system/components/agent-input.md +85 -0
- package/docs/design-system/components/alert-dialog.md +1 -1
- package/docs/design-system/components/alert.md +10 -4
- package/docs/design-system/components/app-header.md +97 -0
- package/docs/design-system/components/approval-card.md +134 -0
- package/docs/design-system/components/attribute.md +14 -2
- package/docs/design-system/components/avatar.md +1 -1
- package/docs/design-system/components/badge.md +1 -1
- package/docs/design-system/components/banner.md +1 -1
- package/docs/design-system/components/breadcrumbs.md +8 -3
- package/docs/design-system/components/button-group.md +1 -1
- package/docs/design-system/components/button.md +8 -5
- package/docs/design-system/components/card.md +15 -1
- package/docs/design-system/components/catalog-components.md +1 -1
- package/docs/design-system/components/chart.md +95 -24
- package/docs/design-system/components/checkbox.md +1 -1
- package/docs/design-system/components/collapsible.md +20 -6
- package/docs/design-system/components/command.md +1 -1
- package/docs/design-system/components/context-menu.md +1 -1
- package/docs/design-system/components/date-picker.md +1 -1
- package/docs/design-system/components/file-upload.md +1 -1
- package/docs/design-system/components/hover-card.md +1 -1
- package/docs/design-system/components/inline-citations.md +93 -0
- package/docs/design-system/components/input-group.md +1 -1
- package/docs/design-system/components/input.md +59 -3
- package/docs/design-system/components/item.md +1 -1
- package/docs/design-system/components/kbd.md +6 -4
- package/docs/design-system/components/menu.md +35 -4
- package/docs/design-system/components/menubar.md +1 -1
- package/docs/design-system/components/modal.md +2 -2
- package/docs/design-system/components/native-select.md +1 -1
- package/docs/design-system/components/navigation-menu.md +1 -1
- package/docs/design-system/components/onboarding-step-list-item.md +1 -1
- package/docs/design-system/components/page-header.md +58 -17
- package/docs/design-system/components/pagination.md +2 -2
- package/docs/design-system/components/popover.md +1 -1
- package/docs/design-system/components/progress-slider.md +1 -1
- package/docs/design-system/components/radio-card.md +1 -1
- package/docs/design-system/components/radio-group.md +1 -1
- package/docs/design-system/components/resizable.md +1 -1
- package/docs/design-system/components/rolling-count.md +61 -0
- package/docs/design-system/components/scroll-area.md +1 -1
- package/docs/design-system/components/select.md +12 -2
- package/docs/design-system/components/sheet.md +1 -1
- package/docs/design-system/components/shimmer-text.md +67 -0
- package/docs/design-system/components/sidebar.md +75 -8
- package/docs/design-system/components/skeleton.md +2 -2
- package/docs/design-system/components/slideout.md +1 -1
- package/docs/design-system/components/slider.md +2 -2
- package/docs/design-system/components/social-button.md +2 -2
- package/docs/design-system/components/streaming-text.md +82 -0
- package/docs/design-system/components/table.md +103 -21
- package/docs/design-system/components/tabs.md +25 -6
- package/docs/design-system/components/tag.md +1 -1
- package/docs/design-system/components/task-list.md +95 -0
- package/docs/design-system/components/textarea.md +1 -1
- package/docs/design-system/components/thinking-reasoning.md +72 -0
- package/docs/design-system/components/thinking-state.md +67 -0
- package/docs/design-system/components/timeline.md +1 -1
- package/docs/design-system/components/toast.md +1 -1
- package/docs/design-system/components/toggle.md +2 -2
- package/docs/design-system/components/toolbar.md +11 -6
- package/docs/design-system/components/tooltip.md +6 -4
- package/docs/design-system/density-and-elevation.md +16 -16
- package/docs/design-system/design-system-test-missing-items.md +8 -8
- package/docs/design-system/installation.md +2 -0
- package/docs/design-system/tabs.md +2 -1
- package/docs/design-system/template-gap-audit.md +32 -0
- package/docs/design-system/templates.md +59 -0
- package/docs/design-system/token-governance.md +15 -2
- package/package.json +2 -2
- package/registry/components/accordion.tsx +2 -2
- package/registry/components/agent-input-parts.tsx +552 -0
- package/registry/components/agent-input-state.ts +156 -0
- package/registry/components/agent-input.css +307 -0
- package/registry/components/agent-input.tsx +382 -0
- package/registry/components/alert-dialog.tsx +2 -2
- package/registry/components/alert.tsx +23 -7
- package/registry/components/app-header.tsx +151 -0
- package/registry/components/approval-card.css +409 -0
- package/registry/components/approval-card.tsx +401 -0
- package/registry/components/approval-frame.tsx +108 -0
- package/registry/components/approval-questions.tsx +559 -0
- package/registry/components/attribute.tsx +48 -13
- package/registry/components/autocomplete.tsx +15 -68
- package/registry/components/avatar.tsx +2 -2
- package/registry/components/badge.tsx +8 -7
- package/registry/components/button-group.tsx +2 -2
- package/registry/components/button.tsx +19 -14
- package/registry/components/card.tsx +18 -5
- package/registry/components/chart-block-data.ts +177 -0
- package/registry/components/chart-block.css +171 -0
- package/registry/components/chart-block.tsx +260 -0
- package/registry/components/chart-interactions.ts +123 -0
- package/registry/components/chart-options.cartesian.ts +162 -0
- package/registry/components/chart-options.paint.ts +99 -0
- package/registry/components/chart-options.parts.ts +72 -0
- package/registry/components/chart-options.radial.ts +109 -0
- package/registry/components/chart-options.ts +44 -0
- package/registry/components/chart-options.types.ts +67 -0
- package/registry/components/chart.tsx +1 -1
- package/registry/components/checkbox.tsx +6 -6
- package/registry/components/collapsible.tsx +43 -3
- package/registry/components/command.tsx +1 -1
- package/registry/components/context-menu.tsx +1 -1
- package/registry/components/data-chart.tsx +40 -0
- package/registry/components/date-picker.tsx +9 -9
- package/registry/components/echart-theme.ts +246 -0
- package/registry/components/echart.css +49 -0
- package/registry/components/echart.tsx +178 -0
- package/registry/components/expanded/Breadcrumbs.tsx +5 -1
- package/registry/components/expanded/PageHeader.tsx +67 -24
- package/registry/components/expanded/Table.tsx +60 -22
- package/registry/components/expanded/activityFeed.css +2 -2
- package/registry/components/expanded/activityHeatmap.css +1 -1
- package/registry/components/expanded/banner.css +2 -2
- package/registry/components/expanded/breadcrumbs.css +19 -2
- package/registry/components/expanded/catalogComponentsShowcase.css +1 -1
- package/registry/components/expanded/fileUpload.css +7 -7
- package/registry/components/expanded/onboardingStepListItem.css +2 -2
- package/registry/components/expanded/pageHeader.css +38 -44
- package/registry/components/expanded/slideout.css +4 -4
- package/registry/components/expanded/table.css +58 -9
- package/registry/components/expanded/timeline.css +1 -1
- package/registry/components/hover-card.tsx +1 -1
- package/registry/components/inline-citations.css +126 -0
- package/registry/components/inline-citations.tsx +292 -0
- package/registry/components/input-group.tsx +4 -4
- package/registry/components/input.tsx +150 -137
- package/registry/components/item.tsx +1 -1
- package/registry/components/kbd.tsx +3 -2
- package/registry/components/menu.tsx +117 -12
- package/registry/components/menubar.tsx +2 -2
- package/registry/components/modal.tsx +3 -3
- package/registry/components/native-select.tsx +4 -4
- package/registry/components/navigation-menu.tsx +2 -2
- package/registry/components/pagination.tsx +4 -4
- package/registry/components/popover.tsx +3 -3
- package/registry/components/progress-slider.tsx +1 -1
- package/registry/components/radio-card.tsx +7 -7
- package/registry/components/radio-group.tsx +2 -2
- package/registry/components/resizable.tsx +2 -2
- package/registry/components/rolling-count.css +37 -0
- package/registry/components/rolling-count.tsx +110 -0
- package/registry/components/scroll-area.tsx +1 -1
- package/registry/components/select.tsx +15 -10
- package/registry/components/sheet.tsx +2 -2
- package/registry/components/shimmer-text.css +69 -0
- package/registry/components/shimmer-text.tsx +43 -0
- package/registry/components/sidebar.tsx +528 -223
- package/registry/components/skeleton.tsx +1 -1
- package/registry/components/slider.tsx +2 -2
- package/registry/components/social-button.tsx +1 -1
- package/registry/components/streaming-text.css +45 -0
- package/registry/components/streaming-text.tsx +165 -0
- package/registry/components/table-elements.tsx +164 -5
- package/registry/components/tabs.css +32 -4
- package/registry/components/tabs.tsx +5 -1
- package/registry/components/tag.tsx +1 -1
- package/registry/components/task-list.css +328 -0
- package/registry/components/task-list.tsx +429 -0
- package/registry/components/textarea.tsx +4 -4
- package/registry/components/thinking-reasoning.css +178 -0
- package/registry/components/thinking-reasoning.tsx +253 -0
- package/registry/components/thinking-state.css +8 -0
- package/registry/components/thinking-state.tsx +46 -0
- package/registry/components/toast.tsx +1 -1
- package/registry/components/toggle.tsx +2 -2
- package/registry/components/toolbar.tsx +6 -6
- package/registry/components/tooltip.tsx +6 -4
- package/registry/examples/agent-input-demo.tsx +90 -0
- package/registry/examples/app-header-demo.tsx +40 -0
- package/registry/examples/approval-card-demo.tsx +83 -0
- package/registry/examples/button-demo.tsx +3 -0
- package/registry/examples/chart-block-demo.tsx +5 -0
- package/registry/examples/chart-demo.tsx +6 -21
- package/registry/examples/collection-filter-bar-demo.tsx +1 -1
- package/registry/examples/control-density-contract-demo.tsx +5 -1
- package/registry/examples/inline-citations-demo.tsx +31 -0
- package/registry/examples/input-actions-demo.tsx +36 -0
- package/registry/examples/menu-demo.tsx +1 -1
- package/registry/examples/popover-demo.tsx +1 -0
- package/registry/examples/rolling-count-demo.tsx +39 -0
- package/registry/examples/shimmer-text-demo.tsx +20 -0
- package/registry/examples/streaming-text-demo.tsx +28 -0
- package/registry/examples/task-list-demo.tsx +74 -0
- package/registry/examples/thinking-reasoning-demo.tsx +74 -0
- package/registry/examples/thinking-state-demo.tsx +13 -0
- package/registry/guidance/charts.LICENSE.txt +24 -0
- package/registry/hooks/use-controllable-state.ts +33 -0
- package/registry/hooks/use-listbox-navigation.ts +73 -0
- package/registry/hooks/use-motion-end.ts +55 -0
- package/registry/hooks/use-reduced-motion.ts +28 -0
- package/registry/index.json +1808 -190
- package/registry/styles/globals.css +992 -1088
- package/registry.json +385 -10
- package/schema/registry.schema.json +5 -1
- package/schema/tokens.schema.json +8 -4
- package/src/cli/file-transaction.js +15 -8
- package/src/cli/index.js +22 -29
- 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(--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
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(--
|
|
80
|
-
soft: "border-[length:var(--bh-border-width-default)] border-transparent bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-default)] shadow-[var(--
|
|
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(--
|
|
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(--
|
|
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 = "
|
|
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-
|
|
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-
|
|
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-
|
|
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(--
|
|
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)]">
|