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
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
ChartContainer,
|
|
5
|
-
ChartTooltip,
|
|
6
|
-
ChartTooltipContent,
|
|
7
|
-
} from "@/components/ui/chart"
|
|
1
|
+
import { DataChart } from "@/components/ui/data-chart"
|
|
8
2
|
|
|
9
3
|
const data = [
|
|
10
4
|
{ month: "Jan", revenue: 120 },
|
|
@@ -15,19 +9,10 @@ const data = [
|
|
|
15
9
|
|
|
16
10
|
export function ChartDemo() {
|
|
17
11
|
return (
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<Line
|
|
24
|
-
dataKey="revenue"
|
|
25
|
-
type="monotone"
|
|
26
|
-
stroke="var(--color-revenue)"
|
|
27
|
-
strokeWidth="var(--bh-icon-stroke-200)"
|
|
28
|
-
dot={false}
|
|
29
|
-
/>
|
|
30
|
-
</LineChart>
|
|
31
|
-
</ChartContainer>
|
|
12
|
+
<DataChart
|
|
13
|
+
label="Monthly revenue"
|
|
14
|
+
description="Illustrative revenue from January through April."
|
|
15
|
+
spec={{ family: "area", data, categoryKey: "month", variant: "gradient", series: [{ key: "revenue", label: "Revenue" }], legend: true }}
|
|
16
|
+
/>
|
|
32
17
|
)
|
|
33
18
|
}
|
|
@@ -36,7 +36,7 @@ export function CollectionFilterBarDemo() {
|
|
|
36
36
|
<form
|
|
37
37
|
aria-label="Collection filters"
|
|
38
38
|
data-recipe="collection-filter-bar"
|
|
39
|
-
className="grid w-full min-w-0 gap-[var(--bh-space-xl-12)] rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-3xl-16)] shadow-[var(--
|
|
39
|
+
className="grid w-full min-w-0 gap-[var(--bh-space-xl-12)] rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] p-[var(--bh-space-3xl-16)] shadow-[var(--elevation-component-default)]"
|
|
40
40
|
onReset={() => {
|
|
41
41
|
setTypeFilter("everything")
|
|
42
42
|
setOrder("recent")
|
|
@@ -32,7 +32,11 @@ export function ControlDensityContractDemo() {
|
|
|
32
32
|
hasLabel={false}
|
|
33
33
|
placeholder="Input"
|
|
34
34
|
/>
|
|
35
|
-
<Select
|
|
35
|
+
<Select
|
|
36
|
+
aria-label={`${density} density select`}
|
|
37
|
+
density={density}
|
|
38
|
+
value="Select"
|
|
39
|
+
/>
|
|
36
40
|
<DatePicker
|
|
37
41
|
density={density}
|
|
38
42
|
placeholder="Date"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CitationMark,
|
|
3
|
+
CitationProse,
|
|
4
|
+
InlineCitations,
|
|
5
|
+
type CitationSource,
|
|
6
|
+
} from "@/components/ui/inline-citations"
|
|
7
|
+
|
|
8
|
+
const sources: CitationSource[] = [
|
|
9
|
+
{
|
|
10
|
+
host: "arxiv.org",
|
|
11
|
+
label: "Attention Is All You Need",
|
|
12
|
+
url: "https://arxiv.org/abs/1706.03762",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
host: "arxiv.org",
|
|
16
|
+
label: "Efficient Transformers: A Survey",
|
|
17
|
+
url: "https://arxiv.org/abs/2009.06732",
|
|
18
|
+
},
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
export function InlineCitationsDemo() {
|
|
22
|
+
return (
|
|
23
|
+
<InlineCitations className="max-w-[var(--bh-space-19xl-384)]" sources={sources}>
|
|
24
|
+
<CitationProse>
|
|
25
|
+
Transformers scale well with data and compute
|
|
26
|
+
<CitationMark n={1} />, though attention is quadratic in sequence length
|
|
27
|
+
<CitationMark n={2} />.
|
|
28
|
+
</CitationProse>
|
|
29
|
+
</InlineCitations>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { Input } from "@/components/ui/input"
|
|
4
|
+
|
|
5
|
+
export function InputActionsDemo() {
|
|
6
|
+
const [quantity, setQuantity] = React.useState("1")
|
|
7
|
+
const [reference, setReference] = React.useState("")
|
|
8
|
+
const [applied, setApplied] = React.useState("Choose a quantity, then apply your reference.")
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="grid gap-[var(--bh-space-xl-12)]">
|
|
12
|
+
<Input
|
|
13
|
+
kind="quantity"
|
|
14
|
+
label="Order quantity"
|
|
15
|
+
quantityLabel="order quantity"
|
|
16
|
+
min={0}
|
|
17
|
+
max={10}
|
|
18
|
+
step={1}
|
|
19
|
+
value={quantity}
|
|
20
|
+
onChange={(event) => setQuantity(event.currentTarget.value)}
|
|
21
|
+
/>
|
|
22
|
+
<Input
|
|
23
|
+
kind="trailing-button"
|
|
24
|
+
label="Order reference"
|
|
25
|
+
placeholder="Reference"
|
|
26
|
+
buttonLabel="Apply"
|
|
27
|
+
value={reference}
|
|
28
|
+
onChange={(event) => setReference(event.currentTarget.value)}
|
|
29
|
+
buttonProps={{
|
|
30
|
+
onClick: () => setApplied(`${quantity || "0"} items${reference ? ` · ${reference}` : ""}`),
|
|
31
|
+
}}
|
|
32
|
+
/>
|
|
33
|
+
<p role="status" className="text-[var(--bh-content-subtle)]">{applied}</p>
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button"
|
|
4
|
+
import { RollingCount } from "@/components/ui/rolling-count"
|
|
5
|
+
|
|
6
|
+
const total = 5
|
|
7
|
+
|
|
8
|
+
export function RollingCountDemo() {
|
|
9
|
+
const [done, setDone] = React.useState(2)
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="grid justify-items-start gap-[var(--bh-space-xl-12)]">
|
|
13
|
+
<p className="m-0 text-[length:var(--bh-text-body-md-medium-font-size)] font-[var(--bh-text-body-md-medium-font-weight)] leading-[var(--bh-text-body-md-medium-line-height)] text-[var(--bh-content-default)]">
|
|
14
|
+
<RollingCount
|
|
15
|
+
aria-label={`${done} of ${total} tasks done`}
|
|
16
|
+
value={`${done}/${total}`}
|
|
17
|
+
/>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<div className="flex gap-[var(--bh-space-md-8)]">
|
|
21
|
+
<Button
|
|
22
|
+
disabled={done === 0}
|
|
23
|
+
onClick={() => setDone((value) => Math.max(0, value - 1))}
|
|
24
|
+
size="sm"
|
|
25
|
+
variant="soft"
|
|
26
|
+
>
|
|
27
|
+
Undo
|
|
28
|
+
</Button>
|
|
29
|
+
<Button
|
|
30
|
+
disabled={done === total}
|
|
31
|
+
onClick={() => setDone((value) => Math.min(total, value + 1))}
|
|
32
|
+
size="sm"
|
|
33
|
+
>
|
|
34
|
+
Complete a task
|
|
35
|
+
</Button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ShimmerText } from "@/components/ui/shimmer-text"
|
|
2
|
+
|
|
3
|
+
export function ShimmerTextDemo() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="grid gap-[var(--bh-space-3xl-16)] text-[length:var(--bh-text-body-sm-regular-font-size)] leading-[var(--bh-text-body-sm-regular-line-height)] text-[var(--bh-content-default)]">
|
|
6
|
+
<p className="m-0">
|
|
7
|
+
Searching the workspace for{" "}
|
|
8
|
+
<ShimmerText tone="default">billing/invoices.ts</ShimmerText>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p className="m-0">
|
|
12
|
+
<ShimmerText>Reading 12 files</ShimmerText>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p className="m-0" dir="rtl">
|
|
16
|
+
جارٍ البحث في <ShimmerText tone="default">billing/invoices.ts</ShimmerText>
|
|
17
|
+
</p>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button"
|
|
4
|
+
import { StreamingText, useTypewriter } from "@/components/ui/streaming-text"
|
|
5
|
+
|
|
6
|
+
const response =
|
|
7
|
+
"The migration touches three files. I will add the sessions table first, wire the auth middleware next, and finish with the login flow so every step stays independently testable."
|
|
8
|
+
|
|
9
|
+
function TypedResponse({ text }: { text: string }) {
|
|
10
|
+
const typed = useTypewriter(text)
|
|
11
|
+
return <StreamingText streaming={typed.typing} text={typed.text} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function StreamingTextDemo() {
|
|
15
|
+
const [run, setRun] = React.useState(0)
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="grid max-w-[var(--bh-space-19xl-384)] gap-[var(--bh-space-3xl-16)]">
|
|
19
|
+
<TypedResponse key={run} text={response} />
|
|
20
|
+
|
|
21
|
+
<div>
|
|
22
|
+
<Button onClick={() => setRun((value) => value + 1)} size="sm" variant="soft">
|
|
23
|
+
Replay
|
|
24
|
+
</Button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button"
|
|
4
|
+
import { TaskList, type TaskListItem } from "@/components/ui/task-list"
|
|
5
|
+
|
|
6
|
+
const labels = [
|
|
7
|
+
"Scaffold the project structure",
|
|
8
|
+
"Build the component registry",
|
|
9
|
+
"Implement entitlement gating",
|
|
10
|
+
"Wire up Stripe checkout",
|
|
11
|
+
"Polish the landing page",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const startDelay = 700
|
|
15
|
+
// How long each task stays active before completing.
|
|
16
|
+
const stepDuration = 2250
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Walks the scripted tasks from pending to active to done. A real agent
|
|
20
|
+
* updates each item status as its work progresses.
|
|
21
|
+
*/
|
|
22
|
+
function useScriptedTasks(run: number): TaskListItem[] {
|
|
23
|
+
// -1 = not started, 0..n-1 = index of the active task, n = all done.
|
|
24
|
+
const [current, setCurrent] = React.useState(-1)
|
|
25
|
+
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
setCurrent(-1)
|
|
28
|
+
|
|
29
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
30
|
+
setCurrent(labels.length)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const timers = [window.setTimeout(() => setCurrent(0), startDelay)]
|
|
35
|
+
labels.forEach((_, index) => {
|
|
36
|
+
timers.push(
|
|
37
|
+
window.setTimeout(
|
|
38
|
+
() => setCurrent(index + 1),
|
|
39
|
+
startDelay + (index + 1) * stepDuration
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return () => timers.forEach((timer) => window.clearTimeout(timer))
|
|
45
|
+
}, [run])
|
|
46
|
+
|
|
47
|
+
return labels.map((label, index) => ({
|
|
48
|
+
id: String(index),
|
|
49
|
+
label,
|
|
50
|
+
status:
|
|
51
|
+
current >= 0 && index < current
|
|
52
|
+
? "done"
|
|
53
|
+
: index === current && current < labels.length
|
|
54
|
+
? "active"
|
|
55
|
+
: "pending",
|
|
56
|
+
}))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function TaskListDemo() {
|
|
60
|
+
const [run, setRun] = React.useState(0)
|
|
61
|
+
const items = useScriptedTasks(run)
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="grid max-w-[var(--bh-space-18xl-320)] gap-[var(--bh-space-3xl-16)]">
|
|
65
|
+
<TaskList items={items} />
|
|
66
|
+
|
|
67
|
+
<div>
|
|
68
|
+
<Button onClick={() => setRun((value) => value + 1)} size="sm" variant="soft">
|
|
69
|
+
Replay
|
|
70
|
+
</Button>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button"
|
|
4
|
+
import { ThinkingReasoning } from "@/components/ui/thinking-reasoning"
|
|
5
|
+
|
|
6
|
+
const reasoning = [
|
|
7
|
+
"Reading the request and the current selection, then locating the jwt.verify call inside the auth middleware.",
|
|
8
|
+
"The verify call sets no algorithms allowlist, so a token signed with none or a weak cipher could be accepted.",
|
|
9
|
+
"Tracing where the signing secret is loaded from and confirming it is never logged or sent back to the client.",
|
|
10
|
+
"Planning to pin the algorithm to HS256 and to validate the issuer and audience claims on every incoming request.",
|
|
11
|
+
"Scanning the existing tests around the middleware so the fix stays covered and nothing downstream regresses.",
|
|
12
|
+
"Drafting the patch with a focused regression test that rejects tampered, expired, and unsigned tokens.",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
// Per-sentence arrival cadence in milliseconds; sums to roughly five seconds.
|
|
16
|
+
const delays = [700, 900, 800, 850, 800, 900]
|
|
17
|
+
const collapseBeat = 360
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Replays a scripted reasoning stream. A real integration appends each
|
|
21
|
+
* sentence as it arrives and flips the status to done when the model stops.
|
|
22
|
+
*/
|
|
23
|
+
function useScriptedReasoning(run: number) {
|
|
24
|
+
const [revealed, setRevealed] = React.useState(0)
|
|
25
|
+
const [status, setStatus] = React.useState<"thinking" | "done">("thinking")
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
setRevealed(0)
|
|
29
|
+
setStatus("thinking")
|
|
30
|
+
|
|
31
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
32
|
+
setRevealed(reasoning.length)
|
|
33
|
+
setStatus("done")
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const timers: number[] = []
|
|
38
|
+
let elapsed = 0
|
|
39
|
+
delays.forEach((delay, index) => {
|
|
40
|
+
elapsed += delay
|
|
41
|
+
timers.push(window.setTimeout(() => setRevealed(index + 1), elapsed))
|
|
42
|
+
})
|
|
43
|
+
timers.push(
|
|
44
|
+
window.setTimeout(() => setStatus("done"), elapsed + collapseBeat)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return () => timers.forEach((timer) => window.clearTimeout(timer))
|
|
48
|
+
}, [run])
|
|
49
|
+
|
|
50
|
+
return { status, steps: reasoning.slice(0, revealed) }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function ThinkingReasoningDemo() {
|
|
54
|
+
const [run, setRun] = React.useState(0)
|
|
55
|
+
const { status, steps } = useScriptedReasoning(run)
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className="grid gap-[var(--bh-space-3xl-16)]">
|
|
59
|
+
<ThinkingReasoning
|
|
60
|
+
duration={5}
|
|
61
|
+
key={run}
|
|
62
|
+
reserveSpace
|
|
63
|
+
status={status}
|
|
64
|
+
steps={steps}
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
<div>
|
|
68
|
+
<Button onClick={() => setRun((value) => value + 1)} size="sm" variant="soft">
|
|
69
|
+
Replay
|
|
70
|
+
</Button>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ThinkingState } from "@/components/ui/thinking-state"
|
|
2
|
+
|
|
3
|
+
export function ThinkingStateDemo() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="grid gap-[var(--bh-space-3xl-16)]">
|
|
6
|
+
<ThinkingState />
|
|
7
|
+
|
|
8
|
+
<div dir="rtl">
|
|
9
|
+
<ThinkingState>جارٍ التفكير</ThinkingState>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Banhaten chart designs and illustrative block datasets are adapted from EvilCharts:
|
|
2
|
+
https://github.com/legions-developer/evilcharts
|
|
3
|
+
|
|
4
|
+
MIT License
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2026 Gurbinder
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* State a parent may control. While `value` is undefined the hook keeps its
|
|
7
|
+
* own copy, seeded from `defaultValue`; either way `onChange` hears every
|
|
8
|
+
* change made through the returned setter.
|
|
9
|
+
*/
|
|
10
|
+
export function useControllableState<T>({
|
|
11
|
+
defaultValue,
|
|
12
|
+
onChange,
|
|
13
|
+
value,
|
|
14
|
+
}: {
|
|
15
|
+
defaultValue: T
|
|
16
|
+
onChange?: (value: T) => void
|
|
17
|
+
value?: T
|
|
18
|
+
}) {
|
|
19
|
+
const [uncontrolled, setUncontrolled] = React.useState(defaultValue)
|
|
20
|
+
const controlled = value !== undefined
|
|
21
|
+
const onChangeRef = React.useRef(onChange)
|
|
22
|
+
onChangeRef.current = onChange
|
|
23
|
+
|
|
24
|
+
const setValue = React.useCallback(
|
|
25
|
+
(next: T) => {
|
|
26
|
+
if (!controlled) setUncontrolled(next)
|
|
27
|
+
onChangeRef.current?.(next)
|
|
28
|
+
},
|
|
29
|
+
[controlled]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return [controlled ? value : uncontrolled, setValue] as const
|
|
33
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Active-option state for a listbox that a text field drives through
|
|
7
|
+
* `aria-activedescendant`. Arrow moves wrap and skip disabled options; the
|
|
8
|
+
* active option stays put while it remains valid and otherwise falls back to
|
|
9
|
+
* the first enabled one, so filtering never strands it.
|
|
10
|
+
*/
|
|
11
|
+
export function useListboxNavigation<T>({
|
|
12
|
+
idPrefix,
|
|
13
|
+
isDisabled,
|
|
14
|
+
open,
|
|
15
|
+
options,
|
|
16
|
+
}: {
|
|
17
|
+
idPrefix: string
|
|
18
|
+
isDisabled?: (option: T) => boolean
|
|
19
|
+
open: boolean
|
|
20
|
+
options: readonly T[]
|
|
21
|
+
}) {
|
|
22
|
+
const [activeIndex, setActiveIndex] = React.useState(-1)
|
|
23
|
+
const isDisabledRef = React.useRef(isDisabled)
|
|
24
|
+
isDisabledRef.current = isDisabled
|
|
25
|
+
|
|
26
|
+
const enabledIndexes = () =>
|
|
27
|
+
options.flatMap((option, index) =>
|
|
28
|
+
isDisabledRef.current?.(option) ? [] : [index]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
if (!open) {
|
|
33
|
+
setActiveIndex(-1)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
setActiveIndex((current) => {
|
|
37
|
+
const enabled = options.flatMap((option, index) =>
|
|
38
|
+
isDisabledRef.current?.(option) ? [] : [index]
|
|
39
|
+
)
|
|
40
|
+
return enabled.includes(current) ? current : enabled[0] ?? -1
|
|
41
|
+
})
|
|
42
|
+
}, [open, options])
|
|
43
|
+
|
|
44
|
+
/** Moves by one enabled option, wrapping at either end. */
|
|
45
|
+
const move = (offset: 1 | -1) => {
|
|
46
|
+
const enabled = enabledIndexes()
|
|
47
|
+
setActiveIndex((current) => {
|
|
48
|
+
if (enabled.length === 0) return -1
|
|
49
|
+
const position = enabled.indexOf(current)
|
|
50
|
+
if (position === -1) return offset > 0 ? enabled[0] : enabled[enabled.length - 1]
|
|
51
|
+
return enabled[(position + offset + enabled.length) % enabled.length]
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Jumps to the first or last enabled option. */
|
|
56
|
+
const moveToEdge = (edge: "first" | "last") => {
|
|
57
|
+
const enabled = enabledIndexes()
|
|
58
|
+
setActiveIndex((edge === "first" ? enabled[0] : enabled[enabled.length - 1]) ?? -1)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const optionId = (index: number) => `${idPrefix}-option-${index}`
|
|
62
|
+
const current =
|
|
63
|
+
open && activeIndex >= 0 && activeIndex < options.length ? activeIndex : -1
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
activeIndex: current,
|
|
67
|
+
activeOptionId: current >= 0 ? optionId(current) : undefined,
|
|
68
|
+
move,
|
|
69
|
+
moveToEdge,
|
|
70
|
+
optionId,
|
|
71
|
+
setActiveIndex,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Calls `done` once the CSS animations and transitions running on `element`
|
|
7
|
+
* have finished, so script timing follows the stylesheet and its motion
|
|
8
|
+
* tokens instead of copying their values. When nothing is running (styles
|
|
9
|
+
* missing, `animation: none`, or no Web Animations support) it calls `done`
|
|
10
|
+
* right away. Returns a function that cancels the pending call.
|
|
11
|
+
*/
|
|
12
|
+
export function waitForMotion(element: Element, done: () => void) {
|
|
13
|
+
let pending = true
|
|
14
|
+
const running =
|
|
15
|
+
typeof element.getAnimations === "function"
|
|
16
|
+
? element
|
|
17
|
+
.getAnimations()
|
|
18
|
+
.filter(
|
|
19
|
+
(animation) => animation.effect?.getTiming().iterations !== Infinity
|
|
20
|
+
)
|
|
21
|
+
: []
|
|
22
|
+
|
|
23
|
+
if (running.length === 0) {
|
|
24
|
+
done()
|
|
25
|
+
} else {
|
|
26
|
+
void Promise.allSettled(running.map((animation) => animation.finished)).then(
|
|
27
|
+
() => {
|
|
28
|
+
if (pending) done()
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
pending = false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Calls `onDone` after the motion that turning `active` on starts on the
|
|
40
|
+
* element in `ref`, for example an exit animation before unmounting.
|
|
41
|
+
*/
|
|
42
|
+
export function useMotionEnd(
|
|
43
|
+
ref: React.RefObject<Element | null>,
|
|
44
|
+
active: boolean,
|
|
45
|
+
onDone: () => void
|
|
46
|
+
) {
|
|
47
|
+
const onDoneRef = React.useRef(onDone)
|
|
48
|
+
onDoneRef.current = onDone
|
|
49
|
+
|
|
50
|
+
React.useLayoutEffect(() => {
|
|
51
|
+
const element = ref.current
|
|
52
|
+
if (!active || !element) return
|
|
53
|
+
return waitForMotion(element, () => onDoneRef.current())
|
|
54
|
+
}, [active, ref])
|
|
55
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
const query = "(prefers-reduced-motion: reduce)"
|
|
6
|
+
|
|
7
|
+
function canMatchMedia() {
|
|
8
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function subscribe(onChange: () => void) {
|
|
12
|
+
if (!canMatchMedia()) return () => undefined
|
|
13
|
+
const media = window.matchMedia(query)
|
|
14
|
+
media.addEventListener?.("change", onChange)
|
|
15
|
+
return () => media.removeEventListener?.("change", onChange)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getSnapshot() {
|
|
19
|
+
return canMatchMedia() && window.matchMedia(query).matches
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether the user asked for reduced motion. Re-renders when that setting
|
|
24
|
+
* changes, and reads false where it cannot be queried (server, tests).
|
|
25
|
+
*/
|
|
26
|
+
export function useReducedMotion() {
|
|
27
|
+
return React.useSyncExternalStore(subscribe, getSnapshot, () => false)
|
|
28
|
+
}
|