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