@almanaclabs/blaze-ui 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/dist/components/accordion/accordion.js +2 -2
- package/dist/components/alert/alert.d.ts +1 -1
- package/dist/components/alert/alert.js +4 -4
- package/dist/components/alert-dialog/alert-dialog.d.ts.map +1 -1
- package/dist/components/alert-dialog/alert-dialog.js +3 -3
- package/dist/components/attachment/attachment.d.ts +2 -2
- package/dist/components/attachment/attachment.js +3 -3
- package/dist/components/avatar/avatar.d.ts +6 -4
- package/dist/components/avatar/avatar.d.ts.map +1 -1
- package/dist/components/avatar/avatar.js +10 -9
- package/dist/components/badge/badge.d.ts +3 -2
- package/dist/components/badge/badge.d.ts.map +1 -1
- package/dist/components/badge/badge.js +30 -14
- package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/breadcrumb/breadcrumb.js +2 -2
- package/dist/components/bubble/bubble.d.ts +1 -1
- package/dist/components/bubble/bubble.js +1 -1
- package/dist/components/button/button.d.ts +5 -3
- package/dist/components/button/button.d.ts.map +1 -1
- package/dist/components/button/button.js +46 -27
- package/dist/components/button/index.d.ts +1 -1
- package/dist/components/button/index.d.ts.map +1 -1
- package/dist/components/button-group/button-group.js +4 -4
- package/dist/components/calendar/calendar.d.ts.map +1 -1
- package/dist/components/calendar/calendar.js +18 -18
- package/dist/components/card/card.js +4 -4
- package/dist/components/carousel/carousel.d.ts.map +1 -1
- package/dist/components/carousel/carousel.js +31 -31
- package/dist/components/chart/chart.js +34 -34
- package/dist/components/checkbox/checkbox.js +1 -1
- package/dist/components/combobox/combobox.d.ts.map +1 -1
- package/dist/components/combobox/combobox.js +44 -44
- package/dist/components/data-table/data-table.js +1 -1
- package/dist/components/date-picker/date-picker.js +10 -10
- package/dist/components/dialog/dialog.js +3 -3
- package/dist/components/drawer/drawer.js +37 -37
- package/dist/components/dropdown-menu/dropdown-menu.js +7 -7
- package/dist/components/empty/empty.d.ts.map +1 -1
- package/dist/components/empty/empty.js +4 -4
- package/dist/components/field/field.d.ts.map +1 -1
- package/dist/components/field/field.js +28 -28
- package/dist/components/identity-mark/identity-mark.d.ts +5 -0
- package/dist/components/identity-mark/identity-mark.d.ts.map +1 -0
- package/dist/components/identity-mark/identity-mark.js +20 -0
- package/dist/components/identity-mark/index.d.ts +2 -0
- package/dist/components/identity-mark/index.d.ts.map +1 -0
- package/dist/components/input/input.d.ts +9 -2
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +23 -10
- package/dist/components/input-group/input-group.d.ts +11 -3
- package/dist/components/input-group/input-group.d.ts.map +1 -1
- package/dist/components/input-group/input-group.js +56 -38
- package/dist/components/item/item.d.ts +2 -2
- package/dist/components/item/item.js +2 -2
- package/dist/components/label/label.js +2 -2
- package/dist/components/marker/marker.d.ts +1 -1
- package/dist/components/marker/marker.js +3 -3
- package/dist/components/message/message.js +1 -1
- package/dist/components/metric-overview/index.d.ts +2 -0
- package/dist/components/metric-overview/index.d.ts.map +1 -0
- package/dist/components/metric-overview/metric-overview.d.ts +12 -0
- package/dist/components/metric-overview/metric-overview.d.ts.map +1 -0
- package/dist/components/metric-overview/metric-overview.js +42 -0
- package/dist/components/native-select/native-select.d.ts +2 -1
- package/dist/components/native-select/native-select.d.ts.map +1 -1
- package/dist/components/native-select/native-select.js +22 -21
- package/dist/components/navigation-action/index.d.ts +2 -0
- package/dist/components/navigation-action/index.d.ts.map +1 -0
- package/dist/components/navigation-action/navigation-action.d.ts +6 -0
- package/dist/components/navigation-action/navigation-action.d.ts.map +1 -0
- package/dist/components/navigation-action/navigation-action.js +18 -0
- package/dist/components/pagination/pagination.d.ts.map +1 -1
- package/dist/components/pagination/pagination.js +6 -6
- package/dist/components/popover/popover.js +1 -1
- package/dist/components/progress/progress.js +2 -2
- package/dist/components/questionnaire/questionnaire-primitive.js +71 -71
- package/dist/components/questionnaire/questionnaire.d.ts.map +1 -1
- package/dist/components/questionnaire/questionnaire.js +7 -7
- package/dist/components/scroll-area/scroll-area.js +1 -1
- package/dist/components/select/select.d.ts +2 -1
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/select/select.js +50 -49
- package/dist/components/sheet/sheet.js +2 -2
- package/dist/components/sidebar/sidebar.d.ts +13 -2
- package/dist/components/sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/sidebar/sidebar.js +156 -95
- package/dist/components/spinner/spinner.js +1 -1
- package/dist/components/table/table.d.ts +4 -1
- package/dist/components/table/table.d.ts.map +1 -1
- package/dist/components/table/table.js +11 -9
- package/dist/components/tabs/tabs.d.ts +1 -1
- package/dist/components/tabs/tabs.js +2 -2
- package/dist/components/textarea/textarea.js +2 -2
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +6 -6
- package/dist/components/toggle/toggle.d.ts +1 -1
- package/dist/components/toggle/toggle.js +1 -1
- package/dist/components/toggle-group/toggle-group.js +10 -10
- package/dist/components/tooltip/tooltip.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -55
- package/dist/lib/control-size.d.ts +9 -0
- package/dist/lib/control-size.d.ts.map +1 -0
- package/dist/lib/control-size.js +10 -0
- package/dist/recipes/recipe-runtime.d.ts +12 -0
- package/dist/recipes/recipe-runtime.d.ts.map +1 -1
- package/package.json +1 -1
- package/recipes/README.md +2 -0
- package/recipes/guided-workflow/README.md +51 -0
- package/recipes/guided-workflow/guided-workflow.tsx +495 -0
- package/recipes/manifest.json +8 -1
- package/recipes/workspace-shell/README.md +79 -25
- package/recipes/workspace-shell/assets/google-search-ads.svg +6 -0
- package/recipes/workspace-shell/assets/home.svg +5 -0
- package/recipes/workspace-shell/assets/radiant-health.png +0 -0
- package/recipes/workspace-shell/workspace-shell.css +55 -0
- package/recipes/workspace-shell/workspace-shell.tsx +463 -220
- package/src/styles/fonts/Sohne-Book.woff2 +0 -0
- package/src/styles/fonts/Sohne-Medium.woff2 +0 -0
- package/src/styles/fonts/Sohne-Semi.woff2 +0 -0
- package/src/styles/globals.css +235 -56
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
IconAlertCircle,
|
|
4
|
+
IconCheck,
|
|
5
|
+
IconChevronRight,
|
|
6
|
+
} from '@tabler/icons-react'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
Badge,
|
|
10
|
+
Button,
|
|
11
|
+
NavigationAction,
|
|
12
|
+
Progress,
|
|
13
|
+
cn,
|
|
14
|
+
} from '@almanaclabs/blaze-ui'
|
|
15
|
+
|
|
16
|
+
export type GuidedWorkflowStep = {
|
|
17
|
+
key: string
|
|
18
|
+
label: string
|
|
19
|
+
description?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type GuidedWorkflowItemStatus = 'complete' | 'attention' | 'pending'
|
|
23
|
+
|
|
24
|
+
export type GuidedWorkflowItem = {
|
|
25
|
+
key: string
|
|
26
|
+
label: string
|
|
27
|
+
description?: string
|
|
28
|
+
status?: GuidedWorkflowItemStatus
|
|
29
|
+
statusLabel?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type GuidedWorkflowVariant = 'default' | 'compact'
|
|
33
|
+
|
|
34
|
+
export type GuidedWorkflowRecipeProps = {
|
|
35
|
+
title: string
|
|
36
|
+
steps: readonly GuidedWorkflowStep[]
|
|
37
|
+
activeStepKey: string
|
|
38
|
+
railLabel: string
|
|
39
|
+
items: readonly GuidedWorkflowItem[]
|
|
40
|
+
activeItemKey: string
|
|
41
|
+
onItemSelect: (key: string) => void
|
|
42
|
+
children: ReactNode
|
|
43
|
+
backLabel?: string
|
|
44
|
+
continueLabel?: string
|
|
45
|
+
continueDisabled?: boolean
|
|
46
|
+
footerStatus?: ReactNode
|
|
47
|
+
secondaryActions?: ReactNode
|
|
48
|
+
onBack?: () => void
|
|
49
|
+
onContinue: () => void
|
|
50
|
+
variant?: GuidedWorkflowVariant
|
|
51
|
+
className?: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type GuidedWorkflowPageHeaderProps = {
|
|
55
|
+
title: string
|
|
56
|
+
steps: readonly GuidedWorkflowStep[]
|
|
57
|
+
activeStepKey: string
|
|
58
|
+
variant?: GuidedWorkflowVariant
|
|
59
|
+
className?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type GuidedWorkflowBodyProps = {
|
|
63
|
+
railLabel: string
|
|
64
|
+
items: readonly GuidedWorkflowItem[]
|
|
65
|
+
activeItemKey: string
|
|
66
|
+
onItemSelect: (key: string) => void
|
|
67
|
+
children: ReactNode
|
|
68
|
+
contentLabel?: string
|
|
69
|
+
variant?: GuidedWorkflowVariant
|
|
70
|
+
className?: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type GuidedWorkflowFooterProps = {
|
|
74
|
+
backLabel?: string
|
|
75
|
+
continueLabel?: string
|
|
76
|
+
continueDisabled?: boolean
|
|
77
|
+
footerStatus?: ReactNode
|
|
78
|
+
secondaryActions?: ReactNode
|
|
79
|
+
onBack?: () => void
|
|
80
|
+
onContinue: () => void
|
|
81
|
+
variant?: GuidedWorkflowVariant
|
|
82
|
+
className?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type GuidedWorkflowSectionProps = {
|
|
86
|
+
title: ReactNode
|
|
87
|
+
icon?: ReactNode
|
|
88
|
+
description?: ReactNode
|
|
89
|
+
action?: ReactNode
|
|
90
|
+
children: ReactNode
|
|
91
|
+
className?: string
|
|
92
|
+
markerClassName?: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const statusConfiguration = {
|
|
96
|
+
attention: {
|
|
97
|
+
fallbackLabel: 'Needs attention',
|
|
98
|
+
icon: IconAlertCircle,
|
|
99
|
+
variant: 'destructive',
|
|
100
|
+
},
|
|
101
|
+
complete: {
|
|
102
|
+
fallbackLabel: 'Complete',
|
|
103
|
+
icon: IconCheck,
|
|
104
|
+
variant: 'success',
|
|
105
|
+
},
|
|
106
|
+
pending: {
|
|
107
|
+
fallbackLabel: 'Pending',
|
|
108
|
+
icon: null,
|
|
109
|
+
variant: 'secondary',
|
|
110
|
+
},
|
|
111
|
+
} as const
|
|
112
|
+
|
|
113
|
+
function WorkflowItemStatus({ item }: { item: GuidedWorkflowItem }) {
|
|
114
|
+
if (!item.status) return null
|
|
115
|
+
|
|
116
|
+
const configuration = statusConfiguration[item.status]
|
|
117
|
+
const Icon = configuration.icon
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Badge variant={configuration.variant}>
|
|
121
|
+
{Icon ? <Icon data-icon="inline-start" aria-hidden="true" /> : null}
|
|
122
|
+
{item.statusLabel ?? configuration.fallbackLabel}
|
|
123
|
+
</Badge>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function GuidedWorkflowSections({
|
|
128
|
+
className,
|
|
129
|
+
...props
|
|
130
|
+
}: React.ComponentProps<'div'>) {
|
|
131
|
+
return <div className={cn('grid gap-8', className)} {...props} />
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function GuidedWorkflowSection({
|
|
135
|
+
title,
|
|
136
|
+
icon,
|
|
137
|
+
description,
|
|
138
|
+
action,
|
|
139
|
+
children,
|
|
140
|
+
className,
|
|
141
|
+
markerClassName,
|
|
142
|
+
}: GuidedWorkflowSectionProps) {
|
|
143
|
+
return (
|
|
144
|
+
<section
|
|
145
|
+
className={cn(
|
|
146
|
+
'grid gap-5 border-b border-border pb-8 last:border-b-0 last:pb-0',
|
|
147
|
+
className,
|
|
148
|
+
)}
|
|
149
|
+
>
|
|
150
|
+
<header className="flex min-w-0 items-start justify-between gap-4">
|
|
151
|
+
<div className="flex min-w-0 items-center gap-2.5">
|
|
152
|
+
{icon ? (
|
|
153
|
+
<span
|
|
154
|
+
className={cn(
|
|
155
|
+
'grid size-8 shrink-0 place-items-center rounded-full text-primary-foreground [--icon-stroke-width:var(--icon-stroke-16)] [&>svg:not([class*=size-])]:size-4',
|
|
156
|
+
markerClassName,
|
|
157
|
+
)}
|
|
158
|
+
aria-hidden="true"
|
|
159
|
+
>
|
|
160
|
+
{icon}
|
|
161
|
+
</span>
|
|
162
|
+
) : null}
|
|
163
|
+
<div className="min-w-0">
|
|
164
|
+
<h2 className="type-section-title font-normal text-foreground">
|
|
165
|
+
{title}
|
|
166
|
+
</h2>
|
|
167
|
+
{description ? (
|
|
168
|
+
<p className="type-body mt-1 text-muted-foreground">
|
|
169
|
+
{description}
|
|
170
|
+
</p>
|
|
171
|
+
) : null}
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
{action}
|
|
175
|
+
</header>
|
|
176
|
+
<div className="min-w-0">{children}</div>
|
|
177
|
+
</section>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getActiveStepIndex(
|
|
182
|
+
steps: readonly GuidedWorkflowStep[],
|
|
183
|
+
activeStepKey: string,
|
|
184
|
+
) {
|
|
185
|
+
return Math.max(
|
|
186
|
+
0,
|
|
187
|
+
steps.findIndex((step) => step.key === activeStepKey),
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function GuidedWorkflowPageHeader({
|
|
192
|
+
title,
|
|
193
|
+
steps,
|
|
194
|
+
activeStepKey,
|
|
195
|
+
variant = 'default',
|
|
196
|
+
className,
|
|
197
|
+
}: GuidedWorkflowPageHeaderProps) {
|
|
198
|
+
const activeStepIndex = getActiveStepIndex(steps, activeStepKey)
|
|
199
|
+
const activeStep = steps[activeStepIndex]
|
|
200
|
+
const progressValue =
|
|
201
|
+
steps.length === 0 ? 0 : ((activeStepIndex + 1) / steps.length) * 100
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<header
|
|
205
|
+
className={cn(
|
|
206
|
+
'border-b bg-background px-4 md:px-6',
|
|
207
|
+
variant === 'compact' ? 'flex min-h-[76px] items-center py-3' : 'py-4',
|
|
208
|
+
className,
|
|
209
|
+
)}
|
|
210
|
+
>
|
|
211
|
+
<div className="flex w-full flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
|
212
|
+
<div className="min-w-0">
|
|
213
|
+
{variant === 'default' ? (
|
|
214
|
+
<p className="type-meta text-muted-foreground uppercase">
|
|
215
|
+
Step {steps.length === 0 ? 0 : activeStepIndex + 1} of{' '}
|
|
216
|
+
{steps.length}
|
|
217
|
+
</p>
|
|
218
|
+
) : null}
|
|
219
|
+
<h1
|
|
220
|
+
className={cn(
|
|
221
|
+
'type-page-title truncate font-normal',
|
|
222
|
+
variant === 'default' && 'mt-1',
|
|
223
|
+
)}
|
|
224
|
+
>
|
|
225
|
+
{title}
|
|
226
|
+
</h1>
|
|
227
|
+
{variant === 'default' && activeStep?.description ? (
|
|
228
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
229
|
+
{activeStep.description}
|
|
230
|
+
</p>
|
|
231
|
+
) : null}
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<nav aria-label="Workflow progress" className="min-w-0 xl:w-3/5">
|
|
235
|
+
<ol className="type-meta flex min-w-max items-center gap-2 overflow-x-auto pb-1 sm:min-w-0 sm:justify-end">
|
|
236
|
+
{steps.map((step, index) => {
|
|
237
|
+
const complete = index < activeStepIndex
|
|
238
|
+
const active = index === activeStepIndex
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<li
|
|
242
|
+
key={step.key}
|
|
243
|
+
aria-current={active ? 'step' : undefined}
|
|
244
|
+
className={cn(
|
|
245
|
+
'flex items-center gap-2 text-muted-foreground',
|
|
246
|
+
active && 'font-medium text-foreground',
|
|
247
|
+
)}
|
|
248
|
+
>
|
|
249
|
+
<span
|
|
250
|
+
className={cn(
|
|
251
|
+
'grid shrink-0 place-items-center border bg-muted',
|
|
252
|
+
variant === 'compact'
|
|
253
|
+
? 'size-4 rounded-[4px] text-[0.625rem]'
|
|
254
|
+
: 'size-5 rounded-md text-[0.65rem]',
|
|
255
|
+
active && 'border-foreground/40 bg-background',
|
|
256
|
+
complete &&
|
|
257
|
+
variant === 'default' &&
|
|
258
|
+
'border-primary bg-primary text-primary-foreground',
|
|
259
|
+
)}
|
|
260
|
+
aria-hidden="true"
|
|
261
|
+
>
|
|
262
|
+
{complete && variant === 'default' ? (
|
|
263
|
+
<IconCheck className="size-3" />
|
|
264
|
+
) : (
|
|
265
|
+
index + 1
|
|
266
|
+
)}
|
|
267
|
+
</span>
|
|
268
|
+
<span>{step.label}</span>
|
|
269
|
+
{index < steps.length - 1 ? (
|
|
270
|
+
<IconChevronRight
|
|
271
|
+
className="size-3.5 text-border"
|
|
272
|
+
aria-hidden="true"
|
|
273
|
+
/>
|
|
274
|
+
) : null}
|
|
275
|
+
</li>
|
|
276
|
+
)
|
|
277
|
+
})}
|
|
278
|
+
</ol>
|
|
279
|
+
{variant === 'default' ? (
|
|
280
|
+
<Progress
|
|
281
|
+
value={progressValue}
|
|
282
|
+
aria-label={`${title} progress`}
|
|
283
|
+
className="mt-2 gap-0"
|
|
284
|
+
/>
|
|
285
|
+
) : null}
|
|
286
|
+
</nav>
|
|
287
|
+
</div>
|
|
288
|
+
</header>
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function GuidedWorkflowBody({
|
|
293
|
+
railLabel,
|
|
294
|
+
items,
|
|
295
|
+
activeItemKey,
|
|
296
|
+
onItemSelect,
|
|
297
|
+
children,
|
|
298
|
+
contentLabel,
|
|
299
|
+
variant = 'default',
|
|
300
|
+
className,
|
|
301
|
+
}: GuidedWorkflowBodyProps) {
|
|
302
|
+
return (
|
|
303
|
+
<div
|
|
304
|
+
className={cn(
|
|
305
|
+
'grid min-h-0 grid-rows-[auto_minmax(0,1fr)]',
|
|
306
|
+
variant === 'compact'
|
|
307
|
+
? 'md:grid-cols-[21.25rem_minmax(0,1fr)] md:grid-rows-1'
|
|
308
|
+
: 'lg:grid-cols-[20rem_minmax(0,1fr)] lg:grid-rows-1',
|
|
309
|
+
className,
|
|
310
|
+
)}
|
|
311
|
+
>
|
|
312
|
+
<aside
|
|
313
|
+
className={cn(
|
|
314
|
+
'min-w-0 border-b',
|
|
315
|
+
variant === 'compact'
|
|
316
|
+
? 'bg-background md:border-r md:border-b-0'
|
|
317
|
+
: 'bg-surface-subtle lg:border-r lg:border-b-0',
|
|
318
|
+
)}
|
|
319
|
+
>
|
|
320
|
+
<nav
|
|
321
|
+
aria-label={railLabel}
|
|
322
|
+
className={cn(
|
|
323
|
+
'flex gap-2 overflow-x-auto p-3',
|
|
324
|
+
variant === 'compact'
|
|
325
|
+
? 'md:grid md:max-h-full md:overflow-y-auto md:p-6'
|
|
326
|
+
: 'lg:grid lg:max-h-full lg:overflow-y-auto lg:p-5',
|
|
327
|
+
)}
|
|
328
|
+
>
|
|
329
|
+
<p
|
|
330
|
+
className={cn(
|
|
331
|
+
'type-label hidden px-2 font-medium text-foreground',
|
|
332
|
+
variant === 'compact' ? 'md:block' : 'lg:block',
|
|
333
|
+
)}
|
|
334
|
+
>
|
|
335
|
+
{railLabel}
|
|
336
|
+
</p>
|
|
337
|
+
{items.map((item) => {
|
|
338
|
+
const selected = item.key === activeItemKey
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<NavigationAction
|
|
342
|
+
key={item.key}
|
|
343
|
+
type="button"
|
|
344
|
+
aria-current={selected ? 'page' : undefined}
|
|
345
|
+
onClick={() => onItemSelect(item.key)}
|
|
346
|
+
className={cn(
|
|
347
|
+
'h-auto min-w-64 items-start justify-start whitespace-normal border border-transparent px-3 py-3 text-left',
|
|
348
|
+
variant === 'compact'
|
|
349
|
+
? 'md:min-w-0 md:w-full'
|
|
350
|
+
: 'lg:min-w-0 lg:w-full',
|
|
351
|
+
variant === 'compact' && 'rounded-sm md:min-h-19 md:px-5',
|
|
352
|
+
)}
|
|
353
|
+
>
|
|
354
|
+
<span className="grid min-w-0 flex-1 gap-1">
|
|
355
|
+
<span className="type-content truncate font-normal">
|
|
356
|
+
{item.label}
|
|
357
|
+
</span>
|
|
358
|
+
{item.description ? (
|
|
359
|
+
<span className="type-body truncate font-normal text-muted-foreground">
|
|
360
|
+
{item.description}
|
|
361
|
+
</span>
|
|
362
|
+
) : null}
|
|
363
|
+
<WorkflowItemStatus item={item} />
|
|
364
|
+
</span>
|
|
365
|
+
</NavigationAction>
|
|
366
|
+
)
|
|
367
|
+
})}
|
|
368
|
+
</nav>
|
|
369
|
+
</aside>
|
|
370
|
+
|
|
371
|
+
<section
|
|
372
|
+
aria-label={contentLabel ?? railLabel}
|
|
373
|
+
className={cn(
|
|
374
|
+
'min-w-0 overflow-y-auto',
|
|
375
|
+
variant === 'compact' ? 'bg-background' : 'bg-surface-subtle',
|
|
376
|
+
)}
|
|
377
|
+
>
|
|
378
|
+
<div
|
|
379
|
+
className={cn(
|
|
380
|
+
'mx-auto w-full p-4 md:p-8',
|
|
381
|
+
variant === 'compact' ? 'max-w-none md:p-6' : 'max-w-5xl lg:p-10',
|
|
382
|
+
)}
|
|
383
|
+
>
|
|
384
|
+
{children}
|
|
385
|
+
</div>
|
|
386
|
+
</section>
|
|
387
|
+
</div>
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function GuidedWorkflowFooter({
|
|
392
|
+
backLabel = 'Back',
|
|
393
|
+
continueLabel = 'Continue',
|
|
394
|
+
continueDisabled = false,
|
|
395
|
+
footerStatus,
|
|
396
|
+
secondaryActions,
|
|
397
|
+
onBack,
|
|
398
|
+
onContinue,
|
|
399
|
+
variant = 'default',
|
|
400
|
+
className,
|
|
401
|
+
}: GuidedWorkflowFooterProps) {
|
|
402
|
+
return (
|
|
403
|
+
<footer
|
|
404
|
+
className={cn(
|
|
405
|
+
'flex flex-wrap items-center justify-between gap-3 border-t bg-background px-4 py-3 md:px-6',
|
|
406
|
+
variant === 'compact' ? 'min-h-[88px]' : 'min-h-16',
|
|
407
|
+
className,
|
|
408
|
+
)}
|
|
409
|
+
>
|
|
410
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
411
|
+
{onBack ? (
|
|
412
|
+
<Button type="button" variant="secondary" size="md" onClick={onBack}>
|
|
413
|
+
{backLabel}
|
|
414
|
+
</Button>
|
|
415
|
+
) : null}
|
|
416
|
+
{footerStatus ? (
|
|
417
|
+
<div className="truncate text-sm text-muted-foreground">
|
|
418
|
+
{footerStatus}
|
|
419
|
+
</div>
|
|
420
|
+
) : null}
|
|
421
|
+
</div>
|
|
422
|
+
<div className="flex items-center gap-2">
|
|
423
|
+
{secondaryActions}
|
|
424
|
+
<Button
|
|
425
|
+
type="button"
|
|
426
|
+
size="md"
|
|
427
|
+
disabled={continueDisabled}
|
|
428
|
+
onClick={onContinue}
|
|
429
|
+
>
|
|
430
|
+
{continueLabel}
|
|
431
|
+
<IconChevronRight data-icon="inline-end" aria-hidden="true" />
|
|
432
|
+
</Button>
|
|
433
|
+
</div>
|
|
434
|
+
</footer>
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function GuidedWorkflowRecipe({
|
|
439
|
+
title,
|
|
440
|
+
steps,
|
|
441
|
+
activeStepKey,
|
|
442
|
+
railLabel,
|
|
443
|
+
items,
|
|
444
|
+
activeItemKey,
|
|
445
|
+
onItemSelect,
|
|
446
|
+
children,
|
|
447
|
+
backLabel = 'Back',
|
|
448
|
+
continueLabel = 'Continue',
|
|
449
|
+
continueDisabled = false,
|
|
450
|
+
footerStatus,
|
|
451
|
+
secondaryActions,
|
|
452
|
+
onBack,
|
|
453
|
+
onContinue,
|
|
454
|
+
variant = 'default',
|
|
455
|
+
className,
|
|
456
|
+
}: GuidedWorkflowRecipeProps) {
|
|
457
|
+
const activeStepIndex = getActiveStepIndex(steps, activeStepKey)
|
|
458
|
+
const activeStep = steps[activeStepIndex]
|
|
459
|
+
|
|
460
|
+
return (
|
|
461
|
+
<section
|
|
462
|
+
className={cn(
|
|
463
|
+
'grid h-dvh min-h-[720px] grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden bg-background text-foreground',
|
|
464
|
+
className,
|
|
465
|
+
)}
|
|
466
|
+
>
|
|
467
|
+
<GuidedWorkflowPageHeader
|
|
468
|
+
title={title}
|
|
469
|
+
steps={steps}
|
|
470
|
+
activeStepKey={activeStepKey}
|
|
471
|
+
variant={variant}
|
|
472
|
+
/>
|
|
473
|
+
<GuidedWorkflowBody
|
|
474
|
+
railLabel={railLabel}
|
|
475
|
+
items={items}
|
|
476
|
+
activeItemKey={activeItemKey}
|
|
477
|
+
onItemSelect={onItemSelect}
|
|
478
|
+
contentLabel={activeStep?.label ?? title}
|
|
479
|
+
variant={variant}
|
|
480
|
+
>
|
|
481
|
+
{children}
|
|
482
|
+
</GuidedWorkflowBody>
|
|
483
|
+
<GuidedWorkflowFooter
|
|
484
|
+
backLabel={backLabel}
|
|
485
|
+
continueLabel={continueLabel}
|
|
486
|
+
continueDisabled={continueDisabled}
|
|
487
|
+
footerStatus={footerStatus}
|
|
488
|
+
secondaryActions={secondaryActions}
|
|
489
|
+
onBack={onBack}
|
|
490
|
+
onContinue={onContinue}
|
|
491
|
+
variant={variant}
|
|
492
|
+
/>
|
|
493
|
+
</section>
|
|
494
|
+
)
|
|
495
|
+
}
|
package/recipes/manifest.json
CHANGED
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
"recipes": [
|
|
4
4
|
{
|
|
5
5
|
"name": "workspace-shell",
|
|
6
|
-
"description": "Authenticated Blaze workspace chrome with
|
|
6
|
+
"description": "Authenticated Blaze workspace chrome with shared standard and focused modes, replaceable identity, page chrome, and pinned actions.",
|
|
7
7
|
"dependencies": ["@almanaclabs/blaze-ui", "@tabler/icons-react"],
|
|
8
8
|
"entry": "workspace-shell/workspace-shell.tsx",
|
|
9
9
|
"instructions": "workspace-shell/README.md"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "guided-workflow",
|
|
13
|
+
"description": "Multi-step creation and review flow with ordered progress, contextual work-item navigation, a focused canvas, and persistent actions.",
|
|
14
|
+
"dependencies": ["@almanaclabs/blaze-ui", "@tabler/icons-react"],
|
|
15
|
+
"entry": "guided-workflow/guided-workflow.tsx",
|
|
16
|
+
"instructions": "guided-workflow/README.md"
|
|
10
17
|
}
|
|
11
18
|
]
|
|
12
19
|
}
|
|
@@ -1,41 +1,95 @@
|
|
|
1
1
|
# Workspace shell recipe
|
|
2
2
|
|
|
3
|
-
Use this recipe for authenticated pages
|
|
4
|
-
workspace. It establishes
|
|
5
|
-
|
|
3
|
+
Use this recipe for authenticated Blaze pages inside a selected customer
|
|
4
|
+
workspace. It establishes one persistent application frame; the page supplies its
|
|
5
|
+
own navigation, workflow header, canvas, and actions through slots.
|
|
6
|
+
|
|
7
|
+
## Modes
|
|
8
|
+
|
|
9
|
+
- `standard`: dashboard, settings, reporting, and channel-management pages. The
|
|
10
|
+
content area owns its scrolling.
|
|
11
|
+
- `focused`: creation and review flows. The content slot owns its internal rail and
|
|
12
|
+
canvas scrolling, while the optional shell footer stays pinned.
|
|
13
|
+
|
|
14
|
+
Both modes preserve the same account sidebar, outer workspace gutter, rounded
|
|
15
|
+
workspace frame, and 56px channel header. A focused workflow is content inside the
|
|
16
|
+
workspace shell, not a second application shell.
|
|
6
17
|
|
|
7
18
|
## Preserve
|
|
8
19
|
|
|
9
|
-
- One `SidebarProvider` wrapping the sidebar and inset.
|
|
10
|
-
-
|
|
20
|
+
- One `SidebarProvider` wrapping the 220px sidebar and workspace inset.
|
|
21
|
+
- A 10px desktop gutter around the bordered, rounded workspace frame.
|
|
22
|
+
- A 76px workspace-identity header aligning the identity to the channel title,
|
|
23
|
+
followed by the same 8px section inset in every shell mode.
|
|
24
|
+
- `Manage` and `Channels` navigation groups with the active destination marked by
|
|
11
25
|
`aria-current="page"`.
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
the
|
|
26
|
+
- 12px medium section labels and 14px regular destination text using the 90%
|
|
27
|
+
foreground in default, hover, and selected states.
|
|
28
|
+
- 15px rail icons using the same 90% foreground as their destination labels in
|
|
29
|
+
default, hover, and selected states.
|
|
30
|
+
- The bespoke Home glyph from the approved workspace-shell Figma frame, inlined
|
|
31
|
+
with `currentColor` and the system 7/6px rendered stroke at 15px; do not
|
|
32
|
+
substitute the Tabler Home icon.
|
|
33
|
+
- Shared `SidebarMenuButton` defaults for rail destinations and
|
|
34
|
+
`NavigationAction` for standard shell tabs: transparent default, muted hover,
|
|
35
|
+
and 8% selected surface.
|
|
36
|
+
- Workspace and account menus composed as specialized tertiary `Button` triggers,
|
|
37
|
+
rather than one-off native-button states. Their surfaces share the navigation
|
|
38
|
+
row's 10px inset and pad from the identity icon so hover does not begin at the
|
|
39
|
+
glyph edge; both retain the default 32px control height for balanced vertical
|
|
40
|
+
space around the identity mark.
|
|
41
|
+
- Rail collapse, expansion, and settings actions use tertiary `icon-sm` buttons
|
|
42
|
+
without overriding their system size or hit area.
|
|
43
|
+
- The expanded rail uses `SidebarRail` resize mode with a 220px default, 200–320px
|
|
44
|
+
caps, a transparent edge at rest, a centered 2px divider on hover/focus/drag,
|
|
45
|
+
column-resize cursor, pointer dragging, and Arrow/Home/End keyboard control.
|
|
46
|
+
Width and caps remain configurable.
|
|
47
|
+
- A 55px collapsed rail that preserves row and group geometry, hides visible
|
|
48
|
+
labels without removing their layout slots, stacks Settings above the profile,
|
|
49
|
+
and moves the expand control into the channel header. Collapse and expansion use
|
|
50
|
+
a 150ms Material standard curve with staged label fades for a settled endpoint.
|
|
51
|
+
Those pattern-specific rules live in `workspace-shell.css`, not the global
|
|
52
|
+
Blaze UI stylesheet.
|
|
53
|
+
- An 8px first-section inset that combines with the identity header's bottom
|
|
54
|
+
padding to optically match the 24px separation between navigation groups; shell
|
|
55
|
+
mode must not alter left-rail geometry.
|
|
56
|
+
- A single channel header with replaceable channel identity and action slots.
|
|
57
|
+
- The shell's responsive mobile sheet and desktop icon-collapse behavior.
|
|
58
|
+
- An optional page-header slot above the content and an optional 88px pinned action
|
|
59
|
+
footer below it.
|
|
60
|
+
- Söhne typography, optical icon sizing, semantic colors, and shared Blaze UI
|
|
61
|
+
controls.
|
|
19
62
|
|
|
20
63
|
## Adapt
|
|
21
64
|
|
|
22
|
-
- Replace the
|
|
23
|
-
|
|
24
|
-
|
|
65
|
+
- Replace the reference workspace, channel, and user identity through
|
|
66
|
+
`workspaceMark`, `channelMark`, and `userAvatar`.
|
|
67
|
+
- Keep fixed brand marks resolution-independent where a vector source exists. When
|
|
68
|
+
a workspace avatar is raster-only, retain the original high-density source and
|
|
69
|
+
size it explicitly in the slot rather than generating a CSS-sized derivative.
|
|
70
|
+
- Keep workspace and channel marks in a 24px identity container with 20px centered
|
|
71
|
+
artwork by composing `IdentityMark` and `IdentityMarkImage`. Compose account
|
|
72
|
+
avatars with `Avatar size="xs" shape="rounded"` for the 20px box and 6px corner
|
|
73
|
+
radius. Account labels use the same 14px regular weight as navigation
|
|
74
|
+
destinations.
|
|
25
75
|
- Replace `href` values through the application's routing layer. In Inertia
|
|
26
76
|
applications, render `Link` through the Blaze UI component's `render` prop rather
|
|
27
77
|
than adding a parallel client-side router.
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
78
|
+
- Pass application-specific navigation groups and derive the active key from the
|
|
79
|
+
current route.
|
|
80
|
+
- Put standard tabs and page titles in `pageHeader`.
|
|
81
|
+
- Put guided-workflow progress in `pageHeader`, its rail and canvas in `children`,
|
|
82
|
+
and its actions in `footer` with `mode="focused"`.
|
|
83
|
+
- Keep routing, authorization, persistence, and workflow state in the application.
|
|
31
84
|
|
|
32
85
|
## Avoid
|
|
33
86
|
|
|
34
|
-
- Importing this recipe as a runtime package component.
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
87
|
+
- Importing this recipe as a runtime package component; copy and adapt its source.
|
|
88
|
+
- Creating separate outer shells for dashboard and creation-flow routes.
|
|
89
|
+
- Placing page titles, tabs, or domain state inside the persistent sidebar.
|
|
90
|
+
- Hiding workflow actions in the scrolling canvas when they must remain available.
|
|
91
|
+
- Using production customer data in Storybook.
|
|
38
92
|
|
|
39
|
-
Start from `workspace-shell.tsx
|
|
40
|
-
`Recipes/Workspace shell`
|
|
41
|
-
|
|
93
|
+
Start from `workspace-shell.tsx` and its colocated `workspace-shell.css`. Review
|
|
94
|
+
`Recipes/Workspace shell/Standard` and `Recipes/Workspace shell/Focused Campaign`
|
|
95
|
+
in Storybook before adapting them.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 8.18185C16 7.61458 15.9481 7.06913 15.8516 6.54549H8.16326V9.64003H12.5566C12.3673 10.64 11.7922 11.4873 10.9276 12.0546V14.0618H13.5659C15.1095 12.6691 16 10.6182 16 8.18185Z" fill="#4285F4"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.16327 16C10.3674 16 12.2152 15.2837 13.5659 14.0618L10.9276 12.0546C10.1967 12.5346 9.26161 12.8182 8.16327 12.8182C6.03711 12.8182 4.23748 11.4109 3.59555 9.51998H0.868279V11.5927C2.21151 14.2072 4.97218 16 8.16327 16Z" fill="#34A853"/>
|
|
4
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.59555 9.51998C3.43229 9.03998 3.33952 8.5273 3.33952 8.00003C3.33952 7.47276 3.43228 6.96003 3.59555 6.48003V4.40731H0.868275C0.315399 5.48731 0 6.70912 0 8.00003C0 9.29094 0.315403 10.5127 0.868279 11.5927L3.59555 9.51998Z" fill="#FBBC05"/>
|
|
5
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.16327 3.18181C9.36179 3.18181 10.4379 3.58545 11.2839 4.37818L13.6252 2.08363C12.2115 0.792726 10.3636 0 8.16327 0C4.97218 0 2.2115 1.79277 0.868275 4.40731L3.59555 6.48003C4.23748 4.58913 6.03711 3.18181 8.16327 3.18181Z" fill="#EA4335"/>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g id="icon">
|
|
3
|
+
<path id="Icon" d="M4.6875 10.6641H10.3125M7.06536 2.00698L2.19036 5.30377C1.99251 5.43757 1.875 5.65416 1.875 5.88503V12.0551C1.875 12.646 2.37868 13.125 3 13.125H12C12.6213 13.125 13.125 12.646 13.125 12.0551V5.88503C13.125 5.65416 13.0075 5.43757 12.8096 5.30377L7.93464 2.00698C7.67442 1.83101 7.32558 1.83101 7.06536 2.00698Z" stroke="black" stroke-opacity="0.9" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
</g>
|
|
5
|
+
</svg>
|
|
Binary file
|