@dxos/react-ui-searchlist 0.8.4-main.84f28bd → 0.8.4-main.a4bbb77
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 +204 -35
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +204 -35
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Listbox.d.ts +31 -0
- package/dist/types/src/components/Listbox.d.ts.map +1 -0
- package/dist/types/src/components/Listbox.stories.d.ts +16 -0
- package/dist/types/src/components/Listbox.stories.d.ts.map +1 -0
- package/dist/types/src/components/SearchList.d.ts +4 -1
- package/dist/types/src/components/SearchList.d.ts.map +1 -1
- package/dist/types/src/components/SearchList.stories.d.ts +10 -9
- package/dist/types/src/components/SearchList.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/composites/PopoverCombobox.d.ts +3 -3
- package/dist/types/src/composites/PopoverCombobox.d.ts.map +1 -1
- package/dist/types/src/composites/PopoverCombobox.stories.d.ts +6 -22
- package/dist/types/src/composites/PopoverCombobox.stories.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -16
- package/src/components/Listbox.stories.tsx +73 -0
- package/src/components/Listbox.tsx +185 -0
- package/src/components/SearchList.stories.tsx +17 -9
- package/src/components/SearchList.tsx +13 -12
- package/src/components/index.ts +1 -0
- package/src/composites/PopoverCombobox.stories.tsx +10 -7
- package/src/composites/PopoverCombobox.tsx +39 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-searchlist",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.a4bbb77",
|
|
4
4
|
"description": "A themed ⌘K-style combobox component, triggered by a button (or keyboard shortcut), where values are queried only within the invoked modal.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"type": "module",
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
+
"source": "./src/index.ts",
|
|
13
14
|
"types": "./dist/types/src/index.d.ts",
|
|
14
15
|
"browser": "./dist/lib/browser/index.mjs",
|
|
15
16
|
"node": "./dist/lib/node-esm/index.mjs"
|
|
@@ -24,29 +25,29 @@
|
|
|
24
25
|
"src"
|
|
25
26
|
],
|
|
26
27
|
"dependencies": {
|
|
28
|
+
"@fluentui/react-tabster": "^9.24.2",
|
|
27
29
|
"@preact-signals/safe-react": "^0.9.0",
|
|
30
|
+
"@radix-ui/react-compose-refs": "1.1.1",
|
|
28
31
|
"@radix-ui/react-context": "1.1.1",
|
|
29
32
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
30
33
|
"cmdk": "^0.2.0"
|
|
31
34
|
},
|
|
32
35
|
"devDependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@types/react": "~
|
|
35
|
-
"
|
|
36
|
-
"react": "~
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/react-ui-theme": "0.8.4-main.
|
|
42
|
-
"@dxos/storybook-utils": "0.8.4-main.84f28bd"
|
|
36
|
+
"@types/react": "~19.2.0",
|
|
37
|
+
"@types/react-dom": "~19.2.0",
|
|
38
|
+
"react": "~19.2.0",
|
|
39
|
+
"react-dom": "~19.2.0",
|
|
40
|
+
"vite": "7.1.9",
|
|
41
|
+
"@dxos/react-ui": "0.8.4-main.a4bbb77",
|
|
42
|
+
"@dxos/storybook-utils": "0.8.4-main.a4bbb77",
|
|
43
|
+
"@dxos/random": "0.8.4-main.a4bbb77",
|
|
44
|
+
"@dxos/react-ui-theme": "0.8.4-main.a4bbb77"
|
|
43
45
|
},
|
|
44
46
|
"peerDependencies": {
|
|
45
|
-
"
|
|
46
|
-
"react": "
|
|
47
|
-
"react-
|
|
48
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
49
|
-
"@dxos/react-ui-theme": "0.8.4-main.84f28bd"
|
|
47
|
+
"react": "^19.0.0",
|
|
48
|
+
"react-dom": "^19.0.0",
|
|
49
|
+
"@dxos/react-ui": "0.8.4-main.a4bbb77",
|
|
50
|
+
"@dxos/react-ui-theme": "0.8.4-main.a4bbb77"
|
|
50
51
|
},
|
|
51
52
|
"publishConfig": {
|
|
52
53
|
"access": "public"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React, { useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
9
|
+
|
|
10
|
+
import { Listbox } from './Listbox';
|
|
11
|
+
|
|
12
|
+
const DefaultStory = () => {
|
|
13
|
+
const [selectedValue, setSelectedValue] = useState<string>('option-2');
|
|
14
|
+
|
|
15
|
+
const options = [
|
|
16
|
+
{ value: 'option-1', label: 'First Option' },
|
|
17
|
+
{ value: 'option-2', label: 'Second Option' },
|
|
18
|
+
{ value: 'option-3', label: 'Third Option' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className='w-64'>
|
|
23
|
+
<Listbox.Root value={selectedValue} onValueChange={setSelectedValue}>
|
|
24
|
+
{options.map((option) => (
|
|
25
|
+
<Listbox.Option key={option.value} value={option.value}>
|
|
26
|
+
<Listbox.OptionLabel>{option.label}</Listbox.OptionLabel>
|
|
27
|
+
<Listbox.OptionIndicator />
|
|
28
|
+
</Listbox.Option>
|
|
29
|
+
))}
|
|
30
|
+
</Listbox.Root>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const DefaultValueStory = () => {
|
|
36
|
+
const options = [
|
|
37
|
+
{ value: 'apple', label: 'Apple' },
|
|
38
|
+
{ value: 'banana', label: 'Banana' },
|
|
39
|
+
{ value: 'cherry', label: 'Cherry' },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className='w-64'>
|
|
44
|
+
<Listbox.Root defaultValue='banana'>
|
|
45
|
+
{options.map((option) => (
|
|
46
|
+
<Listbox.Option key={option.value} value={option.value}>
|
|
47
|
+
<Listbox.OptionLabel>{option.label}</Listbox.OptionLabel>
|
|
48
|
+
<Listbox.OptionIndicator />
|
|
49
|
+
</Listbox.Option>
|
|
50
|
+
))}
|
|
51
|
+
</Listbox.Root>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const meta = {
|
|
57
|
+
title: 'ui/react-ui-searchlist/Listbox',
|
|
58
|
+
component: Listbox.Root,
|
|
59
|
+
decorators: [withTheme],
|
|
60
|
+
parameters: {
|
|
61
|
+
layout: 'fullscreen',
|
|
62
|
+
},
|
|
63
|
+
} satisfies Meta<typeof Listbox.Root>;
|
|
64
|
+
|
|
65
|
+
export default meta;
|
|
66
|
+
|
|
67
|
+
export const Default: StoryObj<typeof DefaultStory> = {
|
|
68
|
+
render: DefaultStory,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const DefaultValue: StoryObj<typeof DefaultValueStory> = {
|
|
72
|
+
render: DefaultValueStory,
|
|
73
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
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/react-ui-theme';
|
|
13
|
+
|
|
14
|
+
import { commandItem, searchListItem } from './SearchList';
|
|
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
|
+
type ListboxScopedProps<P> = P & { __listboxScope?: Scope };
|
|
22
|
+
type ListboxOptionScopedProps<P> = P & { __listboxOptionScope?: Scope };
|
|
23
|
+
|
|
24
|
+
type ListboxRootProps = ThemedClassName<ComponentPropsWithRef<'ul'>> & {
|
|
25
|
+
value?: string;
|
|
26
|
+
defaultValue?: string;
|
|
27
|
+
onValueChange?: (value: string) => void;
|
|
28
|
+
autoFocus?: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type ListboxOptionProps = ThemedClassName<ComponentPropsWithRef<'li'>> & {
|
|
32
|
+
value: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const [createListboxContext, createListboxScope] = createContextScope(LISTBOX_NAME, []);
|
|
36
|
+
const [createListboxOptionContext, createListboxOptionScope] = createContextScope(LISTBOX_OPTION_NAME, [
|
|
37
|
+
createListboxScope,
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
type ListboxContextValue = {
|
|
41
|
+
selectedValue: string | undefined;
|
|
42
|
+
onValueChange: (value: string) => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type ListboxOptionContextValue = {
|
|
46
|
+
value: string;
|
|
47
|
+
isSelected: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const [ListboxProvider, useListboxContext] = createListboxContext<ListboxContextValue>(LISTBOX_NAME);
|
|
51
|
+
const [ListboxOptionProvider, useListboxOptionContext] =
|
|
52
|
+
createListboxOptionContext<ListboxOptionContextValue>(LISTBOX_OPTION_NAME);
|
|
53
|
+
|
|
54
|
+
// TODO(thure): Note that this overlaps significantly with the the `SelectableListbox` story of `List.tsx` in `react-ui`,
|
|
55
|
+
// making this an exemplar of `List` specifying standard `role="listbox"` interactivity, though it is here because it
|
|
56
|
+
// coheres with SearchList’s styles and norms. This can be promoted to `react-ui`, but doing so should involve clearing
|
|
57
|
+
// the technical- and design-debt in its `List` component.
|
|
58
|
+
const ListboxRoot = forwardRef<HTMLUListElement, ListboxRootProps>(
|
|
59
|
+
(props: ListboxScopedProps<ListboxRootProps>, forwardedRef) => {
|
|
60
|
+
const {
|
|
61
|
+
__listboxScope,
|
|
62
|
+
children,
|
|
63
|
+
classNames,
|
|
64
|
+
value: propsValue,
|
|
65
|
+
defaultValue,
|
|
66
|
+
onValueChange,
|
|
67
|
+
autoFocus,
|
|
68
|
+
...rootProps
|
|
69
|
+
} = props;
|
|
70
|
+
|
|
71
|
+
const arrowGroup = useArrowNavigationGroup({ axis: 'vertical' });
|
|
72
|
+
const ref = useRef<HTMLUListElement | null>(null);
|
|
73
|
+
const rootRef = useComposedRefs<HTMLUListElement>(ref, forwardedRef);
|
|
74
|
+
|
|
75
|
+
const [selectedValue, setSelectedValue] = useControllableState({
|
|
76
|
+
prop: propsValue,
|
|
77
|
+
defaultProp: defaultValue,
|
|
78
|
+
onChange: onValueChange,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const handleValueChange = (value: string) => {
|
|
82
|
+
setSelectedValue(value);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
// Autofocus the selected option on mount using querySelector
|
|
87
|
+
(ref.current?.querySelector('[aria-selected="true"]') as HTMLLIElement)?.focus();
|
|
88
|
+
}, [autoFocus]);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<ListboxProvider scope={__listboxScope} selectedValue={selectedValue} onValueChange={handleValueChange}>
|
|
92
|
+
<ul
|
|
93
|
+
role='listbox'
|
|
94
|
+
{...rootProps}
|
|
95
|
+
className={mx('p-cardSpacingChrome', classNames)}
|
|
96
|
+
ref={rootRef}
|
|
97
|
+
{...arrowGroup}
|
|
98
|
+
>
|
|
99
|
+
{children}
|
|
100
|
+
</ul>
|
|
101
|
+
</ListboxProvider>
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
ListboxRoot.displayName = LISTBOX_NAME;
|
|
107
|
+
|
|
108
|
+
const ListboxOption = forwardRef<HTMLLIElement, ListboxOptionProps>(
|
|
109
|
+
(props: ListboxScopedProps<ListboxOptionProps>, forwardedRef) => {
|
|
110
|
+
const { __listboxScope, children, classNames, value, ...rootProps } = props;
|
|
111
|
+
const { selectedValue, onValueChange } = useListboxContext(LISTBOX_OPTION_NAME, __listboxScope);
|
|
112
|
+
|
|
113
|
+
const isSelected = selectedValue === value;
|
|
114
|
+
|
|
115
|
+
const handleSelect = useCallback(() => {
|
|
116
|
+
onValueChange(value);
|
|
117
|
+
}, [value, onValueChange]);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<ListboxOptionProvider scope={__listboxScope} value={value} isSelected={isSelected}>
|
|
121
|
+
<li
|
|
122
|
+
role='option'
|
|
123
|
+
{...rootProps}
|
|
124
|
+
aria-selected={isSelected}
|
|
125
|
+
tabIndex={0}
|
|
126
|
+
className={mx('dx-focus-ring', commandItem, searchListItem, classNames)}
|
|
127
|
+
onClick={handleSelect}
|
|
128
|
+
onKeyDown={({ key }) => {
|
|
129
|
+
if (['Enter', ' '].includes(key)) {
|
|
130
|
+
handleSelect();
|
|
131
|
+
}
|
|
132
|
+
}}
|
|
133
|
+
ref={forwardedRef}
|
|
134
|
+
>
|
|
135
|
+
{children}
|
|
136
|
+
</li>
|
|
137
|
+
</ListboxOptionProvider>
|
|
138
|
+
);
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
ListboxOption.displayName = LISTBOX_OPTION_NAME;
|
|
143
|
+
|
|
144
|
+
const ListboxOptionLabel = forwardRef<HTMLDivElement, ThemedClassName<ComponentPropsWithRef<'div'>>>(
|
|
145
|
+
({ children, classNames, ...rootProps }, forwardedRef) => {
|
|
146
|
+
return (
|
|
147
|
+
<span {...rootProps} className={mx('grow truncate', classNames)} ref={forwardedRef}>
|
|
148
|
+
{children}
|
|
149
|
+
</span>
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
ListboxOptionLabel.displayName = LISTBOX_OPTION_LABEL_NAME;
|
|
155
|
+
|
|
156
|
+
type ListboxOptionIndicatorProps = Omit<IconProps, 'icon'> & Partial<Pick<IconProps, 'icon'>>;
|
|
157
|
+
|
|
158
|
+
const ListboxOptionIndicator = forwardRef<SVGSVGElement, ListboxOptionIndicatorProps>(
|
|
159
|
+
(props: ListboxOptionScopedProps<ListboxOptionIndicatorProps>, forwardedRef) => {
|
|
160
|
+
const { __listboxOptionScope, classNames, ...rootProps } = props;
|
|
161
|
+
const { isSelected } = useListboxOptionContext(LISTBOX_OPTION_INDICATOR_NAME, __listboxOptionScope);
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<Icon
|
|
165
|
+
icon='ph--check--regular'
|
|
166
|
+
{...rootProps}
|
|
167
|
+
classNames={mx(!isSelected && 'invisible', classNames)}
|
|
168
|
+
ref={forwardedRef}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
ListboxOptionIndicator.displayName = LISTBOX_OPTION_INDICATOR_NAME;
|
|
175
|
+
|
|
176
|
+
export const Listbox = {
|
|
177
|
+
Root: ListboxRoot,
|
|
178
|
+
Option: ListboxOption,
|
|
179
|
+
OptionLabel: ListboxOptionLabel,
|
|
180
|
+
OptionIndicator: ListboxOptionIndicator,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export { createListboxScope, useListboxContext };
|
|
184
|
+
|
|
185
|
+
export type { ListboxRootProps, ListboxOptionProps, ListboxScopedProps };
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
7
|
-
import React, { type FC } from 'react';
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React from 'react';
|
|
8
7
|
|
|
9
8
|
import { faker } from '@dxos/random';
|
|
10
|
-
import { withTheme } from '@dxos/
|
|
9
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
11
10
|
|
|
12
11
|
import { SearchList } from './SearchList';
|
|
13
12
|
|
|
@@ -21,7 +20,11 @@ const defaultItems: StoryItems = faker.helpers
|
|
|
21
20
|
return acc;
|
|
22
21
|
}, {});
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
type StoryProps = {
|
|
24
|
+
items: StoryItems;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const DefaultStory = ({ items = defaultItems }: StoryProps) => {
|
|
25
28
|
return (
|
|
26
29
|
<SearchList.Root filter={(value, search) => (items[value].includes(search) ? 1 : 0)}>
|
|
27
30
|
<SearchList.Input placeholder='Search...' />
|
|
@@ -36,12 +39,17 @@ const SearchListStory: FC<{ items: StoryItems }> = ({ items = defaultItems }) =>
|
|
|
36
39
|
);
|
|
37
40
|
};
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
const meta = {
|
|
40
43
|
title: 'ui/react-ui-searchlist/SearchList',
|
|
41
|
-
component:
|
|
44
|
+
component: SearchList.Root as any,
|
|
45
|
+
render: DefaultStory,
|
|
42
46
|
decorators: [withTheme],
|
|
43
|
-
}
|
|
47
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
48
|
+
|
|
49
|
+
export default meta;
|
|
50
|
+
|
|
51
|
+
type Story = StoryObj<typeof meta>;
|
|
44
52
|
|
|
45
|
-
export const Default = {
|
|
53
|
+
export const Default: Story = {
|
|
46
54
|
args: {},
|
|
47
55
|
};
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { CaretDown } from '@phosphor-icons/react';
|
|
6
5
|
import { createContext } from '@radix-ui/react-context';
|
|
7
6
|
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
8
7
|
import { CommandEmpty, CommandInput, CommandItem, CommandList, CommandRoot } from 'cmdk';
|
|
9
|
-
import React, { type ComponentPropsWithRef,
|
|
8
|
+
import React, { type ComponentPropsWithRef, type PropsWithChildren, forwardRef, useCallback } from 'react';
|
|
10
9
|
|
|
11
10
|
import {
|
|
12
11
|
Button,
|
|
13
12
|
type ButtonProps,
|
|
13
|
+
Icon,
|
|
14
14
|
type TextInputProps,
|
|
15
15
|
type ThemedClassName,
|
|
16
16
|
useDensityContext,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
useId,
|
|
19
19
|
useThemeContext,
|
|
20
20
|
} from '@dxos/react-ui';
|
|
21
|
-
import {
|
|
21
|
+
import { mx, staticPlaceholderText } from '@dxos/react-ui-theme';
|
|
22
22
|
|
|
23
23
|
type SearchListVariant = 'list' | 'menu' | 'listbox';
|
|
24
24
|
|
|
@@ -63,10 +63,10 @@ type CommandInputPrimitiveProps = ComponentPropsWithRef<typeof CommandInput>;
|
|
|
63
63
|
|
|
64
64
|
// TODO: Harmonize with other inputs’ `onChange` prop.
|
|
65
65
|
type SearchListInputProps = Omit<TextInputProps, 'value' | 'defaultValue' | 'onChange'> &
|
|
66
|
-
Pick<CommandInputPrimitiveProps, 'value' | '
|
|
66
|
+
Pick<CommandInputPrimitiveProps, 'value' | 'defaultValue' | 'onValueChange'>;
|
|
67
67
|
|
|
68
68
|
const SearchListInput = forwardRef<HTMLInputElement, SearchListInputProps>(
|
|
69
|
-
({
|
|
69
|
+
({ classNames, density: propsDensity, elevation: propsElevation, variant, ...props }, forwardedRef) => {
|
|
70
70
|
// CHORE(thure): Keep this in-sync with `TextInput`, or submit a PR for `cmdk` to support `asChild` so we don’t have to.
|
|
71
71
|
const { hasIosKeyboard } = useThemeContext();
|
|
72
72
|
const { tx } = useThemeContext();
|
|
@@ -121,6 +121,10 @@ const SearchListEmpty = forwardRef<HTMLDivElement, SearchListEmptyProps>(
|
|
|
121
121
|
|
|
122
122
|
type SearchListItemProps = ThemedClassName<ComponentPropsWithRef<typeof CommandItem>>;
|
|
123
123
|
|
|
124
|
+
const commandItem = 'flex items-center overflow-hidden';
|
|
125
|
+
const searchListItem =
|
|
126
|
+
'plb-1 pli-2 rounded-sm select-none cursor-pointer data-[selected]:bg-hoverOverlay hover:bg-hoverOverlay';
|
|
127
|
+
|
|
124
128
|
const SearchListItem = forwardRef<HTMLDivElement, SearchListItemProps>(
|
|
125
129
|
({ children, classNames, onSelect, ...props }, forwardedRef) => {
|
|
126
130
|
const { onValueChange, onOpenChange } = useComboboxContext(SEARCHLIST_ITEM_NAME);
|
|
@@ -133,12 +137,7 @@ const SearchListItem = forwardRef<HTMLDivElement, SearchListItemProps>(
|
|
|
133
137
|
[onValueChange, onOpenChange, onSelect],
|
|
134
138
|
);
|
|
135
139
|
return (
|
|
136
|
-
<CommandItem
|
|
137
|
-
{...props}
|
|
138
|
-
onSelect={handleSelect}
|
|
139
|
-
className={mx('p-1 rounded select-none cursor-pointer data-[selected]:bg-hoverOverlay', classNames)}
|
|
140
|
-
ref={forwardedRef}
|
|
141
|
-
>
|
|
140
|
+
<CommandItem {...props} onSelect={handleSelect} className={mx(searchListItem, classNames)} ref={forwardedRef}>
|
|
142
141
|
{children}
|
|
143
142
|
</CommandItem>
|
|
144
143
|
);
|
|
@@ -215,7 +214,7 @@ const ComboboxTrigger = forwardRef<HTMLButtonElement, ComboboxTriggerProps>(
|
|
|
215
214
|
>
|
|
216
215
|
{value || placeholder}
|
|
217
216
|
</span>
|
|
218
|
-
<
|
|
217
|
+
<Icon icon='ph--caret-down--bold' size={3} />
|
|
219
218
|
</>
|
|
220
219
|
)}
|
|
221
220
|
</Button>
|
|
@@ -248,3 +247,5 @@ export type {
|
|
|
248
247
|
ComboboxRootProps,
|
|
249
248
|
ComboboxTriggerProps,
|
|
250
249
|
};
|
|
250
|
+
|
|
251
|
+
export { commandItem, searchListItem };
|
package/src/components/index.ts
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
6
|
import React from 'react';
|
|
8
7
|
|
|
9
8
|
import { faker } from '@dxos/random';
|
|
10
|
-
import { withTheme } from '@dxos/
|
|
9
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
11
10
|
|
|
12
11
|
import { PopoverCombobox } from './PopoverCombobox';
|
|
13
12
|
|
|
@@ -32,13 +31,17 @@ const DefaultStory = () => {
|
|
|
32
31
|
);
|
|
33
32
|
};
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
const meta = {
|
|
36
35
|
title: 'ui/react-ui-searchlist/PopoverCombobox',
|
|
37
|
-
component: PopoverCombobox,
|
|
36
|
+
component: PopoverCombobox.Root as any,
|
|
38
37
|
render: DefaultStory,
|
|
39
38
|
decorators: [withTheme],
|
|
40
|
-
}
|
|
39
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
40
|
+
|
|
41
|
+
export default meta;
|
|
42
|
+
|
|
43
|
+
type Story = StoryObj<typeof meta>;
|
|
41
44
|
|
|
42
|
-
export const Default = {
|
|
45
|
+
export const Default: Story = {
|
|
43
46
|
args: {},
|
|
44
47
|
};
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
Popover,
|
|
10
10
|
type PopoverArrowProps,
|
|
11
11
|
type PopoverContentProps,
|
|
12
|
-
type PopoverViewportProps,
|
|
13
12
|
type PopoverVirtualTriggerProps,
|
|
14
13
|
} from '@dxos/react-ui';
|
|
15
14
|
|
|
@@ -100,17 +99,17 @@ const PopoverComboboxContent = forwardRef<HTMLDivElement, PopoverComboboxContent
|
|
|
100
99
|
onFocusOutside,
|
|
101
100
|
onInteractOutside,
|
|
102
101
|
forceMount,
|
|
103
|
-
classNames,
|
|
104
102
|
}}
|
|
103
|
+
classNames={[
|
|
104
|
+
'is-[--radix-popover-trigger-width] max-bs-[--radix-popover-content-available-height] grid grid-rows-[min-content_1fr]',
|
|
105
|
+
classNames,
|
|
106
|
+
]}
|
|
105
107
|
id={modalId}
|
|
106
108
|
ref={forwardedRef}
|
|
107
109
|
>
|
|
108
|
-
<
|
|
109
|
-
{
|
|
110
|
-
|
|
111
|
-
{children}
|
|
112
|
-
</SearchList.Root>
|
|
113
|
-
</Popover.Viewport>
|
|
110
|
+
<SearchList.Root {...props} classNames='contents density-fine' role='none'>
|
|
111
|
+
{children}
|
|
112
|
+
</SearchList.Root>
|
|
114
113
|
</Popover.Content>
|
|
115
114
|
);
|
|
116
115
|
},
|
|
@@ -134,24 +133,48 @@ const PopoverComboboxVirtualTrigger = Popover.VirtualTrigger;
|
|
|
134
133
|
|
|
135
134
|
type PopoverComboboxInputProps = SearchListInputProps;
|
|
136
135
|
|
|
137
|
-
const PopoverComboboxInput =
|
|
136
|
+
const PopoverComboboxInput = forwardRef<HTMLInputElement, PopoverComboboxInputProps>(
|
|
137
|
+
({ classNames, ...props }, forwardedRef) => {
|
|
138
|
+
return (
|
|
139
|
+
<SearchList.Input
|
|
140
|
+
{...props}
|
|
141
|
+
classNames={[
|
|
142
|
+
'mli-cardSpacingChrome mbs-cardSpacingChrome mbe-0 is-[calc(100%-2*var(--dx-cardSpacingChrome))]',
|
|
143
|
+
classNames,
|
|
144
|
+
]}
|
|
145
|
+
ref={forwardedRef}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
138
150
|
|
|
139
|
-
type PopoverComboboxListProps = SearchListContentProps
|
|
140
|
-
Pick<PopoverViewportProps, 'constrainBlock' | 'constrainInline'>;
|
|
151
|
+
type PopoverComboboxListProps = SearchListContentProps;
|
|
141
152
|
|
|
142
153
|
const PopoverComboboxList = forwardRef<HTMLDivElement, PopoverComboboxListProps>(
|
|
143
|
-
({
|
|
154
|
+
({ classNames, ...props }, forwardedRef) => {
|
|
144
155
|
return (
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
156
|
+
<SearchList.Content
|
|
157
|
+
{...props}
|
|
158
|
+
classNames={['min-bs-0 overflow-y-auto plb-cardSpacingChrome', classNames]}
|
|
159
|
+
ref={forwardedRef}
|
|
160
|
+
/>
|
|
148
161
|
);
|
|
149
162
|
},
|
|
150
163
|
);
|
|
151
164
|
|
|
152
165
|
type PopoverComboboxItemProps = SearchListItemProps;
|
|
153
166
|
|
|
154
|
-
const PopoverComboboxItem =
|
|
167
|
+
const PopoverComboboxItem = forwardRef<HTMLDivElement, PopoverComboboxItemProps>(
|
|
168
|
+
({ classNames, ...props }, forwardedRef) => {
|
|
169
|
+
return (
|
|
170
|
+
<SearchList.Item
|
|
171
|
+
{...props}
|
|
172
|
+
classNames={['mli-cardSpacingChrome pli-cardSpacingChrome', classNames]}
|
|
173
|
+
ref={forwardedRef}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
},
|
|
177
|
+
);
|
|
155
178
|
|
|
156
179
|
type PopoverComboboxArrowProps = PopoverArrowProps;
|
|
157
180
|
|