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
@@ -58,18 +58,24 @@ export const componentApiSchemas: Record<string, ComponentApiSchema> = {
58
58
  { name: "kind", type: "'text' | 'search' | 'password' | 'number' | 'phone' | 'money' | 'quantity' | 'masked' | 'date' | 'date-range'", defaultValue: "'text'", description: "Routes Input into a supported preset without teaching a new public component name first.", example: "kind=\"search\"" },
59
59
  { name: "onChange", type: "ChangeEventHandler<HTMLInputElement>", defaultValue: "-", description: "Native change handler for value updates.", example: "onChange={(event) => setValue(event.target.value)}" },
60
60
  { name: "onValueChange", type: "(value: string) => void", defaultValue: "-", description: "Direct text callback for controlled string flows.", example: "onValueChange={setQuery}" },
61
+ { name: "onDebouncedValueChange", type: "(value: string) => void", defaultValue: "-", description: "Delayed callback for search-heavy inputs." },
61
62
  { name: "type", type: "HTMLInputTypeAttribute", defaultValue: "'text'", description: "Native input type. Prefer presets only when formatting or masking is required." },
62
63
  { name: "placeholder", type: "string", defaultValue: "-", description: "Short hint for empty input state." },
63
64
  { name: "leading", type: "ReactNode", defaultValue: "-", description: "Non-interactive leading slot for icons, prefixes, or labels." },
64
65
  { name: "trailing", type: "ReactNode", defaultValue: "-", description: "Passive trailing slot for units, counts, or status copy." },
65
66
  { name: "clearable", type: "boolean", defaultValue: "false", description: "Shows the built-in clear action when a value exists.", example: "clearable" },
66
67
  { name: "onClear", type: "() => void", defaultValue: "-", description: "Called after the built-in clear action resets the field." },
68
+ { name: "clearLabel", type: "string", defaultValue: "'Clear'", description: "Accessible label for the clear action." },
67
69
  { name: "trailingAction", type: "ReactNode", defaultValue: "-", description: "Interactive trailing slot for counters, shortcuts, or toggle actions.", example: "trailingAction={<span>12 results</span>}" },
68
70
  { name: "replaceTrailingWhenClear", type: "boolean", defaultValue: "true", description: "Controls whether clear replaces trailing content or sits beside it." },
69
71
  { name: "clearOnEscape", type: "boolean", defaultValue: "false", description: "Clears the value on Escape when clearable mode is active." },
70
72
  { name: "helperText", type: "ReactNode", defaultValue: "-", description: "Support copy rendered below the field." },
71
73
  { name: "errorText", type: "ReactNode", defaultValue: "-", description: "Validation copy rendered below the field and connected to danger styling." },
72
74
  { name: "showCharacterCount", type: "boolean", defaultValue: "false", description: "Shows current length and maxLength when available." },
75
+ { name: "loading", type: "boolean", defaultValue: "false", description: "Shows pending feedback in search-oriented input states." },
76
+ { name: "resultCount", type: "number", defaultValue: "-", description: "Optional search result count displayed in the input meta area." },
77
+ { name: "shortcut", type: "ReactNode", defaultValue: "-", description: "Keyboard hint or shortcut chip rendered inside search-oriented inputs." },
78
+ { name: "debounceMs", type: "number", defaultValue: "-", description: "Delay before `onDebouncedValueChange` fires." },
73
79
  { name: "disabled", type: "boolean", defaultValue: "false", description: "Disables editing and interaction." },
74
80
  { name: "required", type: "boolean", defaultValue: "false", description: "Marks the input as required for native validation." },
75
81
  { name: "name", type: "string", defaultValue: "-", description: "Form field name used during submission." },
@@ -94,18 +100,27 @@ export const componentApiSchemas: Record<string, ComponentApiSchema> = {
94
100
  { name: "options", type: "Array<{ label: string; value: string; disabled?: boolean }>", defaultValue: "[]", required: true, description: "Selectable option list." },
95
101
  { name: "groups", type: "Array<{ label?: ReactNode; options: SelectOption[] }>", defaultValue: "-", description: "Grouped selectable options for longer finite lists." },
96
102
  { name: "placeholder", type: "string", defaultValue: "-", description: "Text shown when no value is selected." },
103
+ { name: "searchPlaceholder", type: "string", defaultValue: "'Search options...'", description: "Placeholder used by the optional search input inside the popup." },
97
104
  { name: "disabled", type: "boolean", defaultValue: "false", description: "Disables the control and option selection." },
98
105
  { name: "searchable", type: "boolean", defaultValue: "false", description: "Enables local filtering on the main Select surface." },
99
106
  { name: "clearable", type: "boolean", defaultValue: "false", description: "Shows a clear action when a value is selected." },
107
+ { name: "clearLabel", type: "string", defaultValue: "'Clear selection'", description: "Accessible label for the clear action." },
108
+ { name: "size", type: "'sm' | 'default' | 'lg'", defaultValue: "'default'", description: "Controls trigger height and density." },
100
109
  { name: "loading", type: "boolean", defaultValue: "false", description: "Disables selection and renders a loading state." },
110
+ { name: "loadingLabel", type: "ReactNode", defaultValue: "'Loading options...'", description: "Content shown while options are loading." },
101
111
  { name: "emptyLabel", type: "ReactNode", defaultValue: "'No options found'", description: "Empty state content for local filtering." },
102
112
  { name: "emptyMessage", type: "ReactNode", defaultValue: "-", description: "Alias for empty state content used by newer docs." },
113
+ { name: "showSelectedDescription", type: "boolean", defaultValue: "true", description: "Shows the selected option description inside the trigger when available." },
114
+ { name: "showSelectedIndicator", type: "boolean", defaultValue: "true", description: "Shows the built-in check indicator inside option rows. Set false when `renderOption` draws its own selected state." },
103
115
  { name: "renderOption", type: "(option, state) => ReactNode", defaultValue: "-", description: "Custom option renderer for richer rows." },
116
+ { name: "invalid", type: "boolean", defaultValue: "false", description: "Marks the trigger invalid and sets `aria-invalid`." },
117
+ { name: "onSearchChange", type: "(value: string) => void", defaultValue: "-", description: "Called as the local search value changes." },
104
118
  ],
105
119
  examples: [
106
120
  { title: "Primary select", code: "<Select value={status} onValueChange={setStatus} options={statusOptions} />" },
107
121
  { title: "Searchable primary select", code: "<Select value={owner} onValueChange={setOwner} searchable options={ownerOptions} />" },
108
122
  { title: "Grouped select", code: "<Select value={region} onValueChange={setRegion} groups={regionGroups} searchable />" },
123
+ { title: "Custom option selected state", code: "<Select options={options} renderOption={(option, state) => <CustomOption selected={state.selected} />} showSelectedIndicator={false} />" },
109
124
  { title: "Async member direction", code: "// Prefer Select first; use AsyncSelect only when remote loading is required." },
110
125
  ],
111
126
  accessibility: ["Use clear option labels.", "Keep disabled options visible but not selectable.", "Do not replace long forms with hidden select menus."],
@@ -134,8 +149,8 @@ export const componentApiSchemas: Record<string, ComponentApiSchema> = {
134
149
  ],
135
150
  accessibility: ["Pair textareas with visible labels.", "Use helper and error copy instead of placeholder-only instructions.", "Keep read-only distinct from disabled."],
136
151
  },
137
- "data-table": {
138
- slug: "data-table",
152
+ "data-table": {
153
+ slug: "data-table",
139
154
  source: "metadata",
140
155
  summary: "Operational grid for dense product data. Keep `columns`, `data`, `getRowId`, toolbar wiring, and pagination explicit so search, selection, row actions, loading, and server-mode behavior stay predictable.",
141
156
  props: [
@@ -143,14 +158,18 @@ export const componentApiSchemas: Record<string, ComponentApiSchema> = {
143
158
  { name: "data", type: "RowData[]", defaultValue: "[]", required: true, description: "Rows for the current client or server page.", example: "data={rows}" },
144
159
  { name: "getRowId", type: "(row: RowData, index: number) => string", defaultValue: "-", description: "Stable row id for selection, row actions, and refresh updates.", example: "getRowId={(row) => row.id}" },
145
160
  { name: "title", type: "ReactNode", defaultValue: "-", description: "Toolbar title when you want the table to explain itself without extra wrapper markup." },
146
- { name: "description", type: "ReactNode", defaultValue: "-", description: "Short supporting line for operators using the table." },
147
- { name: "search", type: "DataTableSearchConfig", defaultValue: "-", description: "Controlled search input config for the built-in toolbar.", example: "search={{ value, onValueChange: setValue, placeholder: \"Search invoices...\" }}" },
148
- { name: "toolbarProps", type: "DataTableToolbarProps | (table) => DataTableToolbarProps", defaultValue: "-", description: "Inject search, filters, actions, and selection affordances into the default toolbar.", notes: "Use this when you want one canonical toolbar instead of composing a separate header card." },
161
+ { name: "description", type: "ReactNode", defaultValue: "-", description: "Short supporting line for operators using the table." },
162
+ { name: "search", type: "DataTableSearchConfig", defaultValue: "-", description: "Controlled search input config for the built-in toolbar.", example: "search={{ value, onValueChange: setValue, placeholder: \"Search invoices...\" }}" },
163
+ { name: "filters", type: "ReactNode | (context) => ReactNode", defaultValue: "-", description: "Filter controls rendered in the default toolbar." },
164
+ { name: "summary", type: "ReactNode | (context) => ReactNode", defaultValue: "-", description: "Compact summary content rendered in the default toolbar." },
165
+ { name: "toolbarProps", type: "DataTableToolbarProps | (table) => DataTableToolbarProps", defaultValue: "-", description: "Inject search, filters, actions, and selection affordances into the default toolbar.", notes: "Use this when you want one canonical toolbar instead of composing a separate header card." },
149
166
  { name: "toolbar", type: "ReactNode | (table) => ReactNode", defaultValue: "-", description: "Replace the default toolbar entirely when the route needs a custom control layout." },
150
167
  { name: "features", type: "DataTableFeatureConfig", defaultValue: "-", description: "Turn default search, column visibility, row actions, bulk actions, refresh, or export behavior on and off." },
151
- { name: "toolbarActions", type: "ReactNode | (context) => ReactNode", defaultValue: "-", description: "Extra action cluster rendered beside visibility / refresh / export controls." },
152
- { name: "bulkActions", type: "DataTableBulkAction<RowData>[]", defaultValue: "-", description: "Bulk actions shown when selected rows exist." },
153
- { name: "rowActions", type: "(row, original) => DataTableRowAction<RowData>[]", defaultValue: "-", description: "Declarative row menu items appended as an actions column." },
168
+ { name: "toolbarActions", type: "ReactNode | (context) => ReactNode", defaultValue: "-", description: "Extra action cluster rendered beside visibility / refresh / export controls." },
169
+ { name: "bulkActions", type: "DataTableBulkAction<RowData>[]", defaultValue: "-", description: "Bulk actions shown when selected rows exist." },
170
+ { name: "rowActions", type: "(row, original) => DataTableRowAction<RowData>[]", defaultValue: "-", description: "Declarative row menu items appended as an actions column." },
171
+ { name: "onRefresh", type: "(context) => void", defaultValue: "-", description: "Callback used by the built-in refresh action." },
172
+ { name: "onExport", type: "(context) => void", defaultValue: "-", description: "Callback used by the built-in export action." },
154
173
  { name: "sorting", type: "SortingState", defaultValue: "[]", description: "Controlled sorting state.", example: "sorting={sorting}" },
155
174
  { name: "onSortingChange", type: "OnChangeFn<SortingState>", defaultValue: "-", description: "Called when sorting changes.", example: "onSortingChange={setSorting}" },
156
175
  { name: "columnVisibility", type: "VisibilityState", defaultValue: "{}", description: "Controlled column visibility map." },
@@ -164,10 +183,13 @@ export const componentApiSchemas: Record<string, ComponentApiSchema> = {
164
183
  { name: "onRowDoubleClick", type: "(row) => void", defaultValue: "-", description: "Optional double-click activation handler for denser operator workflows." },
165
184
  { name: "getRowDisabled", type: "(row) => boolean", defaultValue: "-", description: "Disable specific rows without removing them from the result set." },
166
185
  { name: "density", type: "'compact' | 'default' | 'comfortable'", defaultValue: "'default'", description: "Controls header and cell spacing.", example: "density=\"comfortable\"" },
167
- { name: "striped", type: "boolean", defaultValue: "false", description: "Adds zebra striping for long operational lists." },
168
- { name: "bordered", type: "boolean", defaultValue: "false", description: "Adds stronger cell and table borders." },
169
- { name: "stickyHeader", type: "boolean", defaultValue: "false", description: "Keeps headers pinned while the table scrolls." },
170
- { name: "isLoading", type: "boolean", defaultValue: "false", description: "Shows loading state." },
186
+ { name: "striped", type: "boolean", defaultValue: "false", description: "Adds zebra striping for long operational lists." },
187
+ { name: "bordered", type: "boolean", defaultValue: "false", description: "Adds stronger cell and table borders." },
188
+ { name: "stickyHeader", type: "boolean", defaultValue: "false", description: "Keeps headers pinned while the table scrolls." },
189
+ { name: "renderExpandedRow", type: "(row) => ReactNode", defaultValue: "-", description: "Renders an expanded row panel beneath a parent row." },
190
+ { name: "columnPinning", type: "ColumnPinningState", defaultValue: "-", description: "Controlled TanStack column pinning state." },
191
+ { name: "virtualization", type: "DataTableVirtualizationConfig | false", defaultValue: "-", description: "Enables virtualized row rendering for large datasets." },
192
+ { name: "isLoading", type: "boolean", defaultValue: "false", description: "Shows loading state." },
171
193
  { name: "loadingVariant", type: "'skeleton' | 'state'", defaultValue: "'skeleton'", description: "Choose between skeleton rows or a dedicated loading state block." },
172
194
  { name: "loadingState", type: "LoadingStateProps", defaultValue: "-", description: "Customizes the loading state when `loadingVariant=\"state\"`." },
173
195
  { name: "isError", type: "boolean", defaultValue: "false", description: "Switches the grid into an error fallback state." },
@@ -176,23 +198,136 @@ export const componentApiSchemas: Record<string, ComponentApiSchema> = {
176
198
  { name: "skeletonRows", type: "number", defaultValue: "6", description: "How many skeleton rows to render during loading." },
177
199
  { name: "cellFallback", type: "ReactNode", defaultValue: "\"-\"", description: "Value rendered when a cell returns empty content." },
178
200
  ],
179
- accessibility: ["Keep column headers readable and sortable targets large enough for pointer and keyboard use.", "Provide clear labels for row action menus, visibility controls, and bulk actions.", "When mobile cards replace the table, preserve the same row meaning and action availability."],
180
- },
181
- dialog: {
182
- slug: "dialog",
183
- source: "metadata",
184
- summary: "Focused overlay for short decisions, confirmations and contained edit flows.",
185
- props: [
186
- { name: "open", type: "boolean", defaultValue: "-", description: "Controlled open state." },
187
- { name: "defaultOpen", type: "boolean", defaultValue: "false", description: "Initial uncontrolled open state." },
188
- { name: "onOpenChange", type: "(open: boolean) => void", defaultValue: "-", description: "Called when the dialog opens or closes." },
189
- { name: "title", type: "ReactNode", defaultValue: "-", required: true, description: "Accessible dialog title." },
190
- { name: "description", type: "ReactNode", defaultValue: "-", description: "Supporting description for context." },
191
- { name: "children", type: "ReactNode", defaultValue: "-", description: "Dialog body content." },
192
- { name: "footer", type: "ReactNode", defaultValue: "-", description: "Action row for confirm/cancel buttons." },
193
- ],
194
- accessibility: ["Every dialog needs a title.", "Return focus to the trigger after close.", "Do not put long multi-step workflows in a small dialog."],
195
- },
201
+ accessibility: ["Keep column headers readable and sortable targets large enough for pointer and keyboard use.", "Provide clear labels for row action menus, visibility controls, and bulk actions.", "When mobile cards replace the table, preserve the same row meaning and action availability."],
202
+ },
203
+ "async-select": {
204
+ slug: "async-select",
205
+ source: "metadata",
206
+ summary: "Remote option picker for large, changing or server-filtered datasets. Prefer Select for small finite lists; use AsyncSelect when loading, hydration, create-option, min-search, cache, error and multi-select behavior are required.",
207
+ props: [
208
+ { name: "value", type: "string | string[]", defaultValue: "-", description: "Controlled selected value. Use an array when `isMulti` is true." },
209
+ { name: "isMulti", type: "boolean", defaultValue: "false", description: "Switches the component into multi-value mode with tag rendering and optional select-all behavior.", example: "isMulti" },
210
+ { name: "onValueChange", type: "(value, optionOrOptions) => void", defaultValue: "-", description: "Called with the next selected value and resolved option object(s)." },
211
+ { name: "loadOptions", type: "(search: string, signal?: AbortSignal) => Promise<Option[] | Group[]>", defaultValue: "-", required: true, description: "Async option loader. Respect the AbortSignal when forwarding to fetch." },
212
+ { name: "loadSelectedOption", type: "(value: string, signal?: AbortSignal) => Promise<Option | null>", defaultValue: "-", description: "Hydrates a single selected value when only an ID is stored." },
213
+ { name: "loadSelectedOptions", type: "(values: string[], signal?: AbortSignal) => Promise<Option[]>", defaultValue: "-", description: "Hydrates multiple selected values when only IDs are stored." },
214
+ { name: "defaultOptions", type: "Option[] | Group[]", defaultValue: "-", description: "Initial options shown before a search request resolves." },
215
+ { name: "cacheOptions", type: "boolean", defaultValue: "true", description: "Caches query results by normalized search text." },
216
+ { name: "cacheTtl", type: "number", defaultValue: "Infinity", description: "Maximum age for cached option results." },
217
+ { name: "debounceMs", type: "number", defaultValue: "250", description: "Delay before search requests are sent." },
218
+ { name: "minSearchLength", type: "number", defaultValue: "0", description: "Minimum typed characters before loading remote options." },
219
+ { name: "clearable", type: "boolean", defaultValue: "true", description: "Shows clear actions for selected value or tags." },
220
+ { name: "closeOnSelect", type: "boolean", defaultValue: "false", description: "Multi-select only. Closes the popup after selecting an option." },
221
+ { name: "maxSelected", type: "number", defaultValue: "-", description: "Multi-select only. Limits selected option count." },
222
+ { name: "maxVisibleTags", type: "number", defaultValue: "-", description: "Multi-select only. Collapses extra selected tags into an overflow label." },
223
+ { name: "showSelectAll", type: "boolean", defaultValue: "false", description: "Multi-select only. Adds a select-all-visible action." },
224
+ { name: "onCreateOption", type: "(search: string) => Promise<Option> | Option", defaultValue: "-", description: "Creates a selectable option from the current search query." },
225
+ { name: "showCreateOption", type: "(search: string, options: Option[]) => boolean", defaultValue: "-", description: "Controls when the create-option row should appear." },
226
+ { name: "createOptionLabel", type: "(search: string) => ReactNode", defaultValue: "-", description: "Custom label for the create-option action." },
227
+ { name: "labels", type: "AsyncSelectLabels", defaultValue: "-", description: "Centralized copy for placeholder, loading, error, empty, select-all and selected-count states." },
228
+ { name: "renderOption", type: "(option, state) => ReactNode", defaultValue: "-", description: "Custom option row renderer." },
229
+ { name: "renderValue", type: "(option) => ReactNode", defaultValue: "-", description: "Custom selected value renderer." },
230
+ { name: "renderTag", type: "(option, state) => ReactNode", defaultValue: "-", description: "Multi-select only. Custom selected tag renderer." },
231
+ { name: "renderLoading", type: "(state) => ReactNode", defaultValue: "-", description: "Custom loading state renderer." },
232
+ { name: "renderEmpty", type: "(state) => ReactNode", defaultValue: "-", description: "Custom empty state renderer." },
233
+ { name: "renderError", type: "(state) => ReactNode", defaultValue: "-", description: "Custom error state renderer." },
234
+ { name: "renderMinSearch", type: "(state) => ReactNode", defaultValue: "-", description: "Custom minimum-search state renderer." },
235
+ { name: "renderMaxSelected", type: "(state) => ReactNode", defaultValue: "-", description: "Custom max-selected state renderer." },
236
+ { name: "showSelectedDescription", type: "boolean", defaultValue: "true", description: "Shows option descriptions in selected value and tag surfaces." },
237
+ { name: "invalid", type: "boolean", defaultValue: "false", description: "Marks the trigger invalid and sets `aria-invalid`." },
238
+ ],
239
+ examples: [
240
+ { title: "Remote single select", code: "<AsyncSelect value={value} onValueChange={setValue} loadOptions={loadOptions} minSearchLength={2} />" },
241
+ { title: "Hydrated edit state", code: "<AsyncSelect value={ownerId} loadOptions={searchOwners} loadSelectedOption={loadOwnerById} />" },
242
+ { title: "Multi select with limit", code: "<AsyncSelect isMulti value={ids} onValueChange={setIds} loadOptions={loadTeams} maxSelected={3} showSelectAll />" },
243
+ { title: "Create missing option", code: "<AsyncSelect loadOptions={loadOptions} onCreateOption={createOption} createOptionLabel={(search) => `Create ${search}`} />" },
244
+ ],
245
+ accessibility: ["Keep async loading and error labels readable.", "Use min-search copy so users know why no request has been sent.", "Preserve keyboard access for clear, tag remove and select-all actions.", "Hydrate selected labels so edit forms do not expose raw IDs."],
246
+ },
247
+ "async-multi-select": {
248
+ slug: "async-multi-select",
249
+ source: "metadata",
250
+ summary: "Deprecated public alias for multi-value async selection. Prefer `AsyncSelect` with `isMulti` so single and multi remote selection share one mental model.",
251
+ props: [
252
+ { name: "isMulti", type: "true", defaultValue: "true", required: true, description: "Prefer passing this to `AsyncSelect` instead of importing the deprecated alias." },
253
+ { name: "value", type: "string[]", defaultValue: "[]", description: "Controlled selected values." },
254
+ { name: "onValueChange", type: "(values: string[], options: Option[]) => void", defaultValue: "-", description: "Called when selected values change." },
255
+ { name: "loadOptions", type: "(search: string, signal?: AbortSignal) => Promise<Option[] | Group[]>", defaultValue: "-", required: true, description: "Async option loader." },
256
+ { name: "loadSelectedOptions", type: "(values: string[], signal?: AbortSignal) => Promise<Option[]>", defaultValue: "-", description: "Hydrates selected values from stored IDs." },
257
+ { name: "maxSelected", type: "number", defaultValue: "-", description: "Limits selected option count." },
258
+ { name: "showSelectAll", type: "boolean", defaultValue: "false", description: "Adds a select-all-visible action." },
259
+ { name: "maxVisibleTags", type: "number", defaultValue: "-", description: "Collapses extra selected tags." },
260
+ ],
261
+ examples: [
262
+ { title: "Preferred API", code: "<AsyncSelect isMulti value={teamIds} onValueChange={setTeamIds} loadOptions={loadTeams} />" },
263
+ ],
264
+ accessibility: ["Make tag remove controls keyboard reachable.", "Announce selected count when selections are constrained.", "Keep deprecated alias docs pointed to the preferred API."],
265
+ },
266
+ "app-sidebar": {
267
+ slug: "app-sidebar",
268
+ source: "metadata",
269
+ summary: "Reusable dashboard navigation shell for admin products. Keep route state in one items array, render framework links through `renderLink`, and use the built-in active indicators, collapsed rail, mobile drawer and footer account slots instead of rebuilding sidebar chrome per app.",
270
+ props: [
271
+ { name: "items", type: "SidebarItem[]", defaultValue: "[]", required: true, description: "Primary navigation tree with labels, hrefs, badges, active state, disabled state and nested children." },
272
+ { name: "header", type: "ReactNode", defaultValue: "-", description: "Workspace, product or tenant identity area." },
273
+ { name: "footer", type: "ReactNode", defaultValue: "-", description: "Custom footer content rendered under account and secondary actions." },
274
+ { name: "footerAccount", type: "{ label; description?; avatar?; href?; onSelect? }", defaultValue: "-", description: "Account/workspace switcher surface rendered in the footer." },
275
+ { name: "secondaryActions", type: "SidebarItem[]", defaultValue: "[]", description: "Support, billing, help or logout-like actions below primary navigation." },
276
+ { name: "railItems", type: "SidebarItem[]", defaultValue: "[]", description: "Compact rail actions shown when the sidebar is collapsed." },
277
+ { name: "collapsed", type: "boolean", defaultValue: "false", description: "Switches desktop sidebar into icon rail mode." },
278
+ { name: "width", type: "CSSProperties['width']", defaultValue: "'18rem'", description: "Expanded desktop sidebar width." },
279
+ { name: "collapsedWidth", type: "CSSProperties['width']", defaultValue: "'4.75rem'", description: "Collapsed desktop rail width." },
280
+ { name: "itemSize", type: "'sm' | 'md' | 'lg'", defaultValue: "'md'", description: "Controls item density for compact admin apps or comfortable product shells." },
281
+ { name: "activeIndicator", type: "'none' | 'bar' | 'pill'", defaultValue: "'bar'", description: "Controls how the active route is emphasized." },
282
+ { name: "showSectionLabels", type: "boolean", defaultValue: "true", description: "Shows top-level section labels from item.sectionLabel." },
283
+ { name: "tooltipOnCollapsed", type: "boolean", defaultValue: "true", description: "Shows item labels in tooltips while collapsed." },
284
+ { name: "navigationLabel", type: "string", defaultValue: "'Primary navigation'", description: "Accessible label for the navigation landmark." },
285
+ { name: "responsive", type: "boolean", defaultValue: "true", description: "Enables mobile drawer behavior at the configured breakpoint." },
286
+ { name: "mobileBreakpoint", type: "number", defaultValue: "1024", description: "Viewport width where the sidebar becomes a mobile drawer." },
287
+ { name: "mobileOpen", type: "boolean", defaultValue: "-", description: "Controlled mobile drawer open state." },
288
+ { name: "defaultMobileOpen", type: "boolean", defaultValue: "false", description: "Initial uncontrolled mobile drawer open state." },
289
+ { name: "onMobileOpenChange", type: "(open: boolean) => void", defaultValue: "-", description: "Called when the mobile drawer opens or closes." },
290
+ { name: "mobileTitle", type: "ReactNode", defaultValue: "-", description: "Title rendered in the mobile drawer header." },
291
+ { name: "mobileDescription", type: "ReactNode", defaultValue: "-", description: "Short helper copy rendered in the mobile drawer header." },
292
+ { name: "showMobileToggle", type: "boolean", defaultValue: "true", description: "Controls whether the built-in mobile trigger renders." },
293
+ { name: "closeOnSelect", type: "boolean", defaultValue: "true", description: "Closes the mobile drawer after a navigation item is selected." },
294
+ { name: "renderMobileToggle", type: "({ open, setOpen }) => ReactNode", defaultValue: "-", description: "Replaces the built-in mobile trigger." },
295
+ { name: "onItemSelect", type: "(item: SidebarItem) => void", defaultValue: "-", description: "Global callback fired after any sidebar item is selected." },
296
+ { name: "renderItem", type: "(item, state) => ReactNode", defaultValue: "-", description: "Fully custom item renderer for advanced product shells." },
297
+ { name: "renderLink", type: "(props) => ReactNode", defaultValue: "-", description: "Adapter for React Router Link, Next Link, Remix Link or any framework router.", example: "renderLink={({ href, ...props }) => <Link {...props} to={href} />}" },
298
+ ],
299
+ examples: [
300
+ { title: "React Router links", code: "<Sidebar items={items} renderLink={({ href = '/', ...props }) => <Link {...props} to={href} />} />" },
301
+ { title: "Collapsed rail", code: "<Sidebar collapsed items={items} railItems={railItems} footerAccount={account} />" },
302
+ { title: "Mobile drawer", code: "<Sidebar responsive mobileTitle=\"Workspace\" mobileDescription=\"Navigate admin sections\" items={items} />" },
303
+ { title: "Admin density", code: "<Sidebar itemSize=\"sm\" activeIndicator=\"bar\" showSectionLabels={false} items={items} />" },
304
+ ],
305
+ accessibility: ["Use `navigationLabel` that matches the product language.", "Keep active route state explicit through `item.active` or child active state.", "Use `renderLink` for client routing instead of raw anchors in React Router or Next apps.", "Collapsed sidebars need icons or tooltips that still identify each route.", "Mobile drawer should close on selection unless the route opens another nested group."],
306
+ },
307
+ dialog: {
308
+ slug: "dialog",
309
+ source: "metadata",
310
+ summary: "Focused overlay for short decisions, confirmations and contained edit flows. Use DialogBody for scrollable content so footer actions stay aligned and visible.",
311
+ props: [
312
+ { name: "open", type: "boolean", defaultValue: "-", description: "Controlled open state." },
313
+ { name: "defaultOpen", type: "boolean", defaultValue: "false", description: "Initial uncontrolled open state." },
314
+ { name: "onOpenChange", type: "(open: boolean) => void", defaultValue: "-", description: "Called when the dialog opens or closes." },
315
+ { name: "size", type: "'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full'", defaultValue: "'md'", description: "Controls DialogContent width and full-screen-like layout." },
316
+ { name: "surface", type: "'default' | 'plain'", defaultValue: "'default'", description: "Controls whether the content uses the default dialog surface or a plain custom wrapper." },
317
+ { name: "showCloseButton", type: "boolean", defaultValue: "true", description: "Controls the top-right close button on DialogContent or footer close action on DialogFooter." },
318
+ { name: "title", type: "ReactNode", defaultValue: "-", required: true, description: "Accessible dialog title." },
319
+ { name: "description", type: "ReactNode", defaultValue: "-", description: "Supporting description for context." },
320
+ { name: "DialogBody", type: "Component", defaultValue: "-", description: "Scrollable body area for long content, forms, details and preview blocks." },
321
+ { name: "DialogFooter", type: "Component", defaultValue: "-", description: "Sticky-safe action row. Buttons stay inline-flex and wrap instead of stacking icons above text." },
322
+ { name: "children", type: "ReactNode", defaultValue: "-", description: "Dialog composition: header, body and footer." },
323
+ ],
324
+ examples: [
325
+ { title: "Confirm dialog", code: "<Dialog><DialogTrigger render={<Button />}>Publish</DialogTrigger><DialogContent><DialogHeader><DialogTitle>Publish update</DialogTitle></DialogHeader><DialogFooter><Button>Publish</Button></DialogFooter></DialogContent></Dialog>" },
326
+ { title: "Form dialog", code: "<DialogContent size=\"lg\"><DialogHeader /><DialogBody>...</DialogBody><DialogFooter>...</DialogFooter></DialogContent>" },
327
+ { title: "Destructive dialog", code: "<DialogContent size=\"sm\"><DialogHeader /><DialogBody /><DialogFooter><Button variant=\"destructive\">Deactivate</Button></DialogFooter></DialogContent>" },
328
+ ],
329
+ accessibility: ["Every dialog needs a title.", "Return focus to the trigger after close.", "Use DialogBody for long content instead of scrolling the whole modal.", "Keep footer actions outcome-specific and keyboard reachable.", "Do not put long multi-step workflows in a small dialog."],
330
+ },
196
331
  "date-picker": {
197
332
  slug: "date-picker",
198
333
  source: "metadata",
@@ -208,9 +343,9 @@ export const componentApiSchemas: Record<string, ComponentApiSchema> = {
208
343
  ],
209
344
  accessibility: ["Clarify date format near text inputs.", "Disable unavailable dates rather than hiding them.", "Define timezone assumptions before submitting values."],
210
345
  },
211
- "form-input": {
212
- slug: "form-input",
213
- source: "metadata",
346
+ "form-input": {
347
+ slug: "form-input",
348
+ source: "metadata",
214
349
  summary: "React Hook Form wrapper for Input with label, helper text, validation and error display.",
215
350
  props: [
216
351
  { name: "control", type: "Control<TFieldValues>", defaultValue: "-", required: true, description: "RHF control instance from useForm()." },
@@ -221,11 +356,106 @@ export const componentApiSchemas: Record<string, ComponentApiSchema> = {
221
356
  { name: "required", type: "boolean | '*'", defaultValue: "false", description: "Marks the field as required in the UI." },
222
357
  { name: "disabled", type: "boolean", defaultValue: "false", description: "Disables the field and prevents editing." },
223
358
  { name: "error", type: "string | FieldError | null", defaultValue: "-", description: "Optional error override." },
224
- ],
225
- accessibility: ["Label must remain visible.", "Error text should be connected to the field.", "Required state should be visual and semantic."],
226
- },
227
- }
359
+ ],
360
+ accessibility: ["Label must remain visible.", "Error text should be connected to the field.", "Required state should be visual and semantic."],
361
+ },
362
+ wizard: {
363
+ slug: "wizard",
364
+ source: "metadata",
365
+ summary: "Controlled multi-step workflow surface for onboarding, setup and review flows. Keep the current step in state so validation, route sync and final submit behavior stay predictable.",
366
+ props: [
367
+ { name: "steps", type: "Array<{ id: string; title: ReactNode; description?: ReactNode; disabled?: boolean; completed?: boolean }>", defaultValue: "[]", required: true, description: "Ordered steps shown in the stepper and used by navigation controls." },
368
+ { name: "currentStep", type: "string", defaultValue: "-", required: true, description: "Controlled active step id.", example: "currentStep={step}" },
369
+ { name: "onStepChange", type: "(stepId: string) => void", defaultValue: "-", description: "Called when a clickable step is selected.", example: "onStepChange={setStep}" },
370
+ { name: "onNext", type: "() => void", defaultValue: "-", description: "Called by the primary next action before the final step." },
371
+ { name: "onPrevious", type: "() => void", defaultValue: "-", description: "Called by the previous action when the user can go back." },
372
+ { name: "onFinish", type: "() => void", defaultValue: "-", description: "Called by the final submit action on the last step." },
373
+ { name: "nextLabel", type: "ReactNode", defaultValue: "'Next'", description: "Label for the forward action." },
374
+ { name: "previousLabel", type: "ReactNode", defaultValue: "'Back'", description: "Label for the backward action." },
375
+ { name: "finishLabel", type: "ReactNode", defaultValue: "'Finish'", description: "Label for the final action." },
376
+ { name: "footer", type: "ReactNode", defaultValue: "-", description: "Optional custom footer when the default Back/Next/Finish row is not enough." },
377
+ { name: "children", type: "ReactNode", defaultValue: "-", description: "Current step content rendered between the stepper and footer." },
378
+ { name: "className", type: "string", defaultValue: "-", description: "Optional class override for local spacing and layout composition." },
379
+ ],
380
+ examples: [
381
+ { title: "Controlled wizard", code: "<Wizard steps={steps} currentStep={step} onStepChange={setStep} onNext={goNext} onPrevious={goBack} onFinish={submit}>...</Wizard>" },
382
+ { title: "Validation-gated step", code: "<Wizard steps={steps.map((item) => ({ ...item, disabled: item.id === 'finish' && !isValid }))} currentStep={step} />" },
383
+ { title: "Custom action labels", code: "<Wizard nextLabel=\"Continue setup\" previousLabel=\"Edit previous\" finishLabel=\"Publish\" />" },
384
+ ],
385
+ accessibility: ["Keep step titles short and descriptive.", "Disable future steps when validation has not passed.", "Move focus to the new step content after step changes in long forms.", "Keep final submit copy specific to the workflow."],
386
+ },
387
+ stepper: {
388
+ slug: "stepper",
389
+ source: "metadata",
390
+ summary: "Progress navigation for multi-step forms and setup flows. Use it when the workflow needs visible step context without taking over the page content.",
391
+ props: [
392
+ { name: "steps", type: "Array<{ id: string; title: ReactNode; description?: ReactNode; disabled?: boolean; completed?: boolean }>", defaultValue: "[]", required: true, description: "Ordered step list rendered as the progress navigation." },
393
+ { name: "currentStep", type: "string", defaultValue: "-", required: true, description: "Controlled active step id." },
394
+ { name: "onStepChange", type: "(stepId: string) => void", defaultValue: "-", description: "Called when an enabled step is selected." },
395
+ { name: "orientation", type: "'horizontal' | 'vertical'", defaultValue: "'horizontal'", description: "Controls whether steps render in a row or stacked column." },
396
+ { name: "size", type: "'sm' | 'default' | 'lg'", defaultValue: "'default'", description: "Controls indicator and label density." },
397
+ { name: "showDescriptions", type: "boolean", defaultValue: "true", description: "Shows or hides supporting step descriptions." },
398
+ { name: "className", type: "string", defaultValue: "-", description: "Optional class override for local layout composition." },
399
+ ],
400
+ examples: [
401
+ { title: "Horizontal setup stepper", code: "<Stepper steps={steps} currentStep={step} onStepChange={setStep} />" },
402
+ { title: "Vertical review stepper", code: "<Stepper orientation=\"vertical\" steps={steps} currentStep={step} />" },
403
+ { title: "Compact labels", code: "<Stepper size=\"sm\" showDescriptions={false} steps={steps} currentStep={step} />" },
404
+ ],
405
+ accessibility: ["Use disabled steps when users cannot jump ahead.", "Keep active and completed states visually distinct.", "Do not rely on color alone to communicate progress."],
406
+ },
407
+ }
228
408
 
229
- export function getComponentApiSchema(slug: string): ComponentApiSchema | undefined {
230
- return componentApiSchemas[slug]
231
- }
409
+ function slugToTitle(slug: string) {
410
+ return slug
411
+ .split("-")
412
+ .filter(Boolean)
413
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
414
+ .join(" ")
415
+ }
416
+
417
+ function getFallbackComponentApiSchema(slug: string): ComponentApiSchema {
418
+ const title = slugToTitle(slug)
419
+
420
+ return {
421
+ slug,
422
+ source: "manual-fallback",
423
+ summary: `${title} is documented as a source-copy surface. Start with the default example, keep the public props small, then customize the local source only where the product needs it.`,
424
+ props: [
425
+ {
426
+ name: "className",
427
+ type: "string",
428
+ defaultValue: "-",
429
+ description: "Optional class override for layout, spacing or local product styling.",
430
+ },
431
+ {
432
+ name: "children",
433
+ type: "ReactNode",
434
+ defaultValue: "-",
435
+ description: "Optional composed content, label or slot content when the component supports composition.",
436
+ },
437
+ {
438
+ name: "aria-label",
439
+ type: "string",
440
+ defaultValue: "-",
441
+ description: "Accessible label for icon-only, compact or non-text usage.",
442
+ notes: "Use when visible text does not fully describe the action or content.",
443
+ },
444
+ ],
445
+ examples: [
446
+ {
447
+ title: `${title} default`,
448
+ code: `<${title.replaceAll(" ", "")} className="w-full" />`,
449
+ },
450
+ ],
451
+ accessibility: [
452
+ "Keep visible focus states when the surface is interactive.",
453
+ "Provide readable labels for icon-only or compact controls.",
454
+ "Preserve semantic structure when customizing copied source.",
455
+ ],
456
+ }
457
+ }
458
+
459
+ export function getComponentApiSchema(slug: string): ComponentApiSchema | undefined {
460
+ return componentApiSchemas[slug] ?? getFallbackComponentApiSchema(slug)
461
+ }
@@ -21,7 +21,7 @@ export function Demo() {
21
21
  "clearable-input": `import { Input } from "tembro"
22
22
 
23
23
  export function Demo() {
24
- return <Input defaultValue="Azamat UI" placeholder="Search customer" clearable />
24
+ return <Input defaultValue="Tembro" placeholder="Search customer" clearable />
25
25
  }`,
26
26
  "tag-input": `import { TagInput } from "tembro"
27
27