@duro-app/ui 0.24.0 → 0.26.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.
- package/dist/components/ButtonGroup/ButtonGroup.meta.d.ts.map +1 -1
- package/dist/components/CheckboxGroup/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/CheckboxGroup/CheckboxGroup.meta.d.ts.map +1 -1
- package/dist/components/Combobox/Combobox.d.ts +44 -0
- package/dist/components/Combobox/Combobox.d.ts.map +1 -0
- package/dist/components/Combobox/Combobox.meta.d.ts +3 -0
- package/dist/components/Combobox/Combobox.meta.d.ts.map +1 -0
- package/dist/components/Combobox/Combobox.stories.d.ts +9 -0
- package/dist/components/Combobox/Combobox.stories.d.ts.map +1 -0
- package/dist/components/Combobox/ComboboxContext.d.ts +19 -0
- package/dist/components/Combobox/ComboboxContext.d.ts.map +1 -0
- package/dist/components/Combobox/styles.css.d.ts +112 -0
- package/dist/components/Combobox/styles.css.d.ts.map +1 -0
- package/dist/components/Combobox/useComboboxRoot.d.ts +15 -0
- package/dist/components/Combobox/useComboboxRoot.d.ts.map +1 -0
- package/dist/components/Field/Field.d.ts +4 -0
- package/dist/components/Field/Field.d.ts.map +1 -1
- package/dist/components/Field/Field.stories.d.ts +3 -0
- package/dist/components/Field/Field.stories.d.ts.map +1 -1
- package/dist/components/Field/FieldContext.d.ts +5 -0
- package/dist/components/Field/FieldContext.d.ts.map +1 -1
- package/dist/components/Field/styles.css.d.ts +26 -0
- package/dist/components/Field/styles.css.d.ts.map +1 -1
- package/dist/components/Form/Form.d.ts +7 -2
- package/dist/components/Form/Form.d.ts.map +1 -1
- package/dist/components/Form/Form.stories.d.ts +6 -0
- package/dist/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/components/Form/FormContext.d.ts +9 -2
- package/dist/components/Form/FormContext.d.ts.map +1 -1
- package/dist/components/Input/Input.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.meta.d.ts.map +1 -1
- package/dist/components/Table/SortIndicator.d.ts.map +1 -1
- package/dist/components/Table/Table.d.ts.map +1 -1
- package/dist/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/components/Table/useDataTable.d.ts.map +1 -1
- package/dist/components/Tag/Tag.d.ts +18 -0
- package/dist/components/Tag/Tag.d.ts.map +1 -0
- package/dist/components/Tag/Tag.meta.d.ts +3 -0
- package/dist/components/Tag/Tag.meta.d.ts.map +1 -0
- package/dist/components/Tag/Tag.stories.d.ts +11 -0
- package/dist/components/Tag/Tag.stories.d.ts.map +1 -0
- package/dist/components/Tag/styles.css.d.ts +80 -0
- package/dist/components/Tag/styles.css.d.ts.map +1 -0
- package/dist/components/TagGroup/TagGroup.d.ts +30 -0
- package/dist/components/TagGroup/TagGroup.d.ts.map +1 -0
- package/dist/components/TagGroup/TagGroup.meta.d.ts +3 -0
- package/dist/components/TagGroup/TagGroup.meta.d.ts.map +1 -0
- package/dist/components/TagGroup/TagGroup.stories.d.ts +11 -0
- package/dist/components/TagGroup/TagGroup.stories.d.ts.map +1 -0
- package/dist/components/TagGroup/TagGroupContext.d.ts +17 -0
- package/dist/components/TagGroup/TagGroupContext.d.ts.map +1 -0
- package/dist/components/TagGroup/styles.css.d.ts +75 -0
- package/dist/components/TagGroup/styles.css.d.ts.map +1 -0
- package/dist/components/Textarea/Textarea.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3256 -2318
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ButtonGroup/ButtonGroup.meta.ts +10 -2
- package/src/components/CheckboxGroup/CheckboxGroup.meta.ts +4 -1
- package/src/components/CheckboxGroup/CheckboxGroup.tsx +2 -9
- package/src/components/Combobox/Combobox.meta.ts +41 -0
- package/src/components/Combobox/Combobox.stories.tsx +157 -0
- package/src/components/Combobox/Combobox.tsx +235 -0
- package/src/components/Combobox/ComboboxContext.ts +26 -0
- package/src/components/Combobox/styles.css.ts +119 -0
- package/src/components/Combobox/useComboboxRoot.ts +235 -0
- package/src/components/Field/Field.stories.tsx +62 -0
- package/src/components/Field/Field.tsx +94 -9
- package/src/components/Field/FieldContext.ts +5 -0
- package/src/components/Field/styles.css.ts +26 -0
- package/src/components/Form/Form.stories.tsx +275 -0
- package/src/components/Form/Form.tsx +69 -7
- package/src/components/Form/FormContext.ts +10 -1
- package/src/components/Input/Input.tsx +2 -1
- package/src/components/RadioGroup/RadioGroup.meta.ts +4 -1
- package/src/components/RadioGroup/RadioGroup.tsx +1 -4
- package/src/components/Table/SortIndicator.tsx +1 -4
- package/src/components/Table/Table.stories.tsx +190 -84
- package/src/components/Table/Table.tsx +10 -5
- package/src/components/Table/useDataTable.ts +34 -48
- package/src/components/Tag/Tag.meta.ts +22 -0
- package/src/components/Tag/Tag.stories.tsx +92 -0
- package/src/components/Tag/Tag.tsx +139 -0
- package/src/components/Tag/styles.css.ts +99 -0
- package/src/components/TagGroup/TagGroup.meta.ts +27 -0
- package/src/components/TagGroup/TagGroup.stories.tsx +193 -0
- package/src/components/TagGroup/TagGroup.tsx +302 -0
- package/src/components/TagGroup/TagGroupContext.ts +22 -0
- package/src/components/TagGroup/styles.css.ts +97 -0
- package/src/components/Textarea/Textarea.tsx +2 -1
- package/src/index.ts +4 -0
- package/src/strict.css +1 -1
|
@@ -9,7 +9,21 @@ import {Button} from '../Button/Button'
|
|
|
9
9
|
import {ActionBar} from '../ActionBar/ActionBar'
|
|
10
10
|
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
flexRender,
|
|
14
|
+
createColumnHelper,
|
|
15
|
+
useReactTable,
|
|
16
|
+
getCoreRowModel,
|
|
17
|
+
getSortedRowModel,
|
|
18
|
+
getFilteredRowModel,
|
|
19
|
+
getPaginationRowModel,
|
|
20
|
+
type SortingState,
|
|
21
|
+
type ColumnFiltersState,
|
|
22
|
+
type PaginationState,
|
|
23
|
+
} from '@tanstack/react-table'
|
|
24
|
+
import {useDataTable} from './useDataTable'
|
|
25
|
+
import {Combobox} from '../Combobox/Combobox'
|
|
26
|
+
import {spacing} from '@duro-app/tokens/tokens/spacing.css'
|
|
13
27
|
|
|
14
28
|
const meta: Meta = {
|
|
15
29
|
title: 'Components/Table',
|
|
@@ -429,30 +443,31 @@ export const AllVariants: Story = {
|
|
|
429
443
|
},
|
|
430
444
|
}
|
|
431
445
|
|
|
432
|
-
|
|
433
446
|
// --- Sorting: demonstrates click-to-sort with hover indicators ---
|
|
434
447
|
|
|
435
|
-
|
|
436
|
-
const data = [
|
|
437
|
-
{name: 'Traefik', role: 'Reverse Proxy', port: 443, uptime: '14d 3h'},
|
|
438
|
-
{name: 'Pi-hole', role: 'DNS Filter', port: 80, uptime: '14d 3h'},
|
|
439
|
-
{name: 'Grafana', role: 'Monitoring', port: 3000, uptime: '7d 12h'},
|
|
440
|
-
{name: 'Portainer', role: 'Container Mgmt', port: 9000, uptime: '2d 5h'},
|
|
441
|
-
{name: 'Nginx', role: 'Web Server', port: 8080, uptime: '30d 1h'},
|
|
442
|
-
]
|
|
448
|
+
type Service = {name: string; role: string; port: number; uptime: string}
|
|
443
449
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
+
const sortingData: Service[] = [
|
|
451
|
+
{name: 'Traefik', role: 'Reverse Proxy', port: 443, uptime: '14d 3h'},
|
|
452
|
+
{name: 'Pi-hole', role: 'DNS Filter', port: 80, uptime: '14d 3h'},
|
|
453
|
+
{name: 'Grafana', role: 'Monitoring', port: 3000, uptime: '7d 12h'},
|
|
454
|
+
{name: 'Portainer', role: 'Container Mgmt', port: 9000, uptime: '2d 5h'},
|
|
455
|
+
{name: 'Nginx', role: 'Web Server', port: 8080, uptime: '30d 1h'},
|
|
456
|
+
]
|
|
450
457
|
|
|
458
|
+
const sortingCols = [
|
|
459
|
+
createColumnHelper<Service>().accessor('name', {header: 'Name', enableSorting: true}),
|
|
460
|
+
createColumnHelper<Service>().accessor('role', {header: 'Role', enableSorting: true}),
|
|
461
|
+
createColumnHelper<Service>().accessor('port', {header: 'Port', enableSorting: true}),
|
|
462
|
+
createColumnHelper<Service>().accessor('uptime', {header: 'Uptime', enableSorting: true}),
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
function SortingDemo() {
|
|
451
466
|
const [sorting, setSorting] = useState<SortingState>([])
|
|
452
467
|
|
|
453
468
|
const table = useReactTable({
|
|
454
|
-
data,
|
|
455
|
-
columns:
|
|
469
|
+
data: sortingData,
|
|
470
|
+
columns: sortingCols,
|
|
456
471
|
state: {sorting},
|
|
457
472
|
onSortingChange: setSorting,
|
|
458
473
|
getCoreRowModel: getCoreRowModel(),
|
|
@@ -501,22 +516,103 @@ export const Sorting: Story = {
|
|
|
501
516
|
},
|
|
502
517
|
}
|
|
503
518
|
|
|
519
|
+
// --- Shared data helpers for interactive demos ---
|
|
520
|
+
|
|
521
|
+
interface User {
|
|
522
|
+
name: string
|
|
523
|
+
role: string
|
|
524
|
+
status: string
|
|
525
|
+
email: string
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const firstNames = [
|
|
529
|
+
'Alice',
|
|
530
|
+
'Bob',
|
|
531
|
+
'Carol',
|
|
532
|
+
'Dave',
|
|
533
|
+
'Eve',
|
|
534
|
+
'Frank',
|
|
535
|
+
'Grace',
|
|
536
|
+
'Hank',
|
|
537
|
+
'Iris',
|
|
538
|
+
'Jack',
|
|
539
|
+
'Karen',
|
|
540
|
+
'Leo',
|
|
541
|
+
'Mona',
|
|
542
|
+
'Nick',
|
|
543
|
+
'Olivia',
|
|
544
|
+
'Paul',
|
|
545
|
+
'Quinn',
|
|
546
|
+
'Rita',
|
|
547
|
+
'Sam',
|
|
548
|
+
'Tina',
|
|
549
|
+
'Uma',
|
|
550
|
+
'Vince',
|
|
551
|
+
'Wendy',
|
|
552
|
+
'Xander',
|
|
553
|
+
'Yara',
|
|
554
|
+
'Zach',
|
|
555
|
+
]
|
|
556
|
+
const lastNames = [
|
|
557
|
+
'Adams',
|
|
558
|
+
'Brown',
|
|
559
|
+
'Clark',
|
|
560
|
+
'Davis',
|
|
561
|
+
'Evans',
|
|
562
|
+
'Foster',
|
|
563
|
+
'Garcia',
|
|
564
|
+
'Hill',
|
|
565
|
+
'Ito',
|
|
566
|
+
'Jones',
|
|
567
|
+
'Kim',
|
|
568
|
+
'Lee',
|
|
569
|
+
'Moore',
|
|
570
|
+
'Nash',
|
|
571
|
+
'Owen',
|
|
572
|
+
'Patel',
|
|
573
|
+
'Quinn',
|
|
574
|
+
'Reed',
|
|
575
|
+
'Smith',
|
|
576
|
+
'Tran',
|
|
577
|
+
'Ueda',
|
|
578
|
+
'Voss',
|
|
579
|
+
'Wang',
|
|
580
|
+
'Xu',
|
|
581
|
+
'Yang',
|
|
582
|
+
'Zhang',
|
|
583
|
+
]
|
|
584
|
+
const roles = ['Admin', 'Editor', 'Viewer', 'Moderator', 'Analyst']
|
|
585
|
+
const statuses = ['Active', 'Inactive', 'Pending', 'Suspended']
|
|
586
|
+
|
|
587
|
+
function generateUsers(count: number): User[] {
|
|
588
|
+
return Array.from({length: count}, (_, i) => {
|
|
589
|
+
const first = firstNames[i % firstNames.length]
|
|
590
|
+
const last = lastNames[(i * 7) % lastNames.length]
|
|
591
|
+
return {
|
|
592
|
+
name: `${first} ${last}`,
|
|
593
|
+
role: roles[i % roles.length],
|
|
594
|
+
status: statuses[i % statuses.length],
|
|
595
|
+
email: `${first.toLowerCase()}.${last.toLowerCase()}@example.com`,
|
|
596
|
+
}
|
|
597
|
+
})
|
|
598
|
+
}
|
|
599
|
+
|
|
504
600
|
// --- Pagination: demonstrates page navigation ---
|
|
505
601
|
|
|
506
|
-
|
|
507
|
-
const data = generateUsers(35)
|
|
602
|
+
const paginationData = generateUsers(35)
|
|
508
603
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
604
|
+
const paginationCols = [
|
|
605
|
+
createColumnHelper<User>().accessor('name', {header: 'Name'}),
|
|
606
|
+
createColumnHelper<User>().accessor('role', {header: 'Role'}),
|
|
607
|
+
createColumnHelper<User>().accessor('email', {header: 'Email'}),
|
|
608
|
+
]
|
|
514
609
|
|
|
610
|
+
function PaginationDemo() {
|
|
515
611
|
const [pagination, setPagination] = useState({pageIndex: 0, pageSize: 10})
|
|
516
612
|
|
|
517
613
|
const table = useReactTable({
|
|
518
|
-
data,
|
|
519
|
-
columns:
|
|
614
|
+
data: paginationData,
|
|
615
|
+
columns: paginationCols,
|
|
520
616
|
state: {pagination},
|
|
521
617
|
onPaginationChange: setPagination,
|
|
522
618
|
getCoreRowModel: getCoreRowModel(),
|
|
@@ -564,21 +660,21 @@ export const WithPagination: Story = {
|
|
|
564
660
|
|
|
565
661
|
// --- Filtering: demonstrates column filter inputs ---
|
|
566
662
|
|
|
567
|
-
|
|
568
|
-
const data = generateUsers(20)
|
|
663
|
+
const filteringData = generateUsers(20)
|
|
569
664
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
665
|
+
const filteringCols = [
|
|
666
|
+
createColumnHelper<User>().accessor('name', {header: 'Name', enableColumnFilter: true}),
|
|
667
|
+
createColumnHelper<User>().accessor('role', {header: 'Role', enableColumnFilter: false}),
|
|
668
|
+
createColumnHelper<User>().accessor('status', {header: 'Status', enableColumnFilter: false}),
|
|
669
|
+
createColumnHelper<User>().accessor('email', {header: 'Email', enableColumnFilter: true}),
|
|
670
|
+
]
|
|
576
671
|
|
|
672
|
+
function FilteringDemo() {
|
|
577
673
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
578
674
|
|
|
579
675
|
const table = useReactTable({
|
|
580
|
-
data,
|
|
581
|
-
columns:
|
|
676
|
+
data: filteringData,
|
|
677
|
+
columns: filteringCols,
|
|
582
678
|
state: {columnFilters},
|
|
583
679
|
onColumnFiltersChange: setColumnFilters,
|
|
584
680
|
getCoreRowModel: getCoreRowModel(),
|
|
@@ -628,44 +724,8 @@ export const WithFiltering: Story = {
|
|
|
628
724
|
},
|
|
629
725
|
}
|
|
630
726
|
|
|
631
|
-
|
|
632
727
|
// --- FullFeatured: useDataTable with pagination, sorting, and filtering ---
|
|
633
728
|
|
|
634
|
-
interface User {
|
|
635
|
-
name: string
|
|
636
|
-
role: string
|
|
637
|
-
status: string
|
|
638
|
-
email: string
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
const firstNames = [
|
|
642
|
-
'Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Hank',
|
|
643
|
-
'Iris', 'Jack', 'Karen', 'Leo', 'Mona', 'Nick', 'Olivia', 'Paul',
|
|
644
|
-
'Quinn', 'Rita', 'Sam', 'Tina', 'Uma', 'Vince', 'Wendy', 'Xander',
|
|
645
|
-
'Yara', 'Zach',
|
|
646
|
-
]
|
|
647
|
-
const lastNames = [
|
|
648
|
-
'Adams', 'Brown', 'Clark', 'Davis', 'Evans', 'Foster', 'Garcia',
|
|
649
|
-
'Hill', 'Ito', 'Jones', 'Kim', 'Lee', 'Moore', 'Nash', 'Owen',
|
|
650
|
-
'Patel', 'Quinn', 'Reed', 'Smith', 'Tran', 'Ueda', 'Voss', 'Wang',
|
|
651
|
-
'Xu', 'Yang', 'Zhang',
|
|
652
|
-
]
|
|
653
|
-
const roles = ['Admin', 'Editor', 'Viewer', 'Moderator', 'Analyst']
|
|
654
|
-
const statuses = ['Active', 'Inactive', 'Pending', 'Suspended']
|
|
655
|
-
|
|
656
|
-
function generateUsers(count: number): User[] {
|
|
657
|
-
return Array.from({length: count}, (_, i) => {
|
|
658
|
-
const first = firstNames[i % firstNames.length]
|
|
659
|
-
const last = lastNames[(i * 7) % lastNames.length]
|
|
660
|
-
return {
|
|
661
|
-
name: `${first} ${last}`,
|
|
662
|
-
role: roles[i % roles.length],
|
|
663
|
-
status: statuses[i % statuses.length],
|
|
664
|
-
email: `${first.toLowerCase()}.${last.toLowerCase()}@example.com`,
|
|
665
|
-
}
|
|
666
|
-
})
|
|
667
|
-
}
|
|
668
|
-
|
|
669
729
|
const fakeUsers = generateUsers(50)
|
|
670
730
|
|
|
671
731
|
const columnHelper = createColumnHelper<User>()
|
|
@@ -674,35 +734,45 @@ const fullFeaturedColumns = [
|
|
|
674
734
|
columnHelper.accessor('name', {
|
|
675
735
|
header: 'Name',
|
|
676
736
|
enableSorting: true,
|
|
677
|
-
enableColumnFilter:
|
|
737
|
+
enableColumnFilter: false,
|
|
678
738
|
}),
|
|
679
739
|
columnHelper.accessor('role', {
|
|
680
740
|
header: 'Role',
|
|
681
741
|
enableSorting: true,
|
|
682
|
-
enableColumnFilter:
|
|
742
|
+
enableColumnFilter: true,
|
|
683
743
|
}),
|
|
684
744
|
columnHelper.accessor('status', {
|
|
685
745
|
header: 'Status',
|
|
686
746
|
enableSorting: true,
|
|
687
|
-
enableColumnFilter:
|
|
747
|
+
enableColumnFilter: true,
|
|
688
748
|
}),
|
|
689
749
|
columnHelper.accessor('email', {
|
|
690
750
|
header: 'Email',
|
|
691
751
|
enableSorting: true,
|
|
692
|
-
enableColumnFilter:
|
|
752
|
+
enableColumnFilter: false,
|
|
693
753
|
}),
|
|
694
754
|
]
|
|
695
755
|
|
|
696
756
|
const fullFeaturedStyles = css.create({
|
|
697
757
|
wrapper: {display: 'flex', flexDirection: 'column', gap: 0},
|
|
758
|
+
filterBar: {
|
|
759
|
+
display: 'flex',
|
|
760
|
+
gap: spacing.md,
|
|
761
|
+
paddingBottom: spacing.md,
|
|
762
|
+
},
|
|
698
763
|
headerContent: {
|
|
699
764
|
display: 'flex',
|
|
700
|
-
flexDirection: '
|
|
765
|
+
flexDirection: 'row',
|
|
766
|
+
alignItems: 'center',
|
|
767
|
+
justifyContent: 'space-between',
|
|
701
768
|
cursor: 'pointer',
|
|
702
769
|
userSelect: 'none',
|
|
703
770
|
},
|
|
704
771
|
})
|
|
705
772
|
|
|
773
|
+
const uniqueRoles = [...new Set(fakeUsers.map((u) => u.role))].sort()
|
|
774
|
+
const uniqueStatuses = [...new Set(fakeUsers.map((u) => u.status))].sort()
|
|
775
|
+
|
|
706
776
|
function FullFeaturedDemo() {
|
|
707
777
|
const table = useDataTable(fakeUsers, fullFeaturedColumns, {
|
|
708
778
|
pagination: {pageSize: 10},
|
|
@@ -712,6 +782,48 @@ function FullFeaturedDemo() {
|
|
|
712
782
|
|
|
713
783
|
return (
|
|
714
784
|
<html.div style={fullFeaturedStyles.wrapper}>
|
|
785
|
+
<html.div style={fullFeaturedStyles.filterBar}>
|
|
786
|
+
<Combobox.Root
|
|
787
|
+
value={(table.getColumn('role')?.getFilterValue() as string) ?? ''}
|
|
788
|
+
onValueChange={(v) => table.getColumn('role')?.setFilterValue(v || undefined)}
|
|
789
|
+
onInputChange={(v) => table.getColumn('role')?.setFilterValue(v || undefined)}
|
|
790
|
+
>
|
|
791
|
+
<Combobox.Input placeholder="Filter role...">
|
|
792
|
+
<Combobox.Trigger />
|
|
793
|
+
</Combobox.Input>
|
|
794
|
+
<Combobox.Popup>
|
|
795
|
+
<Combobox.Item value="">
|
|
796
|
+
<Combobox.ItemText>All Roles</Combobox.ItemText>
|
|
797
|
+
</Combobox.Item>
|
|
798
|
+
{uniqueRoles.map((role) => (
|
|
799
|
+
<Combobox.Item key={role} value={role}>
|
|
800
|
+
<Combobox.ItemText>{role}</Combobox.ItemText>
|
|
801
|
+
</Combobox.Item>
|
|
802
|
+
))}
|
|
803
|
+
<Combobox.Empty>No roles found</Combobox.Empty>
|
|
804
|
+
</Combobox.Popup>
|
|
805
|
+
</Combobox.Root>
|
|
806
|
+
<Combobox.Root
|
|
807
|
+
value={(table.getColumn('status')?.getFilterValue() as string) ?? ''}
|
|
808
|
+
onValueChange={(v) => table.getColumn('status')?.setFilterValue(v || undefined)}
|
|
809
|
+
onInputChange={(v) => table.getColumn('status')?.setFilterValue(v || undefined)}
|
|
810
|
+
>
|
|
811
|
+
<Combobox.Input placeholder="Filter status...">
|
|
812
|
+
<Combobox.Trigger />
|
|
813
|
+
</Combobox.Input>
|
|
814
|
+
<Combobox.Popup>
|
|
815
|
+
<Combobox.Item value="">
|
|
816
|
+
<Combobox.ItemText>All Statuses</Combobox.ItemText>
|
|
817
|
+
</Combobox.Item>
|
|
818
|
+
{uniqueStatuses.map((status) => (
|
|
819
|
+
<Combobox.Item key={status} value={status}>
|
|
820
|
+
<Combobox.ItemText>{status}</Combobox.ItemText>
|
|
821
|
+
</Combobox.Item>
|
|
822
|
+
))}
|
|
823
|
+
<Combobox.Empty>No statuses found</Combobox.Empty>
|
|
824
|
+
</Combobox.Popup>
|
|
825
|
+
</Combobox.Root>
|
|
826
|
+
</html.div>
|
|
715
827
|
<Table.Root>
|
|
716
828
|
<Table.Header>
|
|
717
829
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
@@ -725,12 +837,6 @@ function FullFeaturedDemo() {
|
|
|
725
837
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
726
838
|
<Table.SortIndicator column={header.column} />
|
|
727
839
|
</html.div>
|
|
728
|
-
{(header.column.id === 'name' || header.column.id === 'email') && (
|
|
729
|
-
<Table.ColumnFilter
|
|
730
|
-
column={header.column}
|
|
731
|
-
placeholder={`Filter ${header.column.id}...`}
|
|
732
|
-
/>
|
|
733
|
-
)}
|
|
734
840
|
</Table.HeaderCell>
|
|
735
841
|
))}
|
|
736
842
|
</Table.Row>
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ReactNode,
|
|
3
|
+
type MutableRefObject,
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useRef,
|
|
7
|
+
Children,
|
|
8
|
+
isValidElement,
|
|
9
|
+
} from 'react'
|
|
2
10
|
import {html} from 'react-strict-dom'
|
|
3
11
|
import {styles} from './styles.css'
|
|
4
12
|
import {Pagination} from './Pagination'
|
|
@@ -81,10 +89,7 @@ function Root({children, variant = 'default', size = 'md'}: RootProps) {
|
|
|
81
89
|
<TableContext.Provider value={{variant, size, inferredTemplateRef}}>
|
|
82
90
|
<html.div
|
|
83
91
|
role="table"
|
|
84
|
-
style={[
|
|
85
|
-
styles.root,
|
|
86
|
-
template ? styles.gridColumns(template) : undefined,
|
|
87
|
-
]}
|
|
92
|
+
style={[styles.root, template ? styles.gridColumns(template) : undefined]}
|
|
88
93
|
>
|
|
89
94
|
{children}
|
|
90
95
|
</html.div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {useState} from 'react'
|
|
1
|
+
import {useCallback, useState} from 'react'
|
|
2
2
|
import {
|
|
3
3
|
useReactTable,
|
|
4
4
|
getCoreRowModel,
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
type ColumnFiltersState,
|
|
11
11
|
type PaginationState,
|
|
12
12
|
type RowSelectionState,
|
|
13
|
-
type TableOptions,
|
|
14
13
|
type Table,
|
|
15
14
|
} from '@tanstack/react-table'
|
|
16
15
|
|
|
@@ -47,67 +46,54 @@ export function useDataTable<TData>(
|
|
|
47
46
|
options: UseDataTableOptions<TData> = {},
|
|
48
47
|
): Table<TData> {
|
|
49
48
|
const {
|
|
50
|
-
pagination: paginationInit,
|
|
51
49
|
enableSorting = false,
|
|
52
50
|
enableFiltering = false,
|
|
53
51
|
enableRowSelection = false,
|
|
54
52
|
onRowSelectionChange: onSelectionChangeProp,
|
|
55
53
|
} = options
|
|
56
54
|
|
|
55
|
+
const enablePagination = options.pagination != null
|
|
56
|
+
|
|
57
57
|
const [sorting, setSorting] = useState<SortingState>([])
|
|
58
58
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
59
59
|
const [pagination, setPagination] = useState<PaginationState>({
|
|
60
|
-
pageIndex:
|
|
61
|
-
pageSize:
|
|
60
|
+
pageIndex: options.pagination?.pageIndex ?? 0,
|
|
61
|
+
pageSize: options.pagination?.pageSize ?? 10,
|
|
62
62
|
})
|
|
63
63
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
|
64
64
|
|
|
65
|
-
const handleRowSelectionChange = (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
const handleRowSelectionChange = useCallback(
|
|
66
|
+
(updater: any) => {
|
|
67
|
+
setRowSelection((prev: RowSelectionState) => {
|
|
68
|
+
const next = typeof updater === 'function' ? updater(prev) : updater
|
|
69
|
+
onSelectionChangeProp?.(next)
|
|
70
|
+
return next
|
|
71
|
+
})
|
|
72
|
+
},
|
|
73
|
+
[onSelectionChangeProp],
|
|
74
|
+
)
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
// Follow TanStack's recommended pattern: always pass all row models
|
|
77
|
+
// unconditionally and let the library handle unused ones. Conditional
|
|
78
|
+
// spreads create unstable option shapes that can trigger infinite loops.
|
|
79
|
+
return useReactTable({
|
|
74
80
|
data,
|
|
75
81
|
columns,
|
|
82
|
+
state: {sorting, columnFilters, pagination, rowSelection},
|
|
76
83
|
getCoreRowModel: getCoreRowModel(),
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Pagination
|
|
92
|
-
...(paginationInit && {
|
|
93
|
-
getPaginationRowModel: getPaginationRowModel(),
|
|
94
|
-
onPaginationChange: setPagination,
|
|
84
|
+
getSortedRowModel: getSortedRowModel(),
|
|
85
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
86
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
87
|
+
onSortingChange: setSorting,
|
|
88
|
+
onColumnFiltersChange: setColumnFilters,
|
|
89
|
+
onPaginationChange: setPagination,
|
|
90
|
+
enableSorting,
|
|
91
|
+
enableRowSelection:
|
|
92
|
+
typeof enableRowSelection === 'function' ? enableRowSelection : !!enableRowSelection,
|
|
93
|
+
onRowSelectionChange: handleRowSelectionChange,
|
|
94
|
+
// When pagination is disabled, set a very large page size to show all rows
|
|
95
|
+
...(!enablePagination && {
|
|
96
|
+
autoResetPageIndex: false,
|
|
95
97
|
}),
|
|
96
|
-
|
|
97
|
-
// Row selection
|
|
98
|
-
...(enableRowSelection && {
|
|
99
|
-
enableRowSelection: typeof enableRowSelection === 'function' ? enableRowSelection : true,
|
|
100
|
-
onRowSelectionChange: handleRowSelectionChange,
|
|
101
|
-
}),
|
|
102
|
-
|
|
103
|
-
// Merge all state
|
|
104
|
-
state: {
|
|
105
|
-
...(enableSorting && {sorting}),
|
|
106
|
-
...(enableFiltering && {columnFilters}),
|
|
107
|
-
...(paginationInit && {pagination}),
|
|
108
|
-
...(enableRowSelection && {rowSelection}),
|
|
109
|
-
},
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return useReactTable(tableOptions)
|
|
98
|
+
})
|
|
113
99
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type {ComponentMeta} from '../component-meta'
|
|
2
|
+
|
|
3
|
+
export const meta: ComponentMeta = {
|
|
4
|
+
description:
|
|
5
|
+
'Interactive tag/chip with optional remove button. Works standalone or inside TagGroup for collection management. Visually similar to Badge but supports interaction.',
|
|
6
|
+
whenToUse: [
|
|
7
|
+
'Removable items in a list (emails, categories)',
|
|
8
|
+
'Inside TagGroup for editable tag collections',
|
|
9
|
+
'Interactive labels that can be dismissed',
|
|
10
|
+
],
|
|
11
|
+
whenNotToUse: [
|
|
12
|
+
'Static status indicators — use Badge',
|
|
13
|
+
'Toggleable selection — use Toggle or ToggleGroup',
|
|
14
|
+
],
|
|
15
|
+
relatedTo: [
|
|
16
|
+
{component: 'Badge', relationship: 'Static display variant'},
|
|
17
|
+
{component: 'TagGroup', relationship: 'Wrap in TagGroup.Root for collection management'},
|
|
18
|
+
],
|
|
19
|
+
example: `<Tag removable onRemove={() => console.log('removed')}>
|
|
20
|
+
user@example.com
|
|
21
|
+
</Tag>`,
|
|
22
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react'
|
|
2
|
+
import {expect, fn} from 'storybook/test'
|
|
3
|
+
import {css, html} from 'react-strict-dom'
|
|
4
|
+
import {Tag} from './Tag'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Tag> = {
|
|
7
|
+
title: 'Components/Tag',
|
|
8
|
+
component: Tag,
|
|
9
|
+
argTypes: {
|
|
10
|
+
variant: {
|
|
11
|
+
control: 'select',
|
|
12
|
+
options: ['default', 'success', 'warning', 'error', 'info'],
|
|
13
|
+
},
|
|
14
|
+
size: {
|
|
15
|
+
control: 'select',
|
|
16
|
+
options: ['sm', 'md'],
|
|
17
|
+
},
|
|
18
|
+
removable: {control: 'boolean'},
|
|
19
|
+
disabled: {control: 'boolean'},
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default meta
|
|
24
|
+
type Story = StoryObj<typeof Tag>
|
|
25
|
+
|
|
26
|
+
export const Default: Story = {
|
|
27
|
+
args: {children: 'Tag'},
|
|
28
|
+
play: async ({canvas}) => {
|
|
29
|
+
await expect(canvas.getByText('Tag')).toBeInTheDocument()
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const Removable: Story = {
|
|
34
|
+
args: {children: 'user@example.com', removable: true, onRemove: fn()},
|
|
35
|
+
play: async ({canvas}) => {
|
|
36
|
+
await expect(canvas.getByText('user@example.com')).toBeInTheDocument()
|
|
37
|
+
await expect(canvas.getByRole('button', {name: /remove/i})).toBeInTheDocument()
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Disabled: Story = {
|
|
42
|
+
args: {children: 'Disabled', removable: true, disabled: true},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const Small: Story = {
|
|
46
|
+
args: {children: 'Small', size: 'sm', removable: true, onRemove: fn()},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const layoutStyles = css.create({
|
|
50
|
+
row: {display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap'},
|
|
51
|
+
stack: {display: 'flex', flexDirection: 'column', gap: 16},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
export const AllVariants: Story = {
|
|
55
|
+
render: () => (
|
|
56
|
+
<html.div style={layoutStyles.stack}>
|
|
57
|
+
<html.div style={layoutStyles.row}>
|
|
58
|
+
<Tag>Default</Tag>
|
|
59
|
+
<Tag variant="success">Success</Tag>
|
|
60
|
+
<Tag variant="warning">Warning</Tag>
|
|
61
|
+
<Tag variant="error">Error</Tag>
|
|
62
|
+
<Tag variant="info">Info</Tag>
|
|
63
|
+
</html.div>
|
|
64
|
+
<html.div style={layoutStyles.row}>
|
|
65
|
+
<Tag removable onRemove={() => {}}>
|
|
66
|
+
Default
|
|
67
|
+
</Tag>
|
|
68
|
+
<Tag variant="success" removable onRemove={() => {}}>
|
|
69
|
+
Success
|
|
70
|
+
</Tag>
|
|
71
|
+
<Tag variant="warning" removable onRemove={() => {}}>
|
|
72
|
+
Warning
|
|
73
|
+
</Tag>
|
|
74
|
+
<Tag variant="error" removable onRemove={() => {}}>
|
|
75
|
+
Error
|
|
76
|
+
</Tag>
|
|
77
|
+
<Tag variant="info" removable onRemove={() => {}}>
|
|
78
|
+
Info
|
|
79
|
+
</Tag>
|
|
80
|
+
</html.div>
|
|
81
|
+
<html.div style={layoutStyles.row}>
|
|
82
|
+
<Tag size="sm">Small</Tag>
|
|
83
|
+
<Tag size="sm" removable onRemove={() => {}}>
|
|
84
|
+
Removable Small
|
|
85
|
+
</Tag>
|
|
86
|
+
</html.div>
|
|
87
|
+
</html.div>
|
|
88
|
+
),
|
|
89
|
+
play: async ({canvas}) => {
|
|
90
|
+
await expect(canvas.getAllByText('Default').length).toBe(2)
|
|
91
|
+
},
|
|
92
|
+
}
|