@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.
Files changed (65) hide show
  1. package/Select.d.ts +1 -0
  2. package/Select.js +17 -0
  3. package/dist/BottomSheet-cjs.js +8 -7
  4. package/dist/BottomSheet-es.js +9 -8
  5. package/dist/ComboboxChipRemove-cjs.js +2 -1
  6. package/dist/ComboboxChipRemove-es.js +4 -3
  7. package/dist/DrawerDescription-cjs.js +4 -97
  8. package/dist/DrawerDescription-es.js +6 -98
  9. package/dist/FieldDescription-cjs.js +880 -0
  10. package/dist/FieldDescription-es.js +856 -0
  11. package/dist/InputNumberExperimental-cjs.js +5 -875
  12. package/dist/InputNumberExperimental-es.js +2 -854
  13. package/dist/MenuSubmenuTrigger-es.js +4 -4
  14. package/dist/Modal/index.cjs +12 -2
  15. package/dist/Modal/index.mjs +13 -3
  16. package/dist/NumberFieldInput-cjs.js +2 -2
  17. package/dist/NumberFieldInput-es.js +1 -1
  18. package/dist/ScrollAreaViewport-cjs.js +2122 -12
  19. package/dist/ScrollAreaViewport-es.js +2118 -13
  20. package/dist/Select/Select.d.ts +31 -0
  21. package/dist/Select/Select.types.d.ts +145 -0
  22. package/dist/Select/SelectBottomSheet.d.ts +4 -0
  23. package/dist/Select/SelectDropdown.d.ts +4 -0
  24. package/dist/Select/index.cjs +41 -0
  25. package/dist/Select/index.d.ts +2 -0
  26. package/dist/Select/index.mjs +35 -0
  27. package/dist/Select-cjs.js +231 -0
  28. package/dist/Select-es.js +229 -0
  29. package/dist/SelectPrimitive-cjs.js +20 -3
  30. package/dist/SelectPrimitive-es.js +20 -3
  31. package/dist/dialogReturnFocus-cjs.js +8 -5
  32. package/dist/dialogReturnFocus-es.js +9 -6
  33. package/dist/docs/Dialog/Dialog.md +74 -21
  34. package/dist/docs/Select/Select.md +428 -0
  35. package/dist/docs/SelectPrimitive/SelectPrimitive.md +14 -1
  36. package/dist/docs/index.md +1 -0
  37. package/dist/floating-ui.react-dom-es.js +1 -1
  38. package/dist/floating-ui.react-es.js +1 -1
  39. package/dist/floating-ui.utils.dom-es.js +1 -1
  40. package/dist/index.cjs +9 -0
  41. package/dist/index.d.mts +1 -0
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.mjs +8 -0
  44. package/dist/primitives/ComboboxPrimitive/index.cjs +1 -0
  45. package/dist/primitives/ComboboxPrimitive/index.mjs +1 -0
  46. package/dist/primitives/HelperText/HelperText.d.ts +2 -1
  47. package/dist/primitives/InputNumberExperimental/index.cjs +3 -1
  48. package/dist/primitives/InputNumberExperimental/index.mjs +3 -1
  49. package/dist/primitives/SelectPrimitive/SelectPrimitive.d.ts +11 -0
  50. package/dist/primitives/SelectPrimitive/index.d.ts +1 -1
  51. package/dist/primitives/SelectPrimitive/types.d.ts +15 -0
  52. package/dist/primitives/index.cjs +2 -0
  53. package/dist/primitives/index.mjs +2 -0
  54. package/dist/stringifyLocale-cjs.js +13 -0
  55. package/dist/stringifyLocale-es.js +11 -0
  56. package/dist/styles.css +300 -10
  57. package/dist/unstyledPrimitives/index.cjs +219 -2230
  58. package/dist/unstyledPrimitives/index.mjs +225 -2236
  59. package/dist/useButton-es.js +2 -2
  60. package/dist/useCompositeListItem-es.js +1 -1
  61. package/dist/useLabel-cjs.js +0 -11
  62. package/dist/useLabel-es.js +2 -12
  63. package/dist/useScrollLock-es.js +3 -3
  64. package/dist/utils/meta/meta.json +5 -0
  65. 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("span", { "aria-hidden": "true", className: styles.iconWrapper },
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("span", { "aria-hidden": "true", className: styles.iconWrapper },
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 }), children)));
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(DrawerDescription.DialogViewport, { className: styles.viewport },
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" }), children))));
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, e as ScrollAreaViewport, g as DialogRoot, h as DialogTrigger$1, i as DialogPortal } from './ScrollAreaViewport-es.js';
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 DialogViewport, c as DialogPopup, d as DialogDescription, e as createDialogHandle } from './DrawerDescription-es.js';
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 }), children)));
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" }), children))));
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
- The dialog displays content in a layer above the page, focusing the user on a
6
- single task or decision. It is activated by an interactive element such as a
7
- button or icon button. On large screens it appears as a centered modal; on small
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 the dialog
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
- #### Focus
49
+ #### Nested dialog
59
50
 
60
- While the dialog is open, focus is trapped inside it. When it closes, focus
61
- returns to the trigger. Focus can be directed elsewhere when the trigger is
62
- removed while the dialog is open (for example, a "Create…" action whose trigger
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. A dialog interrupts the user, so
118
- every word should earn its place: name the task, state the outcome, and give a
119
- clear way out.
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