@braedonsaunders/appkit-ui 0.2.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.
Files changed (300) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +11 -0
  3. package/account-menu.d.ts +49 -0
  4. package/account-menu.d.ts.map +1 -0
  5. package/account-menu.js +66 -0
  6. package/account-menu.js.map +1 -0
  7. package/alert.d.ts +13 -0
  8. package/alert.d.ts.map +1 -0
  9. package/alert.js +22 -0
  10. package/alert.js.map +1 -0
  11. package/animated-number.d.ts +13 -0
  12. package/animated-number.d.ts.map +1 -0
  13. package/animated-number.js +53 -0
  14. package/animated-number.js.map +1 -0
  15. package/api-reference.d.ts +44 -0
  16. package/api-reference.d.ts.map +1 -0
  17. package/api-reference.js +82 -0
  18. package/api-reference.js.map +1 -0
  19. package/app-shell.d.ts +39 -0
  20. package/app-shell.d.ts.map +1 -0
  21. package/app-shell.js +21 -0
  22. package/app-shell.js.map +1 -0
  23. package/app-sidebar.d.ts +22 -0
  24. package/app-sidebar.d.ts.map +1 -0
  25. package/app-sidebar.js +30 -0
  26. package/app-sidebar.js.map +1 -0
  27. package/avatar.d.ts +9 -0
  28. package/avatar.d.ts.map +1 -0
  29. package/avatar.js +18 -0
  30. package/avatar.js.map +1 -0
  31. package/badge.d.ts +9 -0
  32. package/badge.d.ts.map +1 -0
  33. package/badge.js +23 -0
  34. package/badge.js.map +1 -0
  35. package/button.d.ts +24 -0
  36. package/button.d.ts.map +1 -0
  37. package/button.js +50 -0
  38. package/button.js.map +1 -0
  39. package/card.d.ts +21 -0
  40. package/card.d.ts.map +1 -0
  41. package/card.js +36 -0
  42. package/card.js.map +1 -0
  43. package/checkbox.d.ts +4 -0
  44. package/checkbox.d.ts.map +1 -0
  45. package/checkbox.js +6 -0
  46. package/checkbox.js.map +1 -0
  47. package/confirm-dialog.d.ts +23 -0
  48. package/confirm-dialog.d.ts.map +1 -0
  49. package/confirm-dialog.js +67 -0
  50. package/confirm-dialog.js.map +1 -0
  51. package/context-menu.d.ts +47 -0
  52. package/context-menu.d.ts.map +1 -0
  53. package/context-menu.js +90 -0
  54. package/context-menu.js.map +1 -0
  55. package/dialog.d.ts +25 -0
  56. package/dialog.d.ts.map +1 -0
  57. package/dialog.js +89 -0
  58. package/dialog.js.map +1 -0
  59. package/document-title.d.ts +11 -0
  60. package/document-title.d.ts.map +1 -0
  61. package/document-title.js +17 -0
  62. package/document-title.js.map +1 -0
  63. package/drawer.d.ts +63 -0
  64. package/drawer.d.ts.map +1 -0
  65. package/drawer.js +188 -0
  66. package/drawer.js.map +1 -0
  67. package/empty-state.d.ts +10 -0
  68. package/empty-state.d.ts.map +1 -0
  69. package/empty-state.js +7 -0
  70. package/empty-state.js.map +1 -0
  71. package/file-uploader.d.ts +67 -0
  72. package/file-uploader.d.ts.map +1 -0
  73. package/file-uploader.js +156 -0
  74. package/file-uploader.js.map +1 -0
  75. package/filter-chips.d.ts +47 -0
  76. package/filter-chips.d.ts.map +1 -0
  77. package/filter-chips.js +62 -0
  78. package/filter-chips.js.map +1 -0
  79. package/global-search.d.ts +41 -0
  80. package/global-search.d.ts.map +1 -0
  81. package/global-search.js +116 -0
  82. package/global-search.js.map +1 -0
  83. package/index.d.ts +70 -0
  84. package/index.d.ts.map +1 -0
  85. package/index.js +70 -0
  86. package/index.js.map +1 -0
  87. package/input.d.ts +9 -0
  88. package/input.d.ts.map +1 -0
  89. package/input.js +11 -0
  90. package/input.js.map +1 -0
  91. package/label.d.ts +3 -0
  92. package/label.d.ts.map +1 -0
  93. package/label.js +6 -0
  94. package/label.js.map +1 -0
  95. package/line-grid.d.ts +48 -0
  96. package/line-grid.d.ts.map +1 -0
  97. package/line-grid.js +155 -0
  98. package/line-grid.js.map +1 -0
  99. package/link-context.d.ts +44 -0
  100. package/link-context.d.ts.map +1 -0
  101. package/link-context.js +32 -0
  102. package/link-context.js.map +1 -0
  103. package/list-nav.d.ts +28 -0
  104. package/list-nav.d.ts.map +1 -0
  105. package/list-nav.js +41 -0
  106. package/list-nav.js.map +1 -0
  107. package/list-pagination.d.ts +27 -0
  108. package/list-pagination.d.ts.map +1 -0
  109. package/list-pagination.js +43 -0
  110. package/list-pagination.js.map +1 -0
  111. package/list-params.d.ts +41 -0
  112. package/list-params.d.ts.map +1 -0
  113. package/list-params.js +84 -0
  114. package/list-params.js.map +1 -0
  115. package/list-search-input-state.d.ts +13 -0
  116. package/list-search-input-state.d.ts.map +1 -0
  117. package/list-search-input-state.js +27 -0
  118. package/list-search-input-state.js.map +1 -0
  119. package/list-search-input.d.ts +16 -0
  120. package/list-search-input.d.ts.map +1 -0
  121. package/list-search-input.js +51 -0
  122. package/list-search-input.js.map +1 -0
  123. package/mobile-tab-bar.d.ts +14 -0
  124. package/mobile-tab-bar.d.ts.map +1 -0
  125. package/mobile-tab-bar.js +25 -0
  126. package/mobile-tab-bar.js.map +1 -0
  127. package/multi-select.d.ts +32 -0
  128. package/multi-select.d.ts.map +1 -0
  129. package/multi-select.js +52 -0
  130. package/multi-select.js.map +1 -0
  131. package/nav-overflow.d.ts +12 -0
  132. package/nav-overflow.d.ts.map +1 -0
  133. package/nav-overflow.js +30 -0
  134. package/nav-overflow.js.map +1 -0
  135. package/navigation-config-editor.d.ts +26 -0
  136. package/navigation-config-editor.d.ts.map +1 -0
  137. package/navigation-config-editor.js +57 -0
  138. package/navigation-config-editor.js.map +1 -0
  139. package/navigation-config.d.ts +48 -0
  140. package/navigation-config.d.ts.map +1 -0
  141. package/navigation-config.js +75 -0
  142. package/navigation-config.js.map +1 -0
  143. package/navigation-mode.d.ts +16 -0
  144. package/navigation-mode.d.ts.map +1 -0
  145. package/navigation-mode.js +30 -0
  146. package/navigation-mode.js.map +1 -0
  147. package/notifications-bell.d.ts +22 -0
  148. package/notifications-bell.d.ts.map +1 -0
  149. package/notifications-bell.js +30 -0
  150. package/notifications-bell.js.map +1 -0
  151. package/org-chart.d.ts +85 -0
  152. package/org-chart.d.ts.map +1 -0
  153. package/org-chart.js +193 -0
  154. package/org-chart.js.map +1 -0
  155. package/package.json +61 -0
  156. package/page-header.d.ts +32 -0
  157. package/page-header.d.ts.map +1 -0
  158. package/page-header.js +14 -0
  159. package/page-header.js.map +1 -0
  160. package/page-layout.d.ts +29 -0
  161. package/page-layout.d.ts.map +1 -0
  162. package/page-layout.js +23 -0
  163. package/page-layout.js.map +1 -0
  164. package/page-transition.d.ts +13 -0
  165. package/page-transition.d.ts.map +1 -0
  166. package/page-transition.js +14 -0
  167. package/page-transition.js.map +1 -0
  168. package/paged-table.d.ts +54 -0
  169. package/paged-table.d.ts.map +1 -0
  170. package/paged-table.js +74 -0
  171. package/paged-table.js.map +1 -0
  172. package/popover.d.ts +16 -0
  173. package/popover.d.ts.map +1 -0
  174. package/popover.js +99 -0
  175. package/popover.js.map +1 -0
  176. package/progress.d.ts +7 -0
  177. package/progress.d.ts.map +1 -0
  178. package/progress.js +8 -0
  179. package/progress.js.map +1 -0
  180. package/prompt-dialog.d.ts +21 -0
  181. package/prompt-dialog.d.ts.map +1 -0
  182. package/prompt-dialog.js +109 -0
  183. package/prompt-dialog.js.map +1 -0
  184. package/record-list.d.ts +76 -0
  185. package/record-list.d.ts.map +1 -0
  186. package/record-list.js +51 -0
  187. package/record-list.js.map +1 -0
  188. package/search-select.d.ts +42 -0
  189. package/search-select.d.ts.map +1 -0
  190. package/search-select.js +194 -0
  191. package/search-select.js.map +1 -0
  192. package/select-options.d.ts +16 -0
  193. package/select-options.d.ts.map +1 -0
  194. package/select-options.js +119 -0
  195. package/select-options.js.map +1 -0
  196. package/select.d.ts +24 -0
  197. package/select.d.ts.map +1 -0
  198. package/select.js +65 -0
  199. package/select.js.map +1 -0
  200. package/separator.d.ts +6 -0
  201. package/separator.d.ts.map +1 -0
  202. package/separator.js +7 -0
  203. package/separator.js.map +1 -0
  204. package/settings-layout.d.ts +79 -0
  205. package/settings-layout.d.ts.map +1 -0
  206. package/settings-layout.js +74 -0
  207. package/settings-layout.js.map +1 -0
  208. package/sidebar-nav.d.ts +67 -0
  209. package/sidebar-nav.d.ts.map +1 -0
  210. package/sidebar-nav.js +291 -0
  211. package/sidebar-nav.js.map +1 -0
  212. package/signature-pad.d.ts +16 -0
  213. package/signature-pad.d.ts.map +1 -0
  214. package/signature-pad.js +355 -0
  215. package/signature-pad.js.map +1 -0
  216. package/skeleton.d.ts +9 -0
  217. package/skeleton.d.ts.map +1 -0
  218. package/skeleton.js +12 -0
  219. package/skeleton.js.map +1 -0
  220. package/slider.d.ts +13 -0
  221. package/slider.d.ts.map +1 -0
  222. package/slider.js +15 -0
  223. package/slider.js.map +1 -0
  224. package/sortable-th.d.ts +28 -0
  225. package/sortable-th.d.ts.map +1 -0
  226. package/sortable-th.js +35 -0
  227. package/sortable-th.js.map +1 -0
  228. package/sparkline.d.ts +16 -0
  229. package/sparkline.d.ts.map +1 -0
  230. package/sparkline.js +46 -0
  231. package/sparkline.js.map +1 -0
  232. package/spinner.d.ts +11 -0
  233. package/spinner.d.ts.map +1 -0
  234. package/spinner.js +11 -0
  235. package/spinner.js.map +1 -0
  236. package/styles.css +166 -0
  237. package/subtab-nav.d.ts +17 -0
  238. package/subtab-nav.d.ts.map +1 -0
  239. package/subtab-nav.js +11 -0
  240. package/subtab-nav.js.map +1 -0
  241. package/switch.d.ts +9 -0
  242. package/switch.d.ts.map +1 -0
  243. package/switch.js +11 -0
  244. package/switch.js.map +1 -0
  245. package/tab-content.d.ts +24 -0
  246. package/tab-content.d.ts.map +1 -0
  247. package/tab-content.js +26 -0
  248. package/tab-content.js.map +1 -0
  249. package/table.d.ts +28 -0
  250. package/table.d.ts.map +1 -0
  251. package/table.js +21 -0
  252. package/table.js.map +1 -0
  253. package/tabs.d.ts +18 -0
  254. package/tabs.d.ts.map +1 -0
  255. package/tabs.js +18 -0
  256. package/tabs.js.map +1 -0
  257. package/text-context.d.ts +9 -0
  258. package/text-context.d.ts.map +1 -0
  259. package/text-context.js +12 -0
  260. package/text-context.js.map +1 -0
  261. package/textarea.d.ts +4 -0
  262. package/textarea.d.ts.map +1 -0
  263. package/textarea.js +6 -0
  264. package/textarea.js.map +1 -0
  265. package/theme-script.d.ts +3 -0
  266. package/theme-script.d.ts.map +1 -0
  267. package/theme-script.js +6 -0
  268. package/theme-script.js.map +1 -0
  269. package/theme.d.ts +28 -0
  270. package/theme.d.ts.map +1 -0
  271. package/theme.js +92 -0
  272. package/theme.js.map +1 -0
  273. package/toast.d.ts +52 -0
  274. package/toast.d.ts.map +1 -0
  275. package/toast.js +178 -0
  276. package/toast.js.map +1 -0
  277. package/tooltip.d.ts +12 -0
  278. package/tooltip.d.ts.map +1 -0
  279. package/tooltip.js +14 -0
  280. package/tooltip.js.map +1 -0
  281. package/top-nav.d.ts +18 -0
  282. package/top-nav.d.ts.map +1 -0
  283. package/top-nav.js +160 -0
  284. package/top-nav.js.map +1 -0
  285. package/upload-limits.d.ts +4 -0
  286. package/upload-limits.d.ts.map +1 -0
  287. package/upload-limits.js +21 -0
  288. package/upload-limits.js.map +1 -0
  289. package/use-element-size.d.ts +7 -0
  290. package/use-element-size.d.ts.map +1 -0
  291. package/use-element-size.js +21 -0
  292. package/use-element-size.js.map +1 -0
  293. package/utils.d.ts +4 -0
  294. package/utils.d.ts.map +1 -0
  295. package/utils.js +7 -0
  296. package/utils.js.map +1 -0
  297. package/workspace-launcher.d.ts +50 -0
  298. package/workspace-launcher.d.ts.map +1 -0
  299. package/workspace-launcher.js +127 -0
  300. package/workspace-launcher.js.map +1 -0
@@ -0,0 +1,119 @@
1
+ import * as React from 'react';
2
+ export function selectChildrenEqual(left, right) {
3
+ return (left.placeholder === right.placeholder &&
4
+ left.emptyLabel === right.emptyLabel &&
5
+ left.clearable === right.clearable &&
6
+ left.options.length === right.options.length &&
7
+ left.options.every((option, index) => {
8
+ const other = right.options[index];
9
+ return (option.value === other?.value &&
10
+ option.label === other.label &&
11
+ option.disabled === other.disabled &&
12
+ option.group === other.group);
13
+ }));
14
+ }
15
+ /** Read the resolved option tree after React components have rendered inside
16
+ * the hidden native select. This covers option-producing components, which
17
+ * cannot be evaluated safely while parsing the original ReactNode tree. */
18
+ export function parseNativeSelect(select) {
19
+ const options = [];
20
+ let placeholder;
21
+ let emptyLabel;
22
+ let clearable = false;
23
+ let seenAny = false;
24
+ for (const option of select.options) {
25
+ const label = option.textContent?.trim() || option.label || option.value;
26
+ if (option.value === '' && !seenAny) {
27
+ placeholder = label || undefined;
28
+ emptyLabel = label || 'None';
29
+ clearable = !option.disabled;
30
+ seenAny = true;
31
+ continue;
32
+ }
33
+ seenAny = true;
34
+ if (option.hidden)
35
+ continue;
36
+ const parent = option.parentElement;
37
+ const inGroup = parent instanceof HTMLOptGroupElement;
38
+ options.push({
39
+ value: option.value,
40
+ label: label || option.value,
41
+ disabled: option.disabled || (inGroup && parent.disabled),
42
+ group: inGroup ? parent.label : undefined,
43
+ });
44
+ }
45
+ return { options, placeholder, clearable, emptyLabel };
46
+ }
47
+ function textOf(node) {
48
+ if (node == null || node === false || node === true)
49
+ return '';
50
+ if (typeof node === 'string' || typeof node === 'number')
51
+ return String(node);
52
+ if (Array.isArray(node))
53
+ return node.map(textOf).join('');
54
+ if (React.isValidElement(node)) {
55
+ const props = node.props;
56
+ // Translation and conditional-render helpers are intentionally transparent
57
+ // React components. Their rendered content is carried in `value`, not
58
+ // `children`, so read it when deriving the visible typeahead label.
59
+ return textOf(props.children ?? (typeof node.type !== 'string' ? props.value : undefined));
60
+ }
61
+ return '';
62
+ }
63
+ // Flatten <option>/<optgroup> children into a SelectOption[] for the typeahead.
64
+ // A leading <option value=""> becomes the placeholder (greyed); if it isn't
65
+ // disabled it also makes the field clearable back to "".
66
+ export function parseSelectChildren(children) {
67
+ const options = [];
68
+ let placeholder;
69
+ let emptyLabel;
70
+ let clearable = false;
71
+ let seenAny = false;
72
+ const pushOption = (el, group) => {
73
+ const props = el.props;
74
+ const label = textOf(props.children);
75
+ const value = props.value != null ? String(props.value) : label;
76
+ if (value === '' && !seenAny) {
77
+ placeholder = label || undefined;
78
+ emptyLabel = label || 'None';
79
+ clearable = !props.disabled;
80
+ seenAny = true;
81
+ return;
82
+ }
83
+ seenAny = true;
84
+ if (props.hidden)
85
+ return;
86
+ options.push({ value, label: label || value, disabled: !!props.disabled, group });
87
+ };
88
+ const walk = (nodes) => {
89
+ React.Children.forEach(nodes, (child) => {
90
+ if (!React.isValidElement(child))
91
+ return;
92
+ if (child.type === React.Fragment) {
93
+ walk(child.props.children);
94
+ }
95
+ else if (child.type === 'optgroup') {
96
+ const props = child.props;
97
+ React.Children.forEach(props.children, (option) => {
98
+ if (React.isValidElement(option) && option.type === 'option') {
99
+ pushOption(option, props.label);
100
+ }
101
+ });
102
+ }
103
+ else if (child.type === 'option') {
104
+ pushOption(child);
105
+ }
106
+ else if (typeof child.type !== 'string') {
107
+ // Server/client translation helpers and conditional wrappers can sit
108
+ // between Select and its option elements. Native <select> rendering
109
+ // handles those wrappers after React resolves them, but the custom
110
+ // typeahead must unwrap their ReactNode payload itself.
111
+ const props = child.props;
112
+ walk(props.children ?? props.value);
113
+ }
114
+ });
115
+ };
116
+ walk(children);
117
+ return { options, placeholder, clearable, emptyLabel };
118
+ }
119
+ //# sourceMappingURL=select-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select-options.js","sourceRoot":"","sources":["../src/select-options.ts"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react'\nimport type { SelectOption } from './search-select'\n\ntype ParsedSelectChildren = {\n options: SelectOption[]\n placeholder?: string\n clearable: boolean\n emptyLabel?: string\n}\n\nexport function selectChildrenEqual(\n left: ParsedSelectChildren,\n right: ParsedSelectChildren,\n): boolean {\n return (\n left.placeholder === right.placeholder &&\n left.emptyLabel === right.emptyLabel &&\n left.clearable === right.clearable &&\n left.options.length === right.options.length &&\n left.options.every((option, index) => {\n const other = right.options[index]\n return (\n option.value === other?.value &&\n option.label === other.label &&\n option.disabled === other.disabled &&\n option.group === other.group\n )\n })\n )\n}\n\n/** Read the resolved option tree after React components have rendered inside\n * the hidden native select. This covers option-producing components, which\n * cannot be evaluated safely while parsing the original ReactNode tree. */\nexport function parseNativeSelect(select: HTMLSelectElement): ParsedSelectChildren {\n const options: SelectOption[] = []\n let placeholder: string | undefined\n let emptyLabel: string | undefined\n let clearable = false\n let seenAny = false\n\n for (const option of select.options) {\n const label = option.textContent?.trim() || option.label || option.value\n if (option.value === '' && !seenAny) {\n placeholder = label || undefined\n emptyLabel = label || 'None'\n clearable = !option.disabled\n seenAny = true\n continue\n }\n seenAny = true\n if (option.hidden) continue\n const parent = option.parentElement\n const inGroup = parent instanceof HTMLOptGroupElement\n options.push({\n value: option.value,\n label: label || option.value,\n disabled: option.disabled || (inGroup && parent.disabled),\n group: inGroup ? parent.label : undefined,\n })\n }\n\n return { options, placeholder, clearable, emptyLabel }\n}\n\nfunction textOf(node: React.ReactNode): string {\n if (node == null || node === false || node === true) return ''\n if (typeof node === 'string' || typeof node === 'number') return String(node)\n if (Array.isArray(node)) return node.map(textOf).join('')\n if (React.isValidElement(node)) {\n const props = node.props as { children?: React.ReactNode; value?: React.ReactNode }\n // Translation and conditional-render helpers are intentionally transparent\n // React components. Their rendered content is carried in `value`, not\n // `children`, so read it when deriving the visible typeahead label.\n return textOf(props.children ?? (typeof node.type !== 'string' ? props.value : undefined))\n }\n return ''\n}\n\n// Flatten <option>/<optgroup> children into a SelectOption[] for the typeahead.\n// A leading <option value=\"\"> becomes the placeholder (greyed); if it isn't\n// disabled it also makes the field clearable back to \"\".\nexport function parseSelectChildren(children: React.ReactNode): ParsedSelectChildren {\n const options: SelectOption[] = []\n let placeholder: string | undefined\n let emptyLabel: string | undefined\n let clearable = false\n let seenAny = false\n\n const pushOption = (el: React.ReactElement, group?: string) => {\n const props = el.props as {\n value?: string | number | readonly string[]\n children?: React.ReactNode\n disabled?: boolean\n hidden?: boolean\n }\n const label = textOf(props.children)\n const value = props.value != null ? String(props.value) : label\n if (value === '' && !seenAny) {\n placeholder = label || undefined\n emptyLabel = label || 'None'\n clearable = !props.disabled\n seenAny = true\n return\n }\n seenAny = true\n if (props.hidden) return\n options.push({ value, label: label || value, disabled: !!props.disabled, group })\n }\n\n const walk = (nodes: React.ReactNode) => {\n React.Children.forEach(nodes, (child) => {\n if (!React.isValidElement(child)) return\n if (child.type === React.Fragment) {\n walk((child.props as { children?: React.ReactNode }).children)\n } else if (child.type === 'optgroup') {\n const props = child.props as { label?: string; children?: React.ReactNode }\n React.Children.forEach(props.children, (option) => {\n if (React.isValidElement(option) && option.type === 'option') {\n pushOption(option, props.label)\n }\n })\n } else if (child.type === 'option') {\n pushOption(child)\n } else if (typeof child.type !== 'string') {\n // Server/client translation helpers and conditional wrappers can sit\n // between Select and its option elements. Native <select> rendering\n // handles those wrappers after React resolves them, but the custom\n // typeahead must unwrap their ReactNode payload itself.\n const props = child.props as { children?: React.ReactNode; value?: React.ReactNode }\n walk(props.children ?? props.value)\n }\n })\n }\n walk(children)\n\n return { options, placeholder, clearable, emptyLabel }\n}\n"]}
package/select.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import * as React from 'react';
2
+ export type SelectProps = React.SelectHTMLAttributes<HTMLSelectElement> & {
3
+ /** Greyed placeholder shown when nothing is selected (or use a leading <option value=""> ). */
4
+ placeholder?: string;
5
+ /** Title of the mobile bottom sheet. */
6
+ sheetTitle?: string;
7
+ searchPlaceholder?: string;
8
+ /** Force the in-dropdown search box on/off. Defaults to auto (shown for long lists). */
9
+ searchable?: boolean;
10
+ /** Extra classes for the trigger button (the visible control). */
11
+ triggerClassName?: string;
12
+ };
13
+ export declare const Select: React.ForwardRefExoticComponent<React.SelectHTMLAttributes<HTMLSelectElement> & {
14
+ /** Greyed placeholder shown when nothing is selected (or use a leading <option value=""> ). */
15
+ placeholder?: string;
16
+ /** Title of the mobile bottom sheet. */
17
+ sheetTitle?: string;
18
+ searchPlaceholder?: string;
19
+ /** Force the in-dropdown search box on/off. Defaults to auto (shown for long lists). */
20
+ searchable?: boolean;
21
+ /** Extra classes for the trigger button (the visible control). */
22
+ triggerClassName?: string;
23
+ } & React.RefAttributes<HTMLSelectElement>>;
24
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../src/select.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,GAAG;IACxE,+FAA+F;IAC/F,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,wFAAwF;IACxF,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED,eAAO,MAAM,MAAM;IAXjB,+FAA+F;kBACjF,MAAM;IACpB,wCAAwC;iBAC3B,MAAM;wBACC,MAAM;IAC1B,wFAAwF;iBAC3E,OAAO;IACpB,kEAAkE;uBAC/C,MAAM;2CA4GzB,CAAA"}
package/select.js ADDED
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Select — the app-wide select control. It looks and behaves like the
4
+ // SearchSelect typeahead (anchored dropdown on desktop, bottom sheet on mobile,
5
+ // searchable, keyboard nav, dark-mode) but keeps the *exact* native-<select>
6
+ // API: pass <option>/<optgroup> children, `value`/`onChange`, or
7
+ // `name`/`defaultValue` for server-action forms. A visually-hidden real
8
+ // <select> underneath carries the value, fires the genuine
9
+ // onChange(ChangeEvent<HTMLSelectElement>), and powers native form submission
10
+ // + `required` validation — so this is a drop-in replacement for a plain
11
+ // <select> with no field-contract change anywhere.
12
+ //
13
+ // There are intentionally NO native <select> dropdowns in the product; use this
14
+ // (or SearchSelect / PersonSelectField for fully-custom cases) everywhere.
15
+ import * as React from 'react';
16
+ import { SearchSelect } from './search-select.js';
17
+ import { parseNativeSelect, parseSelectChildren, selectChildrenEqual } from './select-options.js';
18
+ import { cn } from './utils.js';
19
+ export const Select = React.forwardRef(function Select({ className, triggerClassName, children, value, defaultValue, onChange, placeholder, sheetTitle, searchPlaceholder, searchable, disabled, required, name, id, 'aria-label': ariaLabel, ...rest }, ref) {
20
+ const innerRef = React.useRef(null);
21
+ const setRef = React.useCallback((el) => {
22
+ innerRef.current = el;
23
+ if (typeof ref === 'function')
24
+ ref(el);
25
+ else if (ref)
26
+ ref.current = el;
27
+ }, [ref]);
28
+ const isControlled = value !== undefined;
29
+ const [uncontrolled, setUncontrolled] = React.useState(defaultValue != null ? String(defaultValue) : '');
30
+ const current = isControlled ? String(value ?? '') : uncontrolled;
31
+ const childParsed = React.useMemo(() => parseSelectChildren(children), [children]);
32
+ const [parsed, setParsed] = React.useState(childParsed);
33
+ // Option-producing components (translation helpers, field registries, and
34
+ // conditional blocks) only become concrete <option> elements after React
35
+ // renders the hidden select. Synchronize the visible typeahead from that
36
+ // resolved DOM so its menu can never silently appear empty.
37
+ React.useLayoutEffect(() => {
38
+ const select = innerRef.current;
39
+ if (!select)
40
+ return;
41
+ const resolved = parseNativeSelect(select);
42
+ setParsed((current) => (selectChildrenEqual(current, resolved) ? current : resolved));
43
+ }, [children]);
44
+ function handleNativeChange(e) {
45
+ if (!isControlled)
46
+ setUncontrolled(e.currentTarget.value);
47
+ onChange?.(e);
48
+ }
49
+ // Drive the hidden native <select> so the genuine change event fires (real
50
+ // ChangeEvent for callers, native form value + validation stay intact).
51
+ function pick(v) {
52
+ const el = innerRef.current;
53
+ if (!el)
54
+ return;
55
+ el.value = v;
56
+ el.dispatchEvent(new Event('change', { bubbles: true }));
57
+ }
58
+ const isInvalid = rest['aria-invalid'] === true || rest['aria-invalid'] === 'true';
59
+ // Historically `className` styled the native <select> box itself, so callers
60
+ // express height/text there as well as width/flex. Route it to the trigger
61
+ // (height/text/width) and the wrapper (width/flex/layout) so both land.
62
+ return (_jsxs("span", { className: cn('relative block w-full', className), children: [_jsx("select", { ...rest, ref: setRef, name: name, required: required, disabled: disabled, "aria-hidden": true, tabIndex: -1, ...(isControlled ? { value: current } : { defaultValue }), onChange: handleNativeChange, className: "pointer-events-none absolute inset-0 h-full w-full opacity-0", children: children }), _jsx(SearchSelect, { id: id, value: current, onChange: pick, options: parsed.options, placeholder: placeholder ?? parsed.placeholder, searchPlaceholder: searchPlaceholder, sheetTitle: sheetTitle ?? placeholder ?? parsed.placeholder, ariaLabel: ariaLabel, clearable: parsed.clearable, emptyLabel: parsed.emptyLabel, disabled: disabled, searchable: searchable, invalid: isInvalid, triggerClassName: cn(className, triggerClassName) })] }));
63
+ });
64
+ Select.displayName = 'Select';
65
+ //# sourceMappingURL=select.js.map
package/select.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../src/select.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\n// Select — the app-wide select control. It looks and behaves like the\n// SearchSelect typeahead (anchored dropdown on desktop, bottom sheet on mobile,\n// searchable, keyboard nav, dark-mode) but keeps the *exact* native-<select>\n// API: pass <option>/<optgroup> children, `value`/`onChange`, or\n// `name`/`defaultValue` for server-action forms. A visually-hidden real\n// <select> underneath carries the value, fires the genuine\n// onChange(ChangeEvent<HTMLSelectElement>), and powers native form submission\n// + `required` validation — so this is a drop-in replacement for a plain\n// <select> with no field-contract change anywhere.\n//\n// There are intentionally NO native <select> dropdowns in the product; use this\n// (or SearchSelect / PersonSelectField for fully-custom cases) everywhere.\n\nimport * as React from 'react'\nimport { SearchSelect } from './search-select'\nimport { parseNativeSelect, parseSelectChildren, selectChildrenEqual } from './select-options'\nimport { cn } from './utils'\n\nexport type SelectProps = React.SelectHTMLAttributes<HTMLSelectElement> & {\n /** Greyed placeholder shown when nothing is selected (or use a leading <option value=\"\"> ). */\n placeholder?: string\n /** Title of the mobile bottom sheet. */\n sheetTitle?: string\n searchPlaceholder?: string\n /** Force the in-dropdown search box on/off. Defaults to auto (shown for long lists). */\n searchable?: boolean\n /** Extra classes for the trigger button (the visible control). */\n triggerClassName?: string\n}\n\nexport const Select = React.forwardRef<HTMLSelectElement, SelectProps>(function Select(\n {\n className,\n triggerClassName,\n children,\n value,\n defaultValue,\n onChange,\n placeholder,\n sheetTitle,\n searchPlaceholder,\n searchable,\n disabled,\n required,\n name,\n id,\n 'aria-label': ariaLabel,\n ...rest\n },\n ref,\n) {\n const innerRef = React.useRef<HTMLSelectElement | null>(null)\n const setRef = React.useCallback(\n (el: HTMLSelectElement | null) => {\n innerRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLSelectElement | null>).current = el\n },\n [ref],\n )\n\n const isControlled = value !== undefined\n const [uncontrolled, setUncontrolled] = React.useState(\n defaultValue != null ? String(defaultValue) : '',\n )\n const current = isControlled ? String(value ?? '') : uncontrolled\n\n const childParsed = React.useMemo(() => parseSelectChildren(children), [children])\n const [parsed, setParsed] = React.useState(childParsed)\n\n // Option-producing components (translation helpers, field registries, and\n // conditional blocks) only become concrete <option> elements after React\n // renders the hidden select. Synchronize the visible typeahead from that\n // resolved DOM so its menu can never silently appear empty.\n React.useLayoutEffect(() => {\n const select = innerRef.current\n if (!select) return\n const resolved = parseNativeSelect(select)\n setParsed((current) => (selectChildrenEqual(current, resolved) ? current : resolved))\n }, [children])\n\n function handleNativeChange(e: React.ChangeEvent<HTMLSelectElement>) {\n if (!isControlled) setUncontrolled(e.currentTarget.value)\n onChange?.(e)\n }\n\n // Drive the hidden native <select> so the genuine change event fires (real\n // ChangeEvent for callers, native form value + validation stay intact).\n function pick(v: string) {\n const el = innerRef.current\n if (!el) return\n el.value = v\n el.dispatchEvent(new Event('change', { bubbles: true }))\n }\n\n const isInvalid = rest['aria-invalid'] === true || rest['aria-invalid'] === 'true'\n\n // Historically `className` styled the native <select> box itself, so callers\n // express height/text there as well as width/flex. Route it to the trigger\n // (height/text/width) and the wrapper (width/flex/layout) so both land.\n return (\n <span className={cn('relative block w-full', className)}>\n {/* Form/validation/onChange proxy — visually hidden, never tab-focused. */}\n <select\n {...rest}\n ref={setRef}\n name={name}\n required={required}\n disabled={disabled}\n aria-hidden\n tabIndex={-1}\n {...(isControlled ? { value: current } : { defaultValue })}\n onChange={handleNativeChange}\n className=\"pointer-events-none absolute inset-0 h-full w-full opacity-0\"\n >\n {children}\n </select>\n <SearchSelect\n id={id}\n value={current}\n onChange={pick}\n options={parsed.options}\n placeholder={placeholder ?? parsed.placeholder}\n searchPlaceholder={searchPlaceholder}\n sheetTitle={sheetTitle ?? placeholder ?? parsed.placeholder}\n ariaLabel={ariaLabel}\n clearable={parsed.clearable}\n emptyLabel={parsed.emptyLabel}\n disabled={disabled}\n searchable={searchable}\n invalid={isInvalid}\n triggerClassName={cn(className, triggerClassName)}\n />\n </span>\n )\n})\nSelect.displayName = 'Select'\n"]}
package/separator.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import * as React from 'react';
2
+ export type SeparatorProps = React.HTMLAttributes<HTMLDivElement> & {
3
+ orientation?: 'horizontal' | 'vertical';
4
+ };
5
+ export declare function Separator({ className, orientation, ...props }: SeparatorProps): React.JSX.Element;
6
+ //# sourceMappingURL=separator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"separator.d.ts","sourceRoot":"","sources":["../src/separator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,GAAG;IAClE,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;CACxC,CAAA;AAED,wBAAgB,SAAS,CAAC,EAAE,SAAS,EAAE,WAA0B,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc,qBAa5F"}
package/separator.js ADDED
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { cn } from './utils.js';
4
+ export function Separator({ className, orientation = 'horizontal', ...props }) {
5
+ return (_jsx("div", { role: "separator", "aria-orientation": orientation, className: cn('shrink-0 bg-border', orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px', className), ...props }));
6
+ }
7
+ //# sourceMappingURL=separator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"separator.js","sourceRoot":"","sources":["../src/separator.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAM5B,MAAM,UAAU,SAAS,CAAC,EAAE,SAAS,EAAE,WAAW,GAAG,YAAY,EAAE,GAAG,KAAK,EAAkB;IAC3F,OAAO,CACL,cACE,IAAI,EAAC,WAAW,sBACE,WAAW,EAC7B,SAAS,EAAE,EAAE,CACX,oBAAoB,EACpB,WAAW,KAAK,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,EAC5D,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAA;AACH,CAAC","sourcesContent":["import * as React from 'react'\nimport { cn } from './utils'\n\nexport type SeparatorProps = React.HTMLAttributes<HTMLDivElement> & {\n orientation?: 'horizontal' | 'vertical'\n}\n\nexport function Separator({ className, orientation = 'horizontal', ...props }: SeparatorProps) {\n return (\n <div\n role=\"separator\"\n aria-orientation={orientation}\n className={cn(\n 'shrink-0 bg-border',\n orientation === 'horizontal' ? 'h-px w-full' : 'h-full w-px',\n className,\n )}\n {...props}\n />\n )\n}\n"]}
@@ -0,0 +1,79 @@
1
+ import * as React from 'react';
2
+ import { type LinkRender } from './link-context.js';
3
+ export type { LinkRender };
4
+ export type AdminHubAccent = 'teal' | 'violet' | 'amber' | 'sky';
5
+ export type AdminHubCard = {
6
+ href?: string;
7
+ title: string;
8
+ description?: string;
9
+ icon?: React.ReactNode;
10
+ badge?: React.ReactNode;
11
+ features?: string[];
12
+ linkLabel?: string;
13
+ };
14
+ export type AdminHubGroup = {
15
+ label?: string;
16
+ description?: string;
17
+ accent?: AdminHubAccent;
18
+ layout?: 'compact' | 'detailed';
19
+ cards: AdminHubCard[];
20
+ };
21
+ export declare function AdminHub({ title, description, actions, groups, linkRender, }: {
22
+ title?: string;
23
+ description?: string;
24
+ actions?: React.ReactNode;
25
+ groups: AdminHubGroup[];
26
+ linkRender?: LinkRender;
27
+ }): React.JSX.Element;
28
+ export type SettingsNavItem = {
29
+ key: string;
30
+ label: string;
31
+ icon?: React.ReactNode;
32
+ badge?: React.ReactNode;
33
+ };
34
+ export type SettingsNavGroup = {
35
+ label?: string;
36
+ items: SettingsNavItem[];
37
+ };
38
+ export declare function SettingsNav({ groups, activeKey, onSelect, className, }: {
39
+ groups: SettingsNavGroup[];
40
+ activeKey: string;
41
+ onSelect: (key: string) => void;
42
+ className?: string;
43
+ }): React.JSX.Element;
44
+ export declare function SettingsShell({ title, description, back, actions, nav, activeKey, onSelect, children, contentWidth, linkRender, }: {
45
+ title: string;
46
+ description?: string;
47
+ back?: {
48
+ href: string;
49
+ label: string;
50
+ };
51
+ actions?: React.ReactNode;
52
+ nav: SettingsNavGroup[];
53
+ activeKey: string;
54
+ onSelect: (key: string) => void;
55
+ children: React.ReactNode;
56
+ /**
57
+ * Bounds the active pane without changing the navigation shell.
58
+ * Settings forms stay narrow by default; record-heavy administration
59
+ * workspaces can opt into a wider or edge-to-edge canvas.
60
+ */
61
+ contentWidth?: 'narrow' | 'wide' | 'full';
62
+ linkRender?: LinkRender;
63
+ }): React.JSX.Element;
64
+ export declare function SettingsSection({ title, description, footer, children, className, }: {
65
+ title?: string;
66
+ description?: string;
67
+ footer?: React.ReactNode;
68
+ children: React.ReactNode;
69
+ className?: string;
70
+ }): React.JSX.Element;
71
+ export declare function SettingsRow({ title, description, control, stacked, children, className, }: {
72
+ title: React.ReactNode;
73
+ description?: React.ReactNode;
74
+ control?: React.ReactNode;
75
+ stacked?: boolean;
76
+ children?: React.ReactNode;
77
+ className?: string;
78
+ }): React.JSX.Element;
79
+ //# sourceMappingURL=settings-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-layout.d.ts","sourceRoot":"","sources":["../src/settings-layout.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { ArrowRight, ArrowUpRight, Check } from 'lucide-react';
5
+ import { defaultLinkRender } from './link-context.js';
6
+ import { PageHeader } from './page-header.js';
7
+ import { cn } from './utils.js';
8
+ const ACCENTS = {
9
+ teal: {
10
+ chip: 'bg-primary-subtle text-primary ring-primary/15',
11
+ border: 'hover:border-primary/40',
12
+ link: 'group-hover:text-primary',
13
+ },
14
+ violet: {
15
+ chip: 'bg-info-subtle text-info ring-info/15',
16
+ border: 'hover:border-info/40',
17
+ link: 'group-hover:text-info',
18
+ },
19
+ amber: {
20
+ chip: 'bg-warning-subtle text-warning ring-warning/15',
21
+ border: 'hover:border-warning/40',
22
+ link: 'group-hover:text-warning',
23
+ },
24
+ sky: {
25
+ chip: 'bg-success-subtle text-success ring-success/15',
26
+ border: 'hover:border-success/40',
27
+ link: 'group-hover:text-success',
28
+ },
29
+ };
30
+ export function AdminHub({ title, description, actions, groups, linkRender = defaultLinkRender, }) {
31
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [title || description ? (_jsx("div", { className: "shrink-0 border-b border-border bg-surface px-3 py-3 sm:px-6", children: _jsx(PageHeader, { title: title ?? '', description: description, actions: actions }) })) : null, _jsx("div", { className: "app-scroll min-h-0 flex-1 overflow-y-auto bg-bg-subtle", children: _jsx("div", { className: "mx-auto w-full max-w-(--breakpoint-2xl) space-y-8 p-4 sm:p-6", children: groups.map((group, gi) => {
32
+ const accent = ACCENTS[group.accent ?? 'teal'];
33
+ return (_jsxs("section", { className: "space-y-3", children: [group.label || group.description ? (_jsxs("div", { className: "space-y-1 px-0.5", children: [group.label ? (_jsx("h2", { className: "text-xs font-semibold tracking-wider text-fg-subtle uppercase", children: group.label })) : null, group.description ? (_jsx("p", { className: "max-w-3xl text-sm text-fg-muted", children: group.description })) : null] })) : null, _jsx("div", { className: cn('grid grid-cols-1 gap-3 sm:grid-cols-2', group.layout === 'detailed' ? '2xl:grid-cols-3' : 'lg:grid-cols-3 2xl:grid-cols-4'), children: group.cards.map((card, ci) => {
34
+ const detailed = group.layout === 'detailed';
35
+ const content = detailed ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("span", { className: cn('grid size-10 shrink-0 place-items-center rounded-lg ring-1 [&_svg]:size-[18px]', accent.chip), children: card.icon }), _jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("h3", { className: "font-mono text-sm font-semibold text-fg", children: card.title }), card.badge] }), card.description ? (_jsx("p", { className: "text-sm leading-relaxed text-fg-muted", children: card.description })) : null] })] }), card.features?.length ? (_jsx("ul", { className: "mt-4 space-y-2 border-t border-border-subtle pt-4", children: card.features.map((feature) => (_jsxs("li", { className: "flex gap-2 text-xs leading-relaxed text-fg-muted", children: [_jsx(Check, { className: "mt-0.5 size-3.5 shrink-0 text-primary", "aria-hidden": true }), _jsx("span", { children: feature })] }, feature))) })) : null, card.href && card.linkLabel ? (_jsxs("span", { className: cn('mt-auto flex items-center gap-1.5 pt-4 text-xs font-semibold text-fg-muted', accent.link), children: [card.linkLabel, _jsx(ArrowRight, { className: "size-3.5 transition-transform group-hover:translate-x-0.5", "aria-hidden": true })] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: cn('grid size-10 shrink-0 place-items-center rounded-lg ring-1', accent.chip), children: card.icon }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("h3", { className: "truncate text-sm font-semibold text-fg", children: card.title }), card.badge] }), card.description ? (_jsx("p", { className: "truncate text-xs text-fg-muted", children: card.description })) : null] }), card.href ? (_jsx(ArrowUpRight, { size: 15, "aria-hidden": true, className: cn('shrink-0 text-fg-subtle opacity-0 transition-all duration-200 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:opacity-100', accent.link) })) : null] }));
36
+ const className = cn('group rounded-xl border border-border bg-surface shadow-sm transition-all', detailed ? 'flex h-full flex-col p-4' : 'flex items-center gap-3 p-3.5', card.href && 'hover:shadow-md', card.href && accent.border);
37
+ return card.href ? (_jsx(React.Fragment, { children: linkRender({
38
+ href: card.href,
39
+ title: card.description,
40
+ className,
41
+ children: content,
42
+ }) }, ci)) : (_jsx("article", { className: className, children: content }, ci));
43
+ }) })] }, gi));
44
+ }) }) })] }));
45
+ }
46
+ export function SettingsNav({ groups, activeKey, onSelect, className, }) {
47
+ return (_jsx("nav", { className: cn('w-full', className), children: _jsx("div", { className: "space-y-5", children: groups.map((group, gi) => (_jsxs("div", { className: "space-y-1", children: [group.label ? (_jsx("h3", { className: "px-2 text-xs font-semibold tracking-wider text-fg-subtle uppercase", children: group.label })) : null, _jsx("ul", { className: "space-y-0.5", children: group.items.map((item) => {
48
+ const active = item.key === activeKey;
49
+ return (_jsx("li", { children: _jsxs("button", { type: "button", "aria-current": active ? 'page' : undefined, onClick: () => onSelect(item.key), className: cn('flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-sm transition-colors', active
50
+ ? 'bg-primary-subtle font-medium text-primary'
51
+ : 'text-fg-muted hover:bg-surface-hover hover:text-fg'), children: [_jsx("span", { className: cn('shrink-0 [&_svg]:size-[15px]', active ? 'text-primary' : 'text-fg-subtle'), children: item.icon }), _jsx("span", { className: "flex-1 truncate text-left", children: item.label }), item.badge] }) }, item.key));
52
+ }) })] }, gi))) }) }));
53
+ }
54
+ export function SettingsShell({ title, description, back, actions, nav, activeKey, onSelect, children, contentWidth = 'narrow', linkRender = defaultLinkRender, }) {
55
+ const contentWidthClass = {
56
+ narrow: 'max-w-5xl',
57
+ wide: 'max-w-(--breakpoint-2xl)',
58
+ full: 'max-w-none',
59
+ }[contentWidth];
60
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx("div", { className: "shrink-0 border-b border-border bg-surface px-3 py-3 sm:px-6", children: _jsx(PageHeader, { title: title, description: description, actions: actions, back: back
61
+ ? { ...back, render: (p) => linkRender({ href: p.href, className: p.className, children: p.children }) }
62
+ : undefined }) }), _jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsx("aside", { className: "app-scroll w-44 shrink-0 overflow-y-auto border-r border-border bg-surface p-3 sm:w-52 lg:w-60", children: _jsx(SettingsNav, { groups: nav, activeKey: activeKey, onSelect: onSelect }) }), _jsx("div", { className: "app-scroll min-h-0 flex-1 overflow-y-auto bg-bg-subtle", children: _jsx("div", { className: cn('mx-auto w-full space-y-6 p-4 sm:p-6', contentWidthClass), children: children }) })] })] }));
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // Content helpers used inside a SettingsShell.
66
+ // ---------------------------------------------------------------------------
67
+ export function SettingsSection({ title, description, footer, children, className, }) {
68
+ return (_jsxs("section", { className: cn('overflow-hidden rounded-xl border border-border bg-surface', className), children: [title || description ? (_jsxs("div", { className: "border-b border-border px-5 py-4", children: [title ? _jsx("h2", { className: "text-sm font-semibold text-fg", children: title }) : null, description ? _jsx("p", { className: "mt-0.5 text-sm text-fg-muted", children: description }) : null] })) : null, _jsx("div", { className: "divide-y divide-border", children: children }), footer ? (_jsx("div", { className: "flex items-center justify-end gap-2 border-t border-border bg-bg-subtle px-5 py-3", children: footer })) : null] }));
69
+ }
70
+ export function SettingsRow({ title, description, control, stacked = false, children, className, }) {
71
+ const right = control ?? children;
72
+ return (_jsxs("div", { className: cn(stacked ? 'space-y-2 px-5 py-4' : 'flex items-center justify-between gap-4 px-5 py-4', className), children: [_jsxs("div", { className: "min-w-0 space-y-0.5", children: [_jsx("div", { className: "text-sm font-medium text-fg", children: title }), description ? _jsx("div", { className: "text-sm text-fg-muted", children: description }) : null] }), right ? _jsx("div", { className: cn(stacked ? 'w-full' : 'shrink-0'), children: right }) : null] }));
73
+ }
74
+ //# sourceMappingURL=settings-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-layout.js","sourceRoot":"","sources":["../src/settings-layout.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { ArrowRight, ArrowUpRight, Check } from 'lucide-react'\nimport { defaultLinkRender, type LinkRender } from './link-context'\nimport { PageHeader } from './page-header'\nimport { cn } from './utils'\n\nexport type { LinkRender }\n\n// ---------------------------------------------------------------------------\n// AdminHub — the settings LANDING page: grouped cards, each an accent-colored\n// icon chip + title/description + a hover arrow, linking to a sub-area.\n// Expressed through semantic tokens so a\n// consuming application's brand and dark theme remain authoritative.\n// ---------------------------------------------------------------------------\n\nexport type AdminHubAccent = 'teal' | 'violet' | 'amber' | 'sky'\n\nconst ACCENTS: Record<AdminHubAccent, { chip: string; border: string; link: string }> = {\n teal: {\n chip: 'bg-primary-subtle text-primary ring-primary/15',\n border: 'hover:border-primary/40',\n link: 'group-hover:text-primary',\n },\n violet: {\n chip: 'bg-info-subtle text-info ring-info/15',\n border: 'hover:border-info/40',\n link: 'group-hover:text-info',\n },\n amber: {\n chip: 'bg-warning-subtle text-warning ring-warning/15',\n border: 'hover:border-warning/40',\n link: 'group-hover:text-warning',\n },\n sky: {\n chip: 'bg-success-subtle text-success ring-success/15',\n border: 'hover:border-success/40',\n link: 'group-hover:text-success',\n },\n}\n\nexport type AdminHubCard = {\n href?: string\n title: string\n description?: string\n icon?: React.ReactNode\n badge?: React.ReactNode\n features?: string[]\n linkLabel?: string\n}\nexport type AdminHubGroup = {\n label?: string\n description?: string\n accent?: AdminHubAccent\n layout?: 'compact' | 'detailed'\n cards: AdminHubCard[]\n}\n\nexport function AdminHub({\n title,\n description,\n actions,\n groups,\n linkRender = defaultLinkRender,\n}: {\n title?: string\n description?: string\n actions?: React.ReactNode\n groups: AdminHubGroup[]\n linkRender?: LinkRender\n}) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n {title || description ? (\n <div className=\"shrink-0 border-b border-border bg-surface px-3 py-3 sm:px-6\">\n <PageHeader title={title ?? ''} description={description} actions={actions} />\n </div>\n ) : null}\n <div className=\"app-scroll min-h-0 flex-1 overflow-y-auto bg-bg-subtle\">\n <div className=\"mx-auto w-full max-w-(--breakpoint-2xl) space-y-8 p-4 sm:p-6\">\n {groups.map((group, gi) => {\n const accent = ACCENTS[group.accent ?? 'teal']\n return (\n <section key={gi} className=\"space-y-3\">\n {group.label || group.description ? (\n <div className=\"space-y-1 px-0.5\">\n {group.label ? (\n <h2 className=\"text-xs font-semibold tracking-wider text-fg-subtle uppercase\">\n {group.label}\n </h2>\n ) : null}\n {group.description ? (\n <p className=\"max-w-3xl text-sm text-fg-muted\">{group.description}</p>\n ) : null}\n </div>\n ) : null}\n <div\n className={cn(\n 'grid grid-cols-1 gap-3 sm:grid-cols-2',\n group.layout === 'detailed' ? '2xl:grid-cols-3' : 'lg:grid-cols-3 2xl:grid-cols-4',\n )}\n >\n {group.cards.map((card, ci) => {\n const detailed = group.layout === 'detailed'\n const content = detailed ? (\n <>\n <div className=\"flex items-start gap-3\">\n <span\n className={cn(\n 'grid size-10 shrink-0 place-items-center rounded-lg ring-1 [&_svg]:size-[18px]',\n accent.chip,\n )}\n >\n {card.icon}\n </span>\n <div className=\"min-w-0 flex-1 space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h3 className=\"font-mono text-sm font-semibold text-fg\">{card.title}</h3>\n {card.badge}\n </div>\n {card.description ? (\n <p className=\"text-sm leading-relaxed text-fg-muted\">\n {card.description}\n </p>\n ) : null}\n </div>\n </div>\n {card.features?.length ? (\n <ul className=\"mt-4 space-y-2 border-t border-border-subtle pt-4\">\n {card.features.map((feature) => (\n <li\n key={feature}\n className=\"flex gap-2 text-xs leading-relaxed text-fg-muted\"\n >\n <Check\n className=\"mt-0.5 size-3.5 shrink-0 text-primary\"\n aria-hidden\n />\n <span>{feature}</span>\n </li>\n ))}\n </ul>\n ) : null}\n {card.href && card.linkLabel ? (\n <span\n className={cn(\n 'mt-auto flex items-center gap-1.5 pt-4 text-xs font-semibold text-fg-muted',\n accent.link,\n )}\n >\n {card.linkLabel}\n <ArrowRight\n className=\"size-3.5 transition-transform group-hover:translate-x-0.5\"\n aria-hidden\n />\n </span>\n ) : null}\n </>\n ) : (\n <>\n <span\n className={cn(\n 'grid size-10 shrink-0 place-items-center rounded-lg ring-1',\n accent.chip,\n )}\n >\n {card.icon}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <h3 className=\"truncate text-sm font-semibold text-fg\">{card.title}</h3>\n {card.badge}\n </div>\n {card.description ? (\n <p className=\"truncate text-xs text-fg-muted\">{card.description}</p>\n ) : null}\n </div>\n {card.href ? (\n <ArrowUpRight\n size={15}\n aria-hidden\n className={cn(\n 'shrink-0 text-fg-subtle opacity-0 transition-all duration-200 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:opacity-100',\n accent.link,\n )}\n />\n ) : null}\n </>\n )\n const className = cn(\n 'group rounded-xl border border-border bg-surface shadow-sm transition-all',\n detailed ? 'flex h-full flex-col p-4' : 'flex items-center gap-3 p-3.5',\n card.href && 'hover:shadow-md',\n card.href && accent.border,\n )\n\n return card.href ? (\n <React.Fragment key={ci}>\n {linkRender({\n href: card.href,\n title: card.description,\n className,\n children: content,\n })}\n </React.Fragment>\n ) : (\n <article key={ci} className={className}>\n {content}\n </article>\n )\n })}\n </div>\n </section>\n )\n })}\n </div>\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SettingsShell — the SIDEBAR settings area: a fixed header (with a back link\n// to the hub), and a two-pane body — a grouped nav rail beside the active\n// content, each pane scrolling independently. The\n// setup/layout.tsx + SetupNav.tsx.\n// ---------------------------------------------------------------------------\n\nexport type SettingsNavItem = { key: string; label: string; icon?: React.ReactNode; badge?: React.ReactNode }\nexport type SettingsNavGroup = { label?: string; items: SettingsNavItem[] }\n\nexport function SettingsNav({\n groups,\n activeKey,\n onSelect,\n className,\n}: {\n groups: SettingsNavGroup[]\n activeKey: string\n onSelect: (key: string) => void\n className?: string\n}) {\n return (\n <nav className={cn('w-full', className)}>\n <div className=\"space-y-5\">\n {groups.map((group, gi) => (\n <div key={gi} className=\"space-y-1\">\n {group.label ? (\n <h3 className=\"px-2 text-xs font-semibold tracking-wider text-fg-subtle uppercase\">\n {group.label}\n </h3>\n ) : null}\n <ul className=\"space-y-0.5\">\n {group.items.map((item) => {\n const active = item.key === activeKey\n return (\n <li key={item.key}>\n <button\n type=\"button\"\n aria-current={active ? 'page' : undefined}\n onClick={() => onSelect(item.key)}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-sm transition-colors',\n active\n ? 'bg-primary-subtle font-medium text-primary'\n : 'text-fg-muted hover:bg-surface-hover hover:text-fg',\n )}\n >\n <span className={cn('shrink-0 [&_svg]:size-[15px]', active ? 'text-primary' : 'text-fg-subtle')}>\n {item.icon}\n </span>\n <span className=\"flex-1 truncate text-left\">{item.label}</span>\n {item.badge}\n </button>\n </li>\n )\n })}\n </ul>\n </div>\n ))}\n </div>\n </nav>\n )\n}\n\nexport function SettingsShell({\n title,\n description,\n back,\n actions,\n nav,\n activeKey,\n onSelect,\n children,\n contentWidth = 'narrow',\n linkRender = defaultLinkRender,\n}: {\n title: string\n description?: string\n back?: { href: string; label: string }\n actions?: React.ReactNode\n nav: SettingsNavGroup[]\n activeKey: string\n onSelect: (key: string) => void\n children: React.ReactNode\n /**\n * Bounds the active pane without changing the navigation shell.\n * Settings forms stay narrow by default; record-heavy administration\n * workspaces can opt into a wider or edge-to-edge canvas.\n */\n contentWidth?: 'narrow' | 'wide' | 'full'\n linkRender?: LinkRender\n}) {\n const contentWidthClass = {\n narrow: 'max-w-5xl',\n wide: 'max-w-(--breakpoint-2xl)',\n full: 'max-w-none',\n }[contentWidth]\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 border-b border-border bg-surface px-3 py-3 sm:px-6\">\n <PageHeader\n title={title}\n description={description}\n actions={actions}\n back={\n back\n ? { ...back, render: (p) => linkRender({ href: p.href, className: p.className, children: p.children }) }\n : undefined\n }\n />\n </div>\n <div className=\"flex min-h-0 flex-1\">\n <aside className=\"app-scroll w-44 shrink-0 overflow-y-auto border-r border-border bg-surface p-3 sm:w-52 lg:w-60\">\n <SettingsNav groups={nav} activeKey={activeKey} onSelect={onSelect} />\n </aside>\n <div className=\"app-scroll min-h-0 flex-1 overflow-y-auto bg-bg-subtle\">\n <div className={cn('mx-auto w-full space-y-6 p-4 sm:p-6', contentWidthClass)}>\n {children}\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Content helpers used inside a SettingsShell.\n// ---------------------------------------------------------------------------\n\nexport function SettingsSection({\n title,\n description,\n footer,\n children,\n className,\n}: {\n title?: string\n description?: string\n footer?: React.ReactNode\n children: React.ReactNode\n className?: string\n}) {\n return (\n <section className={cn('overflow-hidden rounded-xl border border-border bg-surface', className)}>\n {title || description ? (\n <div className=\"border-b border-border px-5 py-4\">\n {title ? <h2 className=\"text-sm font-semibold text-fg\">{title}</h2> : null}\n {description ? <p className=\"mt-0.5 text-sm text-fg-muted\">{description}</p> : null}\n </div>\n ) : null}\n <div className=\"divide-y divide-border\">{children}</div>\n {footer ? (\n <div className=\"flex items-center justify-end gap-2 border-t border-border bg-bg-subtle px-5 py-3\">\n {footer}\n </div>\n ) : null}\n </section>\n )\n}\n\nexport function SettingsRow({\n title,\n description,\n control,\n stacked = false,\n children,\n className,\n}: {\n title: React.ReactNode\n description?: React.ReactNode\n control?: React.ReactNode\n stacked?: boolean\n children?: React.ReactNode\n className?: string\n}) {\n const right = control ?? children\n return (\n <div\n className={cn(\n stacked ? 'space-y-2 px-5 py-4' : 'flex items-center justify-between gap-4 px-5 py-4',\n className,\n )}\n >\n <div className=\"min-w-0 space-y-0.5\">\n <div className=\"text-sm font-medium text-fg\">{title}</div>\n {description ? <div className=\"text-sm text-fg-muted\">{description}</div> : null}\n </div>\n {right ? <div className={cn(stacked ? 'w-full' : 'shrink-0')}>{right}</div> : null}\n </div>\n )\n}\n"]}
@@ -0,0 +1,67 @@
1
+ import * as React from 'react';
2
+ import { type LucideIcon } from 'lucide-react';
3
+ import { type LinkRender } from './link-context.js';
4
+ declare const ICONS: Record<string, LucideIcon>;
5
+ export type SidebarNavItem = {
6
+ href: string;
7
+ label: string;
8
+ /** Built-in message key. Omitted for tenant-authored/custom labels. */
9
+ labelKey?: string;
10
+ iconKey?: keyof typeof ICONS | string;
11
+ icon?: React.ReactNode;
12
+ exact?: boolean;
13
+ subgroup?: string;
14
+ subgroupHref?: string;
15
+ subgroupIconKey?: string;
16
+ mobile?: boolean;
17
+ };
18
+ export type SidebarNavGroup = {
19
+ /** Optional workspace id; omit it for a simple grouped-list contract. */
20
+ id?: string;
21
+ label: string;
22
+ labelKey?: string;
23
+ iconKey?: string;
24
+ icon?: React.ReactNode;
25
+ items: SidebarNavItem[];
26
+ };
27
+ /**
28
+ * Single-item groups are top-level navigation links, not expandable
29
+ * workspaces. This is the same contract TopNav already uses and keeps the
30
+ * sidebar/mobile drawer faithful when an application chooses a flat menu.
31
+ */
32
+ export declare function isFlatNavigation(groups: SidebarNavGroup[]): boolean;
33
+ export declare function findActiveNavHref(pathname: string | null | undefined, groups: SidebarNavGroup[]): string | null;
34
+ /**
35
+ * The shared navigation registry rendered as a desktop sidebar. Workspace
36
+ * metadata (`id` + group icon) enables collapsible treatment; simpler groups
37
+ * retain labeled sections. Both use the same active-path, icon, and link contracts.
38
+ */
39
+ export declare function SidebarNav({ groups, pathname, collapsed, linkRender, }: {
40
+ groups: SidebarNavGroup[];
41
+ pathname: string;
42
+ collapsed?: boolean;
43
+ linkRender?: LinkRender;
44
+ }): React.JSX.Element;
45
+ export declare function groupContainsActiveHref(group: SidebarNavGroup, activeHref: string | null): boolean;
46
+ export type NavBlock = {
47
+ kind: 'item';
48
+ item: SidebarNavItem;
49
+ } | {
50
+ kind: 'subgroup';
51
+ label: string;
52
+ href?: string;
53
+ iconKey?: string;
54
+ items: SidebarNavItem[];
55
+ };
56
+ export declare function toBlocks(items: SidebarNavItem[]): NavBlock[];
57
+ export declare const ICON_KEYS: string[];
58
+ export declare function NavIcon({ iconKey, icon, size, className, }: {
59
+ iconKey?: string;
60
+ icon?: React.ReactNode;
61
+ size?: number;
62
+ className?: string;
63
+ }): React.JSX.Element;
64
+ /** Mobile shortcut selection: explicitly pinned entries, then registry order. */
65
+ export declare function selectMobileTabs(groups: SidebarNavGroup[], count?: number): SidebarNavItem[];
66
+ export {};
67
+ //# sourceMappingURL=sidebar-nav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar-nav.d.ts","sourceRoot":"","sources":["../src/sidebar-nav.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAwDL,KAAK,UAAU,EAChB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAMnE,QAAA,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAuDrC,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,OAAO,KAAK,GAAG,MAAM,CAAA;IACrC,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,yEAAyE;IACzE,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,EAAE,cAAc,EAAE,CAAA;CACxB,CAAA;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,OAAO,CAUnE;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,MAAM,EAAE,eAAe,EAAE,GACxB,MAAM,GAAG,IAAI,CAgBf;AAQD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,QAAQ,EACR,SAAiB,EACjB,UAA8B,GAC/B,EAAE;IACD,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;CACxB,qBAgKA;AAOD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAElG;AAgGD,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,cAAc,EAAE,CAAA;CAAE,CAAA;AAEjG,wBAAgB,QAAQ,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,QAAQ,EAAE,CA0B5D;AA4ID,eAAO,MAAM,SAAS,UAA4B,CAAA;AAElD,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,IAAI,EACJ,IAAS,EACT,SAAS,GACV,EAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,qBAaA;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,KAAK,SAAI,GAAG,cAAc,EAAE,CAKvF"}