@jobber/components 7.5.0 → 7.6.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 (48) hide show
  1. package/dist/Autocomplete/index.cjs +6 -5
  2. package/dist/Autocomplete/index.mjs +3 -2
  3. package/dist/DataList/components/DataListSearch/index.cjs +1 -0
  4. package/dist/DataList/components/DataListSearch/index.mjs +1 -0
  5. package/dist/DataList/index.cjs +1 -0
  6. package/dist/DataList/index.mjs +1 -0
  7. package/dist/DataTable/index.cjs +2 -14
  8. package/dist/DataTable/index.mjs +2 -14
  9. package/dist/DataTable-cjs.js +3 -3
  10. package/dist/DataTable-es.js +1 -1
  11. package/dist/FormField/index.cjs +14 -13
  12. package/dist/FormField/index.mjs +5 -4
  13. package/dist/FormField-cjs.js +8 -281
  14. package/dist/FormField-es.js +3 -263
  15. package/dist/InputDate/index.cjs +1 -0
  16. package/dist/InputDate/index.mjs +2 -1
  17. package/dist/InputEmail/index.cjs +7 -6
  18. package/dist/InputEmail/index.mjs +4 -3
  19. package/dist/InputNumber/index.cjs +1 -0
  20. package/dist/InputNumber/index.mjs +2 -1
  21. package/dist/InputPassword/index.cjs +1 -0
  22. package/dist/InputPassword/index.mjs +1 -0
  23. package/dist/InputPassword-es.js +1 -1
  24. package/dist/InputPhoneNumber/index.cjs +6 -5
  25. package/dist/InputPhoneNumber/index.mjs +2 -1
  26. package/dist/InputText/index.cjs +8 -7
  27. package/dist/InputText/index.mjs +4 -3
  28. package/dist/InputTime/index.cjs +5 -4
  29. package/dist/InputTime/index.mjs +2 -1
  30. package/dist/RecurringSelect/index.cjs +3 -14
  31. package/dist/RecurringSelect/index.mjs +3 -14
  32. package/dist/RecurringSelect-cjs.js +6 -6
  33. package/dist/RecurringSelect-es.js +1 -1
  34. package/dist/Select/Select.d.ts +6 -2
  35. package/dist/Select/Select.types.d.ts +2 -16
  36. package/dist/Select/index.cjs +11 -103
  37. package/dist/Select/index.d.ts +2 -9
  38. package/dist/Select/index.mjs +9 -105
  39. package/dist/Select-cjs.js +78 -0
  40. package/dist/Select-es.js +75 -0
  41. package/dist/docs/Select/Select.md +69 -33
  42. package/dist/docs/usage-guidelines/usage-guidelines.md +10 -13
  43. package/dist/index.cjs +14 -13
  44. package/dist/index.mjs +3 -2
  45. package/dist/mergeRefs-cjs.js +279 -0
  46. package/dist/mergeRefs-es.js +265 -0
  47. package/package.json +2 -2
  48. package/dist/Select/Select.rebuilt.d.ts +0 -3
@@ -9,9 +9,9 @@ selected. For grouping options with section headers, use Select.OptionGroup.
9
9
 
10
10
  #### Custom grouped menu and browser support
11
11
 
12
- V2 remains a native `<select>` by default. To enable the enhanced grouped menu
13
- styling (bold section headers, edge‑to‑edge dividers and hover backgrounds), set
14
- the `UNSAFE_experimentalStyles` prop on `Select`.
12
+ To enable the enhanced grouped menu styling (bold section headers, edge‑to‑edge
13
+ dividers and hover backgrounds), set the `UNSAFE_experimentalStyles` prop on
14
+ `Select`.
15
15
 
16
16
  * When `UNSAFE_experimentalStyles` is true and the browser supports
17
17
  `appearance: base-select` (Chromium 123+), the select renders with the
@@ -20,21 +20,19 @@ the `UNSAFE_experimentalStyles` prop on `Select`.
20
20
  the select remains native while still rendering the same `OptionGroup`
21
21
  structure.
22
22
 
23
- #### Option Props
24
-
25
- #### OptionGroup Props
26
-
27
23
  ## States
28
24
 
29
25
  ### Invalid
30
26
 
31
27
  ```tsx
32
- import React from "react";
28
+ import React, { useState } from "react";
33
29
  import { Option, Select } from "@jobber/components/Select";
34
30
 
35
31
  export function SelectInvalidExample() {
32
+ const [value, setValue] = useState<string | number | undefined>("");
33
+
36
34
  return (
37
- <Select invalid={true}>
35
+ <Select invalid={true} value={value} onChange={setValue}>
38
36
  <Option value="sad">Tony</Option>
39
37
  <Option value="old">Steve</Option>
40
38
  </Select>
@@ -45,12 +43,14 @@ export function SelectInvalidExample() {
45
43
  ### Disabled
46
44
 
47
45
  ```tsx
48
- import React from "react";
46
+ import React, { useState } from "react";
49
47
  import { Option, Select } from "@jobber/components/Select";
50
48
 
51
49
  export function SelectDisabledExample() {
50
+ const [value, setValue] = useState<string | number | undefined>("");
51
+
52
52
  return (
53
- <Select disabled={true}>
53
+ <Select disabled={true} value={value} onChange={setValue}>
54
54
  <Option value="sad">Tony</Option>
55
55
  <Option value="old">Steve</Option>
56
56
  </Select>
@@ -67,15 +67,18 @@ provides the section header text.
67
67
  ### Basic Option Groups
68
68
 
69
69
  ```tsx
70
- import React from "react";
70
+ import React, { useState } from "react";
71
71
  import { Select } from "@jobber/components/Select";
72
72
 
73
73
  export function SelectCustomOptionGroupsExample() {
74
+ const [value, setValue] = useState<string | number | undefined>("");
75
+
74
76
  return (
75
77
  <Select
76
78
  placeholder="Select an option"
77
- version={2}
78
79
  UNSAFE_experimentalStyles={true}
80
+ value={value}
81
+ onChange={setValue}
79
82
  >
80
83
  <Select.OptionGroup label="Team A">
81
84
  <Select.Option value="alice">Alice</Select.Option>
@@ -100,15 +103,18 @@ export function SelectCustomOptionGroupsExample() {
100
103
  ### Disabled Option Groups
101
104
 
102
105
  ```tsx
103
- import React from "react";
106
+ import React, { useState } from "react";
104
107
  import { Select } from "@jobber/components/Select";
105
108
 
106
109
  export function SelectCustomOptionGroupDisabledExample() {
110
+ const [value, setValue] = useState<string | number | undefined>("");
111
+
107
112
  return (
108
113
  <Select
109
- version={2}
110
114
  UNSAFE_experimentalStyles
111
115
  placeholder="Select an option"
116
+ value={value}
117
+ onChange={setValue}
112
118
  >
113
119
  <Select.OptionGroup label="Available Items">
114
120
  <Select.Option value="option1">Option 1</Select.Option>
@@ -132,7 +138,7 @@ export function SelectCustomOptionGroupDisabledExample() {
132
138
 
133
139
  ### Custom (Chromium) vs native
134
140
 
135
- Select (V2) is native by default. Opt into the customizable Chrome UI with
141
+ Select is native by default. Opt into the customizable Chrome UI with
136
142
  `UNSAFE_experimentalStyles`. This enhances grouped menus in Chromium 123+ when
137
143
  the browser supports `appearance: base-select`.
138
144
 
@@ -186,30 +192,60 @@ dropdown content is stylable; the closed value alignment is not.
186
192
 
187
193
  ### Web
188
194
 
195
+ #### Select
196
+
189
197
  | Prop | Type | Required | Default | Description |
190
198
  |------|------|----------|---------|-------------|
191
199
  | `align` | `"center" | "right"` | No | — | Determines the alignment of the text inside the input. |
192
- | `autofocus` | `boolean` | No | — | Determines if the input should be auto-focused, using the HTML attribute |
200
+ | `aria-activedescendant` | `string` | No | — | ID of the currently active descendant element. Used for composite widgets like combobox or listbox. @see {@link https... |
201
+ | `aria-autocomplete` | `"inline" | "none" | "list" | "both"` | No | — | Indicates the type of autocomplete interaction. @see {@link https://www.w3.org/TR/wai-aria-1.2/#aria-autocomplete} |
202
+ | `aria-controls` | `string` | No | — | Indicates the element that controls the current element. @see {@link https://www.w3.org/TR/wai-aria-1.2/#aria-controls} |
203
+ | `aria-describedby` | `string` | No | — | Identifies the element (or elements) that describes the object. @see {@link https://www.w3.org/TR/wai-aria-1.2/#aria-... |
204
+ | `aria-details` | `string` | No | — | Identifies the element (or elements) that provide a detailed, extended description. @see {@link https://www.w3.org/TR... |
205
+ | `aria-expanded` | `Booleanish` | No | — | Indicates whether the element is expanded or collapsed. @see {@link https://www.w3.org/TR/wai-aria-1.2/#aria-expanded} |
206
+ | `aria-label` | `string` | No | — | Defines a string value that labels the current element. @see {@link https://www.w3.org/TR/wai-aria-1.2/#aria-label} |
207
+ | `aria-labelledby` | `string` | No | — | Identifies the element (or elements) that labels the current element. @see {@link https://www.w3.org/TR/wai-aria-1.2/... |
208
+ | `aria-required` | `Booleanish` | No | — | Indicates that user input is required before form submission. @see {@link https://www.w3.org/TR/wai-aria-1.2/#aria-re... |
209
+ | `autoComplete` | `string` | No | — | Autocomplete behavior for the input (React casing, string values only). Use standard HTML autocomplete values or "on"... |
210
+ | `autoFocus` | `boolean` | No | — | Whether the input should be auto-focused (React casing). |
193
211
  | `children` | `ReactNode` | No | — | If you need to pass in a children. For example, `<options>` inside `<select>`. |
194
- | `defaultValue` | `string | Date` | No | — | Initial value of the input. Only use this when you need to pre-populate the field with a data that is not controlled ... |
195
212
  | `description` | `ReactNode` | No | — | Further description of the input, can be used for a hint. |
196
- | `disabled` | `boolean` | No | — | Disable the input |
197
- | `id` | `string` | No | — | A unique identifier for the input. |
198
- | `inline` | `boolean` | No | — | Adjusts the form field to go inline with a content. This also silences the given `validations` prop. You'd have to us... |
199
- | `inputRef` | `RefObject<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>` | No | | |
213
+ | `disabled` | `boolean` | No | — | Whether the input is disabled. |
214
+ | `error` | `string` | No | — | Error message to display. This also highlights the field red. |
215
+ | `id` | `string` | No | — | The unique identifier for the input element. |
216
+ | `inline` | `boolean` | No | | Adjusts the form field to go inline with content. |
217
+ | `inputMode` | `"none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search"` | No | — | Input mode hint for virtual keyboards. |
218
+ | `inputRef` | `Ref<HTMLSelectElement>` | No | — | |
200
219
  | `invalid` | `boolean` | No | — | Highlights the field red to indicate an error. |
201
- | `maxLength` | `number` | No | — | Changes the width to roughly the same size as the maximum character length |
202
- | `name` | `string` | No | — | Name of the input. |
203
- | `onBlur` | `(event?: FocusEvent<Element, Element>) => void` | No | — | Blur callback. |
204
- | `onChange` | `(newValue: string | number | boolean | Date, event?: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => void` | No | — | onChange handler that provides the new value (or event) @param newValue @param event |
205
- | `onEnter` | `(event: KeyboardEvent<Element>) => void` | No | — | A callback to handle "Enter" keypress. This will only run if Enter is the only key. Will not run if Shift or Control ... |
206
- | `onFocus` | `(event?: FocusEvent<Element, Element>) => void` | No | — | Focus callback. |
207
- | `onValidation` | `(message: string) => void` | No | — | Callback to get the the status and message when validating a field @param message |
220
+ | `loading` | `boolean` | No | — | Show a spinner to indicate loading. |
221
+ | `name` | `string` | No | — | The name attribute for the input element. |
222
+ | `onBlur` | `(event: FocusEvent<HTMLSelectElement, Element>) => void` | No | — | Blur event handler. |
223
+ | `onChange` | `(newValue?: string | number) => void` | No | — | |
224
+ | `onEnter` | `(event: KeyboardEvent<Element>) => void` | No | — | @deprecated Use `onKeyDown` or `onKeyUp` instead. |
225
+ | `onFocus` | `(event: FocusEvent<HTMLSelectElement, Element>) => void` | No | — | Focus event handler. |
226
+ | `pattern` | `string` | No | — | Validation pattern (regex) for the input. |
208
227
  | `placeholder` | `string` | No | — | Text that appears inside the input when empty and floats above the value as a mini label once the user enters a value... |
209
228
  | `prefix` | `Affix` | No | — | Adds a prefix label and icon to the field |
229
+ | `required` | `boolean` | No | — | Whether the input is required before form submission. |
230
+ | `role` | `string` | No | — | Role attribute for accessibility. |
210
231
  | `size` | `"small" | "large"` | No | — | Adjusts the interface to either have small or large spacing. |
211
232
  | `suffix` | `{ onClick: () => void; readonly ariaLabel: string; readonly icon: IconNames; readonly label?: string; } | { onClick?: never; ariaLabel?: never; readonly label?: string; readonly icon?: IconNames; }` | No | — | Adds a suffix label and icon with an optional action to the field |
212
- | `validations` | `RegisterOptions` | No | — | Show an error message above the field. This also highlights the the field red if an error message shows up. |
213
- | `value` | `string | number | Date` | No | | Set the component to the given value. |
214
- | `version` | `1` | No | — | Experimental: Determine which version of the FormField to use. Right now this isn't used but it will be used in the f... |
215
- | `wrapperRef` | `RefObject<HTMLDivElement>` | No | — | |
233
+ | `tabIndex` | `number` | No | — | Tab index for keyboard navigation. |
234
+ | `UNSAFE_experimentalStyles` | `boolean` | No | | Opt-in to the customizable select UI (Chromium 123+). When true, the component will apply the custom select styles De... |
235
+ | `value` | `string | number` | No | — | |
236
+
237
+ #### Select.Option
238
+
239
+ | Prop | Type | Required | Default | Description |
240
+ |------|------|----------|---------|-------------|
241
+ | `disabled` | `boolean` | No | — | |
242
+ | `value` | `string` | No | — | The content of this attribute represents the value to be submitted with the form, should this option be selected. |
243
+
244
+ #### Select.OptionGroup
245
+
246
+ | Prop | Type | Required | Default | Description |
247
+ |------|------|----------|---------|-------------|
248
+ | `label` | `string` | Yes | — | |
249
+ | `disabled` | `boolean` | No | — | |
250
+ | `UNSAFE_className` | `string` | No | — | Use at your own risk: Custom classnames for specific elements. This should only be used as a last resort. Using this ... |
251
+ | `UNSAFE_style` | `{ container?: CSSProperties; }` | No | — | Use at your own risk: Custom style for specific elements. This should only be used as a last resort. Using this may r... |
@@ -11,17 +11,16 @@ The following components have a v2 implementation. **Always pass
11
11
  `version={2}`**. Never generate v1 usage for these components — v1 is
12
12
  deprecated.
13
13
 
14
- | Component | Import | v2 prop |
15
- | ---------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
16
- | Autocomplete | `import { Autocomplete } from "@jobber/components/Autocomplete"` | `version={2}` — fully controlled, async support |
17
- | InputDate | `import { InputDate } from "@jobber/components/InputDate"` | `version={2}` |
18
- | InputEmail | `import { InputEmail } from "@jobber/components/InputEmail"` | `version={2}` |
19
- | InputNumber | `import { InputNumber } from "@jobber/components/InputNumber"` | `version={2}` |
20
- | InputPhoneNumber | `import { InputPhoneNumber } from "@jobber/components/InputPhoneNumber"` | `version={2}` |
21
- | InputText | `import { InputText } from "@jobber/components/InputText"` | `version={2}` |
22
- | InputTime | `import { InputTime } from "@jobber/components/InputTime"` | `version={2}` |
23
- | Modal | `import { Modal } from "@jobber/components/Modal"` | `version={2}` — uses `Modal.Provider` composition |
24
- | Select | `import { Select } from "@jobber/components/Select"` | `version={2}` — native `<select>`; opt into the customizable Chromium UI with `UNSAFE_experimentalStyles` |
14
+ | Component | Import | v2 prop |
15
+ | ---------------- | ------------------------------------------------------------------------ | ------------------------------------------------- |
16
+ | Autocomplete | `import { Autocomplete } from "@jobber/components/Autocomplete"` | `version={2}` — fully controlled, async support |
17
+ | InputDate | `import { InputDate } from "@jobber/components/InputDate"` | `version={2}` |
18
+ | InputEmail | `import { InputEmail } from "@jobber/components/InputEmail"` | `version={2}` |
19
+ | InputNumber | `import { InputNumber } from "@jobber/components/InputNumber"` | `version={2}` |
20
+ | InputPhoneNumber | `import { InputPhoneNumber } from "@jobber/components/InputPhoneNumber"` | `version={2}` |
21
+ | InputText | `import { InputText } from "@jobber/components/InputText"` | `version={2}` |
22
+ | InputTime | `import { InputTime } from "@jobber/components/InputTime"` | `version={2}` |
23
+ | Modal | `import { Modal } from "@jobber/components/Modal"` | `version={2}` — uses `Modal.Provider` composition |
25
24
 
26
25
  ## Deprecated components — do not use
27
26
 
@@ -39,7 +38,6 @@ deprecated.
39
38
  | `Card` | `title` | Use the `header` prop, or compose with `Card.Header` and `Card.Body`. |
40
39
  | `Toast` | `action`, `actionLabel` | Both will be removed in the next major version. Trigger follow-up work from outside the toast. |
41
40
  | Text inputs (`InputText` v2 etc.) | `onEnter` | Use `onKeyDown` or `onKeyUp` and check the key. |
42
- | `Select` v2 | `onEnter` | Use `onKeyDown` or `onKeyUp`. |
43
41
  | `FormField` (`maxLength` on v1 inputs) | `maxLength` | Flagged in source as flawed; only kept for v1 backwards compatibility. |
44
42
 
45
43
  ## Import pattern
@@ -118,7 +116,6 @@ parent reads its compound children to wire up state, accessibility, and styling.
118
116
  | `Banner` | `Banner.Provider`, `Banner.Content`, `Banner.Icon`, `Banner.DismissButton`, `Banner.Action` | Either the simple `<Banner type="...">children</Banner>` form, or the Provider form: `Banner.Provider` > `Banner.Content` (+ optional `Banner.Icon` / `Banner.Action` / `Banner.DismissButton`). |
119
117
  | `Card` | `Card.Header`, `Card.Body` | `Card` > `Card.Header` + `Card.Body`. The legacy `header` string/element prop still works for simple cases. Use composition for any header that needs richer markup. |
120
118
  | `Combobox` | `Combobox.Activator`, `Combobox.Option`, `Combobox.Action` | `Combobox` > optional `Combobox.Activator`, one or more `Combobox.Option`, optional `Combobox.Action`. Omitting `Combobox.Activator` renders the default activator. |
121
- | `Select` v2 | `Select.Option`, `Select.OptionGroup` | These map directly to native `<option>` and `<optgroup>`. Use `UNSAFE_experimentalStyles` to opt into the customizable Chromium UI for grouped menus. |
122
119
  | `DataTable` | `DataTable.Container`, `DataTable.Table`, `DataTable.Header`, `DataTable.HeaderCell`, `DataTable.SortableHeader`, `DataTable.Body`, `DataTable.Row`, `DataTable.Cell`, `DataTable.RowActions`, `DataTable.Actions`, `DataTable.Footer`, `DataTable.Pagination`, `DataTable.PaginationButton` | `DataTable.Footer` is a sibling of `DataTable.Body`, not a child. Place pagination outside the table (not inside the footer). For `layout="fixed"`, pass explicit widths via `style={{ width: "..." }}` on each `HeaderCell`. |
123
120
  | `Menu` (web composable) | `Menu.Trigger`, `Menu.Content`, `Menu.Section`, `Menu.Header`, `Menu.HeaderLabel`, `Menu.Item`, `Menu.ItemIcon`, `Menu.ItemLabel`, `Menu.Separator` | `Menu` > `Menu.Trigger` + `Menu.Content` containing `Menu.Item`s (optionally grouped in `Menu.Section`s). Each `Menu.Item` must include `textValue` for type-ahead and screen readers. |
124
121
  | `Page` | `Page.Header`, `Page.HeaderContent`, `Page.TitleBar`, `Page.Title`, `Page.Subtitle`, `Page.Intro`, `Page.Actions`, `Page.ActionPrimary`, `Page.ActionSecondary`, `Page.ActionMenu`, `Page.PrimaryButton`, `Page.SecondaryButton`, `Page.Menu`, `Page.Body` | Composable `Page` API: `Page` > `Page.Header` (containing `Page.HeaderContent` with `Page.TitleBar` > `Page.Title` / `Page.Subtitle`, and `Page.Actions` with `Page.ActionPrimary` / `Page.ActionSecondary` / `Page.ActionMenu`) + `Page.Body`. The legacy props-based API (`title`, `subtitle`, `intro`, etc.) still works. |
package/dist/index.cjs CHANGED
@@ -43,6 +43,7 @@ var FeatureSwitch = require('./FeatureSwitch-cjs.js');
43
43
  var Flex = require('./Flex-cjs.js');
44
44
  var Form = require('./Form-cjs.js');
45
45
  var FormField = require('./FormField-cjs.js');
46
+ var mergeRefs = require('./mergeRefs-cjs.js');
46
47
  var FormatDate = require('./FormatDate-cjs.js');
47
48
  var FormatEmail = require('./FormatEmail-cjs.js');
48
49
  var FormatFile = require('./FormatFile-cjs.js');
@@ -79,7 +80,7 @@ var RadioGroup = require('./RadioGroup-cjs.js');
79
80
  var RecurringSelect = require('./RecurringSelect-cjs.js');
80
81
  var DayOfMonthSelect = require('./DayOfMonthSelect-cjs.js');
81
82
  var ResponsiveSwitcher = require('./ResponsiveSwitcher-cjs.js');
82
- var Select_index = require('./Select/index.cjs');
83
+ var Select = require('./Select-cjs.js');
83
84
  var SegmentedControl = require('./SegmentedControl-cjs.js');
84
85
  var SideDrawer = require('./SideDrawer-cjs.js');
85
86
  var SideKick = require('./SideKick-cjs.js');
@@ -251,20 +252,20 @@ exports.Emphasis = Emphasis.Emphasis;
251
252
  exports.FeatureSwitch = FeatureSwitch.FeatureSwitch;
252
253
  exports.Flex = Flex.Flex;
253
254
  exports.Form = Form.Form;
254
- exports.AffixIcon = FormField.AffixIcon;
255
- exports.AffixLabel = FormField.AffixLabel;
256
255
  exports.FormField = FormField.FormField;
257
- exports.FormFieldInputHorizontalWrapper = FormField.FormFieldInputHorizontalWrapper;
258
- exports.FormFieldInputWrapperStyles = FormField.FormFieldInputWrapperStyles;
259
- exports.FormFieldLabel = FormField.FormFieldLabel;
260
- exports.FormFieldWrapper = FormField.FormFieldWrapper;
261
- exports.FormFieldWrapperMain = FormField.FormFieldWrapperMain;
262
- exports.FormFieldWrapperToolbar = FormField.FormFieldWrapperToolbar;
263
256
  exports.useAtlantisFormField = FormField.useAtlantisFormField;
264
257
  exports.useAtlantisFormFieldActions = FormField.useAtlantisFormFieldActions;
265
- exports.useAtlantisFormFieldName = FormField.useAtlantisFormFieldName;
266
258
  exports.useAtlantisReactHookForm = FormField.useAtlantisReactHookForm;
267
- exports.useFormFieldWrapperStyles = FormField.useFormFieldWrapperStyles;
259
+ exports.AffixIcon = mergeRefs.AffixIcon;
260
+ exports.AffixLabel = mergeRefs.AffixLabel;
261
+ exports.FormFieldInputHorizontalWrapper = mergeRefs.FormFieldInputHorizontalWrapper;
262
+ exports.FormFieldInputWrapperStyles = mergeRefs.FormFieldInputWrapperStyles;
263
+ exports.FormFieldLabel = mergeRefs.FormFieldLabel;
264
+ exports.FormFieldWrapper = mergeRefs.FormFieldWrapper;
265
+ exports.FormFieldWrapperMain = mergeRefs.FormFieldWrapperMain;
266
+ exports.FormFieldWrapperToolbar = mergeRefs.FormFieldWrapperToolbar;
267
+ exports.useAtlantisFormFieldName = mergeRefs.useAtlantisFormFieldName;
268
+ exports.useFormFieldWrapperStyles = mergeRefs.useFormFieldWrapperStyles;
268
269
  exports.FormatDate = FormatDate.FormatDate;
269
270
  exports.strFormatDate = FormatDate.strFormatDate;
270
271
  exports.FormatEmail = FormatEmail.FormatEmail;
@@ -323,8 +324,8 @@ Object.defineProperty(exports, "WeekDay", {
323
324
  get: function () { return DayOfMonthSelect.WeekDay; }
324
325
  });
325
326
  exports.ResponsiveSwitcher = ResponsiveSwitcher.ResponsiveSwitcher;
326
- exports.Option = Select_index.Option;
327
- exports.Select = Select_index.Select;
327
+ exports.Option = Select.SelectOption;
328
+ exports.Select = Select.Select;
328
329
  exports.SegmentedControl = SegmentedControl.SegmentedControl;
329
330
  exports.SegmentedControlContext = SegmentedControl.SegmentedControlContext;
330
331
  exports.SegmentedControlProvider = SegmentedControl.SegmentedControlProvider;
package/dist/index.mjs CHANGED
@@ -40,7 +40,8 @@ export { E as Emphasis } from './Emphasis-es.js';
40
40
  export { F as FeatureSwitch } from './FeatureSwitch-es.js';
41
41
  export { F as Flex } from './Flex-es.js';
42
42
  export { F as Form } from './Form-es.js';
43
- export { A as AffixIcon, i as AffixLabel, k as FormField, F as FormFieldInputHorizontalWrapper, d as FormFieldInputWrapperStyles, e as FormFieldLabel, f as FormFieldWrapper, g as FormFieldWrapperMain, h as FormFieldWrapperToolbar, u as useAtlantisFormField, a as useAtlantisFormFieldActions, b as useAtlantisFormFieldName, c as useAtlantisReactHookForm, j as useFormFieldWrapperStyles } from './FormField-es.js';
43
+ export { F as FormField, u as useAtlantisFormField, a as useAtlantisFormFieldActions, b as useAtlantisReactHookForm } from './FormField-es.js';
44
+ export { A as AffixIcon, f as AffixLabel, F as FormFieldInputHorizontalWrapper, a as FormFieldInputWrapperStyles, b as FormFieldLabel, c as FormFieldWrapper, d as FormFieldWrapperMain, e as FormFieldWrapperToolbar, u as useAtlantisFormFieldName, g as useFormFieldWrapperStyles } from './mergeRefs-es.js';
44
45
  export { F as FormatDate, s as strFormatDate } from './FormatDate-es.js';
45
46
  export { F as FormatEmail } from './FormatEmail-es.js';
46
47
  export { F as FormatFile, u as useFormatFile, a as useFormatFileStyles } from './FormatFile-es.js';
@@ -77,7 +78,7 @@ export { R as RadioGroup, a as RadioOption } from './RadioGroup-es.js';
77
78
  export { R as RecurringSelect } from './RecurringSelect-es.js';
78
79
  export { D as DurationPeriod, W as WeekDay } from './DayOfMonthSelect-es.js';
79
80
  export { R as ResponsiveSwitcher } from './ResponsiveSwitcher-es.js';
80
- export { Option, Select } from './Select/index.mjs';
81
+ export { S as Option, a as Select } from './Select-es.js';
81
82
  export { S as SegmentedControl, a as SegmentedControlContext, b as SegmentedControlProvider, u as useSegmentedControl } from './SegmentedControl-es.js';
82
83
  export { S as SideDrawer } from './SideDrawer-es.js';
83
84
  export { S as SideKick } from './SideKick-es.js';
@@ -0,0 +1,279 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var jobberHooks = require('@jobber/hooks');
5
+ var framerMotion = require('framer-motion');
6
+ var design = require('@jobber/design');
7
+ var classnames = require('classnames');
8
+ var Button = require('./Button-cjs.js');
9
+ var Icon = require('./Icon-cjs.js');
10
+ var Text = require('./Text-cjs.js');
11
+ var useFormFieldFocus = require('./useFormFieldFocus-cjs.js');
12
+ var InputValidation = require('./InputValidation-cjs.js');
13
+ var Spinner = require('./Spinner-cjs.js');
14
+
15
+ var formFieldStyles = {"container":"YL-mNv-Bl6g-","wrapper":"_8lhbGTQ-hhg-","disabled":"Tz9LK9ABKMk-","horizontalWrapper":"b5mv1x1H7YE-","textarea":"hGr6YW4AeLM-","safari":"QBCWi9GBgMs-","timeInputLabel":"_0pmqVa2zSE4-","miniLabel":"F1t76G6bDKo-","large":"_9tjyT9QUtP8-","text":"QmMiyoAWp-g-","invalid":"XWDSfe6weSY-","small":"Sw5O4I0lMJg-","inline":"SaORbL7SYWY-","center":"ozy2UoT2vsg-","right":"_3TJdT91YD3c-","maxLength":"W6GrMqLy2qk-","inputWrapper":"-LmjnYRU0r0-","childrenWrapper":"yVXYv6hkuOA-","input":"vtSDcuzNr9Q-","emptyPhoneNumber":"MVhuQuzUBUs-","label":"Dgk00tzlODA-","select":"NwQGiWBWIsc-","externalLabel":"Qb8zQ8n-8vc-","postfix":"yTDzs9h1otI-","affixIcon":"m0YpdssD2dY-","suffix":"_-3mMnjSh6ok-","affixLabel":"-Wzcb0pBh5I-","description":"DHX5ijY3xIw-","toolbar":"AL-2brNI7dk-","spinning":"_8Rzv7CcDW80-"};
16
+
17
+ /**
18
+ * @internal Reach out to UX Foundations if using this component since it is possible it might change
19
+ */
20
+ function AffixLabel({ label, variation = "prefix", labelRef, }) {
21
+ const affixLabelClass = classnames(formFieldStyles.affixLabel, {
22
+ [formFieldStyles.suffix]: variation === "suffix",
23
+ });
24
+ if (!label)
25
+ return null;
26
+ return (React.createElement("div", { ref: labelRef, className: affixLabelClass }, label));
27
+ }
28
+ function AffixIcon({ icon, onClick, ariaLabel, variation = "prefix", size, }) {
29
+ const affixIconClass = classnames(formFieldStyles.affixIcon, {
30
+ [formFieldStyles.suffix]: variation === "suffix",
31
+ });
32
+ const iconSize = size === "small" ? "small" : "base";
33
+ if (!icon)
34
+ return null;
35
+ return (React.createElement("div", { className: affixIconClass }, onClick ? (React.createElement(Button.Button
36
+ /**
37
+ * We can cast the ariaLabel here as a `Suffix`
38
+ * requires an ariaLabel if there is an action
39
+ */
40
+ , {
41
+ /**
42
+ * We can cast the ariaLabel here as a `Suffix`
43
+ * requires an ariaLabel if there is an action
44
+ */
45
+ ariaLabel: ariaLabel, icon: icon, onClick: onClick, variation: "subtle", type: "tertiary", size: iconSize })) : (React.createElement(Icon.Icon, { name: icon, size: iconSize, color: "greyBlue" }))));
46
+ }
47
+
48
+ function FormFieldDescription({ id, description, visible = true, }) {
49
+ if (!visible)
50
+ return null;
51
+ const useStringFormat = !description || typeof description === "string";
52
+ return (React.createElement("div", { id: id, className: formFieldStyles.description }, useStringFormat ? (React.createElement(Text.Text, { size: "small", variation: "subdued" }, description)) : (description)));
53
+ }
54
+
55
+ var styles = {"clearInput":"YmRTd-KeXv4-","spinning":"B25z9B8I3gs-"};
56
+
57
+ function ClearAction({ onClick, visible, }) {
58
+ if (!visible)
59
+ return null;
60
+ return (React.createElement("button", { className: styles.clearInput, onClick: onClick, type: "button", "aria-label": "Clear input", "data-testid": "ATL-FormField-clearButton" },
61
+ React.createElement(Icon.Icon, { name: "remove", size: "small" })));
62
+ }
63
+
64
+ function useToolbar(props) {
65
+ const isToolbarVisible = props.toolbar !== undefined &&
66
+ (props.toolbarVisibility === "always" || props.focused);
67
+ const toolbarAnimationEnd = { opacity: 0, height: 0 };
68
+ const toolbarAnimationStart = { opacity: 1, height: "auto" };
69
+ return {
70
+ isToolbarVisible,
71
+ toolbarAnimationEnd,
72
+ toolbarAnimationStart,
73
+ };
74
+ }
75
+
76
+ function useIsSafari() {
77
+ return (globalThis === null || globalThis === void 0 ? void 0 : globalThis.navigator)
78
+ ? /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
79
+ : false;
80
+ }
81
+
82
+ /**
83
+ * Hook for getting the correct styles for the FormField and its children
84
+ */
85
+ function useFormFieldWrapperStyles({ size, align, placeholder, value, invalid, error, max, prefixRef, suffixRef, maxLength, type, disabled, inline, showMiniLabel = true, }) {
86
+ const isSafari = useIsSafari();
87
+ const wrapperClasses = classnames(formFieldStyles.wrapper, size && formFieldStyles[size], align && formFieldStyles[align], {
88
+ [formFieldStyles.miniLabel]: (showMiniLabel && placeholder && value !== "") ||
89
+ (placeholder && type === "select") ||
90
+ // Naively assume that if the the type is tel, it is the InputPhoneNumber
91
+ (placeholder && type === "tel"),
92
+ [formFieldStyles.text]: type === "textarea" || type === "text",
93
+ [formFieldStyles.textarea]: type === "textarea",
94
+ [formFieldStyles.safari]: isSafari && type === "textarea",
95
+ [formFieldStyles.select]: type === "select",
96
+ [formFieldStyles.invalid]: invalid !== null && invalid !== void 0 ? invalid : error,
97
+ [formFieldStyles.disabled]: disabled,
98
+ [formFieldStyles.maxLength]: maxLength,
99
+ [formFieldStyles.timeInputLabel]: placeholder && type === "time" && placeholder && value === "",
100
+ });
101
+ const containerClasses = classnames(formFieldStyles.container, {
102
+ [formFieldStyles.inline]: inline,
103
+ });
104
+ const wrapperInlineStyle = {
105
+ ["--formField-maxLength"]: maxLength || max,
106
+ };
107
+ const [labelStyle, setLabelStyle] = React.useState({
108
+ paddingLeft: undefined,
109
+ paddingRight: undefined,
110
+ });
111
+ React.useEffect(() => {
112
+ var _a, _b;
113
+ setLabelStyle(getAffixPaddding({
114
+ value,
115
+ type,
116
+ prefixWidth: ((_a = prefixRef === null || prefixRef === void 0 ? void 0 : prefixRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0,
117
+ suffixWidth: ((_b = suffixRef === null || suffixRef === void 0 ? void 0 : suffixRef.current) === null || _b === void 0 ? void 0 : _b.offsetWidth) || 0,
118
+ }));
119
+ }, [value]);
120
+ return {
121
+ inputStyle: formFieldStyles.input,
122
+ wrapperClasses,
123
+ containerClasses,
124
+ wrapperInlineStyle,
125
+ labelStyle,
126
+ setLabelStyle,
127
+ };
128
+ }
129
+ function getAffixPaddding({ value, type, prefixWidth, suffixWidth, }) {
130
+ const hasValue = value !== "";
131
+ const newPadding = {
132
+ paddingLeft: undefined,
133
+ paddingRight: undefined,
134
+ };
135
+ // Naively assume that if the the type is tel, it is the InputPhoneNumber
136
+ if (type === "tel")
137
+ return newPadding;
138
+ if (prefixWidth && !hasValue) {
139
+ newPadding.paddingLeft = offset(prefixWidth);
140
+ }
141
+ if (suffixWidth && !hasValue) {
142
+ newPadding.paddingRight = offset(suffixWidth);
143
+ }
144
+ function offset(width) {
145
+ return `calc(${width}px + var(--space-smallest)`;
146
+ }
147
+ return newPadding;
148
+ }
149
+
150
+ function FormFieldWrapper({ align, description, descriptionIdentifier, placeholder, value, children, invalid, error, size, prefix, suffix, max, maxLength, type, disabled, inline, identifier, clearable, onClear, readonly, toolbar, toolbarVisibility = "while-editing", showMiniLabel = true, wrapperRef, }) {
151
+ const prefixRef = React.useRef(null);
152
+ const suffixRef = React.useRef(null);
153
+ const { wrapperClasses, containerClasses, wrapperInlineStyle, labelStyle } = useFormFieldWrapperStyles({
154
+ align,
155
+ max,
156
+ maxLength,
157
+ prefixRef,
158
+ suffixRef,
159
+ placeholder,
160
+ value,
161
+ invalid,
162
+ error,
163
+ type,
164
+ disabled,
165
+ inline,
166
+ size,
167
+ showMiniLabel,
168
+ });
169
+ const { focused } = useFormFieldFocus.useFormFieldFocus({ wrapperRef });
170
+ const showClear = jobberHooks.useShowClear({
171
+ clearable,
172
+ multiline: type === "textarea",
173
+ focused,
174
+ hasValue: Boolean(value),
175
+ disabled,
176
+ readonly,
177
+ });
178
+ const { isToolbarVisible, toolbarAnimationEnd, toolbarAnimationStart } = useToolbar({
179
+ focused,
180
+ toolbar,
181
+ toolbarVisibility,
182
+ });
183
+ return (React.createElement("div", { className: containerClasses },
184
+ React.createElement("div", { className: wrapperClasses, style: wrapperInlineStyle, "data-testid": "Form-Field-Wrapper", ref: wrapperRef },
185
+ React.createElement(FormFieldInputHorizontalWrapper, null,
186
+ React.createElement(AffixIcon, Object.assign({}, prefix, { size: size })),
187
+ React.createElement(FormFieldInputWrapperStyles, null,
188
+ (showMiniLabel || !value) && (React.createElement(FormFieldLabel, { htmlFor: identifier, style: (prefixRef === null || prefixRef === void 0 ? void 0 : prefixRef.current) || (suffixRef === null || suffixRef === void 0 ? void 0 : suffixRef.current)
189
+ ? labelStyle
190
+ : undefined }, placeholder)),
191
+ React.createElement(AffixLabel, Object.assign({}, prefix, { labelRef: prefixRef })),
192
+ React.createElement(FormFieldWrapperMain, null, children),
193
+ React.createElement(AffixLabel, Object.assign({}, suffix, { labelRef: suffixRef, variation: "suffix" }))),
194
+ React.createElement(ClearAction, { onClick: onClear, visible: showClear }),
195
+ React.createElement(AffixIcon, Object.assign({}, suffix, { variation: "suffix", size: size }))),
196
+ React.createElement(FormFieldWrapperToolbar, { toolbarVisibility: toolbarVisibility, isToolbarVisible: isToolbarVisible, toolbarAnimationEnd: toolbarAnimationEnd, toolbarAnimationStart: toolbarAnimationStart, toolbar: toolbar })),
197
+ React.createElement(FormFieldDescription, { visible: !!description && !inline, id: descriptionIdentifier, description: description }),
198
+ React.createElement(InputValidation.InputValidation, { message: error, visible: !!error && !inline })));
199
+ }
200
+ /**
201
+ * @internal Reach out to UX Foundations if using this component since it is possible it might change
202
+ */
203
+ function FormFieldInputHorizontalWrapper({ children, }) {
204
+ return React.createElement("div", { className: formFieldStyles.horizontalWrapper }, children);
205
+ }
206
+ /**
207
+ * @internal Reach out to UX Foundations if using this component since it is possible it might change
208
+ */
209
+ function FormFieldInputWrapperStyles({ children, }) {
210
+ return React.createElement("div", { className: formFieldStyles.inputWrapper }, children);
211
+ }
212
+ /**
213
+ * @internal Reach out to UX Foundations if using this component since it is possible it might change
214
+ */
215
+ function FormFieldWrapperMain({ children, tabIndex = -1, }) {
216
+ return (React.createElement("div", { className: formFieldStyles.childrenWrapper, tabIndex: tabIndex }, children));
217
+ }
218
+ function FormFieldLabel({ children, htmlFor, style, external = false, }) {
219
+ if (!children)
220
+ return null;
221
+ return (React.createElement("label", { className: external ? formFieldStyles.externalLabel : formFieldStyles.label, htmlFor: htmlFor, style: style }, children));
222
+ }
223
+ /**
224
+ * @internal Reach out to UX Foundations if using this component since it is possible it might change
225
+ */
226
+ function FormFieldWrapperToolbar({ toolbar, isToolbarVisible, toolbarAnimationEnd, toolbarAnimationStart, toolbarVisibility, }) {
227
+ return (React.createElement(framerMotion.AnimatePresence, { initial: toolbarVisibility === "always" ? false : true }, isToolbarVisible && (React.createElement(framerMotion.motion.div, { key: "toolbar", initial: toolbarAnimationEnd, animate: toolbarAnimationStart, exit: toolbarAnimationEnd, transition: {
228
+ duration: design.tokens["timing-base"] / 1000,
229
+ ease: "easeInOut",
230
+ }, tabIndex: -1 },
231
+ React.createElement("div", { className: formFieldStyles.toolbar, "data-testid": "ATL-InputText-Toolbar" }, toolbar)))));
232
+ }
233
+
234
+ function FormFieldPostFix({ variation, visible = true, className, }) {
235
+ if (!visible)
236
+ return null;
237
+ return (React.createElement("span", { className: classnames(formFieldStyles.postfix, className) }, variation === "select" ? (React.createElement(Icon.Icon, { name: "arrowDown" })) : (React.createElement(Spinner.Spinner, { size: "small" }))));
238
+ }
239
+
240
+ function useAtlantisFormFieldName({ id, nameProp, }) {
241
+ /**
242
+ * Generate a name if one is not supplied, this is the name
243
+ * that will be used for react-hook-form and not neccessarily
244
+ * attached to the DOM
245
+ */
246
+ const name = nameProp ? nameProp : `generatedName--${id}`;
247
+ return { name };
248
+ }
249
+
250
+ /**
251
+ * Given an array of refs, merge them into a single ref callback.
252
+ * This is useful for cases where you need to pass a multiple refs to a component.
253
+ */
254
+ function mergeRefs(refs) {
255
+ return value => {
256
+ refs.forEach(ref => {
257
+ if (typeof ref === "function") {
258
+ ref(value);
259
+ }
260
+ else if (ref != null) {
261
+ ref.current = value;
262
+ }
263
+ });
264
+ };
265
+ }
266
+
267
+ exports.AffixIcon = AffixIcon;
268
+ exports.AffixLabel = AffixLabel;
269
+ exports.FormFieldInputHorizontalWrapper = FormFieldInputHorizontalWrapper;
270
+ exports.FormFieldInputWrapperStyles = FormFieldInputWrapperStyles;
271
+ exports.FormFieldLabel = FormFieldLabel;
272
+ exports.FormFieldPostFix = FormFieldPostFix;
273
+ exports.FormFieldWrapper = FormFieldWrapper;
274
+ exports.FormFieldWrapperMain = FormFieldWrapperMain;
275
+ exports.FormFieldWrapperToolbar = FormFieldWrapperToolbar;
276
+ exports.formFieldStyles = formFieldStyles;
277
+ exports.mergeRefs = mergeRefs;
278
+ exports.useAtlantisFormFieldName = useAtlantisFormFieldName;
279
+ exports.useFormFieldWrapperStyles = useFormFieldWrapperStyles;