@utilitywarehouse/hearth-react 0.28.7 → 0.29.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.
- package/README.md +34 -20
- package/SKILL.md +355 -0
- package/dist/{chunk-TLCA3FQZ.js → chunk-ABES5BZY.js} +2 -2
- package/dist/{chunk-OHPQ5IRM.cjs → chunk-Y2CHQFKQ.cjs} +2 -2
- package/dist/{chunk-OHPQ5IRM.cjs.map → chunk-Y2CHQFKQ.cjs.map} +1 -1
- package/dist/components/CardAccordion/CardAccordion.context.d.ts.map +1 -1
- package/dist/components/ExpandableCard/ExpandableCard.cjs +1 -1
- package/dist/components/ExpandableCard/ExpandableCard.js +1 -1
- package/dist/components/ProgressBar/ProgressBar.cjs +1 -1
- package/dist/components/ProgressBar/ProgressBar.js +1 -1
- package/dist/helpers/get-classname-styles.d.ts.map +1 -1
- package/dist/helpers/logger.d.ts.map +1 -1
- package/dist/helpers/merge-ids.d.ts.map +1 -1
- package/dist/hooks/use-ids.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +14 -10
- package/public/llms/components/accordion.md +321 -0
- package/public/llms/components/alert.md +217 -0
- package/public/llms/components/avatar.md +112 -0
- package/public/llms/components/badge.md +158 -0
- package/public/llms/components/body-text.md +200 -0
- package/public/llms/components/box.md +148 -0
- package/public/llms/components/breadcrumbs.md +97 -0
- package/public/llms/components/button.md +595 -0
- package/public/llms/components/card-accordion.md +277 -0
- package/public/llms/components/card.md +985 -0
- package/public/llms/components/checkbox-group.md +193 -0
- package/public/llms/components/checkbox-tile.md +116 -0
- package/public/llms/components/checkbox.md +108 -0
- package/public/llms/components/combobox.md +360 -0
- package/public/llms/components/container.md +162 -0
- package/public/llms/components/currency-input.md +85 -0
- package/public/llms/components/date-input.md +90 -0
- package/public/llms/components/date-picker.md +159 -0
- package/public/llms/components/description-list.md +149 -0
- package/public/llms/components/detail-text.md +89 -0
- package/public/llms/components/divider.md +88 -0
- package/public/llms/components/em.md +43 -0
- package/public/llms/components/expandable-card.md +231 -0
- package/public/llms/components/flex.md +197 -0
- package/public/llms/components/grid.md +244 -0
- package/public/llms/components/heading.md +65 -0
- package/public/llms/components/helper-text.md +27 -0
- package/public/llms/components/highlight-banner.md +94 -0
- package/public/llms/components/icon-button.md +516 -0
- package/public/llms/components/icon-container.md +247 -0
- package/public/llms/components/inline-link.md +190 -0
- package/public/llms/components/label.md +28 -0
- package/public/llms/components/link.md +236 -0
- package/public/llms/components/list.md +715 -0
- package/public/llms/components/menu.md +270 -0
- package/public/llms/components/modal.md +328 -0
- package/public/llms/components/pagination.md +138 -0
- package/public/llms/components/password-input.md +93 -0
- package/public/llms/components/progress-bar.md +139 -0
- package/public/llms/components/progress-stepper.md +147 -0
- package/public/llms/components/radio-group.md +487 -0
- package/public/llms/components/search-input.md +132 -0
- package/public/llms/components/section-header.md +82 -0
- package/public/llms/components/select.md +148 -0
- package/public/llms/components/skeleton.md +282 -0
- package/public/llms/components/spinner.md +59 -0
- package/public/llms/components/strong.md +49 -0
- package/public/llms/components/switch.md +106 -0
- package/public/llms/components/table.md +230 -0
- package/public/llms/components/tabs.md +320 -0
- package/public/llms/components/text-area.md +141 -0
- package/public/llms/components/text-input.md +228 -0
- package/public/llms/components/toast.md +323 -0
- package/public/llms/components/toggle-button-card.md +513 -0
- package/public/llms/components/tooltip.md +188 -0
- package/public/llms/components/unstyled-icon-button.md +175 -0
- package/public/llms/components/validation-text.md +29 -0
- package/public/llms/components/verification-input.md +96 -0
- package/public/llms/docs/changelog.md +1430 -0
- package/public/llms/docs/common-props/align-self.md +90 -0
- package/public/llms/docs/common-props/border.md +308 -0
- package/public/llms/docs/common-props/colour.md +221 -0
- package/public/llms/docs/common-props/flex-items.md +91 -0
- package/public/llms/docs/common-props/gap.md +111 -0
- package/public/llms/docs/common-props/grid-items.md +96 -0
- package/public/llms/docs/common-props/margin.md +105 -0
- package/public/llms/docs/common-props/opacity.md +100 -0
- package/public/llms/docs/common-props/order.md +90 -0
- package/public/llms/docs/common-props/overflow.md +89 -0
- package/public/llms/docs/common-props/padding.md +102 -0
- package/public/llms/docs/common-props/position.md +92 -0
- package/public/llms/docs/common-props/size.md +93 -0
- package/public/llms/docs/common-props/spacing.md +97 -0
- package/public/llms/docs/common-props/text.md +35 -0
- package/public/llms/docs/common-props/z-index.md +88 -0
- package/public/llms/docs/design-tokens.md +72 -0
- package/public/llms/docs/getting-started.md +117 -0
- package/public/llms/docs/layout.md +135 -0
- package/public/llms/docs/migrating.md +302 -0
- package/public/llms/docs/responsive-design/breakpoints.md +119 -0
- package/public/llms/docs/responsive-design/media-queries.md +89 -0
- package/public/llms/docs/responsive-design/responsive-props.md +37 -0
- package/public/llms.txt +97 -0
- package/scripts/init-ai.js +142 -0
- package/styles.css +1 -1
- /package/dist/{chunk-TLCA3FQZ.js.map → chunk-ABES5BZY.js.map} +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Menu
|
|
2
|
+
|
|
3
|
+
Use the `Menu` component to present a short list of actions or options in response to a user’s interaction. Menus are ideal for actions like sorting, filtering, or providing additional options without navigating away from the current screen.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
<Menu {...args}>
|
|
7
|
+
<MenuTrigger>
|
|
8
|
+
<Button variant="outline" colorScheme="functional">
|
|
9
|
+
Menu trigger
|
|
10
|
+
<ExpandSmallIcon />
|
|
11
|
+
</Button>
|
|
12
|
+
</MenuTrigger>
|
|
13
|
+
<MenuContent>
|
|
14
|
+
<MenuItem>Item</MenuItem>
|
|
15
|
+
<MenuItem>Item</MenuItem>
|
|
16
|
+
<MenuItem asChild>
|
|
17
|
+
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
|
18
|
+
<a href="#">
|
|
19
|
+
Navigation Item
|
|
20
|
+
<OpenSmallIcon />
|
|
21
|
+
</a>
|
|
22
|
+
</MenuItem>
|
|
23
|
+
<MenuItem colorScheme="functional">Item</MenuItem>
|
|
24
|
+
<MenuItem colorScheme="destructive">
|
|
25
|
+
Destructive item
|
|
26
|
+
<TrashSmallIcon />
|
|
27
|
+
</MenuItem>
|
|
28
|
+
<MenuItem disabled>Disabled item</MenuItem>
|
|
29
|
+
</MenuContent>
|
|
30
|
+
</Menu>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
The root `Menu` component will wrap the `MenuTrigger` & `MenuContent` components.
|
|
36
|
+
|
|
37
|
+
The `MenuTrigger` must wrap a `Button` or `IconButton` component, and the
|
|
38
|
+
`MenuContent` component will wrap any number of `MenuItem` components.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<Menu>
|
|
42
|
+
<MenuTrigger>
|
|
43
|
+
<Button variant="outline" colorScheme="functional">
|
|
44
|
+
Menu trigger
|
|
45
|
+
<ExpandSmallIcon />
|
|
46
|
+
</Button>
|
|
47
|
+
</MenuTrigger>
|
|
48
|
+
<MenuContent>
|
|
49
|
+
<MenuItem>Menu item</MenuItem>
|
|
50
|
+
<MenuItem>Menu item</MenuItem>
|
|
51
|
+
<MenuItem>Menu item</MenuItem>
|
|
52
|
+
<MenuItem>Menu item</MenuItem>
|
|
53
|
+
</MenuContent>
|
|
54
|
+
</Menu>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Modal vs non-modal behaviour
|
|
58
|
+
|
|
59
|
+
If you are using multiple `Menu` components together, for instance in a
|
|
60
|
+
navigation bar, you can set `modal` to `false` so that when a user has an open
|
|
61
|
+
`Menu` and they click on a different `MenuTrigger` the second `Menu` opens
|
|
62
|
+
immediately, closing the first `Menu` without the need for the user to click
|
|
63
|
+
outside of the first `Menu` to close it.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<Menu modal={false}>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Menu trigger
|
|
70
|
+
|
|
71
|
+
You must render either a `Button` or `IconButton` component as a child of the `MenuTrigger`.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<Menu {...args}>
|
|
75
|
+
<MenuTrigger>
|
|
76
|
+
<IconButton variant="outline" colorScheme="functional" label="add">
|
|
77
|
+
<AddMediumIcon />
|
|
78
|
+
</IconButton>
|
|
79
|
+
</MenuTrigger>
|
|
80
|
+
<MenuContent>
|
|
81
|
+
<MenuItem>Item</MenuItem>
|
|
82
|
+
<MenuItem>Item</MenuItem>
|
|
83
|
+
<MenuItem>Item</MenuItem>
|
|
84
|
+
<MenuItem>Item</MenuItem>
|
|
85
|
+
</MenuContent>
|
|
86
|
+
</Menu>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Menu content placement
|
|
90
|
+
|
|
91
|
+
You can adjust the vertical and horizontal placement of the `MenuContent`.
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<Flex height="400px" width="800px" alignItems="center" justifyContent="center" gap="200">
|
|
95
|
+
<Menu {...args}>
|
|
96
|
+
<MenuTrigger>
|
|
97
|
+
<Button variant="outline" colorScheme="functional">
|
|
98
|
+
Bottom left
|
|
99
|
+
</Button>
|
|
100
|
+
</MenuTrigger>
|
|
101
|
+
<MenuContent placement="bottomLeft">
|
|
102
|
+
<MenuItem>Item</MenuItem>
|
|
103
|
+
<MenuItem>Item</MenuItem>
|
|
104
|
+
</MenuContent>
|
|
105
|
+
</Menu>
|
|
106
|
+
<Menu {...args}>
|
|
107
|
+
<MenuTrigger>
|
|
108
|
+
<Button variant="outline" colorScheme="functional">
|
|
109
|
+
Bottom right
|
|
110
|
+
</Button>
|
|
111
|
+
</MenuTrigger>
|
|
112
|
+
<MenuContent placement="bottomRight">
|
|
113
|
+
<MenuItem>Item</MenuItem>
|
|
114
|
+
<MenuItem>Item</MenuItem>
|
|
115
|
+
</MenuContent>
|
|
116
|
+
</Menu>
|
|
117
|
+
<Menu {...args}>
|
|
118
|
+
<MenuTrigger>
|
|
119
|
+
<Button variant="outline" colorScheme="functional">
|
|
120
|
+
Top left
|
|
121
|
+
</Button>
|
|
122
|
+
</MenuTrigger>
|
|
123
|
+
<MenuContent placement="topLeft">
|
|
124
|
+
<MenuItem>Item</MenuItem>
|
|
125
|
+
<MenuItem>Item</MenuItem>
|
|
126
|
+
</MenuContent>
|
|
127
|
+
</Menu>
|
|
128
|
+
<Menu {...args}>
|
|
129
|
+
<MenuTrigger>
|
|
130
|
+
<Button variant="outline" colorScheme="functional">
|
|
131
|
+
Top right
|
|
132
|
+
</Button>
|
|
133
|
+
</MenuTrigger>
|
|
134
|
+
<MenuContent placement="topRight">
|
|
135
|
+
<MenuItem>Item</MenuItem>
|
|
136
|
+
<MenuItem>Item</MenuItem>
|
|
137
|
+
</MenuContent>
|
|
138
|
+
</Menu>
|
|
139
|
+
</Flex>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Menu item
|
|
143
|
+
|
|
144
|
+
The `MenuItem` can be either `functional` or `destructive`, and can also contain icons.
|
|
145
|
+
|
|
146
|
+
If a `MenuItem` is navigating to another page, you can use the `asChild` prop to
|
|
147
|
+
render a semantic `<a>` element.
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
<MenuContent>
|
|
151
|
+
<MenuItem>Item</MenuItem>
|
|
152
|
+
<MenuItem colorScheme="functional">Item</MenuItem>
|
|
153
|
+
<MenuItem colorScheme="destructive">
|
|
154
|
+
Destructive item
|
|
155
|
+
<TrashSmallIcon />
|
|
156
|
+
</MenuItem>
|
|
157
|
+
{/* render a link element if navigating to somewhere else */}
|
|
158
|
+
<MenuItem asChild>
|
|
159
|
+
<a href="/another-page">
|
|
160
|
+
Navigation Item
|
|
161
|
+
<OpenSmallIcon />
|
|
162
|
+
</a>
|
|
163
|
+
</MenuItem>
|
|
164
|
+
<MenuItem disabled>Disabled item</MenuItem>
|
|
165
|
+
</MenuContent>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Accessibility
|
|
169
|
+
|
|
170
|
+
Adheres to the [Menu Button WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) and uses
|
|
171
|
+
[roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex)
|
|
172
|
+
to manage focus movement among menu items.
|
|
173
|
+
|
|
174
|
+
### Keyboard interactions
|
|
175
|
+
|
|
176
|
+
<Flex direction="column" gap="200" className="sb-unstyled">
|
|
177
|
+
<Flex>
|
|
178
|
+
<Box width="300px">
|
|
179
|
+
<BodyText as="span" weight="bold">
|
|
180
|
+
Key
|
|
181
|
+
</BodyText>
|
|
182
|
+
</Box>
|
|
183
|
+
<BodyText as="span" weight="bold">
|
|
184
|
+
Description
|
|
185
|
+
</BodyText>
|
|
186
|
+
</Flex>
|
|
187
|
+
<Divider />
|
|
188
|
+
{[
|
|
189
|
+
{
|
|
190
|
+
key: 'Space',
|
|
191
|
+
description:
|
|
192
|
+
'When focus is on MenuTrigger, opens the menu and focuses the selected item. When focus is on an item, selects the focused item.',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
key: 'Enter',
|
|
196
|
+
description:
|
|
197
|
+
'When focus is on MenuTrigger, opens the menu and focuses the first item. When focus is on an item, selects the focused item.',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
key: 'ArrowDown',
|
|
201
|
+
description:
|
|
202
|
+
'When focus is on MenuTrigger, opens the menu. When focus is on an item, moves focus to the next item.',
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
key: 'ArrowUp',
|
|
206
|
+
description:
|
|
207
|
+
'When focus is on MenuTrigger, opens the menu. When focus is on an item, moves focus to the previous item.',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
key: 'Esc',
|
|
211
|
+
description: 'Closes the menu and moves focus to MenuTrigger.',
|
|
212
|
+
},
|
|
213
|
+
].map((kbi, i) => (
|
|
214
|
+
<>
|
|
215
|
+
<Flex>
|
|
216
|
+
<Box width="300px">
|
|
217
|
+
<kbd>{kbi.key}</kbd>
|
|
218
|
+
</Box>
|
|
219
|
+
<BodyText as="span">{kbi.description}</BodyText>
|
|
220
|
+
</Flex>
|
|
221
|
+
{i < 4 ? <Divider /> : null}
|
|
222
|
+
</>
|
|
223
|
+
))}
|
|
224
|
+
</Flex>
|
|
225
|
+
|
|
226
|
+
## SEO
|
|
227
|
+
|
|
228
|
+
By default, `MenuContent` is removed from the DOM when the menu is closed, which
|
|
229
|
+
means search engines may not index its content. If the menu items are important
|
|
230
|
+
for SEO — for example, primary navigation links — use the `forceMount` prop on
|
|
231
|
+
`MenuContent` to keep them in the DOM at all times.
|
|
232
|
+
|
|
233
|
+
When `forceMount` is set, the closed menu is hidden from view and the
|
|
234
|
+
accessibility tree using the `hidden` attribute, so it has no impact on the
|
|
235
|
+
visual layout or screen reader experience.
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
<MenuContent forceMount>
|
|
239
|
+
<MenuItem>Item</MenuItem>
|
|
240
|
+
</MenuContent>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Menu API
|
|
244
|
+
|
|
245
|
+
| Prop | Type | Default | Description |
|
|
246
|
+
| -------------- | --------------------------- | ------- | ----------- |
|
|
247
|
+
| `open` | `boolean` | — | |
|
|
248
|
+
| `defaultOpen` | `boolean` | — | |
|
|
249
|
+
| `onOpenChange` | `((open: boolean) => void)` | — | |
|
|
250
|
+
| `modal` | `boolean` | — | |
|
|
251
|
+
|
|
252
|
+
## MenuContent API
|
|
253
|
+
|
|
254
|
+
| Prop | Type | Default | Description |
|
|
255
|
+
| ------------------------ | ---------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------- |
|
|
256
|
+
| `forceMount` | `true` | — | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |
|
|
257
|
+
| `updatePositionStrategy` | `"optimized" \| "always"` | — | |
|
|
258
|
+
| `placement` | `"bottomLeft" \| "bottomRight" \| "topLeft" \| "topRight"` | `bottomLeft` | |
|
|
259
|
+
|
|
260
|
+
### MenuItem API
|
|
261
|
+
|
|
262
|
+
This component is based on the `div` element.
|
|
263
|
+
|
|
264
|
+
| Prop | Type | Default | Description |
|
|
265
|
+
| ------------- | ------------------------------- | ------------ | ----------- |
|
|
266
|
+
| `onSelect` | `((event: Event) => void)` | — | |
|
|
267
|
+
| `asChild` | `boolean` | — | |
|
|
268
|
+
| `disabled` | `boolean` | — | |
|
|
269
|
+
| `textValue` | `string` | — | |
|
|
270
|
+
| `colorScheme` | `"functional" \| "destructive"` | `functional` | |
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# Modal
|
|
2
|
+
|
|
3
|
+
A `Modal` overlays content to request a decision or inform users of important information. When
|
|
4
|
+
users need to interact with the application without navigating to a new page or disrupting their
|
|
5
|
+
workflow, a `Modal` creates a floating layer over the current page to gather feedback or display
|
|
6
|
+
information.
|
|
7
|
+
|
|
8
|
+
- [Usage Guidelines](#usage-guidelines)
|
|
9
|
+
- [Content](#content)
|
|
10
|
+
- [Accessibility](#accessibility)
|
|
11
|
+
- [Uncontrolled usage](#uncontrolled-usage)
|
|
12
|
+
- [Controlled usage](#controlled-usage)
|
|
13
|
+
- [With Image](#with-image)
|
|
14
|
+
- [Loading](#loading)
|
|
15
|
+
- [Preventing dismissal](#preventing-dismissal)
|
|
16
|
+
- [API](#api)
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<ModalRoot>
|
|
20
|
+
<ModalTrigger>
|
|
21
|
+
<Button>Open modal</Button>
|
|
22
|
+
</ModalTrigger>
|
|
23
|
+
<Modal {...args}>
|
|
24
|
+
<ModalFooter>
|
|
25
|
+
<ModalClose>
|
|
26
|
+
<Button variant="ghost" colorScheme="functional">
|
|
27
|
+
Cancel
|
|
28
|
+
</Button>
|
|
29
|
+
</ModalClose>
|
|
30
|
+
<ModalClose>
|
|
31
|
+
<Button variant="solid" colorScheme="highlight">
|
|
32
|
+
Primary
|
|
33
|
+
</Button>
|
|
34
|
+
</ModalClose>
|
|
35
|
+
</ModalFooter>
|
|
36
|
+
</Modal>
|
|
37
|
+
</ModalRoot>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage Guidelines
|
|
41
|
+
|
|
42
|
+
`Modal` comprises a number of components you can compose together, and can be
|
|
43
|
+
used as either a controlled or uncontrolled component.
|
|
44
|
+
|
|
45
|
+
- `ModalRoot` must wrap all parts of the modal, providing context to its children.
|
|
46
|
+
- `ModalTrigger` wraps a `Button` component and is used to open the modal.
|
|
47
|
+
- `Modal` is the main component where you should place your content.
|
|
48
|
+
- `ModalContent` is a container for any long content within the modal, ensuring it is scrollable.
|
|
49
|
+
- `ModalFooter` is a container for any actions, typically buttons.
|
|
50
|
+
- `ModalClose` wraps a `Button` component and is used to close the modal.
|
|
51
|
+
|
|
52
|
+
`Modal` uses portals to render a popup. Please make sure you have portals
|
|
53
|
+
setup properly in the root of your application. See the **Getting Started**
|
|
54
|
+
guide for more details.
|
|
55
|
+
|
|
56
|
+
## Content
|
|
57
|
+
|
|
58
|
+
The `Modal` component accepts a `heading` and `description` prop for the main
|
|
59
|
+
content. It is recommended that you use these props to ensure proper spacing
|
|
60
|
+
and alignment of content within the modal. However you can also pass custom
|
|
61
|
+
content as children to the `Modal` component, alongside a `ModalFooter`, in
|
|
62
|
+
which case you may need to handle the layout and spacing of the content
|
|
63
|
+
yourself.
|
|
64
|
+
|
|
65
|
+
If you have a lot of content within the modal, it is recommended to wrap the
|
|
66
|
+
content in a `ModalContent` component. This will ensure that the content is
|
|
67
|
+
scrollable and fits within the viewport.
|
|
68
|
+
|
|
69
|
+
If you have long content that needs to scroll on mobile, you need to set the
|
|
70
|
+
`fullScreen` prop to `true`, which will make the modal take up the full screen
|
|
71
|
+
on mobile, and ensure the content scrolls correctly.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<ModalRoot defaultOpen={viewMode === 'docs' ? undefined : true}>
|
|
75
|
+
<ModalTrigger>
|
|
76
|
+
<Button>Open modal</Button>
|
|
77
|
+
</ModalTrigger>
|
|
78
|
+
<Modal {...args}>
|
|
79
|
+
<ModalContent>
|
|
80
|
+
<BodyText paragraphSpacing size="md">
|
|
81
|
+
<Strong>Cheapest variable energy:</Strong> when you take energy and two other eligible
|
|
82
|
+
bundle services vs standard variable tariffs offered by major suppliers (large and medium
|
|
83
|
+
suppliers as defined by Ofgem), for sale nationally, excl. existing customer tariffs. Based
|
|
84
|
+
on Ofgem's typical domestic usage. Payment by Direct Debit. Correct as of 13/10/2025.
|
|
85
|
+
Contact us to verify. <InlineLink href="#">See UW terms and conditions.</InlineLink>
|
|
86
|
+
</BodyText>
|
|
87
|
+
<BodyText paragraphSpacing size="md">
|
|
88
|
+
<Strong>UW Price Pledge:</Strong> Eligible new customers (from 22/04/24) who take 3 or more
|
|
89
|
+
qualifying services and regularly use their Cashback Card. If a customer doesn't save
|
|
90
|
+
with UW (incl. Cashback) in their first year vs their previous provider (or cheapest major
|
|
91
|
+
provider where applicable), they can apply to claim the UW Price Pledge between 12 - 15
|
|
92
|
+
months after their sign-up date; the pledge & no exit fees only applies to qualifying
|
|
93
|
+
services taken at sign-up. When you claim, we'll assume an average Cashback Card saving
|
|
94
|
+
of £160 a year (based on customer usage data from 03.04.23 to 31.03.24, for users who earned
|
|
95
|
+
Cashback at least at least once a week, excluding promotional activities) or your actual
|
|
96
|
+
Cashback saving if higher. Full details, eligibility and terms available{' '}
|
|
97
|
+
<InlineLink href="#">here.</InlineLink>
|
|
98
|
+
</BodyText>
|
|
99
|
+
<BodyText paragraphSpacing size="md">
|
|
100
|
+
<Strong>£400 to help you switch:</Strong> When you take a 3 or 4+ Service Bundle, we'll
|
|
101
|
+
give you credit up to £400 towards any termination fees (excluding Home Insurance) you have
|
|
102
|
+
to pay your current providers. You'll need to return any equipment and pay for services
|
|
103
|
+
you used before you cancel. Additional requirements apply to customers who are tenants.
|
|
104
|
+
Further terms apply see our Residential Products and Services{' '}
|
|
105
|
+
<InlineLink href="#">terms and conditions.</InlineLink>
|
|
106
|
+
</BodyText>
|
|
107
|
+
</ModalContent>
|
|
108
|
+
<ModalFooter>
|
|
109
|
+
<ModalClose>
|
|
110
|
+
<Button variant="ghost" colorScheme="functional">
|
|
111
|
+
Cancel
|
|
112
|
+
</Button>
|
|
113
|
+
</ModalClose>
|
|
114
|
+
<ModalClose>
|
|
115
|
+
<Button variant="solid" colorScheme="highlight">
|
|
116
|
+
Primary
|
|
117
|
+
</Button>
|
|
118
|
+
</ModalClose>
|
|
119
|
+
</ModalFooter>
|
|
120
|
+
</Modal>
|
|
121
|
+
</ModalRoot>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Accessibility
|
|
125
|
+
|
|
126
|
+
Follows the [WAI-ARIA Dialog (Modal)](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) Pattern.
|
|
127
|
+
|
|
128
|
+
Due to the size of the Modal, all entrance and exit animations adhere to the users [reduce motion](https://www.w3.org/WAI/WCAG21/Techniques/css/C39) preference.
|
|
129
|
+
|
|
130
|
+
It is strongly recommended the modal includes a visible element which closes
|
|
131
|
+
the modal. By default the `Modal` has a close icon button. You can hide this
|
|
132
|
+
button using the `hideCloseButton` prop, so that there will always be a button
|
|
133
|
+
that closes the modal available to keyboard users. However we do recommend
|
|
134
|
+
that you only do this when providing a visible alternative, such as a `Cancel`
|
|
135
|
+
button.
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
<ModalRoot>
|
|
139
|
+
<ModalTrigger>
|
|
140
|
+
<Button>Open modal</Button>
|
|
141
|
+
</ModalTrigger>
|
|
142
|
+
<Modal {...args}>
|
|
143
|
+
<ModalFooter>
|
|
144
|
+
<ModalClose>
|
|
145
|
+
<Button variant="ghost" colorScheme="functional">
|
|
146
|
+
Cancel
|
|
147
|
+
</Button>
|
|
148
|
+
</ModalClose>
|
|
149
|
+
<ModalClose>
|
|
150
|
+
<Button variant="solid" colorScheme="highlight">
|
|
151
|
+
Primary
|
|
152
|
+
</Button>
|
|
153
|
+
</ModalClose>
|
|
154
|
+
</ModalFooter>
|
|
155
|
+
</Modal>
|
|
156
|
+
</ModalRoot>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Uncontrolled usage
|
|
160
|
+
|
|
161
|
+
This is the recommended approach. You can use the `open` & `onOpenChange` props
|
|
162
|
+
on the `ModalRoot` component with your event handlers.
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
<ModalRoot>
|
|
166
|
+
<ModalTrigger>
|
|
167
|
+
<Button>Open modal</Button>
|
|
168
|
+
</ModalTrigger>
|
|
169
|
+
<Modal
|
|
170
|
+
heading="Hearth modal"
|
|
171
|
+
description="Overlays content to request a decision or inform users of important information"
|
|
172
|
+
>
|
|
173
|
+
<ModalFooter>
|
|
174
|
+
<ModalClose>
|
|
175
|
+
<Button variant="ghost" colorScheme="grey">
|
|
176
|
+
Cancel
|
|
177
|
+
</Button>
|
|
178
|
+
</ModalClose>
|
|
179
|
+
<ModalClose>
|
|
180
|
+
<Button variant="solid" colorScheme="yellow">
|
|
181
|
+
Primary
|
|
182
|
+
</Button>
|
|
183
|
+
</ModalClose>
|
|
184
|
+
</ModalFooter>
|
|
185
|
+
</Modal>
|
|
186
|
+
</ModalRoot>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Controlled usage
|
|
190
|
+
|
|
191
|
+
If you need more control over elements of the modal, then you can combine
|
|
192
|
+
state with the `open` and `onOpenChange` props, and control the opening and
|
|
193
|
+
closing of the modal outside the `ModalRoot` context.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
<div>
|
|
197
|
+
<Button onClick={() => setOpen(true)}>Open modal</Button>
|
|
198
|
+
<ModalRoot open={open} onOpenChange={(o: boolean) => setOpen(o)}>
|
|
199
|
+
<Modal heading="Controlled modal">
|
|
200
|
+
<ModalFooter>
|
|
201
|
+
<Button variant="ghost" colorScheme="functional" onClick={() => setOpen(false)}>
|
|
202
|
+
Cancel
|
|
203
|
+
</Button>
|
|
204
|
+
<Button variant="solid" colorScheme="highlight" onClick={() => setOpen(false)}>
|
|
205
|
+
Primary
|
|
206
|
+
</Button>
|
|
207
|
+
</ModalFooter>
|
|
208
|
+
</Modal>
|
|
209
|
+
</ModalRoot>
|
|
210
|
+
</div>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## With Image
|
|
214
|
+
|
|
215
|
+
You can pass an image, such as an SVG illustration, to the `image` prop. The
|
|
216
|
+
`Modal` layout will automatically adjust to accommodate the image, taking up
|
|
217
|
+
more of the viewport height, and centre aligning the text and actions.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
<ModalRoot defaultOpen={viewMode === 'docs' ? undefined : true}>
|
|
221
|
+
<ModalTrigger>
|
|
222
|
+
<Button>Open modal</Button>
|
|
223
|
+
</ModalTrigger>
|
|
224
|
+
<Modal {...args} image={<img src={SpotSavings} alt="Savings Pig" />}>
|
|
225
|
+
<ModalFooter>
|
|
226
|
+
<ModalClose>
|
|
227
|
+
<Button variant="outline" colorScheme="functional">
|
|
228
|
+
Cancel
|
|
229
|
+
</Button>
|
|
230
|
+
</ModalClose>
|
|
231
|
+
<ModalClose>
|
|
232
|
+
<Button variant="solid" colorScheme="highlight">
|
|
233
|
+
Primary
|
|
234
|
+
</Button>
|
|
235
|
+
</ModalClose>
|
|
236
|
+
</ModalFooter>
|
|
237
|
+
</Modal>
|
|
238
|
+
</ModalRoot>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Loading
|
|
242
|
+
|
|
243
|
+
Use the `loading` and `loadingText` props to indicate a loading state within the modal.
|
|
244
|
+
|
|
245
|
+
When `loading` is `true` the `loadingText` is required for accessibility purposes.
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
<ModalRoot defaultOpen={viewMode === 'docs' ? undefined : true}>
|
|
249
|
+
<ModalTrigger>
|
|
250
|
+
<Button>Open modal</Button>
|
|
251
|
+
</ModalTrigger>
|
|
252
|
+
<Modal {...args} />
|
|
253
|
+
</ModalRoot>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Preventing dismissal
|
|
257
|
+
|
|
258
|
+
If you need users to follow the modal flow without being able to dismiss the
|
|
259
|
+
modal, you can use the `onPointerDownOutside`, `onEscapeKeyDown` &
|
|
260
|
+
`onInteractOutside` props on `Modal` to prevent the modal from closing when
|
|
261
|
+
users click outside of the modal or press the escape key.
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
<Modal
|
|
265
|
+
onPointerDownOutside={event => event.preventDefault()}
|
|
266
|
+
onEscapeKeyDown={event => event.preventDefault()}
|
|
267
|
+
onInteractOutside={event => event.preventDefault()}
|
|
268
|
+
>
|
|
269
|
+
{/* Modal content */}
|
|
270
|
+
</Modal>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
<ModalRoot>
|
|
275
|
+
<ModalTrigger>
|
|
276
|
+
<Button>Open modal</Button>
|
|
277
|
+
</ModalTrigger>
|
|
278
|
+
<Modal
|
|
279
|
+
{...args}
|
|
280
|
+
onEscapeKeyDown={e => e.preventDefault()}
|
|
281
|
+
onPointerDownOutside={e => e.preventDefault()}
|
|
282
|
+
hideCloseButton
|
|
283
|
+
>
|
|
284
|
+
<ModalFooter>
|
|
285
|
+
<ModalClose>
|
|
286
|
+
<Button variant="ghost" colorScheme="functional">
|
|
287
|
+
Cancel
|
|
288
|
+
</Button>
|
|
289
|
+
</ModalClose>
|
|
290
|
+
<ModalClose>
|
|
291
|
+
<Button variant="solid" colorScheme="highlight">
|
|
292
|
+
Primary
|
|
293
|
+
</Button>
|
|
294
|
+
</ModalClose>
|
|
295
|
+
</ModalFooter>
|
|
296
|
+
</Modal>
|
|
297
|
+
</ModalRoot>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### ModalRoot
|
|
301
|
+
|
|
302
|
+
This component is based on [Radix UI's Dialog primitive](https://www.radix-ui.com/primitives/docs/components/dialog).
|
|
303
|
+
|
|
304
|
+
### Modal
|
|
305
|
+
|
|
306
|
+
This component is based on Radix UI's Dialog primitive and supports the following common props:
|
|
307
|
+
|
|
308
|
+
- Margin
|
|
309
|
+
|
|
310
|
+
| Prop | Type | Default | Description |
|
|
311
|
+
| ---------------------- | ----------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
312
|
+
| `image` | `ReactNode` | — | |
|
|
313
|
+
| `container` | `Element \| DocumentFragment \| null` | — | Specify a container element to portal the content into. |
|
|
314
|
+
| `forceMount` | `true` | — | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |
|
|
315
|
+
| `heading` | `string` | — | |
|
|
316
|
+
| `onEscapeKeyDown` | `((event: KeyboardEvent) => void)` | — | Event handler called when the escape key is down. Can be prevented. |
|
|
317
|
+
| `onPointerDownOutside` | `((event: PointerDownOutsideEvent) => void)` | — | Event handler called when the a `pointerdown` event happens outside of the `DismissableLayer`. Can be prevented. |
|
|
318
|
+
| `onFocusOutside` | `((event: FocusOutsideEvent) => void)` | — | Event handler called when the focus moves outside of the `DismissableLayer`. Can be prevented. |
|
|
319
|
+
| `onInteractOutside` | `((event: PointerDownOutsideEvent \| FocusOutsideEvent) => void)` | — | Event handler called when an interaction happens outside the `DismissableLayer`. Specifically, when a `pointerdown` event happens outside or focus moves outside of it. Can be prevented. |
|
|
320
|
+
| `onOpenAutoFocus` | `((event: Event) => void)` | — | Event handler called when auto-focusing on open. Can be prevented. |
|
|
321
|
+
| `onCloseAutoFocus` | `((event: Event) => void)` | — | Event handler called when auto-focusing on close. Can be prevented. |
|
|
322
|
+
| `description` | `string` | — | |
|
|
323
|
+
| `hideCloseButton` | `boolean` | — | |
|
|
324
|
+
| `fullScreen` | `boolean` | — | |
|
|
325
|
+
| `loadingText` | `string` | — | @deprecated Please use loadingHeading and loadingDescription instead |
|
|
326
|
+
| `loadingHeading` | `string` | — | |
|
|
327
|
+
| `loadingDescription` | `string` | — | |
|
|
328
|
+
| `loading` | `boolean` | — | |
|