tembro 2.1.12 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/charts/charts.cjs +1 -1
- package/dist/components/charts/charts.d.ts +8 -3
- package/dist/components/charts/charts.js +79 -51
- package/dist/components/charts/horizontal-bar-chart.cjs +1 -1
- package/dist/components/charts/horizontal-bar-chart.d.ts +3 -1
- package/dist/components/charts/horizontal-bar-chart.js +12 -7
- package/dist/components/charts/index.cjs +1 -1
- package/dist/components/charts/index.js +5 -5
- package/dist/components/charts/public.cjs +1 -1
- package/dist/components/charts/public.js +2 -2
- package/dist/components/data-table/demo.cjs +1 -1
- package/dist/components/data-table/demo.d.ts +2 -6
- package/dist/components/data-table/demo.js +27 -3
- package/dist/index.cjs +1 -1
- package/dist/index.js +41 -41
- package/dist/showcase/create-demo.cjs +2 -138
- package/dist/showcase/create-demo.js +37 -52
- package/dist/showcase/demo-snippets/actions/index.cjs +64 -0
- package/dist/showcase/demo-snippets/actions/index.d.ts +1 -0
- package/dist/showcase/demo-snippets/actions/index.js +10 -0
- package/dist/showcase/demo-snippets/forms/index.cjs +26 -0
- package/dist/showcase/demo-snippets/forms/index.d.ts +1 -0
- package/dist/showcase/demo-snippets/forms/index.js +9 -0
- package/dist/showcase/demo-snippets/index.cjs +1 -0
- package/dist/showcase/demo-snippets/index.d.ts +1 -0
- package/dist/showcase/demo-snippets/index.js +13 -0
- package/dist/showcase/demo-snippets/layout/index.cjs +45 -0
- package/dist/showcase/demo-snippets/layout/index.d.ts +1 -0
- package/dist/showcase/demo-snippets/layout/index.js +9 -0
- package/dist/showcase/demo-snippets/upload/index.cjs +5 -0
- package/dist/showcase/demo-snippets/upload/index.d.ts +1 -0
- package/dist/showcase/demo-snippets/upload/index.js +4 -0
- package/dist/showcase/premium/index.cjs +1 -1
- package/dist/showcase/premium/index.js +278 -278
- package/dist/showcase/tembro-registry.json.cjs +1 -1
- package/dist/showcase/tembro-registry.json.js +1 -1
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +40 -40
- package/packages/cli/vendor/src/components/charts/charts.tsx +59 -22
- package/packages/cli/vendor/src/components/charts/horizontal-bar-chart.tsx +34 -20
- package/packages/cli/vendor/src/components/data-table/demo.tsx +31 -2
- package/packages/cli/vendor/src/showcase/create-demo.tsx +8 -160
- package/packages/cli/vendor/src/showcase/demo-snippets/actions/index.ts +70 -0
- package/packages/cli/vendor/src/showcase/demo-snippets/forms/index.ts +31 -0
- package/packages/cli/vendor/src/showcase/demo-snippets/index.ts +11 -0
- package/packages/cli/vendor/src/showcase/demo-snippets/layout/index.ts +50 -0
- package/packages/cli/vendor/src/showcase/demo-snippets/upload/index.ts +7 -0
- package/packages/cli/vendor/src/showcase/tembro-registry.json +1 -1
- package/registry.json +1 -1
|
@@ -40,6 +40,14 @@ function normalizeValue(value: number, max: number) {
|
|
|
40
40
|
function getChartColor(index: number, custom?: string) {
|
|
41
41
|
return custom ?? `var(--color-chart-${(index % 5) + 1}, var(--primary))`
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
function formatChartValue(value: number, formatter?: (value: number) => React.ReactNode) {
|
|
45
|
+
return formatter ? formatter(value) : value.toLocaleString()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function chartLabelToTitle(label: React.ReactNode) {
|
|
49
|
+
return typeof label === "string" || typeof label === "number" ? String(label) : undefined
|
|
50
|
+
}
|
|
43
51
|
|
|
44
52
|
function polarToCartesian(cx: number, cy: number, radius: number, angle: number) {
|
|
45
53
|
const radians = ((angle - 90) * Math.PI) / 180
|
|
@@ -117,18 +125,19 @@ function ChartFrame({ title, description, action, state = "ready", emptyLabel =
|
|
|
117
125
|
)
|
|
118
126
|
}
|
|
119
127
|
|
|
120
|
-
export type BarChartProps = React.ComponentProps<"div"> & {
|
|
121
|
-
data: ChartDatum[]
|
|
128
|
+
export type BarChartProps = React.ComponentProps<"div"> & {
|
|
129
|
+
data: ChartDatum[]
|
|
122
130
|
size?: ChartSize
|
|
123
131
|
max?: number
|
|
124
132
|
showLabels?: boolean
|
|
125
133
|
showValues?: boolean
|
|
134
|
+
valueFormatter?: (value: number) => React.ReactNode
|
|
126
135
|
barClassName?: string
|
|
127
136
|
state?: ChartState
|
|
128
137
|
emptyLabel?: React.ReactNode
|
|
129
138
|
}
|
|
130
139
|
|
|
131
|
-
function BarChart({ data, size = "md", max, showLabels = true, showValues = true, state = "ready", emptyLabel = "No chart data.", className, barClassName, ...props }: BarChartProps) {
|
|
140
|
+
function BarChart({ data, size = "md", max, showLabels = true, showValues = true, valueFormatter, state = "ready", emptyLabel = "No chart data.", className, barClassName, ...props }: BarChartProps) {
|
|
132
141
|
const values = data.map((item) => item.value)
|
|
133
142
|
const absoluteMax = max ?? safeMax(values.map((value) => Math.abs(value)))
|
|
134
143
|
const height = chartHeightBySize[size]
|
|
@@ -147,9 +156,10 @@ function BarChart({ data, size = "md", max, showLabels = true, showValues = true
|
|
|
147
156
|
{data.map((item, index) => {
|
|
148
157
|
const ratio = normalizeValue(Math.abs(item.value), absoluteMax)
|
|
149
158
|
const negative = item.value < 0
|
|
159
|
+
const title = chartLabelToTitle(item.label)
|
|
150
160
|
return (
|
|
151
|
-
<div key={index} className="flex min-w-0 flex-1 flex-col items-center gap-2">
|
|
152
|
-
{showValues && <div className="text-xs text-muted-foreground">{item.value}</div>}
|
|
161
|
+
<div key={index} className="flex min-w-0 flex-1 flex-col items-center gap-2" title={title ? `${title}: ${item.value}` : undefined}>
|
|
162
|
+
{showValues && <div className="text-xs font-medium text-muted-foreground">{formatChartValue(item.value, valueFormatter)}</div>}
|
|
153
163
|
<div className="flex w-full flex-1 items-end rounded-[min(var(--radius-xl),16px)] border border-border/60 bg-muted/38 p-1">
|
|
154
164
|
<div
|
|
155
165
|
className={cn("w-full rounded-[min(var(--radius-lg),12px)] bg-primary transition-all", barClassName)}
|
|
@@ -170,15 +180,19 @@ export type LineChartProps = Omit<React.ComponentProps<"svg">, "values"> & {
|
|
|
170
180
|
size?: ChartSize
|
|
171
181
|
width?: number
|
|
172
182
|
showArea?: boolean
|
|
183
|
+
showGrid?: boolean
|
|
184
|
+
labels?: ChartAxisLabel[]
|
|
185
|
+
valueFormatter?: (value: number) => React.ReactNode
|
|
173
186
|
stroke?: string
|
|
174
187
|
state?: ChartState
|
|
175
188
|
emptyLabel?: React.ReactNode
|
|
176
189
|
}
|
|
177
190
|
|
|
178
|
-
function LineChart({ values, size = "md", width = 560, showArea = false, stroke = "var(--primary)", state = "ready", emptyLabel = "No line data.", className, ...props }: LineChartProps) {
|
|
191
|
+
function LineChart({ values, size = "md", width = 560, showArea = false, showGrid = true, labels, valueFormatter, stroke = "var(--primary)", state = "ready", emptyLabel = "No line data.", className, ...props }: LineChartProps) {
|
|
179
192
|
const height = chartHeightBySize[size]
|
|
180
193
|
const linePath = buildLinePath(values, width, height)
|
|
181
194
|
const areaPath = buildAreaPath(values, width, height)
|
|
195
|
+
const max = safeMax(values)
|
|
182
196
|
|
|
183
197
|
if (state === "loading") {
|
|
184
198
|
return <div className={cn("h-36 animate-pulse rounded-[min(var(--radius-xl),16px)] bg-muted/70", className)} />
|
|
@@ -189,18 +203,39 @@ function LineChart({ values, size = "md", width = 560, showArea = false, stroke
|
|
|
189
203
|
}
|
|
190
204
|
|
|
191
205
|
return (
|
|
192
|
-
<svg data-slot="line-chart" viewBox={`0 0 ${width} ${height}`} className={cn("h-auto w-full overflow-visible", className)} role="img" {...props}>
|
|
193
|
-
{
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
206
|
+
<svg data-slot="line-chart" viewBox={`0 0 ${width} ${height}`} className={cn("h-auto w-full overflow-visible", className)} role="img" {...props}>
|
|
207
|
+
{showGrid && [0.25, 0.5, 0.75].map((position) => (
|
|
208
|
+
<line
|
|
209
|
+
key={position}
|
|
210
|
+
x1="12"
|
|
211
|
+
x2={width - 12}
|
|
212
|
+
y1={12 + position * (height - 24)}
|
|
213
|
+
y2={12 + position * (height - 24)}
|
|
214
|
+
stroke="var(--border)"
|
|
215
|
+
strokeDasharray="4 6"
|
|
216
|
+
opacity="0.7"
|
|
217
|
+
/>
|
|
218
|
+
))}
|
|
219
|
+
{showArea && <path d={areaPath} fill="var(--primary)" opacity="0.12" />}
|
|
220
|
+
<path d={linePath} fill="none" stroke={stroke} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
|
221
|
+
{values.map((value, index) => {
|
|
222
|
+
const x = 12 + (values.length === 1 ? (width - 24) / 2 : (index / (values.length - 1)) * (width - 24))
|
|
223
|
+
const y = 12 + (1 - normalizeValue(value, max)) * (height - 24)
|
|
224
|
+
const label = labels?.[index]
|
|
225
|
+
const title = label != null ? `${chartLabelToTitle(label) ?? "Value"}: ${value}` : `${value}`
|
|
226
|
+
return (
|
|
227
|
+
<circle key={index} cx={x} cy={y} r="3.5" fill={stroke}>
|
|
228
|
+
<title>{valueFormatter ? `${label ?? "Value"}: ${formatChartValue(value, valueFormatter)}` : title}</title>
|
|
229
|
+
</circle>
|
|
230
|
+
)
|
|
231
|
+
})}
|
|
232
|
+
</svg>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function AreaChart(props: Omit<LineChartProps, "showArea">) {
|
|
237
|
+
return <LineChart showArea {...props} />
|
|
238
|
+
}
|
|
204
239
|
|
|
205
240
|
export type SparklineProps = Omit<LineChartProps, "size" | "showArea"> & {
|
|
206
241
|
values: number[]
|
|
@@ -262,9 +297,11 @@ function DonutChart({ data, size = 180, strokeWidth = 18, centerLabel, centerVal
|
|
|
262
297
|
stroke={getChartColor(index, item.color)}
|
|
263
298
|
strokeWidth={strokeWidth}
|
|
264
299
|
strokeLinecap="round"
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
300
|
+
>
|
|
301
|
+
<title>{`${chartLabelToTitle(item.label) ?? "Segment"}: ${item.value}`}</title>
|
|
302
|
+
</path>
|
|
303
|
+
)
|
|
304
|
+
})}
|
|
268
305
|
{(centerValue || centerLabel) && (
|
|
269
306
|
<text x="50%" y="50%" textAnchor="middle" dominantBaseline="middle" fill="var(--foreground)">
|
|
270
307
|
{centerValue && <tspan x="50%" dy={centerLabel ? "-0.2em" : "0"} className="text-lg font-semibold">{centerValue}</tspan>}
|
|
@@ -326,4 +363,4 @@ function MetricTrend({ label, value, change, positive = true, values, className,
|
|
|
326
363
|
)
|
|
327
364
|
}
|
|
328
365
|
|
|
329
|
-
export { BarChart, ChartFrame, ChartLegend, DonutChart, LineChart, MetricTrend, Sparkline }
|
|
366
|
+
export { AreaChart, BarChart, ChartFrame, ChartLegend, DonutChart, LineChart, MetricTrend, Sparkline }
|
|
@@ -10,31 +10,45 @@ export type HorizontalBarDatum = {
|
|
|
10
10
|
color?: string
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export type HorizontalBarChartProps = React.ComponentProps<"div"> & {
|
|
14
|
-
data: HorizontalBarDatum[]
|
|
15
|
-
max?: number
|
|
16
|
-
showValue?: boolean
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
export type HorizontalBarChartProps = React.ComponentProps<"div"> & {
|
|
14
|
+
data: HorizontalBarDatum[]
|
|
15
|
+
max?: number
|
|
16
|
+
showValue?: boolean
|
|
17
|
+
emptyLabel?: React.ReactNode
|
|
18
|
+
valueFormatter?: (value: number) => React.ReactNode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function HorizontalBarChart({ data, max, showValue = true, emptyLabel = "No ranking data.", valueFormatter, className, ...props }: HorizontalBarChartProps) {
|
|
22
|
+
const resolvedMax = max ?? Math.max(...data.map((item) => item.value), 1)
|
|
23
|
+
|
|
24
|
+
if (data.length === 0) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
data-slot="horizontal-bar-chart"
|
|
28
|
+
className={cn("rounded-[min(var(--radius-xl),16px)] border border-dashed border-border/70 px-4 py-10 text-center text-sm text-muted-foreground", className)}
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
{emptyLabel}
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div data-slot="horizontal-bar-chart" className={cn("grid gap-3", className)} {...props}>
|
|
38
|
+
{data.map((item) => {
|
|
39
|
+
const width = resolvedMax > 0 ? Math.max(0, Math.min((item.value / resolvedMax) * 100, 100)) : 0
|
|
26
40
|
|
|
27
41
|
return (
|
|
28
42
|
<div key={item.key} className="grid gap-1.5">
|
|
29
43
|
<div className="flex items-center justify-between gap-3 text-sm">
|
|
30
44
|
<div className="min-w-0">
|
|
31
|
-
<div className="truncate font-medium text-foreground">{item.label}</div>
|
|
32
|
-
{item.description && <div className="truncate text-xs text-muted-foreground">{item.description}</div>}
|
|
33
|
-
</div>
|
|
34
|
-
{showValue && <div className="text-xs font-medium text-muted-foreground">{item.value}</div>}
|
|
35
|
-
</div>
|
|
36
|
-
<div className="h-2 overflow-hidden rounded-full bg-muted">
|
|
37
|
-
<div className="h-full rounded-full bg-primary" style={{ width: `${width}%`, background: item.color }} />
|
|
45
|
+
<div className="truncate font-medium text-foreground">{item.label}</div>
|
|
46
|
+
{item.description && <div className="truncate text-xs text-muted-foreground">{item.description}</div>}
|
|
47
|
+
</div>
|
|
48
|
+
{showValue && <div className="text-xs font-medium text-muted-foreground">{valueFormatter ? valueFormatter(item.value) : item.value.toLocaleString()}</div>}
|
|
49
|
+
</div>
|
|
50
|
+
<div className="h-2 overflow-hidden rounded-full bg-muted">
|
|
51
|
+
<div className="h-full rounded-full bg-primary" style={{ width: `${width}%`, background: item.color }} />
|
|
38
52
|
</div>
|
|
39
53
|
</div>
|
|
40
54
|
)
|
|
@@ -1,3 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DataTableShowcase, dataTableMock } from "@/showcase/premium/data-table"
|
|
2
|
+
import {
|
|
3
|
+
DataTablePartShowcase,
|
|
4
|
+
dataTableActionsColumnMock,
|
|
5
|
+
dataTablePaginationMock,
|
|
6
|
+
dataTableRowActionsMock,
|
|
7
|
+
dataTableSavedFiltersMock,
|
|
8
|
+
dataTableSelectColumnMock,
|
|
9
|
+
dataTableToolbarMock,
|
|
10
|
+
} from "@/showcase/premium/data-table-parts"
|
|
2
11
|
|
|
3
|
-
|
|
12
|
+
import type { ComponentDemoBundle } from "@/showcase/premium/types"
|
|
13
|
+
|
|
14
|
+
function createPartBundle(
|
|
15
|
+
slug: "data-table-pagination" | "data-table-toolbar" | "data-table-row-actions" | "data-table-actions-column" | "data-table-select-column" | "data-table-saved-filters",
|
|
16
|
+
mock: ComponentDemoBundle["mock"]
|
|
17
|
+
): ComponentDemoBundle {
|
|
18
|
+
return {
|
|
19
|
+
mock,
|
|
20
|
+
Showcase: (props) => <DataTablePartShowcase {...props} slug={slug} />,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const dataTableShowcaseDemoRegistry: Record<string, ComponentDemoBundle> = {
|
|
25
|
+
"data-table": { mock: dataTableMock, Showcase: DataTableShowcase },
|
|
26
|
+
"data-table-pagination": createPartBundle("data-table-pagination", dataTablePaginationMock),
|
|
27
|
+
"data-table-toolbar": createPartBundle("data-table-toolbar", dataTableToolbarMock),
|
|
28
|
+
"data-table-row-actions": createPartBundle("data-table-row-actions", dataTableRowActionsMock),
|
|
29
|
+
"data-table-actions-column": createPartBundle("data-table-actions-column", dataTableActionsColumnMock),
|
|
30
|
+
"data-table-select-column": createPartBundle("data-table-select-column", dataTableSelectColumnMock),
|
|
31
|
+
"data-table-saved-filters": createPartBundle("data-table-saved-filters", dataTableSavedFiltersMock),
|
|
32
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { ShowcaseDemoBundle, ShowcaseDemoDefinition, ShowcaseDemoMock, ShowcaseDemoProps } from "./types"
|
|
2
|
-
import { renderShowcasePreview } from "./render-registry-preview"
|
|
1
|
+
import type { ShowcaseDemoBundle, ShowcaseDemoDefinition, ShowcaseDemoMock, ShowcaseDemoProps } from "./types"
|
|
2
|
+
import { renderShowcasePreview } from "./render-registry-preview"
|
|
3
|
+
import { demoCodeSnippets } from "./demo-snippets"
|
|
3
4
|
|
|
4
5
|
export function component(
|
|
5
6
|
slug: string,
|
|
@@ -57,165 +58,12 @@ function createMock(definition: ShowcaseDemoDefinition): ShowcaseDemoMock {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
function createCodeSnippet(definition: ShowcaseDemoDefinition) {
|
|
61
|
-
return
|
|
62
|
-
?? createKindSnippet(definition)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const exactCodeSnippets: Record<string, string> = {
|
|
66
|
-
"search-input": `import { Input } from "tembro"
|
|
67
|
-
|
|
68
|
-
export function Demo() {
|
|
69
|
-
return (
|
|
70
|
-
<Input
|
|
71
|
-
type="search"
|
|
72
|
-
value="invoice"
|
|
73
|
-
onValueChange={(value) => console.log(value)}
|
|
74
|
-
placeholder="Search invoices..."
|
|
75
|
-
resultCount={12}
|
|
76
|
-
shortcut="Ctrl K"
|
|
77
|
-
/>
|
|
78
|
-
)
|
|
79
|
-
}`,
|
|
80
|
-
"password-input": `import { PasswordInput } from "tembro"
|
|
81
|
-
|
|
82
|
-
export function Demo() {
|
|
83
|
-
return <PasswordInput placeholder="Enter secure token" autoComplete="current-password" />
|
|
84
|
-
}`,
|
|
85
|
-
"clearable-input": `import { Input } from "tembro"
|
|
86
|
-
|
|
87
|
-
export function Demo() {
|
|
88
|
-
return <Input defaultValue="Azamat UI" placeholder="Search customer" clearable />
|
|
89
|
-
}`,
|
|
90
|
-
"tag-input": `import { TagInput } from "tembro"
|
|
91
|
-
|
|
92
|
-
export function Demo() {
|
|
93
|
-
return <TagInput defaultValue={["dashboard", "billing"]} placeholder="Add tag" />
|
|
94
|
-
}`,
|
|
95
|
-
"action-menu": `import { ActionMenu, Button } from "tembro"
|
|
96
|
-
|
|
97
|
-
export function Demo() {
|
|
98
|
-
return (
|
|
99
|
-
<ActionMenu
|
|
100
|
-
label="Row actions"
|
|
101
|
-
actions={[
|
|
102
|
-
{ key: "open", label: "Open" },
|
|
103
|
-
{ key: "duplicate", label: "Duplicate" },
|
|
104
|
-
{ key: "archive", label: "Archive", destructive: true },
|
|
105
|
-
]}
|
|
106
|
-
trigger={<Button variant="outline">Actions</Button>}
|
|
107
|
-
/>
|
|
108
|
-
)
|
|
109
|
-
}`,
|
|
110
|
-
"button-group": `import { ButtonGroup } from "tembro"
|
|
111
|
-
|
|
112
|
-
export function Demo() {
|
|
113
|
-
return (
|
|
114
|
-
<ButtonGroup
|
|
115
|
-
items={[
|
|
116
|
-
{ key: "day", label: "Day" },
|
|
117
|
-
{ key: "week", label: "Week" },
|
|
118
|
-
{ key: "month", label: "Month" },
|
|
119
|
-
]}
|
|
120
|
-
/>
|
|
121
|
-
)
|
|
122
|
-
}`,
|
|
123
|
-
"quick-action-grid": `import { QuickActionGrid } from "tembro"
|
|
124
|
-
|
|
125
|
-
export function Demo() {
|
|
126
|
-
return (
|
|
127
|
-
<QuickActionGrid
|
|
128
|
-
columns={3}
|
|
129
|
-
items={[
|
|
130
|
-
{ key: "new", label: "New invoice", description: "Create a billing row." },
|
|
131
|
-
{ key: "import", label: "Import CSV", description: "Upload operational data." },
|
|
132
|
-
{ key: "share", label: "Share", description: "Invite a teammate." },
|
|
133
|
-
]}
|
|
134
|
-
/>
|
|
135
|
-
)
|
|
136
|
-
}`,
|
|
137
|
-
"filter-bar": `import { Button, FilterBar, Input } from "tembro"
|
|
138
|
-
|
|
139
|
-
export function Demo() {
|
|
140
|
-
return (
|
|
141
|
-
<FilterBar
|
|
142
|
-
search={<Input type="search" value="" placeholder="Search rows..." readOnly />}
|
|
143
|
-
chips={[
|
|
144
|
-
{ key: "status", label: "Status", value: "Active", tone: "success" },
|
|
145
|
-
{ key: "owner", label: "Owner", value: "Azamat" },
|
|
146
|
-
]}
|
|
147
|
-
filters={<Button variant="outline">Status</Button>}
|
|
148
|
-
actions={<Button>Export</Button>}
|
|
149
|
-
onReset={() => undefined}
|
|
150
|
-
/>
|
|
151
|
-
)
|
|
152
|
-
}`,
|
|
153
|
-
"description-list": `import { DescriptionList } from "tembro"
|
|
154
|
-
|
|
155
|
-
export function Demo() {
|
|
156
|
-
return (
|
|
157
|
-
<DescriptionList
|
|
158
|
-
title="Invoice details"
|
|
159
|
-
items={[
|
|
160
|
-
{ key: "id", label: "Invoice", value: "#4821" },
|
|
161
|
-
{ key: "amount", label: "Amount", value: "$12,420" },
|
|
162
|
-
{ key: "status", label: "Status", value: "Paid" },
|
|
163
|
-
]}
|
|
164
|
-
/>
|
|
165
|
-
)
|
|
166
|
-
}`,
|
|
167
|
-
"stat-card": `import { StatCard } from "tembro"
|
|
168
|
-
|
|
169
|
-
export function Demo() {
|
|
170
|
-
return (
|
|
171
|
-
<StatCard
|
|
172
|
-
title="Revenue"
|
|
173
|
-
value="$84.2k"
|
|
174
|
-
description="Compared with last month"
|
|
175
|
-
trend={{ value: "+12.4%", tone: "success" }}
|
|
176
|
-
helperText="Updated just now"
|
|
177
|
-
/>
|
|
178
|
-
)
|
|
179
|
-
}`,
|
|
180
|
-
"pagination": `import { Pagination } from "tembro"
|
|
181
|
-
|
|
182
|
-
export function Demo() {
|
|
183
|
-
return <Pagination page={3} pageCount={9} onPageChange={(page) => console.log(page)} />
|
|
184
|
-
}`,
|
|
185
|
-
"dialog-actions": `import { DialogActionButton, DialogActions } from "tembro"
|
|
186
|
-
|
|
187
|
-
export function Demo() {
|
|
188
|
-
return (
|
|
189
|
-
<DialogActions align="end">
|
|
190
|
-
<DialogActionButton variant="ghost">Cancel</DialogActionButton>
|
|
191
|
-
<DialogActionButton variant="outline">Save draft</DialogActionButton>
|
|
192
|
-
<DialogActionButton>Publish</DialogActionButton>
|
|
193
|
-
</DialogActions>
|
|
194
|
-
)
|
|
195
|
-
}`,
|
|
196
|
-
"file-dropzone": `import { FileDropzone } from "tembro"
|
|
197
|
-
|
|
198
|
-
export function Demo() {
|
|
199
|
-
return <FileDropzone label="Drop contract files" description="PDF, PNG or CSV up to 10MB." />
|
|
200
|
-
}`,
|
|
201
|
-
"stepper": `import { Stepper } from "tembro"
|
|
202
|
-
|
|
203
|
-
export function Demo() {
|
|
204
|
-
return (
|
|
205
|
-
<Stepper
|
|
206
|
-
currentStep="billing"
|
|
207
|
-
steps={[
|
|
208
|
-
{ id: "profile", title: "Profile", completed: true },
|
|
209
|
-
{ id: "billing", title: "Billing" },
|
|
210
|
-
{ id: "review", title: "Review" },
|
|
211
|
-
]}
|
|
212
|
-
onStepChange={() => undefined}
|
|
213
|
-
/>
|
|
214
|
-
)
|
|
215
|
-
}`,
|
|
216
|
-
}
|
|
61
|
+
function createCodeSnippet(definition: ShowcaseDemoDefinition) {
|
|
62
|
+
return demoCodeSnippets[definition.slug]
|
|
63
|
+
?? createKindSnippet(definition)
|
|
64
|
+
}
|
|
217
65
|
|
|
218
|
-
function createKindSnippet(definition: ShowcaseDemoDefinition) {
|
|
66
|
+
function createKindSnippet(definition: ShowcaseDemoDefinition) {
|
|
219
67
|
const importName = definition.importName ?? definition.component
|
|
220
68
|
const importPath = "tembro"
|
|
221
69
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const actionDemoCodeSnippets: Record<string, string> = {
|
|
2
|
+
"action-menu": `import { ActionMenu, Button } from "tembro"
|
|
3
|
+
|
|
4
|
+
export function Demo() {
|
|
5
|
+
return (
|
|
6
|
+
<ActionMenu
|
|
7
|
+
label="Row actions"
|
|
8
|
+
actions={[
|
|
9
|
+
{ key: "open", label: "Open" },
|
|
10
|
+
{ key: "duplicate", label: "Duplicate" },
|
|
11
|
+
{ key: "archive", label: "Archive", destructive: true },
|
|
12
|
+
]}
|
|
13
|
+
trigger={<Button variant="outline">Actions</Button>}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}`,
|
|
17
|
+
"button-group": `import { ButtonGroup } from "tembro"
|
|
18
|
+
|
|
19
|
+
export function Demo() {
|
|
20
|
+
return (
|
|
21
|
+
<ButtonGroup
|
|
22
|
+
items={[
|
|
23
|
+
{ key: "day", label: "Day" },
|
|
24
|
+
{ key: "week", label: "Week" },
|
|
25
|
+
{ key: "month", label: "Month" },
|
|
26
|
+
]}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}`,
|
|
30
|
+
"quick-action-grid": `import { QuickActionGrid } from "tembro"
|
|
31
|
+
|
|
32
|
+
export function Demo() {
|
|
33
|
+
return (
|
|
34
|
+
<QuickActionGrid
|
|
35
|
+
columns={3}
|
|
36
|
+
items={[
|
|
37
|
+
{ key: "new", label: "New invoice", description: "Create a billing row." },
|
|
38
|
+
{ key: "import", label: "Import CSV", description: "Upload operational data." },
|
|
39
|
+
{ key: "share", label: "Share", description: "Invite a teammate." },
|
|
40
|
+
]}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}`,
|
|
44
|
+
"dialog-actions": `import { DialogActionButton, DialogActions } from "tembro"
|
|
45
|
+
|
|
46
|
+
export function Demo() {
|
|
47
|
+
return (
|
|
48
|
+
<DialogActions align="end">
|
|
49
|
+
<DialogActionButton variant="ghost">Cancel</DialogActionButton>
|
|
50
|
+
<DialogActionButton variant="outline">Save draft</DialogActionButton>
|
|
51
|
+
<DialogActionButton>Publish</DialogActionButton>
|
|
52
|
+
</DialogActions>
|
|
53
|
+
)
|
|
54
|
+
}`,
|
|
55
|
+
stepper: `import { Stepper } from "tembro"
|
|
56
|
+
|
|
57
|
+
export function Demo() {
|
|
58
|
+
return (
|
|
59
|
+
<Stepper
|
|
60
|
+
currentStep="billing"
|
|
61
|
+
steps={[
|
|
62
|
+
{ id: "profile", title: "Profile", completed: true },
|
|
63
|
+
{ id: "billing", title: "Billing" },
|
|
64
|
+
{ id: "review", title: "Review" },
|
|
65
|
+
]}
|
|
66
|
+
onStepChange={() => undefined}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}`,
|
|
70
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const formDemoCodeSnippets: Record<string, string> = {
|
|
2
|
+
"search-input": `import { Input } from "tembro"
|
|
3
|
+
|
|
4
|
+
export function Demo() {
|
|
5
|
+
return (
|
|
6
|
+
<Input
|
|
7
|
+
type="search"
|
|
8
|
+
value="invoice"
|
|
9
|
+
onValueChange={(value) => console.log(value)}
|
|
10
|
+
placeholder="Search invoices..."
|
|
11
|
+
resultCount={12}
|
|
12
|
+
shortcut="Ctrl K"
|
|
13
|
+
/>
|
|
14
|
+
)
|
|
15
|
+
}`,
|
|
16
|
+
"password-input": `import { PasswordInput } from "tembro"
|
|
17
|
+
|
|
18
|
+
export function Demo() {
|
|
19
|
+
return <PasswordInput placeholder="Enter secure token" autoComplete="current-password" />
|
|
20
|
+
}`,
|
|
21
|
+
"clearable-input": `import { Input } from "tembro"
|
|
22
|
+
|
|
23
|
+
export function Demo() {
|
|
24
|
+
return <Input defaultValue="Azamat UI" placeholder="Search customer" clearable />
|
|
25
|
+
}`,
|
|
26
|
+
"tag-input": `import { TagInput } from "tembro"
|
|
27
|
+
|
|
28
|
+
export function Demo() {
|
|
29
|
+
return <TagInput defaultValue={["dashboard", "billing"]} placeholder="Add tag" />
|
|
30
|
+
}`,
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { actionDemoCodeSnippets } from "./actions"
|
|
2
|
+
import { formDemoCodeSnippets } from "./forms"
|
|
3
|
+
import { layoutDemoCodeSnippets } from "./layout"
|
|
4
|
+
import { uploadDemoCodeSnippets } from "./upload"
|
|
5
|
+
|
|
6
|
+
export const demoCodeSnippets: Record<string, string> = {
|
|
7
|
+
...actionDemoCodeSnippets,
|
|
8
|
+
...formDemoCodeSnippets,
|
|
9
|
+
...layoutDemoCodeSnippets,
|
|
10
|
+
...uploadDemoCodeSnippets,
|
|
11
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const layoutDemoCodeSnippets: Record<string, string> = {
|
|
2
|
+
"filter-bar": `import { Button, FilterBar, Input } from "tembro"
|
|
3
|
+
|
|
4
|
+
export function Demo() {
|
|
5
|
+
return (
|
|
6
|
+
<FilterBar
|
|
7
|
+
search={<Input type="search" value="" placeholder="Search rows..." readOnly />}
|
|
8
|
+
chips={[
|
|
9
|
+
{ key: "status", label: "Status", value: "Active", tone: "success" },
|
|
10
|
+
{ key: "owner", label: "Owner", value: "Azamat" },
|
|
11
|
+
]}
|
|
12
|
+
filters={<Button variant="outline">Status</Button>}
|
|
13
|
+
actions={<Button>Export</Button>}
|
|
14
|
+
onReset={() => undefined}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}`,
|
|
18
|
+
"description-list": `import { DescriptionList } from "tembro"
|
|
19
|
+
|
|
20
|
+
export function Demo() {
|
|
21
|
+
return (
|
|
22
|
+
<DescriptionList
|
|
23
|
+
title="Invoice details"
|
|
24
|
+
items={[
|
|
25
|
+
{ key: "id", label: "Invoice", value: "#4821" },
|
|
26
|
+
{ key: "amount", label: "Amount", value: "$12,420" },
|
|
27
|
+
{ key: "status", label: "Status", value: "Paid" },
|
|
28
|
+
]}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
}`,
|
|
32
|
+
"stat-card": `import { StatCard } from "tembro"
|
|
33
|
+
|
|
34
|
+
export function Demo() {
|
|
35
|
+
return (
|
|
36
|
+
<StatCard
|
|
37
|
+
title="Revenue"
|
|
38
|
+
value="$84.2k"
|
|
39
|
+
description="Compared with last month"
|
|
40
|
+
trend={{ value: "+12.4%", tone: "success" }}
|
|
41
|
+
helperText="Updated just now"
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}`,
|
|
45
|
+
pagination: `import { Pagination } from "tembro"
|
|
46
|
+
|
|
47
|
+
export function Demo() {
|
|
48
|
+
return <Pagination page={3} pageCount={9} onPageChange={(page) => console.log(page)} />
|
|
49
|
+
}`,
|
|
50
|
+
}
|