@jobber/components-native 0.113.4 → 0.113.5-JOB-174434-1d4c303.10

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 (105) hide show
  1. package/dist/docs/AtlantisThemeContext/AtlantisThemeContext.md +27 -10
  2. package/dist/docs/Button/Button.md +1 -1
  3. package/dist/docs/Chip/Chip.md +1 -1
  4. package/dist/docs/Colors/Colors.md +7 -1
  5. package/dist/docs/EmptyState/EmptyState.md +1 -1
  6. package/dist/docs/Icon/Icon.md +69 -35
  7. package/dist/docs/InputNumber/InputNumber.md +131 -17
  8. package/dist/docs/InputText/InputText.md +9 -1
  9. package/dist/docs/Spacing/Spacing.md +1 -1
  10. package/dist/docs/page-layouts/page-layouts.md +29 -0
  11. package/dist/docs/settings/settings.md +3 -3
  12. package/dist/docs/usage-guidelines/usage-guidelines.md +16 -24
  13. package/dist/package.json +6 -7
  14. package/dist/src/ActivityIndicator/ActivityIndicator.js +2 -2
  15. package/dist/src/AtlantisThemeContext/AtlantisThemeContext.js +42 -14
  16. package/dist/src/AtlantisThemeContext/AtlantisThemeContext.test.js +112 -11
  17. package/dist/src/AtlantisThemeContext/buildThemedStyles.js +8 -2
  18. package/dist/src/AtlantisThemeContext/buildThemedStyles.test.js +43 -1
  19. package/dist/src/AutoLink/hooks/useTokenGenerator.js +2 -2
  20. package/dist/src/Banner/components/BannerIcon/BannerIcon.js +1 -1
  21. package/dist/src/BottomSheet/BottomSheet.js +11 -7
  22. package/dist/src/BottomSheet/BottomSheet.style.js +1 -0
  23. package/dist/src/BottomSheet/BottomSheet.test.js +16 -0
  24. package/dist/src/Button/Button.js +1 -1
  25. package/dist/src/Checkbox/Checkbox.js +1 -1
  26. package/dist/src/ContentOverlay/ContentOverlay.js +2 -2
  27. package/dist/src/Disclosure/Disclosure.js +1 -1
  28. package/dist/src/EmptyState/EmptyState.js +1 -1
  29. package/dist/src/EmptyState/EmptyState.test.js +2 -1
  30. package/dist/src/ErrorMessageWrapper/ErrorMessageWrapper.js +5 -5
  31. package/dist/src/FormatFile/components/ProgressBar/ProgressBar.style.js +1 -1
  32. package/dist/src/FormatFile/components/_mocks/mockFiles.js +4 -4
  33. package/dist/src/Glimmer/Glimmer.js +8 -3
  34. package/dist/src/Glimmer/Glimmer.style.js +9 -1
  35. package/dist/src/Glimmer/Glimmer.test.js +17 -1
  36. package/dist/src/Heading/Heading.js +1 -1
  37. package/dist/src/InputFieldWrapper/CommonInputStyles.style.js +1 -1
  38. package/dist/src/InputFieldWrapper/InputFieldWrapper.test.js +2 -0
  39. package/dist/src/InputFieldWrapper/components/Prefix/Prefix.js +3 -1
  40. package/dist/src/InputFieldWrapper/components/Suffix/Suffix.js +3 -1
  41. package/dist/src/InputText/context/InputAccessoriesProvider.js +2 -2
  42. package/dist/src/Switch/components/BaseSwitch/BaseSwitch.js +1 -1
  43. package/dist/src/Toast/Toast.js +1 -1
  44. package/dist/src/Toast/Toast.styles.js +2 -2
  45. package/dist/src/Typography/Typography.js +1 -1
  46. package/dist/src/hooks/useFormController.js +2 -2
  47. package/dist/src/primitives/Select/SelectPrimitive.js +12 -2
  48. package/dist/src/primitives/Select/SelectPrimitive.style.js +6 -0
  49. package/dist/src/primitives/Select/SelectPrimitive.test.js +47 -4
  50. package/dist/tsconfig.build.tsbuildinfo +1 -1
  51. package/dist/types/src/AtlantisThemeContext/AtlantisThemeContext.d.ts +1 -1
  52. package/dist/types/src/AtlantisThemeContext/index.d.ts +1 -1
  53. package/dist/types/src/AtlantisThemeContext/types.d.ts +19 -4
  54. package/dist/types/src/BottomSheet/BottomSheet.style.d.ts +1 -0
  55. package/dist/types/src/Glimmer/Glimmer.d.ts +1 -0
  56. package/dist/types/src/Glimmer/Glimmer.style.d.ts +9 -1
  57. package/dist/types/src/primitives/Select/SelectPrimitive.d.ts +1 -1
  58. package/dist/types/src/primitives/Select/SelectPrimitive.style.d.ts +6 -0
  59. package/package.json +6 -7
  60. package/src/ActivityIndicator/ActivityIndicator.stories.tsx +2 -2
  61. package/src/ActivityIndicator/ActivityIndicator.tsx +2 -2
  62. package/src/ActivityIndicator/guide.md +7 -5
  63. package/src/AtlantisThemeContext/AtlantisThemeContext.guide.md +76 -0
  64. package/src/AtlantisThemeContext/AtlantisThemeContext.test.tsx +193 -11
  65. package/src/AtlantisThemeContext/AtlantisThemeContext.tsx +66 -13
  66. package/src/AtlantisThemeContext/buildThemedStyles.test.tsx +63 -1
  67. package/src/AtlantisThemeContext/buildThemedStyles.ts +11 -2
  68. package/src/AtlantisThemeContext/docs/AtlantisThemeContextBasicExample.tsx +8 -2
  69. package/src/AtlantisThemeContext/docs/AtlantisThemeContextForceThemeForProviderExample.tsx +8 -4
  70. package/src/AtlantisThemeContext/index.ts +1 -0
  71. package/src/AtlantisThemeContext/types.ts +23 -4
  72. package/src/AutoLink/hooks/useTokenGenerator.ts +2 -2
  73. package/src/Banner/components/BannerIcon/BannerIcon.tsx +1 -1
  74. package/src/BottomSheet/BottomSheet.style.ts +1 -0
  75. package/src/BottomSheet/BottomSheet.test.tsx +30 -0
  76. package/src/BottomSheet/BottomSheet.tsx +26 -12
  77. package/src/Button/Button.tsx +1 -1
  78. package/src/Checkbox/Checkbox.tsx +1 -1
  79. package/src/ContentOverlay/ContentOverlay.tsx +2 -2
  80. package/src/Disclosure/Disclosure.tsx +1 -1
  81. package/src/EmptyState/EmptyState.test.tsx +2 -1
  82. package/src/EmptyState/EmptyState.tsx +1 -1
  83. package/src/ErrorMessageWrapper/ErrorMessageWrapper.tsx +5 -5
  84. package/src/FormatFile/components/ProgressBar/ProgressBar.style.tsx +1 -1
  85. package/src/FormatFile/components/_mocks/mockFiles.ts +4 -4
  86. package/src/Glimmer/Glimmer.style.ts +10 -1
  87. package/src/Glimmer/Glimmer.test.tsx +35 -1
  88. package/src/Glimmer/Glimmer.tsx +9 -4
  89. package/src/Heading/Heading.tsx +1 -1
  90. package/src/Heading/__snapshots__/Heading.test.tsx.snap +5 -5
  91. package/src/InputFieldWrapper/CommonInputStyles.style.ts +1 -1
  92. package/src/InputFieldWrapper/InputFieldWrapper.test.tsx +4 -0
  93. package/src/InputFieldWrapper/components/Prefix/Prefix.tsx +3 -1
  94. package/src/InputFieldWrapper/components/Suffix/Suffix.tsx +3 -1
  95. package/src/InputText/context/InputAccessoriesProvider.tsx +2 -2
  96. package/src/Select/Select.guide.md +6 -0
  97. package/src/Switch/components/BaseSwitch/BaseSwitch.tsx +1 -1
  98. package/src/Toast/Toast.styles.ts +2 -2
  99. package/src/Toast/Toast.tsx +1 -1
  100. package/src/Typography/Typography.tsx +1 -1
  101. package/src/Typography/__snapshots__/Typography.test.tsx.snap +32 -32
  102. package/src/hooks/useFormController.ts +2 -2
  103. package/src/primitives/Select/SelectPrimitive.style.ts +8 -0
  104. package/src/primitives/Select/SelectPrimitive.test.tsx +82 -2
  105. package/src/primitives/Select/SelectPrimitive.tsx +25 -2
@@ -5,16 +5,22 @@ tokens.
5
5
 
6
6
  ## Design & usage guidelines
7
7
 
8
- Both the web and mobile components have the exact same API, except for one minor
9
- difference in how you update the theme.
8
+ Web and mobile both expose Atlantis theme context, but the runtime ownership is
9
+ slightly different across platforms.
10
10
 
11
11
  Each platform provides a `useAtlantisTheme` hook that you may use to access the
12
- `theme` and `tokens` in your components.
12
+ current theme information and design tokens in your components.
13
13
 
14
- On mobile, this hook also returns a `setTheme` function which you'll use to
15
- update the theme for the nearest `AtlantisThemeContextProvider` ancestor.
16
- Typically there will only be a single provider at the root, controlling the
17
- theme for the entire app.
14
+ On mobile, the hook returns:
15
+
16
+ * `theme`: the selected theme preference (`"system" | "light" | "dark"`)
17
+ * `effectiveTheme`: the resolved theme Atlantis uses for tokens
18
+ (`"light" | "dark"`)
19
+ * `tokens`
20
+ * `setTheme`
21
+
22
+ Typically there will only be a single provider at the root, and the consuming
23
+ app will control `theme` and persist that preference outside Atlantis.
18
24
 
19
25
  On web, you'll need to import the `updateTheme` function and call it with the
20
26
  new theme. This is a separate function because it synchronizes the theme update
@@ -86,7 +92,7 @@ function App() {
86
92
  }
87
93
 
88
94
  function ThemedComponent() {
89
- const { theme, tokens, setTheme } = useAtlantisTheme();
95
+ const { theme, effectiveTheme, tokens, setTheme } = useAtlantisTheme();
90
96
  return (
91
97
  <Content>
92
98
  <View
@@ -96,11 +102,16 @@ function ThemedComponent() {
96
102
  }}
97
103
  >
98
104
  <Content>
99
- <Text>The current theme is: {theme}.</Text>
105
+ <Text>The selected theme is: {theme}.</Text>
106
+ <Text>The effective theme is: {effectiveTheme}.</Text>
100
107
  <Text>
101
108
  The javascript tokens can be accessed via the tokens object.
102
109
  </Text>
103
110
  <Text>The theme can be changed using `setTheme`</Text>
111
+ <Button
112
+ onPress={() => setTheme("system")}
113
+ label="The theme can be changed to system"
114
+ />
104
115
  <Button
105
116
  onPress={() => setTheme("light")}
106
117
  label="The theme can be changed to light"
@@ -163,6 +174,10 @@ regardless of the main application theme. This can be done by setting the
163
174
  `dangerouslyOverrideTheme` prop to `<themeToSet>` on the
164
175
  `AtlantisThemeContextProvider`.
165
176
 
177
+ On mobile, forcing a theme changes the rendered tokens for that subtree, but it
178
+ does not replace the selected theme preference semantics from the nearest
179
+ non-forced provider.
180
+
166
181
  Note: If you're using `buildThemedStyles`, any `useStyles` calls must be done
167
182
  within a *child component* of the `AtlantisThemeContextProvider`, not in the
168
183
  same component that renders the provider. Same goes for `useAtlantisTheme`.
@@ -293,4 +308,6 @@ Key features:
293
308
  | Prop | Type | Required | Default | Description |
294
309
  |------|------|----------|---------|-------------|
295
310
  | `children` | `ReactNode` | Yes | — | The children to render. |
296
- | `dangerouslyOverrideTheme` | `Theme` | No | — | Force the theme for this provider to always be the same as the provided theme. Useful for sections that should remain... |
311
+ | `dangerouslyOverrideTheme` | `EffectiveTheme` | No | — | Force the theme for this provider to always be the same as the provided theme. Useful for sections that should remain... |
312
+ | `onThemeChange` | `(theme: Theme) => void` | No | — | Called when children request a selected theme change through setTheme. |
313
+ | `theme` | `Theme` | No | — | Control the selected theme for this provider. |
@@ -388,7 +388,7 @@ A key distinction between subtle buttons and the other variations is that the
388
388
  primary/secondary/tertiary scale is styled differently, but the conceptual
389
389
  hierarchy is the same. Notably, the tertiary subtle button has a transparent
390
390
  background, allowing it to be *extra* subtle when placed overtop
391
- `surface--background` elements such as a Modal header.
391
+ `surface--background` elements such as a Dialog header.
392
392
 
393
393
  ```tsx
394
394
  import React from "react";
@@ -22,7 +22,7 @@ export function ChipWithAvatarExample(
22
22
  <Chip.Prefix>
23
23
  <Avatar
24
24
  size="small"
25
- imageUrl="https://images.unsplash.com/photo-1669475535925-a011d7c31d45?q=80&w=1886&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
25
+ src="https://images.unsplash.com/photo-1669475535925-a011d7c31d45?q=80&w=1886&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
26
26
  />
27
27
  </Chip.Prefix>
28
28
  <Chip.Suffix>
@@ -156,7 +156,7 @@ application.
156
156
  #### Overlay
157
157
 
158
158
  Use to mask an area of the interface to promote focus to a foreground action,
159
- such as a [Modal](/components/Modal)'s appearance. Overlay includes built-in
159
+ such as a [Dialog](/components/Dialog)'s appearance. Overlay includes built-in
160
160
  opacity values.
161
161
 
162
162
  #### Overlay--Dimmed
@@ -228,3 +228,9 @@ Use to highlight an element in a way that aligns with our website. Use with
228
228
  caution, it's *bright!*
229
229
 
230
230
  ## Base colors
231
+
232
+ Base blues in light mode do not share the muted saturation of the dark-mode
233
+ overlay. Light `blue--100` through `blue--800` stay in a modest 12–35%
234
+ saturation range; `blue--900`, `blue--950`, and `blue--1000` jump to 90%+ so the
235
+ darkest steps stay distinctly blue. Dark mode remaps `700`–`1000` (including
236
+ `750` and `950`) onto roughly 9–18% saturation instead.
@@ -66,7 +66,7 @@ reflow to a second line if enough content is provided.
66
66
  |------|------|----------|---------|-------------|
67
67
  | `description` | `string` | No | — | Description of the empty state. |
68
68
  | `icon` | `IconNames` | No | — | Icon to display. |
69
- | `iconColor` | `"task" | "text" | "warning" | "icon" | "iconSecondary" | "white" | "grey" | "greyBlue" | "greyBlueDark" | "greyBlueLighter" | "blue" | "lightBlue" | "green" | "yellow" | "red" | ... 35 more ... | "brandHighlight"` | No | `blue` | Color of Icon to display. |
69
+ | `iconColor` | `"task" | "text" | "warning" | "icon" | "iconSecondary" | "white" | "grey" | "greyBlue" | "greyBlueDark" | "greyBlueLighter" | "blue" | "lightBlue" | "green" | "yellow" | "red" | ... 35 more ... | "brandHighlight"` | No | `icon` | Color of Icon to display. |
70
70
  | `primaryAction` | `Action` | No | — | Handler for the primary action. |
71
71
  | `secondaryAction` | `Action` | No | — | Handler for the secondary action. |
72
72
  | `title` | `string` | No | — | Title of the empty state. |
@@ -192,6 +192,7 @@ export function IconSizesExample() {
192
192
 
193
193
  | Icon | `Name` |
194
194
  | :--- | :----------------- |
195
+ | | `arrow` |
195
196
  | | `arrowDown` |
196
197
  | | `arrowLeft` |
197
198
  | | `arrowRight` |
@@ -212,6 +213,7 @@ export function IconSizesExample() {
212
213
  | | `calendar` |
213
214
  | | `calendarCheck` |
214
215
  | | `clearFilters` |
216
+ | | `dueDate` |
215
217
  | | `event` |
216
218
  | | `onlineBooking` |
217
219
  | | `orientationHorizontal` |
@@ -244,22 +246,23 @@ export function IconSizesExample() {
244
246
 
245
247
  ### Forms
246
248
 
247
- | Icon | `Name` |
248
- | :--- | :------------- |
249
- | | `checkbox` |
250
- | | `checklist` |
251
- | | `edit` |
252
- | | `editDisabled` |
253
- | | `radioButton` |
254
- | | `star` |
255
- | | `starHalf` |
256
- | | `starFill` |
257
- | | `text` |
258
- | | `textBox` |
259
- | | `textField` |
260
- | | `toggle` |
261
- | | `dropdown` |
262
- | | `trash` |
249
+ | Icon | `Name` |
250
+ | :--- | :---------------- |
251
+ | | `checkbox` |
252
+ | | `checklist` |
253
+ | | `digestChecklist` |
254
+ | | `edit` |
255
+ | | `editDisabled` |
256
+ | | `radioButton` |
257
+ | | `star` |
258
+ | | `starHalf` |
259
+ | | `starFill` |
260
+ | | `text` |
261
+ | | `textBox` |
262
+ | | `textField` |
263
+ | | `toggle` |
264
+ | | `dropdown` |
265
+ | | `trash` |
263
266
 
264
267
  ### Status and support
265
268
 
@@ -283,25 +286,26 @@ export function IconSizesExample() {
283
286
 
284
287
  ### Messaging
285
288
 
286
- | Icon | `Name` |
287
- | :--- | :---------------- |
288
- | | `chat` |
289
- | | `compose` |
290
- | | `conversation` |
291
- | | `email` |
292
- | | `emailInbound` |
293
- | | `emailOutbound` |
294
- | | `markSent` |
295
- | | `marketing` |
296
- | | `reminder` |
297
- | | `sendMessage` |
298
- | | `sms` |
299
- | | `sms2` |
300
- | | `smsInbound` |
301
- | | `smsOutbound` |
302
- | | `smsProgress` |
303
- | | `webchat` |
304
- | | `webchatProgress` |
289
+ | Icon | `Name` |
290
+ | :--- | :---------------------- |
291
+ | | `chat` |
292
+ | | `compose` |
293
+ | | `conversation` |
294
+ | | `email` |
295
+ | | `emailInbound` |
296
+ | | `emailOutbound` |
297
+ | | `markSent` |
298
+ | | `marketing` |
299
+ | | `reminder` |
300
+ | | `sendMessage` |
301
+ | | `sms` |
302
+ | | `sms2` |
303
+ | | `smsInbound` |
304
+ | | `smsOutbound` |
305
+ | | `smsProgress` |
306
+ | | `snoozeRecommendations` |
307
+ | | `webchat` |
308
+ | | `webchatProgress` |
305
309
 
306
310
  ### User
307
311
 
@@ -346,6 +350,8 @@ export function IconSizesExample() {
346
350
  | | `contactlessPayment` |
347
351
  | | `cheque` |
348
352
  | | `cash` |
353
+ | | `coin` |
354
+ | | `handDollarSign` |
349
355
 
350
356
  ### Work
351
357
 
@@ -425,7 +431,9 @@ export function IconSizesExample() {
425
431
  | | `future` |
426
432
  | | `history` |
427
433
  | | `import` |
434
+ | | `maximize` |
428
435
  | | `merge` |
436
+ | | `minimize` |
429
437
  | | `redo` |
430
438
  | | `search` |
431
439
  | | `sort` |
@@ -441,12 +449,14 @@ export function IconSizesExample() {
441
449
  | | `apps` |
442
450
  | | `at` |
443
451
  | | `camera` |
452
+ | | `cap` |
444
453
  | | `checkmark` |
445
454
  | | `checkmarkCircle` |
446
455
  | | `circle` |
447
456
  | | `cog` |
448
457
  | | `dashboard` |
449
458
  | | `desktop` |
459
+ | | `cube` |
450
460
  | | `website` |
451
461
  | | `eye` |
452
462
  | | `eyeCrossed` |
@@ -456,12 +466,14 @@ export function IconSizesExample() {
456
466
  | | `gift` |
457
467
  | | `grid` |
458
468
  | | `happyFace` |
469
+ | | `hardHat` |
459
470
  | | `headset` |
460
471
  | | `home` |
461
472
  | | `link` |
462
473
  | | `loadingCheck` |
463
474
  | | `lock` |
464
475
  | | `logout` |
476
+ | | `mcp` |
465
477
  | | `measurement` |
466
478
  | | `menu` |
467
479
  | | `microphone` |
@@ -471,12 +483,14 @@ export function IconSizesExample() {
471
483
  | | `number` |
472
484
  | | `numpad` |
473
485
  | | `offline` |
486
+ | | `outdoor` |
474
487
  | | `phone` |
475
488
  | | `phoneCall` |
476
489
  | | `phoneDisconnected` |
477
490
  | | `phoneEnd` |
478
491
  | | `phoneIncoming` |
479
492
  | | `phoneOutgoing` |
493
+ | | `pieChart` |
480
494
  | | `pinned` |
481
495
  | | `presentation` |
482
496
  | | `priceTag` |
@@ -489,6 +503,7 @@ export function IconSizesExample() {
489
503
  | | `soundwave` |
490
504
  | | `sparkles` |
491
505
  | | `speaker` |
506
+ | | `sprayBottle` |
492
507
  | | `sprout` |
493
508
  | | `starburst` |
494
509
  | | `sun` |
@@ -496,8 +511,27 @@ export function IconSizesExample() {
496
511
  | | `thumbsDown` |
497
512
  | | `thumbsUp` |
498
513
  | | `unPinned` |
514
+ | | `wrenchScrewdriver` |
499
515
  | | `xero` |
500
516
 
517
+ ### Weather
518
+
519
+ | Icon | `Name` |
520
+ | :--- | :------------------ |
521
+ | | `clearSunny` |
522
+ | | `foggy` |
523
+ | | `hailStorm` |
524
+ | | `heatWarning` |
525
+ | | `heavyRain` |
526
+ | | `lightRain` |
527
+ | | `mixedSnowShowers` |
528
+ | | `nightClear` |
529
+ | | `overcastCloudy` |
530
+ | | `partlySunny` |
531
+ | | `snow` |
532
+ | | `thunderLightening` |
533
+ | | `windy` |
534
+
501
535
  ### Legacy
502
536
 
503
537
  | Icon | `Name` |
@@ -339,17 +339,17 @@ the **Design** tab.
339
339
  | `Description` | Helper text below the field |
340
340
  | `Error` | Styled error message below the field |
341
341
 
342
- ## Composition
343
-
344
- The prop-driven component is sugar: it renders exactly the tree you would write
345
- by hand with `<InputNumber.Wrapper>` and the parts. To customize a single piece,
346
- compose the tree yourself and swap that one part — the other parts keep their
347
- defaults. The sugar does not merge consumer-provided parts into its render, so
348
- there is no per-slot precedence to reason about.
349
-
350
342
  `Wrapper` owns the field state and shares it with the parts through context, so
351
343
  every part must be rendered inside a `Wrapper` (a part used outside one throws).
352
344
 
345
+ ## Customize one part
346
+
347
+ Pass the content parts you want — `Label`, `Affix`, `Stepper`, `Loading`,
348
+ `Description`, `Error` — straight to `Wrapper`. It builds `Group`, `Input`, and
349
+ `Footer` around them and places each part at the right depth, so you never
350
+ reproduce the skeleton to change one piece. `Wrapper` renders only the parts you
351
+ gave it: leave one out and it is not there.
352
+
353
353
  The example below replaces the default stepper icons with `+` / `−` and leaves
354
354
  everything else as the default:
355
355
 
@@ -361,25 +361,139 @@ export function InputNumberCompositionExample() {
361
361
  const [value, setValue] = useState<number | null>(3);
362
362
 
363
363
  return (
364
- <InputNumber.Wrapper value={value} onValueCommitted={setValue}>
364
+ <InputNumber.Wrapper onValueCommitted={setValue} value={value}>
365
+ <InputNumber.Label>Quantity</InputNumber.Label>
366
+ <InputNumber.Stepper>
367
+ <InputNumber.Increment ariaLabel="Increase Quantity">
368
+ +
369
+ </InputNumber.Increment>
370
+ <InputNumber.Decrement ariaLabel="Decrease Quantity">
371
+ −
372
+ </InputNumber.Decrement>
373
+ </InputNumber.Stepper>
374
+ </InputNumber.Wrapper>
375
+ );
376
+ }
377
+ ```
378
+
379
+ ### Omit a part
380
+
381
+ A field with no stepper is the same composition minus the `Stepper`:
382
+
383
+ ```tsx
384
+ import React, { useState } from "react";
385
+ import { InputNumber } from "@jobber/components";
386
+
387
+ export function InputNumberCompositionStepperlessExample() {
388
+ const [value, setValue] = useState<number | null>(15);
389
+
390
+ return (
391
+ <InputNumber.Wrapper
392
+ format={{ style: "unit", unit: "percent" }}
393
+ onValueCommitted={setValue}
394
+ value={value}
395
+ >
396
+ <InputNumber.Label>Tax rate</InputNumber.Label>
397
+ </InputNumber.Wrapper>
398
+ );
399
+ }
400
+ ```
401
+
402
+ ### Show a part conditionally
403
+
404
+ Presence is ordinary conditional JSX; there is no `show*` prop to reach for:
405
+
406
+ ```tsx
407
+ import React, { useState } from "react";
408
+ import { InputNumber } from "@jobber/components";
409
+
410
+ export function InputNumberCompositionConditionalStepperExample() {
411
+ const [unitCost, setUnitCost] = useState<number | null>(0);
412
+ const [markup, setMarkup] = useState<number | null>(20);
413
+
414
+ return (
415
+ <>
416
+ <InputNumber
417
+ label="Unit cost"
418
+ onValueCommitted={setUnitCost}
419
+ prefix={{ label: "$" }}
420
+ value={unitCost}
421
+ />
422
+ <InputNumber.Wrapper onValueCommitted={setMarkup} value={markup}>
423
+ <InputNumber.Label>Markup</InputNumber.Label>
424
+ <InputNumber.Affix label="%" variation="suffix" />
425
+ {Boolean(unitCost) && <InputNumber.Stepper />}
426
+ </InputNumber.Wrapper>
427
+ </>
428
+ );
429
+ }
430
+ ```
431
+
432
+ ### Below-field content
433
+
434
+ `Description` and `Error` route into the footer row, which exists only while one
435
+ of them is rendered. The two are never shown together: when both are present the
436
+ error replaces the description, since a description hints at what the field
437
+ wants and the error already restates it.
438
+
439
+ ```tsx
440
+ import React, { useState } from "react";
441
+ import { InputNumber } from "@jobber/components";
442
+
443
+ export function InputNumberCompositionFooterExample() {
444
+ const [value, setValue] = useState<number | null>(50);
445
+ const error = (value ?? 0) > 99 ? "Enter a value between 1 and 99" : "";
446
+
447
+ return (
448
+ <InputNumber.Wrapper
449
+ invalid={Boolean(error)}
450
+ max={99}
451
+ min={1}
452
+ onValueChange={setValue}
453
+ value={value}
454
+ >
455
+ <InputNumber.Label>Quantity</InputNumber.Label>
456
+ <InputNumber.Description>Per visit</InputNumber.Description>
457
+ {error && <InputNumber.Error>{error}</InputNumber.Error>}
458
+ </InputNumber.Wrapper>
459
+ );
460
+ }
461
+ ```
462
+
463
+ ## Custom layouts
464
+
465
+ For an arrangement the standard skeleton does not produce, compose the
466
+ structural parts (`Group`, `Input`, `Footer`) yourself. `Wrapper` then renders
467
+ your tree exactly as written and adds nothing:
468
+
469
+ ```tsx
470
+ import React, { useState } from "react";
471
+ import { InputNumber } from "@jobber/components";
472
+
473
+ export function InputNumberCompositionCustomLayoutExample() {
474
+ const [value, setValue] = useState<number | null>(3);
475
+
476
+ return (
477
+ <InputNumber.Wrapper inline onValueCommitted={setValue} value={value}>
365
478
  <InputNumber.Group>
479
+ <InputNumber.Decrement ariaLabel="Decrease quantity" />
366
480
  <InputNumber.Input>
367
481
  <InputNumber.Label>Quantity</InputNumber.Label>
368
- <InputNumber.Stepper>
369
- <InputNumber.Increment ariaLabel="Increase Quantity">
370
- +
371
- </InputNumber.Increment>
372
- <InputNumber.Decrement ariaLabel="Decrease Quantity">
373
- −
374
- </InputNumber.Decrement>
375
- </InputNumber.Stepper>
376
482
  </InputNumber.Input>
483
+ <InputNumber.Increment ariaLabel="Increase quantity" />
377
484
  </InputNumber.Group>
378
485
  </InputNumber.Wrapper>
379
486
  );
380
487
  }
381
488
  ```
382
489
 
490
+ Pick one path per `Wrapper`. Mixing structural parts with loose content parts
491
+ throws, rather than guessing where the loose parts belong.
492
+
493
+ The sugar does not merge consumer-provided parts into its render, so there is no
494
+ per-slot precedence to reason about — children passed to `<InputNumber>` are
495
+ ignored. Compose on `Wrapper` instead.
496
+
383
497
  ## Controlled usage
384
498
 
385
499
  The field is controlled: pass `value` (a `number`, or `null` for empty) and read
@@ -86,6 +86,10 @@ and align the input with
86
86
  With clearer guidance around the purpose of inputs, the user is able to better
87
87
  focus on the task at hand.
88
88
 
89
+ Either affix can be given an `onClick` to make it an action, which renders the
90
+ affix icon as a button. An actioned affix requires both an `icon` and an
91
+ `ariaLabel` to name the button — affix labels are not actionable.
92
+
89
93
  ```tsx
90
94
  import React, { useState } from "react";
91
95
  import type { InputTextProps } from "@jobber/components/InputText";
@@ -128,7 +132,11 @@ export function InputTextPrefixSuffixExample(
128
132
  />
129
133
  <InputText
130
134
  placeholder="Search"
131
- prefix={{ icon: "search" }}
135
+ prefix={{
136
+ icon: "search",
137
+ ariaLabel: "submit search",
138
+ onClick: () => alert("This could submit a search"),
139
+ }}
132
140
  value={search}
133
141
  onChange={handleSearchChange}
134
142
  suffix={{
@@ -48,7 +48,7 @@ form elements.
48
48
  ### Large
49
49
 
50
50
  Use `large` spacing for higher-level "parent" containers.
51
- [Modal](/components/Modal) uses `large` spacing in its header and around the
51
+ [Dialog](/components/Dialog) uses `large` spacing in its header and around the
52
52
  edges to give itself a bit more prominence as a standalone view, in a similar
53
53
  manner to [Page](/components/Page).
54
54
 
@@ -43,6 +43,35 @@ More complex full-page scaffolding can be found on the [Scaffolding](../scaffold
43
43
  </Stack>
44
44
  ```
45
45
 
46
+ ```tsx
47
+ <Stack>
48
+ <Heading level={2}>Stack Dialog Layouts</Heading>
49
+ <Text>Stacks are a great way to layout content in a dialog format. They are responsive and easy to read.</Text>
50
+ <Dialog>
51
+ <Dialog.Trigger>
52
+ <Button label="Open Dialog" />
53
+ </Dialog.Trigger>
54
+ <Dialog.Content>
55
+ <Dialog.Header>
56
+ <Dialog.Title>Dialog Layout</Dialog.Title>
57
+ <Dialog.Close />
58
+ </Dialog.Header>
59
+ <Dialog.Body>
60
+ <Stack>
61
+ <Text>A dialog with a header, body text, and footer</Text>
62
+ <Text>And they're all stacked vertically with equal spacing</Text>
63
+ </Stack>
64
+ </Dialog.Body>
65
+ <Dialog.Footer>
66
+ <Dialog.Actions
67
+ primary={<Dialog.Close render={<Button label="Close the Dialog" />} />}
68
+ />
69
+ </Dialog.Footer>
70
+ </Dialog.Content>
71
+ </Dialog>
72
+ </Stack>
73
+ ```
74
+
46
75
  ```tsx
47
76
  <Stack>
48
77
  <Heading level={1}>Cluster</Heading>
@@ -26,7 +26,7 @@ Most settings can be managed with the
26
26
  [FeatureSwitch](../components/FeatureSwitch) component, which allows:
27
27
 
28
28
  * turning functionality on and off
29
- * editing aspects of the feature via a [Modal](../components/Modal)
29
+ * editing aspects of the feature via a [Dialog](../components/Dialog)
30
30
  * if the page supports it, live updates to the setting
31
31
 
32
32
  ### Grouping settings
@@ -37,7 +37,7 @@ Use a [Card](../Card/Card.md) to group related settings together.
37
37
 
38
38
  The [FeatureSwitch](../components/FeatureSwitch) component is a good starting
39
39
  point for most settings. If the setting is more complex, you can trigger a
40
- [Modal](../components/Modal) to edit the setting.
40
+ [Dialog](../components/Dialog) to edit the setting.
41
41
 
42
42
  For cases where you don't need the full functionality of the `FeatureSwitch`,
43
43
  you can either opt out of props like `onSwitch` or `onEdit`, or compose a layout
@@ -49,7 +49,7 @@ of the pieces you do need.
49
49
 
50
50
  * [FeatureSwitch](../components/FeatureSwitch)
51
51
  * [Card](../Card/Card.md)
52
- * [Modal](../components/Modal)
52
+ * [Dialog](../components/Dialog)
53
53
  * [Switch](../Switch/Switch.md)
54
54
 
55
55
  ## Principles