tembro 3.1.5 → 3.1.7

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 (120) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +241 -91
  3. package/dist/components/ui/dialog/index.d.ts +3 -1
  4. package/dist/components/ui/select/index.d.ts +6 -3
  5. package/dist/showcase/package-meta.d.ts +3 -3
  6. package/dist/showcase/premium/wizard/index.cjs +1 -0
  7. package/dist/showcase/premium/wizard/index.d.ts +2 -0
  8. package/dist/showcase/premium/wizard/index.js +1 -0
  9. package/dist/showcase/premium/wizard/mock.cjs +1 -0
  10. package/dist/showcase/premium/wizard/mock.d.ts +2 -0
  11. package/dist/showcase/premium/wizard/mock.js +1 -0
  12. package/dist/showcase/premium/wizard/showcase.cjs +1 -0
  13. package/dist/showcase/premium/wizard/showcase.d.ts +2 -0
  14. package/dist/showcase/premium/wizard/showcase.js +1 -0
  15. package/dist/showcase/search-utils.d.ts +1 -1
  16. package/dist/showcase/site-data.d.ts +6 -6
  17. package/dist/src/components/layout/app-sidebar.cjs +1 -1
  18. package/dist/src/components/layout/app-sidebar.js +49 -36
  19. package/dist/src/components/theme-provider.cjs +1 -1
  20. package/dist/src/components/theme-provider.js +1 -1
  21. package/dist/src/components/ui/dialog/index.cjs +1 -1
  22. package/dist/src/components/ui/dialog/index.js +14 -7
  23. package/dist/src/components/ui/select/index.cjs +1 -1
  24. package/dist/src/components/ui/select/index.js +44 -43
  25. package/dist/src/index.cjs +1 -1
  26. package/dist/src/index.js +101 -101
  27. package/dist/src/showcase/component-api-schema.cjs +1 -1
  28. package/dist/src/showcase/component-api-schema.js +868 -9
  29. package/dist/src/showcase/demo-snippets/forms/index.cjs +1 -1
  30. package/dist/src/showcase/demo-snippets/forms/index.js +1 -1
  31. package/dist/src/showcase/fallback.cjs +4 -4
  32. package/dist/src/showcase/fallback.js +354 -181
  33. package/dist/src/showcase/index.cjs +1 -1
  34. package/dist/src/showcase/index.js +2 -2
  35. package/dist/src/showcase/package-meta.cjs +1 -1
  36. package/dist/src/showcase/package-meta.js +1 -1
  37. package/dist/src/showcase/premium/app-sidebar/mock.cjs +3 -2
  38. package/dist/src/showcase/premium/app-sidebar/mock.js +1 -1
  39. package/dist/src/showcase/premium/app-sidebar/showcase.cjs +1 -1
  40. package/dist/src/showcase/premium/app-sidebar/showcase.js +1 -1
  41. package/dist/src/showcase/premium/async-select/showcase.cjs +1 -1
  42. package/dist/src/showcase/premium/async-select/showcase.js +118 -74
  43. package/dist/src/showcase/premium/avatar/showcase.cjs +1 -1
  44. package/dist/src/showcase/premium/avatar/showcase.js +5 -5
  45. package/dist/src/showcase/premium/dialog/mock.cjs +6 -1
  46. package/dist/src/showcase/premium/dialog/mock.js +1 -1
  47. package/dist/src/showcase/premium/dialog/showcase.cjs +1 -1
  48. package/dist/src/showcase/premium/dialog/showcase.js +101 -39
  49. package/dist/src/showcase/premium/form-wrapper/mock.cjs +2 -2
  50. package/dist/src/showcase/premium/form-wrapper/mock.js +2 -2
  51. package/dist/src/showcase/premium/form-wrapper/showcase.cjs +1 -1
  52. package/dist/src/showcase/premium/form-wrapper/showcase.js +1 -1
  53. package/dist/src/showcase/premium/index.cjs +1 -1
  54. package/dist/src/showcase/premium/index.js +9 -3
  55. package/dist/src/showcase/premium/input/showcase.cjs +1 -1
  56. package/dist/src/showcase/premium/input/showcase.js +1 -1
  57. package/dist/src/showcase/premium/json-input/showcase.cjs +1 -1
  58. package/dist/src/showcase/premium/json-input/showcase.js +1 -1
  59. package/dist/src/showcase/premium/select/showcase.cjs +1 -1
  60. package/dist/src/showcase/premium/select/showcase.js +53 -0
  61. package/dist/src/showcase/premium/table/mock.cjs +1 -1
  62. package/dist/src/showcase/premium/table/mock.js +1 -1
  63. package/dist/src/showcase/premium/types.cjs +1 -1
  64. package/dist/src/showcase/premium/types.js +1 -1
  65. package/dist/src/showcase/premium/wizard/index.cjs +1 -0
  66. package/dist/src/showcase/premium/wizard/index.js +3 -0
  67. package/dist/src/showcase/premium/wizard/mock.cjs +25 -0
  68. package/dist/src/showcase/premium/wizard/mock.js +32 -0
  69. package/dist/src/showcase/premium/wizard/showcase.cjs +1 -0
  70. package/dist/src/showcase/premium/wizard/showcase.js +130 -0
  71. package/dist/src/showcase/preview-registry.cjs +1 -1
  72. package/dist/src/showcase/preview-registry.js +6 -6
  73. package/dist/src/showcase/registry-quality.cjs +1 -1
  74. package/dist/src/showcase/registry-quality.js +5 -0
  75. package/dist/src/showcase/registry-specific.cjs +1 -1
  76. package/dist/src/showcase/registry-specific.js +2 -2
  77. package/dist/src/showcase/render-registry-preview.cjs +1 -1
  78. package/dist/src/showcase/render-registry-preview.js +2 -2
  79. package/dist/src/showcase/site-data.cjs +1 -1
  80. package/dist/src/showcase/site-data.js +198 -44
  81. package/dist/src/showcase/tembro-registry.json.cjs +1 -1
  82. package/dist/src/showcase/tembro-registry.json.js +1 -1
  83. package/dist/src/showcase/types.cjs +1 -1
  84. package/dist/src/showcase/types.js +1 -1
  85. package/package.json +13 -9
  86. package/packages/cli/dist/index.cjs +127 -127
  87. package/packages/cli/vendor/src/components/layout/app-sidebar.tsx +31 -6
  88. package/packages/cli/vendor/src/components/theme-provider.tsx +1 -1
  89. package/packages/cli/vendor/src/components/ui/dialog/index.tsx +15 -3
  90. package/packages/cli/vendor/src/components/ui/select/index.tsx +17 -10
  91. package/packages/cli/vendor/src/showcase/component-api-schema.ts +269 -39
  92. package/packages/cli/vendor/src/showcase/demo-snippets/forms/index.ts +1 -1
  93. package/packages/cli/vendor/src/showcase/fallback.tsx +202 -39
  94. package/packages/cli/vendor/src/showcase/index.ts +6 -6
  95. package/packages/cli/vendor/src/showcase/package-meta.ts +3 -3
  96. package/packages/cli/vendor/src/showcase/premium/app-sidebar/mock.ts +3 -2
  97. package/packages/cli/vendor/src/showcase/premium/app-sidebar/showcase.tsx +1 -1
  98. package/packages/cli/vendor/src/showcase/premium/async-select/showcase.tsx +50 -4
  99. package/packages/cli/vendor/src/showcase/premium/avatar/showcase.tsx +5 -5
  100. package/packages/cli/vendor/src/showcase/premium/dialog/mock.ts +6 -1
  101. package/packages/cli/vendor/src/showcase/premium/dialog/showcase.tsx +53 -1
  102. package/packages/cli/vendor/src/showcase/premium/form-wrapper/mock.ts +2 -2
  103. package/packages/cli/vendor/src/showcase/premium/form-wrapper/showcase.tsx +1 -1
  104. package/packages/cli/vendor/src/showcase/premium/index.ts +5 -3
  105. package/packages/cli/vendor/src/showcase/premium/input/showcase.tsx +1 -1
  106. package/packages/cli/vendor/src/showcase/premium/json-input/showcase.tsx +1 -1
  107. package/packages/cli/vendor/src/showcase/premium/select/showcase.tsx +38 -0
  108. package/packages/cli/vendor/src/showcase/premium/table/mock.ts +1 -1
  109. package/packages/cli/vendor/src/showcase/premium/types.ts +1 -1
  110. package/packages/cli/vendor/src/showcase/premium/wizard/index.ts +2 -0
  111. package/packages/cli/vendor/src/showcase/premium/wizard/mock.ts +41 -0
  112. package/packages/cli/vendor/src/showcase/premium/wizard/showcase.tsx +88 -0
  113. package/packages/cli/vendor/src/showcase/preview-registry.tsx +6 -6
  114. package/packages/cli/vendor/src/showcase/registry-quality.ts +5 -0
  115. package/packages/cli/vendor/src/showcase/registry-specific.tsx +2 -2
  116. package/packages/cli/vendor/src/showcase/render-registry-preview.tsx +2 -2
  117. package/packages/cli/vendor/src/showcase/site-data.tsx +89 -32
  118. package/packages/cli/vendor/src/showcase/tembro-registry.json +1 -1
  119. package/packages/cli/vendor/src/showcase/types.ts +1 -1
  120. package/registry.json +1 -1
@@ -1,47 +1,56 @@
1
1
  import {
2
- Alert,
3
- Avatar,
4
- AvatarGroup,
5
- Badge,
6
- Button,
7
- ButtonGroup,
8
- CalendarScheduler,
9
- Card,
10
- CardContent,
2
+ Alert,
3
+ Accordion,
4
+ Avatar,
5
+ AvatarGroup,
6
+ Badge,
7
+ Button,
8
+ ButtonGroup,
9
+ CalendarScheduler,
10
+ Card,
11
+ CardContent,
11
12
  CodeBlock,
13
+ ColorPicker,
14
+ CommandPalette,
12
15
  CopyButton,
13
16
  DataState,
14
- Divider,
15
- DualListPicker,
17
+ Divider,
18
+ DualListPicker,
16
19
  FilterBar,
17
- Input,
18
- JsonInput,
19
- KanbanBoard,
20
+ Input,
21
+ ImageCropper,
22
+ JsonInput,
23
+ KanbanBoard,
20
24
  List,
21
25
  OtpInput,
22
26
  PageState,
27
+ QRCode,
23
28
  QuickActionGrid,
24
29
  RangeSlider,
25
- Rating,
26
- ResizableHandle,
27
- ResizablePanel,
28
- ResizablePanelGroup,
29
- SavedFilterSelect,
30
- Section,
31
- SegmentedControl,
32
- Skeleton,
33
- SkeletonCard,
34
- SkeletonText,
35
- Slider,
30
+ Rating,
31
+ ResizableHandle,
32
+ ResizablePanel,
33
+ ResizablePanelGroup,
34
+ RichTextEditor,
35
+ SavedFilterSelect,
36
+ Section,
37
+ SegmentedControl,
38
+ SignaturePad,
39
+ Skeleton,
40
+ SkeletonCard,
41
+ SkeletonText,
42
+ Slider,
36
43
  Spinner,
37
44
  Statistic,
38
45
  StatisticCard,
39
46
  StatisticGrid,
40
47
  StatusLegend,
41
- TagInput,
42
- TimePicker,
43
- TimeRangePicker,
44
- TreeView,
48
+ SortableList,
49
+ TagInput,
50
+ TimePicker,
51
+ TimeRangePicker,
52
+ TreeView,
53
+ VirtualList,
45
54
  } from "@/index"
46
55
  import { Carousel, CarouselItem } from "@/components/display/carousel"
47
56
  import { Tag, TagGroup } from "@/components/display/tag"
@@ -143,10 +152,10 @@ function renderGenericPreviewSurface(
143
152
  setState: ShowcaseDemoProps["setState"],
144
153
  mode: ShowcaseDemoProps["mode"]
145
154
  ) {
146
- if (item.slug === "avatar") {
155
+ if (item.slug === "avatar") {
147
156
  return (
148
157
  <div className="flex flex-wrap items-center gap-4">
149
- <Avatar name="Azamat UI" />
158
+ <Avatar name="Tembro" />
150
159
  <Avatar fallback="AJ" shape="rounded" />
151
160
  <AvatarGroup
152
161
  items={[
@@ -158,7 +167,30 @@ function renderGenericPreviewSurface(
158
167
  />
159
168
  </div>
160
169
  )
161
- }
170
+ }
171
+
172
+ if (item.slug === "accordion") {
173
+ return (
174
+ <Accordion
175
+ type="single"
176
+ defaultValue="install"
177
+ items={[
178
+ {
179
+ key: "install",
180
+ title: "Install with CLI",
181
+ description: "Copy source into your app.",
182
+ content: "Run `npx tembro add accordion`, then edit the local component source.",
183
+ },
184
+ {
185
+ key: "compose",
186
+ title: "Compose route content",
187
+ description: "Use it for settings, FAQ and dense docs groups.",
188
+ content: "Accordion items keep title, description, meta, badges and disabled states together.",
189
+ },
190
+ ]}
191
+ />
192
+ )
193
+ }
162
194
 
163
195
  if (item.slug === "calendar-scheduler") {
164
196
  return (
@@ -195,7 +227,7 @@ function renderGenericPreviewSurface(
195
227
  )
196
228
  }
197
229
 
198
- if (item.slug === "code-block") {
230
+ if (item.slug === "code-block") {
199
231
  return (
200
232
  <CodeBlock
201
233
  title="Install"
@@ -203,13 +235,37 @@ function renderGenericPreviewSurface(
203
235
  code={`npx tembro init --template next\nnpx tembro add button`}
204
236
  />
205
237
  )
206
- }
238
+ }
239
+
240
+ if (item.slug === "command-palette") {
241
+ return (
242
+ <CommandPalette
243
+ open
244
+ onOpenChange={() => undefined}
245
+ title="Preview command palette"
246
+ description="Command palette preview with grouped actions."
247
+ placeholder="Search commands..."
248
+ groups={[
249
+ {
250
+ id: "components",
251
+ label: "Components",
252
+ items: [
253
+ { id: "button", label: "Button", description: "Open button docs", shortcut: "B" },
254
+ { id: "input", label: "Input", description: "Open input docs", shortcut: "I" },
255
+ { id: "data-table", label: "DataTable", description: "Open table docs", shortcut: "T" },
256
+ ],
257
+ },
258
+ ]}
259
+ contentClassName="relative inset-auto translate-x-0 translate-y-0 shadow-none"
260
+ />
261
+ )
262
+ }
207
263
 
208
264
  if (item.slug === "copy-button") {
209
265
  return <CopyButton value="npx tembro add button">Copy command</CopyButton>
210
266
  }
211
267
 
212
- if (item.slug === "data-state") {
268
+ if (item.slug === "data-state") {
213
269
  return (
214
270
  <DataState
215
271
  status="empty"
@@ -218,7 +274,18 @@ function renderGenericPreviewSurface(
218
274
  actions={<Button size="sm">Import CSV</Button>}
219
275
  />
220
276
  )
221
- }
277
+ }
278
+
279
+ if (item.slug === "color-picker") {
280
+ return (
281
+ <ColorPicker
282
+ label="Brand accent"
283
+ defaultValue="#2563ebcc"
284
+ showAlpha
285
+ swatches={["#0f172a", "#2563eb", "#059669", "#d97706", "#dc2626", "#7c3aed"]}
286
+ />
287
+ )
288
+ }
222
289
 
223
290
  if (item.slug === "divider") {
224
291
  return (
@@ -478,15 +545,111 @@ function renderGenericPreviewSurface(
478
545
  )
479
546
  }
480
547
 
481
- if (item.slug === "json-input") {
548
+ if (item.slug === "json-input") {
482
549
  return (
483
550
  <JsonInput
484
- value={`{\n "workspace": "azamat-ui",\n "theme": "dashboard"\n}`}
551
+ value={`{\n "workspace": "tembro",\n "theme": "dashboard"\n}`}
485
552
  onValueChange={() => undefined}
486
553
  rows={7}
487
554
  />
488
555
  )
489
- }
556
+ }
557
+
558
+ if (item.slug === "sortable-list") {
559
+ const tasks = [
560
+ { id: "docs", title: "Docs polish", meta: "Ready" },
561
+ { id: "api", title: "API table", meta: "Review" },
562
+ { id: "preview", title: "Live preview", meta: "Live" },
563
+ ]
564
+
565
+ return (
566
+ <SortableList
567
+ defaultItems={tasks}
568
+ getItemId={(task) => task.id}
569
+ getItemLabel={(task) => task.title}
570
+ renderItem={(task) => (
571
+ <div className="flex min-w-0 items-center justify-between gap-3">
572
+ <span className="truncate text-sm font-medium">{task.title}</span>
573
+ <Badge variant="outline">{task.meta}</Badge>
574
+ </div>
575
+ )}
576
+ />
577
+ )
578
+ }
579
+
580
+ if (item.slug === "virtual-list") {
581
+ const rows = Array.from({ length: 80 }, (_, index) => ({
582
+ id: `row-${index + 1}`,
583
+ name: `Virtual record ${index + 1}`,
584
+ status: index % 3 === 0 ? "Review" : "Live",
585
+ }))
586
+
587
+ return (
588
+ <VirtualList
589
+ items={rows}
590
+ height={280}
591
+ estimateSize={56}
592
+ getItemKey={(row) => row.id}
593
+ renderItem={(row) => (
594
+ <div className="mx-1 flex items-center justify-between rounded-xl border border-[color:var(--aui-divider)] bg-[color:var(--aui-page-bg)] px-4 py-3">
595
+ <span className="text-sm font-medium">{row.name}</span>
596
+ <Badge variant={row.status === "Live" ? "secondary" : "outline"}>{row.status}</Badge>
597
+ </div>
598
+ )}
599
+ />
600
+ )
601
+ }
602
+
603
+ if (item.slug === "signature-pad") {
604
+ return (
605
+ <SignaturePad
606
+ defaultValue={[
607
+ [
608
+ { x: 0.16, y: 0.58, pressure: 0.5 },
609
+ { x: 0.26, y: 0.38, pressure: 0.5 },
610
+ { x: 0.38, y: 0.62, pressure: 0.5 },
611
+ { x: 0.54, y: 0.42, pressure: 0.5 },
612
+ { x: 0.72, y: 0.56, pressure: 0.5 },
613
+ ],
614
+ ]}
615
+ labels={{ canvas: "Signature preview", undo: "Undo", clear: "Clear" }}
616
+ />
617
+ )
618
+ }
619
+
620
+ if (item.slug === "qr-code") {
621
+ return (
622
+ <div className="flex flex-col items-start gap-4 sm:flex-row sm:items-center">
623
+ <div className="rounded-2xl border border-[color:var(--aui-divider)] bg-white p-4">
624
+ <QRCode value="https://tembro.dev/components" size={148} alt="Tembro components QR" />
625
+ </div>
626
+ <div>
627
+ <p className="font-semibold aui-text-strong">QR code</p>
628
+ <p className="mt-2 text-sm leading-6 aui-text-muted">Generates SVG output, supports custom colors, error fallback and async loading.</p>
629
+ </div>
630
+ </div>
631
+ )
632
+ }
633
+
634
+ if (item.slug === "rich-text-editor") {
635
+ return (
636
+ <RichTextEditor
637
+ defaultValue="<h2>Release notes</h2><p>Write rich product copy with toolbar actions, links and placeholder support.</p>"
638
+ minHeight={180}
639
+ onLinkRequest={() => "https://tembro.dev"}
640
+ />
641
+ )
642
+ }
643
+
644
+ if (item.slug === "image-cropper") {
645
+ const cropperImage =
646
+ "data:image/svg+xml;utf8," +
647
+ encodeURIComponent(
648
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 420"><defs><linearGradient id="g" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#0f172a"/><stop offset="1" stop-color="#22c55e"/></linearGradient></defs><rect width="640" height="420" fill="url(#g)"/><circle cx="460" cy="150" r="88" fill="#ffffff" opacity=".22"/><rect x="70" y="245" width="310" height="64" rx="20" fill="#fff" opacity=".88"/><text x="95" y="287" font-family="Arial" font-size="28" font-weight="700" fill="#0f172a">Tembro crop</text></svg>`
649
+ )
650
+
651
+ return <ImageCropper src={cropperImage} aspect={16 / 9} defaultZoom={1.15} defaultRotation={-2} />
652
+ }
490
653
 
491
654
  if (item.slug === "kanban") {
492
655
  return (
@@ -44,12 +44,12 @@ export const showcaseDemoRegistry = {
44
44
  ...overlayShowcaseDemoRegistry,
45
45
  ...patternsShowcaseDemoRegistry,
46
46
  ...uploadShowcaseDemoRegistry,
47
- ...wizardShowcaseDemoRegistry,
48
- ...registrySpecificDemoRegistry,
49
- ...supplementalShowcaseDemoRegistry,
50
- ...premiumShowcaseDemoRegistry,
51
- ...registryQualityDemoRegistry,
52
- }
47
+ ...wizardShowcaseDemoRegistry,
48
+ ...registrySpecificDemoRegistry,
49
+ ...supplementalShowcaseDemoRegistry,
50
+ ...registryQualityDemoRegistry,
51
+ ...premiumShowcaseDemoRegistry,
52
+ }
53
53
 
54
54
  export { defaultComponentDemoState }
55
55
  export const componentDemoRegistry = showcaseDemoRegistry
@@ -1,4 +1,4 @@
1
- export const PACKAGE_LATEST_VERSION = "3.1.5"
2
- export const PACKAGE_LATEST_RELEASE_DATE = "July 13, 2026"
3
- export const PACKAGE_DEPENDENCY_RANGE = `^${PACKAGE_LATEST_VERSION}`
1
+ export const PACKAGE_LATEST_VERSION = "3.1.7"
2
+ export const PACKAGE_LATEST_RELEASE_DATE = "July 15, 2026"
3
+ export const PACKAGE_DEPENDENCY_RANGE = PACKAGE_LATEST_VERSION
4
4
 
@@ -1,7 +1,8 @@
1
1
  import type { ComponentDemoMock } from "../types"
2
2
 
3
3
  export const appSidebarMock: ComponentDemoMock = {
4
- code: `import { Sidebar } from "tembro"
4
+ code: `import { Link } from "react-router-dom"
5
+ import { Sidebar } from "tembro"
5
6
 
6
7
  const items = [
7
8
  { key: "overview", label: "Overview", href: "/overview", active: true, sectionLabel: "Workspace" },
@@ -29,7 +30,7 @@ export function Example() {
29
30
  showSectionLabels
30
31
  responsive
31
32
  renderLink={({ item: _item, href = "/", ...props }) => (
32
- <a {...props} href={href} />
33
+ <Link {...props} to={href} />
33
34
  )}
34
35
  header={<div className="px-3 py-2 text-sm font-semibold">Azamat Workspace</div>}
35
36
  footerAccount={{ label: "Azamat Workspace", description: "Starter plan", avatar: "AW" }}
@@ -56,7 +56,7 @@ export function AppSidebarShowcase({ mode }: ComponentDemoProps) {
56
56
  <p className="text-xs font-semibold uppercase tracking-[0.26em] aui-text-muted">Navigation shell</p>
57
57
  <h3 className="mt-3 text-2xl font-semibold tracking-tight aui-text-strong">Sidebar should feel install-ready on its own</h3>
58
58
  <p className="mt-3 max-w-lg text-sm leading-6 aui-text-muted">
59
- This preview keeps the focus on one reusable sidebar surface: identity, route items, badges, disabled states and collapsed behavior.
59
+ This preview keeps the focus on one reusable sidebar surface: identity, route items, badges, disabled states, section labels, active indicators and collapsed behavior.
60
60
  </p>
61
61
  </div>
62
62
  <div className="flex flex-wrap gap-2">
@@ -1,6 +1,6 @@
1
1
  import { useState } from "react"
2
2
 
3
- import { AsyncSelect, Badge, type AsyncSelectOption } from "@/index"
3
+ import { AsyncSelect, Badge, Button, type AsyncSelectOption } from "@/index"
4
4
  import { routeWorkspaceOptions } from "@/showcase/component-route-data"
5
5
 
6
6
  const workspaceOptions: AsyncSelectOption[] = routeWorkspaceOptions
@@ -11,10 +11,33 @@ const panelClass =
11
11
  const loadOptions = async (query: string): Promise<typeof workspaceOptions> => {
12
12
  await new Promise((resolve) => setTimeout(resolve, 260))
13
13
  const normalized = query.trim().toLowerCase()
14
+ if (normalized === "error") {
15
+ throw new Error("Unable to load regions")
16
+ }
14
17
  if (!normalized) return workspaceOptions
15
18
  return workspaceOptions.filter((item) => String(item.label ?? "").toLowerCase().includes(normalized))
16
19
  }
17
20
 
21
+ const loadSelectedOption = async (selectedValue: string) => {
22
+ await new Promise((resolve) => setTimeout(resolve, 120))
23
+ return workspaceOptions.find((item) => item.value === selectedValue) ?? null
24
+ }
25
+
26
+ const loadSelectedOptions = async (selectedValues: string[]) => {
27
+ await new Promise((resolve) => setTimeout(resolve, 120))
28
+ return workspaceOptions.filter((item) => selectedValues.includes(String(item.value)))
29
+ }
30
+
31
+ const createWorkspaceOption = async (search: string): Promise<AsyncSelectOption> => {
32
+ await new Promise((resolve) => setTimeout(resolve, 180))
33
+ const value = search.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
34
+ return {
35
+ value: value || "custom-region",
36
+ label: search.trim(),
37
+ description: "Created from the current search query",
38
+ }
39
+ }
40
+
18
41
  export function AsyncSelectShowcase() {
19
42
  const [value, setValue] = useState("")
20
43
  const [teamValues, setTeamValues] = useState<string[]>(["north", "west"])
@@ -48,14 +71,18 @@ export function AsyncSelectShowcase() {
48
71
  value={value}
49
72
  onValueChange={(nextValue) => setValue(nextValue ?? "")}
50
73
  loadOptions={loadOptions}
74
+ loadSelectedOption={loadSelectedOption}
51
75
  defaultOptions={workspaceOptions}
52
76
  minSearchLength={1}
53
77
  clearable
54
78
  showSelectedDescription
55
79
  debounceMs={220}
80
+ onCreateOption={createWorkspaceOption}
81
+ createOptionLabel={(search) => `Create region "${search.trim()}"`}
56
82
  labels={{
57
83
  placeholder: "Choose service region",
58
84
  searchPlaceholder: "Search regions...",
85
+ error: "Could not load regions. Try another query.",
59
86
  }}
60
87
  />
61
88
  </div>
@@ -67,6 +94,7 @@ export function AsyncSelectShowcase() {
67
94
  value={teamValues}
68
95
  onValueChange={(nextValue) => setTeamValues(nextValue)}
69
96
  loadOptions={loadOptions}
97
+ loadSelectedOptions={loadSelectedOptions}
70
98
  defaultOptions={workspaceOptions}
71
99
  minSearchLength={1}
72
100
  clearable
@@ -75,27 +103,45 @@ export function AsyncSelectShowcase() {
75
103
  maxVisibleTags={2}
76
104
  showSelectedDescription
77
105
  debounceMs={220}
106
+ onCreateOption={createWorkspaceOption}
107
+ createOptionLabel={(search) => `Create team "${search.trim()}"`}
78
108
  labels={{
79
109
  multiPlaceholder: "Choose team coverage",
80
110
  searchPlaceholder: "Search team regions...",
111
+ error: "Could not load teams. Try another query.",
81
112
  selectedCount: (count) => `${count} regions selected`,
82
113
  hiddenSelected: (count) => `+${count} more`,
114
+ maxSelected: (count) => `Maximum ${count} regions`,
83
115
  }}
84
116
  />
85
117
  </div>
86
118
  </div>
119
+ <div className="mt-4 flex flex-wrap gap-2">
120
+ <Button size="sm" variant="secondary" onClick={() => setValue("south")}>
121
+ Hydrate South
122
+ </Button>
123
+ <Button size="sm" variant="secondary" onClick={() => setTeamValues(["north", "west", "central"])}>
124
+ Fill max selected
125
+ </Button>
126
+ <Button size="sm" variant="outline" onClick={() => {
127
+ setValue("")
128
+ setTeamValues([])
129
+ }}>
130
+ Clear all
131
+ </Button>
132
+ </div>
87
133
  <div className="mt-4 grid gap-3 md:grid-cols-3">
88
134
  <div className="rounded-[18px] border border-[color:var(--aui-divider)] bg-[color:var(--aui-page-bg-alt)] px-4 py-3 text-sm">
89
135
  <p className="font-medium aui-text-strong">Type to query</p>
90
- <p className="mt-2 leading-6 aui-text-muted">Minimum search length keeps remote requests intentional.</p>
136
+ <p className="mt-2 leading-6 aui-text-muted">Minimum search length keeps remote requests intentional. Type error to test failure state.</p>
91
137
  </div>
92
138
  <div className="rounded-[18px] border border-[color:var(--aui-divider)] bg-[color:var(--aui-page-bg-alt)] px-4 py-3 text-sm">
93
139
  <p className="font-medium aui-text-strong">Clear safely</p>
94
140
  <p className="mt-2 leading-6 aui-text-muted">Clear action is isolated from the trigger surface.</p>
95
141
  </div>
96
142
  <div className="rounded-[18px] border border-[color:var(--aui-divider)] bg-[color:var(--aui-page-bg-alt)] px-4 py-3 text-sm">
97
- <p className="font-medium aui-text-strong">Hydrate selection</p>
98
- <p className="mt-2 leading-6 aui-text-muted">Selected value can be restored from ID-first edit state.</p>
143
+ <p className="font-medium aui-text-strong">Create and hydrate</p>
144
+ <p className="mt-2 leading-6 aui-text-muted">Create missing options and restore selected labels from ID-first edit state.</p>
99
145
  </div>
100
146
  </div>
101
147
  </div>
@@ -41,11 +41,11 @@ export function AvatarShowcase() {
41
41
  <div className="rounded-xl border border-[color:var(--aui-divider)] p-4">
42
42
  <p className="text-sm font-semibold aui-text-strong">Size and shape</p>
43
43
  <div className="mt-4 flex flex-wrap items-center gap-3">
44
- <Avatar name="AZ" size="xs" />
45
- <Avatar name="AZ" size="sm" shape="rounded" />
46
- <Avatar name="AZ" size="default" />
47
- <Avatar name="AZ" size="lg" shape="square" />
48
- <Avatar name="AZ" size="xl" shape="rounded" />
44
+ <Avatar name="TM" size="xs" />
45
+ <Avatar name="TM" size="sm" shape="rounded" />
46
+ <Avatar name="TM" size="default" />
47
+ <Avatar name="TM" size="lg" shape="square" />
48
+ <Avatar name="TM" size="xl" shape="rounded" />
49
49
  </div>
50
50
  </div>
51
51
  </div>
@@ -2,7 +2,7 @@ import type { ComponentDemoMock } from "../types"
2
2
 
3
3
  export const dialogMock: ComponentDemoMock = {
4
4
  code: `import * as React from "react"
5
- import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "tembro"
5
+ import { Button, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "tembro"
6
6
 
7
7
  export function Example() {
8
8
  const [open, setOpen] = React.useState(false)
@@ -17,6 +17,11 @@ export function Example() {
17
17
  Confirm that these notes are final before moving to production.
18
18
  </DialogDescription>
19
19
  </DialogHeader>
20
+ <DialogBody>
21
+ <p className="text-sm text-muted-foreground">
22
+ The body scrolls independently when content is long, while the footer stays usable.
23
+ </p>
24
+ </DialogBody>
20
25
  <DialogFooter>
21
26
  <Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
22
27
  <Button>Publish</Button>
@@ -1,4 +1,4 @@
1
- import { Badge, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/index"
1
+ import { Badge, Button, Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/index"
2
2
 
3
3
  import { dialogDemoItems } from "./data"
4
4
 
@@ -34,6 +34,11 @@ export function DialogShowcase() {
34
34
  <DialogTitle>{item.title}</DialogTitle>
35
35
  <DialogDescription>{item.description}</DialogDescription>
36
36
  </DialogHeader>
37
+ <DialogBody>
38
+ <div className="rounded-[16px] border border-[color:var(--aui-divider)] bg-[color:var(--aui-page-bg-alt)] px-4 py-3 text-sm leading-6 aui-text-muted">
39
+ This body area scrolls independently when content becomes long. Footer actions stay visible and aligned.
40
+ </div>
41
+ </DialogBody>
37
42
  <DialogFooter showCloseButton>
38
43
  <Button variant={item.confirmVariant}>{item.confirmLabel}</Button>
39
44
  </DialogFooter>
@@ -45,6 +50,53 @@ export function DialogShowcase() {
45
50
  </div>
46
51
  </section>
47
52
 
53
+ <section className={panelClass}>
54
+ <div className="grid gap-4 xl:grid-cols-2">
55
+ <Dialog>
56
+ <DialogTrigger render={<Button variant="outline" />}>Open form dialog</DialogTrigger>
57
+ <DialogContent size="lg">
58
+ <DialogHeader>
59
+ <DialogTitle>Create staff member</DialogTitle>
60
+ <DialogDescription>Use focused form dialogs for short admin tasks with stable footer actions.</DialogDescription>
61
+ </DialogHeader>
62
+ <DialogBody>
63
+ <div className="grid gap-4 sm:grid-cols-2">
64
+ {["Full name", "Phone", "Role", "Status", "Branch", "Temporary password"].map((label) => (
65
+ <label key={label} className="grid gap-2 text-sm font-medium aui-text-strong">
66
+ {label}
67
+ <input className="h-10 rounded-xl border border-[color:var(--aui-input-border)] bg-[color:var(--aui-input-bg)] px-3 text-sm outline-none focus-visible:border-[color:var(--aui-ring)]" placeholder={label} />
68
+ </label>
69
+ ))}
70
+ </div>
71
+ </DialogBody>
72
+ <DialogFooter>
73
+ <Button variant="outline">Cancel</Button>
74
+ <Button>Create staff</Button>
75
+ </DialogFooter>
76
+ </DialogContent>
77
+ </Dialog>
78
+
79
+ <Dialog>
80
+ <DialogTrigger render={<Button variant="destructive" />}>Open destructive dialog</DialogTrigger>
81
+ <DialogContent size="sm">
82
+ <DialogHeader>
83
+ <DialogTitle>Deactivate restaurant</DialogTitle>
84
+ <DialogDescription>This action pauses public ordering and hides active shifts from the restaurant.</DialogDescription>
85
+ </DialogHeader>
86
+ <DialogBody>
87
+ <div className="rounded-[16px] border border-red-200 bg-red-50 px-4 py-3 text-sm leading-6 text-red-700">
88
+ Operators can reactivate it later, but active customer sessions will be interrupted.
89
+ </div>
90
+ </DialogBody>
91
+ <DialogFooter>
92
+ <Button variant="outline">Keep active</Button>
93
+ <Button variant="destructive">Deactivate</Button>
94
+ </DialogFooter>
95
+ </DialogContent>
96
+ </Dialog>
97
+ </div>
98
+ </section>
99
+
48
100
  <section className={panelClass}>
49
101
  <div className="grid gap-3">
50
102
  {[
@@ -15,7 +15,7 @@ type DemoValues = {
15
15
  export function Example() {
16
16
  const form = useForm<DemoValues>({
17
17
  defaultValues: {
18
- name: "Azamat UI",
18
+ name: "Tembro",
19
19
  ownerPhone: "+998 90 123 45 67",
20
20
  price: 120,
21
21
  notes: "Use control, name, rules, error, and required labels.",
@@ -75,7 +75,7 @@ export function Example() {
75
75
  </form>
76
76
  )`
77
77
  ,
78
- htmlCode: '<form><label for="workspace">Workspace</label><input id="workspace" value="Azamat UI" /></form>',
78
+ htmlCode: '<form><label for="workspace">Workspace</label><input id="workspace" value="Tembro" /></form>',
79
79
  cliCommand: "npx tembro add form-input form-textarea form-switch",
80
80
  highlights: ["control + name contract", "Universal `kind` variants", "Error display and required markers", "Consistent wrapper shell behavior"],
81
81
  relatedBlockSlugs: ["settings-form", "crm-dashboard", "users-table"],
@@ -52,7 +52,7 @@ function loadSelectedWorkspace(value: string) {
52
52
  export function FormWrapperShowcase({ mode }: ComponentDemoProps) {
53
53
  const form = useForm<DemoValues>({
54
54
  defaultValues: {
55
- name: "Azamat UI",
55
+ name: "Tembro",
56
56
  ownerPhone: "+998 90 123 45 67",
57
57
  budget: 120,
58
58
  notes: "RHF wrapper coverage for control, name, and validation.",
@@ -79,7 +79,8 @@ import { tooltipMock, TooltipShowcase } from "./tooltip"
79
79
  import { fileUploadMock, FileUploadShowcase } from "./file-upload"
80
80
  import { imageUploadMock, ImageUploadShowcase } from "./image-upload"
81
81
  import { ratingMock, RatingShowcase } from "./rating"
82
- import { virtualListMock, VirtualListShowcase } from "./virtual-list"
82
+ import { virtualListMock, VirtualListShowcase } from "./virtual-list"
83
+ import { wizardMock, WizardShowcase } from "./wizard"
83
84
 
84
85
  export { defaultComponentDemoState } from "./types"
85
86
  export type { ComponentDemoBundle, ComponentDemoMock, ComponentDemoProps, ComponentDemoState } from "./types"
@@ -174,5 +175,6 @@ export const premiumShowcaseDemoRegistry: Record<string, ComponentDemoBundle> =
174
175
  "scroll-box": { mock: scrollBoxMock, Showcase: ScrollBoxShowcase },
175
176
  timeline: { mock: timelineMock, Showcase: TimelineShowcase },
176
177
  toast: { mock: toastMock, Showcase: ToastShowcase },
177
- "virtual-list": { mock: virtualListMock, Showcase: VirtualListShowcase },
178
- }
178
+ "virtual-list": { mock: virtualListMock, Showcase: VirtualListShowcase },
179
+ wizard: { mock: wizardMock, Showcase: WizardShowcase },
180
+ }
@@ -22,7 +22,7 @@ export function InputShowcase({ state, setState }: ComponentDemoProps) {
22
22
  const [quantityValue, setQuantityValue] = React.useState<number | null>(3)
23
23
  const [phoneValue, setPhoneValue] = React.useState("+998 90 123 45 67")
24
24
  const [dateValue, setDateValue] = React.useState("2026-07-13")
25
- const [clearableValue, setClearableValue] = React.useState("Azamat UI")
25
+ const [clearableValue, setClearableValue] = React.useState("Tembro")
26
26
 
27
27
  return (
28
28
  <div className="space-y-0">