@almanaclabs/blaze-ui 0.3.2 → 0.5.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.
Files changed (133) hide show
  1. package/README.md +23 -0
  2. package/dist/components/accordion/accordion.js +53 -0
  3. package/dist/components/alert/alert.d.ts +1 -1
  4. package/dist/components/alert/alert.js +43 -0
  5. package/dist/components/alert-dialog/alert-dialog.d.ts.map +1 -1
  6. package/dist/components/alert-dialog/alert-dialog.js +94 -0
  7. package/dist/components/aspect-ratio/aspect-ratio.js +13 -0
  8. package/dist/components/attachment/attachment.d.ts +2 -2
  9. package/dist/components/attachment/attachment.js +104 -0
  10. package/dist/components/avatar/avatar.d.ts +6 -4
  11. package/dist/components/avatar/avatar.d.ts.map +1 -1
  12. package/dist/components/avatar/avatar.js +51 -0
  13. package/dist/components/badge/badge.d.ts +3 -2
  14. package/dist/components/badge/badge.d.ts.map +1 -1
  15. package/dist/components/badge/badge.js +45 -0
  16. package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -1
  17. package/dist/components/breadcrumb/breadcrumb.js +72 -0
  18. package/dist/components/bubble/bubble.d.ts +1 -1
  19. package/dist/components/bubble/bubble.js +73 -0
  20. package/dist/components/button/button.d.ts +5 -3
  21. package/dist/components/button/button.d.ts.map +1 -1
  22. package/dist/components/button/button.js +65 -0
  23. package/dist/components/button/index.d.ts +1 -1
  24. package/dist/components/button/index.d.ts.map +1 -1
  25. package/dist/components/button-group/button-group.js +41 -0
  26. package/dist/components/calendar/calendar.d.ts.map +1 -1
  27. package/dist/components/calendar/calendar.js +92 -0
  28. package/dist/components/card/card.js +56 -0
  29. package/dist/components/carousel/carousel.d.ts.map +1 -1
  30. package/dist/components/carousel/carousel.js +110 -0
  31. package/dist/components/chart/chart.js +131 -0
  32. package/dist/components/checkbox/checkbox.js +20 -0
  33. package/dist/components/collapsible/collapsible.js +23 -0
  34. package/dist/components/combobox/combobox.d.ts.map +1 -1
  35. package/dist/components/combobox/combobox.js +165 -0
  36. package/dist/components/data-table/data-table.js +30 -0
  37. package/dist/components/date-picker/date-picker.js +39 -0
  38. package/dist/components/dialog/dialog.js +92 -0
  39. package/dist/components/drawer/drawer.js +121 -0
  40. package/dist/components/dropdown-menu/dropdown-menu.js +136 -0
  41. package/dist/components/empty/empty.d.ts.map +1 -1
  42. package/dist/components/empty/empty.js +59 -0
  43. package/dist/components/field/field.d.ts.map +1 -1
  44. package/dist/components/field/field.js +107 -0
  45. package/dist/components/hover-card/hover-card.js +36 -0
  46. package/dist/components/identity-mark/identity-mark.d.ts +5 -0
  47. package/dist/components/identity-mark/identity-mark.d.ts.map +1 -0
  48. package/dist/components/identity-mark/identity-mark.js +20 -0
  49. package/dist/components/identity-mark/index.d.ts +2 -0
  50. package/dist/components/identity-mark/index.d.ts.map +1 -0
  51. package/dist/components/input/input.d.ts +9 -2
  52. package/dist/components/input/input.d.ts.map +1 -1
  53. package/dist/components/input/input.js +28 -0
  54. package/dist/components/input-group/input-group.d.ts +11 -3
  55. package/dist/components/input-group/input-group.d.ts.map +1 -1
  56. package/dist/components/input-group/input-group.js +95 -0
  57. package/dist/components/item/item.d.ts +2 -2
  58. package/dist/components/item/item.js +121 -0
  59. package/dist/components/label/label.js +13 -0
  60. package/dist/components/marker/marker.d.ts +1 -1
  61. package/dist/components/marker/marker.js +43 -0
  62. package/dist/components/message/message.js +49 -0
  63. package/dist/components/metric-overview/index.d.ts +2 -0
  64. package/dist/components/metric-overview/index.d.ts.map +1 -0
  65. package/dist/components/metric-overview/metric-overview.d.ts +12 -0
  66. package/dist/components/metric-overview/metric-overview.d.ts.map +1 -0
  67. package/dist/components/metric-overview/metric-overview.js +42 -0
  68. package/dist/components/native-select/native-select.d.ts +2 -1
  69. package/dist/components/native-select/native-select.d.ts.map +1 -1
  70. package/dist/components/native-select/native-select.js +40 -0
  71. package/dist/components/navigation-action/index.d.ts +2 -0
  72. package/dist/components/navigation-action/index.d.ts.map +1 -0
  73. package/dist/components/navigation-action/navigation-action.d.ts +6 -0
  74. package/dist/components/navigation-action/navigation-action.d.ts.map +1 -0
  75. package/dist/components/navigation-action/navigation-action.js +18 -0
  76. package/dist/components/pagination/pagination.d.ts.map +1 -1
  77. package/dist/components/pagination/pagination.js +80 -0
  78. package/dist/components/popover/popover.js +54 -0
  79. package/dist/components/progress/progress.js +45 -0
  80. package/dist/components/questionnaire/questionnaire-primitive.js +353 -0
  81. package/dist/components/questionnaire/questionnaire.d.ts.map +1 -1
  82. package/dist/components/questionnaire/questionnaire.js +169 -0
  83. package/dist/components/radio-group/radio-group.js +26 -0
  84. package/dist/components/scroll-area/scroll-area.js +37 -0
  85. package/dist/components/select/select.d.ts +2 -1
  86. package/dist/components/select/select.d.ts.map +1 -1
  87. package/dist/components/select/select.js +99 -0
  88. package/dist/components/separator/separator.js +15 -0
  89. package/dist/components/sheet/sheet.js +89 -0
  90. package/dist/components/sidebar/sidebar.d.ts +13 -2
  91. package/dist/components/sidebar/sidebar.d.ts.map +1 -1
  92. package/dist/components/sidebar/sidebar.js +419 -0
  93. package/dist/components/skeleton/skeleton.js +12 -0
  94. package/dist/components/slider/slider.js +37 -0
  95. package/dist/components/spinner/spinner.js +15 -0
  96. package/dist/components/switch/switch.js +19 -0
  97. package/dist/components/table/table.d.ts +4 -1
  98. package/dist/components/table/table.d.ts.map +1 -1
  99. package/dist/components/table/table.js +68 -0
  100. package/dist/components/tabs/tabs.d.ts +1 -1
  101. package/dist/components/tabs/tabs.js +46 -0
  102. package/dist/components/textarea/textarea.js +13 -0
  103. package/dist/components/toast/toast.d.ts.map +1 -1
  104. package/dist/components/toast/toast.js +116 -0
  105. package/dist/components/toggle/toggle.d.ts +1 -1
  106. package/dist/components/toggle/toggle.js +35 -0
  107. package/dist/components/toggle-group/toggle-group.js +52 -0
  108. package/dist/components/tooltip/tooltip.js +41 -0
  109. package/dist/hooks/use-mobile.js +14 -0
  110. package/dist/index.d.ts +4 -1
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +59 -3699
  113. package/dist/lib/control-size.d.ts +9 -0
  114. package/dist/lib/control-size.d.ts.map +1 -0
  115. package/dist/lib/control-size.js +10 -0
  116. package/dist/lib/utils.js +8 -0
  117. package/dist/recipes/recipe-runtime.d.ts +18 -0
  118. package/dist/recipes/recipe-runtime.d.ts.map +1 -0
  119. package/package.json +2 -1
  120. package/recipes/README.md +24 -0
  121. package/recipes/guided-workflow/README.md +51 -0
  122. package/recipes/guided-workflow/guided-workflow.tsx +495 -0
  123. package/recipes/manifest.json +19 -0
  124. package/recipes/workspace-shell/README.md +95 -0
  125. package/recipes/workspace-shell/assets/google-search-ads.svg +6 -0
  126. package/recipes/workspace-shell/assets/home.svg +5 -0
  127. package/recipes/workspace-shell/assets/radiant-health.png +0 -0
  128. package/recipes/workspace-shell/workspace-shell.css +55 -0
  129. package/recipes/workspace-shell/workspace-shell.tsx +636 -0
  130. package/src/styles/fonts/Sohne-Book.woff2 +0 -0
  131. package/src/styles/fonts/Sohne-Medium.woff2 +0 -0
  132. package/src/styles/fonts/Sohne-Semi.woff2 +0 -0
  133. package/src/styles/globals.css +233 -54
@@ -0,0 +1,9 @@
1
+ export declare const controlSizeClassNames: {
2
+ readonly xs: "h-6 rounded-[6px] px-1.5 text-[13px] leading-[14px] tracking-[0.01em] [--control-padding-inline:0.375rem]";
3
+ readonly sm: "h-7 rounded-[6px] px-2 text-sm leading-5 tracking-[0.01em] [--control-padding-inline:0.5rem]";
4
+ readonly md: "h-8 rounded-[8px] px-[11px] text-sm leading-[15px] tracking-[0.01em] [--control-padding-inline:11px]";
5
+ readonly lg: "h-9 rounded-[8px] px-[13px] text-base leading-[15px] [--control-padding-inline:13px]";
6
+ readonly xl: "h-12 rounded-[10px] px-[18px] text-base leading-5 [--control-padding-inline:18px]";
7
+ };
8
+ export type ControlSize = keyof typeof controlSizeClassNames;
9
+ //# sourceMappingURL=control-size.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-size.d.ts","sourceRoot":"","sources":["../../src/lib/control-size.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB;;;;;;CAMxB,CAAA;AAEV,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,qBAAqB,CAAA"}
@@ -0,0 +1,10 @@
1
+ //#region src/lib/control-size.ts
2
+ var e = {
3
+ xs: "h-6 rounded-[6px] px-1.5 text-[13px] leading-[14px] tracking-[0.01em] [--control-padding-inline:0.375rem]",
4
+ sm: "h-7 rounded-[6px] px-2 text-sm leading-5 tracking-[0.01em] [--control-padding-inline:0.5rem]",
5
+ md: "h-8 rounded-[8px] px-[11px] text-sm leading-[15px] tracking-[0.01em] [--control-padding-inline:11px]",
6
+ lg: "h-9 rounded-[8px] px-[13px] text-base leading-[15px] [--control-padding-inline:13px]",
7
+ xl: "h-12 rounded-[10px] px-[18px] text-base leading-5 [--control-padding-inline:18px]"
8
+ };
9
+ //#endregion
10
+ export { e as controlSizeClassNames };
@@ -0,0 +1,8 @@
1
+ import { clsx as e } from "clsx";
2
+ import { twMerge as t } from "tailwind-merge";
3
+ //#region src/lib/utils.ts
4
+ function n(...n) {
5
+ return t(e(n));
6
+ }
7
+ //#endregion
8
+ export { n as cn };
@@ -0,0 +1,18 @@
1
+ export * from '../components/avatar';
2
+ export * from '../components/badge';
3
+ export * from '../components/button';
4
+ export * from '../components/card';
5
+ export * from '../components/dropdown-menu';
6
+ export * from '../components/field';
7
+ export * from '../components/input';
8
+ export * from '../components/input-group';
9
+ export * from '../components/identity-mark';
10
+ export * from '../components/navigation-action';
11
+ export * from '../components/progress';
12
+ export * from '../components/select';
13
+ export * from '../components/sidebar';
14
+ export * from '../components/table';
15
+ export * from '../components/textarea';
16
+ export * from '../components/tooltip';
17
+ export { cn } from '../lib/utils';
18
+ //# sourceMappingURL=recipe-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipe-runtime.d.ts","sourceRoot":"","sources":["../../src/recipes/recipe-runtime.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almanaclabs/blaze-ui",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "Shared React components and design tokens for Blaze",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -8,6 +8,7 @@
8
8
  ],
9
9
  "files": [
10
10
  "dist",
11
+ "recipes",
11
12
  "src/styles",
12
13
  "README.md"
13
14
  ],
@@ -0,0 +1,24 @@
1
+ # Recipes
2
+
3
+ Recipes are executable reference implementations for designers and AI agents. They
4
+ ship with the npm package and show how Blaze UI primitives combine into recognizable
5
+ product surfaces without becoming part of the package's runtime API.
6
+
7
+ Use a recipe when the page shares its anatomy and interaction model. Copy or adapt
8
+ the composition into the application, replace fictional data, and connect links and
9
+ actions through the application's framework conventions.
10
+
11
+ Installed recipes are available under
12
+ `node_modules/@almanaclabs/blaze-ui/recipes`. Start with `manifest.json` when
13
+ discovering recipes programmatically.
14
+
15
+ Preserve the recipe's semantic landmarks, responsive behavior, Blaze UI primitives,
16
+ and semantic tokens. Do not copy fixture data or introduce application routing,
17
+ authorization, serializers, or business rules into this directory.
18
+
19
+ ## Available recipes
20
+
21
+ - [Workspace shell](workspace-shell/README.md): collapsible navigation, workspace
22
+ identity, page header, sub-navigation, account access, and a page-content slot.
23
+ - [Guided workflow](guided-workflow/README.md): ordered step progress, contextual
24
+ work-item navigation, a focused canvas, and persistent workflow actions.
@@ -0,0 +1,51 @@
1
+ # Guided workflow recipe
2
+
3
+ Use this recipe for multi-step creation and review experiences where people move
4
+ between related work items, refine domain-specific content in a focused canvas,
5
+ and advance through a gated sequence.
6
+
7
+ ## Preserve
8
+
9
+ - A compact, ordered progress landmark with the active step marked by
10
+ `aria-current="step"`.
11
+ - One controlled contextual rail for switching among sibling work items.
12
+ - A single scrollable canvas for the selected item's domain content.
13
+ - Back, secondary, status, and primary actions in a persistent workflow footer.
14
+ - Responsive behavior that moves the rail above the canvas on smaller screens.
15
+ - Semantic Blaze UI colors, spacing, focus treatment, and public components.
16
+ - Divider-separated `GuidedWorkflowSection` blocks for task questions that share
17
+ one continuous canvas instead of behaving like standalone cards.
18
+
19
+ ## Adapt
20
+
21
+ - Replace the fictional Storybook examples with serialized application data.
22
+ - Keep step state, selected-item state, validation, and permissions in the app.
23
+ - Compose forms, review tables, previews, and approval documents inside the
24
+ `children` canvas slot with public Blaze UI primitives.
25
+ - Use `variant="compact"` when the workflow sits inside an established product
26
+ shell and needs the Blaze 3 title, compact stepper, canonical 340px rail, and
27
+ full-width white task canvas.
28
+ - In `WorkspaceShellRecipe mode="focused"`, compose
29
+ `GuidedWorkflowPageHeader` into `pageHeader`, `GuidedWorkflowBody` into
30
+ `children`, and `GuidedWorkflowFooter` into `footer`. The workspace shell owns
31
+ the product identity row and its top-right exit action.
32
+ - Pass only semantic or application-owned classes through `markerClassName`;
33
+ the section component owns marker geometry but not product category colors.
34
+ - Connect callbacks to the application's routing and persistence conventions.
35
+ - Use item statuses only for concise orientation; put actionable validation in
36
+ the canvas beside the field or object that needs attention.
37
+
38
+ ## Avoid
39
+
40
+ - Importing this recipe as a runtime package component.
41
+ - Moving forecasting, validation, approval policy, routing, or data fetching into
42
+ the copied recipe.
43
+ - Turning channel-specific objects or third-party previews into recipe props.
44
+ - Adding a second page shell around the recipe. Use the exported workflow regions
45
+ inside the application's existing authenticated shell; reserve the all-in-one
46
+ `GuidedWorkflowRecipe` for standalone examples and shell-less contexts.
47
+ - Hiding required work behind color alone or allowing a disabled primary action
48
+ without nearby explanatory status text.
49
+
50
+ Start from `guided-workflow.tsx`. Review `Recipes/Guided workflow` in Storybook for
51
+ campaign-planning, workspace-onboarding, mobile, and dark-mode examples.
@@ -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
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "recipes": [
4
+ {
5
+ "name": "workspace-shell",
6
+ "description": "Authenticated Blaze workspace chrome with shared standard and focused modes, replaceable identity, page chrome, and pinned actions.",
7
+ "dependencies": ["@almanaclabs/blaze-ui", "@tabler/icons-react"],
8
+ "entry": "workspace-shell/workspace-shell.tsx",
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"
17
+ }
18
+ ]
19
+ }