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,401 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import {
|
|
5
|
+
DownloadIcon,
|
|
6
|
+
ListTodoIcon,
|
|
7
|
+
Maximize2Icon,
|
|
8
|
+
TerminalIcon,
|
|
9
|
+
XIcon,
|
|
10
|
+
} from "lucide-react"
|
|
11
|
+
|
|
12
|
+
import { Button } from "@/components/ui/button"
|
|
13
|
+
import { RollingCount } from "@/components/ui/rolling-count"
|
|
14
|
+
import {
|
|
15
|
+
TaskListHeader,
|
|
16
|
+
TaskListPreview,
|
|
17
|
+
TaskListRoot,
|
|
18
|
+
} from "@/components/ui/task-list"
|
|
19
|
+
import {
|
|
20
|
+
Tooltip,
|
|
21
|
+
TooltipContent,
|
|
22
|
+
TooltipProvider,
|
|
23
|
+
TooltipTrigger,
|
|
24
|
+
} from "@/components/ui/tooltip"
|
|
25
|
+
import { useMotionEnd } from "@/hooks/use-motion-end"
|
|
26
|
+
|
|
27
|
+
import { ApprovalFrame, type ApprovalCardBaseProps } from "./approval-frame"
|
|
28
|
+
import { ApprovalQuestions } from "./approval-questions"
|
|
29
|
+
|
|
30
|
+
type ApprovalPlanStep = {
|
|
31
|
+
id: string
|
|
32
|
+
title: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** `user` for a click, `timeout` when the plan countdown ran out. */
|
|
36
|
+
type ApprovalSource = "user" | "timeout"
|
|
37
|
+
|
|
38
|
+
type ApprovalPlanPayload = {
|
|
39
|
+
source: ApprovalSource
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type ApprovalPlanLabels = {
|
|
43
|
+
/** Countdown copy. Receives the rolling seconds element. */
|
|
44
|
+
autoApprove: (seconds: React.ReactNode) => React.ReactNode
|
|
45
|
+
cancelAutoApprove: string
|
|
46
|
+
downloadPlan: string
|
|
47
|
+
expandPlan: string
|
|
48
|
+
less: string
|
|
49
|
+
more: (count: number) => string
|
|
50
|
+
todos: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type ApprovalCommandProps = ApprovalCardBaseProps & {
|
|
54
|
+
command: string
|
|
55
|
+
cwd?: string
|
|
56
|
+
onApprove?: () => void
|
|
57
|
+
onReject?: () => void
|
|
58
|
+
rejectLabel?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type ApprovalPlanProps = ApprovalCardBaseProps & {
|
|
62
|
+
/**
|
|
63
|
+
* Opt-in countdown before the plan approves itself with `source: "timeout"`.
|
|
64
|
+
* Off by default so every approval waits for a person.
|
|
65
|
+
*/
|
|
66
|
+
autoApproveSeconds?: number
|
|
67
|
+
labels?: Partial<ApprovalPlanLabels>
|
|
68
|
+
onApprove?: (payload: ApprovalPlanPayload) => void
|
|
69
|
+
/** Renders a download action in the header when provided. */
|
|
70
|
+
onDownload?: () => void
|
|
71
|
+
/** Renders an expand action in the header when provided. */
|
|
72
|
+
onExpand?: () => void
|
|
73
|
+
/** Secondary action that opens the full plan. */
|
|
74
|
+
onViewPlan?: () => void
|
|
75
|
+
plan: readonly ApprovalPlanStep[]
|
|
76
|
+
/** Plan steps shown before the "more" fold. */
|
|
77
|
+
planPreviewCount?: number
|
|
78
|
+
planSummary?: React.ReactNode
|
|
79
|
+
planTitle?: React.ReactNode
|
|
80
|
+
viewPlanLabel?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const defaultPlanLabels: ApprovalPlanLabels = {
|
|
84
|
+
autoApprove: (seconds) => <>Auto approve in {seconds}s</>,
|
|
85
|
+
cancelAutoApprove: "Cancel auto approve",
|
|
86
|
+
downloadPlan: "Download plan",
|
|
87
|
+
expandPlan: "Expand plan",
|
|
88
|
+
less: "Show less",
|
|
89
|
+
more: (count) => `${count} more`,
|
|
90
|
+
todos: "To-dos",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// The countdown counts real seconds, so its tick is a unit of time rather
|
|
94
|
+
// than a theme value; the ring drains over the same tick.
|
|
95
|
+
const TICK_MS = 1000
|
|
96
|
+
|
|
97
|
+
/** Asks before the agent runs a shell command. */
|
|
98
|
+
const ApprovalCommand = React.forwardRef<HTMLDivElement, ApprovalCommandProps>(
|
|
99
|
+
function ApprovalCommand(
|
|
100
|
+
{
|
|
101
|
+
approveLabel = "Run",
|
|
102
|
+
command,
|
|
103
|
+
cwd,
|
|
104
|
+
onApprove,
|
|
105
|
+
onReject,
|
|
106
|
+
rejectLabel = "Skip",
|
|
107
|
+
title = "Run this command?",
|
|
108
|
+
...props
|
|
109
|
+
},
|
|
110
|
+
ref
|
|
111
|
+
) {
|
|
112
|
+
return (
|
|
113
|
+
<ApprovalFrame
|
|
114
|
+
{...props}
|
|
115
|
+
approveLabel={approveLabel}
|
|
116
|
+
icon={<TerminalIcon />}
|
|
117
|
+
onApprove={() => onApprove?.()}
|
|
118
|
+
onSecondary={() => onReject?.()}
|
|
119
|
+
ref={ref}
|
|
120
|
+
secondaryLabel={rejectLabel}
|
|
121
|
+
title={title}
|
|
122
|
+
tone="warning"
|
|
123
|
+
>
|
|
124
|
+
<div data-slot="approval-card-command">
|
|
125
|
+
{cwd ? (
|
|
126
|
+
<div data-slot="approval-card-cwd" dir="ltr">
|
|
127
|
+
{cwd}
|
|
128
|
+
</div>
|
|
129
|
+
) : null}
|
|
130
|
+
<pre data-slot="approval-card-code" dir="ltr">
|
|
131
|
+
{command}
|
|
132
|
+
</pre>
|
|
133
|
+
</div>
|
|
134
|
+
</ApprovalFrame>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
type CountdownState = "active" | "leaving" | "gone"
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Counts down once per second and calls `onElapse` once at zero; `cancel`
|
|
143
|
+
* stops it early. Either way the element in `fadeRef` fades out before it
|
|
144
|
+
* unmounts, so a card the host keeps mounted never lingers on "0s" with a
|
|
145
|
+
* live cancel.
|
|
146
|
+
*/
|
|
147
|
+
function useAutoApproveCountdown(seconds: number, onElapse: () => void) {
|
|
148
|
+
const enabled = seconds > 0
|
|
149
|
+
const [remaining, setRemaining] = React.useState(seconds)
|
|
150
|
+
const [state, setState] = React.useState<CountdownState>(
|
|
151
|
+
enabled ? "active" : "gone"
|
|
152
|
+
)
|
|
153
|
+
const fadeRef = React.useRef<HTMLDivElement>(null)
|
|
154
|
+
const settled = React.useRef(false)
|
|
155
|
+
const onElapseRef = React.useRef(onElapse)
|
|
156
|
+
onElapseRef.current = onElapse
|
|
157
|
+
|
|
158
|
+
const settle = () => {
|
|
159
|
+
settled.current = true
|
|
160
|
+
setState("leaving")
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Restart the countdown when the configured duration changes.
|
|
164
|
+
React.useEffect(() => {
|
|
165
|
+
setRemaining(seconds)
|
|
166
|
+
settled.current = false
|
|
167
|
+
setState(enabled ? "active" : "gone")
|
|
168
|
+
}, [enabled, seconds])
|
|
169
|
+
|
|
170
|
+
// A new duration restarts the tick too, so its first second is a whole one.
|
|
171
|
+
React.useEffect(() => {
|
|
172
|
+
if (!enabled || state !== "active") return
|
|
173
|
+
const interval = window.setInterval(() => {
|
|
174
|
+
setRemaining((value) => Math.max(0, value - 1))
|
|
175
|
+
}, TICK_MS)
|
|
176
|
+
return () => window.clearInterval(interval)
|
|
177
|
+
}, [enabled, seconds, state])
|
|
178
|
+
|
|
179
|
+
React.useEffect(() => {
|
|
180
|
+
if (!enabled || state !== "active") return
|
|
181
|
+
if (remaining > 0 || settled.current) return
|
|
182
|
+
settle()
|
|
183
|
+
onElapseRef.current()
|
|
184
|
+
}, [enabled, remaining, state])
|
|
185
|
+
|
|
186
|
+
const current: CountdownState = enabled ? state : "gone"
|
|
187
|
+
useMotionEnd(fadeRef, current === "leaving", () => setState("gone"))
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
cancel: () => {
|
|
191
|
+
if (current === "active") settle()
|
|
192
|
+
},
|
|
193
|
+
fadeRef,
|
|
194
|
+
remaining,
|
|
195
|
+
state: current,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Presents a short plan for sign-off as a pending to-do list, with an opt-in
|
|
201
|
+
* auto-approve countdown.
|
|
202
|
+
*/
|
|
203
|
+
const ApprovalPlan = React.forwardRef<HTMLDivElement, ApprovalPlanProps>(
|
|
204
|
+
function ApprovalPlan(
|
|
205
|
+
{
|
|
206
|
+
approveLabel = "Approve",
|
|
207
|
+
autoApproveSeconds = 0,
|
|
208
|
+
labels: labelOverrides,
|
|
209
|
+
onApprove,
|
|
210
|
+
onDownload,
|
|
211
|
+
onExpand,
|
|
212
|
+
onViewPlan,
|
|
213
|
+
plan,
|
|
214
|
+
planPreviewCount = 3,
|
|
215
|
+
planSummary,
|
|
216
|
+
planTitle,
|
|
217
|
+
title = "Plan overview",
|
|
218
|
+
viewPlanLabel = "View plan",
|
|
219
|
+
...props
|
|
220
|
+
},
|
|
221
|
+
ref
|
|
222
|
+
) {
|
|
223
|
+
const labels = { ...defaultPlanLabels, ...labelOverrides }
|
|
224
|
+
const countdown = useAutoApproveCountdown(autoApproveSeconds, () =>
|
|
225
|
+
onApprove?.({ source: "timeout" })
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
// A person decided, so the countdown must not approve on its own later.
|
|
229
|
+
const approve = () => {
|
|
230
|
+
countdown.cancel()
|
|
231
|
+
onApprove?.({ source: "user" })
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Opening the plan also stops a pending auto-approval.
|
|
235
|
+
const viewPlan = () => {
|
|
236
|
+
countdown.cancel()
|
|
237
|
+
onViewPlan?.()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<ApprovalFrame
|
|
242
|
+
{...props}
|
|
243
|
+
approveLabel={approveLabel}
|
|
244
|
+
footerStart={
|
|
245
|
+
countdown.state === "gone" ? undefined : (
|
|
246
|
+
<div
|
|
247
|
+
aria-live="polite"
|
|
248
|
+
data-slot="approval-card-auto-approve"
|
|
249
|
+
data-state={countdown.state}
|
|
250
|
+
ref={countdown.fadeRef}
|
|
251
|
+
>
|
|
252
|
+
<TooltipProvider>
|
|
253
|
+
<Tooltip>
|
|
254
|
+
<TooltipTrigger asChild>
|
|
255
|
+
<button
|
|
256
|
+
aria-label={labels.cancelAutoApprove}
|
|
257
|
+
data-slot="approval-card-countdown"
|
|
258
|
+
disabled={countdown.state !== "active"}
|
|
259
|
+
onClick={countdown.cancel}
|
|
260
|
+
type="button"
|
|
261
|
+
>
|
|
262
|
+
<svg
|
|
263
|
+
aria-hidden="true"
|
|
264
|
+
data-slot="approval-card-countdown-pie"
|
|
265
|
+
viewBox="0 0 24 24"
|
|
266
|
+
>
|
|
267
|
+
<circle
|
|
268
|
+
cx="12"
|
|
269
|
+
cy="12"
|
|
270
|
+
data-slot="approval-card-countdown-track"
|
|
271
|
+
fill="none"
|
|
272
|
+
r="9"
|
|
273
|
+
strokeWidth="var(--bh-icon-stroke-180)"
|
|
274
|
+
/>
|
|
275
|
+
<circle
|
|
276
|
+
cx="12"
|
|
277
|
+
cy="12"
|
|
278
|
+
data-slot="approval-card-countdown-fill"
|
|
279
|
+
fill="none"
|
|
280
|
+
pathLength={1}
|
|
281
|
+
r="9"
|
|
282
|
+
strokeDasharray={1}
|
|
283
|
+
strokeLinecap="round"
|
|
284
|
+
strokeWidth="var(--bh-icon-stroke-180)"
|
|
285
|
+
style={{
|
|
286
|
+
strokeDashoffset:
|
|
287
|
+
countdown.remaining / autoApproveSeconds,
|
|
288
|
+
transitionDuration: `${TICK_MS}ms`,
|
|
289
|
+
}}
|
|
290
|
+
transform="rotate(-90 12 12)"
|
|
291
|
+
/>
|
|
292
|
+
</svg>
|
|
293
|
+
<span
|
|
294
|
+
aria-hidden="true"
|
|
295
|
+
data-slot="approval-card-countdown-cancel"
|
|
296
|
+
>
|
|
297
|
+
<XIcon strokeWidth="var(--bh-icon-stroke-250)" />
|
|
298
|
+
</span>
|
|
299
|
+
</button>
|
|
300
|
+
</TooltipTrigger>
|
|
301
|
+
<TooltipContent showShortcut={false} variant="dark">
|
|
302
|
+
{labels.cancelAutoApprove}
|
|
303
|
+
</TooltipContent>
|
|
304
|
+
</Tooltip>
|
|
305
|
+
</TooltipProvider>
|
|
306
|
+
<span data-slot="approval-card-auto-approve-label">
|
|
307
|
+
{labels.autoApprove(
|
|
308
|
+
<RollingCount
|
|
309
|
+
data-slot="approval-card-seconds"
|
|
310
|
+
value={String(countdown.remaining)}
|
|
311
|
+
/>
|
|
312
|
+
)}
|
|
313
|
+
</span>
|
|
314
|
+
</div>
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
headerActions={
|
|
318
|
+
onDownload || onExpand ? (
|
|
319
|
+
<>
|
|
320
|
+
{onDownload ? (
|
|
321
|
+
<Button
|
|
322
|
+
aria-label={labels.downloadPlan}
|
|
323
|
+
onClick={onDownload}
|
|
324
|
+
size="icon-xs"
|
|
325
|
+
variant="ghost"
|
|
326
|
+
>
|
|
327
|
+
<DownloadIcon />
|
|
328
|
+
</Button>
|
|
329
|
+
) : null}
|
|
330
|
+
{onExpand ? (
|
|
331
|
+
<Button
|
|
332
|
+
aria-label={labels.expandPlan}
|
|
333
|
+
onClick={onExpand}
|
|
334
|
+
size="icon-xs"
|
|
335
|
+
variant="ghost"
|
|
336
|
+
>
|
|
337
|
+
<Maximize2Icon />
|
|
338
|
+
</Button>
|
|
339
|
+
) : null}
|
|
340
|
+
</>
|
|
341
|
+
) : undefined
|
|
342
|
+
}
|
|
343
|
+
icon={<ListTodoIcon />}
|
|
344
|
+
onApprove={approve}
|
|
345
|
+
onSecondary={viewPlan}
|
|
346
|
+
ref={ref}
|
|
347
|
+
secondaryLabel={viewPlanLabel}
|
|
348
|
+
title={title}
|
|
349
|
+
tone="success"
|
|
350
|
+
>
|
|
351
|
+
{planTitle || planSummary ? (
|
|
352
|
+
<div data-slot="approval-card-plan">
|
|
353
|
+
{planTitle ? (
|
|
354
|
+
<div data-slot="approval-card-plan-title" dir="auto">
|
|
355
|
+
{planTitle}
|
|
356
|
+
</div>
|
|
357
|
+
) : null}
|
|
358
|
+
{planSummary ? (
|
|
359
|
+
<div data-slot="approval-card-plan-summary" dir="auto">
|
|
360
|
+
{planSummary}
|
|
361
|
+
</div>
|
|
362
|
+
) : null}
|
|
363
|
+
</div>
|
|
364
|
+
) : null}
|
|
365
|
+
<TaskListRoot surface="well">
|
|
366
|
+
<TaskListHeader count={plan.length}>{labels.todos}</TaskListHeader>
|
|
367
|
+
<TaskListPreview
|
|
368
|
+
items={plan.map((planStep) => ({
|
|
369
|
+
id: planStep.id,
|
|
370
|
+
label: planStep.title,
|
|
371
|
+
}))}
|
|
372
|
+
labels={{ less: labels.less, more: labels.more }}
|
|
373
|
+
visibleCount={planPreviewCount}
|
|
374
|
+
/>
|
|
375
|
+
</TaskListRoot>
|
|
376
|
+
</ApprovalFrame>
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
export { ApprovalCommand, ApprovalFrame, ApprovalPlan, ApprovalQuestions }
|
|
382
|
+
export type {
|
|
383
|
+
ApprovalCardBaseProps,
|
|
384
|
+
ApprovalFrameProps,
|
|
385
|
+
ApprovalTone,
|
|
386
|
+
} from "./approval-frame"
|
|
387
|
+
export type {
|
|
388
|
+
ApprovalAnswers,
|
|
389
|
+
ApprovalQuestion,
|
|
390
|
+
ApprovalQuestionsLabels,
|
|
391
|
+
ApprovalQuestionsPayload,
|
|
392
|
+
ApprovalQuestionsProps,
|
|
393
|
+
} from "./approval-questions"
|
|
394
|
+
export type {
|
|
395
|
+
ApprovalCommandProps,
|
|
396
|
+
ApprovalPlanLabels,
|
|
397
|
+
ApprovalPlanPayload,
|
|
398
|
+
ApprovalPlanProps,
|
|
399
|
+
ApprovalPlanStep,
|
|
400
|
+
ApprovalSource,
|
|
401
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { CornerDownLeftIcon } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { Button } from "@/components/ui/button"
|
|
8
|
+
|
|
9
|
+
import "./approval-card.css"
|
|
10
|
+
|
|
11
|
+
/** Tint of the header's icon tile. */
|
|
12
|
+
type ApprovalTone = "info" | "warning" | "success"
|
|
13
|
+
|
|
14
|
+
/** Props every ready-made approval card shares. */
|
|
15
|
+
type ApprovalCardBaseProps = Omit<
|
|
16
|
+
React.ComponentProps<"div">,
|
|
17
|
+
"children" | "title"
|
|
18
|
+
> & {
|
|
19
|
+
/** Primary action label. Each card has its own default. */
|
|
20
|
+
approveLabel?: string
|
|
21
|
+
/** Header copy. Each card has its own default. */
|
|
22
|
+
title?: React.ReactNode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type ApprovalFrameProps = Omit<React.ComponentProps<"div">, "title"> & {
|
|
26
|
+
/** Keeps the primary action disabled until the decision is complete. */
|
|
27
|
+
approveDisabled?: boolean
|
|
28
|
+
approveLabel: string
|
|
29
|
+
/** Start of the action row, such as a step counter or a countdown. */
|
|
30
|
+
footerStart?: React.ReactNode
|
|
31
|
+
/** Controls at the end of the header, such as download or expand. */
|
|
32
|
+
headerActions?: React.ReactNode
|
|
33
|
+
/** Header glyph, shown in a tile tinted by `tone`. */
|
|
34
|
+
icon: React.ReactNode
|
|
35
|
+
onApprove: () => void
|
|
36
|
+
onSecondary: () => void
|
|
37
|
+
secondaryLabel: string
|
|
38
|
+
title: React.ReactNode
|
|
39
|
+
tone: ApprovalTone
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Layout every approval card shares: a tinted icon header, the body, and an
|
|
44
|
+
* action row with a secondary and a primary action. The ready-made cards
|
|
45
|
+
* compose it; compose it yourself for another kind of decision.
|
|
46
|
+
*/
|
|
47
|
+
const ApprovalFrame = React.forwardRef<HTMLDivElement, ApprovalFrameProps>(
|
|
48
|
+
function ApprovalFrame(
|
|
49
|
+
{
|
|
50
|
+
approveDisabled = false,
|
|
51
|
+
approveLabel,
|
|
52
|
+
children,
|
|
53
|
+
className,
|
|
54
|
+
footerStart,
|
|
55
|
+
headerActions,
|
|
56
|
+
icon,
|
|
57
|
+
onApprove,
|
|
58
|
+
onSecondary,
|
|
59
|
+
secondaryLabel,
|
|
60
|
+
title,
|
|
61
|
+
tone,
|
|
62
|
+
...props
|
|
63
|
+
},
|
|
64
|
+
ref
|
|
65
|
+
) {
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
data-slot="approval-card"
|
|
69
|
+
ref={ref}
|
|
70
|
+
className={cn("flex min-w-0 max-w-full flex-col", className)}
|
|
71
|
+
{...props}
|
|
72
|
+
>
|
|
73
|
+
<div data-slot="approval-card-header">
|
|
74
|
+
<span aria-hidden="true" data-slot="approval-card-icon" data-tone={tone}>
|
|
75
|
+
{icon}
|
|
76
|
+
</span>
|
|
77
|
+
<div data-slot="approval-card-title" dir="auto">
|
|
78
|
+
{title}
|
|
79
|
+
</div>
|
|
80
|
+
{headerActions ? (
|
|
81
|
+
<div data-slot="approval-card-header-actions">{headerActions}</div>
|
|
82
|
+
) : null}
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{children}
|
|
86
|
+
|
|
87
|
+
<div data-slot="approval-card-actions">
|
|
88
|
+
{/* Without a start slot, a spacer keeps the buttons at the end. */}
|
|
89
|
+
{footerStart ?? (
|
|
90
|
+
<span aria-hidden="true" data-slot="approval-card-spacer" />
|
|
91
|
+
)}
|
|
92
|
+
<div data-slot="approval-card-buttons">
|
|
93
|
+
<Button onClick={onSecondary} size="sm" variant="soft">
|
|
94
|
+
{secondaryLabel}
|
|
95
|
+
</Button>
|
|
96
|
+
<Button disabled={approveDisabled} onClick={onApprove} size="sm">
|
|
97
|
+
{approveLabel}
|
|
98
|
+
<CornerDownLeftIcon data-icon="inline-end" data-rtl-flip="true" />
|
|
99
|
+
</Button>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
export { ApprovalFrame }
|
|
108
|
+
export type { ApprovalCardBaseProps, ApprovalFrameProps, ApprovalTone }
|