thread-ui 0.5.0 → 0.6.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/README.md +10 -2
- package/dist/components/data-display/filter-controls/filter-controls.d.ts +10 -0
- package/dist/components/data-display/filter-controls/filter-controls.js +39 -0
- package/dist/components/data-display/filter-controls/filter-controls.js.map +1 -0
- package/dist/components/data-display/filter-controls/filter-controls.types.d.ts +75 -0
- package/dist/components/data-display/filter-controls/filter-controls.types.js +2 -0
- package/dist/components/data-display/filter-controls/filter-controls.types.js.map +1 -0
- package/dist/components/data-display/filter-controls/index.d.ts +2 -0
- package/dist/components/data-display/filter-controls/index.js +2 -0
- package/dist/components/data-display/filter-controls/index.js.map +1 -0
- package/dist/components/data-display/filter-controls/use-filter-controls.d.ts +23 -0
- package/dist/components/data-display/filter-controls/use-filter-controls.js +89 -0
- package/dist/components/data-display/filter-controls/use-filter-controls.js.map +1 -0
- package/dist/components/data-display/index.d.ts +2 -0
- package/dist/components/data-display/index.js +3 -0
- package/dist/components/data-display/index.js.map +1 -0
- package/dist/components/data-display/sort-controls/index.d.ts +3 -0
- package/dist/components/data-display/sort-controls/index.js +3 -0
- package/dist/components/data-display/sort-controls/index.js.map +1 -0
- package/dist/components/data-display/sort-controls/sort-controls.d.ts +11 -0
- package/dist/components/data-display/sort-controls/sort-controls.js +51 -0
- package/dist/components/data-display/sort-controls/sort-controls.js.map +1 -0
- package/dist/components/data-display/sort-controls/sort-controls.types.d.ts +62 -0
- package/dist/components/data-display/sort-controls/sort-controls.types.js +2 -0
- package/dist/components/data-display/sort-controls/sort-controls.types.js.map +1 -0
- package/dist/components/data-display/sort-controls/use-sort-controls.d.ts +23 -0
- package/dist/components/data-display/sort-controls/use-sort-controls.js +94 -0
- package/dist/components/data-display/sort-controls/use-sort-controls.js.map +1 -0
- package/dist/components/form-elements/{dropdown → dropdowns/dropdown}/dropdown.d.ts +1 -1
- package/dist/components/form-elements/dropdowns/dropdown/dropdown.js +41 -0
- package/dist/components/form-elements/dropdowns/dropdown/dropdown.js.map +1 -0
- package/dist/components/form-elements/{dropdown → dropdowns/dropdown}/dropdown.types.d.ts +4 -9
- package/dist/components/form-elements/dropdowns/dropdown/dropdown.types.js.map +1 -0
- package/dist/components/form-elements/dropdowns/dropdown/index.js.map +1 -0
- package/dist/components/form-elements/dropdowns/dropdown-base/dropdown-base.d.ts +8 -0
- package/dist/components/form-elements/dropdowns/dropdown-base/dropdown-base.js +46 -0
- package/dist/components/form-elements/dropdowns/dropdown-base/dropdown-base.js.map +1 -0
- package/dist/components/form-elements/dropdowns/dropdown-base/dropdown-base.types.d.ts +25 -0
- package/dist/components/form-elements/dropdowns/dropdown-base/dropdown-base.types.js +2 -0
- package/dist/components/form-elements/dropdowns/dropdown-base/dropdown-base.types.js.map +1 -0
- package/dist/components/form-elements/dropdowns/dropdown-base/index.d.ts +2 -0
- package/dist/components/form-elements/dropdowns/dropdown-base/index.js +2 -0
- package/dist/components/form-elements/dropdowns/dropdown-base/index.js.map +1 -0
- package/dist/components/form-elements/dropdowns/index.d.ts +2 -0
- package/dist/components/form-elements/dropdowns/index.js +3 -0
- package/dist/components/form-elements/dropdowns/index.js.map +1 -0
- package/dist/components/form-elements/dropdowns/multi-dropdown/index.d.ts +2 -0
- package/dist/components/form-elements/dropdowns/multi-dropdown/index.js +2 -0
- package/dist/components/form-elements/dropdowns/multi-dropdown/index.js.map +1 -0
- package/dist/components/form-elements/dropdowns/multi-dropdown/multi-dropdown.d.ts +15 -0
- package/dist/components/form-elements/dropdowns/multi-dropdown/multi-dropdown.js +55 -0
- package/dist/components/form-elements/dropdowns/multi-dropdown/multi-dropdown.js.map +1 -0
- package/dist/components/form-elements/dropdowns/multi-dropdown/multi-dropdown.types.d.ts +25 -0
- package/dist/components/form-elements/dropdowns/multi-dropdown/multi-dropdown.types.js +2 -0
- package/dist/components/form-elements/dropdowns/multi-dropdown/multi-dropdown.types.js.map +1 -0
- package/dist/components/form-elements/form-label/form-label.d.ts +1 -1
- package/dist/components/form-elements/form-label/form-label.js +34 -8
- package/dist/components/form-elements/form-label/form-label.js.map +1 -1
- package/dist/components/form-elements/form-label/form-label.types.d.ts +3 -0
- package/dist/components/form-elements/index.d.ts +2 -2
- package/dist/components/form-elements/index.js +2 -2
- package/dist/components/form-elements/index.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/layouts/column-layout/column-layout.d.ts +1 -1
- package/dist/components/layouts/column-layout/column-layout.js +5 -4
- package/dist/components/layouts/column-layout/column-layout.js.map +1 -1
- package/dist/components/layouts/column-layout/column-layout.types.d.ts +4 -1
- package/dist/components/layouts/container/container.d.ts +11 -0
- package/dist/components/layouts/container/container.js +28 -0
- package/dist/components/layouts/container/container.js.map +1 -0
- package/dist/components/layouts/container/container.types.d.ts +11 -0
- package/dist/components/layouts/container/container.types.js +2 -0
- package/dist/components/layouts/container/container.types.js.map +1 -0
- package/dist/components/layouts/container/index.d.ts +2 -0
- package/dist/components/layouts/container/index.js +2 -0
- package/dist/components/layouts/container/index.js.map +1 -0
- package/dist/components/layouts/index.d.ts +1 -0
- package/dist/components/layouts/index.js +1 -0
- package/dist/components/layouts/index.js.map +1 -1
- package/dist/components/layouts/layout-component.types.d.ts +9 -0
- package/dist/components/layouts/layout-component.types.js +2 -0
- package/dist/components/layouts/layout-component.types.js.map +1 -0
- package/dist/components/layouts/layout-wrapper.d.ts +7 -0
- package/dist/components/layouts/layout-wrapper.js +9 -0
- package/dist/components/layouts/layout-wrapper.js.map +1 -0
- package/dist/components/layouts/masonry-layout/masonry-layout.d.ts +1 -1
- package/dist/components/layouts/masonry-layout/masonry-layout.js +5 -5
- package/dist/components/layouts/masonry-layout/masonry-layout.js.map +1 -1
- package/dist/components/layouts/masonry-layout/masonry-layout.types.d.ts +3 -2
- package/dist/components/media/info-card/info-card.js +3 -4
- package/dist/components/media/info-card/info-card.js.map +1 -1
- package/dist/components/navigation/nav-menu/items/base-item/base-item.d.ts +1 -1
- package/dist/components/navigation/nav-menu/items/base-item/base-item.js +1 -1
- package/dist/components/navigation/nav-menu/items/base-item/base-item.js.map +1 -1
- package/dist/components/navigation/side-nav/side-nav-item/side-nav-item.js +1 -1
- package/dist/components/typography/typography.d.ts +10 -8
- package/dist/components/typography/typography.js +42 -8
- package/dist/components/typography/typography.js.map +1 -1
- package/dist/components/ui/button/button-recipe.js +90 -6
- package/dist/components/ui/button/button-recipe.js.map +1 -1
- package/dist/components/ui/button/button.d.ts +1 -1
- package/dist/components/ui/button/button.js +2 -1
- package/dist/components/ui/button/button.js.map +1 -1
- package/dist/components/ui/button/button.types.d.ts +3 -1
- package/dist/components/ui/icon/icon.d.ts +1 -1
- package/dist/components/ui/icon/icon.js +1 -1
- package/dist/components/ui/icon-button/icon-button.d.ts +1 -1
- package/dist/components/ui/icon-button/icon-button.js +4 -7
- package/dist/components/ui/icon-button/icon-button.js.map +1 -1
- package/dist/components/ui/modal/components/modal-content.js +3 -3
- package/dist/components/ui/modal/components/modal-content.js.map +1 -1
- package/dist/components/ui/modal/modal.js +8 -2
- package/dist/components/ui/modal/modal.js.map +1 -1
- package/dist/components/ui/modal/modal.types.d.ts +2 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/use-click-outside/index.d.ts +1 -0
- package/dist/hooks/use-click-outside/index.js +2 -0
- package/dist/hooks/use-click-outside/index.js.map +1 -0
- package/dist/hooks/use-click-outside/use-click-outside.d.ts +10 -0
- package/dist/hooks/use-click-outside/use-click-outside.js +26 -0
- package/dist/hooks/use-click-outside/use-click-outside.js.map +1 -0
- package/dist/hooks/use-dismiss/index.d.ts +1 -0
- package/dist/hooks/use-dismiss/index.js +2 -0
- package/dist/hooks/use-dismiss/index.js.map +1 -0
- package/dist/hooks/use-dismiss/use-dismiss.d.ts +13 -0
- package/dist/hooks/use-dismiss/use-dismiss.js +42 -0
- package/dist/hooks/use-dismiss/use-dismiss.js.map +1 -0
- package/dist/hooks/use-pathname/index.js.map +1 -0
- package/dist/hooks/use-pathname/use-pathname.d.ts +8 -0
- package/dist/{utils/hooks → hooks}/use-pathname/use-pathname.js +7 -0
- package/dist/hooks/use-pathname/use-pathname.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/internal-components/conditional-wrapper/conditional-wrapper.d.ts +2 -0
- package/dist/internal-components/conditional-wrapper/conditional-wrapper.js +12 -0
- package/dist/internal-components/conditional-wrapper/conditional-wrapper.js.map +1 -0
- package/dist/internal-components/conditional-wrapper/conditional-wrapper.types.d.ts +9 -0
- package/dist/internal-components/conditional-wrapper/conditional-wrapper.types.js +2 -0
- package/dist/internal-components/conditional-wrapper/conditional-wrapper.types.js.map +1 -0
- package/dist/internal-components/conditional-wrapper/index.d.ts +2 -0
- package/dist/internal-components/conditional-wrapper/index.js +2 -0
- package/dist/internal-components/conditional-wrapper/index.js.map +1 -0
- package/dist/internal-components/image/render-image.js.map +1 -1
- package/dist/internal-components/index.d.ts +3 -1
- package/dist/internal-components/index.js +3 -1
- package/dist/internal-components/index.js.map +1 -1
- package/dist/internal-components/optional-icon-button/index.d.ts +2 -0
- package/dist/internal-components/optional-icon-button/index.js +2 -0
- package/dist/internal-components/optional-icon-button/index.js.map +1 -0
- package/dist/internal-components/optional-icon-button/optional-icon-button.d.ts +2 -0
- package/dist/internal-components/optional-icon-button/optional-icon-button.js +9 -0
- package/dist/internal-components/optional-icon-button/optional-icon-button.js.map +1 -0
- package/dist/internal-components/optional-icon-button/optional-icon-button.types.d.ts +4 -0
- package/dist/internal-components/optional-icon-button/optional-icon-button.types.js +2 -0
- package/dist/internal-components/optional-icon-button/optional-icon-button.types.js.map +1 -0
- package/dist/styled-system/recipes/button.d.ts +2 -1
- package/dist/styled-system/recipes/button.mjs +114 -1
- package/dist/styled-system/styles.css +1 -1
- package/dist/styles/panda.css +1 -1
- package/dist/types/colors/utility-color-options.types.d.ts +2 -0
- package/dist/types/image/image.types.d.ts +2 -0
- package/dist/types/theme/theme.types.d.ts +24 -10
- package/dist/types/utility/deep-partial.types.d.ts +1 -0
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/theme-utilities/get-utility-icon-size/get-utility-icon-size.js +4 -4
- package/dist/utils/theme-utilities/get-utility-icon-size/get-utility-icon-size.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/form-elements/dropdown/dropdown.js +0 -83
- package/dist/components/form-elements/dropdown/dropdown.js.map +0 -1
- package/dist/components/form-elements/dropdown/dropdown.types.js.map +0 -1
- package/dist/components/form-elements/dropdown/index.js.map +0 -1
- package/dist/utils/hooks/index.d.ts +0 -2
- package/dist/utils/hooks/index.js +0 -3
- package/dist/utils/hooks/index.js.map +0 -1
- package/dist/utils/hooks/use-outside-close-click/index.d.ts +0 -1
- package/dist/utils/hooks/use-outside-close-click/index.js +0 -2
- package/dist/utils/hooks/use-outside-close-click/index.js.map +0 -1
- package/dist/utils/hooks/use-outside-close-click/use-outside-close-click.d.ts +0 -2
- package/dist/utils/hooks/use-outside-close-click/use-outside-close-click.js +0 -16
- package/dist/utils/hooks/use-outside-close-click/use-outside-close-click.js.map +0 -1
- package/dist/utils/hooks/use-pathname/index.js.map +0 -1
- package/dist/utils/hooks/use-pathname/use-pathname.d.ts +0 -1
- package/dist/utils/hooks/use-pathname/use-pathname.js.map +0 -1
- /package/dist/components/form-elements/{dropdown → dropdowns/dropdown}/dropdown.types.js +0 -0
- /package/dist/components/form-elements/{dropdown → dropdowns/dropdown}/index.d.ts +0 -0
- /package/dist/components/form-elements/{dropdown → dropdowns/dropdown}/index.js +0 -0
- /package/dist/{utils/hooks → hooks}/use-pathname/index.d.ts +0 -0
- /package/dist/{utils/hooks → hooks}/use-pathname/index.js +0 -0
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Component CSS is pre-generated using Panda CSS, so components render correctly s
|
|
|
52
52
|
|
|
53
53
|
### UI Elements
|
|
54
54
|
|
|
55
|
-
`Button` `Card` `Divider` `Icon` `IconButton` `Modal` `Toggle`
|
|
55
|
+
`Button` `Card` `Divider` `Icon` `IconButton` `Modal` `Toggle` `DotsLoader` `SpinLoader` `Skeleton` `SkeletonLayout`
|
|
56
56
|
|
|
57
57
|
### Media Display
|
|
58
58
|
|
|
@@ -72,4 +72,12 @@ Component CSS is pre-generated using Panda CSS, so components render correctly s
|
|
|
72
72
|
|
|
73
73
|
### Layout Components
|
|
74
74
|
|
|
75
|
-
`ColumnLayout` `Footer` `MasonryLayout`
|
|
75
|
+
`Container` `ColumnLayout` `Footer` `MasonryLayout`
|
|
76
|
+
|
|
77
|
+
### Data Display
|
|
78
|
+
|
|
79
|
+
`FilterControls` `SortControls`
|
|
80
|
+
|
|
81
|
+
## Hooks
|
|
82
|
+
|
|
83
|
+
`useClickOutside` `useDismiss` `usePathname`
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FilterControlsProps } from './filter-controls.types';
|
|
2
|
+
/**
|
|
3
|
+
* Renders a row of multi-select dropdown filters and a reset button.
|
|
4
|
+
* Designed to be used with `useFilterControls` via `filterControlsProps`.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const { filteredData, filterControlsProps } = useFilterControls({ data, fields });
|
|
8
|
+
* <FilterControls {...filterControlsProps} />
|
|
9
|
+
*/
|
|
10
|
+
export declare const FilterControls: <T>({ fields, activeFilters, onToggle, onClear, onClearAll, size, isDefault, }: FilterControlsProps<T>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cva } from '../../../styled-system/css';
|
|
4
|
+
import { Button } from '../../../components';
|
|
5
|
+
import { MultiDropdown } from '../../form-elements';
|
|
6
|
+
const styles = {
|
|
7
|
+
controlsContainer: cva({
|
|
8
|
+
base: {
|
|
9
|
+
display: 'flex',
|
|
10
|
+
flexDirection: 'row',
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
width: 'fit-content',
|
|
13
|
+
},
|
|
14
|
+
variants: {
|
|
15
|
+
size: {
|
|
16
|
+
sm: { gap: '1' },
|
|
17
|
+
md: { gap: '2' },
|
|
18
|
+
lg: { gap: '3' },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Renders a row of multi-select dropdown filters and a reset button.
|
|
25
|
+
* Designed to be used with `useFilterControls` via `filterControlsProps`.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const { filteredData, filterControlsProps } = useFilterControls({ data, fields });
|
|
29
|
+
* <FilterControls {...filterControlsProps} />
|
|
30
|
+
*/
|
|
31
|
+
export const FilterControls = ({ fields, activeFilters, onToggle, onClear, onClearAll, size = 'sm', isDefault, }) => {
|
|
32
|
+
const getActive = (key) => activeFilters.find((f) => f.key === key);
|
|
33
|
+
return (_jsxs("div", { className: styles.controlsContainer({ size }), children: [fields.map(({ key, label, icon, color: fieldColor, options }) => {
|
|
34
|
+
const active = getActive(key);
|
|
35
|
+
const selectedValues = (active?.values ?? []);
|
|
36
|
+
return (_jsx(MultiDropdown, { size: size, id: String(key), title: label, options: options, values: selectedValues, onToggle: (value) => onToggle(key, value), onClear: () => onClear(key), icon: icon, showLabel: false }, String(key)));
|
|
37
|
+
}), !isDefault && (_jsx(Button, { color: "text", onClick: onClearAll, size: size, text: true, "aria-label": "Clear all filters", children: "Reset" }))] }));
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=filter-controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-controls.js","sourceRoot":"","sources":["../../../../src/components/data-display/filter-controls/filter-controls.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AACb,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAE1C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,MAAM,GAAG;IACd,iBAAiB,EAAE,GAAG,CAAC;QACtB,IAAI,EAAE;YACL,OAAO,EAAE,MAAM;YACf,aAAa,EAAE,KAAK;YACpB,UAAU,EAAE,QAAQ;YACpB,KAAK,EAAE,aAAa;SACpB;QACD,QAAQ,EAAE;YACT,IAAI,EAAE;gBACL,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE;gBAChB,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE;gBAChB,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE;aAChB;SACD;KACD,CAAC;CACF,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAK,EAClC,MAAM,EACN,aAAa,EACb,QAAQ,EACR,OAAO,EACP,UAAU,EACV,IAAI,GAAG,IAAI,EACX,SAAS,GACe,EAAE,EAAE;IAC5B,MAAM,SAAS,GAAG,CAAC,GAAY,EAA+B,EAAE,CAC/D,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;IAE1C,OAAO,CACN,eAAK,SAAS,EAAE,MAAM,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,CAAC,aAChD,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,EAAE;gBAChE,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;gBAC9B,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,IAAI,EAAE,CAAwB,CAAC;gBAErE,OAAO,CACN,KAAC,aAAa,IACb,IAAI,EAAE,IAAI,EAEV,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EACf,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAsD,EAC/D,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAmB,CAAC,EACvD,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAC3B,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,KAAK,IARX,MAAM,CAAC,GAAG,CAAC,CASf,CACF,CAAC;YACH,CAAC,CAAC,EACD,CAAC,SAAS,IAAI,CACd,KAAC,MAAM,IACN,KAAK,EAAC,MAAM,EACZ,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,IAAI,EACV,IAAI,sBACO,mBAAmB,sBAGtB,CACT,IACI,CACN,CAAC;AACH,CAAC,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { IconNames } from '../../../components/ui';
|
|
2
|
+
import { UtilityColorOptions, UtilitySizeOptions } from '../../../types';
|
|
3
|
+
/** A single selectable option within a filter field */
|
|
4
|
+
export type FilterOption<V> = {
|
|
5
|
+
value: V;
|
|
6
|
+
label: string;
|
|
7
|
+
};
|
|
8
|
+
/** Configuration for a single filterable field */
|
|
9
|
+
export type FilterField<T> = {
|
|
10
|
+
/** Key of the data object this field filters on */
|
|
11
|
+
key: keyof T;
|
|
12
|
+
/** Display label for the filter */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Optional icon displayed in the filter control */
|
|
15
|
+
icon?: IconNames;
|
|
16
|
+
/** Optional color accent for the filter control */
|
|
17
|
+
color?: UtilityColorOptions;
|
|
18
|
+
/** Selectable options for this field. If omitted, options are derived from the data */
|
|
19
|
+
options?: FilterOption<T[keyof T]>[];
|
|
20
|
+
};
|
|
21
|
+
/** `FilterField` with `options` guaranteed to be present after resolution */
|
|
22
|
+
export type ResolvedFilterField<T> = Omit<FilterField<T>, 'options'> & {
|
|
23
|
+
options: FilterOption<T[keyof T]>[];
|
|
24
|
+
};
|
|
25
|
+
/** A currently active filter with one or more selected values */
|
|
26
|
+
export type ActiveFilter<T> = {
|
|
27
|
+
/** The field being filtered */
|
|
28
|
+
key: keyof T;
|
|
29
|
+
/** Currently selected values for this field */
|
|
30
|
+
values: T[keyof T][];
|
|
31
|
+
};
|
|
32
|
+
/** Configuration passed to `useFilterControls` to set up filtering behavior */
|
|
33
|
+
export type FilterControlsConfig<T> = {
|
|
34
|
+
/** Dataset to filter */
|
|
35
|
+
data: T[];
|
|
36
|
+
/** Fields available for filtering */
|
|
37
|
+
fields: FilterField<T>[];
|
|
38
|
+
/** Filters applied on initial render */
|
|
39
|
+
defaultFilters?: ActiveFilter<T>[];
|
|
40
|
+
/** Whether multiple active filters combine with AND or OR logic @default `'and'` */
|
|
41
|
+
mode?: 'and' | 'or';
|
|
42
|
+
};
|
|
43
|
+
/** Return value of `useFilterControls` — filtered data plus props to pass to `FilterControls` */
|
|
44
|
+
export type FilterControlsData<T> = {
|
|
45
|
+
/** The dataset after all active filters are applied */
|
|
46
|
+
filteredData: T[];
|
|
47
|
+
/** Currently active filters */
|
|
48
|
+
activeFilters: ActiveFilter<T>[];
|
|
49
|
+
/** Toggles a single value on or off for a given field */
|
|
50
|
+
toggleFilter: (key: keyof T, value: T[keyof T]) => void;
|
|
51
|
+
/** Clears all selected values for a given field */
|
|
52
|
+
clearFilter: (key: keyof T) => void;
|
|
53
|
+
/** Clears all active filters across all fields */
|
|
54
|
+
clearAllFilters: () => void;
|
|
55
|
+
/** Pre-assembled props to spread directly onto `FilterControls` */
|
|
56
|
+
filterControlsProps: FilterControlsProps<T>;
|
|
57
|
+
};
|
|
58
|
+
export type FilterControlsProps<T> = {
|
|
59
|
+
/** Size variant for the filter controls @default `'sm'` */
|
|
60
|
+
size?: UtilitySizeOptions;
|
|
61
|
+
/** Color accent applied to the controls */
|
|
62
|
+
color?: UtilityColorOptions;
|
|
63
|
+
/** Resolved filter fields with guaranteed options */
|
|
64
|
+
fields: ResolvedFilterField<T>[];
|
|
65
|
+
/** Currently active filters */
|
|
66
|
+
activeFilters: ActiveFilter<T>[];
|
|
67
|
+
/** Hides the reset button when true, indicating filters are in their default state */
|
|
68
|
+
isDefault?: boolean;
|
|
69
|
+
/** Called when a filter value is toggled */
|
|
70
|
+
onToggle: (key: keyof T, value: T[keyof T]) => void;
|
|
71
|
+
/** Called when all values for a field are cleared */
|
|
72
|
+
onClear: (key: keyof T) => void;
|
|
73
|
+
/** Called when all active filters are cleared */
|
|
74
|
+
onClearAll: () => void;
|
|
75
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-controls.types.js","sourceRoot":"","sources":["../../../../src/components/data-display/filter-controls/filter-controls.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/data-display/filter-controls/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FilterControlsConfig, FilterControlsData } from './filter-controls.types';
|
|
2
|
+
/**
|
|
3
|
+
* Manages filter state for a dataset and returns filtered data alongside
|
|
4
|
+
* pre-assembled props for `FilterControls`.
|
|
5
|
+
*
|
|
6
|
+
* Options for each field are derived automatically from the data if not provided.
|
|
7
|
+
* Supports AND and OR filter logic across multiple active fields.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const { filteredData, filterControlsProps } = useFilterControls({
|
|
11
|
+
* data: products,
|
|
12
|
+
* fields: [{ key: 'category', label: 'Category' }],
|
|
13
|
+
* mode: 'or',
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* return (
|
|
17
|
+
* <>
|
|
18
|
+
* <FilterControls {...filterControlsProps} />
|
|
19
|
+
* <ProductList data={filteredData} />
|
|
20
|
+
* </>
|
|
21
|
+
* );
|
|
22
|
+
*/
|
|
23
|
+
export declare const useFilterControls: <T>({ data, fields, defaultFilters, mode, }: FilterControlsConfig<T>) => FilterControlsData<T>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useMemo } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Manages filter state for a dataset and returns filtered data alongside
|
|
5
|
+
* pre-assembled props for `FilterControls`.
|
|
6
|
+
*
|
|
7
|
+
* Options for each field are derived automatically from the data if not provided.
|
|
8
|
+
* Supports AND and OR filter logic across multiple active fields.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const { filteredData, filterControlsProps } = useFilterControls({
|
|
12
|
+
* data: products,
|
|
13
|
+
* fields: [{ key: 'category', label: 'Category' }],
|
|
14
|
+
* mode: 'or',
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* return (
|
|
18
|
+
* <>
|
|
19
|
+
* <FilterControls {...filterControlsProps} />
|
|
20
|
+
* <ProductList data={filteredData} />
|
|
21
|
+
* </>
|
|
22
|
+
* );
|
|
23
|
+
*/
|
|
24
|
+
export const useFilterControls = ({ data, fields, defaultFilters, mode = 'and', }) => {
|
|
25
|
+
const [activeFilters, setActiveFilters] = useState(defaultFilters ?? []);
|
|
26
|
+
const resolvedFields = useMemo(() => fields.map((field) => {
|
|
27
|
+
if (field.options)
|
|
28
|
+
return field;
|
|
29
|
+
const unique = [...new Set(data.map((row) => row[field.key]))];
|
|
30
|
+
const options = unique
|
|
31
|
+
.map((value) => ({ value, label: String(value) }))
|
|
32
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
33
|
+
return { ...field, options };
|
|
34
|
+
}), [data, fields]);
|
|
35
|
+
const isDefault = useMemo(() => {
|
|
36
|
+
if (!defaultFilters)
|
|
37
|
+
return activeFilters.length === 0;
|
|
38
|
+
if (activeFilters.length !== defaultFilters.length)
|
|
39
|
+
return false; // was comparing activeFilters.length to itself
|
|
40
|
+
return activeFilters.every((f, i) => f.key === defaultFilters[i].key &&
|
|
41
|
+
f.values.length === defaultFilters[i].values.length &&
|
|
42
|
+
f.values.every((v) => defaultFilters[i].values.includes(v)));
|
|
43
|
+
}, [activeFilters, defaultFilters]);
|
|
44
|
+
const toggleFilter = (key, value) => {
|
|
45
|
+
setActiveFilters((prev) => {
|
|
46
|
+
const existing = prev.find((f) => f.key === key);
|
|
47
|
+
if (!existing) {
|
|
48
|
+
return [...prev, { key, values: [value] }];
|
|
49
|
+
}
|
|
50
|
+
const hasValue = existing.values.includes(value);
|
|
51
|
+
if (hasValue) {
|
|
52
|
+
const nextValues = existing.values.filter((v) => v !== value);
|
|
53
|
+
// remove the filter entry entirely if no values remain
|
|
54
|
+
if (nextValues.length === 0)
|
|
55
|
+
return prev.filter((f) => f.key !== key);
|
|
56
|
+
return prev.map((f) => (f.key === key ? { ...f, values: nextValues } : f));
|
|
57
|
+
}
|
|
58
|
+
return prev.map((f) => (f.key === key ? { ...f, values: [...f.values, value] } : f));
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const clearFilter = (key) => setActiveFilters((prev) => prev.filter((f) => f.key !== key));
|
|
62
|
+
const clearAllFilters = () => setActiveFilters(defaultFilters ?? []);
|
|
63
|
+
const filteredData = useMemo(() => {
|
|
64
|
+
if (activeFilters.length === 0)
|
|
65
|
+
return data;
|
|
66
|
+
return data.filter((row) => {
|
|
67
|
+
if (mode === 'and') {
|
|
68
|
+
return activeFilters.every((f) => f.values.includes(row[f.key]));
|
|
69
|
+
}
|
|
70
|
+
return activeFilters.some((f) => f.values.includes(row[f.key]));
|
|
71
|
+
});
|
|
72
|
+
}, [data, activeFilters, mode]);
|
|
73
|
+
return {
|
|
74
|
+
filteredData,
|
|
75
|
+
activeFilters,
|
|
76
|
+
toggleFilter,
|
|
77
|
+
clearFilter,
|
|
78
|
+
clearAllFilters,
|
|
79
|
+
filterControlsProps: {
|
|
80
|
+
fields: resolvedFields,
|
|
81
|
+
activeFilters,
|
|
82
|
+
onToggle: toggleFilter,
|
|
83
|
+
onClear: clearFilter,
|
|
84
|
+
onClearAll: clearAllFilters,
|
|
85
|
+
isDefault,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=use-filter-controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-filter-controls.js","sourceRoot":"","sources":["../../../../src/components/data-display/filter-controls/use-filter-controls.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/data-display/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/data-display/sort-controls/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SortControlsProps } from './sort-controls.types';
|
|
2
|
+
/**
|
|
3
|
+
* Renders a row of sort toggle buttons and a reset button.
|
|
4
|
+
* Each button cycles through asc → desc → off with a directional arrow indicator.
|
|
5
|
+
* Designed to be used with `useSortControls` via `sortControlsProps`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const { sortedData, sortControlsProps } = useSortControls({ data, fields });
|
|
9
|
+
* <SortControls {...sortControlsProps} />
|
|
10
|
+
*/
|
|
11
|
+
export declare const SortControls: <T>({ color, fields, activeSort, onToggle, onClear, size, isDefault, }: SortControlsProps<T>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cva } from '../../../styled-system/css';
|
|
4
|
+
import { Button, Icon } from '../../../components';
|
|
5
|
+
import { OptionalIconButton } from '../../../internal-components';
|
|
6
|
+
const styles = {
|
|
7
|
+
controlsContainer: cva({
|
|
8
|
+
base: {
|
|
9
|
+
display: 'flex',
|
|
10
|
+
flexDirection: 'row',
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
},
|
|
13
|
+
variants: {
|
|
14
|
+
size: {
|
|
15
|
+
sm: {
|
|
16
|
+
gap: '1',
|
|
17
|
+
},
|
|
18
|
+
md: {
|
|
19
|
+
gap: '2',
|
|
20
|
+
},
|
|
21
|
+
lg: {
|
|
22
|
+
gap: '3',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Renders a row of sort toggle buttons and a reset button.
|
|
30
|
+
* Each button cycles through asc → desc → off with a directional arrow indicator.
|
|
31
|
+
* Designed to be used with `useSortControls` via `sortControlsProps`.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* const { sortedData, sortControlsProps } = useSortControls({ data, fields });
|
|
35
|
+
* <SortControls {...sortControlsProps} />
|
|
36
|
+
*/
|
|
37
|
+
export const SortControls = ({ color = 'tertiary', fields, activeSort, onToggle, onClear, size = 'sm', isDefault, }) => {
|
|
38
|
+
const getState = (key) => activeSort.find((s) => s.key === key);
|
|
39
|
+
return (_jsxs("div", { className: styles.controlsContainer({ size }), children: [fields.map(({ key, label, icon, color: fieldColor }) => {
|
|
40
|
+
const state = getState(key);
|
|
41
|
+
const buttonProps = {
|
|
42
|
+
color: fieldColor ?? color,
|
|
43
|
+
size,
|
|
44
|
+
onClick: () => onToggle(key),
|
|
45
|
+
name: icon,
|
|
46
|
+
};
|
|
47
|
+
return (_jsxs(OptionalIconButton, { ...buttonProps, children: [label, state && _jsx(SortIndicator, { direction: state.direction })] }, String(key)));
|
|
48
|
+
}), !isDefault && (_jsx(Button, { color: "text", onClick: onClear, size: size, text: true, "aria-label": "Clear all sorting", children: "Reset" }))] }));
|
|
49
|
+
};
|
|
50
|
+
const SortIndicator = ({ direction }) => direction === 'asc' ? _jsx(Icon, { size: 8, name: "ArrowUp" }) : _jsx(Icon, { size: 8, name: "ArrowDown" });
|
|
51
|
+
//# sourceMappingURL=sort-controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sort-controls.js","sourceRoot":"","sources":["../../../../src/components/data-display/sort-controls/sort-controls.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AACb,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAE1C,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,MAAM,GAAG;IACd,iBAAiB,EAAE,GAAG,CAAC;QACtB,IAAI,EAAE;YACL,OAAO,EAAE,MAAM;YACf,aAAa,EAAE,KAAK;YACpB,UAAU,EAAE,QAAQ;SACpB;QACD,QAAQ,EAAE;YACT,IAAI,EAAE;gBACL,EAAE,EAAE;oBACH,GAAG,EAAE,GAAG;iBACR;gBACD,EAAE,EAAE;oBACH,GAAG,EAAE,GAAG;iBACR;gBACD,EAAE,EAAE;oBACH,GAAG,EAAE,GAAG;iBACR;aACD;SACD;KACD,CAAC;CACF,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAK,EAChC,KAAK,GAAG,UAAU,EAClB,MAAM,EACN,UAAU,EACV,QAAQ,EACR,OAAO,EACP,IAAI,GAAG,IAAI,EACX,SAAS,GACa,EAAE,EAAE;IAC1B,MAAM,QAAQ,GAAG,CAAC,GAAY,EAA6B,EAAE,CAC5D,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;IAEvC,OAAO,CACN,eAAK,SAAS,EAAE,MAAM,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,CAAC,aAChD,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE;gBACvD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAE5B,MAAM,WAAW,GAAG;oBACnB,KAAK,EAAE,UAAU,IAAI,KAAK;oBAC1B,IAAI;oBACJ,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC;oBAC5B,IAAI,EAAE,IAAI;iBACV,CAAC;gBAEF,OAAO,CACN,MAAC,kBAAkB,OAAuB,WAAW,aACnD,KAAK,EACL,KAAK,IAAI,KAAC,aAAa,IAAC,SAAS,EAAE,KAAK,CAAC,SAAS,GAAI,KAF/B,MAAM,CAAC,GAAG,CAAC,CAGf,CACrB,CAAC;YACH,CAAC,CAAC,EAED,CAAC,SAAS,IAAI,CACd,KAAC,MAAM,IACN,KAAK,EAAC,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,IAAI,sBACO,mBAAmB,sBAGtB,CACT,IACI,CACN,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,EAAE,SAAS,EAAiC,EAAE,EAAE,CACtE,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,KAAC,IAAI,IAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAC,SAAS,GAAG,CAAC,CAAC,CAAC,KAAC,IAAI,IAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAC,WAAW,GAAG,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { IconNames } from '../../../components/ui';
|
|
2
|
+
import { UtilityColorOptions, UtilitySizeOptions } from '../../../types';
|
|
3
|
+
export type SortDirection = 'asc' | 'desc';
|
|
4
|
+
/** Configuration for a single sortable field */
|
|
5
|
+
export type SortField<T> = {
|
|
6
|
+
/** Key of the data object this field sorts on */
|
|
7
|
+
key: keyof T;
|
|
8
|
+
/** Display label for the sort button */
|
|
9
|
+
label: string;
|
|
10
|
+
/** Optional icon displayed in the sort button */
|
|
11
|
+
icon?: IconNames;
|
|
12
|
+
/** Optional color accent for the sort button */
|
|
13
|
+
color?: UtilityColorOptions;
|
|
14
|
+
/** Custom sort order. Accepts an explicit value array or a comparator function */
|
|
15
|
+
sortOrder?: T[keyof T][] | ((a: T[keyof T], b: T[keyof T]) => number);
|
|
16
|
+
};
|
|
17
|
+
/** A currently active sort with a direction */
|
|
18
|
+
export type ActiveSort<T> = {
|
|
19
|
+
/** The field being sorted */
|
|
20
|
+
key: keyof T;
|
|
21
|
+
direction: SortDirection;
|
|
22
|
+
};
|
|
23
|
+
/** Configuration passed to `useSortControls` to set up sorting behavior */
|
|
24
|
+
export type SortControlsConfig<T> = {
|
|
25
|
+
/** Dataset to sort */
|
|
26
|
+
data: T[];
|
|
27
|
+
/** Fields available for sorting */
|
|
28
|
+
fields: SortField<T>[];
|
|
29
|
+
/** Sort applied on initial render */
|
|
30
|
+
defaultSort?: ActiveSort<T>[];
|
|
31
|
+
/** Allows multiple fields to be sorted simultaneously @default `false` */
|
|
32
|
+
multi?: boolean;
|
|
33
|
+
};
|
|
34
|
+
/** Return value of `useSortControls` — sorted data plus props to pass to `SortControls` */
|
|
35
|
+
export type SortControlsData<T> = {
|
|
36
|
+
/** The dataset after all active sorts are applied */
|
|
37
|
+
sortedData: T[];
|
|
38
|
+
/** Currently active sorts */
|
|
39
|
+
activeSort: ActiveSort<T>[];
|
|
40
|
+
/** Cycles a field through asc → desc → off */
|
|
41
|
+
toggleSort: (key: keyof T) => void;
|
|
42
|
+
/** Resets all active sorts to `defaultSort` */
|
|
43
|
+
clearSort: () => void;
|
|
44
|
+
/** Pre-assembled props to spread directly onto `SortControls` */
|
|
45
|
+
sortControlsProps: SortControlsProps<T>;
|
|
46
|
+
};
|
|
47
|
+
export type SortControlsProps<T> = {
|
|
48
|
+
/** Size variant for the sort controls @default `'sm'` */
|
|
49
|
+
size?: UtilitySizeOptions;
|
|
50
|
+
/** Fallback color for sort buttons when a field has no color set @default `'tertiary'` */
|
|
51
|
+
color?: UtilityColorOptions;
|
|
52
|
+
/** Fields available for sorting */
|
|
53
|
+
fields: SortField<T>[];
|
|
54
|
+
/** Currently active sorts */
|
|
55
|
+
activeSort: ActiveSort<T>[];
|
|
56
|
+
/** Called when a sort field button is clicked */
|
|
57
|
+
onToggle: (key: keyof T) => void;
|
|
58
|
+
/** Called when the reset button is clicked */
|
|
59
|
+
onClear: () => void;
|
|
60
|
+
/** Hides the reset button when true, indicating sort is in its default state */
|
|
61
|
+
isDefault?: boolean;
|
|
62
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sort-controls.types.js","sourceRoot":"","sources":["../../../../src/components/data-display/sort-controls/sort-controls.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SortControlsConfig, SortControlsData } from './sort-controls.types';
|
|
2
|
+
/**
|
|
3
|
+
* Manages sort state for a dataset and returns sorted data alongside
|
|
4
|
+
* pre-assembled props for `SortControls`.
|
|
5
|
+
*
|
|
6
|
+
* Supports single and multi-field sorting, custom sort orders, and comparator functions.
|
|
7
|
+
* Each field cycles through asc → desc → off on toggle.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const { sortedData, sortControlsProps } = useSortControls({
|
|
11
|
+
* data: products,
|
|
12
|
+
* fields: [{ key: 'price', label: 'Price' }],
|
|
13
|
+
* multi: true,
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* return (
|
|
17
|
+
* <>
|
|
18
|
+
* <SortControls {...sortControlsProps} />
|
|
19
|
+
* <ProductList data={sortedData} />
|
|
20
|
+
* </>
|
|
21
|
+
* );
|
|
22
|
+
*/
|
|
23
|
+
export declare const useSortControls: <T>({ data, fields, multi, defaultSort, }: SortControlsConfig<T>) => SortControlsData<T>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useMemo } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Manages sort state for a dataset and returns sorted data alongside
|
|
5
|
+
* pre-assembled props for `SortControls`.
|
|
6
|
+
*
|
|
7
|
+
* Supports single and multi-field sorting, custom sort orders, and comparator functions.
|
|
8
|
+
* Each field cycles through asc → desc → off on toggle.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const { sortedData, sortControlsProps } = useSortControls({
|
|
12
|
+
* data: products,
|
|
13
|
+
* fields: [{ key: 'price', label: 'Price' }],
|
|
14
|
+
* multi: true,
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* return (
|
|
18
|
+
* <>
|
|
19
|
+
* <SortControls {...sortControlsProps} />
|
|
20
|
+
* <ProductList data={sortedData} />
|
|
21
|
+
* </>
|
|
22
|
+
* );
|
|
23
|
+
*/
|
|
24
|
+
export const useSortControls = ({ data, fields, multi = false, defaultSort, }) => {
|
|
25
|
+
const [activeSort, setActiveSort] = useState(defaultSort ?? []);
|
|
26
|
+
const toggleSort = (key) => {
|
|
27
|
+
setActiveSort((prev) => {
|
|
28
|
+
const existing = prev.find((s) => s.key === key);
|
|
29
|
+
if (!existing) {
|
|
30
|
+
const next = { key, direction: 'asc' };
|
|
31
|
+
return multi ? [...prev, next] : [next];
|
|
32
|
+
}
|
|
33
|
+
if (existing.direction === 'asc') {
|
|
34
|
+
// flip to desc
|
|
35
|
+
return prev.map((s) => s.key === key ? { ...s, direction: 'desc' } : s);
|
|
36
|
+
}
|
|
37
|
+
// remove (was desc, now off)
|
|
38
|
+
return prev.filter((s) => s.key !== key);
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const clearSort = () => setActiveSort(defaultSort ?? []);
|
|
42
|
+
const sortedData = useMemo(() => {
|
|
43
|
+
if (activeSort.length === 0)
|
|
44
|
+
return data;
|
|
45
|
+
return [...data].sort((a, b) => {
|
|
46
|
+
for (const { key, direction } of activeSort) {
|
|
47
|
+
const aVal = a[key];
|
|
48
|
+
const bVal = b[key];
|
|
49
|
+
const dir = direction === 'asc' ? 1 : -1;
|
|
50
|
+
const field = fields.find((f) => f.key === key);
|
|
51
|
+
const { sortOrder } = field ?? {};
|
|
52
|
+
let cmp = 0;
|
|
53
|
+
if (typeof sortOrder === 'function') {
|
|
54
|
+
cmp = sortOrder(aVal, bVal);
|
|
55
|
+
}
|
|
56
|
+
else if (Array.isArray(sortOrder)) {
|
|
57
|
+
const aIdx = sortOrder.indexOf(aVal);
|
|
58
|
+
const bIdx = sortOrder.indexOf(bVal);
|
|
59
|
+
// unknowns sort to the end
|
|
60
|
+
const aNorm = aIdx === -1 ? Infinity : aIdx;
|
|
61
|
+
const bNorm = bIdx === -1 ? Infinity : bIdx;
|
|
62
|
+
cmp = aNorm - bNorm;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
66
|
+
}
|
|
67
|
+
if (cmp !== 0)
|
|
68
|
+
return cmp * dir;
|
|
69
|
+
}
|
|
70
|
+
return 0;
|
|
71
|
+
});
|
|
72
|
+
}, [data, activeSort, fields]);
|
|
73
|
+
const isDefault = useMemo(() => {
|
|
74
|
+
if (!defaultSort)
|
|
75
|
+
return activeSort.length === 0;
|
|
76
|
+
if (activeSort.length !== defaultSort.length)
|
|
77
|
+
return false;
|
|
78
|
+
return activeSort.every((s, i) => s.key === defaultSort[i].key && s.direction === defaultSort[i].direction);
|
|
79
|
+
}, [activeSort, defaultSort]);
|
|
80
|
+
return {
|
|
81
|
+
sortedData,
|
|
82
|
+
activeSort,
|
|
83
|
+
toggleSort,
|
|
84
|
+
clearSort,
|
|
85
|
+
sortControlsProps: {
|
|
86
|
+
fields,
|
|
87
|
+
activeSort,
|
|
88
|
+
onToggle: toggleSort,
|
|
89
|
+
onClear: clearSort,
|
|
90
|
+
isDefault,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=use-sort-controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-sort-controls.js","sourceRoot":"","sources":["../../../../src/components/data-display/sort-controls/use-sort-controls.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,4 +10,4 @@ import { DropdownProps } from './dropdown.types';
|
|
|
10
10
|
* onSelect={(val) => setStatus(val)}
|
|
11
11
|
* />
|
|
12
12
|
*/
|
|
13
|
-
export declare const Dropdown: ({ id, title, value, options, onSelect, placeholder, }: DropdownProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare const Dropdown: ({ id, title, value, options, onSelect, placeholder, size, }: DropdownProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { cva } from '../../../../styled-system/css';
|
|
5
|
+
import { DropdownBase } from '../dropdown-base/dropdown-base';
|
|
6
|
+
const itemStyles = cva({
|
|
7
|
+
base: {
|
|
8
|
+
cursor: 'pointer',
|
|
9
|
+
paddingX: '4',
|
|
10
|
+
paddingY: '2',
|
|
11
|
+
_hover: { backgroundColor: 'surface' },
|
|
12
|
+
},
|
|
13
|
+
variants: {
|
|
14
|
+
isSelected: {
|
|
15
|
+
true: { backgroundColor: 'elevated' },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Single-select dropdown with an option list and outside-click dismissal.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* <Dropdown
|
|
24
|
+
* title="Status"
|
|
25
|
+
* value={status}
|
|
26
|
+
* options={[{ label: 'Active', value: 'active' }, { label: 'Inactive', value: 'inactive' }]}
|
|
27
|
+
* onSelect={(val) => setStatus(val)}
|
|
28
|
+
* />
|
|
29
|
+
*/
|
|
30
|
+
export const Dropdown = ({ id, title, value, options, onSelect, placeholder = 'Select an option...', size, }) => {
|
|
31
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
32
|
+
const selected = options.find((opt) => opt.value === value);
|
|
33
|
+
const handleSelect = (option) => {
|
|
34
|
+
onSelect(option.value);
|
|
35
|
+
setIsOpen(false);
|
|
36
|
+
};
|
|
37
|
+
return (_jsx(DropdownBase, { id: id, title: title, options: options, isOpen: isOpen, onToggle: () => setIsOpen((prev) => !prev), onClose: () => {
|
|
38
|
+
setIsOpen(false);
|
|
39
|
+
}, triggerLabel: selected ? selected.label : placeholder, renderItem: (option, index) => (_jsx("li", { className: itemStyles({ isSelected: option.value === selected?.value }), onClick: () => handleSelect(option), children: option.label }, index)), size: size }));
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=dropdown.js.map
|