@proyecto-viviana/solidaria-components 0.2.9 → 0.3.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 (222) hide show
  1. package/README.md +39 -272
  2. package/dist/ActionBar.d.ts +21 -13
  3. package/dist/ActionBar.d.ts.map +1 -1
  4. package/dist/ActionGroup.d.ts +8 -8
  5. package/dist/ActionGroup.d.ts.map +1 -1
  6. package/dist/Alert.d.ts +5 -5
  7. package/dist/Alert.d.ts.map +1 -1
  8. package/dist/Autocomplete.d.ts +5 -5
  9. package/dist/Autocomplete.d.ts.map +1 -1
  10. package/dist/Breadcrumbs.d.ts +18 -7
  11. package/dist/Breadcrumbs.d.ts.map +1 -1
  12. package/dist/Button.d.ts +24 -5
  13. package/dist/Button.d.ts.map +1 -1
  14. package/dist/Calendar.d.ts +38 -7
  15. package/dist/Calendar.d.ts.map +1 -1
  16. package/dist/Checkbox.d.ts +32 -7
  17. package/dist/Checkbox.d.ts.map +1 -1
  18. package/dist/Collection.d.ts +19 -14
  19. package/dist/Collection.d.ts.map +1 -1
  20. package/dist/Color.d.ts +103 -14
  21. package/dist/Color.d.ts.map +1 -1
  22. package/dist/ColorEditor.d.ts +6 -6
  23. package/dist/ColorEditor.d.ts.map +1 -1
  24. package/dist/ComboBox.d.ts +85 -19
  25. package/dist/ComboBox.d.ts.map +1 -1
  26. package/dist/ContextualHelpTrigger.d.ts +2 -2
  27. package/dist/ContextualHelpTrigger.d.ts.map +1 -1
  28. package/dist/DateField.d.ts +8 -6
  29. package/dist/DateField.d.ts.map +1 -1
  30. package/dist/DatePicker.d.ts +53 -22
  31. package/dist/DatePicker.d.ts.map +1 -1
  32. package/dist/DateRangePickerContext.d.ts +30 -0
  33. package/dist/DateRangePickerContext.d.ts.map +1 -0
  34. package/dist/Dialog.d.ts +5 -5
  35. package/dist/Dialog.d.ts.map +1 -1
  36. package/dist/Disclosure.d.ts +23 -5
  37. package/dist/Disclosure.d.ts.map +1 -1
  38. package/dist/DragAndDrop.d.ts +6 -6
  39. package/dist/DragAndDrop.d.ts.map +1 -1
  40. package/dist/DragPreview.d.ts +2 -2
  41. package/dist/DragPreview.d.ts.map +1 -1
  42. package/dist/DropZone.d.ts +4 -4
  43. package/dist/DropZone.d.ts.map +1 -1
  44. package/dist/FieldError.d.ts +9 -5
  45. package/dist/FieldError.d.ts.map +1 -1
  46. package/dist/FileTrigger.d.ts +3 -3
  47. package/dist/FileTrigger.d.ts.map +1 -1
  48. package/dist/Focusable.d.ts +2 -2
  49. package/dist/Focusable.d.ts.map +1 -1
  50. package/dist/Form.d.ts +18 -4
  51. package/dist/Form.d.ts.map +1 -1
  52. package/dist/GridList.d.ts +32 -12
  53. package/dist/GridList.d.ts.map +1 -1
  54. package/dist/HiddenDateInput.d.ts +26 -0
  55. package/dist/HiddenDateInput.d.ts.map +1 -0
  56. package/dist/HiddenTimeInput.d.ts +25 -0
  57. package/dist/HiddenTimeInput.d.ts.map +1 -0
  58. package/dist/Icon.d.ts +5 -5
  59. package/dist/Icon.d.ts.map +1 -1
  60. package/dist/Keyboard.d.ts +1 -1
  61. package/dist/Landmark.d.ts +3 -3
  62. package/dist/Landmark.d.ts.map +1 -1
  63. package/dist/Link.d.ts +10 -4
  64. package/dist/Link.d.ts.map +1 -1
  65. package/dist/ListBox.d.ts +32 -12
  66. package/dist/ListBox.d.ts.map +1 -1
  67. package/dist/ListDropTargetDelegate.d.ts +6 -6
  68. package/dist/ListDropTargetDelegate.d.ts.map +1 -1
  69. package/dist/Menu.d.ts +65 -14
  70. package/dist/Menu.d.ts.map +1 -1
  71. package/dist/Meter.d.ts +3 -3
  72. package/dist/Meter.d.ts.map +1 -1
  73. package/dist/Modal.d.ts +5 -5
  74. package/dist/Modal.d.ts.map +1 -1
  75. package/dist/NumberField.d.ts +8 -12
  76. package/dist/NumberField.d.ts.map +1 -1
  77. package/dist/Popover.d.ts +28 -5
  78. package/dist/Popover.d.ts.map +1 -1
  79. package/dist/Pressable.d.ts +2 -2
  80. package/dist/Pressable.d.ts.map +1 -1
  81. package/dist/ProgressBar.d.ts +5 -3
  82. package/dist/ProgressBar.d.ts.map +1 -1
  83. package/dist/RadioGroup.d.ts +43 -9
  84. package/dist/RadioGroup.d.ts.map +1 -1
  85. package/dist/RangeCalendar.d.ts +34 -7
  86. package/dist/RangeCalendar.d.ts.map +1 -1
  87. package/dist/RouterProvider.d.ts +2 -2
  88. package/dist/RouterProvider.d.ts.map +1 -1
  89. package/dist/SearchField.d.ts +23 -20
  90. package/dist/SearchField.d.ts.map +1 -1
  91. package/dist/Select.d.ts +41 -11
  92. package/dist/Select.d.ts.map +1 -1
  93. package/dist/SelectionIndicator.d.ts +3 -3
  94. package/dist/SelectionIndicator.d.ts.map +1 -1
  95. package/dist/Separator.d.ts +9 -3
  96. package/dist/Separator.d.ts.map +1 -1
  97. package/dist/SharedElementTransition.d.ts +6 -4
  98. package/dist/SharedElementTransition.d.ts.map +1 -1
  99. package/dist/Slider.d.ts +12 -8
  100. package/dist/Slider.d.ts.map +1 -1
  101. package/dist/StepList.d.ts +90 -0
  102. package/dist/StepList.d.ts.map +1 -0
  103. package/dist/Switch.d.ts +11 -5
  104. package/dist/Switch.d.ts.map +1 -1
  105. package/dist/Table.d.ts +187 -23
  106. package/dist/Table.d.ts.map +1 -1
  107. package/dist/Tabs.d.ts +45 -9
  108. package/dist/Tabs.d.ts.map +1 -1
  109. package/dist/TagGroup.d.ts +12 -10
  110. package/dist/TagGroup.d.ts.map +1 -1
  111. package/dist/Text.d.ts +2 -2
  112. package/dist/TextField.d.ts +15 -11
  113. package/dist/TextField.d.ts.map +1 -1
  114. package/dist/TimeField.d.ts +6 -6
  115. package/dist/TimeField.d.ts.map +1 -1
  116. package/dist/Toast.d.ts +29 -14
  117. package/dist/Toast.d.ts.map +1 -1
  118. package/dist/ToggleButton.d.ts +11 -5
  119. package/dist/ToggleButton.d.ts.map +1 -1
  120. package/dist/ToggleButtonGroup.d.ts +7 -7
  121. package/dist/ToggleButtonGroup.d.ts.map +1 -1
  122. package/dist/Toolbar.d.ts +7 -3
  123. package/dist/Toolbar.d.ts.map +1 -1
  124. package/dist/Tooltip.d.ts +50 -8
  125. package/dist/Tooltip.d.ts.map +1 -1
  126. package/dist/Tree.d.ts +66 -17
  127. package/dist/Tree.d.ts.map +1 -1
  128. package/dist/Virtualizer.d.ts +12 -12
  129. package/dist/Virtualizer.d.ts.map +1 -1
  130. package/dist/VirtualizerLayouts.d.ts +2 -2
  131. package/dist/VirtualizerLayouts.d.ts.map +1 -1
  132. package/dist/VisuallyHidden.d.ts +1 -1
  133. package/dist/VisuallyHidden.d.ts.map +1 -1
  134. package/dist/contexts.d.ts +5 -1
  135. package/dist/contexts.d.ts.map +1 -1
  136. package/dist/index.d.ts +73 -71
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +23247 -18564
  139. package/dist/index.js.map +1 -1
  140. package/dist/index.jsx +18110 -0
  141. package/dist/index.jsx.map +1 -0
  142. package/dist/useDragAndDrop.d.ts +13 -13
  143. package/dist/useDragAndDrop.d.ts.map +1 -1
  144. package/dist/utils.d.ts +2 -2
  145. package/dist/utils.d.ts.map +1 -1
  146. package/dist/virtualizer/Layout.d.ts +1 -1
  147. package/dist/virtualizer/Layout.d.ts.map +1 -1
  148. package/package.json +31 -32
  149. package/src/ActionBar.tsx +75 -72
  150. package/src/ActionGroup.tsx +53 -61
  151. package/src/Alert.tsx +17 -42
  152. package/src/Autocomplete.tsx +39 -44
  153. package/src/Breadcrumbs.tsx +149 -80
  154. package/src/Button.tsx +267 -70
  155. package/src/Calendar.tsx +218 -138
  156. package/src/Checkbox.tsx +413 -121
  157. package/src/Collection.tsx +67 -58
  158. package/src/Color.tsx +803 -380
  159. package/src/ColorEditor.tsx +131 -149
  160. package/src/ComboBox.tsx +414 -249
  161. package/src/ContextualHelpTrigger.tsx +86 -74
  162. package/src/DateField.tsx +185 -91
  163. package/src/DatePicker.tsx +524 -213
  164. package/src/DateRangePickerContext.tsx +44 -0
  165. package/src/Dialog.tsx +156 -118
  166. package/src/Disclosure.tsx +127 -80
  167. package/src/DragAndDrop.tsx +60 -54
  168. package/src/DragPreview.tsx +13 -11
  169. package/src/DropZone.tsx +42 -22
  170. package/src/FieldError.tsx +45 -23
  171. package/src/FileTrigger.tsx +19 -19
  172. package/src/Focusable.tsx +21 -24
  173. package/src/Form.tsx +71 -16
  174. package/src/GridList.tsx +273 -197
  175. package/src/HiddenDateInput.tsx +153 -0
  176. package/src/HiddenTimeInput.tsx +133 -0
  177. package/src/Icon.tsx +22 -43
  178. package/src/Keyboard.tsx +3 -3
  179. package/src/Landmark.tsx +37 -63
  180. package/src/Link.tsx +125 -75
  181. package/src/ListBox.tsx +332 -233
  182. package/src/ListDropTargetDelegate.ts +81 -80
  183. package/src/Menu.tsx +1023 -274
  184. package/src/Meter.tsx +38 -56
  185. package/src/Modal.tsx +243 -175
  186. package/src/NumberField.tsx +139 -143
  187. package/src/Popover.tsx +386 -233
  188. package/src/Pressable.tsx +21 -21
  189. package/src/ProgressBar.tsx +48 -57
  190. package/src/RadioGroup.tsx +524 -122
  191. package/src/RangeCalendar.tsx +157 -90
  192. package/src/RouterProvider.tsx +30 -47
  193. package/src/SearchField.tsx +362 -143
  194. package/src/Select.tsx +656 -233
  195. package/src/SelectionIndicator.tsx +18 -15
  196. package/src/Separator.tsx +47 -49
  197. package/src/SharedElementTransition.tsx +103 -97
  198. package/src/Slider.tsx +138 -98
  199. package/src/StepList.tsx +272 -0
  200. package/src/Switch.tsx +93 -46
  201. package/src/Table.tsx +1308 -342
  202. package/src/Tabs.tsx +324 -103
  203. package/src/TagGroup.tsx +139 -126
  204. package/src/Text.tsx +3 -3
  205. package/src/TextField.tsx +389 -79
  206. package/src/TimeField.tsx +136 -76
  207. package/src/Toast.tsx +209 -157
  208. package/src/ToggleButton.tsx +47 -37
  209. package/src/ToggleButtonGroup.tsx +39 -34
  210. package/src/Toolbar.tsx +54 -69
  211. package/src/Tooltip.tsx +387 -119
  212. package/src/Tree.tsx +651 -368
  213. package/src/Virtualizer.tsx +208 -180
  214. package/src/VirtualizerLayouts.ts +45 -30
  215. package/src/VisuallyHidden.tsx +19 -19
  216. package/src/contexts.ts +29 -37
  217. package/src/index.ts +110 -195
  218. package/src/useDragAndDrop.ts +87 -71
  219. package/src/utils.tsx +40 -55
  220. package/src/virtualizer/Layout.ts +14 -22
  221. package/dist/index.ssr.js +0 -16996
  222. package/dist/index.ssr.js.map +0 -1
package/src/Tabs.tsx CHANGED
@@ -9,12 +9,15 @@ import {
9
9
  type Accessor,
10
10
  type JSX,
11
11
  createContext,
12
+ createEffect,
12
13
  createMemo,
14
+ createSignal,
13
15
  splitProps,
14
16
  useContext,
15
17
  For,
16
18
  Show,
17
- } from 'solid-js';
19
+ onCleanup,
20
+ } from "solid-js";
18
21
  import {
19
22
  createTabList,
20
23
  createTab,
@@ -24,14 +27,14 @@ import {
24
27
  type AriaTabListProps,
25
28
  type AriaTabProps,
26
29
  type AriaTabPanelProps,
27
- } from '@proyecto-viviana/solidaria';
30
+ } from "@proyecto-viviana/solidaria";
28
31
  import {
29
32
  createTabListState,
30
33
  type TabListState,
31
34
  type Key,
32
35
  type TabOrientation,
33
36
  type KeyboardActivation,
34
- } from '@proyecto-viviana/solid-stately';
37
+ } from "@proyecto-viviana/solid-stately";
35
38
  import {
36
39
  type RenderChildren,
37
40
  type ClassNameOrFunction,
@@ -39,24 +42,20 @@ import {
39
42
  type SlotProps,
40
43
  useRenderProps,
41
44
  filterDOMProps,
42
- } from './utils';
45
+ } from "./utils";
43
46
  import {
44
47
  SelectionIndicator,
45
48
  SelectionIndicatorContext,
46
49
  type SelectionIndicatorContextValue,
47
- } from './SelectionIndicator';
48
- import { SharedElementTransition } from './SharedElementTransition';
50
+ } from "./SelectionIndicator";
51
+ import { SharedElementTransition } from "./SharedElementTransition";
49
52
 
50
53
  export {
51
54
  SelectionIndicator,
52
55
  SelectionIndicatorContext,
53
56
  type SelectionIndicatorProps,
54
57
  type SelectionIndicatorRenderProps,
55
- } from './SelectionIndicator';
56
-
57
- // ============================================
58
- // TYPES
59
- // ============================================
58
+ } from "./SelectionIndicator";
60
59
 
61
60
  export interface TabsRenderProps {
62
61
  /** The orientation of the tabs. */
@@ -94,6 +93,8 @@ export interface TabsProps<T> extends SlotProps {
94
93
  class?: ClassNameOrFunction<TabsRenderProps>;
95
94
  /** The inline style for the element. */
96
95
  style?: StyleOrFunction<TabsRenderProps>;
96
+ /** Ref for the root tabs element. */
97
+ ref?: RefLike<HTMLDivElement>;
97
98
  }
98
99
 
99
100
  export interface TabListRenderProps {
@@ -107,9 +108,11 @@ export interface TabListRenderProps {
107
108
  isFocusVisible: boolean;
108
109
  }
109
110
 
110
- export interface TabListProps<T> extends Omit<AriaTabListProps, 'children'>, SlotProps {
111
- /** The children of the tab list - render function for each item. */
112
- children: (item: T) => JSX.Element;
111
+ export interface TabListProps<T> extends Omit<AriaTabListProps, "children">, SlotProps {
112
+ /** Items to render in the tab list. */
113
+ items?: T[];
114
+ /** The children of the tab list - static tabs or a render function for each item. */
115
+ children?: JSX.Element | ((item: T) => JSX.Element);
113
116
  /** The CSS className for the element. */
114
117
  class?: ClassNameOrFunction<TabListRenderProps>;
115
118
  /** The inline style for the element. */
@@ -131,7 +134,7 @@ export interface TabRenderProps {
131
134
  isDisabled: boolean;
132
135
  }
133
136
 
134
- export interface TabProps extends Omit<AriaTabProps, 'key'>, SlotProps {
137
+ export interface TabProps extends Omit<AriaTabProps, "key">, SlotProps {
135
138
  /** The unique key for the tab. */
136
139
  id: Key;
137
140
  /** The children of the tab. */
@@ -149,6 +152,14 @@ export interface TabPanelRenderProps {
149
152
  isFocused: boolean;
150
153
  /** Whether the panel has visible focus ring. */
151
154
  isFocusVisible: boolean;
155
+ /** Whether the panel is non-interactive while force mounted. */
156
+ isInert: boolean;
157
+ /** Whether the panel is currently entering. */
158
+ isEntering: boolean;
159
+ /** Whether the panel is currently exiting. */
160
+ isExiting: boolean;
161
+ /** State of the tab list. */
162
+ state: TabListState<unknown> | null;
152
163
  }
153
164
 
154
165
  export interface TabPanelProps extends AriaTabPanelProps, SlotProps {
@@ -162,21 +173,63 @@ export interface TabPanelProps extends AriaTabPanelProps, SlotProps {
162
173
  shouldForceMount?: boolean;
163
174
  }
164
175
 
165
- // ============================================
166
- // CONTEXT
167
- // ============================================
176
+ export interface TabPanelsProps extends SlotProps {
177
+ /** The grouped tab panels. */
178
+ children?: JSX.Element;
179
+ /** The CSS className for the element. */
180
+ class?: string;
181
+ /** The inline style for the element. */
182
+ style?: JSX.CSSProperties;
183
+ }
168
184
 
169
185
  interface TabsContextValue<T> {
170
186
  state: TabListState<T>;
171
187
  items: Accessor<T[]>;
188
+ setTabListItems(items: T[] | undefined): void;
189
+ registerTab(tab: RegisteredTab): void;
190
+ unregisterTab(id: Key): void;
191
+ }
192
+
193
+ type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
194
+
195
+ function assignRef<T>(ref: RefLike<T>, element: T): void {
196
+ if (!ref) return;
197
+ if (typeof ref === "function") {
198
+ ref(element);
199
+ } else if (typeof ref === "object" && "current" in ref) {
200
+ ref.current = element;
201
+ }
172
202
  }
173
203
 
174
204
  export const TabsContext = createContext<TabsContextValue<unknown> | null>(null);
175
205
  export const TabsStateContext = createContext<TabListState<unknown> | null>(null);
176
206
 
177
- // ============================================
178
- // COMPONENTS
179
- // ============================================
207
+ interface RegisteredTab {
208
+ id: Key;
209
+ textValue?: string;
210
+ isDisabled?: boolean;
211
+ }
212
+
213
+ interface CollectionItemLike {
214
+ id?: Key;
215
+ key?: Key;
216
+ label?: string;
217
+ textValue?: string;
218
+ isDisabled?: boolean;
219
+ }
220
+
221
+ function collectionItemKey<T>(item: T): Key {
222
+ const tab = item as CollectionItemLike;
223
+ const key = tab.id ?? tab.key;
224
+ if (key == null) {
225
+ throw new Error("Tabs collection items require an id or key.");
226
+ }
227
+ return key;
228
+ }
229
+
230
+ function staticTabText(tab: RegisteredTab): string {
231
+ return tab.textValue ?? String(tab.id);
232
+ }
180
233
 
181
234
  /**
182
235
  * Tabs provide a way to organize content into multiple sections, with only one section visible at a time.
@@ -184,23 +237,55 @@ export const TabsStateContext = createContext<TabListState<unknown> | null>(null
184
237
  export function Tabs<T>(props: TabsProps<T>): JSX.Element {
185
238
  const [local, stateProps, rest] = splitProps(
186
239
  props,
187
- ['class', 'style', 'slot'],
188
- ['items', 'getKey', 'getTextValue', 'getDisabled', 'disabledKeys', 'selectedKey', 'defaultSelectedKey', 'onSelectionChange', 'isDisabled', 'keyboardActivation', 'orientation']
240
+ ["class", "style", "slot", "ref"],
241
+ [
242
+ "items",
243
+ "getKey",
244
+ "getTextValue",
245
+ "getDisabled",
246
+ "disabledKeys",
247
+ "selectedKey",
248
+ "defaultSelectedKey",
249
+ "onSelectionChange",
250
+ "isDisabled",
251
+ "keyboardActivation",
252
+ "orientation",
253
+ ],
189
254
  );
190
255
 
256
+ const [tabListItems, setTabListItems] = createSignal<T[] | undefined>(undefined);
257
+ const [registeredTabs, setRegisteredTabs] = createSignal<RegisteredTab[]>([]);
258
+ const effectiveItems = createMemo<T[]>(() => {
259
+ if (stateProps.items) return stateProps.items;
260
+ if (tabListItems()) return tabListItems() ?? [];
261
+ return registeredTabs() as T[];
262
+ });
263
+
191
264
  // Create tab list state
192
265
  const state = createTabListState<T>({
193
266
  get items() {
194
- return stateProps.items;
267
+ return effectiveItems();
195
268
  },
196
269
  get getKey() {
197
- return stateProps.getKey;
270
+ return stateProps.getKey ?? collectionItemKey;
198
271
  },
199
272
  get getTextValue() {
200
- return stateProps.getTextValue;
273
+ return (
274
+ stateProps.getTextValue ??
275
+ ((item: T) => {
276
+ const tab = item as RegisteredTab & CollectionItemLike;
277
+ return tab.textValue ?? tab.label ?? staticTabText(tab);
278
+ })
279
+ );
201
280
  },
202
281
  get getDisabled() {
203
- return stateProps.getDisabled;
282
+ return (
283
+ stateProps.getDisabled ??
284
+ ((item: T) => {
285
+ const tab = item as RegisteredTab & CollectionItemLike;
286
+ return tab.isDisabled ?? false;
287
+ })
288
+ );
204
289
  },
205
290
  get disabledKeys() {
206
291
  return stateProps.disabledKeys;
@@ -225,31 +310,49 @@ export function Tabs<T>(props: TabsProps<T>): JSX.Element {
225
310
  },
226
311
  });
227
312
 
228
- // Render props values
229
313
  const renderValues = createMemo<TabsRenderProps>(() => ({
230
314
  orientation: state.orientation(),
231
315
  isDisabled: state.isDisabled(),
232
316
  }));
233
317
 
234
- // Resolve render props
235
318
  const renderProps = useRenderProps(
236
319
  {
237
320
  class: local.class,
238
321
  style: local.style,
239
322
  children: props.children,
240
- defaultClassName: 'solidaria-Tabs',
323
+ defaultClassName: "solidaria-Tabs",
241
324
  },
242
- renderValues
325
+ renderValues,
326
+ );
327
+
328
+ const domProps = createMemo(() =>
329
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
243
330
  );
244
331
 
245
- // Filter DOM props
246
- const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
332
+ const contextValue: TabsContextValue<T> = {
333
+ state,
334
+ items: effectiveItems,
335
+ setTabListItems(items) {
336
+ setTabListItems(() => items);
337
+ },
338
+ registerTab(tab) {
339
+ setRegisteredTabs((current) => {
340
+ const next = current.filter((item) => item.id !== tab.id);
341
+ next.push(tab);
342
+ return next;
343
+ });
344
+ },
345
+ unregisterTab(id) {
346
+ setRegisteredTabs((current) => current.filter((item) => item.id !== id));
347
+ },
348
+ };
247
349
 
248
350
  return (
249
- <TabsContext.Provider value={{ state, items: () => stateProps.items ?? [] }}>
351
+ <TabsContext.Provider value={contextValue as TabsContextValue<unknown>}>
250
352
  <TabsStateContext.Provider value={state}>
251
353
  <div
252
354
  {...domProps()}
355
+ ref={(element) => assignRef(local.ref, element)}
253
356
  class={renderProps.class()}
254
357
  style={renderProps.style()}
255
358
  data-orientation={state.orientation()}
@@ -266,24 +369,21 @@ export function Tabs<T>(props: TabsProps<T>): JSX.Element {
266
369
  * A TabList contains Tab elements that represent the available tabs.
267
370
  */
268
371
  export function TabList<T>(props: TabListProps<T>): JSX.Element {
269
- const [local, ariaProps] = splitProps(props, [
270
- 'class',
271
- 'style',
272
- 'slot',
273
- ]);
372
+ const [local, collectionProps, ariaProps] = splitProps(
373
+ props,
374
+ ["class", "style", "slot"],
375
+ ["items"],
376
+ );
274
377
 
275
- // Get state from context
276
378
  const context = useContext(TabsContext);
277
379
 
278
380
  return (
279
- <Show
280
- when={context}
281
- fallback={<div class="solidaria-TabList" role="tablist" />}
282
- >
381
+ <Show when={context} fallback={<div class="solidaria-TabList" role="tablist" />}>
283
382
  {(ctx) => (
284
383
  <TabListInner
285
384
  context={ctx()}
286
385
  local={local}
386
+ items={collectionProps.items}
287
387
  ariaProps={ariaProps}
288
388
  children={props.children}
289
389
  />
@@ -295,20 +395,50 @@ export function TabList<T>(props: TabListProps<T>): JSX.Element {
295
395
  /** Inner TabList component that has access to context */
296
396
  function TabListInner<T>(props: {
297
397
  context: TabsContextValue<unknown>;
298
- local: { class?: ClassNameOrFunction<TabListRenderProps>; style?: StyleOrFunction<TabListRenderProps>; slot?: string };
299
- ariaProps: Omit<TabListProps<T>, 'children' | 'class' | 'style' | 'slot'>;
300
- children?: (item: T) => JSX.Element;
398
+ local: {
399
+ class?: ClassNameOrFunction<TabListRenderProps>;
400
+ style?: StyleOrFunction<TabListRenderProps>;
401
+ slot?: string;
402
+ };
403
+ ariaProps: Omit<TabListProps<T>, "children" | "class" | "style" | "slot" | "items">;
404
+ items?: T[];
405
+ children?: JSX.Element | ((item: T) => JSX.Element);
301
406
  }): JSX.Element {
302
407
  const state = props.context.state as TabListState<T>;
303
408
  const items = props.context.items as Accessor<T[]>;
409
+ const renderItem = createMemo(() => {
410
+ if (typeof props.children !== "function") {
411
+ return undefined;
412
+ }
413
+
414
+ const child = props.children as (...args: unknown[]) => JSX.Element;
415
+ if (child.length === 0 && props.items === undefined) {
416
+ return undefined;
417
+ }
418
+
419
+ return props.children as (item: T) => JSX.Element;
420
+ });
421
+ const renderedChildren = createMemo(() => {
422
+ if (renderItem() || typeof props.children !== "function") {
423
+ return props.children as JSX.Element;
424
+ }
425
+
426
+ return (props.children as () => JSX.Element)();
427
+ });
428
+
429
+ createEffect(() => {
430
+ props.context.setTabListItems(props.items as unknown[] | undefined);
431
+ });
432
+
433
+ onCleanup(() => {
434
+ props.context.setTabListItems(undefined);
435
+ });
304
436
 
305
437
  // Create tab list aria props
306
438
  const { tabListProps } = createTabList<T>(props.ariaProps as AriaTabListProps, state);
307
439
 
308
- // Create focus ring
309
440
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
310
441
 
311
- // Render props values
312
442
  const renderValues = createMemo<TabListRenderProps>(() => ({
313
443
  orientation: state.orientation(),
314
444
  isDisabled: state.isDisabled(),
@@ -316,31 +446,34 @@ function TabListInner<T>(props: {
316
446
  isFocusVisible: isFocusVisible(),
317
447
  }));
318
448
 
319
- // Resolve render props
320
449
  const renderProps = useRenderProps(
321
450
  {
322
451
  class: props.local.class,
323
452
  style: props.local.style,
324
- defaultClassName: 'solidaria-TabList',
453
+ defaultClassName: "solidaria-TabList",
325
454
  },
326
- renderValues
455
+ renderValues,
327
456
  );
328
457
 
329
458
  // Helper to safely call event handlers that may be bound tuples
330
459
  const callHandler = <E extends Event>(
331
460
  handler: JSX.EventHandlerUnion<HTMLElement, E> | undefined,
332
- event: E
461
+ event: E,
333
462
  ) => {
334
463
  if (!handler) return;
335
464
  if (Array.isArray(handler)) {
336
465
  handler[1].call(handler[0], event);
337
466
  return;
338
467
  }
339
- if (typeof handler === 'function') {
468
+ if (typeof handler === "function") {
340
469
  (handler as (evt: E) => void)(event);
341
470
  return;
342
471
  }
343
- if (typeof handler === 'object' && 'handleEvent' in handler && typeof handler.handleEvent === 'function') {
472
+ if (
473
+ typeof handler === "object" &&
474
+ "handleEvent" in handler &&
475
+ typeof handler.handleEvent === "function"
476
+ ) {
344
477
  (handler.handleEvent as (evt: E) => void)(event);
345
478
  }
346
479
  };
@@ -363,10 +496,10 @@ function TabListInner<T>(props: {
363
496
  return (
364
497
  <div
365
498
  role={tabListProps.role}
366
- aria-orientation={tabListProps['aria-orientation']}
367
- aria-label={tabListProps['aria-label']}
368
- aria-labelledby={tabListProps['aria-labelledby']}
369
- aria-describedby={tabListProps['aria-describedby']}
499
+ aria-orientation={tabListProps["aria-orientation"]}
500
+ aria-label={tabListProps["aria-label"]}
501
+ aria-labelledby={tabListProps["aria-labelledby"]}
502
+ aria-describedby={tabListProps["aria-describedby"]}
370
503
  class={renderProps.class()}
371
504
  style={renderProps.style()}
372
505
  onKeyDown={handleKeyDown}
@@ -377,9 +510,13 @@ function TabListInner<T>(props: {
377
510
  data-orientation={state.orientation()}
378
511
  data-disabled={state.isDisabled() || undefined}
379
512
  >
380
- <SharedElementTransition>
381
- <For each={items()}>{(item) => props.children?.(item)}</For>
382
- </SharedElementTransition>
513
+ {renderItem() ? (
514
+ <SharedElementTransition>
515
+ <For each={(props.items ?? items()) as T[]}>{(item) => renderItem()?.(item)}</For>
516
+ </SharedElementTransition>
517
+ ) : (
518
+ renderedChildren()
519
+ )}
383
520
  </div>
384
521
  );
385
522
  }
@@ -388,24 +525,17 @@ function TabListInner<T>(props: {
388
525
  * A Tab represents an individual tab in a TabList.
389
526
  */
390
527
  export function Tab(props: TabProps): JSX.Element {
391
- const [local, ariaProps] = splitProps(props, [
392
- 'class',
393
- 'style',
394
- 'slot',
395
- 'id',
396
- ]);
397
-
398
- // Get state from context
528
+ const [local, ariaProps] = splitProps(props, ["class", "style", "slot", "id"]);
529
+
399
530
  const context = useContext(TabsStateContext);
531
+ const tabsContext = useContext(TabsContext);
400
532
 
401
533
  return (
402
- <Show
403
- when={context}
404
- fallback={<div class="solidaria-Tab" role="tab" />}
405
- >
534
+ <Show when={context} fallback={<div class="solidaria-Tab" role="tab" />}>
406
535
  {(state) => (
407
536
  <TabInner
408
537
  state={state()}
538
+ tabsContext={tabsContext}
409
539
  local={local}
410
540
  ariaProps={ariaProps}
411
541
  children={props.children}
@@ -418,11 +548,33 @@ export function Tab(props: TabProps): JSX.Element {
418
548
  /** Inner Tab component that has access to context */
419
549
  function TabInner(props: {
420
550
  state: TabListState<unknown>;
421
- local: { class?: ClassNameOrFunction<TabRenderProps>; style?: StyleOrFunction<TabRenderProps>; slot?: string; id: Key };
422
- ariaProps: Omit<TabProps, 'children' | 'class' | 'style' | 'slot' | 'id'>;
551
+ tabsContext: TabsContextValue<unknown> | null;
552
+ local: {
553
+ class?: ClassNameOrFunction<TabRenderProps>;
554
+ style?: StyleOrFunction<TabRenderProps>;
555
+ slot?: string;
556
+ id: Key;
557
+ };
558
+ ariaProps: Omit<TabProps, "children" | "class" | "style" | "slot" | "id">;
423
559
  children?: RenderChildren<TabRenderProps>;
424
560
  }): JSX.Element {
425
561
  let tabRef: HTMLDivElement | undefined;
562
+ const textValue = () => {
563
+ if (props.ariaProps["aria-label"]) return props.ariaProps["aria-label"];
564
+ return typeof props.children === "string" ? props.children : undefined;
565
+ };
566
+
567
+ createEffect(() => {
568
+ props.tabsContext?.registerTab({
569
+ id: props.local.id,
570
+ textValue: textValue(),
571
+ isDisabled: props.ariaProps.isDisabled,
572
+ });
573
+ });
574
+
575
+ onCleanup(() => {
576
+ props.tabsContext?.unregisterTab(props.local.id);
577
+ });
426
578
 
427
579
  // Create tab aria props
428
580
  const tabAria = createTab<unknown>(
@@ -431,22 +583,23 @@ function TabInner(props: {
431
583
  get isDisabled() {
432
584
  return props.ariaProps.isDisabled;
433
585
  },
434
- get 'aria-label'() {
435
- return props.ariaProps['aria-label'];
586
+ get "aria-label"() {
587
+ return props.ariaProps["aria-label"];
588
+ },
589
+ get "aria-labelledby"() {
590
+ return props.ariaProps["aria-labelledby"];
436
591
  },
437
592
  },
438
593
  props.state,
439
- () => tabRef ?? null
594
+ () => tabRef ?? null,
440
595
  );
441
596
 
442
- // Create hover
443
597
  const { isHovered, hoverProps } = createHover({
444
598
  get isDisabled() {
445
599
  return tabAria.isDisabled();
446
600
  },
447
601
  });
448
602
 
449
- // Render props values
450
603
  const renderValues = createMemo<TabRenderProps>(() => ({
451
604
  isSelected: tabAria.isSelected(),
452
605
  isFocused: tabAria.isFocused(),
@@ -456,15 +609,14 @@ function TabInner(props: {
456
609
  isDisabled: tabAria.isDisabled(),
457
610
  }));
458
611
 
459
- // Resolve render props
460
612
  const renderProps = useRenderProps(
461
613
  {
462
614
  children: props.children,
463
615
  class: props.local.class,
464
616
  style: props.local.style,
465
- defaultClassName: 'solidaria-Tab',
617
+ defaultClassName: "solidaria-Tab",
466
618
  },
467
- renderValues
619
+ renderValues,
468
620
  );
469
621
 
470
622
  const selectionIndicatorContext = createMemo<SelectionIndicatorContextValue>(() => ({
@@ -479,8 +631,9 @@ function TabInner(props: {
479
631
  role={tabAria.tabProps.role}
480
632
  aria-selected={tabAria.isSelected()}
481
633
  aria-disabled={tabAria.isDisabled() || undefined}
482
- aria-controls={tabAria.isSelected() ? tabAria.tabProps['aria-controls'] : undefined}
483
- aria-label={tabAria.tabProps['aria-label']}
634
+ aria-controls={tabAria.isSelected() ? tabAria.tabProps["aria-controls"] : undefined}
635
+ aria-label={tabAria.tabProps["aria-label"]}
636
+ aria-labelledby={tabAria.tabProps["aria-labelledby"]}
484
637
  tabIndex={tabAria.isSelected() && !tabAria.isDisabled() ? 0 : -1}
485
638
  class={renderProps.class()}
486
639
  style={renderProps.style()}
@@ -503,16 +656,74 @@ function TabInner(props: {
503
656
  );
504
657
  }
505
658
 
659
+ /**
660
+ * Groups multiple TabPanel elements.
661
+ */
662
+ export function TabPanels(props: TabPanelsProps): JSX.Element {
663
+ const [local, rest] = splitProps(props, ["class", "style", "slot", "children"]);
664
+ const domProps = createMemo(() =>
665
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
666
+ );
667
+ const state = useContext(TabsStateContext);
668
+ let ref: HTMLDivElement | undefined;
669
+ let previousSelectedKey: Key | null | undefined = undefined;
670
+ const [panelSize, setPanelSize] = createSignal<{ width?: string; height?: string }>({});
671
+
672
+ createEffect(() => {
673
+ const selectedKey = state?.selectedKey() ?? null;
674
+ if (!ref) {
675
+ previousSelectedKey = selectedKey;
676
+ return;
677
+ }
678
+
679
+ if (previousSelectedKey === undefined) {
680
+ previousSelectedKey = selectedKey;
681
+ return;
682
+ }
683
+
684
+ if (previousSelectedKey === selectedKey) return;
685
+ previousSelectedKey = selectedKey;
686
+
687
+ const transition = window.getComputedStyle(ref).transition;
688
+ const shouldMeasureWidth = /width|inline-size|all/.test(transition);
689
+ const shouldMeasureHeight = /height|block-size|all/.test(transition);
690
+ if (!shouldMeasureWidth && !shouldMeasureHeight) return;
691
+
692
+ const selectedPanel = ref.querySelector<HTMLElement>('[role="tabpanel"]:not([hidden])');
693
+ if (!selectedPanel) return;
694
+
695
+ setPanelSize({
696
+ width: shouldMeasureWidth ? `${selectedPanel.offsetWidth}px` : undefined,
697
+ height: shouldMeasureHeight ? `${selectedPanel.offsetHeight}px` : undefined,
698
+ });
699
+ });
700
+
701
+ const mergedStyle = (): JSX.CSSProperties =>
702
+ ({
703
+ ...local.style,
704
+ ...(panelSize().width ? { "--tab-panel-width": panelSize().width } : {}),
705
+ ...(panelSize().height ? { "--tab-panel-height": panelSize().height } : {}),
706
+ }) as JSX.CSSProperties;
707
+
708
+ return (
709
+ <div
710
+ {...domProps()}
711
+ ref={(el) => {
712
+ ref = el;
713
+ }}
714
+ class={local.class ?? "solidaria-TabPanels"}
715
+ style={mergedStyle()}
716
+ >
717
+ {local.children}
718
+ </div>
719
+ );
720
+ }
721
+
506
722
  /**
507
723
  * A TabPanel displays the content for a selected Tab.
508
724
  */
509
725
  export function TabPanel(props: TabPanelProps): JSX.Element {
510
- const [local, ariaProps] = splitProps(props, [
511
- 'class',
512
- 'style',
513
- 'slot',
514
- 'shouldForceMount',
515
- ]);
726
+ const [local, ariaProps] = splitProps(props, ["class", "style", "slot", "shouldForceMount"]);
516
727
 
517
728
  // Get state from context (may be null for SSR scenarios)
518
729
  const state = useContext(TabsStateContext);
@@ -522,23 +733,29 @@ export function TabPanel(props: TabPanelProps): JSX.Element {
522
733
 
523
734
  // Create focus ring for the panel
524
735
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
736
+ const isInert = () =>
737
+ Boolean(local.shouldForceMount && ariaProps.id !== undefined && !isSelected());
738
+ const isEntering = () => false;
739
+ const isExiting = () => false;
525
740
 
526
- // Render props values
527
741
  const renderValues = createMemo<TabPanelRenderProps>(() => ({
528
742
  isSelected: isSelected(),
529
743
  isFocused: isFocused(),
530
744
  isFocusVisible: isFocusVisible(),
745
+ isInert: isInert(),
746
+ isEntering: isEntering(),
747
+ isExiting: isExiting(),
748
+ state,
531
749
  }));
532
750
 
533
- // Resolve render props
534
751
  const renderProps = useRenderProps(
535
752
  {
536
753
  children: props.children,
537
754
  class: local.class,
538
755
  style: local.style,
539
- defaultClassName: 'solidaria-TabPanel',
756
+ defaultClassName: "solidaria-TabPanel",
540
757
  },
541
- renderValues
758
+ renderValues,
542
759
  );
543
760
 
544
761
  // Determine if we should render the panel
@@ -547,7 +764,6 @@ export function TabPanel(props: TabPanelProps): JSX.Element {
547
764
  const shouldRender = () => {
548
765
  if (local.shouldForceMount) return true;
549
766
  if (ariaProps.id === undefined) {
550
- // Shared panel pattern - render when any tab is selected
551
767
  return state ? state.selectedKey() !== null : true;
552
768
  }
553
769
  return isSelected();
@@ -558,9 +774,9 @@ export function TabPanel(props: TabPanelProps): JSX.Element {
558
774
  <div
559
775
  id={tabPanelProps.id}
560
776
  role={tabPanelProps.role}
561
- aria-labelledby={tabPanelProps['aria-labelledby']}
562
- aria-label={tabPanelProps['aria-label']}
563
- aria-describedby={tabPanelProps['aria-describedby']}
777
+ aria-labelledby={tabPanelProps["aria-labelledby"]}
778
+ aria-label={tabPanelProps["aria-label"]}
779
+ aria-describedby={tabPanelProps["aria-describedby"]}
564
780
  tabIndex={tabPanelProps.tabIndex}
565
781
  class={renderProps.class()}
566
782
  style={renderProps.style()}
@@ -569,8 +785,13 @@ export function TabPanel(props: TabPanelProps): JSX.Element {
569
785
  data-selected={isSelected() || undefined}
570
786
  data-focused={isFocused() || undefined}
571
787
  data-focus-visible={isFocusVisible() || undefined}
572
- inert={ariaProps.id !== undefined && !isSelected() ? true : undefined}
573
- hidden={ariaProps.id !== undefined && !isSelected() && !local.shouldForceMount ? true : undefined}
788
+ inert={isInert() ? true : undefined}
789
+ data-inert={isInert() || undefined}
790
+ data-entering={isEntering() || undefined}
791
+ data-exiting={isExiting() || undefined}
792
+ hidden={
793
+ ariaProps.id !== undefined && !isSelected() && !local.shouldForceMount ? true : undefined
794
+ }
574
795
  >
575
796
  {renderProps.renderChildren()}
576
797
  </div>
@@ -578,8 +799,8 @@ export function TabPanel(props: TabPanelProps): JSX.Element {
578
799
  );
579
800
  }
580
801
 
581
- // Attach sub-components
582
802
  Tabs.List = TabList;
583
803
  Tabs.Tab = Tab;
804
+ Tabs.Panels = TabPanels;
584
805
  Tabs.Panel = TabPanel;
585
806
  Tabs.SelectionIndicator = SelectionIndicator;