@dxos/react-ui-searchlist 0.8.3 → 0.8.4-main.1068cf700f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +741 -265
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +741 -265
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Combobox/Combobox.d.ts +84 -0
- package/dist/types/src/components/Combobox/Combobox.d.ts.map +1 -0
- package/dist/types/src/components/Combobox/Combobox.stories.d.ts +21 -0
- package/dist/types/src/components/Combobox/Combobox.stories.d.ts.map +1 -0
- package/dist/types/src/components/Combobox/index.d.ts +2 -0
- package/dist/types/src/components/Combobox/index.d.ts.map +1 -0
- package/dist/types/src/components/Listbox/Listbox.d.ts +31 -0
- package/dist/types/src/components/Listbox/Listbox.d.ts.map +1 -0
- package/dist/types/src/components/Listbox/Listbox.stories.d.ts +21 -0
- package/dist/types/src/components/Listbox/Listbox.stories.d.ts.map +1 -0
- package/dist/types/src/components/Listbox/index.d.ts +2 -0
- package/dist/types/src/components/Listbox/index.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/SearchList.d.ts +88 -0
- package/dist/types/src/components/SearchList/SearchList.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/SearchList.stories.d.ts +28 -0
- package/dist/types/src/components/SearchList/SearchList.stories.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/context.d.ts +33 -0
- package/dist/types/src/components/SearchList/context.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/hooks/index.d.ts +5 -0
- package/dist/types/src/components/SearchList/hooks/index.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/hooks/useGlobalFilter.d.ts +34 -0
- package/dist/types/src/components/SearchList/hooks/useGlobalFilter.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListInput.d.ts +12 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListInput.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListItem.d.ts +10 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListItem.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListResults.d.ts +36 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListResults.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/index.d.ts +3 -0
- package/dist/types/src/components/SearchList/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +7 -6
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +24 -20
- package/src/components/Combobox/Combobox.stories.tsx +62 -0
- package/src/components/Combobox/Combobox.tsx +348 -0
- package/src/components/Combobox/index.ts +5 -0
- package/src/components/Listbox/Listbox.stories.tsx +53 -0
- package/src/components/Listbox/Listbox.tsx +214 -0
- package/src/components/Listbox/index.ts +5 -0
- package/src/components/SearchList/SearchList.stories.tsx +532 -0
- package/src/components/SearchList/SearchList.tsx +560 -0
- package/src/components/SearchList/context.ts +43 -0
- package/src/components/SearchList/hooks/index.ts +8 -0
- package/src/components/SearchList/hooks/useGlobalFilter.tsx +61 -0
- package/src/components/SearchList/hooks/useSearchListInput.ts +14 -0
- package/src/components/SearchList/hooks/useSearchListItem.ts +14 -0
- package/src/components/SearchList/hooks/useSearchListResults.ts +104 -0
- package/src/components/SearchList/index.ts +6 -0
- package/src/components/index.ts +2 -0
- package/src/index.ts +1 -2
- package/src/translations.ts +8 -4
- package/src/types/command-score.d.ts +16 -0
- package/dist/lib/node/index.cjs +0 -324
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/types/src/components/SearchList.d.ts +0 -44
- package/dist/types/src/components/SearchList.d.ts.map +0 -1
- package/dist/types/src/components/SearchList.stories.d.ts +0 -15
- package/dist/types/src/components/SearchList.stories.d.ts.map +0 -1
- package/dist/types/src/composites/PopoverCombobox.d.ts +0 -32
- package/dist/types/src/composites/PopoverCombobox.d.ts.map +0 -1
- package/dist/types/src/composites/PopoverCombobox.stories.d.ts +0 -28
- package/dist/types/src/composites/PopoverCombobox.stories.d.ts.map +0 -1
- package/dist/types/src/composites/index.d.ts +0 -2
- package/dist/types/src/composites/index.d.ts.map +0 -1
- package/src/components/SearchList.stories.tsx +0 -47
- package/src/components/SearchList.tsx +0 -250
- package/src/composites/PopoverCombobox.stories.tsx +0 -44
- package/src/composites/PopoverCombobox.tsx +0 -186
- package/src/composites/index.ts +0 -5
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useArrowNavigationGroup } from '@fluentui/react-tabster';
|
|
6
|
+
import { useComposedRefs } from '@radix-ui/react-compose-refs';
|
|
7
|
+
import { type Scope, createContextScope } from '@radix-ui/react-context';
|
|
8
|
+
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
9
|
+
import React, { type ComponentPropsWithRef, forwardRef, useCallback, useEffect, useRef } from 'react';
|
|
10
|
+
|
|
11
|
+
import { Icon, type IconProps, type ThemedClassName } from '@dxos/react-ui';
|
|
12
|
+
import { mx } from '@dxos/ui-theme';
|
|
13
|
+
|
|
14
|
+
const commandItem = 'flex items-center overflow-hidden';
|
|
15
|
+
|
|
16
|
+
const LISTBOX_NAME = 'Listbox';
|
|
17
|
+
const LISTBOX_OPTION_NAME = 'ListboxOption';
|
|
18
|
+
const LISTBOX_OPTION_LABEL_NAME = 'ListboxOptionLabel';
|
|
19
|
+
const LISTBOX_OPTION_INDICATOR_NAME = 'ListboxOptionIndicator';
|
|
20
|
+
|
|
21
|
+
//
|
|
22
|
+
// Context
|
|
23
|
+
//
|
|
24
|
+
|
|
25
|
+
type ListboxScopedProps<P> = P & { __listboxScope?: Scope };
|
|
26
|
+
type ListboxOptionScopedProps<P> = P & { __listboxOptionScope?: Scope };
|
|
27
|
+
|
|
28
|
+
type ListboxOptionProps = ThemedClassName<ComponentPropsWithRef<'li'>> & {
|
|
29
|
+
value: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const [createListboxContext, createListboxScope] = createContextScope(LISTBOX_NAME, []);
|
|
33
|
+
const [createListboxOptionContext, createListboxOptionScope] = createContextScope(LISTBOX_OPTION_NAME, [
|
|
34
|
+
createListboxScope,
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
type ListboxContextValue = {
|
|
38
|
+
selectedValue: string | undefined;
|
|
39
|
+
onValueChange: (value: string) => void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type ListboxOptionContextValue = {
|
|
43
|
+
value: string;
|
|
44
|
+
isSelected: boolean;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const [ListboxProvider, useListboxContext] = createListboxContext<ListboxContextValue>(LISTBOX_NAME);
|
|
48
|
+
const [ListboxOptionProvider, useListboxOptionContext] =
|
|
49
|
+
createListboxOptionContext<ListboxOptionContextValue>(LISTBOX_OPTION_NAME);
|
|
50
|
+
|
|
51
|
+
//
|
|
52
|
+
// Root
|
|
53
|
+
//
|
|
54
|
+
|
|
55
|
+
type ListboxRootProps = ThemedClassName<ComponentPropsWithRef<'ul'>> & {
|
|
56
|
+
value?: string;
|
|
57
|
+
defaultValue?: string;
|
|
58
|
+
onValueChange?: (value: string) => void;
|
|
59
|
+
autoFocus?: boolean;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// TODO(thure): Note that this overlaps significantly with the the `SelectableListbox` story of `List.tsx` in `react-ui`,
|
|
63
|
+
// making this an exemplar of `List` specifying standard `role="listbox"` interactivity, though it is here because it
|
|
64
|
+
// coheres with SearchList’s styles and norms. This can be promoted to `react-ui`, but doing so should involve clearing
|
|
65
|
+
// the technical- and design-debt in its `List` component.
|
|
66
|
+
const ListboxRoot = forwardRef<HTMLUListElement, ListboxRootProps>(
|
|
67
|
+
(props: ListboxScopedProps<ListboxRootProps>, forwardedRef) => {
|
|
68
|
+
const {
|
|
69
|
+
__listboxScope,
|
|
70
|
+
children,
|
|
71
|
+
classNames,
|
|
72
|
+
value: propsValue,
|
|
73
|
+
defaultValue,
|
|
74
|
+
onValueChange,
|
|
75
|
+
autoFocus,
|
|
76
|
+
...rootProps
|
|
77
|
+
} = props;
|
|
78
|
+
|
|
79
|
+
const arrowGroup = useArrowNavigationGroup({ axis: 'vertical' });
|
|
80
|
+
const ref = useRef<HTMLUListElement | null>(null);
|
|
81
|
+
const rootRef = useComposedRefs<HTMLUListElement>(ref, forwardedRef);
|
|
82
|
+
|
|
83
|
+
const [selectedValue, setSelectedValue] = useControllableState({
|
|
84
|
+
prop: propsValue,
|
|
85
|
+
defaultProp: defaultValue,
|
|
86
|
+
onChange: onValueChange,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const handleValueChange = (value: string) => {
|
|
90
|
+
setSelectedValue(value);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
// Autofocus the selected option on mount using querySelector
|
|
95
|
+
(ref.current?.querySelector('[aria-selected="true"]') as HTMLLIElement)?.focus();
|
|
96
|
+
}, [autoFocus]);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<ListboxProvider scope={__listboxScope} selectedValue={selectedValue} onValueChange={handleValueChange}>
|
|
100
|
+
<ul
|
|
101
|
+
role='listbox'
|
|
102
|
+
{...rootProps}
|
|
103
|
+
className={mx('is-full p-cardSpacingChrome', classNames)}
|
|
104
|
+
ref={rootRef}
|
|
105
|
+
{...arrowGroup}
|
|
106
|
+
>
|
|
107
|
+
{children}
|
|
108
|
+
</ul>
|
|
109
|
+
</ListboxProvider>
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
ListboxRoot.displayName = LISTBOX_NAME;
|
|
115
|
+
|
|
116
|
+
//
|
|
117
|
+
// Option
|
|
118
|
+
//
|
|
119
|
+
|
|
120
|
+
const ListboxOption = forwardRef<HTMLLIElement, ListboxOptionProps>(
|
|
121
|
+
(props: ListboxScopedProps<ListboxOptionProps>, forwardedRef) => {
|
|
122
|
+
const { __listboxScope, children, classNames, value, ...rootProps } = props;
|
|
123
|
+
const { selectedValue, onValueChange } = useListboxContext(LISTBOX_OPTION_NAME, __listboxScope);
|
|
124
|
+
|
|
125
|
+
const isSelected = selectedValue === value;
|
|
126
|
+
|
|
127
|
+
const handleSelect = useCallback(() => {
|
|
128
|
+
onValueChange(value);
|
|
129
|
+
}, [value, onValueChange]);
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<ListboxOptionProvider scope={__listboxScope} value={value} isSelected={isSelected}>
|
|
133
|
+
<li
|
|
134
|
+
role='option'
|
|
135
|
+
{...rootProps}
|
|
136
|
+
aria-selected={isSelected}
|
|
137
|
+
tabIndex={0}
|
|
138
|
+
className={mx(
|
|
139
|
+
'dx-focus-ring',
|
|
140
|
+
'plb-1 pli-2 rounded-sm select-none cursor-pointer data-[selected=true]:bg-hoverOverlay hover:bg-hoverOverlay',
|
|
141
|
+
commandItem,
|
|
142
|
+
classNames,
|
|
143
|
+
)}
|
|
144
|
+
onClick={handleSelect}
|
|
145
|
+
onKeyDown={({ key }) => {
|
|
146
|
+
if (['Enter', ' '].includes(key)) {
|
|
147
|
+
handleSelect();
|
|
148
|
+
}
|
|
149
|
+
}}
|
|
150
|
+
ref={forwardedRef}
|
|
151
|
+
>
|
|
152
|
+
{children}
|
|
153
|
+
</li>
|
|
154
|
+
</ListboxOptionProvider>
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
ListboxOption.displayName = LISTBOX_OPTION_NAME;
|
|
160
|
+
|
|
161
|
+
//
|
|
162
|
+
// OptionLabel
|
|
163
|
+
//
|
|
164
|
+
|
|
165
|
+
const ListboxOptionLabel = forwardRef<HTMLDivElement, ThemedClassName<ComponentPropsWithRef<'div'>>>(
|
|
166
|
+
({ children, classNames, ...rootProps }, forwardedRef) => {
|
|
167
|
+
return (
|
|
168
|
+
<span {...rootProps} className={mx('grow truncate', classNames)} ref={forwardedRef}>
|
|
169
|
+
{children}
|
|
170
|
+
</span>
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
ListboxOptionLabel.displayName = LISTBOX_OPTION_LABEL_NAME;
|
|
176
|
+
|
|
177
|
+
type ListboxOptionIndicatorProps = Omit<IconProps, 'icon'> & Partial<Pick<IconProps, 'icon'>>;
|
|
178
|
+
|
|
179
|
+
//
|
|
180
|
+
// OptionIndicator
|
|
181
|
+
//
|
|
182
|
+
|
|
183
|
+
const ListboxOptionIndicator = forwardRef<SVGSVGElement, ListboxOptionIndicatorProps>(
|
|
184
|
+
(props: ListboxOptionScopedProps<ListboxOptionIndicatorProps>, forwardedRef) => {
|
|
185
|
+
const { __listboxOptionScope, classNames, ...rootProps } = props;
|
|
186
|
+
const { isSelected } = useListboxOptionContext(LISTBOX_OPTION_INDICATOR_NAME, __listboxOptionScope);
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Icon
|
|
190
|
+
icon='ph--check--regular'
|
|
191
|
+
{...rootProps}
|
|
192
|
+
classNames={mx(!isSelected && 'invisible', classNames)}
|
|
193
|
+
ref={forwardedRef}
|
|
194
|
+
/>
|
|
195
|
+
);
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
ListboxOptionIndicator.displayName = LISTBOX_OPTION_INDICATOR_NAME;
|
|
200
|
+
|
|
201
|
+
//
|
|
202
|
+
// Listbox
|
|
203
|
+
//
|
|
204
|
+
|
|
205
|
+
export const Listbox = {
|
|
206
|
+
Root: ListboxRoot,
|
|
207
|
+
Option: ListboxOption,
|
|
208
|
+
OptionLabel: ListboxOptionLabel,
|
|
209
|
+
OptionIndicator: ListboxOptionIndicator,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export { createListboxScope, useListboxContext };
|
|
213
|
+
|
|
214
|
+
export type { ListboxRootProps, ListboxOptionProps, ListboxScopedProps };
|