@wallarm-org/design-system 1.28.2-rc-fix-table-window-scroll-root.2 → 1.29.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 (80) hide show
  1. package/dist/components/FilterDropdown/FilterDropdown.d.ts +58 -0
  2. package/dist/components/FilterDropdown/FilterDropdown.js +168 -0
  3. package/dist/components/FilterDropdown/FilterDropdownAllOption.d.ts +14 -0
  4. package/dist/components/FilterDropdown/FilterDropdownAllOption.js +25 -0
  5. package/dist/components/FilterDropdown/FilterDropdownClear.d.ts +12 -0
  6. package/dist/components/FilterDropdown/FilterDropdownClear.js +33 -0
  7. package/dist/components/FilterDropdown/FilterDropdownContent.d.ts +13 -0
  8. package/dist/components/FilterDropdown/FilterDropdownContent.js +57 -0
  9. package/dist/components/FilterDropdown/FilterDropdownContext.d.ts +60 -0
  10. package/dist/components/FilterDropdown/FilterDropdownContext.js +21 -0
  11. package/dist/components/FilterDropdown/FilterDropdownEmpty.d.ts +9 -0
  12. package/dist/components/FilterDropdown/FilterDropdownEmpty.js +21 -0
  13. package/dist/components/FilterDropdown/FilterDropdownFooter.d.ts +9 -0
  14. package/dist/components/FilterDropdown/FilterDropdownFooter.js +19 -0
  15. package/dist/components/FilterDropdown/FilterDropdownFooterClear.d.ts +9 -0
  16. package/dist/components/FilterDropdown/FilterDropdownFooterClear.js +40 -0
  17. package/dist/components/FilterDropdown/FilterDropdownGroup.d.ts +5 -0
  18. package/dist/components/FilterDropdown/FilterDropdownGroup.js +8 -0
  19. package/dist/components/FilterDropdown/FilterDropdownGroupLabel.d.ts +4 -0
  20. package/dist/components/FilterDropdown/FilterDropdownGroupLabel.js +8 -0
  21. package/dist/components/FilterDropdown/FilterDropdownOption.d.ts +15 -0
  22. package/dist/components/FilterDropdown/FilterDropdownOption.js +32 -0
  23. package/dist/components/FilterDropdown/FilterDropdownSearch.d.ts +12 -0
  24. package/dist/components/FilterDropdown/FilterDropdownSearch.js +38 -0
  25. package/dist/components/FilterDropdown/FilterDropdownSelected.d.ts +16 -0
  26. package/dist/components/FilterDropdown/FilterDropdownSelected.js +38 -0
  27. package/dist/components/FilterDropdown/FilterDropdownTrigger.d.ts +16 -0
  28. package/dist/components/FilterDropdown/FilterDropdownTrigger.js +139 -0
  29. package/dist/components/FilterDropdown/classes.d.ts +16 -0
  30. package/dist/components/FilterDropdown/classes.js +43 -0
  31. package/dist/components/FilterDropdown/index.d.ts +15 -0
  32. package/dist/components/FilterDropdown/index.js +15 -0
  33. package/dist/components/FilterDropdown/lib/index.d.ts +1 -0
  34. package/dist/components/FilterDropdown/lib/index.js +1 -0
  35. package/dist/components/FilterDropdown/lib/values.d.ts +59 -0
  36. package/dist/components/FilterDropdown/lib/values.js +58 -0
  37. package/dist/components/Select/Select.d.ts +1 -1
  38. package/dist/components/Select/SelectContent.d.ts +2 -1
  39. package/dist/components/Select/SelectContent.js +3 -2
  40. package/dist/components/Select/SelectEmptyState.d.ts +1 -0
  41. package/dist/components/Select/SelectEmptyState.js +2 -1
  42. package/dist/components/Select/SelectFooter.d.ts +7 -2
  43. package/dist/components/Select/SelectFooter.js +6 -3
  44. package/dist/components/Select/SelectGroup.d.ts +8 -2
  45. package/dist/components/Select/SelectGroup.js +3 -2
  46. package/dist/components/Select/SelectGroupLabel.d.ts +5 -2
  47. package/dist/components/Select/SelectGroupLabel.js +2 -2
  48. package/dist/components/Select/SelectOption.js +1 -1
  49. package/dist/components/Select/SelectOptionHint.d.ts +11 -0
  50. package/dist/components/Select/SelectOptionHint.js +15 -0
  51. package/dist/components/Select/SelectPositioner.d.ts +13 -3
  52. package/dist/components/Select/SelectPositioner.js +2 -1
  53. package/dist/components/Select/SelectSearchInput.d.ts +5 -2
  54. package/dist/components/Select/SelectSearchInput.js +11 -1
  55. package/dist/components/Select/classes.d.ts +3 -0
  56. package/dist/components/Select/classes.js +13 -0
  57. package/dist/components/Select/index.d.ts +4 -2
  58. package/dist/components/Select/index.js +2 -0
  59. package/dist/components/Table/TableBody/TableBodyVirtualizedCore.js +1 -1
  60. package/dist/components/Table/TableBody/TableBodyVirtualizedWindow.js +8 -26
  61. package/dist/components/Table/TableInner/TableInnerWindow.js +2 -7
  62. package/dist/components/Table/hooks/index.d.ts +0 -1
  63. package/dist/components/Table/hooks/index.js +0 -1
  64. package/dist/components/Table/hooks/infiniteScroll/useInfiniteScroll.d.ts +2 -4
  65. package/dist/components/Table/hooks/infiniteScroll/useInfiniteScroll.js +1 -4
  66. package/dist/components/Table/hooks/infiniteScroll/usePrependScrollAnchor.d.ts +1 -4
  67. package/dist/components/Table/hooks/infiniteScroll/usePrependScrollAnchor.js +11 -13
  68. package/dist/components/Table/hooks/infiniteScroll/useScrollEdge.d.ts +1 -4
  69. package/dist/components/Table/hooks/infiniteScroll/useScrollEdge.js +18 -6
  70. package/dist/components/Table/lib/index.d.ts +0 -1
  71. package/dist/components/Table/lib/index.js +0 -1
  72. package/dist/components/Table/types.d.ts +1 -4
  73. package/dist/index.d.ts +2 -1
  74. package/dist/index.js +2 -1
  75. package/dist/metadata/components.json +6317 -1145
  76. package/package.json +1 -1
  77. package/dist/components/Table/hooks/useWindowScrollRoot.d.ts +0 -14
  78. package/dist/components/Table/hooks/useWindowScrollRoot.js +0 -35
  79. package/dist/components/Table/lib/scrollRoot.d.ts +0 -24
  80. package/dist/components/Table/lib/scrollRoot.js +0 -22
@@ -0,0 +1,58 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type CollectionItem, type ListCollection } from '@ark-ui/react/collection';
3
+ import type { TestableProps } from '../../utils/testId';
4
+ import { type SelectProps } from '../Select/Select';
5
+ import { type UseSelectSearchOptions } from '../Select/useSelectSearch';
6
+ export interface FilterDropdownValueChangeDetails<T extends CollectionItem = CollectionItem> {
7
+ /** Picked values; `[]` means unset («All» in single mode). */
8
+ value: string[];
9
+ items: T[];
10
+ }
11
+ export interface FilterDropdownOpenChangeDetails {
12
+ open: boolean;
13
+ value: string[];
14
+ }
15
+ /**
16
+ * Select props FilterDropdown owns itself: the value model (alias and sentinel values must never
17
+ * reach the consumer), the highlight (reset when the query changes), and form wiring (the hidden
18
+ * select would submit internal values).
19
+ */
20
+ type FilterDropdownOmittedSelectProps = 'collection' | 'multiple' | 'value' | 'defaultValue' | 'onValueChange' | 'onOpenChange' | 'highlightedValue' | 'defaultHighlightedValue' | 'onHighlightChange' | 'onSelect' | 'deselectable' | 'name' | 'form' | 'loading' | 'children';
21
+ export type FilterDropdownProps<T extends CollectionItem = CollectionItem> = Omit<SelectProps<T>, FilterDropdownOmittedSelectProps> & TestableProps & {
22
+ /** Attribute name: trigger text in multi mode, the trigger's aria-label, «Clear {label}». */
23
+ label: string;
24
+ /**
25
+ * Single mode: trigger text while unset and the default text of `FilterDropdownAllOption`.
26
+ * @default 'All'
27
+ */
28
+ allLabel?: string;
29
+ /** Items to pick from. `groupBy` is supported. */
30
+ collection: ListCollection<T>;
31
+ /** Checkbox mode: several values, OR-ed, the menu stays open. */
32
+ multiple?: boolean;
33
+ /** Controlled picked values; `[]` = unset. */
34
+ value?: string[];
35
+ defaultValue?: string[];
36
+ onValueChange?: (details: FilterDropdownValueChangeDetails<T>) => void;
37
+ onOpenChange?: (details: FilterDropdownOpenChangeDetails) => void;
38
+ /**
39
+ * Item count from which the search field and (multi) the "Selected" section appear. Counts
40
+ * the collection's own items only.
41
+ * @default 8
42
+ */
43
+ searchThreshold?: number;
44
+ /** Overrides the default case-insensitive match on the item label. */
45
+ filterFn?: UseSelectSearchOptions<T>['filterFn'];
46
+ /**
47
+ * Multi mode: pin the picked values under «Selected» on top of the list (from
48
+ * `searchThreshold` items, hidden while searching). Render `FilterDropdownSelected` for it.
49
+ * @default true
50
+ */
51
+ selectedOnTop?: boolean;
52
+ children?: ReactNode;
53
+ };
54
+ export declare function FilterDropdown<T extends CollectionItem>({ label, allLabel, collection, multiple, value: valueProp, defaultValue, onValueChange, open: openProp, defaultOpen, onOpenChange, searchThreshold, filterFn, selectedOnTop, disabled, className, children, 'data-testid': testId, ...props }: FilterDropdownProps<T>): import("react").JSX.Element;
55
+ export declare namespace FilterDropdown {
56
+ var displayName: string;
57
+ }
58
+ export {};
@@ -0,0 +1,168 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useMemo, useRef, useState } from "react";
3
+ import { createListCollection } from "@ark-ui/react/collection";
4
+ import { cn } from "../../utils/cn.js";
5
+ import { Select } from "../Select/Select.js";
6
+ import { useSelectSearch } from "../Select/useSelectSearch.js";
7
+ import { FilterDropdownContext } from "./FilterDropdownContext.js";
8
+ import { createAliasItem, createAllItem, isSpecialItem, toConsumerValue, toInternalValue } from "./lib/index.js";
9
+ const DEFAULT_SEARCH_THRESHOLD = 8;
10
+ const EMPTY = [];
11
+ const FilterDropdown = ({ label, allLabel = 'All', collection, multiple = false, value: valueProp, defaultValue, onValueChange, open: openProp, defaultOpen, onOpenChange, searchThreshold = DEFAULT_SEARCH_THRESHOLD, filterFn, selectedOnTop = true, disabled = false, className, children, 'data-testid': testId, ...props })=>{
12
+ const [valueState, setValueState] = useState(defaultValue ?? EMPTY);
13
+ const value = valueProp ?? valueState;
14
+ const [openState, setOpenState] = useState(defaultOpen ?? false);
15
+ const open = openProp ?? openState;
16
+ const baseCollection = collection;
17
+ const searchOptions = useMemo(()=>({
18
+ filterFn: filterFn
19
+ }), [
20
+ filterFn
21
+ ]);
22
+ const { searchValue: query, onSearchChange, filteredCollection } = useSelectSearch(baseCollection, searchOptions);
23
+ const [snapshot, setSnapshot] = useState(value);
24
+ const [lockedWidth, setLockedWidth] = useState();
25
+ const [highlightState, setHighlightState] = useState(null);
26
+ const [resetHighlight, setResetHighlight] = useState(false);
27
+ const [prevOpen, setPrevOpen] = useState(open);
28
+ if (open !== prevOpen) {
29
+ setPrevOpen(open);
30
+ onSearchChange('');
31
+ setLockedWidth(void 0);
32
+ setResetHighlight(false);
33
+ if (open) setSnapshot(value);
34
+ }
35
+ const selectedValues = open ? snapshot : value;
36
+ const triggerRef = useRef(null);
37
+ const contentRef = useRef(null);
38
+ const isSearchable = baseCollection.size >= searchThreshold;
39
+ const isSearchActive = isSearchable && query.length > 0;
40
+ const showAll = !multiple && !isSearchActive;
41
+ const showSelected = selectedOnTop && multiple && isSearchable && !isSearchActive && selectedValues.length > 0;
42
+ const allItem = useMemo(()=>createAllItem(allLabel), [
43
+ allLabel
44
+ ]);
45
+ const groups = useMemo(()=>filteredCollection.group(), [
46
+ filteredCollection
47
+ ]);
48
+ const aliasItems = useMemo(()=>showSelected ? baseCollection.findMany(selectedValues).map((item)=>createAliasItem(item, baseCollection)) : [], [
49
+ showSelected,
50
+ baseCollection,
51
+ selectedValues
52
+ ]);
53
+ const arkCollection = useMemo(()=>createListCollection({
54
+ items: [
55
+ ...showAll ? [
56
+ allItem
57
+ ] : [],
58
+ ...aliasItems,
59
+ ...groups.flatMap(([, items])=>items)
60
+ ],
61
+ itemToValue: (item)=>isSpecialItem(item) ? item.value : baseCollection.getItemValue(item) ?? '',
62
+ itemToString: (item)=>isSpecialItem(item) ? item.label : baseCollection.stringifyItem(item) ?? '',
63
+ isItemDisabled: (item)=>{
64
+ if (!isSpecialItem(item)) return baseCollection.getItemDisabled(item);
65
+ return item.source ? baseCollection.getItemDisabled(item.source) : false;
66
+ }
67
+ }), [
68
+ showAll,
69
+ allItem,
70
+ aliasItems,
71
+ groups,
72
+ baseCollection
73
+ ]);
74
+ const aliased = useMemo(()=>new Set(aliasItems.map((item)=>baseCollection.getItemValue(item.source ?? null) ?? '')), [
75
+ aliasItems,
76
+ baseCollection
77
+ ]);
78
+ const internalValue = useMemo(()=>toInternalValue(value, {
79
+ multiple,
80
+ aliased,
81
+ hasAll: showAll
82
+ }), [
83
+ value,
84
+ multiple,
85
+ aliased,
86
+ showAll
87
+ ]);
88
+ const highlightedValue = resetHighlight ? arkCollection.firstValue : highlightState;
89
+ const commit = (next)=>{
90
+ if (void 0 === valueProp) setValueState(next);
91
+ onValueChange?.({
92
+ value: next,
93
+ items: collection.findMany(next)
94
+ });
95
+ };
96
+ const clear = ()=>{
97
+ if (disabled || 0 === value.length) return;
98
+ commit([]);
99
+ };
100
+ const handleValueChange = (details)=>{
101
+ commit(toConsumerValue(details.value, {
102
+ multiple,
103
+ previousInternal: internalValue,
104
+ previousValue: value
105
+ }));
106
+ };
107
+ const handleOpenChange = ({ open: nextOpen })=>{
108
+ setOpenState(nextOpen);
109
+ onOpenChange?.({
110
+ open: nextOpen,
111
+ value
112
+ });
113
+ };
114
+ const handleHighlightChange = (details)=>{
115
+ setHighlightState(details.highlightedValue);
116
+ setResetHighlight(false);
117
+ };
118
+ const setQuery = (next)=>{
119
+ if (!query && next) setLockedWidth(contentRef.current?.offsetWidth || void 0);
120
+ if (query && !next) setSnapshot(value);
121
+ onSearchChange(next);
122
+ setResetHighlight(true);
123
+ };
124
+ const context = {
125
+ label,
126
+ allLabel,
127
+ multiple,
128
+ disabled,
129
+ value,
130
+ collection: baseCollection,
131
+ filteredCollection,
132
+ groups,
133
+ query,
134
+ setQuery,
135
+ isSearchable,
136
+ isSearchActive,
137
+ showAll,
138
+ allItem,
139
+ showSelected,
140
+ aliasItems,
141
+ clear,
142
+ triggerRef,
143
+ contentRef,
144
+ lockedWidth
145
+ };
146
+ return /*#__PURE__*/ jsx(FilterDropdownContext.Provider, {
147
+ value: context,
148
+ children: /*#__PURE__*/ jsx(Select, {
149
+ ...props,
150
+ "data-slot": "filter-dropdown",
151
+ "data-testid": testId,
152
+ className: cn('inline-flex max-w-full', className),
153
+ collection: arkCollection,
154
+ multiple: multiple,
155
+ disabled: disabled,
156
+ value: internalValue,
157
+ onValueChange: handleValueChange,
158
+ open: openProp,
159
+ defaultOpen: defaultOpen,
160
+ onOpenChange: handleOpenChange,
161
+ highlightedValue: highlightedValue,
162
+ onHighlightChange: handleHighlightChange,
163
+ children: children
164
+ })
165
+ });
166
+ };
167
+ FilterDropdown.displayName = 'FilterDropdown';
168
+ export { FilterDropdown };
@@ -0,0 +1,14 @@
1
+ import type { ComponentProps, FC, ReactNode, Ref } from 'react';
2
+ import { SelectOption } from '../Select/SelectOption';
3
+ export interface FilterDropdownAllOptionProps extends Omit<ComponentProps<typeof SelectOption>, 'item' | 'children'> {
4
+ ref?: Ref<HTMLDivElement>;
5
+ /** @default the root's `allLabel` */
6
+ children?: ReactNode;
7
+ }
8
+ /**
9
+ * The «All …» row of single mode: first in the list, picked while the filter is unset, and
10
+ * picking it resets the filter to `[]`. Hidden in multi mode and while a query is active.
11
+ * Always render it in single mode: the row is part of keyboard navigation whenever the menu is
12
+ * open in single mode.
13
+ */
14
+ export declare const FilterDropdownAllOption: FC<FilterDropdownAllOptionProps>;
@@ -0,0 +1,25 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useTestId } from "../../utils/testId.js";
3
+ import { SelectOption } from "../Select/SelectOption.js";
4
+ import { SelectOptionIndicator } from "../Select/SelectOptionIndicator.js";
5
+ import { SelectOptionText } from "../Select/SelectOptionText.js";
6
+ import { useFilterDropdownContext } from "./FilterDropdownContext.js";
7
+ const FilterDropdownAllOption = ({ children, 'data-testid': testIdProp, ...props })=>{
8
+ const { showAll, allItem, allLabel } = useFilterDropdownContext();
9
+ const testId = useTestId('all-option', testIdProp);
10
+ if (!showAll) return null;
11
+ return /*#__PURE__*/ jsxs(SelectOption, {
12
+ ...props,
13
+ item: allItem,
14
+ "data-slot": "filter-dropdown-all-option",
15
+ "data-testid": testId,
16
+ children: [
17
+ /*#__PURE__*/ jsx(SelectOptionText, {
18
+ children: children ?? allLabel
19
+ }),
20
+ /*#__PURE__*/ jsx(SelectOptionIndicator, {})
21
+ ]
22
+ });
23
+ };
24
+ FilterDropdownAllOption.displayName = 'FilterDropdownAllOption';
25
+ export { FilterDropdownAllOption };
@@ -0,0 +1,12 @@
1
+ import type { ButtonHTMLAttributes, FC, Ref } from 'react';
2
+ import { type TestableProps } from '../../utils/testId';
3
+ export interface FilterDropdownClearProps extends ButtonHTMLAttributes<HTMLButtonElement>, TestableProps {
4
+ ref?: Ref<HTMLButtonElement>;
5
+ }
6
+ /**
7
+ * The ✕ that clears a multi filter. `FilterDropdownTrigger` renders one on its own; pass a
8
+ * `FilterDropdownClear` as the trigger's child to replace it (for example to add attributes).
9
+ * Renders only in multi mode while something is picked, and hands focus back to the trigger
10
+ * before it disappears.
11
+ */
12
+ export declare const FilterDropdownClear: FC<FilterDropdownClearProps>;
@@ -0,0 +1,33 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { X } from "../../icons/index.js";
3
+ import { cn } from "../../utils/cn.js";
4
+ import { useTestId } from "../../utils/testId.js";
5
+ import { filterDropdownClearClassName } from "./classes.js";
6
+ import { useFilterDropdownContext } from "./FilterDropdownContext.js";
7
+ const FilterDropdownClear = ({ ref, className, children, disabled, onClick, 'aria-label': ariaLabel, 'data-testid': testIdProp, ...props })=>{
8
+ const { multiple, value, label, clear, triggerRef, disabled: rootDisabled } = useFilterDropdownContext();
9
+ const testId = useTestId('clear', testIdProp);
10
+ if (!multiple || 0 === value.length) return null;
11
+ const handleClick = (event)=>{
12
+ onClick?.(event);
13
+ if (event.defaultPrevented) return;
14
+ triggerRef.current?.focus();
15
+ clear();
16
+ };
17
+ return /*#__PURE__*/ jsx("button", {
18
+ type: "button",
19
+ ...props,
20
+ ref: ref,
21
+ "data-slot": "filter-dropdown-clear",
22
+ "data-testid": testId,
23
+ "aria-label": ariaLabel ?? `Clear ${label}`,
24
+ disabled: rootDisabled || disabled,
25
+ onClick: handleClick,
26
+ className: cn(filterDropdownClearClassName, className),
27
+ children: children ?? /*#__PURE__*/ jsx(X, {
28
+ size: "md"
29
+ })
30
+ });
31
+ };
32
+ FilterDropdownClear.displayName = 'FilterDropdownClear';
33
+ export { FilterDropdownClear };
@@ -0,0 +1,13 @@
1
+ import { type FC, type ReactNode, type Ref } from 'react';
2
+ import type { Select as ArkUiSelect } from '@ark-ui/react/select';
3
+ import { type TestableProps } from '../../utils/testId';
4
+ export interface FilterDropdownContentProps extends Omit<ArkUiSelect.ContentProps, 'asChild' | 'children'>, TestableProps {
5
+ ref?: Ref<HTMLDivElement>;
6
+ children?: ReactNode;
7
+ }
8
+ /**
9
+ * The menu panel. `FilterDropdownSearch` children are pinned above the scrolling list and
10
+ * `FilterDropdownFooter` children below it; everything else scrolls. Hugs its options up to
11
+ * 360px and at most 340px tall, and keeps its width while a query narrows the list.
12
+ */
13
+ export declare const FilterDropdownContent: FC<FilterDropdownContentProps>;
@@ -0,0 +1,57 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Children, isValidElement } from "react";
3
+ import { cn } from "../../utils/cn.js";
4
+ import { mergeRefs } from "../../utils/mergeRefs.js";
5
+ import { TestIdProvider, useTestId } from "../../utils/testId.js";
6
+ import { SelectContent } from "../Select/SelectContent.js";
7
+ import { SelectHeader } from "../Select/SelectHeader.js";
8
+ import { SelectPositioner } from "../Select/SelectPositioner.js";
9
+ import { filterDropdownContentClassName } from "./classes.js";
10
+ import { useFilterDropdownContext } from "./FilterDropdownContext.js";
11
+ import { FilterDropdownFooter } from "./FilterDropdownFooter.js";
12
+ import { FilterDropdownSearch } from "./FilterDropdownSearch.js";
13
+ const FilterDropdownContent = ({ ref, className, style, children, 'data-testid': testIdProp, ...props })=>{
14
+ const { isSearchable, isSearchActive, lockedWidth, contentRef } = useFilterDropdownContext();
15
+ const testId = useTestId('content', testIdProp);
16
+ const listTestId = useTestId('list');
17
+ const baseTestId = useTestId();
18
+ const header = [];
19
+ const footer = [];
20
+ const body = [];
21
+ Children.forEach(children, (child)=>{
22
+ if (/*#__PURE__*/ isValidElement(child) && child.type === FilterDropdownSearch) header.push(child);
23
+ else if (/*#__PURE__*/ isValidElement(child) && child.type === FilterDropdownFooter) footer.push(child);
24
+ else body.push(child);
25
+ });
26
+ const width = isSearchActive && lockedWidth ? lockedWidth : void 0;
27
+ return /*#__PURE__*/ jsxs(SelectPositioner, {
28
+ className: cn(filterDropdownContentClassName, className),
29
+ contentProps: {
30
+ ...props,
31
+ ref: mergeRefs(ref, contentRef),
32
+ style: void 0 === width ? style : {
33
+ ...style,
34
+ width
35
+ },
36
+ 'data-slot': 'filter-dropdown-content',
37
+ 'data-testid': testId
38
+ },
39
+ children: [
40
+ isSearchable && header.length > 0 && /*#__PURE__*/ jsx(SelectHeader, {
41
+ children: header
42
+ }),
43
+ /*#__PURE__*/ jsx(SelectContent, {
44
+ "data-testid": listTestId,
45
+ tabIndex: -1,
46
+ "data-no-autofocus": "",
47
+ children: /*#__PURE__*/ jsx(TestIdProvider, {
48
+ value: baseTestId,
49
+ children: body
50
+ })
51
+ }),
52
+ footer
53
+ ]
54
+ });
55
+ };
56
+ FilterDropdownContent.displayName = 'FilterDropdownContent';
57
+ export { FilterDropdownContent };
@@ -0,0 +1,60 @@
1
+ import { type RefObject } from 'react';
2
+ import type { CollectionItem, ListCollection } from '@ark-ui/react/collection';
3
+ import type { FilterDropdownSpecialItem } from './lib';
4
+ export interface FilterDropdownContextValue {
5
+ /** Attribute name: trigger text in multi, aria labels, «Clear {label}». */
6
+ label: string;
7
+ /** Single mode: trigger text while unset and the default «All» option text. */
8
+ allLabel: string;
9
+ multiple: boolean;
10
+ disabled: boolean;
11
+ /** The consumer value (base values only, `[]` = unset). */
12
+ value: string[];
13
+ /** The consumer's collection, unfiltered. */
14
+ collection: ListCollection<CollectionItem>;
15
+ /** The consumer's collection narrowed by the search query. */
16
+ filteredCollection: ListCollection<CollectionItem>;
17
+ /** `filteredCollection.group()` — groups with no matches are absent. */
18
+ groups: [string, CollectionItem[]][];
19
+ query: string;
20
+ setQuery: (query: string) => void;
21
+ /** The search field is shown: the collection has at least `searchThreshold` items. */
22
+ isSearchable: boolean;
23
+ /** A non-empty query is narrowing the list. */
24
+ isSearchActive: boolean;
25
+ /** Single mode and no query: «All» is part of the list. */
26
+ showAll: boolean;
27
+ allItem: FilterDropdownSpecialItem;
28
+ /**
29
+ * Multi mode, `selectedOnTop`, searchable, no query and something was picked when the menu
30
+ * opened.
31
+ */
32
+ showSelected: boolean;
33
+ /** Alias rows of the "Selected" snapshot, in pick order. */
34
+ aliasItems: FilterDropdownSpecialItem[];
35
+ clear: () => void;
36
+ triggerRef: RefObject<HTMLButtonElement | null>;
37
+ contentRef: RefObject<HTMLDivElement | null>;
38
+ /** Width the menu is pinned to while a query is active, so it does not jump as rows drop. */
39
+ lockedWidth: number | undefined;
40
+ }
41
+ export declare const FilterDropdownContext: import("react").Context<FilterDropdownContextValue | null>;
42
+ export declare const useFilterDropdownContext: () => FilterDropdownContextValue;
43
+ /** Set by `FilterDropdownSelected` around each of its rows: the alias that row stands for. */
44
+ export declare const FilterDropdownAliasContext: import("react").Context<FilterDropdownSpecialItem<any> | null>;
45
+ export interface UseFilterDropdownResult<T extends CollectionItem = CollectionItem> {
46
+ /** The collection narrowed by the current query (the whole collection when it is empty). */
47
+ filteredCollection: ListCollection<T>;
48
+ /** `filteredCollection.group()`; render these so the list follows the search. */
49
+ groups: [string, T[]][];
50
+ query: string;
51
+ isSearchActive: boolean;
52
+ multiple: boolean;
53
+ value: string[];
54
+ clear: () => void;
55
+ }
56
+ /**
57
+ * Reads the state of the surrounding `FilterDropdown` — chiefly the filtered items and groups
58
+ * to render inside `FilterDropdownContent`.
59
+ */
60
+ export declare const useFilterDropdown: <T extends CollectionItem = CollectionItem>() => UseFilterDropdownResult<T>;
@@ -0,0 +1,21 @@
1
+ import { createContext, useContext } from "react";
2
+ const FilterDropdownContext = /*#__PURE__*/ createContext(null);
3
+ const useFilterDropdownContext = ()=>{
4
+ const context = useContext(FilterDropdownContext);
5
+ if (!context) throw new Error('FilterDropdown parts must be rendered inside <FilterDropdown>');
6
+ return context;
7
+ };
8
+ const FilterDropdownAliasContext = /*#__PURE__*/ createContext(null);
9
+ const useFilterDropdown = ()=>{
10
+ const { filteredCollection, groups, query, isSearchActive, multiple, value, clear } = useFilterDropdownContext();
11
+ return {
12
+ filteredCollection: filteredCollection,
13
+ groups: groups,
14
+ query,
15
+ isSearchActive,
16
+ multiple,
17
+ value,
18
+ clear
19
+ };
20
+ };
21
+ export { FilterDropdownAliasContext, FilterDropdownContext, useFilterDropdown, useFilterDropdownContext };
@@ -0,0 +1,9 @@
1
+ import type { FC, HTMLAttributes, ReactNode, Ref } from 'react';
2
+ import { type TestableProps } from '../../utils/testId';
3
+ export interface FilterDropdownEmptyProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'>, TestableProps {
4
+ ref?: Ref<HTMLDivElement>;
5
+ /** @default 'Nothing matches' */
6
+ children?: ReactNode;
7
+ }
8
+ /** Shown in place of the options when the query matches none of them. */
9
+ export declare const FilterDropdownEmpty: FC<FilterDropdownEmptyProps>;
@@ -0,0 +1,21 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useTestId } from "../../utils/testId.js";
3
+ import { SelectEmptyState } from "../Select/SelectEmptyState.js";
4
+ import { useFilterDropdownContext } from "./FilterDropdownContext.js";
5
+ const FilterDropdownEmpty = ({ ref, children, 'data-testid': testIdProp, ...props })=>{
6
+ const { filteredCollection } = useFilterDropdownContext();
7
+ const testId = useTestId('empty', testIdProp);
8
+ if (filteredCollection.size > 0) return null;
9
+ return /*#__PURE__*/ jsx("div", {
10
+ ...props,
11
+ ref: ref,
12
+ "data-slot": "filter-dropdown-empty",
13
+ "data-testid": testId,
14
+ children: /*#__PURE__*/ jsx(SelectEmptyState, {
15
+ className: "w-full max-w-240",
16
+ description: children ?? 'Nothing matches'
17
+ })
18
+ });
19
+ };
20
+ FilterDropdownEmpty.displayName = 'FilterDropdownEmpty';
21
+ export { FilterDropdownEmpty };
@@ -0,0 +1,9 @@
1
+ import type { FC } from 'react';
2
+ import { type TestableProps } from '../../utils/testId';
3
+ import { type SelectFooterProps } from '../Select/SelectFooter';
4
+ export type FilterDropdownFooterProps = Omit<SelectFooterProps, 'variant' | 'data-testid'> & TestableProps;
5
+ /**
6
+ * Footer pinned under the list. Multi mode only, and only while something is picked; its default
7
+ * content is `FilterDropdownFooterClear`.
8
+ */
9
+ export declare const FilterDropdownFooter: FC<FilterDropdownFooterProps>;
@@ -0,0 +1,19 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useTestId } from "../../utils/testId.js";
3
+ import { SelectFooter } from "../Select/SelectFooter.js";
4
+ import { useFilterDropdownContext } from "./FilterDropdownContext.js";
5
+ import { FilterDropdownFooterClear } from "./FilterDropdownFooterClear.js";
6
+ const FilterDropdownFooter = ({ children, 'data-testid': testIdProp, ...props })=>{
7
+ const { multiple, value } = useFilterDropdownContext();
8
+ const testId = useTestId('footer', testIdProp);
9
+ if (!multiple || 0 === value.length) return null;
10
+ return /*#__PURE__*/ jsx(SelectFooter, {
11
+ ...props,
12
+ variant: "actions",
13
+ "data-slot": "filter-dropdown-footer",
14
+ "data-testid": testId,
15
+ children: children ?? /*#__PURE__*/ jsx(FilterDropdownFooterClear, {})
16
+ });
17
+ };
18
+ FilterDropdownFooter.displayName = 'FilterDropdownFooter';
19
+ export { FilterDropdownFooter };
@@ -0,0 +1,9 @@
1
+ import type { FC } from 'react';
2
+ import { type TestableProps } from '../../utils/testId';
3
+ import { type ButtonProps } from '../Button';
4
+ export type FilterDropdownFooterClearProps = Omit<ButtonProps<'button'>, 'variant' | 'color' | 'size' | 'as' | 'asChild'> & TestableProps;
5
+ /**
6
+ * The «Clear» button of the multi-mode footer. Keeps the menu open and moves focus back into it
7
+ * (the search, or the list) before the footer disappears.
8
+ */
9
+ export declare const FilterDropdownFooterClear: FC<FilterDropdownFooterClearProps>;
@@ -0,0 +1,40 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useTestId } from "../../utils/testId.js";
3
+ import { Button } from "../Button/index.js";
4
+ import { useFilterDropdownContext } from "./FilterDropdownContext.js";
5
+ const BUTTON_OWNED_KEYS = new Set([
6
+ 'Enter',
7
+ ' '
8
+ ]);
9
+ const FilterDropdownFooterClear = ({ children, onClick, onKeyDown, 'data-testid': testIdProp, ...props })=>{
10
+ const { clear } = useFilterDropdownContext();
11
+ const testId = useTestId('footer-clear', testIdProp);
12
+ const handleKeyDown = (event)=>{
13
+ onKeyDown?.(event);
14
+ if (BUTTON_OWNED_KEYS.has(event.key)) event.stopPropagation();
15
+ };
16
+ const handleClick = (event)=>{
17
+ onClick?.(event);
18
+ if (event.defaultPrevented) return;
19
+ const content = event.currentTarget.closest('[data-scope="select"][data-part="content"]');
20
+ const search = content?.querySelector('[data-slot="filter-dropdown-search"] input');
21
+ (search ?? content)?.focus({
22
+ preventScroll: true
23
+ });
24
+ clear();
25
+ };
26
+ return /*#__PURE__*/ jsx(Button, {
27
+ ...props,
28
+ "data-slot": "filter-dropdown-footer-clear",
29
+ "data-testid": testId,
30
+ "data-no-autofocus": "",
31
+ variant: "ghost",
32
+ color: "neutral",
33
+ size: "small",
34
+ onClick: handleClick,
35
+ onKeyDown: handleKeyDown,
36
+ children: children ?? 'Clear'
37
+ });
38
+ };
39
+ FilterDropdownFooterClear.displayName = 'FilterDropdownFooterClear';
40
+ export { FilterDropdownFooterClear };
@@ -0,0 +1,5 @@
1
+ import type { ComponentProps, FC } from 'react';
2
+ import { SelectGroup } from '../Select/SelectGroup';
3
+ export type FilterDropdownGroupProps = ComponentProps<typeof SelectGroup>;
4
+ /** A labelled run of options. Render one per entry of `useFilterDropdown().groups`. */
5
+ export declare const FilterDropdownGroup: FC<FilterDropdownGroupProps>;
@@ -0,0 +1,8 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { SelectGroup } from "../Select/SelectGroup.js";
3
+ const FilterDropdownGroup = (props)=>/*#__PURE__*/ jsx(SelectGroup, {
4
+ ...props,
5
+ "data-slot": "filter-dropdown-group"
6
+ });
7
+ FilterDropdownGroup.displayName = 'FilterDropdownGroup';
8
+ export { FilterDropdownGroup };
@@ -0,0 +1,4 @@
1
+ import type { ComponentProps, FC } from 'react';
2
+ import { SelectGroupLabel } from '../Select/SelectGroupLabel';
3
+ export type FilterDropdownGroupLabelProps = ComponentProps<typeof SelectGroupLabel>;
4
+ export declare const FilterDropdownGroupLabel: FC<FilterDropdownGroupLabelProps>;
@@ -0,0 +1,8 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { SelectGroupLabel } from "../Select/SelectGroupLabel.js";
3
+ const FilterDropdownGroupLabel = (props)=>/*#__PURE__*/ jsx(SelectGroupLabel, {
4
+ ...props,
5
+ "data-slot": "filter-dropdown-group-label"
6
+ });
7
+ FilterDropdownGroupLabel.displayName = 'FilterDropdownGroupLabel';
8
+ export { FilterDropdownGroupLabel };
@@ -0,0 +1,15 @@
1
+ import { type ComponentProps, type ReactNode, type Ref } from 'react';
2
+ import type { CollectionItem } from '@ark-ui/react/collection';
3
+ import { SelectOption } from '../Select/SelectOption';
4
+ export interface FilterDropdownOptionProps<T extends CollectionItem = CollectionItem> extends Omit<ComponentProps<typeof SelectOption>, 'item' | 'children'> {
5
+ ref?: Ref<HTMLDivElement>;
6
+ item: T;
7
+ /** Secondary text at the right end of the row, such as a count. */
8
+ hint?: ReactNode;
9
+ /** Row text. Defaults to the item's label from the collection. */
10
+ children?: ReactNode;
11
+ }
12
+ export declare function FilterDropdownOption<T extends CollectionItem>({ item, hint, children, 'data-testid': testIdProp, ...props }: FilterDropdownOptionProps<T>): import("react").JSX.Element;
13
+ export declare namespace FilterDropdownOption {
14
+ var displayName: string;
15
+ }