@jobber/components 8.26.3 → 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.
Files changed (55) hide show
  1. package/Select.d.ts +1 -0
  2. package/Select.js +17 -0
  3. package/dist/ComboboxChipRemove-cjs.js +2 -1
  4. package/dist/ComboboxChipRemove-es.js +2 -1
  5. package/dist/FieldDescription-cjs.js +880 -0
  6. package/dist/FieldDescription-es.js +856 -0
  7. package/dist/InputNumberExperimental-cjs.js +5 -875
  8. package/dist/InputNumberExperimental-es.js +2 -854
  9. package/dist/MenuSubmenuTrigger-es.js +1 -1
  10. package/dist/NumberFieldInput-cjs.js +2 -2
  11. package/dist/NumberFieldInput-es.js +1 -1
  12. package/dist/Select/Select.d.ts +31 -0
  13. package/dist/Select/Select.types.d.ts +145 -0
  14. package/dist/Select/SelectBottomSheet.d.ts +4 -0
  15. package/dist/Select/SelectDropdown.d.ts +4 -0
  16. package/dist/Select/index.cjs +41 -0
  17. package/dist/Select/index.d.ts +2 -0
  18. package/dist/Select/index.mjs +35 -0
  19. package/dist/Select-cjs.js +231 -0
  20. package/dist/Select-es.js +229 -0
  21. package/dist/SelectPrimitive-cjs.js +20 -3
  22. package/dist/SelectPrimitive-es.js +20 -3
  23. package/dist/docs/Dialog/Dialog.md +74 -21
  24. package/dist/docs/Select/Select.md +428 -0
  25. package/dist/docs/SelectPrimitive/SelectPrimitive.md +14 -1
  26. package/dist/docs/index.md +1 -0
  27. package/dist/floating-ui.react-dom-es.js +1 -1
  28. package/dist/floating-ui.react-es.js +1 -1
  29. package/dist/floating-ui.utils.dom-es.js +1 -1
  30. package/dist/index.cjs +9 -0
  31. package/dist/index.d.mts +1 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.mjs +8 -0
  34. package/dist/primitives/ComboboxPrimitive/index.cjs +1 -0
  35. package/dist/primitives/ComboboxPrimitive/index.mjs +1 -0
  36. package/dist/primitives/HelperText/HelperText.d.ts +2 -1
  37. package/dist/primitives/InputNumberExperimental/index.cjs +3 -1
  38. package/dist/primitives/InputNumberExperimental/index.mjs +3 -1
  39. package/dist/primitives/SelectPrimitive/SelectPrimitive.d.ts +11 -0
  40. package/dist/primitives/SelectPrimitive/index.d.ts +1 -1
  41. package/dist/primitives/SelectPrimitive/types.d.ts +15 -0
  42. package/dist/primitives/index.cjs +2 -0
  43. package/dist/primitives/index.mjs +2 -0
  44. package/dist/stringifyLocale-cjs.js +13 -0
  45. package/dist/stringifyLocale-es.js +11 -0
  46. package/dist/styles.css +234 -0
  47. package/dist/unstyledPrimitives/index.cjs +1 -0
  48. package/dist/unstyledPrimitives/index.mjs +2 -1
  49. package/dist/useButton-es.js +1 -1
  50. package/dist/useCompositeListItem-es.js +1 -1
  51. package/dist/useLabel-cjs.js +0 -11
  52. package/dist/useLabel-es.js +2 -12
  53. package/dist/useScrollLock-es.js +1 -1
  54. package/dist/utils/meta/meta.json +5 -0
  55. 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,
@@ -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