@furystack/shades-common-components 12.3.0 → 12.5.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 (268) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/esm/components/app-bar-link.spec.js +16 -19
  3. package/esm/components/app-bar-link.spec.js.map +1 -1
  4. package/esm/components/app-bar.spec.js +6 -4
  5. package/esm/components/app-bar.spec.js.map +1 -1
  6. package/esm/components/avatar.spec.js +9 -9
  7. package/esm/components/avatar.spec.js.map +1 -1
  8. package/esm/components/breadcrumb.spec.js +2 -2
  9. package/esm/components/breadcrumb.spec.js.map +1 -1
  10. package/esm/components/button-group.d.ts +32 -0
  11. package/esm/components/button-group.d.ts.map +1 -1
  12. package/esm/components/button-group.js +26 -2
  13. package/esm/components/button-group.js.map +1 -1
  14. package/esm/components/button-group.spec.js +127 -11
  15. package/esm/components/button-group.spec.js.map +1 -1
  16. package/esm/components/button.spec.js +4 -4
  17. package/esm/components/button.spec.js.map +1 -1
  18. package/esm/components/cache-view.spec.js +2 -3
  19. package/esm/components/cache-view.spec.js.map +1 -1
  20. package/esm/components/carousel.spec.js +47 -47
  21. package/esm/components/carousel.spec.js.map +1 -1
  22. package/esm/components/circular-progress.spec.js +2 -2
  23. package/esm/components/command-palette/command-palette-input.spec.js +23 -19
  24. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -1
  25. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +27 -27
  26. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -1
  27. package/esm/components/command-palette/index.spec.js +64 -51
  28. package/esm/components/command-palette/index.spec.js.map +1 -1
  29. package/esm/components/context-menu/context-menu.spec.js +33 -33
  30. package/esm/components/context-menu/context-menu.spec.js.map +1 -1
  31. package/esm/components/data-grid/body.spec.js +13 -13
  32. package/esm/components/data-grid/body.spec.js.map +1 -1
  33. package/esm/components/data-grid/data-grid-row.spec.js +8 -8
  34. package/esm/components/data-grid/data-grid-row.spec.js.map +1 -1
  35. package/esm/components/data-grid/data-grid.d.ts +40 -2
  36. package/esm/components/data-grid/data-grid.d.ts.map +1 -1
  37. package/esm/components/data-grid/data-grid.js +7 -10
  38. package/esm/components/data-grid/data-grid.js.map +1 -1
  39. package/esm/components/data-grid/data-grid.spec.js +71 -28
  40. package/esm/components/data-grid/data-grid.spec.js.map +1 -1
  41. package/esm/components/data-grid/filters/boolean-filter.d.ts +12 -0
  42. package/esm/components/data-grid/filters/boolean-filter.d.ts.map +1 -0
  43. package/esm/components/data-grid/filters/boolean-filter.js +27 -0
  44. package/esm/components/data-grid/filters/boolean-filter.js.map +1 -0
  45. package/esm/components/data-grid/filters/boolean-filter.spec.d.ts +2 -0
  46. package/esm/components/data-grid/filters/boolean-filter.spec.d.ts.map +1 -0
  47. package/esm/components/data-grid/filters/boolean-filter.spec.js +114 -0
  48. package/esm/components/data-grid/filters/boolean-filter.spec.js.map +1 -0
  49. package/esm/components/data-grid/filters/date-filter.d.ts +12 -0
  50. package/esm/components/data-grid/filters/date-filter.d.ts.map +1 -0
  51. package/esm/components/data-grid/filters/date-filter.js +109 -0
  52. package/esm/components/data-grid/filters/date-filter.js.map +1 -0
  53. package/esm/components/data-grid/filters/date-filter.spec.d.ts +2 -0
  54. package/esm/components/data-grid/filters/date-filter.spec.d.ts.map +1 -0
  55. package/esm/components/data-grid/filters/date-filter.spec.js +145 -0
  56. package/esm/components/data-grid/filters/date-filter.spec.js.map +1 -0
  57. package/esm/components/data-grid/filters/enum-filter.d.ts +16 -0
  58. package/esm/components/data-grid/filters/enum-filter.d.ts.map +1 -0
  59. package/esm/components/data-grid/filters/enum-filter.js +72 -0
  60. package/esm/components/data-grid/filters/enum-filter.js.map +1 -0
  61. package/esm/components/data-grid/filters/enum-filter.spec.d.ts +2 -0
  62. package/esm/components/data-grid/filters/enum-filter.spec.d.ts.map +1 -0
  63. package/esm/components/data-grid/filters/enum-filter.spec.js +136 -0
  64. package/esm/components/data-grid/filters/enum-filter.spec.js.map +1 -0
  65. package/esm/components/data-grid/filters/filter-dropdown.d.ts +6 -0
  66. package/esm/components/data-grid/filters/filter-dropdown.d.ts.map +1 -0
  67. package/esm/components/data-grid/filters/filter-dropdown.js +41 -0
  68. package/esm/components/data-grid/filters/filter-dropdown.js.map +1 -0
  69. package/esm/components/data-grid/filters/filter-dropdown.spec.d.ts +2 -0
  70. package/esm/components/data-grid/filters/filter-dropdown.spec.d.ts.map +1 -0
  71. package/esm/components/data-grid/filters/filter-dropdown.spec.js +69 -0
  72. package/esm/components/data-grid/filters/filter-dropdown.spec.js.map +1 -0
  73. package/esm/components/data-grid/filters/filter-styles.d.ts +24 -0
  74. package/esm/components/data-grid/filters/filter-styles.d.ts.map +1 -0
  75. package/esm/components/data-grid/filters/filter-styles.js +25 -0
  76. package/esm/components/data-grid/filters/filter-styles.js.map +1 -0
  77. package/esm/components/data-grid/filters/index.d.ts +7 -0
  78. package/esm/components/data-grid/filters/index.d.ts.map +1 -0
  79. package/esm/components/data-grid/filters/index.js +7 -0
  80. package/esm/components/data-grid/filters/index.js.map +1 -0
  81. package/esm/components/data-grid/filters/number-filter.d.ts +12 -0
  82. package/esm/components/data-grid/filters/number-filter.d.ts.map +1 -0
  83. package/esm/components/data-grid/filters/number-filter.js +65 -0
  84. package/esm/components/data-grid/filters/number-filter.js.map +1 -0
  85. package/esm/components/data-grid/filters/number-filter.spec.d.ts +2 -0
  86. package/esm/components/data-grid/filters/number-filter.spec.d.ts.map +1 -0
  87. package/esm/components/data-grid/filters/number-filter.spec.js +142 -0
  88. package/esm/components/data-grid/filters/number-filter.spec.js.map +1 -0
  89. package/esm/components/data-grid/filters/string-filter.d.ts +12 -0
  90. package/esm/components/data-grid/filters/string-filter.d.ts.map +1 -0
  91. package/esm/components/data-grid/filters/string-filter.js +63 -0
  92. package/esm/components/data-grid/filters/string-filter.js.map +1 -0
  93. package/esm/components/data-grid/filters/string-filter.spec.d.ts +2 -0
  94. package/esm/components/data-grid/filters/string-filter.spec.d.ts.map +1 -0
  95. package/esm/components/data-grid/filters/string-filter.spec.js +128 -0
  96. package/esm/components/data-grid/filters/string-filter.spec.js.map +1 -0
  97. package/esm/components/data-grid/footer.d.ts.map +1 -1
  98. package/esm/components/data-grid/footer.js +24 -9
  99. package/esm/components/data-grid/footer.js.map +1 -1
  100. package/esm/components/data-grid/footer.spec.js +38 -36
  101. package/esm/components/data-grid/footer.spec.js.map +1 -1
  102. package/esm/components/data-grid/header.d.ts +6 -9
  103. package/esm/components/data-grid/header.d.ts.map +1 -1
  104. package/esm/components/data-grid/header.js +51 -117
  105. package/esm/components/data-grid/header.js.map +1 -1
  106. package/esm/components/data-grid/header.spec.js +116 -187
  107. package/esm/components/data-grid/header.spec.js.map +1 -1
  108. package/esm/components/data-grid/index.d.ts +1 -0
  109. package/esm/components/data-grid/index.d.ts.map +1 -1
  110. package/esm/components/data-grid/index.js +1 -0
  111. package/esm/components/data-grid/index.js.map +1 -1
  112. package/esm/components/data-grid/selection-cell.spec.js +8 -8
  113. package/esm/components/data-grid/selection-cell.spec.js.map +1 -1
  114. package/esm/components/drawer/drawer-toggle-button.spec.js +22 -22
  115. package/esm/components/drawer/drawer-toggle-button.spec.js.map +1 -1
  116. package/esm/components/drawer/index.spec.js +36 -36
  117. package/esm/components/drawer/index.spec.js.map +1 -1
  118. package/esm/components/dropdown.spec.js +38 -30
  119. package/esm/components/dropdown.spec.js.map +1 -1
  120. package/esm/components/fab.spec.js +4 -4
  121. package/esm/components/fab.spec.js.map +1 -1
  122. package/esm/components/form.d.ts +5 -2
  123. package/esm/components/form.d.ts.map +1 -1
  124. package/esm/components/form.js +28 -6
  125. package/esm/components/form.js.map +1 -1
  126. package/esm/components/form.spec.js +227 -20
  127. package/esm/components/form.spec.js.map +1 -1
  128. package/esm/components/grid.spec.js +3 -3
  129. package/esm/components/grid.spec.js.map +1 -1
  130. package/esm/components/image.spec.js +55 -52
  131. package/esm/components/image.spec.js.map +1 -1
  132. package/esm/components/inputs/autocomplete.spec.js +7 -14
  133. package/esm/components/inputs/autocomplete.spec.js.map +1 -1
  134. package/esm/components/inputs/checkbox.spec.js +22 -22
  135. package/esm/components/inputs/checkbox.spec.js.map +1 -1
  136. package/esm/components/inputs/input-number.spec.js +47 -47
  137. package/esm/components/inputs/input-number.spec.js.map +1 -1
  138. package/esm/components/inputs/input.spec.js +53 -53
  139. package/esm/components/inputs/input.spec.js.map +1 -1
  140. package/esm/components/inputs/radio-group.spec.js +14 -14
  141. package/esm/components/inputs/radio-group.spec.js.map +1 -1
  142. package/esm/components/inputs/radio.spec.js +16 -16
  143. package/esm/components/inputs/radio.spec.js.map +1 -1
  144. package/esm/components/inputs/select.spec.js +74 -74
  145. package/esm/components/inputs/select.spec.js.map +1 -1
  146. package/esm/components/inputs/slider.spec.js +16 -16
  147. package/esm/components/inputs/slider.spec.js.map +1 -1
  148. package/esm/components/inputs/switch.spec.js +24 -24
  149. package/esm/components/inputs/switch.spec.js.map +1 -1
  150. package/esm/components/inputs/text-area.spec.js +17 -17
  151. package/esm/components/inputs/text-area.spec.js.map +1 -1
  152. package/esm/components/linear-progress.spec.js +2 -2
  153. package/esm/components/list/list.spec.js +36 -36
  154. package/esm/components/list/list.spec.js.map +1 -1
  155. package/esm/components/markdown/markdown-display.spec.js +15 -15
  156. package/esm/components/markdown/markdown-display.spec.js.map +1 -1
  157. package/esm/components/markdown/markdown-editor.spec.js +8 -8
  158. package/esm/components/markdown/markdown-editor.spec.js.map +1 -1
  159. package/esm/components/markdown/markdown-input.spec.js +17 -17
  160. package/esm/components/markdown/markdown-input.spec.js.map +1 -1
  161. package/esm/components/menu/menu.spec.js +28 -28
  162. package/esm/components/menu/menu.spec.js.map +1 -1
  163. package/esm/components/modal.spec.js +15 -18
  164. package/esm/components/modal.spec.js.map +1 -1
  165. package/esm/components/noty-list.spec.js +25 -23
  166. package/esm/components/noty-list.spec.js.map +1 -1
  167. package/esm/components/page-container/index.spec.js +16 -16
  168. package/esm/components/page-container/index.spec.js.map +1 -1
  169. package/esm/components/page-container/page-header.spec.js +16 -16
  170. package/esm/components/page-container/page-header.spec.js.map +1 -1
  171. package/esm/components/page-layout/index.spec.js +29 -29
  172. package/esm/components/page-layout/index.spec.js.map +1 -1
  173. package/esm/components/paper.spec.js +3 -3
  174. package/esm/components/paper.spec.js.map +1 -1
  175. package/esm/components/rating.spec.js +61 -61
  176. package/esm/components/rating.spec.js.map +1 -1
  177. package/esm/components/skeleton.spec.js +10 -6
  178. package/esm/components/skeleton.spec.js.map +1 -1
  179. package/esm/components/suggest/suggest-input.spec.js +4 -10
  180. package/esm/components/suggest/suggest-input.spec.js.map +1 -1
  181. package/esm/components/tabs.spec.js +30 -30
  182. package/esm/components/tabs.spec.js.map +1 -1
  183. package/esm/components/tree/tree.spec.js +27 -27
  184. package/esm/components/tree/tree.spec.js.map +1 -1
  185. package/esm/components/typography.spec.js +3 -3
  186. package/esm/components/typography.spec.js.map +1 -1
  187. package/esm/components/wizard/index.spec.js +5 -5
  188. package/esm/components/wizard/index.spec.js.map +1 -1
  189. package/esm/utils/promisify-animation.d.ts.map +1 -1
  190. package/esm/utils/promisify-animation.js +3 -0
  191. package/esm/utils/promisify-animation.js.map +1 -1
  192. package/package.json +2 -2
  193. package/src/components/app-bar-link.spec.tsx +16 -19
  194. package/src/components/app-bar.spec.tsx +6 -4
  195. package/src/components/avatar.spec.tsx +9 -9
  196. package/src/components/breadcrumb.spec.tsx +2 -2
  197. package/src/components/button-group.spec.tsx +155 -11
  198. package/src/components/button-group.tsx +49 -2
  199. package/src/components/button.spec.tsx +4 -4
  200. package/src/components/cache-view.spec.tsx +3 -3
  201. package/src/components/carousel.spec.tsx +47 -47
  202. package/src/components/circular-progress.spec.tsx +2 -2
  203. package/src/components/command-palette/command-palette-input.spec.tsx +23 -19
  204. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +27 -27
  205. package/src/components/command-palette/index.spec.tsx +64 -51
  206. package/src/components/context-menu/context-menu.spec.tsx +33 -33
  207. package/src/components/data-grid/body.spec.tsx +13 -13
  208. package/src/components/data-grid/data-grid-row.spec.tsx +8 -8
  209. package/src/components/data-grid/data-grid.spec.tsx +106 -28
  210. package/src/components/data-grid/data-grid.tsx +44 -11
  211. package/src/components/data-grid/filters/boolean-filter.spec.tsx +142 -0
  212. package/src/components/data-grid/filters/boolean-filter.tsx +45 -0
  213. package/src/components/data-grid/filters/date-filter.spec.tsx +181 -0
  214. package/src/components/data-grid/filters/date-filter.tsx +162 -0
  215. package/src/components/data-grid/filters/enum-filter.spec.tsx +168 -0
  216. package/src/components/data-grid/filters/enum-filter.tsx +119 -0
  217. package/src/components/data-grid/filters/filter-dropdown.spec.tsx +89 -0
  218. package/src/components/data-grid/filters/filter-dropdown.tsx +60 -0
  219. package/src/components/data-grid/filters/filter-styles.ts +26 -0
  220. package/src/components/data-grid/filters/index.ts +6 -0
  221. package/src/components/data-grid/filters/number-filter.spec.tsx +174 -0
  222. package/src/components/data-grid/filters/number-filter.tsx +115 -0
  223. package/src/components/data-grid/filters/string-filter.spec.tsx +157 -0
  224. package/src/components/data-grid/filters/string-filter.tsx +112 -0
  225. package/src/components/data-grid/footer.spec.tsx +38 -36
  226. package/src/components/data-grid/footer.tsx +21 -8
  227. package/src/components/data-grid/header.spec.tsx +128 -212
  228. package/src/components/data-grid/header.tsx +95 -183
  229. package/src/components/data-grid/index.tsx +1 -0
  230. package/src/components/data-grid/selection-cell.spec.tsx +8 -8
  231. package/src/components/drawer/drawer-toggle-button.spec.tsx +22 -22
  232. package/src/components/drawer/index.spec.tsx +36 -36
  233. package/src/components/dropdown.spec.tsx +38 -30
  234. package/src/components/fab.spec.tsx +4 -4
  235. package/src/components/form.spec.tsx +329 -20
  236. package/src/components/form.tsx +31 -8
  237. package/src/components/grid.spec.tsx +3 -3
  238. package/src/components/image.spec.tsx +55 -52
  239. package/src/components/inputs/autocomplete.spec.tsx +7 -14
  240. package/src/components/inputs/checkbox.spec.tsx +22 -22
  241. package/src/components/inputs/input-number.spec.tsx +47 -47
  242. package/src/components/inputs/input.spec.tsx +53 -53
  243. package/src/components/inputs/radio-group.spec.tsx +14 -14
  244. package/src/components/inputs/radio.spec.tsx +16 -16
  245. package/src/components/inputs/select.spec.tsx +74 -74
  246. package/src/components/inputs/slider.spec.tsx +16 -16
  247. package/src/components/inputs/switch.spec.tsx +24 -24
  248. package/src/components/inputs/text-area.spec.tsx +17 -17
  249. package/src/components/linear-progress.spec.tsx +2 -2
  250. package/src/components/list/list.spec.tsx +36 -36
  251. package/src/components/markdown/markdown-display.spec.tsx +15 -15
  252. package/src/components/markdown/markdown-editor.spec.tsx +8 -8
  253. package/src/components/markdown/markdown-input.spec.tsx +17 -17
  254. package/src/components/menu/menu.spec.tsx +28 -28
  255. package/src/components/modal.spec.tsx +15 -18
  256. package/src/components/noty-list.spec.tsx +25 -23
  257. package/src/components/page-container/index.spec.tsx +16 -16
  258. package/src/components/page-container/page-header.spec.tsx +16 -16
  259. package/src/components/page-layout/index.spec.tsx +29 -29
  260. package/src/components/paper.spec.tsx +3 -3
  261. package/src/components/rating.spec.tsx +61 -61
  262. package/src/components/skeleton.spec.tsx +10 -6
  263. package/src/components/suggest/suggest-input.spec.tsx +4 -10
  264. package/src/components/tabs.spec.tsx +30 -30
  265. package/src/components/tree/tree.spec.tsx +27 -27
  266. package/src/components/typography.spec.tsx +3 -3
  267. package/src/components/wizard/index.spec.tsx +5 -5
  268. package/src/utils/promisify-animation.ts +3 -0
@@ -1,29 +1,27 @@
1
- import type { FilterType, FindOptions } from '@furystack/core'
1
+ import type { FindOptions } from '@furystack/core'
2
2
  import type { ChildrenList } from '@furystack/shades'
3
3
  import { Shade, createComponent } from '@furystack/shades'
4
4
  import type { ObservableValue } from '@furystack/utils'
5
- import { sleepAsync } from '@furystack/utils'
6
- import { collapse, expand } from '../animations.js'
7
- import { Button } from '../button.js'
8
- import { Form } from '../form.js'
5
+ import { ToggleButton } from '../button-group.js'
6
+ import { arrowDown, arrowUp, arrowUpDown, filter as filterIcon } from '../icons/icon-definitions.js'
9
7
  import { Icon } from '../icons/icon.js'
10
- import { arrowDown, arrowUp, arrowUpDown, close as closeIcon, search as searchIcon } from '../icons/icon-definitions.js'
11
- import { Input } from '../inputs/input.js'
12
-
13
- export interface DataGridHeaderProps<T, Column extends string> {
8
+ import type { ColumnFilterConfig, FilterableFindOptions } from './data-grid.js'
9
+ import { BooleanFilter } from './filters/boolean-filter.js'
10
+ import { DateFilter } from './filters/date-filter.js'
11
+ import { EnumFilter } from './filters/enum-filter.js'
12
+ import { FilterDropdown } from './filters/filter-dropdown.js'
13
+ import { NumberFilter } from './filters/number-filter.js'
14
+ import { StringFilter } from './filters/string-filter.js'
15
+
16
+ export type DataGridHeaderProps<T, Column extends string> = {
14
17
  field: Column
15
18
  findOptions: ObservableValue<FindOptions<T, Array<keyof T>>>
16
- }
17
-
18
- export interface DataGridHeaderState<T, K extends keyof T> {
19
- findOptions: FindOptions<T, K[]>
20
- isSearchOpened: boolean
21
- updateSearchValue: (value: string) => void
19
+ filterConfig?: ColumnFilterConfig
22
20
  }
23
21
 
24
22
  export const OrderButton = Shade<{
25
23
  field: string
26
- findOptions: ObservableValue<FindOptions<any, any[]>>
24
+ findOptions: ObservableValue<FilterableFindOptions>
27
25
  }>({
28
26
  shadowDomName: 'data-grid-order-button',
29
27
  css: {
@@ -35,14 +33,15 @@ export const OrderButton = Shade<{
35
33
  const currentOrder = Object.keys(findOptions.order || {})[0]
36
34
  const currentOrderDirection = Object.values(findOptions.order || {})[0]
37
35
  return (
38
- <Button
36
+ <ToggleButton
39
37
  title="Change order"
40
- variant="outlined"
41
- color={currentOrder === props.field ? 'info' : undefined}
38
+ pressed={currentOrder === props.field}
39
+ size="small"
40
+ value={props.field}
42
41
  onclick={(ev) => {
43
42
  ev.stopPropagation()
44
43
  let newDirection: 'ASC' | 'DESC' = 'ASC'
45
- const newOrder: { [K in keyof any]: 'ASC' | 'DESC' } = {}
44
+ const newOrder: Record<string, 'ASC' | 'DESC'> = {}
46
45
 
47
46
  if (currentOrder === props.field) {
48
47
  newDirection = currentOrderDirection === 'ASC' ? 'DESC' : 'ASC'
@@ -56,220 +55,133 @@ export const OrderButton = Shade<{
56
55
  >
57
56
  {(currentOrder === props.field &&
58
57
  (currentOrderDirection === 'ASC' ? (
59
- <Icon icon={arrowDown} size={16} />
58
+ <Icon icon={arrowDown} size={14} />
60
59
  ) : (
61
- <Icon icon={arrowUp} size={16} />
62
- ))) || <Icon icon={arrowUpDown} size={16} />}
63
- </Button>
60
+ <Icon icon={arrowUp} size={14} />
61
+ ))) || <Icon icon={arrowUpDown} size={14} />}
62
+ </ToggleButton>
64
63
  )
65
64
  },
66
65
  })
67
66
 
68
- const SearchButton = Shade<{
69
- fieldName: string
67
+ const FilterButton = Shade<{
68
+ field: string
69
+ findOptions: ObservableValue<FilterableFindOptions>
70
70
  onclick: () => void
71
- findOptions: ObservableValue<FindOptions<any, any[]>>
72
71
  }>({
73
- shadowDomName: 'data-grid-search-button',
72
+ shadowDomName: 'data-grid-filter-button',
74
73
  css: {
75
74
  display: 'inline-block',
76
75
  },
77
76
  render: ({ props, useObservable }) => {
78
- const [findOptions] = useObservable('currentValue', props.findOptions, {
79
- filter: (newValue) => {
80
- return !!newValue.filter?.[props.fieldName]
81
- },
82
- })
77
+ const [findOptions] = useObservable('currentValue', props.findOptions)
83
78
 
84
- const filterValue =
85
- (findOptions.filter?.[props.fieldName] as FilterType<{ [K in typeof props.fieldName]: string }>)?.$regex || ''
79
+ const hasActiveFilter = !!findOptions.filter?.[props.field]
86
80
 
87
81
  return (
88
- <Button
82
+ <ToggleButton
89
83
  type="button"
90
84
  title="Filter"
91
- variant="outlined"
92
- color={filterValue ? 'info' : undefined}
93
- onclick={props.onclick}
94
- >
95
- <Icon icon={searchIcon} size={16} />
96
- </Button>
97
- )
98
- },
99
- })
100
-
101
- const SearchForm = Shade<{
102
- onSubmit: (newValue: string) => void
103
- onClear: () => void
104
- fieldName: string
105
- findOptions: ObservableValue<FindOptions<any, any[]>>
106
- }>({
107
- shadowDomName: 'data-grid-search-form',
108
- css: {
109
- display: 'block',
110
- '& .search-form': {
111
- display: 'flex',
112
- width: '100%',
113
- overflow: 'hidden',
114
- height: '0px',
115
- justifyContent: 'space-around',
116
- opacity: '0',
117
- },
118
- '& .search-form-actions': {
119
- display: 'flex',
120
- width: '64px',
121
- alignItems: 'center',
122
- justifyContent: 'center',
123
- gap: '8px',
124
- },
125
- },
126
- render: ({ props, useObservable }) => {
127
- type SearchSubmitType = { searchValue: string }
128
-
129
- const [findOptions] = useObservable('currentValue', props.findOptions, {
130
- filter: (newValue, lastValue) => {
131
- const newFilter = newValue.filter?.[props.fieldName] as FilterType<{ [K in typeof props.fieldName]: string }>
132
- const lastFilter = lastValue.filter?.[props.fieldName] as FilterType<{ [K in typeof props.fieldName]: string }>
133
- return newFilter?.$regex !== lastFilter?.$regex
134
- },
135
- })
136
-
137
- const currentValue = (findOptions.filter?.[props.fieldName] as FilterType<Record<string, string>>)?.$regex || ''
138
-
139
- return (
140
- <Form<SearchSubmitType>
141
- className="search-form"
142
- validate={(data): data is SearchSubmitType =>
143
- typeof (data as SearchSubmitType).searchValue?.length === 'number'
144
- }
145
- onSubmit={({ searchValue }) => {
146
- props.onSubmit(searchValue)
85
+ size="small"
86
+ value={props.field}
87
+ pressed={hasActiveFilter}
88
+ onclick={(ev) => {
89
+ ev.stopPropagation()
90
+ props.onclick()
147
91
  }}
148
92
  >
149
- <Input
150
- placeholder={props.fieldName}
151
- autofocus
152
- labelTitle={`${props.fieldName}`}
153
- name="searchValue"
154
- value={typeof currentValue === 'string' ? currentValue : ''}
155
- labelProps={{
156
- style: { padding: '0px 2em' },
157
- }}
158
- />
159
- <div className="search-form-actions">
160
- <Button
161
- type="reset"
162
- variant="outlined"
163
- onclick={(ev) => {
164
- ev.preventDefault()
165
- ev.stopPropagation()
166
- props.onClear()
167
- }}
168
- >
169
- <Icon icon={closeIcon} size={16} />
170
- </Button>
171
- <Button variant="outlined" type="submit">
172
- <Icon icon={searchIcon} size={16} />
173
- </Button>
174
- </div>
175
- </Form>
93
+ <Icon icon={filterIcon} size={14} />
94
+ </ToggleButton>
176
95
  )
177
96
  },
178
97
  })
179
98
 
99
+ const renderFilterComponent = (
100
+ filterConfig: ColumnFilterConfig,
101
+ field: string,
102
+ findOptions: ObservableValue<FilterableFindOptions>,
103
+ onClose: () => void,
104
+ ): JSX.Element => {
105
+ switch (filterConfig.type) {
106
+ case 'number':
107
+ return <NumberFilter field={field} findOptions={findOptions} onClose={onClose} />
108
+ case 'boolean':
109
+ return <BooleanFilter field={field} findOptions={findOptions} onClose={onClose} />
110
+ case 'enum':
111
+ return <EnumFilter field={field} values={filterConfig.values} findOptions={findOptions} onClose={onClose} />
112
+ case 'date':
113
+ return <DateFilter field={field} findOptions={findOptions} onClose={onClose} />
114
+ case 'string':
115
+ return <StringFilter field={field} findOptions={findOptions} onClose={onClose} />
116
+ default: {
117
+ const _exhaustive: never = filterConfig
118
+ throw new Error(`Unknown filter type: ${(_exhaustive as ColumnFilterConfig).type}`)
119
+ }
120
+ }
121
+ }
122
+
180
123
  export const DataGridHeader: <T, Column extends string>(
181
124
  props: DataGridHeaderProps<T, Column>,
182
125
  children: ChildrenList,
183
- findOptions: ObservableValue<FindOptions<T, Array<keyof T>>>,
184
126
  ) => JSX.Element<any> = Shade({
185
127
  shadowDomName: 'data-grid-header',
186
128
  css: {
187
129
  display: 'block',
130
+ position: 'relative',
188
131
  '& .header-content': {
189
132
  display: 'flex',
190
133
  width: '100%',
191
- height: '48px',
134
+ height: '36px',
192
135
  justifyContent: 'space-between',
193
136
  alignItems: 'center',
194
- gap: '12px',
137
+ gap: '8px',
195
138
  overflow: 'hidden',
196
139
  },
197
140
  '& .header-field-name': {
198
- fontWeight: '600',
141
+ overflow: 'hidden',
142
+ textOverflow: 'ellipsis',
143
+ whiteSpace: 'nowrap',
199
144
  },
200
145
  '& .header-controls': {
201
146
  display: 'flex',
202
- justifyContent: 'center',
203
147
  alignItems: 'center',
204
- gap: '4px',
148
+ gap: '2px',
149
+ flexShrink: '0',
205
150
  },
206
151
  },
207
- render: ({ props, useObservable, useState, useRef }) => {
208
- const searchFormRef = useRef<HTMLElement>('searchForm')
209
- const headerContentRef = useRef<HTMLDivElement>('headerContent')
210
-
211
- const [, setIsSearchOpened] = useState('isSearchOpened', false)
212
-
213
- const openSearch = () => {
214
- setIsSearchOpened(true)
215
- const searchForm = searchFormRef.current?.querySelector('.search-form') as HTMLElement | null
216
- const headerContent = headerContentRef.current
217
- if (!searchForm || !headerContent) return
218
- searchForm.style.display = 'flex'
219
- void expand(searchForm).then(async () => {
220
- await sleepAsync(100)
221
- searchForm.querySelector('input')?.focus()
222
- })
223
- void collapse(headerContent)
224
- }
225
-
226
- const closeSearch = () => {
227
- setIsSearchOpened(false)
228
- const searchForm = searchFormRef.current?.querySelector('.search-form') as HTMLElement | null
229
- const headerContent = headerContentRef.current
230
- if (!searchForm || !headerContent) return
231
- void collapse(searchForm)
232
- void expand(headerContent)
233
- }
152
+ render: ({ props, useState }) => {
153
+ const [isFilterOpen, setIsFilterOpen] = useState('isFilterOpen', false)
234
154
 
235
- const [findOptions, setFindOptions] = useObservable('findOptions', props.findOptions)
236
-
237
- const updateSearchValue = (value?: string) => {
238
- if (value) {
239
- const newSettings = {
240
- ...findOptions,
241
- filter: {
242
- ...findOptions.filter,
243
- [props.field]: { $regex: value },
244
- },
245
- } as typeof findOptions
246
- setFindOptions(newSettings)
247
- } else {
248
- const { [props.field]: _, ...newFilter } = findOptions.filter || {}
249
- setFindOptions({ ...findOptions, filter: newFilter })
250
- }
251
-
252
- closeSearch()
253
- }
155
+ const closeFilter = () => setIsFilterOpen(false)
254
156
 
255
157
  return (
256
158
  <>
257
- <div ref={searchFormRef} style={{ display: 'contents' }}>
258
- <SearchForm
259
- onSubmit={updateSearchValue}
260
- onClear={updateSearchValue}
261
- fieldName={props.field}
262
- findOptions={props.findOptions}
263
- />
264
- </div>
265
- <div ref={headerContentRef} className="header-content">
159
+ <div className="header-content">
266
160
  <div className="header-field-name">{props.field}</div>
267
161
  <div className="header-controls">
268
- <SearchButton onclick={openSearch} findOptions={props.findOptions} fieldName={props.field} />
269
-
270
- <OrderButton field={props.field} findOptions={props.findOptions} />
162
+ {props.filterConfig && (
163
+ <FilterButton
164
+ onclick={() => setIsFilterOpen(!isFilterOpen)}
165
+ findOptions={props.findOptions as ObservableValue<FilterableFindOptions>}
166
+ field={props.field}
167
+ />
168
+ )}
169
+ <OrderButton
170
+ field={props.field}
171
+ findOptions={props.findOptions as ObservableValue<FilterableFindOptions>}
172
+ />
271
173
  </div>
272
174
  </div>
175
+ {isFilterOpen && props.filterConfig && (
176
+ <FilterDropdown onClose={closeFilter}>
177
+ {renderFilterComponent(
178
+ props.filterConfig,
179
+ props.field,
180
+ props.findOptions as ObservableValue<FilterableFindOptions>,
181
+ closeFilter,
182
+ )}
183
+ </FilterDropdown>
184
+ )}
273
185
  </>
274
186
  )
275
187
  },
@@ -1,2 +1,3 @@
1
1
  export * from './data-grid.js'
2
2
  export * from './selection-cell.js'
3
+ export * from './filters/index.js'
@@ -1,6 +1,6 @@
1
1
  import { Injector } from '@furystack/inject'
2
- import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
- import { sleepAsync, usingAsync } from '@furystack/utils'
2
+ import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'
3
+ import { usingAsync } from '@furystack/utils'
4
4
  import { afterEach, beforeEach, describe, expect, it } from 'vitest'
5
5
  import { CollectionService } from '../../services/collection-service.js'
6
6
  import { SelectionCell } from './selection-cell.js'
@@ -24,7 +24,7 @@ describe('SelectionCell', () => {
24
24
  rootElement: root,
25
25
  jsxElement: <SelectionCell entry={entry} service={service} />,
26
26
  })
27
- await sleepAsync(50)
27
+ await flushUpdates()
28
28
  return {
29
29
  injector,
30
30
  cell: root.querySelector('shades-data-grid-selection-cell') as HTMLElement,
@@ -76,12 +76,12 @@ describe('SelectionCell', () => {
76
76
  expect(getCheckbox()?.checked).toBe(false)
77
77
 
78
78
  service.selection.setValue([entry])
79
- await sleepAsync(50)
79
+ await flushUpdates()
80
80
 
81
81
  expect(getCheckbox()?.checked).toBe(true)
82
82
 
83
83
  service.selection.setValue([])
84
- await sleepAsync(50)
84
+ await flushUpdates()
85
85
 
86
86
  expect(getCheckbox()?.checked).toBe(false)
87
87
  })
@@ -109,7 +109,7 @@ describe('SelectionCell', () => {
109
109
 
110
110
  const checkbox = getCheckbox()
111
111
  checkbox?.dispatchEvent(new Event('change', { bubbles: true }))
112
- await sleepAsync(50)
112
+ await flushUpdates()
113
113
 
114
114
  expect(service.selection.getValue()).toContain(entry)
115
115
  })
@@ -125,7 +125,7 @@ describe('SelectionCell', () => {
125
125
 
126
126
  const checkbox = getCheckbox()
127
127
  checkbox?.dispatchEvent(new Event('change', { bubbles: true }))
128
- await sleepAsync(50)
128
+ await flushUpdates()
129
129
 
130
130
  expect(service.selection.getValue()).not.toContain(entry)
131
131
  })
@@ -140,7 +140,7 @@ describe('SelectionCell', () => {
140
140
  await usingAsync(await renderSelectionCell(entry, service), async ({ getCheckbox }) => {
141
141
  const checkbox = getCheckbox()
142
142
  checkbox?.dispatchEvent(new Event('change', { bubbles: true }))
143
- await sleepAsync(50)
143
+ await flushUpdates()
144
144
 
145
145
  expect(service.selection.getValue()).toContain(entry)
146
146
  expect(service.selection.getValue()).toContain(otherEntry)
@@ -1,6 +1,6 @@
1
1
  import { Injector } from '@furystack/inject'
2
- import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
- import { sleepAsync, usingAsync } from '@furystack/utils'
2
+ import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'
3
+ import { usingAsync } from '@furystack/utils'
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
5
  import { LayoutService } from '../../services/layout-service.js'
6
6
  import { DrawerToggleButton } from './drawer-toggle-button.js'
@@ -37,7 +37,7 @@ describe('DrawerToggleButton component', () => {
37
37
  jsxElement: <DrawerToggleButton position="left" />,
38
38
  })
39
39
 
40
- await sleepAsync(50)
40
+ await flushUpdates()
41
41
  const element = document.querySelector('shade-drawer-toggle-button')
42
42
  expect(element).not.toBeNull()
43
43
  expect(element?.tagName.toLowerCase()).toBe('shade-drawer-toggle-button')
@@ -56,7 +56,7 @@ describe('DrawerToggleButton component', () => {
56
56
  jsxElement: <DrawerToggleButton position="left" />,
57
57
  })
58
58
 
59
- await sleepAsync(50)
59
+ await flushUpdates()
60
60
  const button = document.querySelector('button')
61
61
  expect(button).not.toBeNull()
62
62
  })
@@ -74,7 +74,7 @@ describe('DrawerToggleButton component', () => {
74
74
  jsxElement: <DrawerToggleButton position="left" />,
75
75
  })
76
76
 
77
- await sleepAsync(50)
77
+ await flushUpdates()
78
78
  const hamburger = document.querySelector('.hamburger')
79
79
  expect(hamburger).not.toBeNull()
80
80
 
@@ -102,7 +102,7 @@ describe('DrawerToggleButton component', () => {
102
102
  jsxElement: <DrawerToggleButton position="left" />,
103
103
  })
104
104
 
105
- await sleepAsync(50)
105
+ await flushUpdates()
106
106
  const button = document.querySelector('[data-testid="drawer-toggle-left"]') as HTMLButtonElement
107
107
  expect(button).not.toBeNull()
108
108
  // Button is rendered, aria-label is set in JSX (may not be visible in JSDOM)
@@ -121,7 +121,7 @@ describe('DrawerToggleButton component', () => {
121
121
  jsxElement: <DrawerToggleButton position="left" ariaLabel="Toggle navigation menu" />,
122
122
  })
123
123
 
124
- await sleepAsync(50)
124
+ await flushUpdates()
125
125
  const button = document.querySelector('[data-testid="drawer-toggle-left"]') as HTMLButtonElement
126
126
  expect(button).not.toBeNull()
127
127
  // Button is rendered with custom ariaLabel prop (may not be visible in JSDOM)
@@ -143,7 +143,7 @@ describe('DrawerToggleButton component', () => {
143
143
  jsxElement: <DrawerToggleButton position="left" />,
144
144
  })
145
145
 
146
- await sleepAsync(50)
146
+ await flushUpdates()
147
147
  const button = document.querySelector('[data-testid="drawer-toggle-left"]') as HTMLButtonElement
148
148
  expect(button).not.toBeNull()
149
149
 
@@ -153,7 +153,7 @@ describe('DrawerToggleButton component', () => {
153
153
 
154
154
  // Close drawer
155
155
  layoutService.setDrawerOpen('left', false)
156
- await sleepAsync(50)
156
+ await flushUpdates()
157
157
 
158
158
  // Verify hamburger doesn't have open class when drawer is closed
159
159
  hamburger = document.querySelector('.hamburger')
@@ -173,7 +173,7 @@ describe('DrawerToggleButton component', () => {
173
173
  jsxElement: <DrawerToggleButton position="left" />,
174
174
  })
175
175
 
176
- await sleepAsync(50)
176
+ await flushUpdates()
177
177
  const button = document.querySelector('button')
178
178
  expect(button?.getAttribute('type')).toBe('button')
179
179
  })
@@ -196,19 +196,19 @@ describe('DrawerToggleButton component', () => {
196
196
  jsxElement: <DrawerToggleButton position="left" />,
197
197
  })
198
198
 
199
- await sleepAsync(50)
199
+ await flushUpdates()
200
200
  expect(layoutService.drawerState.getValue().left?.open).toBe(true)
201
201
 
202
202
  // Click the button
203
203
  const button = document.querySelector('button') as HTMLButtonElement
204
204
  button.click()
205
- await sleepAsync(50)
205
+ await flushUpdates()
206
206
 
207
207
  expect(layoutService.drawerState.getValue().left?.open).toBe(false)
208
208
 
209
209
  // Click again
210
210
  button.click()
211
- await sleepAsync(50)
211
+ await flushUpdates()
212
212
 
213
213
  expect(layoutService.drawerState.getValue().left?.open).toBe(true)
214
214
  })
@@ -229,13 +229,13 @@ describe('DrawerToggleButton component', () => {
229
229
  jsxElement: <DrawerToggleButton position="right" />,
230
230
  })
231
231
 
232
- await sleepAsync(50)
232
+ await flushUpdates()
233
233
  expect(layoutService.drawerState.getValue().right?.open).toBe(true)
234
234
 
235
235
  // Click the button
236
236
  const button = document.querySelector('button') as HTMLButtonElement
237
237
  button.click()
238
- await sleepAsync(50)
238
+ await flushUpdates()
239
239
 
240
240
  expect(layoutService.drawerState.getValue().right?.open).toBe(false)
241
241
  })
@@ -253,7 +253,7 @@ describe('DrawerToggleButton component', () => {
253
253
  jsxElement: <DrawerToggleButton position="left" />,
254
254
  })
255
255
 
256
- await sleepAsync(50)
256
+ await flushUpdates()
257
257
 
258
258
  // Should not throw when clicking even though drawer isn't initialized
259
259
  const button = document.querySelector('button') as HTMLButtonElement
@@ -278,7 +278,7 @@ describe('DrawerToggleButton component', () => {
278
278
  jsxElement: <DrawerToggleButton position="left" />,
279
279
  })
280
280
 
281
- await sleepAsync(50)
281
+ await flushUpdates()
282
282
  const hamburger = document.querySelector('.hamburger')
283
283
  expect(hamburger?.classList.contains('open')).toBe(false)
284
284
  })
@@ -299,7 +299,7 @@ describe('DrawerToggleButton component', () => {
299
299
  jsxElement: <DrawerToggleButton position="left" />,
300
300
  })
301
301
 
302
- await sleepAsync(50)
302
+ await flushUpdates()
303
303
  const hamburger = document.querySelector('.hamburger')
304
304
  expect(hamburger?.classList.contains('open')).toBe(true)
305
305
  })
@@ -320,13 +320,13 @@ describe('DrawerToggleButton component', () => {
320
320
  jsxElement: <DrawerToggleButton position="left" />,
321
321
  })
322
322
 
323
- await sleepAsync(50)
323
+ await flushUpdates()
324
324
  let hamburger = document.querySelector('.hamburger')
325
325
  expect(hamburger?.classList.contains('open')).toBe(true)
326
326
 
327
327
  // Close drawer via LayoutService
328
328
  layoutService.setDrawerOpen('left', false)
329
- await sleepAsync(50)
329
+ await flushUpdates()
330
330
 
331
331
  hamburger = document.querySelector('.hamburger')
332
332
  expect(hamburger?.classList.contains('open')).toBe(false)
@@ -347,7 +347,7 @@ describe('DrawerToggleButton component', () => {
347
347
  jsxElement: <DrawerToggleButton position="left" />,
348
348
  })
349
349
 
350
- await sleepAsync(50)
350
+ await flushUpdates()
351
351
  const button = document.querySelector('[data-testid="drawer-toggle-left"]')
352
352
  expect(button).not.toBeNull()
353
353
  })
@@ -365,7 +365,7 @@ describe('DrawerToggleButton component', () => {
365
365
  jsxElement: <DrawerToggleButton position="right" />,
366
366
  })
367
367
 
368
- await sleepAsync(50)
368
+ await flushUpdates()
369
369
  const button = document.querySelector('[data-testid="drawer-toggle-right"]')
370
370
  expect(button).not.toBeNull()
371
371
  })