@almanaclabs/blaze-ui 0.3.2 → 0.4.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 (66) hide show
  1. package/README.md +23 -0
  2. package/dist/components/accordion/accordion.js +53 -0
  3. package/dist/components/alert/alert.js +43 -0
  4. package/dist/components/alert-dialog/alert-dialog.js +94 -0
  5. package/dist/components/aspect-ratio/aspect-ratio.js +13 -0
  6. package/dist/components/attachment/attachment.js +104 -0
  7. package/dist/components/avatar/avatar.js +50 -0
  8. package/dist/components/badge/badge.js +29 -0
  9. package/dist/components/breadcrumb/breadcrumb.js +72 -0
  10. package/dist/components/bubble/bubble.js +73 -0
  11. package/dist/components/button/button.js +46 -0
  12. package/dist/components/button-group/button-group.js +41 -0
  13. package/dist/components/calendar/calendar.js +92 -0
  14. package/dist/components/card/card.js +56 -0
  15. package/dist/components/carousel/carousel.js +110 -0
  16. package/dist/components/chart/chart.js +131 -0
  17. package/dist/components/checkbox/checkbox.js +20 -0
  18. package/dist/components/collapsible/collapsible.js +23 -0
  19. package/dist/components/combobox/combobox.js +165 -0
  20. package/dist/components/data-table/data-table.js +30 -0
  21. package/dist/components/date-picker/date-picker.js +39 -0
  22. package/dist/components/dialog/dialog.js +92 -0
  23. package/dist/components/drawer/drawer.js +121 -0
  24. package/dist/components/dropdown-menu/dropdown-menu.js +136 -0
  25. package/dist/components/empty/empty.js +59 -0
  26. package/dist/components/field/field.js +107 -0
  27. package/dist/components/hover-card/hover-card.js +36 -0
  28. package/dist/components/input/input.js +15 -0
  29. package/dist/components/input-group/input-group.js +77 -0
  30. package/dist/components/item/item.js +121 -0
  31. package/dist/components/label/label.js +13 -0
  32. package/dist/components/marker/marker.js +43 -0
  33. package/dist/components/message/message.js +49 -0
  34. package/dist/components/native-select/native-select.js +39 -0
  35. package/dist/components/pagination/pagination.js +80 -0
  36. package/dist/components/popover/popover.js +54 -0
  37. package/dist/components/progress/progress.js +45 -0
  38. package/dist/components/questionnaire/questionnaire-primitive.js +353 -0
  39. package/dist/components/questionnaire/questionnaire.js +169 -0
  40. package/dist/components/radio-group/radio-group.js +26 -0
  41. package/dist/components/scroll-area/scroll-area.js +37 -0
  42. package/dist/components/select/select.js +98 -0
  43. package/dist/components/separator/separator.js +15 -0
  44. package/dist/components/sheet/sheet.js +89 -0
  45. package/dist/components/sidebar/sidebar.js +358 -0
  46. package/dist/components/skeleton/skeleton.js +12 -0
  47. package/dist/components/slider/slider.js +37 -0
  48. package/dist/components/spinner/spinner.js +15 -0
  49. package/dist/components/switch/switch.js +19 -0
  50. package/dist/components/table/table.js +66 -0
  51. package/dist/components/tabs/tabs.js +46 -0
  52. package/dist/components/textarea/textarea.js +13 -0
  53. package/dist/components/toast/toast.js +116 -0
  54. package/dist/components/toggle/toggle.js +35 -0
  55. package/dist/components/toggle-group/toggle-group.js +52 -0
  56. package/dist/components/tooltip/tooltip.js +41 -0
  57. package/dist/hooks/use-mobile.js +14 -0
  58. package/dist/index.js +56 -3699
  59. package/dist/lib/utils.js +8 -0
  60. package/dist/recipes/recipe-runtime.d.ts +6 -0
  61. package/dist/recipes/recipe-runtime.d.ts.map +1 -0
  62. package/package.json +2 -1
  63. package/recipes/README.md +22 -0
  64. package/recipes/manifest.json +12 -0
  65. package/recipes/workspace-shell/README.md +41 -0
  66. package/recipes/workspace-shell/workspace-shell.tsx +386 -0
@@ -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,6 @@
1
+ export * from '../components/avatar';
2
+ export * from '../components/button';
3
+ export * from '../components/dropdown-menu';
4
+ export * from '../components/sidebar';
5
+ export * from '../components/tooltip';
6
+ //# 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,qBAAqB,CAAA;AACnC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,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.4.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,22 @@
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.
@@ -0,0 +1,12 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "recipes": [
4
+ {
5
+ "name": "workspace-shell",
6
+ "description": "Authenticated Blaze workspace chrome with access-mode theming, navigation, and an empty page-content slot.",
7
+ "dependencies": ["@almanaclabs/blaze-ui", "@tabler/icons-react"],
8
+ "entry": "workspace-shell/workspace-shell.tsx",
9
+ "instructions": "workspace-shell/README.md"
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,41 @@
1
+ # Workspace shell recipe
2
+
3
+ Use this recipe for authenticated pages that sit inside a selected customer
4
+ workspace. It establishes the persistent application chrome; the page owns only the
5
+ content rendered inside the shell.
6
+
7
+ ## Preserve
8
+
9
+ - One `SidebarProvider` wrapping the sidebar and inset.
10
+ - Primary and settings navigation landmarks with an active item marked by
11
+ `aria-current="page"`.
12
+ - A single page heading in the sticky header.
13
+ - Optional page-level navigation centered in the header on desktop and available in
14
+ the normal document order.
15
+ - The sidebar's icon-collapse and mobile-sheet behavior.
16
+ - Semantic Blaze UI colors and spacing.
17
+ - Access-mode theming: AM mode always uses the light sidebar; client mode always uses
18
+ the dark sidebar.
19
+
20
+ ## Adapt
21
+
22
+ - Replace the fictional workspace and user with serialized application data.
23
+ - Declare `@tabler/icons-react` as an application dependency because the copied
24
+ recipe imports its navigation icons directly.
25
+ - Replace `href` values through the application's routing layer. In Inertia
26
+ applications, render `Link` through the Blaze UI component's `render` prop rather
27
+ than adding a parallel client-side router.
28
+ - Derive the active item and access mode from server-authorized page data.
29
+ - Fill the empty content area with the prototype's page-specific design. The shell
30
+ recipe deliberately provides no example page content.
31
+
32
+ ## Avoid
33
+
34
+ - Importing this recipe as a runtime package component.
35
+ - Moving routing, authorization, access switching, or data fetching into the recipe.
36
+ - Rebuilding the sidebar, header, or responsive behavior with arbitrary CSS.
37
+ - Using production customer names, emails, logos, or metrics in Storybook.
38
+
39
+ Start from `workspace-shell.tsx`. Use the hosted Storybook
40
+ `Recipes/Workspace shell` entry to review expanded, collapsed, AM-mode, client-mode,
41
+ and mobile states.
@@ -0,0 +1,386 @@
1
+ import type { ComponentProps, ComponentType, ReactNode } from 'react'
2
+ import {
3
+ IconArrowsUpDown,
4
+ IconBolt,
5
+ IconBrowser,
6
+ IconChartBar,
7
+ IconHome,
8
+ IconLogout,
9
+ IconSearch,
10
+ IconSettings,
11
+ IconStar,
12
+ IconTags,
13
+ IconTarget,
14
+ IconUser,
15
+ IconUsers,
16
+ IconWorld,
17
+ } from '@tabler/icons-react'
18
+
19
+ import {
20
+ Avatar,
21
+ AvatarFallback,
22
+ Button,
23
+ DropdownMenu,
24
+ DropdownMenuContent,
25
+ DropdownMenuGroup,
26
+ DropdownMenuItem,
27
+ DropdownMenuLabel,
28
+ DropdownMenuSeparator,
29
+ DropdownMenuTrigger,
30
+ Sidebar,
31
+ SidebarContent,
32
+ SidebarFooter,
33
+ SidebarGroup,
34
+ SidebarGroupContent,
35
+ SidebarGroupLabel,
36
+ SidebarHeader,
37
+ SidebarInset,
38
+ SidebarMenu,
39
+ SidebarMenuButton,
40
+ SidebarMenuItem,
41
+ SidebarProvider,
42
+ SidebarRail,
43
+ SidebarTrigger,
44
+ TooltipProvider,
45
+ } from '@almanaclabs/blaze-ui'
46
+
47
+ type NavigationIcon = ComponentType<{
48
+ className?: string
49
+ 'aria-hidden'?: boolean | 'true' | 'false'
50
+ }>
51
+
52
+ export type WorkspaceShellNavigationItem = {
53
+ key: string
54
+ label: string
55
+ href: string
56
+ icon: NavigationIcon
57
+ }
58
+
59
+ export type WorkspaceShellSubNavigationItem = {
60
+ key: string
61
+ label: string
62
+ href: string
63
+ }
64
+
65
+ export type WorkspaceShellRecipeProps = {
66
+ title: string
67
+ workspaceName: string
68
+ userEmail: string
69
+ activeNavigationKey: string
70
+ activeSubNavigationKey?: string
71
+ accessMode?: 'am' | 'client'
72
+ children?: ReactNode
73
+ defaultSidebarOpen?: boolean
74
+ subNavigation?: readonly WorkspaceShellSubNavigationItem[]
75
+ }
76
+
77
+ const primaryNavigation: readonly WorkspaceShellNavigationItem[] = [
78
+ { key: 'home', label: 'Home', href: '#home', icon: IconHome },
79
+ {
80
+ key: 'analytics',
81
+ label: 'Analytics',
82
+ href: '#analytics',
83
+ icon: IconChartBar,
84
+ },
85
+ {
86
+ key: 'integrations',
87
+ label: 'Integrations',
88
+ href: '#integrations',
89
+ icon: IconBolt,
90
+ },
91
+ { key: 'brand-kit', label: 'Brand Kit', href: '#brand-kit', icon: IconTags },
92
+ {
93
+ key: 'scorecard',
94
+ label: 'Scorecard',
95
+ href: '#scorecard',
96
+ icon: IconTarget,
97
+ },
98
+ ]
99
+
100
+ const channelNavigation: readonly WorkspaceShellNavigationItem[] = [
101
+ {
102
+ key: 'google-ads',
103
+ label: 'Google Ads',
104
+ href: '#google-ads',
105
+ icon: IconSearch,
106
+ },
107
+ { key: 'seo-aeo', label: 'SEO/AEO', href: '#seo-aeo', icon: IconWorld },
108
+ { key: 'website', label: 'Website', href: '#website', icon: IconBrowser },
109
+ {
110
+ key: 'reputation',
111
+ label: 'Reputation',
112
+ href: '#reputation',
113
+ icon: IconStar,
114
+ },
115
+ {
116
+ key: 'inbound-sdr',
117
+ label: 'Inbound SDR',
118
+ href: '#inbound-sdr',
119
+ icon: IconUsers,
120
+ },
121
+ ]
122
+
123
+ function initials(value: string) {
124
+ return value
125
+ .split(/\s+|@/)
126
+ .filter(Boolean)
127
+ .slice(0, 2)
128
+ .map((part) => part[0]?.toUpperCase())
129
+ .join('')
130
+ }
131
+
132
+ function RecipeLink({
133
+ href,
134
+ children,
135
+ ...props
136
+ }: ComponentProps<'a'> & { href: string }) {
137
+ return (
138
+ <a href={href} onClick={(event) => event.preventDefault()} {...props}>
139
+ {children}
140
+ </a>
141
+ )
142
+ }
143
+
144
+ function WorkspaceSelector({ workspaceName }: { workspaceName: string }) {
145
+ return (
146
+ <SidebarMenu>
147
+ <SidebarMenuItem>
148
+ <DropdownMenu>
149
+ <DropdownMenuTrigger
150
+ render={
151
+ <SidebarMenuButton
152
+ size="lg"
153
+ aria-label={`Open workspace menu for ${workspaceName}`}
154
+ className="data-[popup-open]:bg-sidebar-accent data-[popup-open]:text-sidebar-accent-foreground"
155
+ />
156
+ }
157
+ >
158
+ <span className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary font-semibold text-sidebar-primary-foreground">
159
+ {initials(workspaceName).slice(0, 1)}
160
+ </span>
161
+ <span className="grid flex-1 text-left text-sm leading-tight">
162
+ <span className="truncate font-medium">{workspaceName}</span>
163
+ <span className="truncate text-xs text-sidebar-foreground/65">
164
+ Client workspace
165
+ </span>
166
+ </span>
167
+ <IconArrowsUpDown className="ml-auto" aria-hidden="true" />
168
+ </DropdownMenuTrigger>
169
+ <DropdownMenuContent align="start" side="right" className="min-w-56">
170
+ <DropdownMenuGroup>
171
+ <DropdownMenuLabel>Workspace</DropdownMenuLabel>
172
+ <DropdownMenuItem>
173
+ <IconUsers aria-hidden="true" />
174
+ All workspaces
175
+ </DropdownMenuItem>
176
+ </DropdownMenuGroup>
177
+ <DropdownMenuSeparator />
178
+ <DropdownMenuItem>
179
+ <IconLogout aria-hidden="true" />
180
+ Sign out
181
+ </DropdownMenuItem>
182
+ </DropdownMenuContent>
183
+ </DropdownMenu>
184
+ </SidebarMenuItem>
185
+ </SidebarMenu>
186
+ )
187
+ }
188
+
189
+ function NavigationGroup({
190
+ items,
191
+ activeKey,
192
+ label,
193
+ }: {
194
+ items: readonly WorkspaceShellNavigationItem[]
195
+ activeKey: string
196
+ label?: string
197
+ }) {
198
+ return (
199
+ <SidebarGroup>
200
+ {label && <SidebarGroupLabel>{label}</SidebarGroupLabel>}
201
+ <SidebarGroupContent>
202
+ <SidebarMenu>
203
+ {items.map((item) => (
204
+ <SidebarMenuItem key={item.key}>
205
+ <SidebarMenuButton
206
+ tooltip={item.label}
207
+ isActive={item.key === activeKey}
208
+ render={
209
+ <RecipeLink
210
+ href={item.href}
211
+ aria-current={item.key === activeKey ? 'page' : undefined}
212
+ />
213
+ }
214
+ >
215
+ <item.icon aria-hidden="true" />
216
+ <span>{item.label}</span>
217
+ </SidebarMenuButton>
218
+ </SidebarMenuItem>
219
+ ))}
220
+ </SidebarMenu>
221
+ </SidebarGroupContent>
222
+ </SidebarGroup>
223
+ )
224
+ }
225
+
226
+ function AccountMenu({ userEmail }: { userEmail: string }) {
227
+ return (
228
+ <DropdownMenu>
229
+ <DropdownMenuTrigger
230
+ render={
231
+ <Button
232
+ variant="ghost"
233
+ size="icon"
234
+ aria-label={`Open account menu. Signed in as ${userEmail}`}
235
+ className="rounded-full"
236
+ />
237
+ }
238
+ >
239
+ <Avatar size="sm">
240
+ <AvatarFallback>{initials(userEmail)}</AvatarFallback>
241
+ </Avatar>
242
+ </DropdownMenuTrigger>
243
+ <DropdownMenuContent align="end" className="min-w-56">
244
+ <DropdownMenuGroup>
245
+ <DropdownMenuLabel>{userEmail}</DropdownMenuLabel>
246
+ </DropdownMenuGroup>
247
+ <DropdownMenuSeparator />
248
+ <DropdownMenuGroup>
249
+ <DropdownMenuItem>
250
+ <IconUser aria-hidden="true" />
251
+ Account settings
252
+ </DropdownMenuItem>
253
+ </DropdownMenuGroup>
254
+ <DropdownMenuSeparator />
255
+ <DropdownMenuItem>
256
+ <IconLogout aria-hidden="true" />
257
+ Sign out
258
+ </DropdownMenuItem>
259
+ </DropdownMenuContent>
260
+ </DropdownMenu>
261
+ )
262
+ }
263
+
264
+ export function WorkspaceShellRecipe({
265
+ title,
266
+ workspaceName,
267
+ userEmail,
268
+ activeNavigationKey,
269
+ activeSubNavigationKey,
270
+ children,
271
+ accessMode = 'client',
272
+ defaultSidebarOpen = true,
273
+ subNavigation = [],
274
+ }: WorkspaceShellRecipeProps) {
275
+ const darkSidebar = accessMode === 'client'
276
+ const sidebarTheme = darkSidebar ? 'dark' : 'light'
277
+
278
+ return (
279
+ <TooltipProvider>
280
+ <SidebarProvider
281
+ defaultOpen={defaultSidebarOpen}
282
+ className="min-h-[720px]"
283
+ >
284
+ <Sidebar collapsible="icon">
285
+ <div
286
+ className={`${darkSidebar ? 'dark' : ''} flex min-h-0 flex-1 flex-col bg-sidebar text-sidebar-foreground`}
287
+ data-sidebar-theme={sidebarTheme}
288
+ >
289
+ <SidebarHeader className="border-b border-sidebar-border p-2">
290
+ <WorkspaceSelector workspaceName={workspaceName} />
291
+ </SidebarHeader>
292
+ <SidebarContent>
293
+ <nav aria-label="Primary">
294
+ <NavigationGroup
295
+ items={primaryNavigation}
296
+ activeKey={activeNavigationKey}
297
+ />
298
+ <NavigationGroup
299
+ items={channelNavigation}
300
+ activeKey={activeNavigationKey}
301
+ label="Channels"
302
+ />
303
+ </nav>
304
+ </SidebarContent>
305
+ <SidebarFooter className="p-2">
306
+ <nav aria-label="Settings">
307
+ <SidebarMenu>
308
+ <SidebarMenuItem>
309
+ <SidebarMenuButton
310
+ tooltip="Settings"
311
+ isActive={activeNavigationKey === 'settings'}
312
+ render={
313
+ <RecipeLink
314
+ href="#settings"
315
+ aria-current={
316
+ activeNavigationKey === 'settings'
317
+ ? 'page'
318
+ : undefined
319
+ }
320
+ />
321
+ }
322
+ >
323
+ <IconSettings aria-hidden="true" />
324
+ <span>Settings</span>
325
+ </SidebarMenuButton>
326
+ </SidebarMenuItem>
327
+ </SidebarMenu>
328
+ </nav>
329
+ </SidebarFooter>
330
+ </div>
331
+ <SidebarRail />
332
+ </Sidebar>
333
+
334
+ <SidebarInset className="min-w-0">
335
+ <header className="sticky top-0 z-20 shrink-0 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80">
336
+ <div className="grid min-h-16 grid-cols-[minmax(0,1fr)_auto] items-center gap-3 px-4 transition-[min-height] group-has-data-[collapsible=icon]/sidebar-wrapper:min-h-12 md:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] md:px-6">
337
+ <div className="col-start-1 row-start-1 flex min-w-0 items-center gap-2">
338
+ <SidebarTrigger className="-ml-2 shrink-0" />
339
+ <h1 className="truncate text-lg font-semibold tracking-tight">
340
+ {title}
341
+ </h1>
342
+ </div>
343
+
344
+ {subNavigation.length > 0 && (
345
+ <nav
346
+ aria-label="Page"
347
+ className="col-span-2 col-start-1 row-start-2 -mx-4 overflow-x-auto border-t px-4 md:col-span-1 md:col-start-2 md:row-start-1 md:mx-0 md:overflow-visible md:border-0 md:px-0"
348
+ >
349
+ <ul className="flex min-w-max items-center justify-center gap-1">
350
+ {subNavigation.map((item) => {
351
+ const active = item.key === activeSubNavigationKey
352
+
353
+ return (
354
+ <li key={item.key}>
355
+ <Button
356
+ aria-current={active ? 'page' : undefined}
357
+ nativeButton={false}
358
+ render={<RecipeLink href={item.href} />}
359
+ role="link"
360
+ size="sm"
361
+ variant={active ? 'secondary' : 'ghost'}
362
+ >
363
+ {item.label}
364
+ </Button>
365
+ </li>
366
+ )
367
+ })}
368
+ </ul>
369
+ </nav>
370
+ )}
371
+
372
+ <div className="col-start-2 row-start-1 flex items-center gap-2 justify-self-end md:col-start-3">
373
+ <Button variant="outline" size="sm">
374
+ {accessMode === 'client' ? 'Client' : 'AM'}
375
+ </Button>
376
+ <AccountMenu userEmail={userEmail} />
377
+ </div>
378
+ </div>
379
+ </header>
380
+
381
+ <div className="flex-1 p-4 md:p-6">{children}</div>
382
+ </SidebarInset>
383
+ </SidebarProvider>
384
+ </TooltipProvider>
385
+ )
386
+ }