@proyecto-viviana/solidaria-components 0.2.2 → 0.2.3

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 (64) hide show
  1. package/dist/Color.d.ts +2 -6
  2. package/dist/Color.d.ts.map +1 -1
  3. package/dist/ComboBox.d.ts +3 -3
  4. package/dist/ComboBox.d.ts.map +1 -1
  5. package/dist/GridList.d.ts +2 -2
  6. package/dist/GridList.d.ts.map +1 -1
  7. package/dist/ListBox.d.ts +5 -5
  8. package/dist/ListBox.d.ts.map +1 -1
  9. package/dist/Menu.d.ts +3 -3
  10. package/dist/Menu.d.ts.map +1 -1
  11. package/dist/Select.d.ts +3 -3
  12. package/dist/Select.d.ts.map +1 -1
  13. package/dist/Table.d.ts +2 -2
  14. package/dist/Table.d.ts.map +1 -1
  15. package/dist/Tabs.d.ts +1 -1
  16. package/dist/Tabs.d.ts.map +1 -1
  17. package/dist/index.js +56 -56
  18. package/dist/index.js.map +2 -2
  19. package/dist/index.ssr.js +56 -56
  20. package/dist/index.ssr.js.map +2 -2
  21. package/package.json +10 -8
  22. package/src/Autocomplete.tsx +174 -0
  23. package/src/Breadcrumbs.tsx +264 -0
  24. package/src/Button.tsx +238 -0
  25. package/src/Calendar.tsx +471 -0
  26. package/src/Checkbox.tsx +387 -0
  27. package/src/Color.tsx +1370 -0
  28. package/src/ComboBox.tsx +824 -0
  29. package/src/DateField.tsx +337 -0
  30. package/src/DatePicker.tsx +367 -0
  31. package/src/Dialog.tsx +262 -0
  32. package/src/Disclosure.tsx +439 -0
  33. package/src/GridList.tsx +511 -0
  34. package/src/Landmark.tsx +203 -0
  35. package/src/Link.tsx +201 -0
  36. package/src/ListBox.tsx +346 -0
  37. package/src/Menu.tsx +544 -0
  38. package/src/Meter.tsx +157 -0
  39. package/src/Modal.tsx +433 -0
  40. package/src/NumberField.tsx +542 -0
  41. package/src/Popover.tsx +540 -0
  42. package/src/ProgressBar.tsx +162 -0
  43. package/src/RadioGroup.tsx +356 -0
  44. package/src/RangeCalendar.tsx +462 -0
  45. package/src/SearchField.tsx +479 -0
  46. package/src/Select.tsx +734 -0
  47. package/src/Separator.tsx +130 -0
  48. package/src/Slider.tsx +500 -0
  49. package/src/Switch.tsx +213 -0
  50. package/src/Table.tsx +857 -0
  51. package/src/Tabs.tsx +552 -0
  52. package/src/TagGroup.tsx +421 -0
  53. package/src/TextField.tsx +271 -0
  54. package/src/TimeField.tsx +455 -0
  55. package/src/Toast.tsx +503 -0
  56. package/src/Toolbar.tsx +160 -0
  57. package/src/Tooltip.tsx +423 -0
  58. package/src/Tree.tsx +551 -0
  59. package/src/VisuallyHidden.tsx +60 -0
  60. package/src/contexts.ts +74 -0
  61. package/src/index.ts +620 -0
  62. package/src/utils.tsx +329 -0
  63. package/dist/index.jsx +0 -9056
  64. package/dist/index.jsx.map +0 -7
package/src/Table.tsx ADDED
@@ -0,0 +1,857 @@
1
+ /**
2
+ * Table component for solidaria-components
3
+ *
4
+ * A pre-wired headless table that combines state + aria hooks.
5
+ * Based on react-aria-components/src/Table.tsx
6
+ */
7
+
8
+ import {
9
+ type JSX,
10
+ createContext,
11
+ createMemo,
12
+ createSignal,
13
+ splitProps,
14
+ useContext,
15
+ For,
16
+ Show,
17
+ } from 'solid-js';
18
+ import {
19
+ createTable,
20
+ createTableColumnHeader,
21
+ createTableRow,
22
+ createTableCell,
23
+ createTableRowGroup,
24
+ createTableSelectionCheckbox,
25
+ createTableSelectAllCheckbox,
26
+ createFocusRing,
27
+ createHover,
28
+ type AriaTableProps,
29
+ } from '@proyecto-viviana/solidaria';
30
+ import {
31
+ createTableState,
32
+ createTableCollection,
33
+ type TableState,
34
+ type TableCollection,
35
+ type Key,
36
+ type SortDescriptor,
37
+ type ColumnDefinition,
38
+ type GridNode,
39
+ } from '@proyecto-viviana/solid-stately';
40
+ import {
41
+ type RenderChildren,
42
+ type ClassNameOrFunction,
43
+ type StyleOrFunction,
44
+ type SlotProps,
45
+ useRenderProps,
46
+ filterDOMProps,
47
+ } from './utils';
48
+
49
+ // ============================================
50
+ // TYPES
51
+ // ============================================
52
+
53
+ export interface TableRenderProps {
54
+ /** Whether the table has focus. */
55
+ isFocused: boolean;
56
+ /** Whether the table has keyboard focus. */
57
+ isFocusVisible: boolean;
58
+ /** Whether the table is disabled. */
59
+ isDisabled: boolean;
60
+ /** Whether the table is empty. */
61
+ isEmpty: boolean;
62
+ }
63
+
64
+ export interface TableProps<T extends object> extends Omit<AriaTableProps, 'children'>, SlotProps {
65
+ /** The data items to render in the table. */
66
+ items: T[];
67
+ /** The column definitions. */
68
+ columns: ColumnDefinition<T>[];
69
+ /** Function to get the key from an item. */
70
+ getKey?: (item: T) => Key;
71
+ /** Function to get the text value from an item for a column. */
72
+ getTextValue?: (item: T, column: ColumnDefinition<T>) => string;
73
+ /** The selection mode. */
74
+ selectionMode?: 'none' | 'single' | 'multiple';
75
+ /** Keys of disabled items. */
76
+ disabledKeys?: Iterable<Key>;
77
+ /** Currently selected keys (controlled). */
78
+ selectedKeys?: 'all' | Iterable<Key>;
79
+ /** Default selected keys (uncontrolled). */
80
+ defaultSelectedKeys?: 'all' | Iterable<Key>;
81
+ /** Handler called when selection changes. */
82
+ onSelectionChange?: (keys: 'all' | Set<Key>) => void;
83
+ /** The current sort descriptor. */
84
+ sortDescriptor?: SortDescriptor;
85
+ /** Handler called when sort changes. */
86
+ onSortChange?: (descriptor: SortDescriptor) => void;
87
+ /** Whether to show selection checkboxes. */
88
+ showSelectionCheckboxes?: boolean;
89
+ /** The children of the component. */
90
+ children?: JSX.Element | RenderChildren<TableRenderProps>;
91
+ /** The CSS className for the element. */
92
+ class?: ClassNameOrFunction<TableRenderProps>;
93
+ /** The inline style for the element. */
94
+ style?: StyleOrFunction<TableRenderProps>;
95
+ /** A function to render when the table is empty. */
96
+ renderEmptyState?: () => JSX.Element;
97
+ }
98
+
99
+ export interface TableHeaderRenderProps {
100
+ /** Whether the header has focus. */
101
+ isFocused: boolean;
102
+ }
103
+
104
+ export interface TableHeaderProps extends SlotProps {
105
+ /** The children (usually TableColumn components). */
106
+ children?: JSX.Element;
107
+ /** The CSS className for the element. */
108
+ class?: ClassNameOrFunction<TableHeaderRenderProps>;
109
+ /** The inline style for the element. */
110
+ style?: StyleOrFunction<TableHeaderRenderProps>;
111
+ }
112
+
113
+ export interface TableColumnRenderProps {
114
+ /** Whether the column is focused. */
115
+ isFocused: boolean;
116
+ /** Whether the column has keyboard focus. */
117
+ isFocusVisible: boolean;
118
+ /** Whether the column is sortable. */
119
+ isSortable: boolean;
120
+ /** The current sort direction ('ascending', 'descending', or undefined). */
121
+ sortDirection: 'ascending' | 'descending' | undefined;
122
+ /** Whether the column is being hovered. */
123
+ isHovered: boolean;
124
+ }
125
+
126
+ export interface TableColumnProps extends SlotProps {
127
+ /** The unique key for the column. */
128
+ id: Key;
129
+ /** Whether the column allows sorting. */
130
+ allowsSorting?: boolean;
131
+ /** The children of the column. */
132
+ children?: RenderChildren<TableColumnRenderProps>;
133
+ /** The CSS className for the element. */
134
+ class?: ClassNameOrFunction<TableColumnRenderProps>;
135
+ /** The inline style for the element. */
136
+ style?: StyleOrFunction<TableColumnRenderProps>;
137
+ }
138
+
139
+ export interface TableBodyRenderProps {
140
+ /** Whether the body is empty. */
141
+ isEmpty: boolean;
142
+ }
143
+
144
+ export interface TableBodyProps<T> extends SlotProps {
145
+ /** The items to render. If not provided, uses items from Table. */
146
+ items?: T[];
147
+ /** The children (usually a render function for TableRow). */
148
+ children?: (item: T) => JSX.Element;
149
+ /** The CSS className for the element. */
150
+ class?: ClassNameOrFunction<TableBodyRenderProps>;
151
+ /** The inline style for the element. */
152
+ style?: StyleOrFunction<TableBodyRenderProps>;
153
+ /** A function to render when the body is empty. */
154
+ renderEmptyState?: () => JSX.Element;
155
+ }
156
+
157
+ export interface TableRowRenderProps {
158
+ /** Whether the row is selected. */
159
+ isSelected: boolean;
160
+ /** Whether the row is focused. */
161
+ isFocused: boolean;
162
+ /** Whether the row has keyboard focus. */
163
+ isFocusVisible: boolean;
164
+ /** Whether the row is pressed. */
165
+ isPressed: boolean;
166
+ /** Whether the row is hovered. */
167
+ isHovered: boolean;
168
+ /** Whether the row is disabled. */
169
+ isDisabled: boolean;
170
+ }
171
+
172
+ export interface TableRowProps<T> extends SlotProps {
173
+ /** The unique key for the row. */
174
+ id: Key;
175
+ /** The item value. */
176
+ item?: T;
177
+ /** The children of the row (usually TableCell components). */
178
+ children?: JSX.Element | RenderChildren<TableRowRenderProps>;
179
+ /** The CSS className for the element. */
180
+ class?: ClassNameOrFunction<TableRowRenderProps>;
181
+ /** The inline style for the element. */
182
+ style?: StyleOrFunction<TableRowRenderProps>;
183
+ /** Handler called when the row is activated (double-click or Enter). */
184
+ onAction?: () => void;
185
+ }
186
+
187
+ export interface TableCellRenderProps {
188
+ /** Whether the cell is focused. */
189
+ isFocused: boolean;
190
+ /** Whether the cell has keyboard focus. */
191
+ isFocusVisible: boolean;
192
+ /** Whether the cell is pressed. */
193
+ isPressed: boolean;
194
+ /** Whether the cell is hovered. */
195
+ isHovered: boolean;
196
+ }
197
+
198
+ export interface TableCellProps extends SlotProps {
199
+ /** The unique key for the cell. */
200
+ id?: Key;
201
+ /** The children of the cell. */
202
+ children?: RenderChildren<TableCellRenderProps>;
203
+ /** The CSS className for the element. */
204
+ class?: ClassNameOrFunction<TableCellRenderProps>;
205
+ /** The inline style for the element. */
206
+ style?: StyleOrFunction<TableCellRenderProps>;
207
+ }
208
+
209
+ // ============================================
210
+ // CONTEXT
211
+ // ============================================
212
+
213
+ interface TableContextValue<T extends object> {
214
+ state: TableState<T, TableCollection<T>>;
215
+ collection: TableCollection<T>;
216
+ items: T[];
217
+ columns: ColumnDefinition<T>[];
218
+ isDisabled: boolean;
219
+ showSelectionCheckboxes: boolean;
220
+ }
221
+
222
+ export const TableContext = createContext<TableContextValue<object> | null>(null);
223
+ export const TableStateContext = createContext<TableState<object, TableCollection<object>> | null>(null);
224
+
225
+ // Row-level context for cells
226
+ interface TableRowContextValue {
227
+ rowKey: Key;
228
+ rowNode: GridNode<unknown>;
229
+ }
230
+
231
+ export const TableRowContext = createContext<TableRowContextValue | null>(null);
232
+
233
+ // ============================================
234
+ // COMPONENTS
235
+ // ============================================
236
+
237
+ /**
238
+ * A table displays data in rows and columns and enables a user to navigate its contents via directional navigation keys,
239
+ * and optionally supports row selection and sorting.
240
+ */
241
+ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
242
+ const [local, stateProps, ariaProps] = splitProps(
243
+ props,
244
+ ['class', 'style', 'slot', 'renderEmptyState'],
245
+ [
246
+ 'items',
247
+ 'columns',
248
+ 'getKey',
249
+ 'getTextValue',
250
+ 'disabledKeys',
251
+ 'selectionMode',
252
+ 'selectedKeys',
253
+ 'defaultSelectedKeys',
254
+ 'onSelectionChange',
255
+ 'sortDescriptor',
256
+ 'onSortChange',
257
+ 'showSelectionCheckboxes',
258
+ ]
259
+ );
260
+
261
+ // Create ref signal
262
+ const [ref, setRef] = createSignal<HTMLTableElement | null>(null);
263
+
264
+ // Create collection
265
+ const collection = createMemo(() =>
266
+ createTableCollection<T>({
267
+ columns: stateProps.columns,
268
+ rows: stateProps.items,
269
+ getKey: stateProps.getKey,
270
+ getTextValue: stateProps.getTextValue,
271
+ showSelectionCheckboxes: stateProps.showSelectionCheckboxes ?? false,
272
+ })
273
+ );
274
+
275
+ // Create table state
276
+ const state = createTableState<T, TableCollection<T>>(() => ({
277
+ collection: collection(),
278
+ disabledKeys: stateProps.disabledKeys,
279
+ selectionMode: stateProps.selectionMode,
280
+ selectedKeys: stateProps.selectedKeys,
281
+ defaultSelectedKeys: stateProps.defaultSelectedKeys,
282
+ onSelectionChange: stateProps.onSelectionChange,
283
+ sortDescriptor: stateProps.sortDescriptor,
284
+ onSortChange: stateProps.onSortChange,
285
+ showSelectionCheckboxes: stateProps.showSelectionCheckboxes,
286
+ }));
287
+
288
+ // Create table aria props
289
+ const { gridProps } = createTable<T>(
290
+ () => ({
291
+ id: ariaProps.id,
292
+ 'aria-label': ariaProps['aria-label'],
293
+ 'aria-labelledby': ariaProps['aria-labelledby'],
294
+ 'aria-describedby': ariaProps['aria-describedby'],
295
+ isVirtualized: ariaProps.isVirtualized,
296
+ onRowAction: ariaProps.onRowAction,
297
+ onCellAction: ariaProps.onCellAction,
298
+ focusMode: ariaProps.focusMode,
299
+ }),
300
+ () => state,
301
+ ref
302
+ );
303
+
304
+ // Create focus ring
305
+ const { isFocused, isFocusVisible, focusProps } = createFocusRing();
306
+
307
+ // Render props values
308
+ const renderValues = createMemo<TableRenderProps>(() => ({
309
+ isFocused: state.isFocused || isFocused(),
310
+ isFocusVisible: isFocusVisible(),
311
+ isDisabled: false, // Tables don't have a global disabled state
312
+ isEmpty: stateProps.items.length === 0,
313
+ }));
314
+
315
+ // Resolve render props
316
+ const renderProps = useRenderProps(
317
+ {
318
+ children: props.children,
319
+ class: local.class,
320
+ style: local.style,
321
+ defaultClassName: 'solidaria-Table',
322
+ },
323
+ renderValues
324
+ );
325
+
326
+ // Filter DOM props
327
+ const domProps = createMemo(() => {
328
+ const filtered = filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
329
+ return filtered;
330
+ });
331
+
332
+ // Remove ref from spread props
333
+ const cleanGridProps = () => {
334
+ const { ref: _ref1, ...rest } = gridProps as Record<string, unknown>;
335
+ return rest;
336
+ };
337
+ const cleanFocusProps = () => {
338
+ const { ref: _ref2, ...rest } = focusProps as Record<string, unknown>;
339
+ return rest;
340
+ };
341
+
342
+ const contextValue = createMemo<TableContextValue<T>>(() => ({
343
+ state,
344
+ collection: collection(),
345
+ items: stateProps.items,
346
+ columns: stateProps.columns,
347
+ isDisabled: false,
348
+ showSelectionCheckboxes: stateProps.showSelectionCheckboxes ?? false,
349
+ }));
350
+
351
+ return (
352
+ <TableContext.Provider value={contextValue() as TableContextValue<object>}>
353
+ <TableStateContext.Provider value={state as unknown as TableState<object, TableCollection<object>>}>
354
+ <table
355
+ ref={setRef}
356
+ {...domProps()}
357
+ {...cleanGridProps()}
358
+ {...cleanFocusProps()}
359
+ class={renderProps.class()}
360
+ style={renderProps.style()}
361
+ data-focused={state.isFocused || undefined}
362
+ data-focus-visible={isFocusVisible() || undefined}
363
+ data-empty={stateProps.items.length === 0 || undefined}
364
+ >
365
+ {renderProps.renderChildren()}
366
+ </table>
367
+ </TableStateContext.Provider>
368
+ </TableContext.Provider>
369
+ );
370
+ }
371
+
372
+ /**
373
+ * A header row in a table containing column headers.
374
+ */
375
+ export function TableHeader(props: TableHeaderProps): JSX.Element {
376
+ const [local] = splitProps(props, ['class', 'style', 'slot']);
377
+
378
+ // Get context
379
+ const context = useContext(TableContext);
380
+ if (!context) {
381
+ throw new Error('TableHeader must be used within a Table');
382
+ }
383
+
384
+ const { rowGroupProps } = createTableRowGroup(() => ({ type: 'thead' }));
385
+
386
+ // Render props values
387
+ const renderValues = createMemo<TableHeaderRenderProps>(() => ({
388
+ isFocused: false,
389
+ }));
390
+
391
+ // Resolve render props
392
+ const renderProps = useRenderProps(
393
+ {
394
+ class: local.class,
395
+ style: local.style,
396
+ defaultClassName: 'solidaria-Table-header',
397
+ },
398
+ renderValues
399
+ );
400
+
401
+ const cleanRowGroupProps = () => {
402
+ const { ref: _ref, ...rest } = rowGroupProps as Record<string, unknown>;
403
+ return rest;
404
+ };
405
+
406
+ return (
407
+ <thead {...cleanRowGroupProps()} class={renderProps.class()} style={renderProps.style()}>
408
+ <tr role="row">{props.children}</tr>
409
+ </thead>
410
+ );
411
+ }
412
+
413
+ /**
414
+ * A column header in a table.
415
+ */
416
+ export function TableColumn(props: TableColumnProps): JSX.Element {
417
+ const [local] = splitProps(props, ['class', 'style', 'slot', 'id', 'allowsSorting']);
418
+
419
+ // Get context
420
+ const context = useContext(TableContext);
421
+ if (!context) {
422
+ throw new Error('TableColumn must be used within a Table');
423
+ }
424
+ const { state, collection } = context;
425
+
426
+ // Create ref signal
427
+ const [ref, setRef] = createSignal<HTMLTableCellElement | null>(null);
428
+
429
+ // Find the column node
430
+ const columnNode = createMemo(() => {
431
+ const node = collection.getItem(local.id);
432
+ if (!node) {
433
+ // Create a simple node for the column
434
+ return {
435
+ type: 'column' as const,
436
+ key: local.id,
437
+ value: null,
438
+ textValue: String(local.id),
439
+ level: 0,
440
+ index: 0,
441
+ hasChildNodes: false,
442
+ childNodes: [],
443
+ } as GridNode<unknown>;
444
+ }
445
+ return node;
446
+ });
447
+
448
+ // Create column header aria props
449
+ const { columnHeaderProps } = createTableColumnHeader<object>(
450
+ () => ({
451
+ node: columnNode(),
452
+ allowsSorting: local.allowsSorting,
453
+ }),
454
+ () => state as TableState<object, TableCollection<object>>,
455
+ ref
456
+ );
457
+
458
+ // Create hover
459
+ const { isHovered, hoverProps } = createHover({
460
+ isDisabled: false,
461
+ });
462
+
463
+ // Create focus ring
464
+ const { isFocusVisible, focusProps } = createFocusRing();
465
+
466
+ // Get sort direction
467
+ const sortDirection = createMemo(() => {
468
+ const sortDescriptor = state.sortDescriptor;
469
+ if (sortDescriptor?.column === local.id) {
470
+ return sortDescriptor.direction;
471
+ }
472
+ return undefined;
473
+ });
474
+
475
+ // Render props values
476
+ const renderValues = createMemo<TableColumnRenderProps>(() => ({
477
+ isFocused: state.focusedKey === local.id,
478
+ isFocusVisible: isFocusVisible() && state.focusedKey === local.id,
479
+ isSortable: local.allowsSorting ?? false,
480
+ sortDirection: sortDirection(),
481
+ isHovered: isHovered(),
482
+ }));
483
+
484
+ // Resolve render props
485
+ const renderProps = useRenderProps(
486
+ {
487
+ children: props.children,
488
+ class: local.class,
489
+ style: local.style,
490
+ defaultClassName: 'solidaria-Table-column',
491
+ },
492
+ renderValues
493
+ );
494
+
495
+ // Remove ref from spread props
496
+ const cleanColumnHeaderProps = () => {
497
+ const { ref: _ref1, ...rest } = columnHeaderProps as Record<string, unknown>;
498
+ return rest;
499
+ };
500
+ const cleanHoverProps = () => {
501
+ const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
502
+ return rest;
503
+ };
504
+ const cleanFocusProps = () => {
505
+ const { ref: _ref3, ...rest } = focusProps as Record<string, unknown>;
506
+ return rest;
507
+ };
508
+
509
+ return (
510
+ <th
511
+ ref={setRef}
512
+ {...cleanColumnHeaderProps()}
513
+ {...cleanHoverProps()}
514
+ {...cleanFocusProps()}
515
+ class={renderProps.class()}
516
+ style={renderProps.style()}
517
+ data-sortable={local.allowsSorting || undefined}
518
+ data-sort-direction={sortDirection() || undefined}
519
+ data-hovered={isHovered() || undefined}
520
+ data-focused={state.focusedKey === local.id || undefined}
521
+ data-focus-visible={(isFocusVisible() && state.focusedKey === local.id) || undefined}
522
+ >
523
+ {renderProps.renderChildren()}
524
+ </th>
525
+ );
526
+ }
527
+
528
+ /**
529
+ * The body of a table containing data rows.
530
+ */
531
+ export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Element {
532
+ const [local] = splitProps(props, ['items', 'class', 'style', 'slot', 'renderEmptyState']);
533
+
534
+ // Get context
535
+ const context = useContext(TableContext);
536
+ if (!context) {
537
+ throw new Error('TableBody must be used within a Table');
538
+ }
539
+
540
+ const { rowGroupProps } = createTableRowGroup(() => ({ type: 'tbody' }));
541
+
542
+ // Use provided items or context items
543
+ const items = createMemo(() => (local.items ?? context.items) as T[]);
544
+
545
+ // Render props values
546
+ const renderValues = createMemo<TableBodyRenderProps>(() => ({
547
+ isEmpty: items().length === 0,
548
+ }));
549
+
550
+ // Resolve render props
551
+ const renderProps = useRenderProps(
552
+ {
553
+ class: local.class,
554
+ style: local.style,
555
+ defaultClassName: 'solidaria-Table-body',
556
+ },
557
+ renderValues
558
+ );
559
+
560
+ const cleanRowGroupProps = () => {
561
+ const { ref: _ref, ...rest } = rowGroupProps as Record<string, unknown>;
562
+ return rest;
563
+ };
564
+
565
+ const isEmpty = () => items().length === 0;
566
+
567
+ return (
568
+ <tbody {...cleanRowGroupProps()} class={renderProps.class()} style={renderProps.style()}>
569
+ <Show when={isEmpty() && local.renderEmptyState} fallback={<For each={items()}>{(item) => props.children?.(item)}</For>}>
570
+ {local.renderEmptyState?.()}
571
+ </Show>
572
+ </tbody>
573
+ );
574
+ }
575
+
576
+ /**
577
+ * A row in a table.
578
+ */
579
+ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element {
580
+ const [local] = splitProps(props, ['class', 'style', 'slot', 'id', 'item', 'onAction']);
581
+
582
+ // Get context
583
+ const context = useContext(TableContext);
584
+ if (!context) {
585
+ throw new Error('TableRow must be used within a Table');
586
+ }
587
+ const { state, collection } = context;
588
+
589
+ // Create ref signal
590
+ const [ref, setRef] = createSignal<HTMLTableRowElement | null>(null);
591
+
592
+ // Find the row node
593
+ const rowNode = createMemo(() => {
594
+ const node = collection.getItem(local.id);
595
+ if (!node) {
596
+ // Create a simple node for the row
597
+ return {
598
+ type: 'item' as const,
599
+ key: local.id,
600
+ value: local.item ?? null,
601
+ textValue: String(local.id),
602
+ level: 0,
603
+ index: 0,
604
+ hasChildNodes: true,
605
+ childNodes: [],
606
+ } as GridNode<unknown>;
607
+ }
608
+ return node;
609
+ });
610
+
611
+ // Create row aria props
612
+ const { rowProps, isSelected, isDisabled, isPressed } = createTableRow<object>(
613
+ () => ({
614
+ node: rowNode(),
615
+ onAction: local.onAction,
616
+ }),
617
+ () => state as TableState<object, TableCollection<object>>,
618
+ ref
619
+ );
620
+
621
+ // Create hover
622
+ const { isHovered, hoverProps } = createHover({
623
+ get isDisabled() {
624
+ return isDisabled;
625
+ },
626
+ });
627
+
628
+ // Create focus ring
629
+ const { isFocusVisible, focusProps } = createFocusRing();
630
+
631
+ // Check if focused
632
+ const isFocused = createMemo(() => state.focusedKey === local.id);
633
+
634
+ // Render props values
635
+ const renderValues = createMemo<TableRowRenderProps>(() => ({
636
+ isSelected,
637
+ isFocused: isFocused(),
638
+ isFocusVisible: isFocusVisible() && isFocused(),
639
+ isPressed,
640
+ isHovered: isHovered(),
641
+ isDisabled,
642
+ }));
643
+
644
+ // Resolve render props
645
+ const renderProps = useRenderProps(
646
+ {
647
+ children: props.children,
648
+ class: local.class,
649
+ style: local.style,
650
+ defaultClassName: 'solidaria-Table-row',
651
+ },
652
+ renderValues
653
+ );
654
+
655
+ // Remove ref from spread props
656
+ const cleanRowProps = () => {
657
+ const { ref: _ref1, ...rest } = rowProps as Record<string, unknown>;
658
+ return rest;
659
+ };
660
+ const cleanHoverProps = () => {
661
+ const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
662
+ return rest;
663
+ };
664
+ const cleanFocusProps = () => {
665
+ const { ref: _ref3, ...rest } = focusProps as Record<string, unknown>;
666
+ return rest;
667
+ };
668
+
669
+ const rowContextValue: TableRowContextValue = {
670
+ rowKey: local.id,
671
+ rowNode: rowNode(),
672
+ };
673
+
674
+ return (
675
+ <TableRowContext.Provider value={rowContextValue}>
676
+ <tr
677
+ ref={setRef}
678
+ {...cleanRowProps()}
679
+ {...cleanHoverProps()}
680
+ {...cleanFocusProps()}
681
+ class={renderProps.class()}
682
+ style={renderProps.style()}
683
+ data-selected={isSelected || undefined}
684
+ data-focused={isFocused() || undefined}
685
+ data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
686
+ data-pressed={isPressed || undefined}
687
+ data-hovered={isHovered() || undefined}
688
+ data-disabled={isDisabled || undefined}
689
+ >
690
+ {renderProps.renderChildren()}
691
+ </tr>
692
+ </TableRowContext.Provider>
693
+ );
694
+ }
695
+
696
+ /**
697
+ * A cell in a table row.
698
+ */
699
+ export function TableCell(props: TableCellProps): JSX.Element {
700
+ const [local] = splitProps(props, ['class', 'style', 'slot', 'id']);
701
+
702
+ // Get context
703
+ const tableContext = useContext(TableContext);
704
+ const rowContext = useContext(TableRowContext);
705
+
706
+ if (!tableContext) {
707
+ throw new Error('TableCell must be used within a Table');
708
+ }
709
+ if (!rowContext) {
710
+ throw new Error('TableCell must be used within a Table');
711
+ }
712
+
713
+ const { state, collection } = tableContext;
714
+ const { rowKey, rowNode } = rowContext;
715
+
716
+ // Create ref signal
717
+ const [ref, setRef] = createSignal<HTMLTableCellElement | null>(null);
718
+
719
+ // Find the cell node
720
+ const cellNode = createMemo(() => {
721
+ // If id is provided, look for that specific cell
722
+ if (local.id != null) {
723
+ const cellKey = `${rowKey}-${local.id}`;
724
+ const node = collection.getItem(cellKey);
725
+ if (node) return node;
726
+ }
727
+
728
+ // Otherwise create a simple node
729
+ return {
730
+ type: 'cell' as const,
731
+ key: local.id ?? `${rowKey}-cell`,
732
+ value: rowNode.value,
733
+ textValue: '',
734
+ level: 1,
735
+ index: 0,
736
+ parentKey: rowKey,
737
+ hasChildNodes: false,
738
+ childNodes: [],
739
+ } as GridNode<unknown>;
740
+ });
741
+
742
+ // Create cell aria props
743
+ const { gridCellProps, isPressed } = createTableCell<object>(
744
+ () => ({
745
+ node: cellNode(),
746
+ }),
747
+ () => state as TableState<object, TableCollection<object>>,
748
+ ref
749
+ );
750
+
751
+ // Create hover
752
+ const { isHovered, hoverProps } = createHover({
753
+ isDisabled: false,
754
+ });
755
+
756
+ // Create focus ring
757
+ const { isFocusVisible, focusProps } = createFocusRing();
758
+
759
+ // Check if focused
760
+ const isFocused = createMemo(() => state.focusedKey === cellNode().key);
761
+
762
+ // Render props values
763
+ const renderValues = createMemo<TableCellRenderProps>(() => ({
764
+ isFocused: isFocused(),
765
+ isFocusVisible: isFocusVisible() && isFocused(),
766
+ isPressed,
767
+ isHovered: isHovered(),
768
+ }));
769
+
770
+ // Resolve render props
771
+ const renderProps = useRenderProps(
772
+ {
773
+ children: props.children,
774
+ class: local.class,
775
+ style: local.style,
776
+ defaultClassName: 'solidaria-Table-cell',
777
+ },
778
+ renderValues
779
+ );
780
+
781
+ // Remove ref from spread props
782
+ const cleanCellProps = () => {
783
+ const { ref: _ref1, ...rest } = gridCellProps as Record<string, unknown>;
784
+ return rest;
785
+ };
786
+ const cleanHoverProps = () => {
787
+ const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
788
+ return rest;
789
+ };
790
+ const cleanFocusProps = () => {
791
+ const { ref: _ref3, ...rest } = focusProps as Record<string, unknown>;
792
+ return rest;
793
+ };
794
+
795
+ return (
796
+ <td
797
+ ref={setRef}
798
+ {...cleanCellProps()}
799
+ {...cleanHoverProps()}
800
+ {...cleanFocusProps()}
801
+ class={renderProps.class()}
802
+ style={renderProps.style()}
803
+ data-focused={isFocused() || undefined}
804
+ data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
805
+ data-pressed={isPressed || undefined}
806
+ data-hovered={isHovered() || undefined}
807
+ >
808
+ {renderProps.renderChildren()}
809
+ </td>
810
+ );
811
+ }
812
+
813
+ /**
814
+ * A checkbox cell for row selection.
815
+ */
816
+ export function TableSelectionCheckbox(props: { rowKey: Key }): JSX.Element {
817
+ const context = useContext(TableContext);
818
+ if (!context) {
819
+ throw new Error('TableSelectionCheckbox must be used within a Table');
820
+ }
821
+
822
+ const { state } = context;
823
+
824
+ const { checkboxProps } = createTableSelectionCheckbox<object>(
825
+ () => ({ key: props.rowKey }),
826
+ () => state as TableState<object, TableCollection<object>>
827
+ );
828
+
829
+ return <input {...checkboxProps} />;
830
+ }
831
+
832
+ /**
833
+ * A checkbox for select-all functionality.
834
+ */
835
+ export function TableSelectAllCheckbox(): JSX.Element {
836
+ const context = useContext(TableContext);
837
+ if (!context) {
838
+ throw new Error('TableSelectAllCheckbox must be used within a Table');
839
+ }
840
+
841
+ const { state } = context;
842
+
843
+ const { checkboxProps } = createTableSelectAllCheckbox<object>(
844
+ () => state as TableState<object, TableCollection<object>>
845
+ );
846
+
847
+ return <input {...checkboxProps} />;
848
+ }
849
+
850
+ // Attach components as static properties
851
+ Table.Header = TableHeader;
852
+ Table.Column = TableColumn;
853
+ Table.Body = TableBody;
854
+ Table.Row = TableRow;
855
+ Table.Cell = TableCell;
856
+ Table.SelectionCheckbox = TableSelectionCheckbox;
857
+ Table.SelectAllCheckbox = TableSelectAllCheckbox;