tembro 3.1.9 → 3.1.11

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 (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/components/charts/demo.cjs +1 -0
  3. package/dist/components/charts/demo.d.ts +6 -0
  4. package/dist/components/charts/demo.js +1 -0
  5. package/dist/components/data-table/data-table.d.ts +3 -1
  6. package/dist/showcase/package-meta.d.ts +2 -2
  7. package/dist/src/components/charts/demo.cjs +1 -0
  8. package/dist/src/components/charts/demo.js +5 -0
  9. package/dist/src/components/data-table/data-table.cjs +1 -1
  10. package/dist/src/components/data-table/data-table.js +146 -111
  11. package/dist/src/components/display/kanban.cjs +1 -1
  12. package/dist/src/components/display/kanban.js +1 -1
  13. package/dist/src/showcase/index.cjs +1 -1
  14. package/dist/src/showcase/index.js +18 -16
  15. package/dist/src/showcase/package-meta.cjs +1 -1
  16. package/dist/src/showcase/package-meta.js +1 -1
  17. package/dist/src/showcase/preview-registry.cjs +1 -1
  18. package/dist/src/showcase/preview-registry.js +290 -288
  19. package/dist/src/showcase/render-registry-preview.cjs +1 -1
  20. package/dist/src/showcase/render-registry-preview.js +257 -182
  21. package/dist/src/showcase/site-data.cjs +1 -1
  22. package/dist/src/showcase/site-data.js +1 -1
  23. package/dist/src/showcase/tembro-registry.json.cjs +1 -1
  24. package/dist/src/showcase/tembro-registry.json.js +2 -2
  25. package/package.json +1 -1
  26. package/packages/cli/dist/index.cjs +14 -14
  27. package/packages/cli/vendor/src/components/charts/demo.tsx +5 -0
  28. package/packages/cli/vendor/src/components/data-table/data-table.tsx +112 -41
  29. package/packages/cli/vendor/src/components/display/kanban.tsx +4 -4
  30. package/packages/cli/vendor/src/showcase/index.ts +6 -4
  31. package/packages/cli/vendor/src/showcase/package-meta.ts +1 -1
  32. package/packages/cli/vendor/src/showcase/preview-registry.tsx +2 -0
  33. package/packages/cli/vendor/src/showcase/render-registry-preview.tsx +36 -0
  34. package/packages/cli/vendor/src/showcase/tembro-registry.json +5 -2
  35. package/packages/cli/vendor/templates/showcase/src/App.tsx +1 -1
  36. package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +3 -3
  37. package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
  38. package/packages/cli/vendor/templates/showcase/src/showcase/sections/CalendarSection.tsx +1 -1
  39. package/packages/cli/vendor/templates/showcase/src/showcase/sections/ComponentLivePreview.tsx +286 -13
  40. package/packages/cli/vendor/templates/showcase/src/showcase/sections/CoreUiSection.tsx +2 -2
  41. package/packages/cli/vendor/templates/showcase/src/showcase/sections/FormsSection.tsx +1 -1
  42. package/packages/cli/vendor/templates/showcase/src/showcase/sections/OverlaySection.tsx +1 -1
  43. package/packages/cli/vendor/templates/showcase/src/showcase/sections/PatternsSection.tsx +1 -1
  44. package/packages/cli/vendor/templates/showcase/src/showcase/sections/VerificationSection.tsx +1 -1
  45. package/packages/cli/vendor/templates/showcase/src/showcase/sections/WizardSection.tsx +1 -1
  46. package/registry.json +5 -2
@@ -1,5 +1,45 @@
1
+ import { useMemo } from "react"
2
+ import {
3
+ CalendarDaysIcon,
4
+ CheckCircle2Icon,
5
+ ChevronRightIcon,
6
+ CommandIcon,
7
+ FolderIcon,
8
+ LayoutDashboardIcon,
9
+ ListChecksIcon,
10
+ SearchIcon,
11
+ SettingsIcon,
12
+ SparklesIcon,
13
+ UsersIcon,
14
+ } from "lucide-react"
15
+
16
+ import { KanbanBoard, type KanbanColumn } from "@/components/display/kanban"
17
+ import { Progress, ProgressCard } from "@/components/display/progress"
18
+ import { StatusLegend } from "@/components/display/status-legend"
19
+ import { Tag, TagGroup } from "@/components/display/tag"
20
+ import { Timeline } from "@/components/display/timeline"
21
+ import { Alert } from "@/components/feedback/alert"
22
+ import { LoadingState } from "@/components/feedback/loading-state"
23
+ import { Pagination } from "@/components/navigation/pagination"
24
+ import { EmptyState } from "@/components/patterns/empty-state"
1
25
  import { Badge } from "@/components/ui/badge"
26
+ import { Button } from "@/components/ui/button"
2
27
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
28
+ import { Checkbox } from "@/components/ui/checkbox"
29
+ import { Divider } from "@/components/ui/divider"
30
+ import { Input } from "@/components/ui/input"
31
+ import { Kbd } from "@/components/ui/kbd"
32
+ import { RadioGroup } from "@/components/ui/radio-group"
33
+ import { ScrollBox } from "@/components/ui/scroll-box"
34
+ import { SegmentedControl } from "@/components/ui/segmented-control"
35
+ import { Select } from "@/components/ui/select"
36
+ import { Skeleton, SkeletonCard, SkeletonText } from "@/components/ui/skeleton"
37
+ import { Spinner } from "@/components/ui/spinner"
38
+ import { Switch } from "@/components/ui/switch"
39
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
40
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
41
+ import { Textarea } from "@/components/ui/textarea"
42
+ import { Sidebar } from "@/components/layout/app-sidebar"
3
43
  import type { RegistryComponent, RegistryGroup } from "@/showcase/data/registry"
4
44
  import { CalendarSection } from "@/showcase/sections/CalendarSection"
5
45
  import { CoreUiSection } from "@/showcase/sections/CoreUiSection"
@@ -15,17 +55,239 @@ type ComponentLivePreviewProps = {
15
55
  component: RegistryComponent
16
56
  }
17
57
 
18
- function getPreviewLabel(groupName: string, componentName: string) {
19
- if (componentName === "kanban") return "KanbanBoard"
20
- if (componentName === "data-table") return "DataTable"
21
- if (["calendar", "date-picker", "date-range-picker"].includes(componentName)) return "Calendar and date pickers"
22
- if (["drawer", "confirm-dialog", "alert-dialog", "dialog"].includes(componentName)) return "Overlay controls"
23
- if (["stepper", "wizard"].includes(componentName)) return "Stepper and Wizard"
24
- if (["rating", "slider", "tag-input", "async-select", "color-picker", "signature-pad", "json-input", "time-picker"].includes(componentName)) return "Advanced input controls"
25
- if (groupName === "ui") return "UI primitive controls"
26
- if (groupName === "display") return "Display and status components"
27
- if (groupName === "patterns") return "Pattern components"
28
- return `${componentName} live surface`
58
+ function SourcePill({ component }: { component: RegistryComponent }) {
59
+ return (
60
+ <div className="flex flex-wrap items-center gap-2 rounded-lg border bg-muted/35 px-3 py-2 text-xs text-muted-foreground">
61
+ <span className="font-medium text-foreground">Direct local import</span>
62
+ <code className="rounded bg-background px-1.5 py-0.5">{component.sourcePath}</code>
63
+ </div>
64
+ )
65
+ }
66
+
67
+ function ButtonPreview() {
68
+ return (
69
+ <div className="flex flex-wrap gap-2">
70
+ <Button leftIcon={<SparklesIcon className="size-4" />}>Default</Button>
71
+ <Button variant="secondary">Secondary</Button>
72
+ <Button variant="outline">Outline</Button>
73
+ <Button variant="ghost">Ghost</Button>
74
+ <Button variant="warning">Warning</Button>
75
+ <Button variant="destructive">Destructive</Button>
76
+ <Button loading loadingLabel="Loading" />
77
+ <Button iconOnly aria-label="Command">
78
+ <CommandIcon className="size-4" />
79
+ </Button>
80
+ </div>
81
+ )
82
+ }
83
+
84
+ function InputPreview() {
85
+ return (
86
+ <div className="grid gap-3 md:grid-cols-2">
87
+ <Input placeholder="Text input" defaultValue="Tembro input" />
88
+ <Input kind="search" placeholder="Search input" searchIcon={<SearchIcon className="size-4" />} />
89
+ <Input kind="password" placeholder="Password input" defaultValue="secret" />
90
+ <Input kind="number" aria-label="Number input" defaultValue={42} />
91
+ </div>
92
+ )
93
+ }
94
+
95
+ function SelectPreview() {
96
+ return (
97
+ <Select
98
+ placeholder="Select component"
99
+ searchable
100
+ clearable
101
+ options={[
102
+ { label: "Button", value: "button", description: "ui/button" },
103
+ { label: "Kanban", value: "kanban", description: "display/kanban" },
104
+ { label: "DataTable", value: "data-table", description: "data-table/data-table" },
105
+ ]}
106
+ />
107
+ )
108
+ }
109
+
110
+ function TablePreview() {
111
+ return (
112
+ <Table>
113
+ <TableHeader>
114
+ <TableRow>
115
+ <TableHead>Component</TableHead>
116
+ <TableHead>Status</TableHead>
117
+ <TableHead>Group</TableHead>
118
+ </TableRow>
119
+ </TableHeader>
120
+ <TableBody>
121
+ {[
122
+ ["sidebar", "stable", "layout"],
123
+ ["kanban", "preview", "display"],
124
+ ["button", "stable", "ui"],
125
+ ].map(([name, status, group]) => (
126
+ <TableRow key={name}>
127
+ <TableCell>{name}</TableCell>
128
+ <TableCell>
129
+ <Badge label={status} status={status === "stable" ? "success" : "info"} />
130
+ </TableCell>
131
+ <TableCell>{group}</TableCell>
132
+ </TableRow>
133
+ ))}
134
+ </TableBody>
135
+ </Table>
136
+ )
137
+ }
138
+
139
+ function TabsPreview() {
140
+ return (
141
+ <Tabs defaultValue="preview">
142
+ <TabsList>
143
+ <TabsTrigger value="preview">Preview</TabsTrigger>
144
+ <TabsTrigger value="props">Props</TabsTrigger>
145
+ </TabsList>
146
+ <TabsContent value="preview" className="rounded-lg border bg-muted/25 p-4">
147
+ TabsContent rendered from local tabs component.
148
+ </TabsContent>
149
+ <TabsContent value="props" className="rounded-lg border bg-muted/25 p-4">
150
+ Root, List, Trigger and Content exports are active.
151
+ </TabsContent>
152
+ </Tabs>
153
+ )
154
+ }
155
+
156
+ function KanbanPreview() {
157
+ const columns = useMemo<KanbanColumn[]>(
158
+ () => [
159
+ {
160
+ key: "todo",
161
+ title: "Todo",
162
+ count: 2,
163
+ cards: [
164
+ { key: "button", title: "Button", description: "variants, size, iconOnly", meta: <Badge label="ui" /> },
165
+ { key: "sidebar", title: "Sidebar", description: "navigation surface", meta: <Badge label="layout" /> },
166
+ ],
167
+ },
168
+ {
169
+ key: "testing",
170
+ title: "Testing",
171
+ count: 2,
172
+ cards: [
173
+ { key: "kanban", title: "KanbanBoard", description: "drag and drop columns", meta: <Badge status="info" label="dnd" /> },
174
+ { key: "datatable", title: "DataTable", description: "search and pagination", meta: <Badge status="warning" label="table" /> },
175
+ ],
176
+ },
177
+ {
178
+ key: "ready",
179
+ title: "Ready",
180
+ count: 1,
181
+ cards: [{ key: "theme", title: "Theme tokens", description: "local styles loaded", meta: <Badge status="success" label="ready" /> }],
182
+ },
183
+ ],
184
+ []
185
+ )
186
+
187
+ return <KanbanBoard defaultColumns={columns} />
188
+ }
189
+
190
+ function SidebarPreview() {
191
+ return (
192
+ <div className="min-h-[430px] overflow-hidden rounded-xl border bg-background">
193
+ <Sidebar
194
+ responsive={false}
195
+ width="18rem"
196
+ header={
197
+ <div className="grid gap-1 px-1">
198
+ <div className="font-semibold">Tembro</div>
199
+ <div className="text-xs text-muted-foreground">Local sidebar component</div>
200
+ </div>
201
+ }
202
+ items={[
203
+ {
204
+ key: "workspace",
205
+ label: "Workspace",
206
+ sectionLabel: "Main",
207
+ icon: <LayoutDashboardIcon className="size-4" />,
208
+ active: true,
209
+ badge: <Badge label="live" status="success" />,
210
+ },
211
+ { key: "components", label: "Components", icon: <FolderIcon className="size-4" />, defaultExpanded: true, items: [
212
+ { key: "button", label: "button", icon: <CheckCircle2Icon className="size-4" /> },
213
+ { key: "kanban", label: "kanban", icon: <ListChecksIcon className="size-4" /> },
214
+ { key: "data-table", label: "data-table", icon: <UsersIcon className="size-4" /> },
215
+ ] },
216
+ { key: "calendar", label: "Calendar", icon: <CalendarDaysIcon className="size-4" /> },
217
+ { key: "settings", label: "Settings", icon: <SettingsIcon className="size-4" /> },
218
+ ]}
219
+ footerAccount={{
220
+ label: "Azamat",
221
+ description: "QA workspace",
222
+ }}
223
+ />
224
+ </div>
225
+ )
226
+ }
227
+
228
+ function DisplayPreview() {
229
+ return (
230
+ <div className="grid items-start gap-4 lg:grid-cols-2">
231
+ <ProgressCard title="ProgressCard" description="display/progress" value={72} tone="success" />
232
+ <StatusLegend
233
+ title="StatusLegend"
234
+ items={[
235
+ { key: "ready", label: "Ready", tone: "success" },
236
+ { key: "testing", label: "Testing", tone: "info" },
237
+ { key: "blocked", label: "Blocked", tone: "danger" },
238
+ ]}
239
+ />
240
+ <Timeline
241
+ items={[
242
+ { key: "init", title: "Init", description: "CLI scaffold", tone: "success" },
243
+ { key: "add", title: "Add components", description: "Registry copied", tone: "info" },
244
+ { key: "test", title: "Browser test", description: "Screenshot validation", tone: "warning" },
245
+ ]}
246
+ />
247
+ <TagGroup>
248
+ <Tag tone="success" selected>selected</Tag>
249
+ <Tag tone="info">info</Tag>
250
+ <Tag tone="warning" removable>removable</Tag>
251
+ </TagGroup>
252
+ </div>
253
+ )
254
+ }
255
+
256
+ function UiPreview({ name }: { name: string }) {
257
+ if (name === "button") return <ButtonPreview />
258
+ if (name === "input") return <InputPreview />
259
+ if (name === "textarea") return <Textarea defaultValue="Textarea component with helper text and character count." helperText="helperText prop" maxLength={120} showCharacterCount />
260
+ if (name === "checkbox") return <label className="flex items-center gap-3 text-sm"><Checkbox defaultChecked />Checkbox checked state</label>
261
+ if (name === "switch") return <Switch defaultChecked label="Switch enabled" description="Switch label and description props." />
262
+ if (name === "badge") return <div className="flex flex-wrap gap-2"><Badge label="default" /><Badge label="info" status="info" /><Badge label="success" status="success" /><Badge label="warning" status="warning" /><Badge label="danger" status="danger" removable /></div>
263
+ if (name === "card") return <Card variant="elevated" tone="info" title="Card title prop" description="Card description prop" badge={<Badge label="badge" />} action={<Button size="sm" variant="outline" rightIcon={<ChevronRightIcon className="size-4" />}>Action</Button>} content={<Progress value={64} label="Card content" showValue />} footer="Card footer prop" />
264
+ if (name === "skeleton") return <div className="grid gap-3 md:grid-cols-2"><SkeletonCard /><div className="grid content-start gap-3"><Skeleton className="h-10 w-40" /><SkeletonText rows={4} /></div></div>
265
+ if (name === "tabs") return <TabsPreview />
266
+ if (name === "select") return <SelectPreview />
267
+ if (name === "table") return <TablePreview />
268
+ if (name === "segmented-control") return <SegmentedControl defaultValue="desktop" options={[{ label: "Desktop", value: "desktop" }, { label: "Tablet", value: "tablet" }, { label: "Mobile", value: "mobile" }]} />
269
+ if (name === "radio-group") return <RadioGroup defaultValue="stable" options={[{ label: "Stable", value: "stable" }, { label: "Preview", value: "preview" }, { label: "Experimental", value: "experimental" }]} />
270
+ if (name === "kbd") return <div className="flex flex-wrap gap-2"><Kbd>Ctrl</Kbd><Kbd>K</Kbd><Kbd>Enter</Kbd></div>
271
+ if (name === "scroll-box") return <ScrollBox className="h-36 rounded-lg border p-4">{Array.from({ length: 12 }, (_, index) => <p key={index} className="text-sm">Scrollable local row {index + 1}</p>)}</ScrollBox>
272
+ if (name === "divider") return <div className="grid gap-4"><span>Above divider</span><Divider /><span>Below divider</span></div>
273
+ if (name === "spinner") return <div className="flex items-center gap-3"><Spinner /><span className="text-sm text-muted-foreground">Spinner component</span></div>
274
+ return <CoreUiSection />
275
+ }
276
+
277
+ function DirectComponentSurface({ group, component }: ComponentLivePreviewProps) {
278
+ if (component.name === "kanban") return <KanbanPreview />
279
+ if (component.name === "data-table") return <DataTableSection />
280
+ if (component.name === "sidebar") return <SidebarPreview />
281
+ if (group.name === "ui") return <UiPreview name={component.name} />
282
+ if (group.name === "inputs") return <FormsSection />
283
+ if (group.name === "form") return <FormsSection />
284
+ if (group.name === "display") return <DisplayPreview />
285
+ if (group.name === "calendar" || group.name === "navigation") return group.name === "navigation" ? <Pagination page={2} pageCount={8} onPageChange={() => undefined} /> : <CalendarSection />
286
+ if (group.name === "feedback") return component.name === "alert" ? <Alert tone="success" title="Alert component" description="Rendered from feedback/alert." /> : <LoadingState label="LoadingState" description="Rendered from feedback/loading-state." />
287
+ if (group.name === "patterns") return <EmptyState title="EmptyState component" description="Rendered from patterns/empty-state." primaryAction={{ label: "Create item" }} secondaryAction={{ label: "Reset" }} />
288
+ if (group.name === "wizard") return <WizardSection />
289
+ if (group.name === "overlay") return <OverlaySection />
290
+ return <PatternsSection />
29
291
  }
30
292
 
31
293
  function RenderLiveSurface({ group, component }: ComponentLivePreviewProps) {
@@ -46,8 +308,8 @@ export function ComponentLivePreview({ group, component }: ComponentLivePreviewP
46
308
  <CardHeader>
47
309
  <div className="flex flex-wrap items-start justify-between gap-3">
48
310
  <div>
49
- <CardTitle>Live preview: {component.name}</CardTitle>
50
- <CardDescription>{getPreviewLabel(group.name, component.name)} rendered from copied local source.</CardDescription>
311
+ <CardTitle>Component preview: {component.name}</CardTitle>
312
+ <CardDescription>{component.name} is rendered from the copied local component file.</CardDescription>
51
313
  </div>
52
314
  <div className="flex flex-wrap gap-2">
53
315
  <Badge label={group.name} status="info" />
@@ -55,6 +317,17 @@ export function ComponentLivePreview({ group, component }: ComponentLivePreviewP
55
317
  </div>
56
318
  </div>
57
319
  </CardHeader>
320
+ <CardContent className="grid gap-4">
321
+ <SourcePill component={component} />
322
+ <DirectComponentSurface group={group} component={component} />
323
+ </CardContent>
324
+ </Card>
325
+
326
+ <Card>
327
+ <CardHeader>
328
+ <CardTitle>Related group surface</CardTitle>
329
+ <CardDescription>Same installed local components used together inside a full section.</CardDescription>
330
+ </CardHeader>
58
331
  <CardContent>
59
332
  <RenderLiveSurface group={group} component={component} />
60
333
  </CardContent>
@@ -16,7 +16,7 @@ import { registryGroups } from "@/showcase/data/registry"
16
16
 
17
17
  export function CoreUiSection() {
18
18
  return (
19
- <div className="grid gap-4 xl:grid-cols-[1.35fr_0.65fr]">
19
+ <div className="grid items-start gap-4 xl:grid-cols-[1.35fr_0.65fr]">
20
20
  <Card>
21
21
  <CardHeader>
22
22
  <CardTitle>UI controls and variants</CardTitle>
@@ -53,7 +53,7 @@ export function CoreUiSection() {
53
53
  }))}
54
54
  />
55
55
  </div>
56
- <div className="grid gap-4 md:grid-cols-2">
56
+ <div className="grid items-start gap-4 md:grid-cols-2">
57
57
  <Textarea defaultValue="Textarea with helper text, counter and Tembro theme." helperText="Textarea helper text" maxLength={140} showCharacterCount />
58
58
  <div className="grid content-start gap-4 rounded-lg border bg-muted/25 p-4">
59
59
  <Switch defaultChecked label="Enable preview" description="Switch with label and description" />
@@ -7,7 +7,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
7
7
 
8
8
  export function FormsSection() {
9
9
  return (
10
- <div className="grid gap-4 lg:grid-cols-3">
10
+ <div className="grid items-start gap-4 lg:grid-cols-3">
11
11
  <Card>
12
12
  <CardHeader>
13
13
  <CardTitle>Advanced inputs</CardTitle>
@@ -12,7 +12,7 @@ export function OverlaySection() {
12
12
  const [confirmOpen, setConfirmOpen] = useState(false)
13
13
 
14
14
  return (
15
- <div className="grid gap-4 lg:grid-cols-3">
15
+ <div className="grid items-start gap-4 lg:grid-cols-3">
16
16
  <Card>
17
17
  <CardHeader>
18
18
  <CardTitle>Drawer</CardTitle>
@@ -4,7 +4,7 @@ import { EmptyState } from "@/components/patterns/empty-state"
4
4
 
5
5
  export function PatternsSection() {
6
6
  return (
7
- <div className="grid gap-4 lg:grid-cols-3">
7
+ <div className="grid items-start gap-4 lg:grid-cols-3">
8
8
  <EmptyState
9
9
  title="EmptyState pattern"
10
10
  description="Pattern components are installed: resource pages, settings pages, page toolbar, data view and form builder."
@@ -8,7 +8,7 @@ export function VerificationSection() {
8
8
  return (
9
9
  <section>
10
10
  <SectionTitle title="Verification" description="Package va project setup holati." />
11
- <div className="grid gap-4 md:grid-cols-3">
11
+ <div className="grid items-start gap-4 md:grid-cols-3">
12
12
  <Card>
13
13
  <CardContent className="flex items-start gap-3 pt-6">
14
14
  <CheckCircle2Icon className="mt-1 size-5 text-emerald-600" />
@@ -9,7 +9,7 @@ export function WizardSection() {
9
9
  const [step, setStep] = useState("test")
10
10
 
11
11
  return (
12
- <div className="grid gap-4 lg:grid-cols-[1fr_0.8fr]">
12
+ <div className="grid items-start gap-4 lg:grid-cols-[1fr_0.8fr]">
13
13
  <Card>
14
14
  <CardHeader>
15
15
  <CardTitle>Stepper</CardTitle>
package/registry.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://tembro.dev/registry.schema.json",
3
3
  "name": "tembro",
4
- "version": "3.1.9",
4
+ "version": "3.1.11",
5
5
  "description": "Reusable React + TypeScript UI kit registry",
6
6
  "groups": {
7
7
  "ui": [
@@ -161,7 +161,10 @@
161
161
  "wizard-kit",
162
162
  "all"
163
163
  ],
164
- "charts": []
164
+ "charts": [
165
+ "charts",
166
+ "progress-circle"
167
+ ]
165
168
  },
166
169
  "recommended": [
167
170
  "button",