@sproutsocial/seeds-react-menu 1.12.3 → 1.13.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/.turbo/turbo-build.log +13 -13
- package/CHANGELOG.md +10 -0
- package/dist/esm/v3/index.js +368 -14
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/index.d.mts +22 -22
- package/dist/index.d.ts +22 -22
- package/dist/v3/index.d.mts +156 -50
- package/dist/v3/index.d.ts +156 -50
- package/dist/v3/index.js +374 -14
- package/dist/v3/index.js.map +1 -1
- package/package.json +1 -1
- package/src/v3/ActionMenu.stories.tsx +260 -0
- package/src/v3/ActionMenu.tsx +345 -0
- package/src/v3/ActionMenuStyles.tsx +153 -0
- package/src/v3/Common/SelectStyles.tsx +4 -3
- package/src/v3/ModalStories.stories.tsx +159 -0
- package/src/v3/MultiCombobox.stories.tsx +17 -0
- package/src/v3/MultiCombobox.tsx +3 -1
- package/src/v3/MultiSearchableSelect.stories.tsx +18 -0
- package/src/v3/MultiSearchableSelect.tsx +3 -1
- package/src/v3/MultiSelect.stories.tsx +17 -0
- package/src/v3/MultiSelect.tsx +3 -2
- package/src/v3/SingleCombobox.stories.tsx +17 -0
- package/src/v3/SingleCombobox.tsx +3 -1
- package/src/v3/SingleSearchableSelect.stories.tsx +18 -0
- package/src/v3/SingleSearchableSelect.tsx +3 -1
- package/src/v3/SingleSelect.stories.tsx +17 -0
- package/src/v3/SingleSelect.tsx +3 -2
- package/src/v3/index.ts +1 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Menu } from "@base-ui/react/menu";
|
|
2
|
+
import styled, { css } from "styled-components";
|
|
3
|
+
import { focusRing } from "@sproutsocial/seeds-react-mixins";
|
|
4
|
+
|
|
5
|
+
export const ActionMenuTrigger = styled(Menu.Trigger)``;
|
|
6
|
+
|
|
7
|
+
export const ActionMenuPositioner = styled(Menu.Positioner)`
|
|
8
|
+
min-width: var(--anchor-width);
|
|
9
|
+
z-index: 7;
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
export const ActionMenuPopup = styled(Menu.Popup)`
|
|
13
|
+
font-family: ${({ theme }) => theme.fontFamily};
|
|
14
|
+
background: ${({ theme }) => theme.colors.container.background.base};
|
|
15
|
+
border: 1px solid ${({ theme }) => theme.colors.container.border.base};
|
|
16
|
+
border-radius: ${({ theme }) => theme.radii[500]};
|
|
17
|
+
box-shadow: ${({ theme }) => theme.shadows.low};
|
|
18
|
+
outline: none;
|
|
19
|
+
max-height: min(var(--base-ui-available-height, 400px), 400px);
|
|
20
|
+
overflow-y: auto;
|
|
21
|
+
padding: ${({ theme }) => theme.space[200]};
|
|
22
|
+
|
|
23
|
+
&[data-open] {
|
|
24
|
+
animation: action-menu-popup-in 120ms ease-out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&[data-closed] {
|
|
28
|
+
animation: action-menu-popup-out 100ms ease-in;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@keyframes action-menu-popup-in {
|
|
32
|
+
from {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
transform: scale(0.95);
|
|
35
|
+
}
|
|
36
|
+
to {
|
|
37
|
+
opacity: 1;
|
|
38
|
+
transform: scale(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@keyframes action-menu-popup-out {
|
|
43
|
+
from {
|
|
44
|
+
opacity: 1;
|
|
45
|
+
transform: scale(1);
|
|
46
|
+
}
|
|
47
|
+
to {
|
|
48
|
+
opacity: 0;
|
|
49
|
+
transform: scale(0.95);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
export const ActionMenuGroup = styled(Menu.Group)`
|
|
55
|
+
display: block;
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
export const ActionMenuGroupLabel = styled(Menu.GroupLabel)`
|
|
59
|
+
padding: ${({ theme }) =>
|
|
60
|
+
`${theme.space[300]} ${theme.space[300]} ${theme.space[200]}`};
|
|
61
|
+
font-size: ${({ theme }) => theme.typography[100].fontSize};
|
|
62
|
+
font-weight: ${({ theme }) => theme.fontWeights.semibold};
|
|
63
|
+
text-transform: uppercase;
|
|
64
|
+
letter-spacing: 0.05em;
|
|
65
|
+
color: ${({ theme }) => theme.colors.text.subtext};
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
export const ActionMenuSeparator = styled(Menu.Separator)`
|
|
69
|
+
height: 1px;
|
|
70
|
+
margin: ${({ theme }) => `${theme.space[200]} ${theme.space[300]}`};
|
|
71
|
+
background: ${({ theme }) => theme.colors.container.border.base};
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
const menuItemStyles = css`
|
|
75
|
+
box-sizing: border-box;
|
|
76
|
+
display: block;
|
|
77
|
+
width: 100%;
|
|
78
|
+
border-radius: ${({ theme }) => theme.radii[500]};
|
|
79
|
+
background-color: ${({ theme }) => theme.colors.listItem.background.base};
|
|
80
|
+
border: 1px solid ${({ theme }) => theme.colors.listItem.background.base};
|
|
81
|
+
color: ${({ theme }) => theme.colors.text.body};
|
|
82
|
+
font-family: ${({ theme }) => theme.fontFamily};
|
|
83
|
+
font-weight: ${({ theme }) => theme.fontWeights.normal};
|
|
84
|
+
text-align: left;
|
|
85
|
+
text-decoration: none;
|
|
86
|
+
padding: ${({ theme }) => theme.space[300]};
|
|
87
|
+
outline: 0;
|
|
88
|
+
${({ theme }) => theme.typography[200]};
|
|
89
|
+
transition: all ${({ theme }) => theme.duration.fast};
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
user-select: none;
|
|
92
|
+
|
|
93
|
+
&[data-highlighted] {
|
|
94
|
+
border-color: ${({ theme }) => theme.colors.listItem.border.base};
|
|
95
|
+
background-color: ${({ theme }) => theme.colors.listItem.background.hover};
|
|
96
|
+
color: ${({ theme }) => theme.colors.text.body};
|
|
97
|
+
text-decoration: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&[data-disabled] {
|
|
101
|
+
cursor: not-allowed;
|
|
102
|
+
opacity: 0.4;
|
|
103
|
+
}
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
export const StyledMenuItem = styled(Menu.Item)`
|
|
107
|
+
${menuItemStyles}
|
|
108
|
+
`;
|
|
109
|
+
|
|
110
|
+
export const StyledCheckboxItem = styled(Menu.CheckboxItem)`
|
|
111
|
+
${menuItemStyles}
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: ${({ theme }) => theme.space[300]};
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
export const StyledRadioItem = styled(Menu.RadioItem)`
|
|
118
|
+
${menuItemStyles}
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
gap: ${({ theme }) => theme.space[300]};
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
export const StyledCheckboxItemIndicator = styled(Menu.CheckboxItemIndicator)`
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
width: 16px;
|
|
128
|
+
color: ${({ theme }) => theme.colors.text.body};
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
export const StyledRadioItemIndicator = styled(Menu.RadioItemIndicator)`
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
width: 16px;
|
|
135
|
+
color: ${({ theme }) => theme.colors.text.body};
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
export const StyledSubmenuTrigger = styled(Menu.SubmenuTrigger)`
|
|
139
|
+
${menuItemStyles}
|
|
140
|
+
display: flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
gap: ${({ theme }) => theme.space[300]};
|
|
143
|
+
justify-content: space-between;
|
|
144
|
+
|
|
145
|
+
&[data-popup-open] {
|
|
146
|
+
border-color: ${({ theme }) => theme.colors.listItem.border.base};
|
|
147
|
+
background-color: ${({ theme }) => theme.colors.listItem.background.hover};
|
|
148
|
+
}
|
|
149
|
+
`;
|
|
150
|
+
|
|
151
|
+
export const StyledLinkItem = styled(Menu.LinkItem)`
|
|
152
|
+
${menuItemStyles}
|
|
153
|
+
`;
|
|
@@ -2,11 +2,12 @@ import { Select } from "@base-ui/react/select";
|
|
|
2
2
|
import styled from "styled-components";
|
|
3
3
|
import { focusRing } from "@sproutsocial/seeds-react-mixins";
|
|
4
4
|
|
|
5
|
-
export const Field = styled.div
|
|
6
|
-
display:
|
|
5
|
+
export const Field = styled.div<{ $fullWidth?: boolean }>`
|
|
6
|
+
display: ${({ $fullWidth }) =>
|
|
7
|
+
$fullWidth === false ? "inline-flex" : "flex"};
|
|
7
8
|
flex-direction: column;
|
|
8
9
|
gap: ${({ theme }) => theme.space[200]};
|
|
9
|
-
width: 100%;
|
|
10
|
+
${({ $fullWidth }) => $fullWidth !== false && "width: 100%;"}
|
|
10
11
|
`;
|
|
11
12
|
|
|
12
13
|
export const SelectLabel = styled(Select.Label)`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { action } from "@storybook/addon-actions";
|
|
3
4
|
import { FormField } from "@sproutsocial/seeds-react-form-field";
|
|
4
5
|
import { Button } from "@sproutsocial/seeds-react-button";
|
|
5
6
|
import { Input } from "@sproutsocial/seeds-react-input";
|
|
@@ -16,6 +17,13 @@ import { SingleCombobox } from "./SingleCombobox";
|
|
|
16
17
|
import { MultiCombobox } from "./MultiCombobox";
|
|
17
18
|
import { SingleSearchableSelect } from "./SingleSearchableSelect";
|
|
18
19
|
import { MultiSearchableSelect } from "./MultiSearchableSelect";
|
|
20
|
+
import {
|
|
21
|
+
ActionMenu,
|
|
22
|
+
MenuGroup,
|
|
23
|
+
MenuItem,
|
|
24
|
+
MenuSub,
|
|
25
|
+
MenuSubTrigger,
|
|
26
|
+
} from "./ActionMenu";
|
|
19
27
|
import { books, itemToString } from "./storyData";
|
|
20
28
|
|
|
21
29
|
// ─── Shared content ───────────────────────────────────────────────────────────
|
|
@@ -179,3 +187,154 @@ export const V2Modal: Story = {
|
|
|
179
187
|
</>
|
|
180
188
|
),
|
|
181
189
|
};
|
|
190
|
+
|
|
191
|
+
// ─── Action Menu in Modal ─────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
export const ActionMenuInV1Modal: Story = {
|
|
194
|
+
name: "Action Menu in V1 Modal",
|
|
195
|
+
render: () => {
|
|
196
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
197
|
+
return (
|
|
198
|
+
<>
|
|
199
|
+
<Button appearance="primary" onClick={() => setIsOpen(true)}>
|
|
200
|
+
Open V1 Modal
|
|
201
|
+
</Button>
|
|
202
|
+
<ModalV1
|
|
203
|
+
appElementSelector="#root"
|
|
204
|
+
isOpen={isOpen}
|
|
205
|
+
onClose={() => setIsOpen(false)}
|
|
206
|
+
closeButtonLabel="Close"
|
|
207
|
+
label="Action Menu in V1 Modal"
|
|
208
|
+
>
|
|
209
|
+
<ModalV1.Header title="" subtitle="" bordered>
|
|
210
|
+
Actions
|
|
211
|
+
</ModalV1.Header>
|
|
212
|
+
<ModalV1.Content>
|
|
213
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
214
|
+
<ActionMenu
|
|
215
|
+
trigger={<Button appearance="secondary">Basic Menu</Button>}
|
|
216
|
+
>
|
|
217
|
+
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
|
|
218
|
+
<MenuItem onClick={action("View Messages")}>
|
|
219
|
+
View Messages
|
|
220
|
+
</MenuItem>
|
|
221
|
+
<MenuItem onClick={action("Analyze post")}>
|
|
222
|
+
Analyze post
|
|
223
|
+
</MenuItem>
|
|
224
|
+
</ActionMenu>
|
|
225
|
+
|
|
226
|
+
<ActionMenu
|
|
227
|
+
trigger={
|
|
228
|
+
<Button appearance="secondary">Menu with Groups</Button>
|
|
229
|
+
}
|
|
230
|
+
>
|
|
231
|
+
<MenuGroup label="Actions" showSeparator>
|
|
232
|
+
<MenuItem onClick={action("Edit")}>Edit</MenuItem>
|
|
233
|
+
<MenuItem onClick={action("Duplicate")}>Duplicate</MenuItem>
|
|
234
|
+
</MenuGroup>
|
|
235
|
+
<MenuGroup label="Danger">
|
|
236
|
+
<MenuItem onClick={action("Delete")} disabled>
|
|
237
|
+
Delete
|
|
238
|
+
</MenuItem>
|
|
239
|
+
</MenuGroup>
|
|
240
|
+
</ActionMenu>
|
|
241
|
+
|
|
242
|
+
<ActionMenu
|
|
243
|
+
trigger={
|
|
244
|
+
<Button appearance="secondary">Menu with Submenu</Button>
|
|
245
|
+
}
|
|
246
|
+
>
|
|
247
|
+
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
|
|
248
|
+
<MenuSub trigger={<MenuSubTrigger>Share</MenuSubTrigger>}>
|
|
249
|
+
<MenuItem onClick={action("Share to Twitter")}>
|
|
250
|
+
Twitter
|
|
251
|
+
</MenuItem>
|
|
252
|
+
<MenuItem onClick={action("Share to LinkedIn")}>
|
|
253
|
+
LinkedIn
|
|
254
|
+
</MenuItem>
|
|
255
|
+
<MenuSub
|
|
256
|
+
trigger={<MenuSubTrigger>More platforms</MenuSubTrigger>}
|
|
257
|
+
>
|
|
258
|
+
<MenuItem onClick={action("Share to Facebook")}>
|
|
259
|
+
Facebook
|
|
260
|
+
</MenuItem>
|
|
261
|
+
<MenuItem onClick={action("Share to Instagram")}>
|
|
262
|
+
Instagram
|
|
263
|
+
</MenuItem>
|
|
264
|
+
</MenuSub>
|
|
265
|
+
</MenuSub>
|
|
266
|
+
<MenuItem onClick={action("Delete")} disabled>
|
|
267
|
+
Delete
|
|
268
|
+
</MenuItem>
|
|
269
|
+
</ActionMenu>
|
|
270
|
+
</div>
|
|
271
|
+
</ModalV1.Content>
|
|
272
|
+
<ModalV1.Footer>Footer</ModalV1.Footer>
|
|
273
|
+
</ModalV1>
|
|
274
|
+
</>
|
|
275
|
+
);
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export const ActionMenuInModal: Story = {
|
|
280
|
+
name: "Action Menu in V2 Modal",
|
|
281
|
+
render: () => (
|
|
282
|
+
<ModalV2
|
|
283
|
+
title="Actions"
|
|
284
|
+
modalTrigger={<Button appearance="primary">Open Modal</Button>}
|
|
285
|
+
closeButtonAriaLabel="Close"
|
|
286
|
+
>
|
|
287
|
+
<ModalBody>
|
|
288
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
289
|
+
<ActionMenu
|
|
290
|
+
trigger={<Button appearance="secondary">Basic Menu</Button>}
|
|
291
|
+
>
|
|
292
|
+
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
|
|
293
|
+
<MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
|
|
294
|
+
<MenuItem onClick={action("Analyze post")}>Analyze post</MenuItem>
|
|
295
|
+
</ActionMenu>
|
|
296
|
+
|
|
297
|
+
<ActionMenu
|
|
298
|
+
trigger={<Button appearance="secondary">Menu with Groups</Button>}
|
|
299
|
+
>
|
|
300
|
+
<MenuGroup label="Actions" showSeparator>
|
|
301
|
+
<MenuItem onClick={action("Edit")}>Edit</MenuItem>
|
|
302
|
+
<MenuItem onClick={action("Duplicate")}>Duplicate</MenuItem>
|
|
303
|
+
</MenuGroup>
|
|
304
|
+
<MenuGroup label="Danger">
|
|
305
|
+
<MenuItem onClick={action("Delete")} disabled>
|
|
306
|
+
Delete
|
|
307
|
+
</MenuItem>
|
|
308
|
+
</MenuGroup>
|
|
309
|
+
</ActionMenu>
|
|
310
|
+
|
|
311
|
+
<ActionMenu
|
|
312
|
+
trigger={<Button appearance="secondary">Menu with Submenu</Button>}
|
|
313
|
+
>
|
|
314
|
+
<MenuItem onClick={action("Compose")}>Compose</MenuItem>
|
|
315
|
+
<MenuSub trigger={<MenuSubTrigger>Share</MenuSubTrigger>}>
|
|
316
|
+
<MenuItem onClick={action("Share to Twitter")}>Twitter</MenuItem>
|
|
317
|
+
<MenuItem onClick={action("Share to LinkedIn")}>
|
|
318
|
+
LinkedIn
|
|
319
|
+
</MenuItem>
|
|
320
|
+
<MenuSub
|
|
321
|
+
trigger={<MenuSubTrigger>More platforms</MenuSubTrigger>}
|
|
322
|
+
>
|
|
323
|
+
<MenuItem onClick={action("Share to Facebook")}>
|
|
324
|
+
Facebook
|
|
325
|
+
</MenuItem>
|
|
326
|
+
<MenuItem onClick={action("Share to Instagram")}>
|
|
327
|
+
Instagram
|
|
328
|
+
</MenuItem>
|
|
329
|
+
</MenuSub>
|
|
330
|
+
</MenuSub>
|
|
331
|
+
<MenuItem onClick={action("Delete")} disabled>
|
|
332
|
+
Delete
|
|
333
|
+
</MenuItem>
|
|
334
|
+
</ActionMenu>
|
|
335
|
+
</div>
|
|
336
|
+
</ModalBody>
|
|
337
|
+
<ModalFooter cancelButton={<Button>Close</Button>} />
|
|
338
|
+
</ModalV2>
|
|
339
|
+
),
|
|
340
|
+
};
|
|
@@ -396,6 +396,23 @@ export const VirtualizedSelectAll: Story = {
|
|
|
396
396
|
),
|
|
397
397
|
};
|
|
398
398
|
|
|
399
|
+
export const Inline: Story = {
|
|
400
|
+
name: "Inline (fullWidth=false)",
|
|
401
|
+
render: () => (
|
|
402
|
+
<FormField label="Pick books">
|
|
403
|
+
{({ id }) => (
|
|
404
|
+
<MultiCombobox
|
|
405
|
+
id={id}
|
|
406
|
+
data={books}
|
|
407
|
+
itemToString={itemToString}
|
|
408
|
+
placeholder="Search books..."
|
|
409
|
+
fullWidth={false}
|
|
410
|
+
/>
|
|
411
|
+
)}
|
|
412
|
+
</FormField>
|
|
413
|
+
),
|
|
414
|
+
};
|
|
415
|
+
|
|
399
416
|
export const CustomSearch: Story = {
|
|
400
417
|
name: "Custom search (title + author)",
|
|
401
418
|
render: () => (
|
package/src/v3/MultiCombobox.tsx
CHANGED
|
@@ -74,6 +74,7 @@ interface MultiComboboxBaseProps {
|
|
|
74
74
|
inputValue?: string;
|
|
75
75
|
onInputValueChange?: (value: string) => void;
|
|
76
76
|
"aria-describedby"?: string;
|
|
77
|
+
fullWidth?: boolean;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
interface MultiComboboxDataProps<T extends DataWithId>
|
|
@@ -141,6 +142,7 @@ export function MultiCombobox<T extends DataWithId>(
|
|
|
141
142
|
filter,
|
|
142
143
|
inputValue,
|
|
143
144
|
onInputValueChange,
|
|
145
|
+
fullWidth = true,
|
|
144
146
|
} = props;
|
|
145
147
|
const ariaDescribedBy = props["aria-describedby"];
|
|
146
148
|
|
|
@@ -481,7 +483,7 @@ export function MultiCombobox<T extends DataWithId>(
|
|
|
481
483
|
: (value as unknown as T[]);
|
|
482
484
|
|
|
483
485
|
return (
|
|
484
|
-
<Field>
|
|
486
|
+
<Field $fullWidth={fullWidth}>
|
|
485
487
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
486
488
|
<Combobox.Root
|
|
487
489
|
multiple
|
|
@@ -413,6 +413,24 @@ export const LoadingWithToggle: Story = {
|
|
|
413
413
|
},
|
|
414
414
|
};
|
|
415
415
|
|
|
416
|
+
export const Inline: Story = {
|
|
417
|
+
name: "Inline (fullWidth=false)",
|
|
418
|
+
render: () => (
|
|
419
|
+
<FormField label="Pick books">
|
|
420
|
+
{({ id }) => (
|
|
421
|
+
<MultiSearchableSelect
|
|
422
|
+
id={id}
|
|
423
|
+
data={books}
|
|
424
|
+
itemToString={itemToString}
|
|
425
|
+
placeholder="Select books"
|
|
426
|
+
searchPlaceholder="Search books..."
|
|
427
|
+
fullWidth={false}
|
|
428
|
+
/>
|
|
429
|
+
)}
|
|
430
|
+
</FormField>
|
|
431
|
+
),
|
|
432
|
+
};
|
|
433
|
+
|
|
416
434
|
export const CustomSearch: Story = {
|
|
417
435
|
name: "Custom search (title + author)",
|
|
418
436
|
render: () => (
|
|
@@ -80,6 +80,7 @@ interface MultiSearchableSelectBaseProps {
|
|
|
80
80
|
disabled?: boolean;
|
|
81
81
|
filter?: ((item: unknown, query: string) => boolean) | null;
|
|
82
82
|
"aria-describedby"?: string;
|
|
83
|
+
fullWidth?: boolean;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
interface MultiSearchableSelectDataProps<T extends DataWithId>
|
|
@@ -145,6 +146,7 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
145
146
|
isLoading = false,
|
|
146
147
|
loadingText = "Loading...",
|
|
147
148
|
filter,
|
|
149
|
+
fullWidth = true,
|
|
148
150
|
} = props;
|
|
149
151
|
const ariaDescribedBy = props["aria-describedby"];
|
|
150
152
|
|
|
@@ -461,7 +463,7 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
461
463
|
: (value as unknown as T[]);
|
|
462
464
|
|
|
463
465
|
return (
|
|
464
|
-
<Field>
|
|
466
|
+
<Field $fullWidth={fullWidth}>
|
|
465
467
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
466
468
|
<Combobox.Root
|
|
467
469
|
multiple
|
|
@@ -266,6 +266,23 @@ export const RemoveSelectedItems: Story = {
|
|
|
266
266
|
),
|
|
267
267
|
};
|
|
268
268
|
|
|
269
|
+
export const Inline: Story = {
|
|
270
|
+
name: "Inline (fullWidth=false)",
|
|
271
|
+
render: () => (
|
|
272
|
+
<FormField label="Pick books">
|
|
273
|
+
{({ id }) => (
|
|
274
|
+
<MultiSelect
|
|
275
|
+
id={id}
|
|
276
|
+
data={books}
|
|
277
|
+
itemToString={itemToString}
|
|
278
|
+
placeholder="Select books..."
|
|
279
|
+
fullWidth={false}
|
|
280
|
+
/>
|
|
281
|
+
)}
|
|
282
|
+
</FormField>
|
|
283
|
+
),
|
|
284
|
+
};
|
|
285
|
+
|
|
269
286
|
export const Children: Story = {
|
|
270
287
|
name: "Children API",
|
|
271
288
|
render: () => (
|
package/src/v3/MultiSelect.tsx
CHANGED
|
@@ -44,6 +44,7 @@ interface MultiSelectBaseProps {
|
|
|
44
44
|
placeholder?: string;
|
|
45
45
|
disabled?: boolean;
|
|
46
46
|
"aria-describedby"?: string;
|
|
47
|
+
fullWidth?: boolean;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
interface MultiSelectDataProps<T extends DataWithId>
|
|
@@ -88,7 +89,7 @@ export type MultiSelectProps<T extends DataWithId> =
|
|
|
88
89
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
89
90
|
|
|
90
91
|
export function MultiSelect<T extends DataWithId>(props: MultiSelectProps<T>) {
|
|
91
|
-
const { id, placeholder = "Select..." } = props;
|
|
92
|
+
const { id, placeholder = "Select...", fullWidth = true } = props;
|
|
92
93
|
const ariaDescribedBy = props["aria-describedby"];
|
|
93
94
|
|
|
94
95
|
const isChildrenMode = "children" in props && props.children != null;
|
|
@@ -171,7 +172,7 @@ export function MultiSelect<T extends DataWithId>(props: MultiSelectProps<T>) {
|
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
return (
|
|
174
|
-
<Field>
|
|
175
|
+
<Field $fullWidth={fullWidth}>
|
|
175
176
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
176
177
|
<Select.Root
|
|
177
178
|
multiple
|
|
@@ -192,6 +192,23 @@ export const Virtualized: Story = {
|
|
|
192
192
|
),
|
|
193
193
|
};
|
|
194
194
|
|
|
195
|
+
export const Inline: Story = {
|
|
196
|
+
name: "Inline (fullWidth=false)",
|
|
197
|
+
render: () => (
|
|
198
|
+
<FormField label="Pick a book">
|
|
199
|
+
{({ id }) => (
|
|
200
|
+
<SingleCombobox
|
|
201
|
+
id={id}
|
|
202
|
+
data={books}
|
|
203
|
+
itemToString={itemToString}
|
|
204
|
+
placeholder="Search books..."
|
|
205
|
+
fullWidth={false}
|
|
206
|
+
/>
|
|
207
|
+
)}
|
|
208
|
+
</FormField>
|
|
209
|
+
),
|
|
210
|
+
};
|
|
211
|
+
|
|
195
212
|
export const CustomSearch: Story = {
|
|
196
213
|
name: "Custom search (title + author)",
|
|
197
214
|
render: () => (
|
|
@@ -32,6 +32,7 @@ interface SingleComboboxBaseProps {
|
|
|
32
32
|
disabled?: boolean;
|
|
33
33
|
filter?: ((item: unknown, query: string) => boolean) | null;
|
|
34
34
|
"aria-describedby"?: string;
|
|
35
|
+
fullWidth?: boolean;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
interface SingleComboboxDataBaseProps<T extends DataWithId>
|
|
@@ -89,6 +90,7 @@ export function SingleCombobox<T extends DataWithId>(
|
|
|
89
90
|
placeholder = "Search...",
|
|
90
91
|
emptyText = "No results found.",
|
|
91
92
|
filter,
|
|
93
|
+
fullWidth = true,
|
|
92
94
|
} = props;
|
|
93
95
|
const ariaDescribedBy = props["aria-describedby"];
|
|
94
96
|
|
|
@@ -168,7 +170,7 @@ export function SingleCombobox<T extends DataWithId>(
|
|
|
168
170
|
);
|
|
169
171
|
|
|
170
172
|
return (
|
|
171
|
-
<Field>
|
|
173
|
+
<Field $fullWidth={fullWidth}>
|
|
172
174
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
173
175
|
<Combobox.Root
|
|
174
176
|
id={id}
|
|
@@ -304,6 +304,24 @@ export const LoadingWithToggle: Story = {
|
|
|
304
304
|
},
|
|
305
305
|
};
|
|
306
306
|
|
|
307
|
+
export const Inline: Story = {
|
|
308
|
+
name: "Inline (fullWidth=false)",
|
|
309
|
+
render: () => (
|
|
310
|
+
<FormField label="Pick a book">
|
|
311
|
+
{({ id }) => (
|
|
312
|
+
<SingleSearchableSelect
|
|
313
|
+
id={id}
|
|
314
|
+
data={books}
|
|
315
|
+
itemToString={itemToString}
|
|
316
|
+
placeholder="Select a book"
|
|
317
|
+
searchPlaceholder="Search books..."
|
|
318
|
+
fullWidth={false}
|
|
319
|
+
/>
|
|
320
|
+
)}
|
|
321
|
+
</FormField>
|
|
322
|
+
),
|
|
323
|
+
};
|
|
324
|
+
|
|
307
325
|
export const CustomSearch: Story = {
|
|
308
326
|
name: "Custom search (title + author)",
|
|
309
327
|
render: () => (
|
|
@@ -42,6 +42,7 @@ interface SingleSearchableSelectBaseProps {
|
|
|
42
42
|
disabled?: boolean;
|
|
43
43
|
filter?: ((item: unknown, query: string) => boolean) | null;
|
|
44
44
|
"aria-describedby"?: string;
|
|
45
|
+
fullWidth?: boolean;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
interface SingleSearchableSelectDataBaseProps<T extends DataWithId>
|
|
@@ -106,6 +107,7 @@ export function SingleSearchableSelect<T extends DataWithId>(
|
|
|
106
107
|
isLoading = false,
|
|
107
108
|
loadingText = "Loading...",
|
|
108
109
|
filter,
|
|
110
|
+
fullWidth = true,
|
|
109
111
|
} = props;
|
|
110
112
|
const ariaDescribedBy = props["aria-describedby"];
|
|
111
113
|
|
|
@@ -190,7 +192,7 @@ export function SingleSearchableSelect<T extends DataWithId>(
|
|
|
190
192
|
);
|
|
191
193
|
|
|
192
194
|
return (
|
|
193
|
-
<Field>
|
|
195
|
+
<Field $fullWidth={fullWidth}>
|
|
194
196
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
195
197
|
<Combobox.Root
|
|
196
198
|
id={id}
|
|
@@ -259,6 +259,23 @@ export const DisabledItems: Story = {
|
|
|
259
259
|
),
|
|
260
260
|
};
|
|
261
261
|
|
|
262
|
+
export const Inline: Story = {
|
|
263
|
+
name: "Inline (fullWidth=false)",
|
|
264
|
+
render: () => (
|
|
265
|
+
<FormField label="Pick a book">
|
|
266
|
+
{({ id }) => (
|
|
267
|
+
<SingleSelect
|
|
268
|
+
id={id}
|
|
269
|
+
data={books}
|
|
270
|
+
itemToString={itemToString}
|
|
271
|
+
placeholder="Select a book"
|
|
272
|
+
fullWidth={false}
|
|
273
|
+
/>
|
|
274
|
+
)}
|
|
275
|
+
</FormField>
|
|
276
|
+
),
|
|
277
|
+
};
|
|
278
|
+
|
|
262
279
|
export const Children: Story = {
|
|
263
280
|
name: "Children API",
|
|
264
281
|
render: () => (
|
package/src/v3/SingleSelect.tsx
CHANGED
|
@@ -40,6 +40,7 @@ interface SingleSelectBaseProps {
|
|
|
40
40
|
includePlaceholderItem?: boolean;
|
|
41
41
|
disabled?: boolean;
|
|
42
42
|
"aria-describedby"?: string;
|
|
43
|
+
fullWidth?: boolean;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
interface SingleSelectDataProps<T extends DataWithId>
|
|
@@ -80,7 +81,7 @@ export type SingleSelectProps<T extends DataWithId> =
|
|
|
80
81
|
export function SingleSelect<T extends DataWithId>(
|
|
81
82
|
props: SingleSelectProps<T>
|
|
82
83
|
) {
|
|
83
|
-
const { id, placeholder, includePlaceholderItem } = props;
|
|
84
|
+
const { id, placeholder, includePlaceholderItem, fullWidth = true } = props;
|
|
84
85
|
const ariaDescribedBy = props["aria-describedby"];
|
|
85
86
|
|
|
86
87
|
const isChildrenMode = "children" in props && props.children != null;
|
|
@@ -159,7 +160,7 @@ export function SingleSelect<T extends DataWithId>(
|
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
return (
|
|
162
|
-
<Field>
|
|
163
|
+
<Field $fullWidth={fullWidth}>
|
|
163
164
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
164
165
|
<Select.Root
|
|
165
166
|
items={items}
|
package/src/v3/index.ts
CHANGED