@jobber/components 8.26.2 → 8.27.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/Select.d.ts +1 -0
- package/Select.js +17 -0
- package/dist/ComboboxChipRemove-cjs.js +2 -1
- package/dist/ComboboxChipRemove-es.js +2 -1
- package/dist/FieldDescription-cjs.js +880 -0
- package/dist/FieldDescription-es.js +856 -0
- package/dist/InputNumberExperimental-cjs.js +5 -875
- package/dist/InputNumberExperimental-es.js +2 -854
- package/dist/MenuSubmenuTrigger-es.js +1 -1
- package/dist/NumberFieldInput-cjs.js +2 -2
- package/dist/NumberFieldInput-es.js +1 -1
- package/dist/Select/Select.d.ts +31 -0
- package/dist/Select/Select.types.d.ts +145 -0
- package/dist/Select/SelectBottomSheet.d.ts +4 -0
- package/dist/Select/SelectDropdown.d.ts +4 -0
- package/dist/Select/index.cjs +41 -0
- package/dist/Select/index.d.ts +2 -0
- package/dist/Select/index.mjs +35 -0
- package/dist/Select-cjs.js +231 -0
- package/dist/Select-es.js +229 -0
- package/dist/SelectPrimitive-cjs.js +20 -3
- package/dist/SelectPrimitive-es.js +20 -3
- package/dist/docs/Dialog/Dialog.md +74 -21
- package/dist/docs/Icon/Icon.md +60 -53
- package/dist/docs/Select/Select.md +428 -0
- package/dist/docs/SelectPrimitive/SelectPrimitive.md +14 -1
- package/dist/docs/index.md +1 -0
- package/dist/floating-ui.react-dom-es.js +1 -1
- package/dist/floating-ui.react-es.js +1 -1
- package/dist/floating-ui.utils.dom-es.js +1 -1
- package/dist/index.cjs +9 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +8 -0
- package/dist/primitives/ComboboxPrimitive/index.cjs +1 -0
- package/dist/primitives/ComboboxPrimitive/index.mjs +1 -0
- package/dist/primitives/HelperText/HelperText.d.ts +2 -1
- package/dist/primitives/InputNumberExperimental/index.cjs +3 -1
- package/dist/primitives/InputNumberExperimental/index.mjs +3 -1
- package/dist/primitives/SelectPrimitive/SelectPrimitive.d.ts +11 -0
- package/dist/primitives/SelectPrimitive/index.d.ts +1 -1
- package/dist/primitives/SelectPrimitive/types.d.ts +15 -0
- package/dist/primitives/index.cjs +2 -0
- package/dist/primitives/index.mjs +2 -0
- package/dist/stringifyLocale-cjs.js +13 -0
- package/dist/stringifyLocale-es.js +11 -0
- package/dist/styles.css +234 -0
- package/dist/unstyledPrimitives/index.cjs +1 -0
- package/dist/unstyledPrimitives/index.mjs +2 -1
- package/dist/useButton-es.js +1 -1
- package/dist/useCompositeListItem-es.js +1 -1
- package/dist/useLabel-cjs.js +0 -11
- package/dist/useLabel-es.js +2 -12
- package/dist/useScrollLock-es.js +1 -1
- package/dist/utils/meta/meta.json +5 -0
- package/package.json +8 -3
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import React__default, { useState, useRef, useId, useImperativeHandle } from 'react';
|
|
2
|
+
import { BREAKPOINT_SIZES, useWindowDimensions } from '@jobber/hooks';
|
|
3
|
+
import classnames from 'classnames';
|
|
4
|
+
import { S as SelectPrimitive } from './SelectPrimitive-es.js';
|
|
5
|
+
import { a as BottomSheet } from './BottomSheet-es.js';
|
|
6
|
+
import { I as Icon } from './Icon-es.js';
|
|
7
|
+
import { H as HelperText } from './HelperText-es.js';
|
|
8
|
+
import { F as FieldRoot, a as FieldLabel, b as FieldDescription, c as FieldError } from './FieldDescription-es.js';
|
|
9
|
+
|
|
10
|
+
var styles = {"field":"cWI-uFeNWWw-","inline":"ddkBixixBz4-","control":"_-1-lIAkgLuo-","trigger":"_7rwBeCqTgRY-","small":"_5QeBX5VxX6o-","large":"Pp5nj1nxejw-","label":"M1IjAW17-OE-","hasLabel":"_1rx-nSAJT0U-","sheetListbox":"AVkCcDISAio-","sheetOption":"_35H33txs5QU-","sheetOptionLabel":"uL9j4OZn-3c-","sheetOptionIndicator":"_7NHtDHZnkG8-","sheetGroup":"UQ1tsDd1NFs-","sheetGroupLabel":"_0kBfkWoRI5c-","sheetSeparator":"RPsN9vqlzRw-","spinning":"oOACJmrVDf0-"};
|
|
11
|
+
|
|
12
|
+
const SheetContext = React__default.createContext(null);
|
|
13
|
+
function useSheetContext() {
|
|
14
|
+
const context = React__default.useContext(SheetContext);
|
|
15
|
+
if (!context) {
|
|
16
|
+
throw new Error("Select sheet parts must render inside the Select sheet");
|
|
17
|
+
}
|
|
18
|
+
return context;
|
|
19
|
+
}
|
|
20
|
+
const SheetGroupContext = React__default.createContext(null);
|
|
21
|
+
function SheetOption({ value, children, className, style }) {
|
|
22
|
+
const { value: selectedValue, onSelect } = useSheetContext();
|
|
23
|
+
const selected = value === selectedValue;
|
|
24
|
+
return (React__default.createElement("div", { role: "option", "aria-selected": selected, "data-value": value, tabIndex: -1, className: classnames(styles.sheetOption, className), style: style, onClick: () => onSelect(value) },
|
|
25
|
+
React__default.createElement("span", { className: styles.sheetOptionLabel }, children),
|
|
26
|
+
selected && (React__default.createElement("span", { className: styles.sheetOptionIndicator, "aria-hidden": "true" },
|
|
27
|
+
React__default.createElement(Icon, { name: "checkmark", size: "small" })))));
|
|
28
|
+
}
|
|
29
|
+
function SheetGroup({ children, className, style }) {
|
|
30
|
+
const labelId = useId();
|
|
31
|
+
return (React__default.createElement(SheetGroupContext.Provider, { value: { labelId } },
|
|
32
|
+
React__default.createElement("div", { role: "group", "aria-labelledby": labelId, className: classnames(styles.sheetGroup, className), style: style }, children)));
|
|
33
|
+
}
|
|
34
|
+
function SheetGroupLabel({ children, className, style, }) {
|
|
35
|
+
const group = React__default.useContext(SheetGroupContext);
|
|
36
|
+
return (React__default.createElement("div", { id: group === null || group === void 0 ? void 0 : group.labelId, className: classnames(styles.sheetGroupLabel, className), style: style }, children));
|
|
37
|
+
}
|
|
38
|
+
function SheetSeparator({ className, style }) {
|
|
39
|
+
return (React__default.createElement("hr", { className: classnames(styles.sheetSeparator, className), style: style, "data-testid": "ATL-Select-SheetSeparator" }));
|
|
40
|
+
}
|
|
41
|
+
const selectBottomSheetShell = {
|
|
42
|
+
Item: SheetOption,
|
|
43
|
+
Group: SheetGroup,
|
|
44
|
+
GroupLabel: SheetGroupLabel,
|
|
45
|
+
Separator: SheetSeparator,
|
|
46
|
+
};
|
|
47
|
+
const OPTION_SELECTOR = '[role="option"]';
|
|
48
|
+
function getOptions(list) {
|
|
49
|
+
if (!list)
|
|
50
|
+
return [];
|
|
51
|
+
return Array.from(list.querySelectorAll(OPTION_SELECTOR));
|
|
52
|
+
}
|
|
53
|
+
function handleListboxKeyDown(event) {
|
|
54
|
+
var _a, _b, _c, _d;
|
|
55
|
+
const options = getOptions(event.currentTarget);
|
|
56
|
+
if (options.length === 0)
|
|
57
|
+
return;
|
|
58
|
+
const active = document.activeElement;
|
|
59
|
+
const currentIndex = active ? options.indexOf(active) : -1;
|
|
60
|
+
switch (event.key) {
|
|
61
|
+
case "ArrowDown":
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
(_a = options[(currentIndex + 1 + options.length) % options.length]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
64
|
+
break;
|
|
65
|
+
case "ArrowUp":
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
(_b = options[(currentIndex - 1 + options.length) % options.length]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
68
|
+
break;
|
|
69
|
+
case "Home":
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
(_c = options[0]) === null || _c === void 0 ? void 0 : _c.focus();
|
|
72
|
+
break;
|
|
73
|
+
case "End":
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
(_d = options[options.length - 1]) === null || _d === void 0 ? void 0 : _d.focus();
|
|
76
|
+
break;
|
|
77
|
+
case "Enter":
|
|
78
|
+
case " ":
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
active === null || active === void 0 ? void 0 : active.click();
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function SelectBottomSheetRoot({ value, onValueChange, onBlur, onFocus, disabled, invalid, renderValue, triggerId, triggerClassName, ariaLabel, children, }) {
|
|
85
|
+
const [open, setOpen] = useState(false);
|
|
86
|
+
const listRef = useRef(null);
|
|
87
|
+
const selectedValue = value !== null && value !== void 0 ? value : undefined;
|
|
88
|
+
const valueLabel = value != null ? renderValue(value) : undefined;
|
|
89
|
+
function select(optionValue) {
|
|
90
|
+
onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(optionValue);
|
|
91
|
+
setOpen(false);
|
|
92
|
+
}
|
|
93
|
+
function getInitialFocus() {
|
|
94
|
+
const options = getOptions(listRef.current);
|
|
95
|
+
const selected = options.find(option => option.dataset.value === selectedValue);
|
|
96
|
+
return selected !== null && selected !== void 0 ? selected : options[0];
|
|
97
|
+
}
|
|
98
|
+
return (React__default.createElement(SheetContext.Provider, { value: { value: selectedValue, onSelect: select } },
|
|
99
|
+
React__default.createElement(BottomSheet.Root, { open: open, onOpenChange: setOpen },
|
|
100
|
+
React__default.createElement(BottomSheet.Trigger, { render: React__default.createElement(SelectPrimitive.FieldTrigger, { id: triggerId, disabled: disabled, onBlur: onBlur, onFocus: onFocus, "data-placeholder": selectedValue ? undefined : "", "data-invalid": invalid ? "" : undefined, "data-popup-open": open ? "" : undefined, className: triggerClassName, valueSlot: React__default.createElement(SelectPrimitive.ValueText, null, valueLabel) }) }),
|
|
101
|
+
React__default.createElement(BottomSheet.Popup, { initialFocus: getInitialFocus },
|
|
102
|
+
React__default.createElement(BottomSheet.Content, null,
|
|
103
|
+
React__default.createElement("div", { ref: listRef, role: "listbox", "aria-label": ariaLabel, className: styles.sheetListbox, onKeyDown: handleListboxKeyDown }, children))))));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function DropdownItem({ value, children, className, style }) {
|
|
107
|
+
return (React__default.createElement(SelectPrimitive.Item, { className: className, style: style, value: value },
|
|
108
|
+
React__default.createElement(SelectPrimitive.ItemText, null, children),
|
|
109
|
+
React__default.createElement(SelectPrimitive.ItemIndicator, null)));
|
|
110
|
+
}
|
|
111
|
+
function DropdownGroup({ children, className, style }) {
|
|
112
|
+
return (React__default.createElement(SelectPrimitive.Group, { className: className, style: style }, children));
|
|
113
|
+
}
|
|
114
|
+
function DropdownGroupLabel({ children, className, style, }) {
|
|
115
|
+
return (React__default.createElement(SelectPrimitive.GroupLabel, { className: className, style: style }, children));
|
|
116
|
+
}
|
|
117
|
+
function DropdownSeparator({ className, style }) {
|
|
118
|
+
return (React__default.createElement(SelectPrimitive.Separator, { className: className, style: style, orientation: "horizontal" }));
|
|
119
|
+
}
|
|
120
|
+
const selectDropdownShell = {
|
|
121
|
+
Item: DropdownItem,
|
|
122
|
+
Group: DropdownGroup,
|
|
123
|
+
GroupLabel: DropdownGroupLabel,
|
|
124
|
+
Separator: DropdownSeparator,
|
|
125
|
+
};
|
|
126
|
+
function SelectDropdownRoot({ value, onValueChange, onBlur, onFocus, disabled, renderValue, triggerId, triggerClassName, children, }) {
|
|
127
|
+
return (React__default.createElement(SelectPrimitive.Root, { value: value !== null && value !== void 0 ? value : null, onValueChange: newValue => {
|
|
128
|
+
if (typeof newValue === "string")
|
|
129
|
+
onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(newValue);
|
|
130
|
+
}, disabled: disabled },
|
|
131
|
+
React__default.createElement(SelectPrimitive.Trigger, { className: triggerClassName, id: triggerId, onBlur: onBlur, onFocus: onFocus },
|
|
132
|
+
React__default.createElement(SelectPrimitive.Value, null, selectedValue => typeof selectedValue === "string"
|
|
133
|
+
? renderValue(selectedValue)
|
|
134
|
+
: null)),
|
|
135
|
+
React__default.createElement(SelectPrimitive.Portal, null,
|
|
136
|
+
React__default.createElement(SelectPrimitive.Positioner, null,
|
|
137
|
+
React__default.createElement(SelectPrimitive.Popup, null,
|
|
138
|
+
React__default.createElement(SelectPrimitive.List, null, children))))));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const SMALL_SCREEN_BREAKPOINT = BREAKPOINT_SIZES.sm;
|
|
142
|
+
const SelectShellContext = React__default.createContext(null);
|
|
143
|
+
function useSelectShell() {
|
|
144
|
+
const shell = React__default.useContext(SelectShellContext);
|
|
145
|
+
if (!shell) {
|
|
146
|
+
throw new Error("Select.* components must be rendered inside Select");
|
|
147
|
+
}
|
|
148
|
+
return shell;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Resolves the field's `id` (falling back to a generated id, since the id wires
|
|
152
|
+
* the label and imperative focus) and wires the imperative `focus()` handle to
|
|
153
|
+
* the trigger element. `name` is intentionally not synthesized: an unset `name`
|
|
154
|
+
* stays `undefined` so the field is simply excluded from form submission rather
|
|
155
|
+
* than submitted under a generated `useId()` value.
|
|
156
|
+
*/
|
|
157
|
+
function useSelectField(ref, idProp) {
|
|
158
|
+
const generatedId = useId();
|
|
159
|
+
const id = idProp !== null && idProp !== void 0 ? idProp : generatedId;
|
|
160
|
+
useImperativeHandle(ref, () => ({
|
|
161
|
+
focus: () => {
|
|
162
|
+
var _a;
|
|
163
|
+
if (typeof document !== "undefined") {
|
|
164
|
+
(_a = document.getElementById(id)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
}), [id]);
|
|
168
|
+
return { id };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* A prop-driven, labelled Select. On small web screens the options open in a
|
|
172
|
+
* `BottomSheet` instead of an anchored dropdown.
|
|
173
|
+
*/
|
|
174
|
+
function Select({ label, description, error, invalid, value, onValueChange, onBlur, onFocus, renderValue, name, id: idProp, ref, disabled, size, inline, children, }) {
|
|
175
|
+
const { id } = useSelectField(ref, idProp);
|
|
176
|
+
const renderSelectedValue = renderValue !== null && renderValue !== void 0 ? renderValue : capitalize;
|
|
177
|
+
const hasError = Boolean(error);
|
|
178
|
+
const isInvalid = hasError || Boolean(invalid);
|
|
179
|
+
const showDescription = !inline && Boolean(description) && !hasError;
|
|
180
|
+
const showError = !inline && hasError;
|
|
181
|
+
const { width } = useWindowDimensions();
|
|
182
|
+
const isBottomSheet = width !== undefined && width <= SMALL_SCREEN_BREAKPOINT;
|
|
183
|
+
const shell = isBottomSheet ? selectBottomSheetShell : selectDropdownShell;
|
|
184
|
+
const ShellRoot = isBottomSheet ? SelectBottomSheetRoot : SelectDropdownRoot;
|
|
185
|
+
return (React__default.createElement(FieldRoot, { className: classnames(styles.field, inline && styles.inline, size && styles[size], label && styles.hasLabel), disabled: disabled, invalid: isInvalid || undefined, name: name },
|
|
186
|
+
React__default.createElement("div", { className: styles.control },
|
|
187
|
+
label && (React__default.createElement(FieldLabel, { className: styles.label, htmlFor: id }, label)),
|
|
188
|
+
React__default.createElement(SelectShellContext.Provider, { value: shell },
|
|
189
|
+
React__default.createElement(ShellRoot, { value: value, onValueChange: onValueChange, onBlur: onBlur, onFocus: onFocus, disabled: disabled, invalid: isInvalid, renderValue: renderSelectedValue, triggerId: id, triggerClassName: styles.trigger, ariaLabel: label }, children))),
|
|
190
|
+
showDescription && (React__default.createElement(FieldDescription, { render: React__default.createElement(HelperText, { message: description }) })),
|
|
191
|
+
showError && (React__default.createElement(FieldError, { match: true, render: React__default.createElement(HelperText, { message: error, status: "critical" }) }))));
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Default value renderer for the closed trigger: capitalizes the value (e.g.
|
|
195
|
+
* `"active"` → `"Active"`). Consumers that need a different display pass
|
|
196
|
+
* `renderValue`.
|
|
197
|
+
*/
|
|
198
|
+
function capitalize(value) {
|
|
199
|
+
return value.length > 0
|
|
200
|
+
? value.charAt(0).toUpperCase() + value.slice(1)
|
|
201
|
+
: value;
|
|
202
|
+
}
|
|
203
|
+
function SelectItem(props) {
|
|
204
|
+
const { Item } = useSelectShell();
|
|
205
|
+
return React__default.createElement(Item, Object.assign({}, props));
|
|
206
|
+
}
|
|
207
|
+
function SelectGroup(props) {
|
|
208
|
+
const { Group } = useSelectShell();
|
|
209
|
+
return React__default.createElement(Group, Object.assign({}, props));
|
|
210
|
+
}
|
|
211
|
+
function SelectGroupLabel(props) {
|
|
212
|
+
const { GroupLabel } = useSelectShell();
|
|
213
|
+
return React__default.createElement(GroupLabel, Object.assign({}, props));
|
|
214
|
+
}
|
|
215
|
+
function SelectSeparator(props) {
|
|
216
|
+
const { Separator } = useSelectShell();
|
|
217
|
+
return React__default.createElement(Separator, Object.assign({}, props));
|
|
218
|
+
}
|
|
219
|
+
Select.displayName = "Select";
|
|
220
|
+
SelectItem.displayName = "Select.Item";
|
|
221
|
+
SelectGroup.displayName = "Select.Group";
|
|
222
|
+
SelectGroupLabel.displayName = "Select.GroupLabel";
|
|
223
|
+
SelectSeparator.displayName = "Select.Separator";
|
|
224
|
+
Select.Item = SelectItem;
|
|
225
|
+
Select.Group = SelectGroup;
|
|
226
|
+
Select.GroupLabel = SelectGroupLabel;
|
|
227
|
+
Select.Separator = SelectSeparator;
|
|
228
|
+
|
|
229
|
+
export { Select as S };
|
|
@@ -22,19 +22,32 @@ function mergeClassName(baseClassName, className) {
|
|
|
22
22
|
*/
|
|
23
23
|
const SELECT_OFFSET = 6;
|
|
24
24
|
// ─── Trigger ─────────────────────────────────────────────────────────────────
|
|
25
|
+
function TriggerChevron() {
|
|
26
|
+
return (React.createElement("span", { "aria-hidden": "true", className: styles.iconWrapper },
|
|
27
|
+
React.createElement("span", { className: styles.iconRotate },
|
|
28
|
+
React.createElement(Icon.Icon, { color: "greyBlue", name: "arrowDown", size: "base" }))));
|
|
29
|
+
}
|
|
30
|
+
function SelectPrimitiveFieldTrigger(_a) {
|
|
31
|
+
var { valueSlot, className } = _a, props = tslib_es6.__rest(_a, ["valueSlot", "className"]);
|
|
32
|
+
return (React.createElement("button", Object.assign({ type: "button", className: classnames(styles.trigger, className) }, props),
|
|
33
|
+
valueSlot,
|
|
34
|
+
React.createElement(TriggerChevron, null)));
|
|
35
|
+
}
|
|
25
36
|
function SelectPrimitiveTrigger(_a) {
|
|
26
37
|
var { className, children } = _a, props = tslib_es6.__rest(_a, ["className", "children"]);
|
|
27
38
|
return (React.createElement(SelectGroupLabel.SelectTrigger, Object.assign({ className: mergeClassName(styles.trigger, className) }, props),
|
|
28
39
|
children,
|
|
29
|
-
React.createElement(
|
|
30
|
-
React.createElement("span", { className: styles.iconRotate },
|
|
31
|
-
React.createElement(Icon.Icon, { color: "greyBlue", name: "arrowDown", size: "base" })))));
|
|
40
|
+
React.createElement(TriggerChevron, null)));
|
|
32
41
|
}
|
|
33
42
|
// ─── Value ───────────────────────────────────────────────────────────────────
|
|
34
43
|
function SelectPrimitiveValue(_a) {
|
|
35
44
|
var { className } = _a, props = tslib_es6.__rest(_a, ["className"]);
|
|
36
45
|
return (React.createElement(SelectGroupLabel.SelectValue, Object.assign({ className: mergeClassName(styles.value, className) }, props)));
|
|
37
46
|
}
|
|
47
|
+
function SelectPrimitiveValueText(_a) {
|
|
48
|
+
var { className } = _a, props = tslib_es6.__rest(_a, ["className"]);
|
|
49
|
+
return React.createElement("span", Object.assign({ className: classnames(styles.value, className) }, props));
|
|
50
|
+
}
|
|
38
51
|
// ─── Positioner ───────────────────────────────────────────────────────────────
|
|
39
52
|
function SelectPrimitivePositioner(_a) {
|
|
40
53
|
var { className, sideOffset = SELECT_OFFSET, alignItemWithTrigger = false } = _a, props = tslib_es6.__rest(_a, ["className", "sideOffset", "alignItemWithTrigger"]);
|
|
@@ -82,7 +95,9 @@ function SelectPrimitiveSeparator(_a) {
|
|
|
82
95
|
}
|
|
83
96
|
// ─── Display names ───────────────────────────────────────────────────────────
|
|
84
97
|
SelectPrimitiveTrigger.displayName = "SelectPrimitive.Trigger";
|
|
98
|
+
SelectPrimitiveFieldTrigger.displayName = "SelectPrimitive.FieldTrigger";
|
|
85
99
|
SelectPrimitiveValue.displayName = "SelectPrimitive.Value";
|
|
100
|
+
SelectPrimitiveValueText.displayName = "SelectPrimitive.ValueText";
|
|
86
101
|
SelectPrimitivePositioner.displayName = "SelectPrimitive.Positioner";
|
|
87
102
|
SelectPrimitivePopup.displayName = "SelectPrimitive.Popup";
|
|
88
103
|
SelectPrimitiveList.displayName = "SelectPrimitive.List";
|
|
@@ -96,7 +111,9 @@ SelectPrimitiveSeparator.displayName = "SelectPrimitive.Separator";
|
|
|
96
111
|
const SelectPrimitive = {
|
|
97
112
|
Root: SelectGroupLabel.SelectRoot,
|
|
98
113
|
Trigger: SelectPrimitiveTrigger,
|
|
114
|
+
FieldTrigger: SelectPrimitiveFieldTrigger,
|
|
99
115
|
Value: SelectPrimitiveValue,
|
|
116
|
+
ValueText: SelectPrimitiveValueText,
|
|
100
117
|
Portal: SelectGroupLabel.SelectPortal,
|
|
101
118
|
Positioner: SelectPrimitivePositioner,
|
|
102
119
|
Popup: SelectPrimitivePopup,
|
|
@@ -20,19 +20,32 @@ function mergeClassName(baseClassName, className) {
|
|
|
20
20
|
*/
|
|
21
21
|
const SELECT_OFFSET = 6;
|
|
22
22
|
// ─── Trigger ─────────────────────────────────────────────────────────────────
|
|
23
|
+
function TriggerChevron() {
|
|
24
|
+
return (React__default.createElement("span", { "aria-hidden": "true", className: styles.iconWrapper },
|
|
25
|
+
React__default.createElement("span", { className: styles.iconRotate },
|
|
26
|
+
React__default.createElement(Icon, { color: "greyBlue", name: "arrowDown", size: "base" }))));
|
|
27
|
+
}
|
|
28
|
+
function SelectPrimitiveFieldTrigger(_a) {
|
|
29
|
+
var { valueSlot, className } = _a, props = __rest(_a, ["valueSlot", "className"]);
|
|
30
|
+
return (React__default.createElement("button", Object.assign({ type: "button", className: classnames(styles.trigger, className) }, props),
|
|
31
|
+
valueSlot,
|
|
32
|
+
React__default.createElement(TriggerChevron, null)));
|
|
33
|
+
}
|
|
23
34
|
function SelectPrimitiveTrigger(_a) {
|
|
24
35
|
var { className, children } = _a, props = __rest(_a, ["className", "children"]);
|
|
25
36
|
return (React__default.createElement(SelectTrigger, Object.assign({ className: mergeClassName(styles.trigger, className) }, props),
|
|
26
37
|
children,
|
|
27
|
-
React__default.createElement(
|
|
28
|
-
React__default.createElement("span", { className: styles.iconRotate },
|
|
29
|
-
React__default.createElement(Icon, { color: "greyBlue", name: "arrowDown", size: "base" })))));
|
|
38
|
+
React__default.createElement(TriggerChevron, null)));
|
|
30
39
|
}
|
|
31
40
|
// ─── Value ───────────────────────────────────────────────────────────────────
|
|
32
41
|
function SelectPrimitiveValue(_a) {
|
|
33
42
|
var { className } = _a, props = __rest(_a, ["className"]);
|
|
34
43
|
return (React__default.createElement(SelectValue, Object.assign({ className: mergeClassName(styles.value, className) }, props)));
|
|
35
44
|
}
|
|
45
|
+
function SelectPrimitiveValueText(_a) {
|
|
46
|
+
var { className } = _a, props = __rest(_a, ["className"]);
|
|
47
|
+
return React__default.createElement("span", Object.assign({ className: classnames(styles.value, className) }, props));
|
|
48
|
+
}
|
|
36
49
|
// ─── Positioner ───────────────────────────────────────────────────────────────
|
|
37
50
|
function SelectPrimitivePositioner(_a) {
|
|
38
51
|
var { className, sideOffset = SELECT_OFFSET, alignItemWithTrigger = false } = _a, props = __rest(_a, ["className", "sideOffset", "alignItemWithTrigger"]);
|
|
@@ -80,7 +93,9 @@ function SelectPrimitiveSeparator(_a) {
|
|
|
80
93
|
}
|
|
81
94
|
// ─── Display names ───────────────────────────────────────────────────────────
|
|
82
95
|
SelectPrimitiveTrigger.displayName = "SelectPrimitive.Trigger";
|
|
96
|
+
SelectPrimitiveFieldTrigger.displayName = "SelectPrimitive.FieldTrigger";
|
|
83
97
|
SelectPrimitiveValue.displayName = "SelectPrimitive.Value";
|
|
98
|
+
SelectPrimitiveValueText.displayName = "SelectPrimitive.ValueText";
|
|
84
99
|
SelectPrimitivePositioner.displayName = "SelectPrimitive.Positioner";
|
|
85
100
|
SelectPrimitivePopup.displayName = "SelectPrimitive.Popup";
|
|
86
101
|
SelectPrimitiveList.displayName = "SelectPrimitive.List";
|
|
@@ -94,7 +109,9 @@ SelectPrimitiveSeparator.displayName = "SelectPrimitive.Separator";
|
|
|
94
109
|
const SelectPrimitive = {
|
|
95
110
|
Root: SelectRoot,
|
|
96
111
|
Trigger: SelectPrimitiveTrigger,
|
|
112
|
+
FieldTrigger: SelectPrimitiveFieldTrigger,
|
|
97
113
|
Value: SelectPrimitiveValue,
|
|
114
|
+
ValueText: SelectPrimitiveValueText,
|
|
98
115
|
Portal: SelectPortal,
|
|
99
116
|
Positioner: SelectPrimitivePositioner,
|
|
100
117
|
Popup: SelectPrimitivePopup,
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Summary
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
screens it appears as a sheet anchored to the bottom of the screen.
|
|
5
|
+
Dialog is a modal surface that presents a single, focused task or decision above
|
|
6
|
+
the page. On large screens it appears as a centered modal; on small screens it
|
|
7
|
+
appears as a sheet anchored to the bottom of the screen.
|
|
9
8
|
|
|
10
9
|
## Anatomy
|
|
11
10
|
|
|
@@ -23,7 +22,7 @@ screens it appears as a sheet anchored to the bottom of the screen.
|
|
|
23
22
|
|
|
24
23
|
## Behavior
|
|
25
24
|
|
|
26
|
-
#### Opening and closing
|
|
25
|
+
#### Opening and closing
|
|
27
26
|
|
|
28
27
|
The dialog opens when:
|
|
29
28
|
|
|
@@ -40,14 +39,6 @@ The dialog closes when:
|
|
|
40
39
|
An `alertdialog` is the exception: it ignores outside and backdrop dismissal, so
|
|
41
40
|
the user must choose an action to resolve it. ESC still closes it.
|
|
42
41
|
|
|
43
|
-
#### Width
|
|
44
|
-
|
|
45
|
-
Dialog width defaults to `base` (720px). It can be set to `small` (440px),
|
|
46
|
-
`large` (1032px), or `fullScreen`. These are max widths — the dialog narrows to
|
|
47
|
-
fit smaller viewports. `fullScreen` fills the viewport on large screens (squared
|
|
48
|
-
corners) and becomes a full-height sheet on small screens. See [Size](#size) for
|
|
49
|
-
when to use each.
|
|
50
|
-
|
|
51
42
|
#### Scrolling
|
|
52
43
|
|
|
53
44
|
The `Dialog.Body` is the default scroll region, while the header and footer stay
|
|
@@ -55,12 +46,11 @@ pinned as the content scrolls. Which region scrolls is decided by composition,
|
|
|
55
46
|
not a prop — wrap parts in a `Dialog.ScrollArea` when a header or footer should
|
|
56
47
|
scroll with the content instead of pinning.
|
|
57
48
|
|
|
58
|
-
####
|
|
49
|
+
#### Nested dialog
|
|
59
50
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
is replaced by a card on save).
|
|
51
|
+
A dialog can open a second dialog, which layers on top of the first. The
|
|
52
|
+
underlying dialog stays in place behind the new overlay and returns when the top
|
|
53
|
+
one closes.
|
|
64
54
|
|
|
65
55
|
#### Small screens
|
|
66
56
|
|
|
@@ -114,9 +104,72 @@ instead.
|
|
|
114
104
|
## Content Guidelines
|
|
115
105
|
|
|
116
106
|
These build on the general [Voice & tone](../voice-and-tone/voice-and-tone.md) and
|
|
117
|
-
[Formatting](../formatting/formatting.md) guidelines.
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
[Formatting](../formatting/formatting.md) guidelines.
|
|
108
|
+
|
|
109
|
+
A dialog interrupts the user, so every word should earn its place:
|
|
110
|
+
name the task, state the outcome, and give a clear way out.
|
|
111
|
+
|
|
112
|
+
#### Title
|
|
113
|
+
|
|
114
|
+
The title names the task as opposed to the component. Use verbs when the
|
|
115
|
+
dialog is asking the user to actually do something. Be specific. Use a noun
|
|
116
|
+
when you're presenting information. Don't phrase confirmation requests as questions.
|
|
117
|
+
|
|
118
|
+
| Do | Don't |
|
|
119
|
+
| ------------------ | -------------------------------------------- |
|
|
120
|
+
| Delete client | Are you sure you want to delete this client? |
|
|
121
|
+
| Edit job details | Job Editor |
|
|
122
|
+
| Assign team member | Assign |
|
|
123
|
+
| Send invoice | Send |
|
|
124
|
+
| Client information | Info |
|
|
125
|
+
|
|
126
|
+
#### Body
|
|
127
|
+
|
|
128
|
+
Body copy needs to expand on the title by providing what the title can't. It shouldn't
|
|
129
|
+
repeat the title and should provide useful information such as explaining the impact of
|
|
130
|
+
any action the user might be taking. For a routine task, one short sentence is often enough.
|
|
131
|
+
|
|
132
|
+
| Do | Don't |
|
|
133
|
+
| ---------------------------------------------- | -------------------------------------------------------------- |
|
|
134
|
+
| This will remove the discount from the invoice | This will let you remove the discount that's currently applied |
|
|
135
|
+
|
|
136
|
+
#### Actions
|
|
137
|
+
|
|
138
|
+
Primary action lables will need to lead with a verb and match the action taking place.
|
|
139
|
+
They shouldn't be generic such as "Confirm" or "Yes". The action needs to be clearly
|
|
140
|
+
intentional. Also, remember that buttons are title case.
|
|
141
|
+
|
|
142
|
+
| Do | Don't |
|
|
143
|
+
| ------------------- | ------- |
|
|
144
|
+
| Cancel Subscription | Confirm |
|
|
145
|
+
| Sync QuickBooks | Next |
|
|
146
|
+
| Delete 3 Files | Delete |
|
|
147
|
+
|
|
148
|
+
#### Alert dialogs
|
|
149
|
+
|
|
150
|
+
As `alertdialog` can't be dismissed by clicking outside, the way out needs to be clear. We should not be reliant on
|
|
151
|
+
users pressing ESC. When relevant, cancel actions need to be considered alonsgside confirmation actions and
|
|
152
|
+
should be just as easy to find. Ideally via secondary CTAs.
|
|
153
|
+
|
|
154
|
+
## Do's and Don'ts
|
|
155
|
+
|
|
156
|
+
Here are some general rules to follow when working with the dialog component:
|
|
157
|
+
|
|
158
|
+
#### Do:
|
|
159
|
+
|
|
160
|
+
* ✅ Give every dialog a clear title that names the task
|
|
161
|
+
* ✅ Keep a dialog focused on a single task or decision
|
|
162
|
+
* ✅ Use an alert dialog for destructive or irreversible actions, and always
|
|
163
|
+
offer a way to cancel
|
|
164
|
+
* ✅ Lead with a short, action-oriented primary button label
|
|
165
|
+
* ✅ Choose the size that comfortably fits the content
|
|
166
|
+
|
|
167
|
+
#### Don't:
|
|
168
|
+
|
|
169
|
+
* ❌ Combine multiple unrelated tasks in one dialog
|
|
170
|
+
* ❌ Use a dialog for long or multi-step flows that belong on their own page
|
|
171
|
+
* ❌ Use a nested dialog unless the flow genuinely requires it
|
|
172
|
+
* ❌ Remove the ability to dismiss without offering an explicit action to leave
|
|
120
173
|
|
|
121
174
|
## Accessibility
|
|
122
175
|
|
package/dist/docs/Icon/Icon.md
CHANGED
|
@@ -429,59 +429,66 @@ export function IconSizesExample() {
|
|
|
429
429
|
|
|
430
430
|
### Other
|
|
431
431
|
|
|
432
|
-
| Icon | `Name`
|
|
433
|
-
| :--- |
|
|
434
|
-
| | `apps`
|
|
435
|
-
| | `at`
|
|
436
|
-
| | `camera`
|
|
437
|
-
| | `checkmark`
|
|
438
|
-
| | `checkmarkCircle`
|
|
439
|
-
| | `circle`
|
|
440
|
-
| | `cog`
|
|
441
|
-
| | `dashboard`
|
|
442
|
-
| | `desktop`
|
|
443
|
-
| | `website`
|
|
444
|
-
| | `eye`
|
|
445
|
-
| | `eyeCrossed`
|
|
446
|
-
| | `flash`
|
|
447
|
-
| | `flashAuto`
|
|
448
|
-
| | `flashOff`
|
|
449
|
-
| | `gift`
|
|
450
|
-
| | `grid`
|
|
451
|
-
| | `happyFace`
|
|
452
|
-
| | `headset`
|
|
453
|
-
| | `home`
|
|
454
|
-
| | `link`
|
|
455
|
-
| | `loadingCheck`
|
|
456
|
-
| | `lock`
|
|
457
|
-
| | `logout`
|
|
458
|
-
| | `measurement`
|
|
459
|
-
| | `menu`
|
|
460
|
-
| | `microphone`
|
|
461
|
-
| | `microphoneMuted`
|
|
462
|
-
| | `mobile`
|
|
463
|
-
| | `more`
|
|
464
|
-
| | `number`
|
|
465
|
-
| | `
|
|
466
|
-
| | `
|
|
467
|
-
| | `
|
|
468
|
-
| | `
|
|
469
|
-
| | `
|
|
470
|
-
| | `
|
|
471
|
-
| | `
|
|
472
|
-
| | `
|
|
473
|
-
| | `
|
|
474
|
-
| | `
|
|
475
|
-
| | `
|
|
476
|
-
| | `
|
|
477
|
-
| | `
|
|
478
|
-
| | `
|
|
479
|
-
| | `
|
|
480
|
-
| | `
|
|
481
|
-
| | `
|
|
482
|
-
| | `
|
|
483
|
-
| | `
|
|
484
|
-
| | `
|
|
432
|
+
| Icon | `Name` |
|
|
433
|
+
| :--- | :------------------ |
|
|
434
|
+
| | `apps` |
|
|
435
|
+
| | `at` |
|
|
436
|
+
| | `camera` |
|
|
437
|
+
| | `checkmark` |
|
|
438
|
+
| | `checkmarkCircle` |
|
|
439
|
+
| | `circle` |
|
|
440
|
+
| | `cog` |
|
|
441
|
+
| | `dashboard` |
|
|
442
|
+
| | `desktop` |
|
|
443
|
+
| | `website` |
|
|
444
|
+
| | `eye` |
|
|
445
|
+
| | `eyeCrossed` |
|
|
446
|
+
| | `flash` |
|
|
447
|
+
| | `flashAuto` |
|
|
448
|
+
| | `flashOff` |
|
|
449
|
+
| | `gift` |
|
|
450
|
+
| | `grid` |
|
|
451
|
+
| | `happyFace` |
|
|
452
|
+
| | `headset` |
|
|
453
|
+
| | `home` |
|
|
454
|
+
| | `link` |
|
|
455
|
+
| | `loadingCheck` |
|
|
456
|
+
| | `lock` |
|
|
457
|
+
| | `logout` |
|
|
458
|
+
| | `measurement` |
|
|
459
|
+
| | `menu` |
|
|
460
|
+
| | `microphone` |
|
|
461
|
+
| | `microphoneMuted` |
|
|
462
|
+
| | `mobile` |
|
|
463
|
+
| | `more` |
|
|
464
|
+
| | `number` |
|
|
465
|
+
| | `numpad` |
|
|
466
|
+
| | `offline` |
|
|
467
|
+
| | `phone` |
|
|
468
|
+
| | `phoneCall` |
|
|
469
|
+
| | `phoneDisconnected` |
|
|
470
|
+
| | `phoneEnd` |
|
|
471
|
+
| | `phoneIncoming` |
|
|
472
|
+
| | `phoneOutgoing` |
|
|
473
|
+
| | `pinned` |
|
|
474
|
+
| | `presentation` |
|
|
475
|
+
| | `priceTag` |
|
|
476
|
+
| | `quickbooks` |
|
|
477
|
+
| | `reports` |
|
|
478
|
+
| | `shape` |
|
|
479
|
+
| | `sidebar` |
|
|
480
|
+
| | `signature` |
|
|
481
|
+
| | `sneaker` |
|
|
482
|
+
| | `sparkles` |
|
|
483
|
+
| | `speaker` |
|
|
484
|
+
| | `sprout` |
|
|
485
|
+
| | `starburst` |
|
|
486
|
+
| | `sun` |
|
|
487
|
+
| | `tableColumns` |
|
|
488
|
+
| | `thumbsDown` |
|
|
489
|
+
| | `thumbsUp` |
|
|
490
|
+
| | `unPinned` |
|
|
491
|
+
| | `xero` |
|
|
485
492
|
|
|
486
493
|
### Legacy
|
|
487
494
|
|