@jobber/components 8.26.3 → 8.27.1
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/BottomSheet-cjs.js +8 -7
- package/dist/BottomSheet-es.js +9 -8
- package/dist/ComboboxChipRemove-cjs.js +2 -1
- package/dist/ComboboxChipRemove-es.js +4 -3
- package/dist/DrawerDescription-cjs.js +4 -97
- package/dist/DrawerDescription-es.js +6 -98
- 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 +4 -4
- package/dist/Modal/index.cjs +12 -2
- package/dist/Modal/index.mjs +13 -3
- package/dist/NumberFieldInput-cjs.js +2 -2
- package/dist/NumberFieldInput-es.js +1 -1
- package/dist/ScrollAreaViewport-cjs.js +2122 -12
- package/dist/ScrollAreaViewport-es.js +2118 -13
- 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/dialogReturnFocus-cjs.js +8 -5
- package/dist/dialogReturnFocus-es.js +9 -6
- package/dist/docs/Dialog/Dialog.md +74 -21
- 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 +300 -10
- package/dist/unstyledPrimitives/index.cjs +219 -2230
- package/dist/unstyledPrimitives/index.mjs +225 -2236
- package/dist/useButton-es.js +2 -2
- 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 +3 -3
- package/dist/utils/meta/meta.json +5 -0
- package/package.json +7 -2
|
@@ -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,
|
|
@@ -133,11 +133,11 @@ function useInsideDialogScrollArea() {
|
|
|
133
133
|
* backed by the BottomSheet primitive's scroll area, for its fade.)
|
|
134
134
|
*/
|
|
135
135
|
function DialogScrollArea$1({ children, className, style, }) {
|
|
136
|
-
return (React.createElement(ScrollAreaViewport.ScrollAreaRoot, { className: classnames(styles.scrollRegion, className), style: style },
|
|
136
|
+
return (React.createElement(ScrollAreaViewport.ScrollAreaRoot, { className: classnames(styles.scrollRegion, className), style: style, "data-testid": "ATL-Dialog-ScrollRegion" },
|
|
137
137
|
React.createElement(ScrollAreaViewport.ScrollAreaViewport, { className: styles.scrollViewport,
|
|
138
138
|
// BaseUI defaults the viewport to `overflow: scroll`; `auto` so the
|
|
139
139
|
// native scrollbar only shows when the content overflows.
|
|
140
|
-
style: { overflow: "auto" } },
|
|
140
|
+
style: { overflow: "auto" }, "data-testid": "ATL-Dialog-ScrollViewport" },
|
|
141
141
|
React.createElement(DialogScrollAreaContext.Provider, { value: true }, children))));
|
|
142
142
|
}
|
|
143
143
|
/** Layout-only header row; sticky unless nested inside a `Dialog.ScrollArea`. */
|
|
@@ -195,7 +195,9 @@ function DialogBottomSheetContent(_a) {
|
|
|
195
195
|
React.createElement(BottomSheet.BottomSheet.Popup, Object.assign({}, rest, {
|
|
196
196
|
// The bottom sheet is backed by a drawer popup, so re-shape its state
|
|
197
197
|
// into the dialog popup state the consumer's `render` is typed against.
|
|
198
|
-
render: resolveBottomSheetContentRender(render), className: classnames(styles.sheetSurface, className), style: style, role: type, finalFocus: finalFocus, "data-dialog-variant": "sheet", "data-size": size }),
|
|
198
|
+
render: resolveBottomSheetContentRender(render), className: classnames(styles.sheetSurface, className), style: style, role: type, finalFocus: finalFocus, "data-dialog-variant": "sheet", "data-size": size }),
|
|
199
|
+
React.createElement(ScrollAreaViewport.DrawerContent, { "data-base-ui-swipe-ignore": true },
|
|
200
|
+
React.createElement(DialogScrollAreaContext.Provider, { value: false }, children)))));
|
|
199
201
|
}
|
|
200
202
|
function DialogBottomSheetTitle(_a) {
|
|
201
203
|
var { children } = _a, rest = tslib_es6.__rest(_a, ["children"]);
|
|
@@ -247,8 +249,9 @@ function DialogModalContent(_a) {
|
|
|
247
249
|
const { type, size, finalFocus } = useDialogContext();
|
|
248
250
|
return (React.createElement(ScrollAreaViewport.DialogPortal, null,
|
|
249
251
|
React.createElement(DrawerDescription.DialogBackdrop, { forceRender: true, className: styles.backdrop, "data-testid": "ATL-Dialog-Backdrop" }),
|
|
250
|
-
React.createElement(
|
|
251
|
-
React.createElement(DrawerDescription.DialogPopup, Object.assign({}, rest, { className: classnames(styles.popup, styles[size], className), style: style, role: type, finalFocus: finalFocus, "data-dialog-variant": "modal" }),
|
|
252
|
+
React.createElement(ScrollAreaViewport.DialogViewport, { className: styles.viewport },
|
|
253
|
+
React.createElement(DrawerDescription.DialogPopup, Object.assign({}, rest, { className: classnames(styles.popup, styles[size], className), style: style, role: type, finalFocus: finalFocus, "data-dialog-variant": "modal" }),
|
|
254
|
+
React.createElement(DialogScrollAreaContext.Provider, { value: false }, children)))));
|
|
252
255
|
}
|
|
253
256
|
function DialogModalTitle(_a) {
|
|
254
257
|
var { children } = _a, rest = tslib_es6.__rest(_a, ["children"]);
|
|
@@ -4,12 +4,12 @@ import { useWindowDimensions } from '@jobber/hooks';
|
|
|
4
4
|
import classnames from 'classnames';
|
|
5
5
|
import { n as normalizeBaseUiButtonProps, i as isAtlantisButtonRenderTarget, s as stripNativeTypeFromButtonRenderTarget, r as renderAtlantisButtonTarget } from './buttonRenderAdapter-es.js';
|
|
6
6
|
import { B as Button } from './Button-es.js';
|
|
7
|
-
import { S as ScrollAreaRoot,
|
|
7
|
+
import { S as ScrollAreaRoot, f as ScrollAreaViewport, g as DrawerContent, j as DialogRoot, k as DialogTrigger$1, l as DialogPortal, m as DialogViewport } from './ScrollAreaViewport-es.js';
|
|
8
8
|
import { m as mergeProps } from './useRenderElement-es.js';
|
|
9
9
|
import { H as Heading } from './Heading-es.js';
|
|
10
10
|
import { a as BottomSheet, B as BottomSheetScrollAreaContext } from './BottomSheet-es.js';
|
|
11
11
|
import { D as DrawerTitle, a as DrawerClose, b as DialogTitle$1, c as DialogClose$1 } from './DrawerTitle-es.js';
|
|
12
|
-
import { D as DrawerDescription, a as DialogBackdrop, b as
|
|
12
|
+
import { D as DrawerDescription, a as DialogBackdrop, b as DialogPopup, c as DialogDescription, d as createDialogHandle } from './DrawerDescription-es.js';
|
|
13
13
|
|
|
14
14
|
// Copied from Menu's constant so the two surfaces agree on what "mobile" means.
|
|
15
15
|
// (The proposal calls for copying shared Menu values into the component first
|
|
@@ -131,11 +131,11 @@ function useInsideDialogScrollArea() {
|
|
|
131
131
|
* backed by the BottomSheet primitive's scroll area, for its fade.)
|
|
132
132
|
*/
|
|
133
133
|
function DialogScrollArea$1({ children, className, style, }) {
|
|
134
|
-
return (React__default.createElement(ScrollAreaRoot, { className: classnames(styles.scrollRegion, className), style: style },
|
|
134
|
+
return (React__default.createElement(ScrollAreaRoot, { className: classnames(styles.scrollRegion, className), style: style, "data-testid": "ATL-Dialog-ScrollRegion" },
|
|
135
135
|
React__default.createElement(ScrollAreaViewport, { className: styles.scrollViewport,
|
|
136
136
|
// BaseUI defaults the viewport to `overflow: scroll`; `auto` so the
|
|
137
137
|
// native scrollbar only shows when the content overflows.
|
|
138
|
-
style: { overflow: "auto" } },
|
|
138
|
+
style: { overflow: "auto" }, "data-testid": "ATL-Dialog-ScrollViewport" },
|
|
139
139
|
React__default.createElement(DialogScrollAreaContext.Provider, { value: true }, children))));
|
|
140
140
|
}
|
|
141
141
|
/** Layout-only header row; sticky unless nested inside a `Dialog.ScrollArea`. */
|
|
@@ -193,7 +193,9 @@ function DialogBottomSheetContent(_a) {
|
|
|
193
193
|
React__default.createElement(BottomSheet.Popup, Object.assign({}, rest, {
|
|
194
194
|
// The bottom sheet is backed by a drawer popup, so re-shape its state
|
|
195
195
|
// into the dialog popup state the consumer's `render` is typed against.
|
|
196
|
-
render: resolveBottomSheetContentRender(render), className: classnames(styles.sheetSurface, className), style: style, role: type, finalFocus: finalFocus, "data-dialog-variant": "sheet", "data-size": size }),
|
|
196
|
+
render: resolveBottomSheetContentRender(render), className: classnames(styles.sheetSurface, className), style: style, role: type, finalFocus: finalFocus, "data-dialog-variant": "sheet", "data-size": size }),
|
|
197
|
+
React__default.createElement(DrawerContent, { "data-base-ui-swipe-ignore": true },
|
|
198
|
+
React__default.createElement(DialogScrollAreaContext.Provider, { value: false }, children)))));
|
|
197
199
|
}
|
|
198
200
|
function DialogBottomSheetTitle(_a) {
|
|
199
201
|
var { children } = _a, rest = __rest(_a, ["children"]);
|
|
@@ -246,7 +248,8 @@ function DialogModalContent(_a) {
|
|
|
246
248
|
return (React__default.createElement(DialogPortal, null,
|
|
247
249
|
React__default.createElement(DialogBackdrop, { forceRender: true, className: styles.backdrop, "data-testid": "ATL-Dialog-Backdrop" }),
|
|
248
250
|
React__default.createElement(DialogViewport, { className: styles.viewport },
|
|
249
|
-
React__default.createElement(DialogPopup, Object.assign({}, rest, { className: classnames(styles.popup, styles[size], className), style: style, role: type, finalFocus: finalFocus, "data-dialog-variant": "modal" }),
|
|
251
|
+
React__default.createElement(DialogPopup, Object.assign({}, rest, { className: classnames(styles.popup, styles[size], className), style: style, role: type, finalFocus: finalFocus, "data-dialog-variant": "modal" }),
|
|
252
|
+
React__default.createElement(DialogScrollAreaContext.Provider, { value: false }, children)))));
|
|
250
253
|
}
|
|
251
254
|
function DialogModalTitle(_a) {
|
|
252
255
|
var { children } = _a, rest = __rest(_a, ["children"]);
|
|
@@ -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
|
|