reshaped 3.4.5 → 3.4.7
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/CHANGELOG.md +17 -2
- package/dist/bundle.css +1 -1
- package/dist/bundle.d.ts +2 -0
- package/dist/bundle.js +11 -11
- package/dist/components/Autocomplete/Autocomplete.js +25 -8
- package/dist/components/Autocomplete/Autocomplete.module.css +1 -0
- package/dist/components/Autocomplete/tests/Autocomplete.stories.d.ts +15 -1
- package/dist/components/Autocomplete/tests/Autocomplete.stories.js +193 -64
- package/dist/components/Avatar/Avatar.js +2 -2
- package/dist/components/Avatar/Avatar.types.d.ts +4 -1
- package/dist/components/Calendar/Calendar.module.css +1 -1
- package/dist/components/Checkbox/Checkbox.js +17 -4
- package/dist/components/Checkbox/Checkbox.module.css +1 -1
- package/dist/components/Checkbox/Checkbox.types.d.ts +1 -0
- package/dist/components/Checkbox/tests/Checkbox.stories.d.ts +1 -0
- package/dist/components/Checkbox/tests/Checkbox.stories.js +40 -0
- package/dist/components/Dismissible/Dismissible.js +1 -0
- package/dist/components/DropdownMenu/DropdownMenu.js +2 -2
- package/dist/components/FormControl/FormControlLabel.js +2 -7
- package/dist/components/Image/Image.js +4 -4
- package/dist/components/Image/Image.types.d.ts +4 -1
- package/dist/components/NumberField/NumberField.d.ts +6 -0
- package/dist/components/NumberField/NumberField.js +11 -0
- package/dist/components/NumberField/NumberField.module.css +1 -0
- package/dist/components/NumberField/NumberField.types.d.ts +19 -0
- package/dist/components/NumberField/NumberField.types.js +1 -0
- package/dist/components/NumberField/NumberFieldControlled.d.ts +6 -0
- package/dist/components/NumberField/NumberFieldControlled.js +146 -0
- package/dist/components/NumberField/NumberFieldUncontrolled.d.ts +6 -0
- package/dist/components/NumberField/NumberFieldUncontrolled.js +16 -0
- package/dist/components/NumberField/index.d.ts +2 -0
- package/dist/components/NumberField/index.js +1 -0
- package/dist/components/NumberField/tests/NumberField.stories.d.ts +29 -0
- package/dist/components/NumberField/tests/NumberField.stories.js +215 -0
- package/dist/components/PinField/PinField.module.css +1 -1
- package/dist/components/PinField/PinField.types.d.ts +1 -1
- package/dist/components/PinField/PinFieldControlled.js +1 -0
- package/dist/components/PinField/tests/PinField.stories.js +8 -0
- package/dist/components/Radio/Radio.js +11 -4
- package/dist/components/Radio/Radio.module.css +1 -1
- package/dist/components/Radio/Radio.types.d.ts +1 -0
- package/dist/components/Radio/tests/Radio.stories.d.ts +1 -0
- package/dist/components/Radio/tests/Radio.stories.js +31 -0
- package/dist/components/Select/Select.module.css +1 -1
- package/dist/components/Select/Select.types.d.ts +1 -1
- package/dist/components/Select/tests/Select.stories.js +42 -16
- package/dist/components/Switch/Switch.js +10 -4
- package/dist/components/Switch/Switch.module.css +1 -1
- package/dist/components/Switch/Switch.types.d.ts +1 -1
- package/dist/components/Switch/tests/Switch.stories.js +30 -15
- package/dist/components/TextField/TextField.js +1 -1
- package/dist/components/TextField/TextField.module.css +1 -1
- package/dist/components/TextField/TextField.types.d.ts +2 -2
- package/dist/components/TextField/index.d.ts +1 -1
- package/dist/components/TextField/tests/TextField.stories.js +4 -0
- package/dist/hooks/tests/useScrollLock.stories.d.ts +1 -0
- package/dist/hooks/tests/useScrollLock.stories.js +29 -0
- package/dist/icons/ChevronUp.d.ts +2 -0
- package/dist/icons/ChevronUp.js +5 -0
- package/dist/icons/Minus.d.ts +2 -0
- package/dist/icons/Minus.js +3 -0
- package/dist/icons/Plus.d.ts +2 -2
- package/dist/icons/Plus.js +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/utilities/scroll/lock.js +15 -7
- package/dist/utilities/scroll/lockStandard.d.ts +1 -2
- package/dist/utilities/scroll/lockStandard.js +2 -9
- package/package.json +1 -1
- package/dist/components/Autocomplete/tests/Autocomplete.test.stories.d.ts +0 -27
- package/dist/components/Autocomplete/tests/Autocomplete.test.stories.js +0 -86
@@ -7,6 +7,7 @@ import { getActiveElement } from "../../utilities/a11y/index.js";
|
|
7
7
|
import * as keys from "../../constants/keys.js";
|
8
8
|
import useHotkeys from "../../hooks/useHotkeys.js";
|
9
9
|
import useHandlerRef from "../../hooks/useHandlerRef.js";
|
10
|
+
import s from "./Autocomplete.module.css";
|
10
11
|
const AutocompleteContext = React.createContext({});
|
11
12
|
const Autocomplete = (props) => {
|
12
13
|
const { children, onChange, onInput, onItemSelect, name, containerRef, instanceRef, onBackspace, active, onOpen, onClose, ...textFieldProps } = props;
|
@@ -22,6 +23,14 @@ const Autocomplete = (props) => {
|
|
22
23
|
const onOpenRef = useHandlerRef(onOpen);
|
23
24
|
const onCloseRef = useHandlerRef(onClose);
|
24
25
|
const isDropdownActive = hasChildren && (active ?? internalActive);
|
26
|
+
const lockDropdown = () => {
|
27
|
+
// Prevent dropdown from re-opening when clicked on item with mouse
|
28
|
+
// and focus moves to the item and back to the input
|
29
|
+
lockedRef.current = true;
|
30
|
+
setTimeout(() => {
|
31
|
+
lockedRef.current = false;
|
32
|
+
}, 100);
|
33
|
+
};
|
25
34
|
const handleOpen = React.useCallback(() => {
|
26
35
|
if (lockedRef.current)
|
27
36
|
return;
|
@@ -35,12 +44,7 @@ const Autocomplete = (props) => {
|
|
35
44
|
const handleItemClick = (args) => {
|
36
45
|
onChange?.({ value: args.value, name });
|
37
46
|
onItemSelect?.(args);
|
38
|
-
|
39
|
-
// and focus moves to the item and back to the input
|
40
|
-
lockedRef.current = true;
|
41
|
-
setTimeout(() => {
|
42
|
-
lockedRef.current = false;
|
43
|
-
}, 100);
|
47
|
+
lockDropdown();
|
44
48
|
};
|
45
49
|
const handleChange = (args) => {
|
46
50
|
onChange?.(args);
|
@@ -50,6 +54,15 @@ const Autocomplete = (props) => {
|
|
50
54
|
onInput?.({ value: e.currentTarget.value, name, event: e });
|
51
55
|
textFieldProps.inputAttributes?.onInput?.(e);
|
52
56
|
};
|
57
|
+
/**
|
58
|
+
* Make sure focus stays on the input even after user clicks on the content
|
59
|
+
* but outside the items
|
60
|
+
*/
|
61
|
+
const handleContentClick = () => {
|
62
|
+
// Prevent the content from being selected
|
63
|
+
lockDropdown();
|
64
|
+
inputRef.current?.focus();
|
65
|
+
};
|
53
66
|
useHotkeys({
|
54
67
|
[keys.BACKSPACE]: () => {
|
55
68
|
onBackspaceRef.current?.();
|
@@ -79,6 +92,7 @@ const Autocomplete = (props) => {
|
|
79
92
|
onFocus: (e) => {
|
80
93
|
attributes.onFocus?.();
|
81
94
|
textFieldProps.onFocus?.(e);
|
95
|
+
// Only select the value when user clicks on the input
|
82
96
|
if (!lockedRef.current)
|
83
97
|
inputRef.current?.select();
|
84
98
|
},
|
@@ -86,7 +100,7 @@ const Autocomplete = (props) => {
|
|
86
100
|
onClick: attributes.onFocus,
|
87
101
|
ref: inputRef,
|
88
102
|
role: "combobox",
|
89
|
-
} })) }), _jsx(DropdownMenu.Content, { children: children })] }) }));
|
103
|
+
} })) }), _jsx(DropdownMenu.Content, { attributes: { onClick: handleContentClick }, children: children })] }) }));
|
90
104
|
};
|
91
105
|
const AutocompleteItem = (props) => {
|
92
106
|
const { value, data, onClick, ...menuItemProps } = props;
|
@@ -95,7 +109,10 @@ const AutocompleteItem = (props) => {
|
|
95
109
|
onClick?.(e);
|
96
110
|
onItemClick({ value, data });
|
97
111
|
};
|
98
|
-
return (_jsx(DropdownMenu.Item, { ...menuItemProps,
|
112
|
+
return (_jsx(DropdownMenu.Item, { ...menuItemProps, className: [menuItemProps.disabled && s["item--disabled"], menuItemProps.className], attributes: {
|
113
|
+
...menuItemProps.attributes,
|
114
|
+
role: "option",
|
115
|
+
}, onClick: handleClick }));
|
99
116
|
};
|
100
117
|
Autocomplete.Item = AutocompleteItem;
|
101
118
|
Autocomplete.displayName = "Autocomplete";
|
@@ -0,0 +1 @@
|
|
1
|
+
.item--disabled{pointer-events:none}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import React from "react";
|
2
2
|
import { StoryObj } from "@storybook/react";
|
3
|
+
import { Mock } from "@storybook/test";
|
3
4
|
declare const _default: {
|
4
5
|
title: string;
|
5
6
|
component: {
|
@@ -17,7 +18,20 @@ declare const _default: {
|
|
17
18
|
};
|
18
19
|
};
|
19
20
|
export default _default;
|
20
|
-
export declare const
|
21
|
+
export declare const foo: () => React.JSX.Element;
|
22
|
+
export declare const active: StoryObj<{
|
23
|
+
handleOpen: Mock;
|
24
|
+
handleClose: Mock;
|
25
|
+
}>;
|
26
|
+
export declare const base: StoryObj<{
|
27
|
+
handleInput: Mock;
|
28
|
+
handleItemSelect: Mock;
|
29
|
+
handleBackspace: Mock;
|
30
|
+
}>;
|
31
|
+
export declare const itemData: StoryObj<{
|
32
|
+
handleItemSelect: Mock;
|
33
|
+
}>;
|
34
|
+
export declare const itemDisabled: StoryObj;
|
21
35
|
export declare const multiselect: {
|
22
36
|
name: string;
|
23
37
|
render: () => React.JSX.Element;
|
@@ -1,16 +1,12 @@
|
|
1
1
|
import React from "react";
|
2
|
+
import { fn, expect, within, waitFor, userEvent, fireEvent } from "@storybook/test";
|
2
3
|
import { Example } from "../../../utilities/storybook/index.js";
|
3
4
|
import Autocomplete from "../index.js";
|
4
5
|
import View from "../../View/index.js";
|
5
6
|
import Badge from "../../Badge/index.js";
|
6
7
|
import useToggle from "../../../hooks/useToggle.js";
|
7
|
-
import Modal from "../../Modal/index.js";
|
8
|
-
import TextField from "../../TextField/index.js";
|
9
|
-
import Text from "../../Text/index.js";
|
10
|
-
import Button from "../../Button/index.js";
|
11
|
-
import PlusIcon from "../../../icons/Plus.js";
|
12
|
-
import Dismissible from "../../Dismissible/index.js";
|
13
8
|
import FormControl from "../../FormControl/index.js";
|
9
|
+
import { sleep } from "../../../utilities/helpers.js";
|
14
10
|
export default {
|
15
11
|
title: "Components/Autocomplete",
|
16
12
|
component: Autocomplete,
|
@@ -20,16 +16,80 @@ export default {
|
|
20
16
|
},
|
21
17
|
},
|
22
18
|
};
|
19
|
+
export const foo = () => {
|
20
|
+
const [value, setValue] = React.useState("");
|
21
|
+
const options = ["Pizza", "Pie", "Ice-cream"];
|
22
|
+
return (<FormControl>
|
23
|
+
<FormControl.Label>Favorite food</FormControl.Label>
|
24
|
+
<Autocomplete name="fruit" placeholder="Pick your option" value={value} onChange={(args) => setValue(args.value)}>
|
25
|
+
{options.map((option) => (<Autocomplete.Item key={option} value={option}>
|
26
|
+
{option}
|
27
|
+
</Autocomplete.Item>))}
|
28
|
+
</Autocomplete>
|
29
|
+
</FormControl>);
|
30
|
+
};
|
31
|
+
export const active = {
|
32
|
+
name: "active, onOpen, onClose",
|
33
|
+
args: {
|
34
|
+
handleClose: fn(),
|
35
|
+
handleOpen: fn(),
|
36
|
+
},
|
37
|
+
render: (args) => {
|
38
|
+
const toggle = useToggle(true);
|
39
|
+
return (<Example>
|
40
|
+
<Example.Item title="active, onOpen, onClose">
|
41
|
+
<FormControl>
|
42
|
+
<FormControl.Label>Label</FormControl.Label>
|
43
|
+
<Autocomplete name="fruit" placeholder="Pick your food" active={toggle.active} onOpen={() => {
|
44
|
+
args.handleOpen();
|
45
|
+
toggle.activate();
|
46
|
+
}} onClose={() => {
|
47
|
+
args.handleClose();
|
48
|
+
toggle.deactivate();
|
49
|
+
}}>
|
50
|
+
{["Pizza", "Pie", "Ice-cream"].map((v, i) => {
|
51
|
+
return (<Autocomplete.Item key={v} value={v}>
|
52
|
+
{v}
|
53
|
+
</Autocomplete.Item>);
|
54
|
+
})}
|
55
|
+
</Autocomplete>
|
56
|
+
</FormControl>
|
57
|
+
</Example.Item>
|
58
|
+
</Example>);
|
59
|
+
},
|
60
|
+
play: async ({ canvasElement, args }) => {
|
61
|
+
const canvas = within(canvasElement.ownerDocument.body);
|
62
|
+
const input = canvas.getByRole("combobox");
|
63
|
+
const list = canvas.getByRole("listbox");
|
64
|
+
expect(list).toBeInTheDocument();
|
65
|
+
expect(args.handleOpen).not.toHaveBeenCalled();
|
66
|
+
await userEvent.click(document.body);
|
67
|
+
await waitFor(() => {
|
68
|
+
expect(args.handleClose).toHaveBeenCalledTimes(1);
|
69
|
+
expect(args.handleClose).toHaveBeenLastCalledWith();
|
70
|
+
});
|
71
|
+
await userEvent.click(input);
|
72
|
+
await waitFor(() => {
|
73
|
+
expect(args.handleOpen).toHaveBeenCalledTimes(1);
|
74
|
+
expect(args.handleOpen).toHaveBeenLastCalledWith();
|
75
|
+
});
|
76
|
+
},
|
77
|
+
};
|
23
78
|
export const base = {
|
24
|
-
name: "
|
25
|
-
|
79
|
+
name: "onInput, onItemSelect, onBackspace",
|
80
|
+
args: {
|
81
|
+
handleInput: fn(),
|
82
|
+
handleBackspace: fn(),
|
83
|
+
handleItemSelect: fn(),
|
84
|
+
},
|
85
|
+
render: (args) => {
|
26
86
|
const [value, setValue] = React.useState("");
|
27
87
|
return (<Example>
|
28
88
|
<Example.Item title="Base">
|
29
89
|
<FormControl>
|
30
90
|
<FormControl.Label>Food</FormControl.Label>
|
31
|
-
<Autocomplete name="fruit" placeholder="Pick your food" value={value} onChange={(args) => setValue(args.value)}>
|
32
|
-
{["Pizza", "Pie", "Ice-cream"].map((v) => {
|
91
|
+
<Autocomplete name="fruit" placeholder="Pick your food" value={value} onChange={(args) => setValue(args.value)} onBackspace={args.handleBackspace} onItemSelect={args.handleItemSelect}>
|
92
|
+
{["Pizza", "Pie", "Ice-cream"].map((v, i) => {
|
33
93
|
return (<Autocomplete.Item key={v} value={v}>
|
34
94
|
{v}
|
35
95
|
</Autocomplete.Item>);
|
@@ -39,54 +99,148 @@ export const base = {
|
|
39
99
|
</Example.Item>
|
40
100
|
</Example>);
|
41
101
|
},
|
102
|
+
play: async ({ canvasElement, args }) => {
|
103
|
+
const canvas = within(canvasElement.ownerDocument.body);
|
104
|
+
const input = canvas.getByRole("combobox");
|
105
|
+
// Reset the focus
|
106
|
+
document.body.focus();
|
107
|
+
// Test keyboard selection after focusing the input
|
108
|
+
input.focus();
|
109
|
+
let options = [];
|
110
|
+
await waitFor(() => {
|
111
|
+
options = canvas.getAllByRole("option");
|
112
|
+
});
|
113
|
+
expect(options).toHaveLength(3);
|
114
|
+
await waitFor(() => {
|
115
|
+
expect(options[0]).toHaveAttribute("data-rs-focus");
|
116
|
+
});
|
117
|
+
expect(options[1]).not.toHaveAttribute("data-rs-focus");
|
118
|
+
await userEvent.keyboard("{ArrowDown/}");
|
119
|
+
await userEvent.keyboard("{Enter/}");
|
120
|
+
expect(input).toHaveValue("Pie");
|
121
|
+
expect(args.handleItemSelect).toHaveBeenCalledTimes(1);
|
122
|
+
expect(args.handleItemSelect).toHaveBeenCalledWith({
|
123
|
+
value: "Pie",
|
124
|
+
data: undefined,
|
125
|
+
});
|
126
|
+
// Give browser time to focus on the input
|
127
|
+
await sleep(100);
|
128
|
+
// Test click selection after opening with down arrow
|
129
|
+
await userEvent.keyboard("{ArrowDown/}");
|
130
|
+
await waitFor(() => {
|
131
|
+
options = canvas.getAllByRole("option");
|
132
|
+
});
|
133
|
+
await userEvent.click(options[0]);
|
134
|
+
expect(input).toHaveValue("Pizza");
|
135
|
+
expect(args.handleItemSelect).toHaveBeenCalledTimes(2);
|
136
|
+
expect(args.handleItemSelect).toHaveBeenCalledWith({
|
137
|
+
value: "Pizza",
|
138
|
+
});
|
139
|
+
input.focus();
|
140
|
+
await userEvent.keyboard("{Backspace/}");
|
141
|
+
await waitFor(() => {
|
142
|
+
expect(args.handleBackspace).toHaveBeenCalledTimes(1);
|
143
|
+
expect(args.handleBackspace).toHaveBeenCalledWith();
|
144
|
+
});
|
145
|
+
},
|
146
|
+
};
|
147
|
+
export const itemData = {
|
148
|
+
name: "item data",
|
149
|
+
args: {
|
150
|
+
handleItemSelect: fn(),
|
151
|
+
},
|
152
|
+
render: (args) => {
|
153
|
+
return (<Example>
|
154
|
+
<Example.Item title="item data">
|
155
|
+
<FormControl>
|
156
|
+
<FormControl.Label>Label</FormControl.Label>
|
157
|
+
<Autocomplete name="fruit" placeholder="Pick your food" onItemSelect={args.handleItemSelect} active>
|
158
|
+
{["Pizza", "Pie", "Ice-cream"].map((v, i) => {
|
159
|
+
return (<Autocomplete.Item key={v} value={v} data={i === 1 ? { foo: true } : undefined}>
|
160
|
+
{v}
|
161
|
+
</Autocomplete.Item>);
|
162
|
+
})}
|
163
|
+
</Autocomplete>
|
164
|
+
</FormControl>
|
165
|
+
</Example.Item>
|
166
|
+
</Example>);
|
167
|
+
},
|
168
|
+
play: async ({ canvasElement, args }) => {
|
169
|
+
const canvas = within(canvasElement.ownerDocument.body);
|
170
|
+
const options = canvas.getAllByRole("option");
|
171
|
+
await userEvent.click(options[0]);
|
172
|
+
expect(args.handleItemSelect).toHaveBeenLastCalledWith({ value: "Pizza" });
|
173
|
+
await userEvent.click(options[1]);
|
174
|
+
expect(args.handleItemSelect).toHaveBeenLastCalledWith({ value: "Pie", data: { foo: true } });
|
175
|
+
},
|
176
|
+
};
|
177
|
+
export const itemDisabled = {
|
178
|
+
name: "item disabled",
|
179
|
+
render: () => {
|
180
|
+
return (<Example>
|
181
|
+
<Example.Item title="item disabled">
|
182
|
+
<FormControl>
|
183
|
+
<FormControl.Label>Label</FormControl.Label>
|
184
|
+
<Autocomplete name="fruit" placeholder="Pick your food" active>
|
185
|
+
{["Pizza", "Pie", "Ice-cream"].map((v, i) => {
|
186
|
+
return (<Autocomplete.Item key={v} value={v} disabled={i === 1}>
|
187
|
+
{v}
|
188
|
+
</Autocomplete.Item>);
|
189
|
+
})}
|
190
|
+
</Autocomplete>
|
191
|
+
</FormControl>
|
192
|
+
</Example.Item>
|
193
|
+
</Example>);
|
194
|
+
},
|
195
|
+
play: async ({ canvasElement }) => {
|
196
|
+
const canvas = within(canvasElement.ownerDocument.body);
|
197
|
+
const input = canvas.getByRole("combobox");
|
198
|
+
const options = canvas.getAllByRole("option");
|
199
|
+
await fireEvent.click(options[1]);
|
200
|
+
expect(options[1]).toBeDisabled();
|
201
|
+
// Check that focus stays on input when clicking on disabled elements
|
202
|
+
expect(document.activeElement).toBe(input);
|
203
|
+
},
|
42
204
|
};
|
43
205
|
export const multiselect = {
|
44
|
-
name: "multiselect",
|
206
|
+
name: "test: multiselect",
|
45
207
|
render: () => {
|
208
|
+
const options = [
|
209
|
+
"Pizza",
|
210
|
+
"Pie",
|
211
|
+
"Ice-cream",
|
212
|
+
"Fries",
|
213
|
+
"Salad",
|
214
|
+
"Option 4",
|
215
|
+
"Option 5",
|
216
|
+
"Option 6",
|
217
|
+
];
|
46
218
|
const inputRef = React.useRef(null);
|
47
|
-
const [
|
48
|
-
|
219
|
+
const [values, setValues] = React.useState([
|
220
|
+
"Option 4",
|
221
|
+
"Option 5",
|
222
|
+
"Option 6",
|
223
|
+
"Pizza",
|
224
|
+
"Ice-cream",
|
225
|
+
]);
|
49
226
|
const [query, setQuery] = React.useState("");
|
50
|
-
const [customValueQuery, setCustomValueQuery] = React.useState("");
|
51
|
-
const customValueToggle = useToggle();
|
52
227
|
const handleDismiss = (dismissedValue) => {
|
53
228
|
const nextValues = values.filter((value) => value !== dismissedValue);
|
54
229
|
setValues(nextValues);
|
55
230
|
inputRef.current?.focus();
|
56
231
|
};
|
57
|
-
const handleAddCustomValue = () => {
|
58
|
-
if (customValueQuery.length) {
|
59
|
-
setValues((prev) => [...prev, customValueQuery]);
|
60
|
-
}
|
61
|
-
customValueToggle.deactivate();
|
62
|
-
setCustomValueQuery("");
|
63
|
-
};
|
64
232
|
const valuesNode = !!values.length && (<View direction="row" gap={1}>
|
65
|
-
{values.map((value) => (<Badge dismissAriaLabel="Dismiss value" onDismiss={() => handleDismiss(value)} key={value}
|
233
|
+
{values.map((value) => (<Badge dismissAriaLabel="Dismiss value" onDismiss={() => handleDismiss(value)} key={value}>
|
66
234
|
{value}
|
67
235
|
</Badge>))}
|
68
236
|
</View>);
|
69
237
|
return (<FormControl>
|
70
238
|
<FormControl.Label>Food</FormControl.Label>
|
71
|
-
<Autocomplete name="fruit" value={query} placeholder="Pick your food" startSlot={valuesNode} inputAttributes={{ ref: inputRef }}
|
72
|
-
if (!query.length)
|
73
|
-
handleDismiss(values[values.length - 1]);
|
74
|
-
}} onOpen={() => {
|
75
|
-
setActive(true);
|
76
|
-
}} onClose={(args) => {
|
77
|
-
if (args.reason === "item-selection")
|
78
|
-
return;
|
79
|
-
setActive(false);
|
80
|
-
}} active={active} multiline onChange={(args) => setQuery(args.value)} onItemSelect={(args) => {
|
81
|
-
setCustomValueQuery(query);
|
239
|
+
<Autocomplete name="fruit" value={query} placeholder="Pick your food" startSlot={valuesNode} multiline inputAttributes={{ ref: inputRef }} onChange={(args) => setQuery(args.value)} onItemSelect={(args) => {
|
82
240
|
setQuery("");
|
83
|
-
if (args.value === "_custom") {
|
84
|
-
customValueToggle.activate();
|
85
|
-
return;
|
86
|
-
}
|
87
241
|
setValues((prev) => [...prev, args.value]);
|
88
242
|
}}>
|
89
|
-
{
|
243
|
+
{options.map((v) => {
|
90
244
|
if (!v.toLowerCase().includes(query.toLowerCase()))
|
91
245
|
return;
|
92
246
|
if (values.includes(v))
|
@@ -95,32 +249,7 @@ export const multiselect = {
|
|
95
249
|
{v}
|
96
250
|
</Autocomplete.Item>);
|
97
251
|
})}
|
98
|
-
{!!query.length && (<Autocomplete.Item value="_custom" icon={PlusIcon}>
|
99
|
-
Add a custom value
|
100
|
-
</Autocomplete.Item>)}
|
101
252
|
</Autocomplete>
|
102
|
-
<Modal onClose={customValueToggle.deactivate} active={customValueToggle.active}>
|
103
|
-
<View gap={4}>
|
104
|
-
<Dismissible onClose={customValueToggle.deactivate} closeAriaLabel="Close modal">
|
105
|
-
<Modal.Title>
|
106
|
-
<Text variant="body-3" weight="medium">
|
107
|
-
Add a custom value
|
108
|
-
</Text>
|
109
|
-
</Modal.Title>
|
110
|
-
</Dismissible>
|
111
|
-
<View direction="row" gap={3} as="form" attributes={{
|
112
|
-
onSubmit: (e) => {
|
113
|
-
e.preventDefault();
|
114
|
-
handleAddCustomValue();
|
115
|
-
},
|
116
|
-
}}>
|
117
|
-
<View.Item grow>
|
118
|
-
<TextField name="custom" onChange={(args) => setCustomValueQuery(args.value)} value={customValueQuery}/>
|
119
|
-
</View.Item>
|
120
|
-
<Button type="submit">Add</Button>
|
121
|
-
</View>
|
122
|
-
</View>
|
123
|
-
</Modal>
|
124
253
|
</FormControl>);
|
125
254
|
},
|
126
255
|
};
|
@@ -27,8 +27,8 @@ const Avatar = (props) => {
|
|
27
27
|
const imageAttributes = {
|
28
28
|
...passedImageAttributes,
|
29
29
|
role: !alt ? "presentation" : undefined,
|
30
|
-
src,
|
31
|
-
alt,
|
30
|
+
src: src ?? "",
|
31
|
+
alt: alt ?? "",
|
32
32
|
className: s.img,
|
33
33
|
};
|
34
34
|
// eslint-disable-next-line jsx-a11y/alt-text
|
@@ -4,7 +4,10 @@ export type Props = {
|
|
4
4
|
src?: string;
|
5
5
|
alt?: string;
|
6
6
|
imageAttributes?: G.Attributes<"img">;
|
7
|
-
renderImage?: (attributes: G.Attributes<"img">
|
7
|
+
renderImage?: (attributes: Omit<G.Attributes<"img">, "src" | "alt"> & {
|
8
|
+
src: string;
|
9
|
+
alt: string;
|
10
|
+
}) => React.ReactNode;
|
8
11
|
initials?: string;
|
9
12
|
icon?: IconProps["svg"];
|
10
13
|
squared?: boolean;
|
@@ -1 +1 @@
|
|
1
|
-
.selection{table-layout:fixed;width:100%}.weekday{color:var(--rs-color-foreground-neutral-faded);font-weight:var(--rs-font-weight-regular);padding-bottom:var(--rs-unit-x2)}[dir=rtl] .control{transform:scaleX(-1)}.cell{isolation:isolate;padding:2px}.cell:hover .cell-button{background-color:rgba(var(--rs-color-rgb-background-neutral),32%)}.cell.--active .cell-button{background-color:var(--rs-color-background-primary);color:var(--rs-color-on-background-primary)}.cell.--selection-range .cell-button{background-color:rgba(var(--rs-color-rgb-background-neutral),32%)}.cell.--selection-range:not(:last-child) .cell-button,.cell.--selection-start:not(:last-child) .cell-button{border-end-end-radius:0;border-start-end-radius:0}.cell.--selection-end:not(:first-child) .cell-button,.cell.--selection-range+:hover .cell-button,.cell.--selection-range:not(:first-child) .cell-button,.cell.--selection-start+:hover .cell-button{border-end-start-radius:0;border-start-start-radius:0}.cell.--selection-range+.--selection-end .cell-button:before,.cell.--selection-range+.--selection-range .cell-button:before,.cell.--selection-range+:hover .cell-button:before,.cell.--selection-start+.--selection-range .cell-button:before,.cell.--selection-start+:hover .cell-button:before{background-color:rgba(var(--rs-color-rgb-background-neutral),32%)}
|
1
|
+
@layer rs.calendar.base;@layer rs.calendar.range;@layer rs.calendar.disabled;@layer rs.calendar.base{.selection{table-layout:fixed;width:100%}.weekday{color:var(--rs-color-foreground-neutral-faded);font-weight:var(--rs-font-weight-regular);padding-bottom:var(--rs-unit-x2)}[dir=rtl] .control{transform:scaleX(-1)}.cell{isolation:isolate;padding:2px}.cell:hover .cell-button{background-color:rgba(var(--rs-color-rgb-background-neutral),32%)}.cell.--active .cell-button{background-color:var(--rs-color-background-primary);color:var(--rs-color-on-background-primary)}.cell-button{border-radius:var(--rs-radius-small);padding:var(--rs-unit-x2);position:relative;text-align:center;transition:var(--rs-duration-fast) var(--rs-easing-standard);transition-property:color,background-color,border-radius}.cell-button:before{content:"";inset-block:0;inset-inline-start:-4px;position:absolute;transition:var(--rs-duration-fast) var(--rs-easing-standard);transition-property:background-color;width:4px}}@layer rs.calendar.selection{.cell.--selection-range .cell-button{background-color:rgba(var(--rs-color-rgb-background-neutral),32%);color:var(--rs-color-foreground-neutral)}.cell.--selection-range:not(:last-child) .cell-button,.cell.--selection-start:not(:last-child) .cell-button{border-end-end-radius:0;border-start-end-radius:0}.cell.--selection-end:not(:first-child) .cell-button,.cell.--selection-range+:hover .cell-button,.cell.--selection-range:not(:first-child) .cell-button,.cell.--selection-start+:hover .cell-button{border-end-start-radius:0;border-start-start-radius:0}.cell.--selection-range+.--selection-end .cell-button:before,.cell.--selection-range+.--selection-range .cell-button:before,.cell.--selection-range+:hover .cell-button:before,.cell.--selection-start+.--selection-range .cell-button:before,.cell.--selection-start+:hover .cell-button:before{background-color:rgba(var(--rs-color-rgb-background-neutral),32%)}}@layer rs.calendar.disabled{.cell-button[disabled]{background-color:transparent;color:var(--rs-color-foreground-disabled)}}
|
@@ -1,16 +1,17 @@
|
|
1
1
|
"use client";
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
3
3
|
import React from "react";
|
4
|
-
import { classNames } from "../../utilities/helpers.js";
|
4
|
+
import { classNames, responsiveClassNames, responsivePropDependency } from "../../utilities/helpers.js";
|
5
5
|
import useIsomorphicLayoutEffect from "../../hooks/useIsomorphicLayoutEffect.js";
|
6
6
|
import HiddenInput from "../_private/HiddenInput/index.js";
|
7
7
|
import { useFormControl } from "../FormControl/index.js";
|
8
8
|
import { useCheckboxGroup } from "../CheckboxGroup/index.js";
|
9
9
|
import Icon from "../Icon/index.js";
|
10
|
+
import Text from "../Text/index.js";
|
10
11
|
import IconCheckmark from "../../icons/Checkmark.js";
|
11
12
|
import s from "./Checkbox.module.css";
|
12
13
|
const Checkbox = (props) => {
|
13
|
-
const { children, value, onChange, onFocus, onBlur, indeterminate, className, attributes, inputAttributes, } = props;
|
14
|
+
const { children, value, onChange, onFocus, onBlur, indeterminate, size = "medium", className, attributes, inputAttributes, } = props;
|
14
15
|
const checkboxGroup = useCheckboxGroup();
|
15
16
|
const formControl = useFormControl();
|
16
17
|
const hasError = formControl?.hasError || props.hasError || checkboxGroup?.hasError;
|
@@ -19,7 +20,7 @@ const Checkbox = (props) => {
|
|
19
20
|
const defaultChecked = checkboxGroup ? undefined : props.defaultChecked;
|
20
21
|
const name = checkboxGroup ? checkboxGroup.name : props.name;
|
21
22
|
const inputRef = React.useRef(null);
|
22
|
-
const rootClassName = classNames(s.root, className, hasError && s["--error"], disabled && s["--disabled"]);
|
23
|
+
const rootClassName = classNames(s.root, className, size && hasError && s["--error"], disabled && s["--disabled"], size && responsiveClassNames(s, "--size", size));
|
23
24
|
const handleChange = (event) => {
|
24
25
|
if (!name)
|
25
26
|
return;
|
@@ -36,7 +37,19 @@ const Checkbox = (props) => {
|
|
36
37
|
return (_jsxs("label", { ...attributes, className: rootClassName, children: [_jsxs("span", { className: s.field, children: [_jsx(HiddenInput, { className: s.input, type: "checkbox", checked: checked, defaultChecked: defaultChecked, name: name, disabled: disabled, value: value, onChange: handleChange, onFocus: onFocus, onBlur: onBlur, attributes: {
|
37
38
|
...inputAttributes,
|
38
39
|
ref: inputRef,
|
39
|
-
} }), _jsx("div", { className: s.decorator, children: _jsx(Icon, { svg: IconCheckmark, className: s.icon
|
40
|
+
} }), _jsx("div", { className: s.decorator, children: _jsx(Icon, { svg: IconCheckmark, className: s.icon, size: responsivePropDependency(size, (size) => {
|
41
|
+
if (size === "large")
|
42
|
+
return 5;
|
43
|
+
if (size === "small")
|
44
|
+
return 3;
|
45
|
+
return 4;
|
46
|
+
}) }) })] }), children && (_jsx(Text, { as: "span", variant: responsivePropDependency(size, (size) => {
|
47
|
+
if (size === "large")
|
48
|
+
return "body-2";
|
49
|
+
if (size === "small")
|
50
|
+
return "caption-1";
|
51
|
+
return "body-3";
|
52
|
+
}), children: children }))] }));
|
40
53
|
};
|
41
54
|
Checkbox.displayName = "Checkbox";
|
42
55
|
export default Checkbox;
|
@@ -1 +1 @@
|
|
1
|
-
.root{align-items:center;cursor:pointer;display:inline-flex;gap:var(--rs-
|
1
|
+
@layer rs.checkbox.base;@layer rs.checkbox.error;@layer rs.checkbox.checked;@layer rs.checkbox.disabled;@layer rs.checkbox.base{.root{align-items:center;cursor:pointer;display:inline-flex;gap:var(--rs-checkbox-gap);user-select:none;vertical-align:top;-webkit-tap-highlight-color:transparent}.root:hover .decorator{background:var(--rs-color-background-neutral-faded)}.field{position:relative}.decorator{background:var(--rs-color-background-elevation-base);border:1px solid var(--rs-color-border-neutral);border-radius:var(--rs-radius-small);color:var(--rs-color-on-background-primary);height:var(--rs-checkbox-line-height);transition:var(--rs-duration-fast) var(--rs-easing-standard);transition-property:background-color,border-color;width:var(--rs-checkbox-line-height)}.decorator:before{background:var(--rs-color-on-background-primary);border-radius:999px;content:"";height:1.5px;left:50%;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%) scale(0);transition:var(--rs-duration-fast) var(--rs-easing-standard);transition-property:opacity,transform;width:calc(var(--rs-checkbox-line-height) * .5)}.--size-small{--rs-checkbox-line-height:var(--rs-line-height-caption-1);--rs-checkbox-gap:var(--rs-unit-x1)}.--size-medium{--rs-checkbox-line-height:var(--rs-line-height-body-3);--rs-checkbox-gap:var(--rs-unit-x2)}.--size-large{--rs-checkbox-line-height:var(--rs-line-height-body-2);--rs-checkbox-gap:var(--rs-unit-x2)}.icon{left:50%;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%) scale(0);transition:var(--rs-duration-fast) var(--rs-easing-standard);transition-property:opacity,transform}[data-rs-keyboard] .input:focus-visible+.decorator{box-shadow:var(--rs-focus-shadow)}}@layer rs.checkbox.error{.root.--error .decorator{border-color:var(--rs-color-border-critical)}}@layer rs.checkbox.checked{.input:checked+.decorator,.input:indeterminate+.decorator{background:var(--rs-color-background-primary);border-color:var(--rs-color-background-primary)}.input:checked+.decorator .icon,.input:indeterminate+.decorator:before{opacity:1;transform:translate(-50%,-50%) scale(1)}}@layer rs.checkbox.disabled{.root.--disabled{color:var(--rs-color-foreground-disabled);cursor:not-allowed}.root.--disabled .decorator{background:var(--rs-color-background-disabled-faded);border-color:var(--rs-color-border-disabled);color:var(--rs-color-foreground-disabled)}.root.--disabled .input:checked+.decorator,.root.--disabled .input:indeterminate+.decorator{background:var(--rs-color-background-disabled);border-color:transparent}.root.--disabled .input:checked+.decorator:before,.root.--disabled .input:indeterminate+.decorator:before{background-color:var(--rs-color-foreground-disabled)}}@media (--rs-viewport-m ){.--size-small--m{--rs-checkbox-line-height:var(--rs-line-height-caption-1);--rs-checkbox-gap:var(--rs-unit-x1)}.--size-medium--m{--rs-checkbox-line-height:var(--rs-line-height-body-3);--rs-checkbox-gap:var(--rs-unit-x2)}.--size-large--m{--rs-checkbox-line-height:var(--rs-line-height-body-2);--rs-checkbox-gap:var(--rs-unit-x2)}}@media (--rs-viewport-l ){.--size-small--l{--rs-checkbox-line-height:var(--rs-line-height-caption-1);--rs-checkbox-gap:var(--rs-unit-x1)}.--size-medium--l{--rs-checkbox-line-height:var(--rs-line-height-body-3);--rs-checkbox-gap:var(--rs-unit-x2)}.--size-large--l{--rs-checkbox-line-height:var(--rs-line-height-body-2);--rs-checkbox-gap:var(--rs-unit-x2)}}@media (--rs-viewport-xl ){.--size-small--xl{--rs-checkbox-line-height:var(--rs-line-height-caption-1);--rs-checkbox-gap:var(--rs-unit-x1)}.--size-medium--xl{--rs-checkbox-line-height:var(--rs-line-height-body-3);--rs-checkbox-gap:var(--rs-unit-x2)}.--size-large--xl{--rs-checkbox-line-height:var(--rs-line-height-body-2);--rs-checkbox-gap:var(--rs-unit-x2)}}
|
@@ -12,5 +12,6 @@ declare const _default: {
|
|
12
12
|
};
|
13
13
|
export default _default;
|
14
14
|
export declare const selection: () => import("react").JSX.Element;
|
15
|
+
export declare const size: () => import("react").JSX.Element;
|
15
16
|
export declare const error: () => import("react").JSX.Element;
|
16
17
|
export declare const disabled: () => import("react").JSX.Element;
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { Example } from "../../../utilities/storybook/index.js";
|
2
2
|
import Checkbox from "../index.js";
|
3
|
+
import View from "../../View/index.js";
|
3
4
|
export default {
|
4
5
|
title: "Components/Checkbox",
|
5
6
|
component: Checkbox,
|
@@ -34,6 +35,45 @@ export const selection = () => (<Example>
|
|
34
35
|
</Checkbox>
|
35
36
|
</Example.Item>
|
36
37
|
</Example>);
|
38
|
+
export const size = () => (<Example>
|
39
|
+
<Example.Item title="size: small">
|
40
|
+
<View gap={4} direction="row">
|
41
|
+
<Checkbox name="animal" value="dog" size="small" defaultChecked>
|
42
|
+
Checkbox
|
43
|
+
</Checkbox>
|
44
|
+
<Checkbox name="animal" value="dog" size="small" indeterminate>
|
45
|
+
Checkbox
|
46
|
+
</Checkbox>
|
47
|
+
</View>
|
48
|
+
</Example.Item>
|
49
|
+
<Example.Item title="size: medium">
|
50
|
+
<View gap={4} direction="row">
|
51
|
+
<Checkbox name="animal" value="dog" size="medium" defaultChecked>
|
52
|
+
Checkbox
|
53
|
+
</Checkbox>
|
54
|
+
<Checkbox name="animal" value="dog" size="medium" indeterminate>
|
55
|
+
Checkbox
|
56
|
+
</Checkbox>
|
57
|
+
</View>
|
58
|
+
</Example.Item>
|
59
|
+
<Example.Item title="size: large">
|
60
|
+
<View gap={4} direction="row">
|
61
|
+
<Checkbox name="animal" value="dog" size="large" defaultChecked>
|
62
|
+
Checkbox
|
63
|
+
</Checkbox>
|
64
|
+
<Checkbox name="animal" value="dog" size="large" indeterminate>
|
65
|
+
Checkbox
|
66
|
+
</Checkbox>
|
67
|
+
</View>
|
68
|
+
</Example.Item>
|
69
|
+
<Example.Item title="size: responsive, s: small, m: large">
|
70
|
+
<View gap={4} direction="row">
|
71
|
+
<Checkbox name="animal" value="dog" size={{ s: "small", m: "large" }} defaultChecked>
|
72
|
+
Checkbox
|
73
|
+
</Checkbox>
|
74
|
+
</View>
|
75
|
+
</Example.Item>
|
76
|
+
</Example>);
|
37
77
|
export const error = () => (<Example>
|
38
78
|
<Example.Item title="error">
|
39
79
|
<Checkbox name="animal" value="dog" hasError>
|
@@ -13,7 +13,7 @@ import * as keys from "../../constants/keys.js";
|
|
13
13
|
import s from "./DropdownMenu.module.css";
|
14
14
|
const DropdownMenuSubContext = React.createContext(null);
|
15
15
|
const DropdownMenu = (props) => {
|
16
|
-
const { children, position = "bottom-start", triggerType = "click", trapFocusMode = "action-
|
16
|
+
const { children, position = "bottom-start", triggerType = "click", trapFocusMode = "action-menu", ...popoverProps } = props;
|
17
17
|
return (_jsx(Popover, { ...popoverProps, position: position, padding: 0, trapFocusMode: trapFocusMode, triggerType: triggerType, children: children }));
|
18
18
|
};
|
19
19
|
const DropdownMenuContent = (props) => {
|
@@ -50,7 +50,7 @@ const DropdownMenuItem = (props) => {
|
|
50
50
|
if (onClick)
|
51
51
|
onClick(e);
|
52
52
|
};
|
53
|
-
return (_jsx(MenuItem, { ...props, roundedCorners: true, className: s.item, attributes: { role: "menuitem", ...props.attributes }, onClick: handleClick }));
|
53
|
+
return (_jsx(MenuItem, { ...props, roundedCorners: true, className: [s.item, props.className], attributes: { role: "menuitem", ...props.attributes }, onClick: handleClick }));
|
54
54
|
};
|
55
55
|
const DropdownMenuSubMenu = (props) => {
|
56
56
|
const { children } = props;
|
@@ -7,13 +7,8 @@ const FormControlLabel = (props) => {
|
|
7
7
|
const { children } = props;
|
8
8
|
const { attributes, required, group, disabled, size } = useFormControlPrivate();
|
9
9
|
const id = `${attributes.id}-label`;
|
10
|
-
const
|
11
|
-
|
12
|
-
: {
|
13
|
-
as: "label",
|
14
|
-
attributes: { id, htmlFor: attributes.id },
|
15
|
-
};
|
16
|
-
return (_jsxs(Text, { ...tagProps, variant: size === "large" ? "body-2" : "body-3", weight: "medium", className: s.label, color: disabled ? "disabled" : undefined, "aria-disabled": disabled, children: [children, required && (_jsx(Text, { color: disabled ? "disabled" : "critical", as: "span", children: "*" }))] }));
|
10
|
+
const TagName = group ? "legend" : "label";
|
11
|
+
return (_jsxs(Text, { variant: size === "large" ? "body-2" : "body-3", weight: "medium", className: s.label, color: disabled ? "disabled" : undefined, "aria-disabled": disabled, children: [_jsx(TagName, { id: id, htmlFor: group ? undefined : attributes.id, children: children }), required && (_jsx(Text, { color: disabled ? "disabled" : "critical", as: "span", children: "*" }))] }));
|
17
12
|
};
|
18
13
|
FormControlLabel.displayName = "FormControl.Label";
|
19
14
|
export default FormControlLabel;
|