@xemahq/ui-kernel 0.1.11 → 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 (235) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/biome-host/biome-builders.d.ts +21 -0
  6. package/dist/lib/biome-host/biome-builders.d.ts.map +1 -0
  7. package/dist/lib/biome-host/biome-builders.js +25 -0
  8. package/dist/lib/biome-host/biome-builders.js.map +1 -0
  9. package/dist/lib/biome-host/biome-navigation.d.ts +3 -0
  10. package/dist/lib/biome-host/biome-navigation.d.ts.map +1 -0
  11. package/dist/lib/biome-host/biome-navigation.js +14 -0
  12. package/dist/lib/biome-host/biome-navigation.js.map +1 -0
  13. package/dist/lib/biome-host/biome-scope.d.ts +18 -0
  14. package/dist/lib/biome-host/biome-scope.d.ts.map +1 -0
  15. package/dist/lib/biome-host/biome-scope.js +42 -0
  16. package/dist/lib/biome-host/biome-scope.js.map +1 -0
  17. package/dist/lib/biome-host/biome-scoped-query.d.ts +17 -0
  18. package/dist/lib/biome-host/biome-scoped-query.d.ts.map +1 -0
  19. package/dist/lib/biome-host/biome-scoped-query.js +39 -0
  20. package/dist/lib/biome-host/biome-scoped-query.js.map +1 -0
  21. package/dist/lib/biome-host/host-bridge.d.ts +3 -0
  22. package/dist/lib/biome-host/host-bridge.d.ts.map +1 -1
  23. package/dist/lib/biome-host/host-bridge.js.map +1 -1
  24. package/dist/lib/biome-host/index.d.ts +4 -0
  25. package/dist/lib/biome-host/index.d.ts.map +1 -1
  26. package/dist/lib/biome-host/index.js +4 -0
  27. package/dist/lib/biome-host/index.js.map +1 -1
  28. package/dist/lib/capabilities/capability-provider.d.ts +15 -0
  29. package/dist/lib/capabilities/capability-provider.d.ts.map +1 -0
  30. package/dist/lib/capabilities/capability-provider.js +36 -0
  31. package/dist/lib/capabilities/capability-provider.js.map +1 -0
  32. package/dist/lib/capabilities/index.d.ts +4 -0
  33. package/dist/lib/capabilities/index.d.ts.map +1 -0
  34. package/dist/lib/capabilities/index.js +20 -0
  35. package/dist/lib/capabilities/index.js.map +1 -0
  36. package/dist/lib/capabilities/types.d.ts +18 -0
  37. package/dist/lib/capabilities/types.d.ts.map +1 -0
  38. package/dist/lib/capabilities/types.js +3 -0
  39. package/dist/lib/capabilities/types.js.map +1 -0
  40. package/dist/lib/capabilities/use-capability.d.ts +18 -0
  41. package/dist/lib/capabilities/use-capability.d.ts.map +1 -0
  42. package/dist/lib/capabilities/use-capability.js +21 -0
  43. package/dist/lib/capabilities/use-capability.js.map +1 -0
  44. package/dist/session/shell/SessionWorkspaceShell.js +1 -1
  45. package/dist/session/shell/SessionWorkspaceShell.js.map +1 -1
  46. package/dist/session-kit/display/ThinkingPanel.d.ts.map +1 -1
  47. package/dist/session-kit/display/ThinkingPanel.js +3 -0
  48. package/dist/session-kit/display/ThinkingPanel.js.map +1 -1
  49. package/dist/ui/chrome/AsyncBoundary.d.ts +22 -0
  50. package/dist/ui/chrome/AsyncBoundary.d.ts.map +1 -0
  51. package/dist/ui/chrome/AsyncBoundary.js +23 -0
  52. package/dist/ui/chrome/AsyncBoundary.js.map +1 -0
  53. package/dist/ui/chrome/EmptyState.d.ts +34 -0
  54. package/dist/ui/chrome/EmptyState.d.ts.map +1 -0
  55. package/dist/ui/chrome/EmptyState.js +27 -0
  56. package/dist/ui/chrome/EmptyState.js.map +1 -0
  57. package/dist/ui/chrome/ErrorCard.d.ts +11 -0
  58. package/dist/ui/chrome/ErrorCard.d.ts.map +1 -0
  59. package/dist/ui/chrome/ErrorCard.js +21 -0
  60. package/dist/ui/chrome/ErrorCard.js.map +1 -0
  61. package/dist/ui/chrome/LoadingState.d.ts +10 -0
  62. package/dist/ui/chrome/LoadingState.d.ts.map +1 -0
  63. package/dist/ui/chrome/LoadingState.js +17 -0
  64. package/dist/ui/chrome/LoadingState.js.map +1 -0
  65. package/dist/ui/chrome/PageHeader.d.ts +20 -0
  66. package/dist/ui/chrome/PageHeader.d.ts.map +1 -0
  67. package/dist/ui/chrome/PageHeader.js +26 -0
  68. package/dist/ui/chrome/PageHeader.js.map +1 -0
  69. package/dist/ui/chrome/StateCard.d.ts +24 -0
  70. package/dist/ui/chrome/StateCard.d.ts.map +1 -0
  71. package/dist/ui/chrome/StateCard.js +17 -0
  72. package/dist/ui/chrome/StateCard.js.map +1 -0
  73. package/dist/ui/cn.d.ts +3 -0
  74. package/dist/ui/cn.d.ts.map +1 -0
  75. package/dist/ui/cn.js +18 -0
  76. package/dist/ui/cn.js.map +1 -0
  77. package/dist/ui/index.d.ts +33 -0
  78. package/dist/ui/index.d.ts.map +1 -0
  79. package/dist/ui/index.js +61 -0
  80. package/dist/ui/index.js.map +1 -0
  81. package/dist/ui/primitives/alert-dialog.d.ts +21 -0
  82. package/dist/ui/primitives/alert-dialog.d.ts.map +1 -0
  83. package/dist/ui/primitives/alert-dialog.js +72 -0
  84. package/dist/ui/primitives/alert-dialog.js.map +1 -0
  85. package/dist/ui/primitives/badge.d.ts +10 -0
  86. package/dist/ui/primitives/badge.d.ts.map +1 -0
  87. package/dist/ui/primitives/badge.js +60 -0
  88. package/dist/ui/primitives/badge.js.map +1 -0
  89. package/dist/ui/primitives/button.d.ts +12 -0
  90. package/dist/ui/primitives/button.d.ts.map +1 -0
  91. package/dist/ui/primitives/button.js +71 -0
  92. package/dist/ui/primitives/button.js.map +1 -0
  93. package/dist/ui/primitives/card.d.ts +9 -0
  94. package/dist/ui/primitives/card.d.ts.map +1 -0
  95. package/dist/ui/primitives/card.js +58 -0
  96. package/dist/ui/primitives/card.js.map +1 -0
  97. package/dist/ui/primitives/checkbox.d.ts +5 -0
  98. package/dist/ui/primitives/checkbox.d.ts.map +1 -0
  99. package/dist/ui/primitives/checkbox.js +45 -0
  100. package/dist/ui/primitives/checkbox.js.map +1 -0
  101. package/dist/ui/primitives/collapsible.d.ts +6 -0
  102. package/dist/ui/primitives/collapsible.d.ts.map +1 -0
  103. package/dist/ui/primitives/collapsible.js +44 -0
  104. package/dist/ui/primitives/collapsible.js.map +1 -0
  105. package/dist/ui/primitives/dialog.d.ts +22 -0
  106. package/dist/ui/primitives/dialog.d.ts.map +1 -0
  107. package/dist/ui/primitives/dialog.js +68 -0
  108. package/dist/ui/primitives/dialog.js.map +1 -0
  109. package/dist/ui/primitives/dropdown-menu.d.ts +28 -0
  110. package/dist/ui/primitives/dropdown-menu.d.ts.map +1 -0
  111. package/dist/ui/primitives/dropdown-menu.js +83 -0
  112. package/dist/ui/primitives/dropdown-menu.js.map +1 -0
  113. package/dist/ui/primitives/input.d.ts +4 -0
  114. package/dist/ui/primitives/input.d.ts.map +1 -0
  115. package/dist/ui/primitives/input.js +45 -0
  116. package/dist/ui/primitives/input.js.map +1 -0
  117. package/dist/ui/primitives/label.d.ts +6 -0
  118. package/dist/ui/primitives/label.d.ts.map +1 -0
  119. package/dist/ui/primitives/label.js +46 -0
  120. package/dist/ui/primitives/label.js.map +1 -0
  121. package/dist/ui/primitives/overflow-tabs.d.ts +18 -0
  122. package/dist/ui/primitives/overflow-tabs.d.ts.map +1 -0
  123. package/dist/ui/primitives/overflow-tabs.js +84 -0
  124. package/dist/ui/primitives/overflow-tabs.js.map +1 -0
  125. package/dist/ui/primitives/popover.d.ts +9 -0
  126. package/dist/ui/primitives/popover.d.ts.map +1 -0
  127. package/dist/ui/primitives/popover.js +48 -0
  128. package/dist/ui/primitives/popover.js.map +1 -0
  129. package/dist/ui/primitives/radio-group.d.ts +6 -0
  130. package/dist/ui/primitives/radio-group.d.ts.map +1 -0
  131. package/dist/ui/primitives/radio-group.js +52 -0
  132. package/dist/ui/primitives/radio-group.js.map +1 -0
  133. package/dist/ui/primitives/resizable.d.ts +12 -0
  134. package/dist/ui/primitives/resizable.d.ts.map +1 -0
  135. package/dist/ui/primitives/resizable.js +18 -0
  136. package/dist/ui/primitives/resizable.js.map +1 -0
  137. package/dist/ui/primitives/scroll-area.d.ts +6 -0
  138. package/dist/ui/primitives/scroll-area.d.ts.map +1 -0
  139. package/dist/ui/primitives/scroll-area.js +47 -0
  140. package/dist/ui/primitives/scroll-area.js.map +1 -0
  141. package/dist/ui/primitives/select.d.ts +14 -0
  142. package/dist/ui/primitives/select.d.ts.map +1 -0
  143. package/dist/ui/primitives/select.js +71 -0
  144. package/dist/ui/primitives/select.js.map +1 -0
  145. package/dist/ui/primitives/separator.d.ts +5 -0
  146. package/dist/ui/primitives/separator.d.ts.map +1 -0
  147. package/dist/ui/primitives/separator.js +44 -0
  148. package/dist/ui/primitives/separator.js.map +1 -0
  149. package/dist/ui/primitives/sheet.d.ts +26 -0
  150. package/dist/ui/primitives/sheet.d.ts.map +1 -0
  151. package/dist/ui/primitives/sheet.js +82 -0
  152. package/dist/ui/primitives/sheet.js.map +1 -0
  153. package/dist/ui/primitives/skeleton.d.ts +13 -0
  154. package/dist/ui/primitives/skeleton.d.ts.map +1 -0
  155. package/dist/ui/primitives/skeleton.js +29 -0
  156. package/dist/ui/primitives/skeleton.js.map +1 -0
  157. package/dist/ui/primitives/switch.d.ts +5 -0
  158. package/dist/ui/primitives/switch.d.ts.map +1 -0
  159. package/dist/ui/primitives/switch.js +44 -0
  160. package/dist/ui/primitives/switch.js.map +1 -0
  161. package/dist/ui/primitives/table.d.ts +11 -0
  162. package/dist/ui/primitives/table.d.ts.map +1 -0
  163. package/dist/ui/primitives/table.js +64 -0
  164. package/dist/ui/primitives/table.js.map +1 -0
  165. package/dist/ui/primitives/tabs.d.ts +8 -0
  166. package/dist/ui/primitives/tabs.d.ts.map +1 -0
  167. package/dist/ui/primitives/tabs.js +52 -0
  168. package/dist/ui/primitives/tabs.js.map +1 -0
  169. package/dist/ui/primitives/tag-multi-select.d.ts +19 -0
  170. package/dist/ui/primitives/tag-multi-select.d.ts.map +1 -0
  171. package/dist/ui/primitives/tag-multi-select.js +92 -0
  172. package/dist/ui/primitives/tag-multi-select.js.map +1 -0
  173. package/dist/ui/primitives/textarea.d.ts +5 -0
  174. package/dist/ui/primitives/textarea.d.ts.map +1 -0
  175. package/dist/ui/primitives/textarea.js +45 -0
  176. package/dist/ui/primitives/textarea.js.map +1 -0
  177. package/dist/ui/primitives/tooltip.d.ts +8 -0
  178. package/dist/ui/primitives/tooltip.d.ts.map +1 -0
  179. package/dist/ui/primitives/tooltip.js +50 -0
  180. package/dist/ui/primitives/tooltip.js.map +1 -0
  181. package/package.json +27 -4
  182. package/src/index.ts +1 -0
  183. package/src/lib/biome-host/biome-builders.ts +109 -0
  184. package/src/lib/biome-host/biome-navigation.ts +37 -0
  185. package/src/lib/biome-host/biome-scope.tsx +119 -0
  186. package/src/lib/biome-host/biome-scoped-query.ts +130 -0
  187. package/src/lib/biome-host/host-bridge.ts +23 -0
  188. package/src/lib/biome-host/index.ts +4 -0
  189. package/src/lib/capabilities/capability-provider.tsx +95 -0
  190. package/src/lib/capabilities/index.ts +16 -0
  191. package/src/lib/capabilities/types.ts +69 -0
  192. package/src/lib/capabilities/use-capability.ts +72 -0
  193. package/src/session/shell/SessionWorkspaceShell.tsx +2 -2
  194. package/src/session-kit/display/ThinkingPanel.tsx +3 -0
  195. package/src/ui/chrome/AsyncBoundary.tsx +66 -0
  196. package/src/ui/chrome/EmptyState.tsx +184 -0
  197. package/src/ui/chrome/ErrorCard.tsx +68 -0
  198. package/src/ui/chrome/LoadingState.tsx +61 -0
  199. package/src/ui/chrome/PageHeader.tsx +137 -0
  200. package/src/ui/chrome/StateCard.tsx +150 -0
  201. package/src/ui/cn.ts +32 -0
  202. package/src/ui/index.ts +53 -0
  203. package/src/ui/primitives/alert-dialog.tsx +104 -0
  204. package/src/ui/primitives/badge.tsx +32 -0
  205. package/src/ui/primitives/button.tsx +47 -0
  206. package/src/ui/primitives/card.tsx +43 -0
  207. package/src/ui/primitives/checkbox.tsx +26 -0
  208. package/src/ui/primitives/collapsible.tsx +9 -0
  209. package/src/ui/primitives/dialog.tsx +103 -0
  210. package/src/ui/primitives/dropdown-menu.tsx +179 -0
  211. package/src/ui/primitives/input.tsx +22 -0
  212. package/src/ui/primitives/label.tsx +17 -0
  213. package/src/ui/primitives/overflow-tabs.tsx +281 -0
  214. package/src/ui/primitives/popover.tsx +33 -0
  215. package/src/ui/primitives/radio-group.tsx +36 -0
  216. package/src/ui/primitives/resizable.tsx +67 -0
  217. package/src/ui/primitives/scroll-area.tsx +38 -0
  218. package/src/ui/primitives/select.tsx +143 -0
  219. package/src/ui/primitives/separator.tsx +20 -0
  220. package/src/ui/primitives/sheet.tsx +107 -0
  221. package/src/ui/primitives/skeleton.tsx +99 -0
  222. package/src/ui/primitives/switch.tsx +27 -0
  223. package/src/ui/primitives/table.tsx +72 -0
  224. package/src/ui/primitives/tabs.tsx +53 -0
  225. package/src/ui/primitives/tag-multi-select.tsx +241 -0
  226. package/src/ui/primitives/textarea.tsx +21 -0
  227. package/src/ui/primitives/tooltip.tsx +30 -0
  228. package/dist/lib/biome-host/composition-validation.d.ts +0 -22
  229. package/dist/lib/biome-host/composition-validation.d.ts.map +0 -1
  230. package/dist/lib/biome-host/composition-validation.js +0 -127
  231. package/dist/lib/biome-host/composition-validation.js.map +0 -1
  232. package/dist/registry/lib/composition-validation-host.d.ts +0 -3
  233. package/dist/registry/lib/composition-validation-host.d.ts.map +0 -1
  234. package/dist/registry/lib/composition-validation-host.js +0 -10
  235. package/dist/registry/lib/composition-validation-host.js.map +0 -1
@@ -0,0 +1,241 @@
1
+ import { Check, ChevronsUpDown, Plus, Search, X } from 'lucide-react';
2
+ import { useMemo, useRef, useState, type KeyboardEvent } from 'react';
3
+
4
+ import { Badge } from './badge';
5
+ import { Button } from './button';
6
+ import { Input } from './input';
7
+ import { Popover, PopoverContent, PopoverTrigger } from './popover';
8
+ import { ScrollArea } from './scroll-area';
9
+ import { cn } from '../cn';
10
+
11
+ export interface TagOption {
12
+ value: string;
13
+ label: string;
14
+ hint?: string;
15
+ }
16
+
17
+ export interface TagMultiSelectProps {
18
+ value: string[];
19
+ onChange: (next: string[]) => void;
20
+ options: TagOption[];
21
+ placeholder?: string;
22
+ searchPlaceholder?: string;
23
+ emptyMessage?: string;
24
+ /** Allow the user to add values that aren't in the predefined options. */
25
+ allowCustom?: boolean;
26
+ /** Badge tone for selected chips. */
27
+ chipVariant?: 'default' | 'secondary' | 'success' | 'destructive';
28
+ /** Extra className for the trigger. */
29
+ triggerClassName?: string;
30
+ disabled?: boolean;
31
+ }
32
+
33
+ const CHIP_CLASSES: Record<NonNullable<TagMultiSelectProps['chipVariant']>, string> = {
34
+ default: 'bg-primary/10 text-primary border-primary/20',
35
+ secondary: 'bg-secondary text-secondary-foreground border-secondary',
36
+ success: 'bg-success/10 text-success border-success/20',
37
+ destructive: 'bg-destructive/10 text-destructive border-destructive/20',
38
+ };
39
+
40
+ export function TagMultiSelect({
41
+ value,
42
+ onChange,
43
+ options,
44
+ placeholder = 'Select…',
45
+ searchPlaceholder = 'Search…',
46
+ emptyMessage = 'No matches',
47
+ allowCustom = false,
48
+ chipVariant = 'default',
49
+ triggerClassName,
50
+ disabled,
51
+ }: Readonly<TagMultiSelectProps>) {
52
+ const [open, setOpen] = useState(false);
53
+ const [search, setSearch] = useState('');
54
+ const inputRef = useRef<HTMLInputElement>(null);
55
+
56
+ const selectedSet = useMemo(() => new Set(value), [value]);
57
+
58
+ const optionByValue = useMemo(() => {
59
+ const map = new Map<string, TagOption>();
60
+ options.forEach((o) => map.set(o.value, o));
61
+ return map;
62
+ }, [options]);
63
+
64
+ const filteredOptions = useMemo(() => {
65
+ const q = search.trim().toLowerCase();
66
+ if (!q) return options;
67
+ return options.filter(
68
+ (o) =>
69
+ o.label.toLowerCase().includes(q) ||
70
+ o.value.toLowerCase().includes(q) ||
71
+ (o.hint?.toLowerCase().includes(q) ?? false),
72
+ );
73
+ }, [options, search]);
74
+
75
+ const normalizedSearch = search.trim();
76
+ const canAddCustom =
77
+ allowCustom &&
78
+ normalizedSearch.length > 0 &&
79
+ !optionByValue.has(normalizedSearch) &&
80
+ !selectedSet.has(normalizedSearch);
81
+
82
+ const toggle = (val: string) => {
83
+ if (selectedSet.has(val)) {
84
+ onChange(value.filter((v) => v !== val));
85
+ } else {
86
+ onChange([...value, val]);
87
+ }
88
+ };
89
+
90
+ const remove = (val: string, e?: React.MouseEvent) => {
91
+ e?.stopPropagation();
92
+ onChange(value.filter((v) => v !== val));
93
+ };
94
+
95
+ const addCustom = () => {
96
+ if (!canAddCustom) return;
97
+ onChange([...value, normalizedSearch]);
98
+ setSearch('');
99
+ inputRef.current?.focus();
100
+ };
101
+
102
+ const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
103
+ if (e.key === 'Enter') {
104
+ e.preventDefault();
105
+ if (canAddCustom) {
106
+ addCustom();
107
+ } else if (filteredOptions.length === 1) {
108
+ toggle(filteredOptions[0].value);
109
+ }
110
+ }
111
+ };
112
+
113
+ const chipClass = CHIP_CLASSES[chipVariant];
114
+
115
+ return (
116
+ <Popover open={open} onOpenChange={setOpen}>
117
+ <PopoverTrigger asChild>
118
+ <Button
119
+ variant="outline"
120
+ role="combobox"
121
+ aria-expanded={open}
122
+ disabled={disabled}
123
+ className={cn(
124
+ 'w-full min-h-9 h-auto justify-between font-normal py-1.5',
125
+ triggerClassName,
126
+ )}
127
+ >
128
+ <div className="flex flex-wrap gap-1 flex-1 text-left">
129
+ {value.length === 0 && (
130
+ <span className="text-ink-3 text-body-1">{placeholder}</span>
131
+ )}
132
+ {value.map((val) => {
133
+ const opt = optionByValue.get(val);
134
+ return (
135
+ <Badge
136
+ key={val}
137
+ variant="outline"
138
+ className={cn('text-body-1 gap-1 pr-1', chipClass)}
139
+ >
140
+ <span className="truncate max-w-[160px]">{opt?.label ?? val}</span>
141
+ <span
142
+ role="button"
143
+ tabIndex={-1}
144
+ onClick={(e) => remove(val, e)}
145
+ onKeyDown={(e) => {
146
+ if (e.key === 'Enter' || e.key === ' ') {
147
+ e.preventDefault();
148
+ e.stopPropagation();
149
+ remove(val);
150
+ }
151
+ }}
152
+ className="rounded-sm hover:bg-paper-elev p-0.5"
153
+ aria-label={`Remove ${opt?.label ?? val}`}
154
+ >
155
+ <X className="h-4 w-4" />
156
+ </span>
157
+ </Badge>
158
+ );
159
+ })}
160
+ </div>
161
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
162
+ </Button>
163
+ </PopoverTrigger>
164
+ <PopoverContent
165
+ className="w-[--radix-popover-trigger-width] p-0"
166
+ align="start"
167
+ onOpenAutoFocus={(e) => {
168
+ e.preventDefault();
169
+ setTimeout(() => inputRef.current?.focus(), 30);
170
+ }}
171
+ >
172
+ <div className="relative border-b">
173
+ <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-ink-3" />
174
+ <Input
175
+ ref={inputRef}
176
+ value={search}
177
+ onChange={(e) => setSearch(e.target.value)}
178
+ onKeyDown={onKeyDown}
179
+ placeholder={searchPlaceholder}
180
+ className="h-9 text-body-1 pl-8 border-0 rounded-none focus-visible:ring-0 focus-visible:ring-offset-0"
181
+ />
182
+ </div>
183
+
184
+ <ScrollArea className="max-h-56">
185
+ <div className="p-1">
186
+ {filteredOptions.length === 0 && !canAddCustom && (
187
+ <p className="text-body-1 text-ink-3 text-center py-3">{emptyMessage}</p>
188
+ )}
189
+
190
+ {filteredOptions.map((opt) => {
191
+ const selected = selectedSet.has(opt.value);
192
+ return (
193
+ <button
194
+ key={opt.value}
195
+ type="button"
196
+ onClick={() => toggle(opt.value)}
197
+ className={cn(
198
+ 'w-full text-left text-body-1 px-2 py-1.5 rounded hover:bg-paper-elev transition-colors flex items-start gap-2',
199
+ selected && 'bg-paper-elev/60',
200
+ )}
201
+ >
202
+ <span
203
+ className={cn(
204
+ 'mt-0.5 h-4 w-4 shrink-0 rounded border flex items-center justify-center',
205
+ selected
206
+ ? 'bg-primary border-primary text-primary-foreground'
207
+ : 'border-muted-foreground/40',
208
+ )}
209
+ >
210
+ {selected && <Check className="h-4 w-4" />}
211
+ </span>
212
+ <span className="flex-1 min-w-0">
213
+ <span className="font-medium block truncate">{opt.label}</span>
214
+ {opt.hint && (
215
+ <span className="text-body-1 text-ink-3 block truncate">
216
+ {opt.hint}
217
+ </span>
218
+ )}
219
+ </span>
220
+ </button>
221
+ );
222
+ })}
223
+
224
+ {canAddCustom && (
225
+ <button
226
+ type="button"
227
+ onClick={addCustom}
228
+ className="w-full text-left text-body-1 px-2 py-1.5 rounded hover:bg-paper-elev transition-colors flex items-center gap-2 border-t mt-1 pt-2"
229
+ >
230
+ <Plus className="h-4 w-4 text-ink-3" />
231
+ <span>
232
+ Add <span className="font-mono font-medium">{normalizedSearch}</span>
233
+ </span>
234
+ </button>
235
+ )}
236
+ </div>
237
+ </ScrollArea>
238
+ </PopoverContent>
239
+ </Popover>
240
+ );
241
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from '../cn';
4
+
5
+ export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
6
+
7
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => {
8
+ return (
9
+ <textarea
10
+ className={cn(
11
+ "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-body-1 ring-offset-background placeholder:text-ink-3 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
12
+ className,
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ );
18
+ });
19
+ Textarea.displayName = "Textarea";
20
+
21
+ export { Textarea };
@@ -0,0 +1,30 @@
1
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
2
+ import * as React from "react";
3
+
4
+ import { cn } from '../cn';
5
+
6
+ const TooltipProvider = TooltipPrimitive.Provider;
7
+
8
+ const Tooltip = TooltipPrimitive.Root;
9
+
10
+ const TooltipTrigger = TooltipPrimitive.Trigger;
11
+
12
+ const TooltipContent = React.forwardRef<
13
+ React.ElementRef<typeof TooltipPrimitive.Content>,
14
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
15
+ >(({ className, sideOffset = 4, ...props }, ref) => (
16
+ <TooltipPrimitive.Portal>
17
+ <TooltipPrimitive.Content
18
+ ref={ref}
19
+ sideOffset={sideOffset}
20
+ className={cn(
21
+ "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-body-1 text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
22
+ className,
23
+ )}
24
+ {...props}
25
+ />
26
+ </TooltipPrimitive.Portal>
27
+ ));
28
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
29
+
30
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
@@ -1,22 +0,0 @@
1
- import type { FrontendBiome } from './frontend-biome';
2
- export declare enum CompositionDiagnosticSeverity {
3
- Error = "error",
4
- Warning = "warning"
5
- }
6
- export declare enum CompositionDiagnosticCode {
7
- DuplicateRoutePath = "duplicate_route_path",
8
- DuplicateNavId = "duplicate_nav_id",
9
- UnknownSlot = "unknown_slot",
10
- OrphanedPanelSlot = "orphaned_panel_slot"
11
- }
12
- export interface CompositionDiagnostic {
13
- readonly code: CompositionDiagnosticCode;
14
- readonly severity: CompositionDiagnosticSeverity;
15
- readonly biomeIds: readonly string[];
16
- readonly message: string;
17
- }
18
- export interface ValidateBiomeCompositionOptions {
19
- readonly knownSlots: ReadonlySet<string>;
20
- }
21
- export declare function validateBiomeComposition(biomes: readonly FrontendBiome[], options: ValidateBiomeCompositionOptions): readonly CompositionDiagnostic[];
22
- //# sourceMappingURL=composition-validation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"composition-validation.d.ts","sourceRoot":"","sources":["../../../src/lib/biome-host/composition-validation.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,oBAAY,6BAA6B;IAMvC,KAAK,UAAU;IAOf,OAAO,YAAY;CACpB;AAGD,oBAAY,yBAAyB;IAEnC,kBAAkB,yBAAyB;IAE3C,cAAc,qBAAqB;IAKnC,WAAW,iBAAiB;IAK5B,iBAAiB,wBAAwB;CAC1C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,6BAA6B,CAAC;IAEjD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAErC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,+BAA+B;IAM9C,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC1C;AAwBD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,OAAO,EAAE,+BAA+B,GACvC,SAAS,qBAAqB,EAAE,CA6FlC"}
@@ -1,127 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CompositionDiagnosticCode = exports.CompositionDiagnosticSeverity = void 0;
4
- exports.validateBiomeComposition = validateBiomeComposition;
5
- var CompositionDiagnosticSeverity;
6
- (function (CompositionDiagnosticSeverity) {
7
- CompositionDiagnosticSeverity["Error"] = "error";
8
- CompositionDiagnosticSeverity["Warning"] = "warning";
9
- })(CompositionDiagnosticSeverity || (exports.CompositionDiagnosticSeverity = CompositionDiagnosticSeverity = {}));
10
- var CompositionDiagnosticCode;
11
- (function (CompositionDiagnosticCode) {
12
- CompositionDiagnosticCode["DuplicateRoutePath"] = "duplicate_route_path";
13
- CompositionDiagnosticCode["DuplicateNavId"] = "duplicate_nav_id";
14
- CompositionDiagnosticCode["UnknownSlot"] = "unknown_slot";
15
- CompositionDiagnosticCode["OrphanedPanelSlot"] = "orphaned_panel_slot";
16
- })(CompositionDiagnosticCode || (exports.CompositionDiagnosticCode = CompositionDiagnosticCode = {}));
17
- function biomeOwnedSlotOwner(slot) {
18
- const slashIndex = slot.indexOf('/');
19
- if (slashIndex <= 0)
20
- return null;
21
- if (slot.indexOf('/', slashIndex + 1) !== -1)
22
- return null;
23
- const owner = slot.slice(0, slashIndex);
24
- const name = slot.slice(slashIndex + 1);
25
- if (owner.length === 0 || name.length === 0)
26
- return null;
27
- return owner;
28
- }
29
- function validateBiomeComposition(biomes, options) {
30
- const diagnostics = [];
31
- const biomeIds = new Set(biomes.map((b) => b.id));
32
- const routeOwners = new Map();
33
- for (const biome of biomes) {
34
- for (const route of biome.routes ?? []) {
35
- const scope = route.projectScoped ? 'project' : 'root';
36
- const key = `${scope}:${route.path}`;
37
- const owners = routeOwners.get(key);
38
- if (owners)
39
- owners.push(biome.id);
40
- else
41
- routeOwners.set(key, [biome.id]);
42
- }
43
- }
44
- for (const [key, owners] of routeOwners) {
45
- if (owners.length < 2)
46
- continue;
47
- const [scope, path] = splitFirst(key, ':');
48
- diagnostics.push({
49
- code: CompositionDiagnosticCode.DuplicateRoutePath,
50
- severity: CompositionDiagnosticSeverity.Error,
51
- biomeIds: owners,
52
- message: `Route path '${path}' (${scope}-scoped) is contributed by multiple biomes: ` +
53
- `${owners.join(', ')}. Each route must be unique within its scope — ` +
54
- `only one would mount and the others would be unreachable.`,
55
- });
56
- }
57
- const navOwners = new Map();
58
- for (const biome of biomes) {
59
- for (const navItem of biome.navItems ?? []) {
60
- const owners = navOwners.get(navItem.id);
61
- if (owners)
62
- owners.push(biome.id);
63
- else
64
- navOwners.set(navItem.id, [biome.id]);
65
- }
66
- }
67
- for (const [navId, owners] of navOwners) {
68
- if (owners.length < 2)
69
- continue;
70
- diagnostics.push({
71
- code: CompositionDiagnosticCode.DuplicateNavId,
72
- severity: CompositionDiagnosticSeverity.Error,
73
- biomeIds: owners,
74
- message: `Nav id '${navId}' is contributed by multiple biomes: ${owners.join(', ')}. ` +
75
- `Nav ids must be unique — a duplicate silently overwrites the ` +
76
- `feature-route mapping and collides as a render key.`,
77
- });
78
- }
79
- for (const biome of biomes) {
80
- for (const panel of biome.panels ?? []) {
81
- if (options.knownSlots.has(panel.slot))
82
- continue;
83
- const owner = biomeOwnedSlotOwner(panel.slot);
84
- if (owner === null) {
85
- diagnostics.push({
86
- code: CompositionDiagnosticCode.UnknownSlot,
87
- severity: CompositionDiagnosticSeverity.Warning,
88
- biomeIds: [biome.id],
89
- message: `Biome '${biome.id}' panel '${panel.id}' targets slot '${panel.slot}', ` +
90
- `which is neither a known host slot nor a well-formed ` +
91
- `'<owner>/<name>' biome-owned slot. It will never render — ` +
92
- `check for a typo against the host slot catalog.`,
93
- });
94
- continue;
95
- }
96
- if (!biomeIds.has(owner)) {
97
- diagnostics.push({
98
- code: CompositionDiagnosticCode.OrphanedPanelSlot,
99
- severity: CompositionDiagnosticSeverity.Warning,
100
- biomeIds: [biome.id],
101
- message: `Biome '${biome.id}' panel '${panel.id}' targets biome-owned slot ` +
102
- `'${panel.slot}', but its owner biome '${owner}' is not registered. ` +
103
- `The panel will never render until '${owner}' is enabled.`,
104
- });
105
- }
106
- }
107
- }
108
- return sortDiagnostics(diagnostics);
109
- }
110
- function splitFirst(value, sep) {
111
- const idx = value.indexOf(sep);
112
- if (idx === -1)
113
- return [value, ''];
114
- return [value.slice(0, idx), value.slice(idx + sep.length)];
115
- }
116
- function sortDiagnostics(diagnostics) {
117
- return diagnostics.slice().sort((a, b) => {
118
- if (a.code !== b.code)
119
- return a.code.localeCompare(b.code);
120
- const ai = a.biomeIds.join(',');
121
- const bi = b.biomeIds.join(',');
122
- if (ai !== bi)
123
- return ai.localeCompare(bi);
124
- return a.message.localeCompare(b.message);
125
- });
126
- }
127
- //# sourceMappingURL=composition-validation.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"composition-validation.js","sourceRoot":"","sources":["../../../src/lib/biome-host/composition-validation.ts"],"names":[],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import { type CompositionDiagnostic, type FrontendBiome } from '../../index';
2
- export declare function validateHostBiomeComposition(biomes: readonly FrontendBiome[]): readonly CompositionDiagnostic[];
3
- //# sourceMappingURL=composition-validation-host.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"composition-validation-host.d.ts","sourceRoot":"","sources":["../../../src/registry/lib/composition-validation-host.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAEnB,MAAM,aAAa,CAAC;AAkBrB,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,SAAS,aAAa,EAAE,GAC/B,SAAS,qBAAqB,EAAE,CAElC"}
@@ -1,10 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateHostBiomeComposition = validateHostBiomeComposition;
4
- const index_1 = require("../../index");
5
- const extension_points_1 = require("./extension-points");
6
- const HOST_SLOT_IDS = new Set(Object.values(extension_points_1.HostExtensionSlots));
7
- function validateHostBiomeComposition(biomes) {
8
- return (0, index_1.validateBiomeComposition)(biomes, { knownSlots: HOST_SLOT_IDS });
9
- }
10
- //# sourceMappingURL=composition-validation-host.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"composition-validation-host.js","sourceRoot":"","sources":["../../../src/registry/lib/composition-validation-host.ts"],"names":[],"mappings":";;AAgCA,oEAIC;AA1BD,uCAIqB;AAErB,yDAAwD;AAOxD,MAAM,aAAa,GAAwB,IAAI,GAAG,CAChD,MAAM,CAAC,MAAM,CAAC,qCAAkB,CAAC,CAClC,CAAC;AAOF,SAAgB,4BAA4B,CAC1C,MAAgC;IAEhC,OAAO,IAAA,gCAAwB,EAAC,MAAM,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,CAAC;AACzE,CAAC"}