@proyecto-viviana/solidaria-components 0.2.5 → 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 (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -272
  3. package/dist/ActionBar.d.ts +79 -0
  4. package/dist/ActionBar.d.ts.map +1 -0
  5. package/dist/ActionGroup.d.ts +74 -0
  6. package/dist/ActionGroup.d.ts.map +1 -0
  7. package/dist/Alert.d.ts +70 -0
  8. package/dist/Alert.d.ts.map +1 -0
  9. package/dist/Autocomplete.d.ts +5 -5
  10. package/dist/Autocomplete.d.ts.map +1 -1
  11. package/dist/Breadcrumbs.d.ts +27 -8
  12. package/dist/Breadcrumbs.d.ts.map +1 -1
  13. package/dist/Button.d.ts +28 -5
  14. package/dist/Button.d.ts.map +1 -1
  15. package/dist/Calendar.d.ts +51 -7
  16. package/dist/Calendar.d.ts.map +1 -1
  17. package/dist/Checkbox.d.ts +33 -8
  18. package/dist/Checkbox.d.ts.map +1 -1
  19. package/dist/Collection.d.ts +130 -0
  20. package/dist/Collection.d.ts.map +1 -0
  21. package/dist/Color.d.ts +210 -9
  22. package/dist/Color.d.ts.map +1 -1
  23. package/dist/ColorEditor.d.ts +42 -0
  24. package/dist/ColorEditor.d.ts.map +1 -0
  25. package/dist/ComboBox.d.ts +146 -16
  26. package/dist/ComboBox.d.ts.map +1 -1
  27. package/dist/ContextualHelpTrigger.d.ts +40 -0
  28. package/dist/ContextualHelpTrigger.d.ts.map +1 -0
  29. package/dist/DateField.d.ts +35 -8
  30. package/dist/DateField.d.ts.map +1 -1
  31. package/dist/DatePicker.d.ts +101 -5
  32. package/dist/DatePicker.d.ts.map +1 -1
  33. package/dist/DateRangePickerContext.d.ts +30 -0
  34. package/dist/DateRangePickerContext.d.ts.map +1 -0
  35. package/dist/Dialog.d.ts +5 -5
  36. package/dist/Dialog.d.ts.map +1 -1
  37. package/dist/Disclosure.d.ts +25 -5
  38. package/dist/Disclosure.d.ts.map +1 -1
  39. package/dist/DragAndDrop.d.ts +80 -0
  40. package/dist/DragAndDrop.d.ts.map +1 -0
  41. package/dist/DragPreview.d.ts +14 -0
  42. package/dist/DragPreview.d.ts.map +1 -0
  43. package/dist/DropZone.d.ts +27 -0
  44. package/dist/DropZone.d.ts.map +1 -0
  45. package/dist/FieldError.d.ts +27 -0
  46. package/dist/FieldError.d.ts.map +1 -0
  47. package/dist/FileTrigger.d.ts +26 -0
  48. package/dist/FileTrigger.d.ts.map +1 -0
  49. package/dist/Focusable.d.ts +27 -0
  50. package/dist/Focusable.d.ts.map +1 -0
  51. package/dist/Form.d.ts +41 -0
  52. package/dist/Form.d.ts.map +1 -0
  53. package/dist/GridList.d.ts +69 -10
  54. package/dist/GridList.d.ts.map +1 -1
  55. package/dist/HiddenDateInput.d.ts +26 -0
  56. package/dist/HiddenDateInput.d.ts.map +1 -0
  57. package/dist/HiddenTimeInput.d.ts +25 -0
  58. package/dist/HiddenTimeInput.d.ts.map +1 -0
  59. package/dist/Icon.d.ts +57 -0
  60. package/dist/Icon.d.ts.map +1 -0
  61. package/dist/Keyboard.d.ts +13 -0
  62. package/dist/Keyboard.d.ts.map +1 -0
  63. package/dist/Landmark.d.ts +3 -3
  64. package/dist/Landmark.d.ts.map +1 -1
  65. package/dist/Link.d.ts +10 -4
  66. package/dist/Link.d.ts.map +1 -1
  67. package/dist/ListBox.d.ts +73 -11
  68. package/dist/ListBox.d.ts.map +1 -1
  69. package/dist/ListDropTargetDelegate.d.ts +38 -0
  70. package/dist/ListDropTargetDelegate.d.ts.map +1 -0
  71. package/dist/Menu.d.ts +79 -10
  72. package/dist/Menu.d.ts.map +1 -1
  73. package/dist/Meter.d.ts +4 -4
  74. package/dist/Meter.d.ts.map +1 -1
  75. package/dist/Modal.d.ts +6 -4
  76. package/dist/Modal.d.ts.map +1 -1
  77. package/dist/NumberField.d.ts +10 -12
  78. package/dist/NumberField.d.ts.map +1 -1
  79. package/dist/Popover.d.ts +32 -7
  80. package/dist/Popover.d.ts.map +1 -1
  81. package/dist/Pressable.d.ts +27 -0
  82. package/dist/Pressable.d.ts.map +1 -0
  83. package/dist/ProgressBar.d.ts +6 -4
  84. package/dist/ProgressBar.d.ts.map +1 -1
  85. package/dist/RadioGroup.d.ts +43 -9
  86. package/dist/RadioGroup.d.ts.map +1 -1
  87. package/dist/RangeCalendar.d.ts +39 -7
  88. package/dist/RangeCalendar.d.ts.map +1 -1
  89. package/dist/RouterProvider.d.ts +75 -0
  90. package/dist/RouterProvider.d.ts.map +1 -0
  91. package/dist/SearchField.d.ts +23 -21
  92. package/dist/SearchField.d.ts.map +1 -1
  93. package/dist/Select.d.ts +48 -7
  94. package/dist/Select.d.ts.map +1 -1
  95. package/dist/SelectionIndicator.d.ts +30 -0
  96. package/dist/SelectionIndicator.d.ts.map +1 -0
  97. package/dist/Separator.d.ts +9 -3
  98. package/dist/Separator.d.ts.map +1 -1
  99. package/dist/SharedElementTransition.d.ts +41 -0
  100. package/dist/SharedElementTransition.d.ts.map +1 -0
  101. package/dist/Slider.d.ts +15 -8
  102. package/dist/Slider.d.ts.map +1 -1
  103. package/dist/StepList.d.ts +90 -0
  104. package/dist/StepList.d.ts.map +1 -0
  105. package/dist/Switch.d.ts +11 -5
  106. package/dist/Switch.d.ts.map +1 -1
  107. package/dist/Table.d.ts +222 -19
  108. package/dist/Table.d.ts.map +1 -1
  109. package/dist/Tabs.d.ts +47 -10
  110. package/dist/Tabs.d.ts.map +1 -1
  111. package/dist/TagGroup.d.ts +22 -10
  112. package/dist/TagGroup.d.ts.map +1 -1
  113. package/dist/Text.d.ts +10 -0
  114. package/dist/Text.d.ts.map +1 -0
  115. package/dist/TextField.d.ts +19 -11
  116. package/dist/TextField.d.ts.map +1 -1
  117. package/dist/TimeField.d.ts +32 -7
  118. package/dist/TimeField.d.ts.map +1 -1
  119. package/dist/Toast.d.ts +29 -14
  120. package/dist/Toast.d.ts.map +1 -1
  121. package/dist/ToggleButton.d.ts +36 -0
  122. package/dist/ToggleButton.d.ts.map +1 -0
  123. package/dist/ToggleButtonGroup.d.ts +33 -0
  124. package/dist/ToggleButtonGroup.d.ts.map +1 -0
  125. package/dist/Toolbar.d.ts +7 -3
  126. package/dist/Toolbar.d.ts.map +1 -1
  127. package/dist/Tooltip.d.ts +58 -7
  128. package/dist/Tooltip.d.ts.map +1 -1
  129. package/dist/Tree.d.ts +102 -11
  130. package/dist/Tree.d.ts.map +1 -1
  131. package/dist/Virtualizer.d.ts +61 -0
  132. package/dist/Virtualizer.d.ts.map +1 -0
  133. package/dist/VirtualizerLayouts.d.ts +82 -0
  134. package/dist/VirtualizerLayouts.d.ts.map +1 -0
  135. package/dist/VisuallyHidden.d.ts +4 -2
  136. package/dist/VisuallyHidden.d.ts.map +1 -1
  137. package/dist/contexts.d.ts +6 -1
  138. package/dist/contexts.d.ts.map +1 -1
  139. package/dist/index.d.ts +73 -39
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +23342 -10644
  142. package/dist/index.js.map +1 -7
  143. package/dist/index.jsx +18110 -0
  144. package/dist/index.jsx.map +1 -0
  145. package/dist/useDragAndDrop.d.ts +93 -0
  146. package/dist/useDragAndDrop.d.ts.map +1 -0
  147. package/dist/utils.d.ts +8 -2
  148. package/dist/utils.d.ts.map +1 -1
  149. package/dist/virtualizer/Layout.d.ts +79 -0
  150. package/dist/virtualizer/Layout.d.ts.map +1 -0
  151. package/package.json +33 -32
  152. package/src/ActionBar.tsx +251 -0
  153. package/src/ActionGroup.tsx +277 -0
  154. package/src/Alert.tsx +152 -0
  155. package/src/Autocomplete.tsx +39 -44
  156. package/src/Breadcrumbs.tsx +227 -72
  157. package/src/Button.tsx +315 -74
  158. package/src/Calendar.tsx +347 -141
  159. package/src/Checkbox.tsx +414 -123
  160. package/src/Collection.tsx +350 -0
  161. package/src/Color.tsx +1325 -284
  162. package/src/ColorEditor.tsx +213 -0
  163. package/src/ComboBox.tsx +644 -245
  164. package/src/ContextualHelpTrigger.tsx +195 -0
  165. package/src/DateField.tsx +274 -106
  166. package/src/DatePicker.tsx +892 -111
  167. package/src/DateRangePickerContext.tsx +44 -0
  168. package/src/Dialog.tsx +173 -104
  169. package/src/Disclosure.tsx +158 -105
  170. package/src/DragAndDrop.tsx +340 -0
  171. package/src/DragPreview.tsx +47 -0
  172. package/src/DropZone.tsx +233 -0
  173. package/src/FieldError.tsx +89 -0
  174. package/src/FileTrigger.tsx +83 -0
  175. package/src/Focusable.tsx +103 -0
  176. package/src/Form.tsx +140 -0
  177. package/src/GridList.tsx +542 -128
  178. package/src/HiddenDateInput.tsx +153 -0
  179. package/src/HiddenTimeInput.tsx +133 -0
  180. package/src/Icon.tsx +133 -0
  181. package/src/Keyboard.tsx +26 -0
  182. package/src/Landmark.tsx +37 -63
  183. package/src/Link.tsx +132 -69
  184. package/src/ListBox.tsx +656 -106
  185. package/src/ListDropTargetDelegate.ts +283 -0
  186. package/src/Menu.tsx +1234 -132
  187. package/src/Meter.tsx +44 -58
  188. package/src/Modal.tsx +262 -166
  189. package/src/NumberField.tsx +267 -151
  190. package/src/Popover.tsx +452 -343
  191. package/src/Pressable.tsx +108 -0
  192. package/src/ProgressBar.tsx +54 -59
  193. package/src/RadioGroup.tsx +533 -121
  194. package/src/RangeCalendar.tsx +249 -150
  195. package/src/RouterProvider.tsx +223 -0
  196. package/src/SearchField.tsx +460 -133
  197. package/src/Select.tsx +804 -233
  198. package/src/SelectionIndicator.tsx +108 -0
  199. package/src/Separator.tsx +47 -49
  200. package/src/SharedElementTransition.tsx +264 -0
  201. package/src/Slider.tsx +148 -98
  202. package/src/StepList.tsx +272 -0
  203. package/src/Switch.tsx +93 -46
  204. package/src/Table.tsx +1551 -225
  205. package/src/Tabs.tsx +377 -123
  206. package/src/TagGroup.tsx +233 -135
  207. package/src/Text.tsx +18 -0
  208. package/src/TextField.tsx +413 -86
  209. package/src/TimeField.tsx +232 -222
  210. package/src/Toast.tsx +306 -160
  211. package/src/ToggleButton.tsx +169 -0
  212. package/src/ToggleButtonGroup.tsx +141 -0
  213. package/src/Toolbar.tsx +61 -70
  214. package/src/Tooltip.tsx +473 -116
  215. package/src/Tree.tsx +1514 -175
  216. package/src/Virtualizer.tsx +730 -0
  217. package/src/VirtualizerLayouts.ts +280 -0
  218. package/src/VisuallyHidden.tsx +32 -38
  219. package/src/contexts.ts +29 -36
  220. package/src/index.ts +972 -620
  221. package/src/useDragAndDrop.ts +367 -0
  222. package/src/utils.tsx +69 -50
  223. package/src/virtualizer/Layout.ts +192 -0
  224. package/dist/index.ssr.js +0 -9785
  225. package/dist/index.ssr.js.map +0 -7
package/src/Tabs.tsx CHANGED
@@ -6,14 +6,18 @@
6
6
  */
7
7
 
8
8
  import {
9
+ type Accessor,
9
10
  type JSX,
10
11
  createContext,
12
+ createEffect,
11
13
  createMemo,
14
+ createSignal,
12
15
  splitProps,
13
16
  useContext,
14
17
  For,
15
18
  Show,
16
- } from 'solid-js';
19
+ onCleanup,
20
+ } from "solid-js";
17
21
  import {
18
22
  createTabList,
19
23
  createTab,
@@ -23,14 +27,14 @@ import {
23
27
  type AriaTabListProps,
24
28
  type AriaTabProps,
25
29
  type AriaTabPanelProps,
26
- } from '@proyecto-viviana/solidaria';
30
+ } from "@proyecto-viviana/solidaria";
27
31
  import {
28
32
  createTabListState,
29
33
  type TabListState,
30
34
  type Key,
31
35
  type TabOrientation,
32
36
  type KeyboardActivation,
33
- } from '@proyecto-viviana/solid-stately';
37
+ } from "@proyecto-viviana/solid-stately";
34
38
  import {
35
39
  type RenderChildren,
36
40
  type ClassNameOrFunction,
@@ -38,11 +42,20 @@ import {
38
42
  type SlotProps,
39
43
  useRenderProps,
40
44
  filterDOMProps,
41
- } from './utils';
42
-
43
- // ============================================
44
- // TYPES
45
- // ============================================
45
+ } from "./utils";
46
+ import {
47
+ SelectionIndicator,
48
+ SelectionIndicatorContext,
49
+ type SelectionIndicatorContextValue,
50
+ } from "./SelectionIndicator";
51
+ import { SharedElementTransition } from "./SharedElementTransition";
52
+
53
+ export {
54
+ SelectionIndicator,
55
+ SelectionIndicatorContext,
56
+ type SelectionIndicatorProps,
57
+ type SelectionIndicatorRenderProps,
58
+ } from "./SelectionIndicator";
46
59
 
47
60
  export interface TabsRenderProps {
48
61
  /** The orientation of the tabs. */
@@ -80,6 +93,8 @@ export interface TabsProps<T> extends SlotProps {
80
93
  class?: ClassNameOrFunction<TabsRenderProps>;
81
94
  /** The inline style for the element. */
82
95
  style?: StyleOrFunction<TabsRenderProps>;
96
+ /** Ref for the root tabs element. */
97
+ ref?: RefLike<HTMLDivElement>;
83
98
  }
84
99
 
85
100
  export interface TabListRenderProps {
@@ -93,9 +108,11 @@ export interface TabListRenderProps {
93
108
  isFocusVisible: boolean;
94
109
  }
95
110
 
96
- export interface TabListProps<T> extends Omit<AriaTabListProps, 'children'>, SlotProps {
97
- /** The children of the tab list - render function for each item. */
98
- 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);
99
116
  /** The CSS className for the element. */
100
117
  class?: ClassNameOrFunction<TabListRenderProps>;
101
118
  /** The inline style for the element. */
@@ -117,7 +134,7 @@ export interface TabRenderProps {
117
134
  isDisabled: boolean;
118
135
  }
119
136
 
120
- export interface TabProps extends Omit<AriaTabProps, 'key'>, SlotProps {
137
+ export interface TabProps extends Omit<AriaTabProps, "key">, SlotProps {
121
138
  /** The unique key for the tab. */
122
139
  id: Key;
123
140
  /** The children of the tab. */
@@ -135,6 +152,14 @@ export interface TabPanelRenderProps {
135
152
  isFocused: boolean;
136
153
  /** Whether the panel has visible focus ring. */
137
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;
138
163
  }
139
164
 
140
165
  export interface TabPanelProps extends AriaTabPanelProps, SlotProps {
@@ -148,21 +173,63 @@ export interface TabPanelProps extends AriaTabPanelProps, SlotProps {
148
173
  shouldForceMount?: boolean;
149
174
  }
150
175
 
151
- // ============================================
152
- // CONTEXT
153
- // ============================================
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
+ }
154
184
 
155
185
  interface TabsContextValue<T> {
156
186
  state: TabListState<T>;
157
- items: T[];
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
+ }
158
202
  }
159
203
 
160
204
  export const TabsContext = createContext<TabsContextValue<unknown> | null>(null);
161
205
  export const TabsStateContext = createContext<TabListState<unknown> | null>(null);
162
206
 
163
- // ============================================
164
- // COMPONENTS
165
- // ============================================
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
+ }
166
233
 
167
234
  /**
168
235
  * Tabs provide a way to organize content into multiple sections, with only one section visible at a time.
@@ -170,23 +237,55 @@ export const TabsStateContext = createContext<TabListState<unknown> | null>(null
170
237
  export function Tabs<T>(props: TabsProps<T>): JSX.Element {
171
238
  const [local, stateProps, rest] = splitProps(
172
239
  props,
173
- ['class', 'style', 'slot'],
174
- ['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
+ ],
175
254
  );
176
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
+
177
264
  // Create tab list state
178
265
  const state = createTabListState<T>({
179
266
  get items() {
180
- return stateProps.items;
267
+ return effectiveItems();
181
268
  },
182
269
  get getKey() {
183
- return stateProps.getKey;
270
+ return stateProps.getKey ?? collectionItemKey;
184
271
  },
185
272
  get getTextValue() {
186
- 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
+ );
187
280
  },
188
281
  get getDisabled() {
189
- 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
+ );
190
289
  },
191
290
  get disabledKeys() {
192
291
  return stateProps.disabledKeys;
@@ -211,31 +310,49 @@ export function Tabs<T>(props: TabsProps<T>): JSX.Element {
211
310
  },
212
311
  });
213
312
 
214
- // Render props values
215
313
  const renderValues = createMemo<TabsRenderProps>(() => ({
216
314
  orientation: state.orientation(),
217
315
  isDisabled: state.isDisabled(),
218
316
  }));
219
317
 
220
- // Resolve render props
221
318
  const renderProps = useRenderProps(
222
319
  {
223
320
  class: local.class,
224
321
  style: local.style,
225
322
  children: props.children,
226
- defaultClassName: 'solidaria-Tabs',
323
+ defaultClassName: "solidaria-Tabs",
227
324
  },
228
- renderValues
325
+ renderValues,
229
326
  );
230
327
 
231
- // Filter DOM props
232
- const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
328
+ const domProps = createMemo(() =>
329
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
330
+ );
331
+
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
+ };
233
349
 
234
350
  return (
235
- <TabsContext.Provider value={{ state, items: stateProps.items ?? [] }}>
351
+ <TabsContext.Provider value={contextValue as TabsContextValue<unknown>}>
236
352
  <TabsStateContext.Provider value={state}>
237
353
  <div
238
354
  {...domProps()}
355
+ ref={(element) => assignRef(local.ref, element)}
239
356
  class={renderProps.class()}
240
357
  style={renderProps.style()}
241
358
  data-orientation={state.orientation()}
@@ -252,24 +369,21 @@ export function Tabs<T>(props: TabsProps<T>): JSX.Element {
252
369
  * A TabList contains Tab elements that represent the available tabs.
253
370
  */
254
371
  export function TabList<T>(props: TabListProps<T>): JSX.Element {
255
- const [local, ariaProps] = splitProps(props, [
256
- 'class',
257
- 'style',
258
- 'slot',
259
- ]);
372
+ const [local, collectionProps, ariaProps] = splitProps(
373
+ props,
374
+ ["class", "style", "slot"],
375
+ ["items"],
376
+ );
260
377
 
261
- // Get state from context
262
378
  const context = useContext(TabsContext);
263
379
 
264
380
  return (
265
- <Show
266
- when={context}
267
- fallback={<div class="solidaria-TabList" role="tablist" />}
268
- >
381
+ <Show when={context} fallback={<div class="solidaria-TabList" role="tablist" />}>
269
382
  {(ctx) => (
270
383
  <TabListInner
271
384
  context={ctx()}
272
385
  local={local}
386
+ items={collectionProps.items}
273
387
  ariaProps={ariaProps}
274
388
  children={props.children}
275
389
  />
@@ -281,20 +395,50 @@ export function TabList<T>(props: TabListProps<T>): JSX.Element {
281
395
  /** Inner TabList component that has access to context */
282
396
  function TabListInner<T>(props: {
283
397
  context: TabsContextValue<unknown>;
284
- local: { class?: ClassNameOrFunction<TabListRenderProps>; style?: StyleOrFunction<TabListRenderProps>; slot?: string };
285
- ariaProps: Omit<TabListProps<T>, 'children' | 'class' | 'style' | 'slot'>;
286
- 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);
287
406
  }): JSX.Element {
288
407
  const state = props.context.state as TabListState<T>;
289
- const items = props.context.items as T[];
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
+ });
290
436
 
291
437
  // Create tab list aria props
292
438
  const { tabListProps } = createTabList<T>(props.ariaProps as AriaTabListProps, state);
293
439
 
294
- // Create focus ring
295
440
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
296
441
 
297
- // Render props values
298
442
  const renderValues = createMemo<TabListRenderProps>(() => ({
299
443
  orientation: state.orientation(),
300
444
  isDisabled: state.isDisabled(),
@@ -302,26 +446,35 @@ function TabListInner<T>(props: {
302
446
  isFocusVisible: isFocusVisible(),
303
447
  }));
304
448
 
305
- // Resolve render props
306
449
  const renderProps = useRenderProps(
307
450
  {
308
451
  class: props.local.class,
309
452
  style: props.local.style,
310
- defaultClassName: 'solidaria-TabList',
453
+ defaultClassName: "solidaria-TabList",
311
454
  },
312
- renderValues
455
+ renderValues,
313
456
  );
314
457
 
315
458
  // Helper to safely call event handlers that may be bound tuples
316
459
  const callHandler = <E extends Event>(
317
- handler: ((e: E) => void) | [object, (e: E) => void] | undefined,
318
- event: E
460
+ handler: JSX.EventHandlerUnion<HTMLElement, E> | undefined,
461
+ event: E,
319
462
  ) => {
320
463
  if (!handler) return;
321
464
  if (Array.isArray(handler)) {
322
465
  handler[1].call(handler[0], event);
323
- } else {
324
- handler(event);
466
+ return;
467
+ }
468
+ if (typeof handler === "function") {
469
+ (handler as (evt: E) => void)(event);
470
+ return;
471
+ }
472
+ if (
473
+ typeof handler === "object" &&
474
+ "handleEvent" in handler &&
475
+ typeof handler.handleEvent === "function"
476
+ ) {
477
+ (handler.handleEvent as (evt: E) => void)(event);
325
478
  }
326
479
  };
327
480
 
@@ -332,21 +485,21 @@ function TabListInner<T>(props: {
332
485
 
333
486
  const handleFocus = (e: FocusEvent) => {
334
487
  tabListProps.onFocus(e);
335
- callHandler(focusProps.onFocus as any, e);
488
+ callHandler(focusProps.onFocus, e);
336
489
  };
337
490
 
338
491
  const handleBlur = (e: FocusEvent) => {
339
492
  tabListProps.onBlur(e);
340
- callHandler(focusProps.onBlur as any, e);
493
+ callHandler(focusProps.onBlur, e);
341
494
  };
342
495
 
343
496
  return (
344
497
  <div
345
498
  role={tabListProps.role}
346
- aria-orientation={tabListProps['aria-orientation']}
347
- aria-label={tabListProps['aria-label']}
348
- aria-labelledby={tabListProps['aria-labelledby']}
349
- 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"]}
350
503
  class={renderProps.class()}
351
504
  style={renderProps.style()}
352
505
  onKeyDown={handleKeyDown}
@@ -357,7 +510,13 @@ function TabListInner<T>(props: {
357
510
  data-orientation={state.orientation()}
358
511
  data-disabled={state.isDisabled() || undefined}
359
512
  >
360
- <For each={items}>{(item) => props.children?.(item)}</For>
513
+ {renderItem() ? (
514
+ <SharedElementTransition>
515
+ <For each={(props.items ?? items()) as T[]}>{(item) => renderItem()?.(item)}</For>
516
+ </SharedElementTransition>
517
+ ) : (
518
+ renderedChildren()
519
+ )}
361
520
  </div>
362
521
  );
363
522
  }
@@ -366,24 +525,17 @@ function TabListInner<T>(props: {
366
525
  * A Tab represents an individual tab in a TabList.
367
526
  */
368
527
  export function Tab(props: TabProps): JSX.Element {
369
- const [local, ariaProps] = splitProps(props, [
370
- 'class',
371
- 'style',
372
- 'slot',
373
- 'id',
374
- ]);
375
-
376
- // Get state from context
528
+ const [local, ariaProps] = splitProps(props, ["class", "style", "slot", "id"]);
529
+
377
530
  const context = useContext(TabsStateContext);
531
+ const tabsContext = useContext(TabsContext);
378
532
 
379
533
  return (
380
- <Show
381
- when={context}
382
- fallback={<div class="solidaria-Tab" role="tab" />}
383
- >
534
+ <Show when={context} fallback={<div class="solidaria-Tab" role="tab" />}>
384
535
  {(state) => (
385
536
  <TabInner
386
537
  state={state()}
538
+ tabsContext={tabsContext}
387
539
  local={local}
388
540
  ariaProps={ariaProps}
389
541
  children={props.children}
@@ -396,10 +548,34 @@ export function Tab(props: TabProps): JSX.Element {
396
548
  /** Inner Tab component that has access to context */
397
549
  function TabInner(props: {
398
550
  state: TabListState<unknown>;
399
- local: { class?: ClassNameOrFunction<TabRenderProps>; style?: StyleOrFunction<TabRenderProps>; slot?: string; id: Key };
400
- 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">;
401
559
  children?: RenderChildren<TabRenderProps>;
402
560
  }): JSX.Element {
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
+ });
578
+
403
579
  // Create tab aria props
404
580
  const tabAria = createTab<unknown>(
405
581
  {
@@ -407,21 +583,23 @@ function TabInner(props: {
407
583
  get isDisabled() {
408
584
  return props.ariaProps.isDisabled;
409
585
  },
410
- get 'aria-label'() {
411
- 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"];
412
591
  },
413
592
  },
414
- props.state
593
+ props.state,
594
+ () => tabRef ?? null,
415
595
  );
416
596
 
417
- // Create hover
418
597
  const { isHovered, hoverProps } = createHover({
419
598
  get isDisabled() {
420
599
  return tabAria.isDisabled();
421
600
  },
422
601
  });
423
602
 
424
- // Render props values
425
603
  const renderValues = createMemo<TabRenderProps>(() => ({
426
604
  isSelected: tabAria.isSelected(),
427
605
  isFocused: tabAria.isFocused(),
@@ -431,42 +609,112 @@ function TabInner(props: {
431
609
  isDisabled: tabAria.isDisabled(),
432
610
  }));
433
611
 
434
- // Resolve render props
435
612
  const renderProps = useRenderProps(
436
613
  {
437
614
  children: props.children,
438
615
  class: props.local.class,
439
616
  style: props.local.style,
440
- defaultClassName: 'solidaria-Tab',
617
+ defaultClassName: "solidaria-Tab",
441
618
  },
442
- renderValues
619
+ renderValues,
620
+ );
621
+
622
+ const selectionIndicatorContext = createMemo<SelectionIndicatorContextValue>(() => ({
623
+ isSelected: tabAria.isSelected,
624
+ }));
625
+
626
+ return (
627
+ <SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
628
+ <div
629
+ ref={tabRef}
630
+ id={tabAria.tabProps.id}
631
+ role={tabAria.tabProps.role}
632
+ aria-selected={tabAria.isSelected()}
633
+ aria-disabled={tabAria.isDisabled() || undefined}
634
+ aria-controls={tabAria.isSelected() ? tabAria.tabProps["aria-controls"] : undefined}
635
+ aria-label={tabAria.tabProps["aria-label"]}
636
+ aria-labelledby={tabAria.tabProps["aria-labelledby"]}
637
+ tabIndex={tabAria.isSelected() && !tabAria.isDisabled() ? 0 : -1}
638
+ class={renderProps.class()}
639
+ style={renderProps.style()}
640
+ onKeyDown={tabAria.tabProps.onKeyDown}
641
+ onMouseDown={tabAria.tabProps.onMouseDown}
642
+ onPointerDown={tabAria.tabProps.onPointerDown}
643
+ onClick={tabAria.tabProps.onClick}
644
+ onFocus={tabAria.tabProps.onFocus}
645
+ {...hoverProps}
646
+ data-selected={tabAria.isSelected() || undefined}
647
+ data-focused={tabAria.isFocused() || undefined}
648
+ data-focus-visible={tabAria.isFocusVisible() || undefined}
649
+ data-pressed={tabAria.isPressed() || undefined}
650
+ data-hovered={isHovered() || undefined}
651
+ data-disabled={tabAria.isDisabled() || undefined}
652
+ >
653
+ {renderProps.renderChildren()}
654
+ </div>
655
+ </SelectionIndicatorContext.Provider>
443
656
  );
657
+ }
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;
444
707
 
445
708
  return (
446
709
  <div
447
- id={tabAria.tabProps.id}
448
- role={tabAria.tabProps.role}
449
- aria-selected={tabAria.isSelected()}
450
- aria-disabled={tabAria.isDisabled() || undefined}
451
- aria-controls={tabAria.isSelected() ? tabAria.tabProps['aria-controls'] : undefined}
452
- aria-label={tabAria.tabProps['aria-label']}
453
- tabIndex={tabAria.isSelected() && !tabAria.isDisabled() ? 0 : -1}
454
- class={renderProps.class()}
455
- style={renderProps.style()}
456
- onKeyDown={tabAria.tabProps.onKeyDown}
457
- onMouseDown={tabAria.tabProps.onMouseDown}
458
- onPointerDown={tabAria.tabProps.onPointerDown}
459
- onClick={tabAria.tabProps.onClick}
460
- onFocus={tabAria.tabProps.onFocus}
461
- {...hoverProps}
462
- data-selected={tabAria.isSelected() || undefined}
463
- data-focused={tabAria.isFocused() || undefined}
464
- data-focus-visible={tabAria.isFocusVisible() || undefined}
465
- data-pressed={tabAria.isPressed() || undefined}
466
- data-hovered={isHovered() || undefined}
467
- data-disabled={tabAria.isDisabled() || undefined}
710
+ {...domProps()}
711
+ ref={(el) => {
712
+ ref = el;
713
+ }}
714
+ class={local.class ?? "solidaria-TabPanels"}
715
+ style={mergedStyle()}
468
716
  >
469
- {renderProps.renderChildren()}
717
+ {local.children}
470
718
  </div>
471
719
  );
472
720
  }
@@ -475,12 +723,7 @@ function TabInner(props: {
475
723
  * A TabPanel displays the content for a selected Tab.
476
724
  */
477
725
  export function TabPanel(props: TabPanelProps): JSX.Element {
478
- const [local, ariaProps] = splitProps(props, [
479
- 'class',
480
- 'style',
481
- 'slot',
482
- 'shouldForceMount',
483
- ]);
726
+ const [local, ariaProps] = splitProps(props, ["class", "style", "slot", "shouldForceMount"]);
484
727
 
485
728
  // Get state from context (may be null for SSR scenarios)
486
729
  const state = useContext(TabsStateContext);
@@ -490,23 +733,29 @@ export function TabPanel(props: TabPanelProps): JSX.Element {
490
733
 
491
734
  // Create focus ring for the panel
492
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;
493
740
 
494
- // Render props values
495
741
  const renderValues = createMemo<TabPanelRenderProps>(() => ({
496
742
  isSelected: isSelected(),
497
743
  isFocused: isFocused(),
498
744
  isFocusVisible: isFocusVisible(),
745
+ isInert: isInert(),
746
+ isEntering: isEntering(),
747
+ isExiting: isExiting(),
748
+ state,
499
749
  }));
500
750
 
501
- // Resolve render props
502
751
  const renderProps = useRenderProps(
503
752
  {
504
753
  children: props.children,
505
754
  class: local.class,
506
755
  style: local.style,
507
- defaultClassName: 'solidaria-TabPanel',
756
+ defaultClassName: "solidaria-TabPanel",
508
757
  },
509
- renderValues
758
+ renderValues,
510
759
  );
511
760
 
512
761
  // Determine if we should render the panel
@@ -515,7 +764,6 @@ export function TabPanel(props: TabPanelProps): JSX.Element {
515
764
  const shouldRender = () => {
516
765
  if (local.shouldForceMount) return true;
517
766
  if (ariaProps.id === undefined) {
518
- // Shared panel pattern - render when any tab is selected
519
767
  return state ? state.selectedKey() !== null : true;
520
768
  }
521
769
  return isSelected();
@@ -526,9 +774,9 @@ export function TabPanel(props: TabPanelProps): JSX.Element {
526
774
  <div
527
775
  id={tabPanelProps.id}
528
776
  role={tabPanelProps.role}
529
- aria-labelledby={tabPanelProps['aria-labelledby']}
530
- aria-label={tabPanelProps['aria-label']}
531
- aria-describedby={tabPanelProps['aria-describedby']}
777
+ aria-labelledby={tabPanelProps["aria-labelledby"]}
778
+ aria-label={tabPanelProps["aria-label"]}
779
+ aria-describedby={tabPanelProps["aria-describedby"]}
532
780
  tabIndex={tabPanelProps.tabIndex}
533
781
  class={renderProps.class()}
534
782
  style={renderProps.style()}
@@ -537,8 +785,13 @@ export function TabPanel(props: TabPanelProps): JSX.Element {
537
785
  data-selected={isSelected() || undefined}
538
786
  data-focused={isFocused() || undefined}
539
787
  data-focus-visible={isFocusVisible() || undefined}
540
- inert={ariaProps.id !== undefined && !isSelected() ? true : undefined}
541
- 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
+ }
542
795
  >
543
796
  {renderProps.renderChildren()}
544
797
  </div>
@@ -546,7 +799,8 @@ export function TabPanel(props: TabPanelProps): JSX.Element {
546
799
  );
547
800
  }
548
801
 
549
- // Attach sub-components
550
802
  Tabs.List = TabList;
551
803
  Tabs.Tab = Tab;
804
+ Tabs.Panels = TabPanels;
552
805
  Tabs.Panel = TabPanel;
806
+ Tabs.SelectionIndicator = SelectionIndicator;