@drivenets/design-system 0.3.0 → 0.4.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 (149) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/components/ds-alert-banner/ds-alert-banner.d.cts +5 -5
  4. package/dist/components/ds-alert-banner/ds-alert-banner.d.cts.map +1 -1
  5. package/dist/components/ds-alert-banner/ds-alert-banner.d.ts +5 -5
  6. package/dist/components/ds-alert-banner/ds-alert-banner.d.ts.map +1 -1
  7. package/dist/components/ds-alert-banner/ds-alert-banner.types.d.cts.map +1 -1
  8. package/dist/components/ds-autocomplete/ds-autocomplete.cjs +218 -0
  9. package/dist/components/ds-autocomplete/ds-autocomplete.d.cts +22 -0
  10. package/dist/components/ds-autocomplete/ds-autocomplete.d.cts.map +1 -0
  11. package/dist/components/ds-autocomplete/ds-autocomplete.d.ts +22 -0
  12. package/dist/components/ds-autocomplete/ds-autocomplete.d.ts.map +1 -0
  13. package/dist/components/ds-autocomplete/ds-autocomplete.js +217 -0
  14. package/dist/components/ds-autocomplete/ds-autocomplete.js.map +1 -0
  15. package/dist/components/ds-autocomplete/ds-autocomplete.module.cjs +27 -0
  16. package/dist/components/ds-autocomplete/ds-autocomplete.module.js +27 -0
  17. package/dist/components/ds-autocomplete/ds-autocomplete.module.js.map +1 -0
  18. package/dist/components/ds-autocomplete/ds-autocomplete.types.d.cts +79 -0
  19. package/dist/components/ds-autocomplete/ds-autocomplete.types.d.cts.map +1 -0
  20. package/dist/components/ds-autocomplete/ds-autocomplete.types.d.ts +80 -0
  21. package/dist/components/ds-autocomplete/ds-autocomplete.types.d.ts.map +1 -0
  22. package/dist/components/ds-autocomplete/index.cjs +1 -0
  23. package/dist/components/ds-autocomplete/index.d.ts +2 -0
  24. package/dist/components/ds-autocomplete/index.js +1 -0
  25. package/dist/components/ds-avatar/ds-avatar.module.js.map +1 -1
  26. package/dist/components/ds-avatar-group/ds-avatar-group.module.js.map +1 -1
  27. package/dist/components/ds-button/ds-button.d.cts +2 -2
  28. package/dist/components/ds-button/ds-button.d.cts.map +1 -1
  29. package/dist/components/ds-confirmation/ds-confirmation.cjs +20 -221
  30. package/dist/components/ds-confirmation/ds-confirmation.d.cts +22 -27
  31. package/dist/components/ds-confirmation/ds-confirmation.d.cts.map +1 -1
  32. package/dist/components/ds-confirmation/ds-confirmation.d.ts +22 -27
  33. package/dist/components/ds-confirmation/ds-confirmation.d.ts.map +1 -1
  34. package/dist/components/ds-confirmation/ds-confirmation.js +21 -221
  35. package/dist/components/ds-confirmation/ds-confirmation.js.map +1 -1
  36. package/dist/components/ds-confirmation/ds-confirmation.types.d.cts +2 -1
  37. package/dist/components/ds-confirmation/ds-confirmation.types.d.cts.map +1 -1
  38. package/dist/components/ds-confirmation/ds-confirmation.types.d.ts +2 -1
  39. package/dist/components/ds-confirmation/ds-confirmation.types.d.ts.map +1 -1
  40. package/dist/components/ds-date-input/ds-date-input.cjs +1 -1
  41. package/dist/components/ds-date-input/ds-date-input.d.cts +2 -2
  42. package/dist/components/ds-date-input/ds-date-input.d.ts +2 -2
  43. package/dist/components/ds-date-input/ds-date-input.js +1 -1
  44. package/dist/components/ds-drawer/ds-drawer.cjs +1 -1
  45. package/dist/components/ds-drawer/ds-drawer.d.cts +9 -9
  46. package/dist/components/ds-drawer/ds-drawer.d.cts.map +1 -1
  47. package/dist/components/ds-drawer/ds-drawer.d.ts +9 -9
  48. package/dist/components/ds-drawer/ds-drawer.js +1 -1
  49. package/dist/components/ds-dropdown-menu/ds-dropdown-menu.cjs +1 -1
  50. package/dist/components/ds-dropdown-menu/ds-dropdown-menu.js +1 -1
  51. package/dist/components/ds-dropdown-menu/ds-dropdown-menu.module.cjs +20 -20
  52. package/dist/components/ds-dropdown-menu/ds-dropdown-menu.module.js +20 -20
  53. package/dist/components/ds-dropdown-menu/ds-dropdown-menu.module.js.map +1 -1
  54. package/dist/components/ds-expandable-text-input/ds-expandable-text-input.d.cts +2 -2
  55. package/dist/components/ds-expandable-text-input/ds-expandable-text-input.d.ts +2 -2
  56. package/dist/components/ds-form-control/ds-form-control.d.cts +8 -8
  57. package/dist/components/ds-form-control/ds-form-control.d.ts +8 -8
  58. package/dist/components/ds-loader/ds-loader.d.cts +2 -2
  59. package/dist/components/ds-loader/ds-loader.d.ts +2 -2
  60. package/dist/components/ds-modal/ds-modal.cjs +47 -48
  61. package/dist/components/ds-modal/ds-modal.d.cts +11 -10
  62. package/dist/components/ds-modal/ds-modal.d.cts.map +1 -1
  63. package/dist/components/ds-modal/ds-modal.d.ts +11 -10
  64. package/dist/components/ds-modal/ds-modal.d.ts.map +1 -1
  65. package/dist/components/ds-modal/ds-modal.js +47 -48
  66. package/dist/components/ds-modal/ds-modal.js.map +1 -1
  67. package/dist/components/ds-modal/ds-modal.module.cjs +24 -24
  68. package/dist/components/ds-modal/ds-modal.module.js +24 -24
  69. package/dist/components/ds-modal/ds-modal.module.js.map +1 -1
  70. package/dist/components/ds-modal/ds-modal.types.d.cts +10 -5
  71. package/dist/components/ds-modal/ds-modal.types.d.cts.map +1 -1
  72. package/dist/components/ds-modal/ds-modal.types.d.ts +10 -5
  73. package/dist/components/ds-modal/ds-modal.types.d.ts.map +1 -1
  74. package/dist/components/ds-panel/ds-panel.d.cts +2 -2
  75. package/dist/components/ds-panel/ds-panel.d.ts +2 -2
  76. package/dist/components/ds-select/ds-select.cjs +1 -1
  77. package/dist/components/ds-select/ds-select.d.cts +2 -2
  78. package/dist/components/ds-select/ds-select.js +1 -1
  79. package/dist/components/ds-skeleton/ds-skeleton-circle.d.cts +2 -2
  80. package/dist/components/ds-skeleton/ds-skeleton-circle.d.ts +2 -2
  81. package/dist/components/ds-skeleton/ds-skeleton-rect.d.cts +2 -2
  82. package/dist/components/ds-skeleton/ds-skeleton-rect.d.ts +2 -2
  83. package/dist/components/ds-skeleton/ds-skeleton-rect.d.ts.map +1 -1
  84. package/dist/components/ds-skeleton/ds-skeleton-text.d.cts +2 -2
  85. package/dist/components/ds-skeleton/ds-skeleton-text.d.ts +2 -2
  86. package/dist/components/ds-skeleton/ds-skeleton.d.cts +4 -4
  87. package/dist/components/ds-skeleton/ds-skeleton.d.ts +4 -4
  88. package/dist/components/ds-smart-tabs/ds-smart-tabs.d.cts +2 -2
  89. package/dist/components/ds-smart-tabs/ds-smart-tabs.d.ts +2 -2
  90. package/dist/components/ds-smart-tabs/ds-smart-tabs.d.ts.map +1 -1
  91. package/dist/components/ds-stepper/components/ds-step-content.d.cts +2 -2
  92. package/dist/components/ds-stepper/components/ds-step-content.d.ts +2 -2
  93. package/dist/components/ds-stepper/components/ds-step-next-button.d.cts +2 -2
  94. package/dist/components/ds-stepper/components/ds-step-next-button.d.ts +2 -2
  95. package/dist/components/ds-stepper/components/ds-step.d.cts +2 -2
  96. package/dist/components/ds-stepper/components/ds-step.d.ts +2 -2
  97. package/dist/components/ds-stepper/ds-stepper.d.cts +2 -2
  98. package/dist/components/ds-stepper/ds-stepper.d.ts +2 -2
  99. package/dist/components/ds-table/ds-table.d.ts +2 -2
  100. package/dist/components/ds-table/filters/hooks/index.d.ts +1 -1
  101. package/dist/components/ds-table/filters/hooks/use-table-filters.cjs +180 -185
  102. package/dist/components/ds-table/filters/hooks/use-table-filters.d.cts +58 -25
  103. package/dist/components/ds-table/filters/hooks/use-table-filters.d.cts.map +1 -1
  104. package/dist/components/ds-table/filters/hooks/use-table-filters.d.ts +58 -25
  105. package/dist/components/ds-table/filters/hooks/use-table-filters.d.ts.map +1 -1
  106. package/dist/components/ds-table/filters/hooks/use-table-filters.js +180 -185
  107. package/dist/components/ds-table/filters/hooks/use-table-filters.js.map +1 -1
  108. package/dist/components/ds-table/filters/index.d.ts +1 -1
  109. package/dist/components/ds-table/index.d.ts +1 -1
  110. package/dist/components/ds-tabs/components/ds-tabs-content/ds-tabs-content.module.cjs +2 -2
  111. package/dist/components/ds-tabs/components/ds-tabs-content/ds-tabs-content.module.js +2 -2
  112. package/dist/components/ds-tabs/components/ds-tabs-content/ds-tabs-content.module.js.map +1 -1
  113. package/dist/components/ds-tabs/ds-tabs.d.cts +5 -5
  114. package/dist/components/ds-tabs/ds-tabs.d.ts +5 -5
  115. package/dist/components/ds-tag/ds-tag.d.cts +2 -2
  116. package/dist/components/ds-tag/ds-tag.d.ts +2 -2
  117. package/dist/components/ds-tag-filter/ds-tag-filter.d.cts +2 -2
  118. package/dist/components/ds-tag-filter/ds-tag-filter.d.ts +2 -2
  119. package/dist/components/ds-toggle/ds-toggle.d.cts +2 -2
  120. package/dist/components/ds-toggle/ds-toggle.d.ts +2 -2
  121. package/dist/components/ds-vertical-tabs/ds-vertical-tabs.d.cts +5 -5
  122. package/dist/components/ds-vertical-tabs/ds-vertical-tabs.d.ts +5 -5
  123. package/dist/index.cjs +4 -1
  124. package/dist/index.d.cts +4 -2
  125. package/dist/index.d.ts +5 -2
  126. package/dist/index.js +4 -2
  127. package/dist/index.min.css +1 -1
  128. package/package.json +1 -1
  129. package/src/components/ds-autocomplete/ds-autocomplete.module.scss +257 -0
  130. package/src/components/ds-autocomplete/ds-autocomplete.tsx +130 -0
  131. package/src/components/ds-autocomplete/ds-autocomplete.types.tsx +76 -0
  132. package/src/components/ds-autocomplete/index.ts +2 -0
  133. package/src/components/ds-avatar/ds-avatar.module.scss +1 -1
  134. package/src/components/ds-avatar-group/ds-avatar-group.module.scss +1 -1
  135. package/src/components/ds-confirmation/ds-confirmation.tsx +11 -115
  136. package/src/components/ds-confirmation/ds-confirmation.types.tsx +2 -1
  137. package/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss +0 -1
  138. package/src/components/ds-modal/ds-modal.module.scss +123 -60
  139. package/src/components/ds-modal/ds-modal.tsx +5 -2
  140. package/src/components/ds-modal/ds-modal.types.tsx +14 -5
  141. package/src/components/ds-table/filters/hooks/index.ts +1 -1
  142. package/src/components/ds-table/filters/hooks/use-table-filters.ts +117 -116
  143. package/src/components/ds-tabs/components/ds-tabs-content/ds-tabs-content.module.scss +4 -0
  144. package/src/index.ts +1 -0
  145. package/src/styles/shared/_modal.scss +0 -123
  146. package/dist/components/ds-confirmation/ds-confirmation.module.cjs +0 -21
  147. package/dist/components/ds-confirmation/ds-confirmation.module.js +0 -21
  148. package/dist/components/ds-confirmation/ds-confirmation.module.js.map +0 -1
  149. package/src/components/ds-confirmation/ds-confirmation.module.scss +0 -58
@@ -1,20 +1,91 @@
1
- @use '../../styles/shared/modal' as shared;
2
-
3
- $pad-x: 24px;
4
- $pad-y: 16px;
1
+ $pad-x: var(--spacing-lg);
2
+ $pad-y: var(--spacing-standard);
5
3
  $max-width: 90vw;
6
- $max-height: 90vh;
4
+ $border-size: 1px;
5
+ $border: $border-size solid var(--color-border-secondary);
7
6
 
8
7
  .overlay {
9
- @include shared.overlay-base;
8
+ position: fixed;
9
+ inset: 0;
10
+ background: var(--color-background-brand);
11
+ opacity: 0.4;
12
+ z-index: 1199;
13
+
14
+ &[data-state='open'] {
15
+ animation: fade-in-overlay 0.2s ease-in-out both;
16
+ }
17
+
18
+ &[data-state='closed'] {
19
+ animation: fade-out-overlay 0.2s 0.1s ease-in-out both;
20
+ }
21
+ }
22
+
23
+ @keyframes fade-in-overlay {
24
+ 0% {
25
+ opacity: 0;
26
+ }
27
+
28
+ 100% {
29
+ opacity: 0.4;
30
+ }
31
+ }
32
+
33
+ @keyframes fade-out-overlay {
34
+ 0% {
35
+ opacity: 0.4;
36
+ }
37
+
38
+ 100% {
39
+ opacity: 0;
40
+ }
10
41
  }
11
42
 
12
43
  .modal {
13
- @include shared.modal-base;
44
+ position: fixed;
45
+ inset: 50% auto auto 50%;
46
+ background: var(--color-background);
47
+ border-radius: 4px;
48
+ box-shadow: 0 5px 15px 0 rgba(0, 0, 0, 0.15);
49
+ z-index: 1200;
50
+ overflow: hidden;
51
+ inline-size: min(var(--col-span-6), $max-width);
52
+
53
+ &[data-state='open'] {
54
+ animation: scale-in-modal 0.2s 0.1s ease-in-out both;
55
+ }
56
+
57
+ &[data-state='closed'] {
58
+ animation: scale-out-modal 0.2s ease-in-out both;
59
+ }
60
+ }
61
+
62
+ @keyframes scale-in-modal {
63
+ 0% {
64
+ opacity: 0;
65
+ transform: translate(-50%, -50%) scale(0.9);
66
+ }
67
+
68
+ 100% {
69
+ opacity: 1;
70
+ transform: translate(-50%, -50%) scale(1);
71
+ }
72
+ }
73
+
74
+ @keyframes scale-out-modal {
75
+ 0% {
76
+ opacity: 1;
77
+ transform: translate(-50%, -50%) scale(1);
78
+ }
79
+
80
+ 100% {
81
+ opacity: 0;
82
+ transform: translate(-50%, -50%) scale(0.9);
83
+ }
14
84
  }
15
85
 
16
86
  .content {
17
- @include shared.content-base;
87
+ display: flex;
88
+ flex-direction: column;
18
89
  height: 100%;
19
90
  }
20
91
 
@@ -25,76 +96,68 @@ $max-height: 90vh;
25
96
  }
26
97
 
27
98
  .header {
28
- @include shared.header-base;
99
+ display: flex;
100
+ align-items: center;
101
+ gap: var(--spacing-xs);
102
+ position: relative;
103
+ background: inherit;
104
+ flex-shrink: 0;
105
+
106
+ &::after {
107
+ content: '';
108
+ position: absolute;
109
+ bottom: 0;
110
+ left: $pad-x;
111
+ right: $pad-x;
112
+ height: $border-size;
113
+ background: var(--color-border-secondary);
114
+ }
29
115
  }
30
116
 
31
117
  .footer {
32
- @include shared.footer-base;
33
118
  display: flex;
119
+ background: inherit;
120
+ flex-shrink: 0;
34
121
  }
35
122
 
36
123
  .title {
37
- @include shared.title-base;
124
+ color: var(--color-font-main);
125
+ margin: 0;
126
+ flex: 1;
38
127
  }
39
128
 
40
129
  .body {
41
- @include shared.body-base;
130
+ flex: 1;
131
+ overflow-y: auto;
132
+ overflow-x: hidden;
133
+ min-height: 0;
42
134
  }
43
135
 
44
136
  .actions {
45
- @include shared.actions-base;
137
+ display: flex;
138
+ gap: var(--spacing-xs);
139
+ justify-content: flex-end;
46
140
  margin-left: auto;
47
141
  }
48
142
 
49
- // width classes derived from DS grid
50
- .modal {
51
- inline-size: min(var(--col-span-6), $max-width);
52
- }
53
-
54
- .modal.cols-1 {
55
- inline-size: min(var(--col-span-1), $max-width);
56
- }
57
-
58
- .modal.cols-2 {
59
- inline-size: min(var(--col-span-2), $max-width);
60
- }
61
-
62
- .modal.cols-3 {
63
- inline-size: min(var(--col-span-3), $max-width);
64
- }
65
-
66
- .modal.cols-4 {
67
- inline-size: min(var(--col-span-4), $max-width);
68
- }
69
-
70
- .modal.cols-5 {
71
- inline-size: min(var(--col-span-5), $max-width);
72
- }
73
-
74
- .modal.cols-6 {
75
- inline-size: min(var(--col-span-6), $max-width);
76
- }
77
-
78
- .modal.cols-7 {
79
- inline-size: min(var(--col-span-7), $max-width);
80
- }
143
+ // Dividers mode: full-width borders on header and footer
144
+ .content[data-dividers] {
145
+ .header {
146
+ border-bottom: $border;
81
147
 
82
- .modal.cols-8 {
83
- inline-size: min(var(--col-span-8), $max-width);
84
- }
85
-
86
- .modal.cols-9 {
87
- inline-size: min(var(--col-span-9), $max-width);
88
- }
89
-
90
- .modal.cols-10 {
91
- inline-size: min(var(--col-span-10), $max-width);
92
- }
148
+ &::after {
149
+ display: none;
150
+ }
151
+ }
93
152
 
94
- .modal.cols-11 {
95
- inline-size: min(var(--col-span-11), $max-width);
153
+ .footer {
154
+ border-top: $border;
155
+ }
96
156
  }
97
157
 
98
- .modal.cols-12 {
99
- inline-size: min(var(--col-span-12), $max-width);
158
+ // Width classes derived from DS grid
159
+ @for $i from 1 through 12 {
160
+ .modal.cols-#{$i} {
161
+ inline-size: min(var(--col-span-#{$i}), $max-width);
162
+ }
100
163
  }
@@ -14,14 +14,15 @@ import { DsTypography } from '../ds-typography';
14
14
  */
15
15
  const DsModal = ({
16
16
  open,
17
- onOpenChange,
18
17
  columns = 6,
18
+ dividers = false,
19
19
  style,
20
20
  className,
21
21
  modal = true,
22
22
  closeOnEscape,
23
23
  closeOnInteractOutside = false,
24
24
  children,
25
+ onOpenChange,
25
26
  }: DsModalProps) => {
26
27
  const handleOpenChange = (details: { open: boolean }) => {
27
28
  onOpenChange(details.open);
@@ -48,7 +49,9 @@ const DsModal = ({
48
49
  styles[`cols-${columns}`],
49
50
  )}
50
51
  >
51
- <div className={classNames(styles.content)}>{children}</div>
52
+ <div className={styles.content} data-dividers={dividers || undefined}>
53
+ {children}
54
+ </div>
52
55
  </Dialog.Content>
53
56
  </Dialog.Positioner>
54
57
  </Portal>
@@ -17,21 +17,24 @@ export interface DsModalProps extends Pick<
17
17
  * Whether the modal is open
18
18
  */
19
19
  open: boolean;
20
- /**
21
- * Callback when modal open state changes
22
- * @param open - whether the modal is open
23
- */
24
- onOpenChange: (open: boolean) => void;
25
20
 
26
21
  /**
27
22
  * Number of grid columns for modal width
28
23
  * @default 6
29
24
  */
30
25
  columns?: DsModalColumns;
26
+
27
+ /**
28
+ * Show full-width dividers between header, body, and footer sections
29
+ * @default false
30
+ */
31
+ dividers?: boolean;
32
+
31
33
  /**
32
34
  * Optional inline styles to apply to the component
33
35
  */
34
36
  style?: CSSProperties;
37
+
35
38
  /**
36
39
  * Additional CSS class name
37
40
  */
@@ -41,4 +44,10 @@ export interface DsModalProps extends Pick<
41
44
  * Modal body content
42
45
  */
43
46
  children: ReactNode;
47
+
48
+ /**
49
+ * Callback when modal open state changes
50
+ * @param open - whether the modal is open
51
+ */
52
+ onOpenChange: (open: boolean) => void;
44
53
  }
@@ -1,2 +1,2 @@
1
1
  export { useTableFilters } from './use-table-filters';
2
- export type { UseTableFiltersResult } from './use-table-filters';
2
+ export type { UseTableFiltersOptions, UseTableFiltersResult } from './use-table-filters';
@@ -9,24 +9,49 @@ import type {
9
9
  FilterState,
10
10
  } from '../types/filter-adapter.types';
11
11
 
12
+ export interface UseTableFiltersOptions<TData, TValue, TCellValue> {
13
+ /**
14
+ * Array of filter adapters that define filter behavior
15
+ */
16
+ filterAdapters: FilterAdapter<TData, TValue, TCellValue>[] | AnyAdapter[];
17
+
18
+ /**
19
+ * Base column definitions
20
+ */
21
+ baseColumns?: ColumnDef<TData>[];
22
+
23
+ /**
24
+ * Controlled mode: External applied filters (source of truth).
25
+ * When provided with onFiltersChange, the hook operates in controlled mode.
26
+ * Keys must match adapter IDs.
27
+ */
28
+ appliedFilters?: FilterState<TValue>;
29
+
30
+ /**
31
+ * Controlled mode: Callback when filters change.
32
+ * Called by applyFilters(), deleteChip(), clearAll().
33
+ */
34
+ onFiltersChange?: (filters: FilterState<TValue>) => void;
35
+ }
36
+
12
37
  export interface UseTableFiltersResult<TData, TValue> {
13
38
  /**
14
- * Current filter state
39
+ * Current draft filter state (uncommitted user selections)
15
40
  */
16
41
  filterState: FilterState<TValue>;
17
42
 
18
43
  /**
19
- * Column filters for TanStack Table
44
+ * Column filters (derived from applied state)
20
45
  */
21
46
  columnFilters: ColumnFilterState<TValue>[];
22
47
 
23
48
  /**
24
- * Filter chips for display
49
+ * Filter chips for display (derived from applied state)
25
50
  */
26
51
  filterChips: ChipItem[];
27
52
 
28
53
  /**
29
- * Filter navigation items with counts
54
+ * Filter navigation items with active counts
30
55
  */
31
56
  filterNavItems: FilterNavItem[];
32
57
 
@@ -36,132 +61,128 @@ export interface UseTableFiltersResult<TData, TValue> {
36
61
  enhancedColumns: ColumnDef<TData>[];
37
62
 
38
63
  /**
39
- * Handlers
64
+ * Handlers for filter operations
40
65
  */
41
66
  handlers: {
42
- /**
43
- * Update a specific filter's value
44
- */
67
+ /** Update a specific filter's draft value */
45
68
  updateFilter: (filterId: string, value: TValue) => void;
46
-
47
- /**
48
- * Apply all filters (converts to TanStack format)
49
- */
69
+ /** Commit draft state to applied state */
50
70
  applyFilters: () => void;
51
-
52
- /**
53
- * Clear all filters
54
- */
71
+ /** Clear all filters */
55
72
  clearAll: () => void;
56
-
57
- /**
58
- * Delete a specific chip
59
- */
73
+ /** Delete a specific filter chip */
60
74
  deleteChip: (chip: ChipItem) => void;
61
75
  };
62
76
 
63
77
  /**
64
- * Render function for filter modal content
78
+ * Render function for filter content
65
79
  */
66
80
  renderFilterContent: (selectedFilter: FilterNavItem) => ReactNode;
67
81
  }
68
82
 
69
83
  /**
70
- * Hook to orchestrate table filtering with adapters
71
- * Manages filter state, generates chips, handles column definitions
84
+ * Hook to orchestrate table filtering with adapters.
85
+ * Manages filter state, generates chips, and handles column definitions.
86
+ *
87
+ * **Uncontrolled mode** (default): Filters are managed internally.
88
+ * **Controlled mode**: Filters are managed externally via appliedFilters/onFiltersChange.
72
89
  *
73
- * @param filterAdapters Array of filter adapters
74
- * @param baseColumns Base column definitions (optional)
75
- * @returns Complete filter management system
90
+ * @example
91
+ * // Uncontrolled mode
92
+ * const { filterChips, handlers } = useTableFilters({
93
+ * filterAdapters: adapters,
94
+ * baseColumns: columns,
95
+ * });
96
+ *
97
+ * @example
98
+ * // Controlled mode (e.g., URL-driven filtering)
99
+ * const { filterChips, handlers } = useTableFilters({
100
+ * filterAdapters: adapters,
101
+ * baseColumns: columns,
102
+ * appliedFilters: filtersFromUrl,
103
+ * onFiltersChange: (filters) => updateUrlAndRefetch(filters),
104
+ * });
76
105
  */
77
- export function useTableFilters<TData, TValue, TCellValue>(
78
- filterAdapters: FilterAdapter<TData, TValue, TCellValue>[] | AnyAdapter[],
79
- baseColumns?: ColumnDef<TData>[],
80
- ): UseTableFiltersResult<TData, TValue> {
106
+ export function useTableFilters<TData, TValue, TCellValue>({
107
+ filterAdapters,
108
+ baseColumns,
109
+ appliedFilters: externalAppliedFilters,
110
+ onFiltersChange: setExternalAppliedFilters,
111
+ }: UseTableFiltersOptions<TData, TValue, TCellValue>): UseTableFiltersResult<TData, TValue> {
81
112
  // Internal variable just to avoid assignment issues of `any`.
82
113
  const _filterAdapters = filterAdapters as FilterAdapter<TData, TValue, TCellValue>[];
83
114
 
84
- // Initialize filter state from adapters
85
- const initialState: FilterState<TValue> = {};
86
- _filterAdapters.forEach((adapter) => {
87
- initialState[adapter.id] = adapter.initialValue;
88
- });
115
+ const isControlled = externalAppliedFilters !== undefined && setExternalAppliedFilters !== undefined;
89
116
 
90
- const [filterState, setFilterState] = useState<FilterState<TValue>>(initialState);
91
- const [columnFilters, setColumnFilters] = useState<ColumnFilterState<TValue>[]>([]);
92
- const [filterChips, setFilterChips] = useState<ChipItem[]>([]);
117
+ const initialFilters = _filterAdapters.reduce<FilterState<TValue>>(
118
+ (state, adapter) => ({ ...state, [adapter.id]: adapter.initialValue }),
119
+ {},
120
+ );
93
121
 
94
- const getValueByAdapterId = (adapterId: string) => {
95
- // Should be safe to cast because it's being filled in the initialState
96
- return filterState[adapterId] as TValue;
97
- };
122
+ const [internalAppliedFilters, setInternalAppliedFilters] = useState<FilterState<TValue>>({});
123
+ const [pendingFilters, setPendingFilters] = useState<FilterState<TValue>>({});
124
+
125
+ const appliedFilters = isControlled ? externalAppliedFilters : internalAppliedFilters;
126
+ const setAppliedFilters = isControlled ? setExternalAppliedFilters : setInternalAppliedFilters;
127
+
128
+ const draftFilters = { ...initialFilters, ...appliedFilters, ...pendingFilters };
129
+
130
+ const columnFilters: ColumnFilterState<TValue>[] = Object.entries(appliedFilters)
131
+ .filter(([id]) => {
132
+ const adapter = _filterAdapters.find((a) => a.id === id);
133
+ return adapter ? adapter.getActiveFiltersCount(appliedFilters[id] as TValue) > 0 : false;
134
+ })
135
+ .map(([id, value]) => ({ id, value }));
136
+
137
+ const filterChips = _filterAdapters.flatMap((adapter) => {
138
+ const value = appliedFilters[adapter.id];
139
+ return value !== undefined ? adapter.toChips(value) : [];
140
+ });
98
141
 
99
- // Generate filter nav items with counts (updates as user changes filters in modal)
100
142
  const filterNavItems: FilterNavItem[] = _filterAdapters.map((adapter) => ({
101
143
  id: adapter.id,
102
144
  label: adapter.label,
103
- count: adapter.getActiveFiltersCount(getValueByAdapterId(adapter.id)),
145
+ count: adapter.getActiveFiltersCount(draftFilters[adapter.id] as TValue),
104
146
  }));
105
147
 
106
- // Enhance column definitions with filter functions and renderers
107
148
  const enhancedColumns: ColumnDef<TData>[] = !baseColumns
108
149
  ? []
109
150
  : baseColumns.map((col) => {
110
151
  const adapter = _filterAdapters.find((a) => a.id === col.id);
111
-
112
- if (adapter) {
113
- const cellRenderer = adapter.cellRenderer;
114
- return {
115
- ...col,
116
- filterFn: adapter.columnFilterFn,
117
- ...(cellRenderer && {
118
- cell: (info: CellContext<TData, TCellValue>) => cellRenderer(info.getValue()),
119
- }),
120
- };
152
+ if (!adapter) {
153
+ return col;
121
154
  }
122
155
 
123
- return col;
156
+ const cellRenderer = adapter.cellRenderer;
157
+ return {
158
+ ...col,
159
+ filterFn: adapter.columnFilterFn,
160
+ ...(cellRenderer && {
161
+ cell: (info: CellContext<TData, TCellValue>) => cellRenderer(info.getValue()),
162
+ }),
163
+ };
124
164
  });
125
165
 
126
- // Handlers
127
166
  const updateFilter = (filterId: string, value: TValue) => {
128
- setFilterState((prev) => ({
129
- ...prev,
130
- [filterId]: value,
131
- }));
167
+ setPendingFilters((prev) => ({ ...prev, [filterId]: value }));
132
168
  };
133
169
 
134
170
  const applyFilters = () => {
135
- const filters: ColumnFilterState<TValue>[] = [];
136
- const chips: ChipItem[] = [];
137
-
138
- _filterAdapters.forEach((adapter) => {
139
- const value = getValueByAdapterId(adapter.id);
140
-
171
+ const filtersToApply = _filterAdapters.reduce<FilterState<TValue>>((acc, adapter) => {
172
+ const value = draftFilters[adapter.id] as TValue;
141
173
  if (adapter.getActiveFiltersCount(value) > 0) {
142
- filters.push({
143
- id: adapter.id,
144
- value,
145
- });
174
+ acc[adapter.id] = value;
146
175
  }
176
+ return acc;
177
+ }, {});
147
178
 
148
- // Generate chips from current filter value
149
- const adapterChips = adapter.toChips(value);
150
- chips.push(...adapterChips);
151
- });
152
-
153
- setColumnFilters(filters);
154
- setFilterChips(chips);
179
+ setAppliedFilters(filtersToApply);
180
+ setPendingFilters({});
155
181
  };
156
182
 
157
183
  const clearAll = () => {
158
- const resetState: FilterState<TValue> = {};
159
- _filterAdapters.forEach((adapter) => {
160
- resetState[adapter.id] = adapter.reset();
161
- });
162
- setFilterState(resetState);
163
- setColumnFilters([]);
164
- setFilterChips([]);
184
+ setPendingFilters({});
185
+ setAppliedFilters({});
165
186
  };
166
187
 
167
188
  const deleteChip = (chip: ChipItem) => {
@@ -175,39 +196,19 @@ export function useTableFilters<TData, TValue, TCellValue>(
175
196
  return;
176
197
  }
177
198
 
178
- const currentValue = getValueByAdapterId(filterKey);
179
- const newValue = adapter.fromChip(chip, currentValue);
180
-
181
- // Regenerate chips to check if this was the last one
182
- const chips: ChipItem[] = [];
183
- _filterAdapters.forEach((adapter) => {
184
- const value = adapter.id === filterKey ? newValue : getValueByAdapterId(adapter.id);
185
- const adapterChips = adapter.toChips(value);
186
- chips.push(...adapterChips);
187
- });
188
-
189
- // If no chips left, clear all filters
190
- if (chips.length === 0) {
191
- clearAll();
199
+ const currentValue = appliedFilters[filterKey];
200
+ if (currentValue === undefined) {
192
201
  return;
193
202
  }
194
203
 
195
- // Otherwise, update state and filters
196
- setFilterState((prev) => ({
197
- ...prev,
198
- [filterKey]: newValue,
199
- }));
200
-
201
- // Update column filters
202
- if (adapter.getActiveFiltersCount(newValue) === 0) {
203
- setColumnFilters((prev) => prev.filter((cf) => cf.id !== filterKey));
204
- } else {
205
- setColumnFilters((prev) =>
206
- prev.map((cf) => (cf.id === filterKey ? { id: filterKey, value: newValue } : cf)),
207
- );
208
- }
204
+ const newValue = adapter.fromChip(chip, currentValue);
205
+
206
+ const newFilters =
207
+ adapter.getActiveFiltersCount(newValue) === 0
208
+ ? Object.fromEntries(Object.entries(appliedFilters).filter(([key]) => key !== filterKey))
209
+ : { ...appliedFilters, [filterKey]: newValue };
209
210
 
210
- setFilterChips(chips);
211
+ setAppliedFilters(newFilters);
211
212
  };
212
213
 
213
214
  const renderFilterContent = (selectedFilter: FilterNavItem) => {
@@ -216,14 +217,14 @@ export function useTableFilters<TData, TValue, TCellValue>(
216
217
  return null;
217
218
  }
218
219
 
219
- const value = getValueByAdapterId(adapter.id);
220
+ const value = draftFilters[adapter.id] as TValue;
220
221
  const onChange = (newValue: TValue) => updateFilter(adapter.id, newValue);
221
222
 
222
223
  return adapter.renderFilter(value, onChange);
223
224
  };
224
225
 
225
226
  return {
226
- filterState,
227
+ filterState: draftFilters,
227
228
  columnFilters,
228
229
  filterChips,
229
230
  filterNavItems,
@@ -4,6 +4,10 @@ $animation-translate: -4px;
4
4
  .tabContent {
5
5
  padding: var(--spacing-standard) 0;
6
6
  animation: fadeIn $animation-duration ease-in;
7
+
8
+ &[data-state='closed'] {
9
+ display: none;
10
+ }
7
11
  }
8
12
 
9
13
  @keyframes fadeIn {
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './components/ds-alert-banner';
2
+ export * from './components/ds-autocomplete';
2
3
  export * from './components/ds-avatar';
3
4
  export * from './components/ds-avatar-group';
4
5
  export * from './components/ds-breadcrumb';