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.
- package/CHANGELOG.md +7 -1
- package/README.md +241 -91
- package/dist/components/ui/dialog/index.d.ts +3 -1
- package/dist/components/ui/select/index.d.ts +6 -3
- package/dist/showcase/package-meta.d.ts +3 -3
- package/dist/showcase/premium/wizard/index.cjs +1 -0
- package/dist/showcase/premium/wizard/index.d.ts +2 -0
- package/dist/showcase/premium/wizard/index.js +1 -0
- package/dist/showcase/premium/wizard/mock.cjs +1 -0
- package/dist/showcase/premium/wizard/mock.d.ts +2 -0
- package/dist/showcase/premium/wizard/mock.js +1 -0
- package/dist/showcase/premium/wizard/showcase.cjs +1 -0
- package/dist/showcase/premium/wizard/showcase.d.ts +2 -0
- package/dist/showcase/premium/wizard/showcase.js +1 -0
- package/dist/showcase/search-utils.d.ts +1 -1
- package/dist/showcase/site-data.d.ts +6 -6
- package/dist/src/components/layout/app-sidebar.cjs +1 -1
- package/dist/src/components/layout/app-sidebar.js +49 -36
- package/dist/src/components/theme-provider.cjs +1 -1
- package/dist/src/components/theme-provider.js +1 -1
- package/dist/src/components/ui/dialog/index.cjs +1 -1
- package/dist/src/components/ui/dialog/index.js +14 -7
- package/dist/src/components/ui/select/index.cjs +1 -1
- package/dist/src/components/ui/select/index.js +44 -43
- package/dist/src/index.cjs +1 -1
- package/dist/src/index.js +101 -101
- package/dist/src/showcase/component-api-schema.cjs +1 -1
- package/dist/src/showcase/component-api-schema.js +868 -9
- package/dist/src/showcase/demo-snippets/forms/index.cjs +1 -1
- package/dist/src/showcase/demo-snippets/forms/index.js +1 -1
- package/dist/src/showcase/fallback.cjs +4 -4
- package/dist/src/showcase/fallback.js +354 -181
- package/dist/src/showcase/index.cjs +1 -1
- package/dist/src/showcase/index.js +2 -2
- package/dist/src/showcase/package-meta.cjs +1 -1
- package/dist/src/showcase/package-meta.js +1 -1
- package/dist/src/showcase/premium/app-sidebar/mock.cjs +3 -2
- package/dist/src/showcase/premium/app-sidebar/mock.js +1 -1
- package/dist/src/showcase/premium/app-sidebar/showcase.cjs +1 -1
- package/dist/src/showcase/premium/app-sidebar/showcase.js +1 -1
- package/dist/src/showcase/premium/async-select/showcase.cjs +1 -1
- package/dist/src/showcase/premium/async-select/showcase.js +118 -74
- package/dist/src/showcase/premium/avatar/showcase.cjs +1 -1
- package/dist/src/showcase/premium/avatar/showcase.js +5 -5
- package/dist/src/showcase/premium/dialog/mock.cjs +6 -1
- package/dist/src/showcase/premium/dialog/mock.js +1 -1
- package/dist/src/showcase/premium/dialog/showcase.cjs +1 -1
- package/dist/src/showcase/premium/dialog/showcase.js +101 -39
- package/dist/src/showcase/premium/form-wrapper/mock.cjs +2 -2
- package/dist/src/showcase/premium/form-wrapper/mock.js +2 -2
- package/dist/src/showcase/premium/form-wrapper/showcase.cjs +1 -1
- package/dist/src/showcase/premium/form-wrapper/showcase.js +1 -1
- package/dist/src/showcase/premium/index.cjs +1 -1
- package/dist/src/showcase/premium/index.js +9 -3
- package/dist/src/showcase/premium/input/showcase.cjs +1 -1
- package/dist/src/showcase/premium/input/showcase.js +1 -1
- package/dist/src/showcase/premium/json-input/showcase.cjs +1 -1
- package/dist/src/showcase/premium/json-input/showcase.js +1 -1
- package/dist/src/showcase/premium/select/showcase.cjs +1 -1
- package/dist/src/showcase/premium/select/showcase.js +53 -0
- package/dist/src/showcase/premium/table/mock.cjs +1 -1
- package/dist/src/showcase/premium/table/mock.js +1 -1
- package/dist/src/showcase/premium/types.cjs +1 -1
- package/dist/src/showcase/premium/types.js +1 -1
- package/dist/src/showcase/premium/wizard/index.cjs +1 -0
- package/dist/src/showcase/premium/wizard/index.js +3 -0
- package/dist/src/showcase/premium/wizard/mock.cjs +25 -0
- package/dist/src/showcase/premium/wizard/mock.js +32 -0
- package/dist/src/showcase/premium/wizard/showcase.cjs +1 -0
- package/dist/src/showcase/premium/wizard/showcase.js +130 -0
- package/dist/src/showcase/preview-registry.cjs +1 -1
- package/dist/src/showcase/preview-registry.js +6 -6
- package/dist/src/showcase/registry-quality.cjs +1 -1
- package/dist/src/showcase/registry-quality.js +5 -0
- package/dist/src/showcase/registry-specific.cjs +1 -1
- package/dist/src/showcase/registry-specific.js +2 -2
- package/dist/src/showcase/render-registry-preview.cjs +1 -1
- package/dist/src/showcase/render-registry-preview.js +2 -2
- package/dist/src/showcase/site-data.cjs +1 -1
- package/dist/src/showcase/site-data.js +198 -44
- package/dist/src/showcase/tembro-registry.json.cjs +1 -1
- package/dist/src/showcase/tembro-registry.json.js +1 -1
- package/dist/src/showcase/types.cjs +1 -1
- package/dist/src/showcase/types.js +1 -1
- package/package.json +13 -9
- package/packages/cli/dist/index.cjs +127 -127
- package/packages/cli/vendor/src/components/layout/app-sidebar.tsx +31 -6
- package/packages/cli/vendor/src/components/theme-provider.tsx +1 -1
- package/packages/cli/vendor/src/components/ui/dialog/index.tsx +15 -3
- package/packages/cli/vendor/src/components/ui/select/index.tsx +17 -10
- package/packages/cli/vendor/src/showcase/component-api-schema.ts +269 -39
- package/packages/cli/vendor/src/showcase/demo-snippets/forms/index.ts +1 -1
- package/packages/cli/vendor/src/showcase/fallback.tsx +202 -39
- package/packages/cli/vendor/src/showcase/index.ts +6 -6
- package/packages/cli/vendor/src/showcase/package-meta.ts +3 -3
- package/packages/cli/vendor/src/showcase/premium/app-sidebar/mock.ts +3 -2
- package/packages/cli/vendor/src/showcase/premium/app-sidebar/showcase.tsx +1 -1
- package/packages/cli/vendor/src/showcase/premium/async-select/showcase.tsx +50 -4
- package/packages/cli/vendor/src/showcase/premium/avatar/showcase.tsx +5 -5
- package/packages/cli/vendor/src/showcase/premium/dialog/mock.ts +6 -1
- package/packages/cli/vendor/src/showcase/premium/dialog/showcase.tsx +53 -1
- package/packages/cli/vendor/src/showcase/premium/form-wrapper/mock.ts +2 -2
- package/packages/cli/vendor/src/showcase/premium/form-wrapper/showcase.tsx +1 -1
- package/packages/cli/vendor/src/showcase/premium/index.ts +5 -3
- package/packages/cli/vendor/src/showcase/premium/input/showcase.tsx +1 -1
- package/packages/cli/vendor/src/showcase/premium/json-input/showcase.tsx +1 -1
- package/packages/cli/vendor/src/showcase/premium/select/showcase.tsx +38 -0
- package/packages/cli/vendor/src/showcase/premium/table/mock.ts +1 -1
- package/packages/cli/vendor/src/showcase/premium/types.ts +1 -1
- package/packages/cli/vendor/src/showcase/premium/wizard/index.ts +2 -0
- package/packages/cli/vendor/src/showcase/premium/wizard/mock.ts +41 -0
- package/packages/cli/vendor/src/showcase/premium/wizard/showcase.tsx +88 -0
- package/packages/cli/vendor/src/showcase/preview-registry.tsx +6 -6
- package/packages/cli/vendor/src/showcase/registry-quality.ts +5 -0
- package/packages/cli/vendor/src/showcase/registry-specific.tsx +2 -2
- package/packages/cli/vendor/src/showcase/render-registry-preview.tsx +2 -2
- package/packages/cli/vendor/src/showcase/site-data.tsx +89 -32
- package/packages/cli/vendor/src/showcase/tembro-registry.json +1 -1
- package/packages/cli/vendor/src/showcase/types.ts +1 -1
- package/registry.json +1 -1
|
@@ -1,47 +1,56 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Alert,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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="
|
|
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": "
|
|
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
|
-
...
|
|
51
|
-
...
|
|
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.
|
|
2
|
-
export const PACKAGE_LATEST_RELEASE_DATE = "July
|
|
3
|
-
export const PACKAGE_DEPENDENCY_RANGE =
|
|
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 {
|
|
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
|
-
<
|
|
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">
|
|
98
|
-
<p className="mt-2 leading-6 aui-text-muted">
|
|
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="
|
|
45
|
-
<Avatar name="
|
|
46
|
-
<Avatar name="
|
|
47
|
-
<Avatar name="
|
|
48
|
-
<Avatar name="
|
|
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: "
|
|
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="
|
|
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: "
|
|
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("
|
|
25
|
+
const [clearableValue, setClearableValue] = React.useState("Tembro")
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
28
|
<div className="space-y-0">
|