@utilitywarehouse/hearth-react 0.30.2 → 0.31.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 (121) hide show
  1. package/dist/chunk-52NQJSZ4.cjs +4 -0
  2. package/dist/chunk-52NQJSZ4.cjs.map +1 -0
  3. package/dist/chunk-7CV7RBFK.js +4 -0
  4. package/dist/chunk-7CV7RBFK.js.map +1 -0
  5. package/dist/chunk-DSRDXNH3.cjs +4 -0
  6. package/dist/chunk-DSRDXNH3.cjs.map +1 -0
  7. package/dist/chunk-E37ICSZV.js +4 -0
  8. package/dist/chunk-E37ICSZV.js.map +1 -0
  9. package/dist/chunk-ECA6VI7J.js +4 -0
  10. package/dist/chunk-ECA6VI7J.js.map +1 -0
  11. package/dist/chunk-EUOX7RSZ.js +4 -0
  12. package/dist/chunk-EUOX7RSZ.js.map +1 -0
  13. package/dist/chunk-FTCABZFP.cjs +4 -0
  14. package/dist/{chunk-AO6OVADW.cjs.map → chunk-FTCABZFP.cjs.map} +1 -1
  15. package/dist/chunk-GDJ3WYMP.cjs +4 -0
  16. package/dist/chunk-GDJ3WYMP.cjs.map +1 -0
  17. package/dist/chunk-HL4LTGKN.js +4 -0
  18. package/dist/chunk-HL4LTGKN.js.map +1 -0
  19. package/dist/chunk-NJMLALZH.js +4 -0
  20. package/dist/chunk-NJMLALZH.js.map +1 -0
  21. package/dist/chunk-OSQTRLPK.js +4 -0
  22. package/dist/chunk-OSQTRLPK.js.map +1 -0
  23. package/dist/chunk-PQPP4JZR.cjs +4 -0
  24. package/dist/chunk-PQPP4JZR.cjs.map +1 -0
  25. package/dist/{chunk-DDEOPEK5.cjs → chunk-U3YIMIX2.cjs} +2 -2
  26. package/dist/{chunk-DDEOPEK5.cjs.map → chunk-U3YIMIX2.cjs.map} +1 -1
  27. package/dist/chunk-UVAC3YXB.js +4 -0
  28. package/dist/chunk-UVAC3YXB.js.map +1 -0
  29. package/dist/chunk-UXOZQAZJ.cjs +4 -0
  30. package/dist/chunk-UXOZQAZJ.cjs.map +1 -0
  31. package/dist/chunk-WDLLAQFF.cjs +4 -0
  32. package/dist/chunk-WDLLAQFF.cjs.map +1 -0
  33. package/dist/chunk-XPPODAZP.cjs +4 -0
  34. package/dist/chunk-XPPODAZP.cjs.map +1 -0
  35. package/dist/chunk-XVV4DBFD.js +4 -0
  36. package/dist/chunk-XVV4DBFD.js.map +1 -0
  37. package/dist/components/Menu/Menu.cjs +1 -1
  38. package/dist/components/Menu/Menu.d.ts +2 -0
  39. package/dist/components/Menu/Menu.d.ts.map +1 -1
  40. package/dist/components/Menu/Menu.js +1 -1
  41. package/dist/components/Menu/Menu.props.d.ts +8 -3
  42. package/dist/components/Menu/Menu.props.d.ts.map +1 -1
  43. package/dist/components/Menu/MenuContent.cjs +1 -1
  44. package/dist/components/Menu/MenuContent.cjs.map +1 -1
  45. package/dist/components/Menu/MenuContent.d.ts +1 -1
  46. package/dist/components/Menu/MenuContent.d.ts.map +1 -1
  47. package/dist/components/Menu/MenuContent.js +1 -1
  48. package/dist/components/Menu/MenuContent.props.d.ts +21 -4
  49. package/dist/components/Menu/MenuContent.props.d.ts.map +1 -1
  50. package/dist/components/Menu/MenuItem.cjs +1 -1
  51. package/dist/components/Menu/MenuItem.cjs.map +1 -1
  52. package/dist/components/Menu/MenuItem.d.ts.map +1 -1
  53. package/dist/components/Menu/MenuItem.js +1 -1
  54. package/dist/components/Menu/MenuItem.props.d.ts +21 -4
  55. package/dist/components/Menu/MenuItem.props.d.ts.map +1 -1
  56. package/dist/components/Menu/MenuTrigger.cjs +1 -1
  57. package/dist/components/Menu/MenuTrigger.js +1 -1
  58. package/dist/components/Menu/MenuTrigger.props.d.ts +10 -3
  59. package/dist/components/Menu/MenuTrigger.props.d.ts.map +1 -1
  60. package/dist/components/Modal/Modal.cjs +1 -1
  61. package/dist/components/Modal/Modal.js +1 -1
  62. package/dist/components/Modal/ModalContent.cjs +1 -1
  63. package/dist/components/Modal/ModalContent.d.ts.map +1 -1
  64. package/dist/components/Modal/ModalContent.js +1 -1
  65. package/dist/components/SegmentedControl/SegmentedControl.cjs +4 -0
  66. package/dist/components/SegmentedControl/SegmentedControl.cjs.map +1 -0
  67. package/dist/components/SegmentedControl/SegmentedControl.d.ts +13 -0
  68. package/dist/components/SegmentedControl/SegmentedControl.d.ts.map +1 -0
  69. package/dist/components/SegmentedControl/SegmentedControl.js +4 -0
  70. package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -0
  71. package/dist/components/SegmentedControl/SegmentedControl.props.cjs +4 -0
  72. package/dist/components/SegmentedControl/SegmentedControl.props.cjs.map +1 -0
  73. package/dist/components/SegmentedControl/SegmentedControl.props.d.ts +23 -0
  74. package/dist/components/SegmentedControl/SegmentedControl.props.d.ts.map +1 -0
  75. package/dist/components/SegmentedControl/SegmentedControl.props.js +4 -0
  76. package/dist/components/SegmentedControl/SegmentedControl.props.js.map +1 -0
  77. package/dist/components/SegmentedControl/SegmentedControlOption.cjs +4 -0
  78. package/dist/components/SegmentedControl/SegmentedControlOption.cjs.map +1 -0
  79. package/dist/components/SegmentedControl/SegmentedControlOption.d.ts +9 -0
  80. package/dist/components/SegmentedControl/SegmentedControlOption.d.ts.map +1 -0
  81. package/dist/components/SegmentedControl/SegmentedControlOption.js +4 -0
  82. package/dist/components/SegmentedControl/SegmentedControlOption.js.map +1 -0
  83. package/dist/components/SegmentedControl/SegmentedControlOption.props.cjs +3 -0
  84. package/dist/components/SegmentedControl/SegmentedControlOption.props.cjs.map +1 -0
  85. package/dist/components/SegmentedControl/SegmentedControlOption.props.d.ts +17 -0
  86. package/dist/components/SegmentedControl/SegmentedControlOption.props.d.ts.map +1 -0
  87. package/dist/components/SegmentedControl/SegmentedControlOption.props.js +3 -0
  88. package/dist/components/SegmentedControl/SegmentedControlOption.props.js.map +1 -0
  89. package/dist/index.cjs +1 -1
  90. package/dist/index.cjs.map +1 -1
  91. package/dist/index.d.ts +4 -0
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +1 -1
  94. package/package.json +3 -3
  95. package/public/llms/components/menu.md +161 -33
  96. package/public/llms/components/modal.md +54 -0
  97. package/public/llms/components/segmented-control.md +295 -0
  98. package/public/llms/docs/getting-started.md +1 -1
  99. package/public/llms.txt +2 -1
  100. package/styles.css +2 -2
  101. package/dist/chunk-7BXFXDDZ.js +0 -4
  102. package/dist/chunk-7BXFXDDZ.js.map +0 -1
  103. package/dist/chunk-AO6OVADW.cjs +0 -4
  104. package/dist/chunk-BCAGQPQD.cjs +0 -4
  105. package/dist/chunk-BCAGQPQD.cjs.map +0 -1
  106. package/dist/chunk-BZOGWVQZ.js +0 -4
  107. package/dist/chunk-BZOGWVQZ.js.map +0 -1
  108. package/dist/chunk-GVIVGMOJ.js +0 -4
  109. package/dist/chunk-GVIVGMOJ.js.map +0 -1
  110. package/dist/chunk-GVUQYOSO.cjs +0 -4
  111. package/dist/chunk-GVUQYOSO.cjs.map +0 -1
  112. package/dist/chunk-JPH4HCSL.js +0 -4
  113. package/dist/chunk-JPH4HCSL.js.map +0 -1
  114. package/dist/chunk-K3AF4B5W.cjs +0 -4
  115. package/dist/chunk-K3AF4B5W.cjs.map +0 -1
  116. package/dist/chunk-NLZMPDP5.cjs +0 -4
  117. package/dist/chunk-NLZMPDP5.cjs.map +0 -1
  118. package/dist/chunk-PRWRW3JD.js +0 -4
  119. package/dist/chunk-PRWRW3JD.js.map +0 -1
  120. package/dist/chunk-PXIOYGGU.js +0 -4
  121. package/dist/chunk-PXIOYGGU.js.map +0 -1
@@ -1,6 +1,18 @@
1
1
  # Menu
2
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.
3
+ Use the `Menu` component to present a short list of actions or options in
4
+ response to a user’s interaction. Menus are ideal for actions like sorting,
5
+ filtering, or providing additional options without navigating away from the
6
+ current screen.
7
+
8
+ - [Usage](#usage)
9
+ - [MenuTrigger](#menutrigger)
10
+ - [Placement](#placement)
11
+ - [MenuItem](#menuitem)
12
+ - [Accessibility](#accessibility)
13
+ - [SEO](#seo)
14
+ - [Migration](#migration)
15
+ - [API](#api)
4
16
 
5
17
  ```tsx
6
18
  <Menu {...args}>
@@ -66,7 +78,7 @@ outside of the first `Menu` to close it.
66
78
  <Menu modal={false}>
67
79
  ```
68
80
 
69
- ## Menu trigger
81
+ ## MenuTrigger
70
82
 
71
83
  You must render either a `Button` or `IconButton` component as a child of the `MenuTrigger`.
72
84
 
@@ -86,7 +98,57 @@ You must render either a `Button` or `IconButton` component as a child of the `M
86
98
  </Menu>
87
99
  ```
88
100
 
89
- ## Menu content placement
101
+ ### Detached trigger
102
+
103
+ By default the `MenuTrigger` lives inside `Menu`. When the trigger needs to exist in a
104
+ different part of the component tree — for example in a toolbar while the menu state is
105
+ managed elsewhere — use `Menu.createHandle()` to connect them.
106
+
107
+ Create the handle once at module level (outside any component), then pass it to both
108
+ `MenuTrigger` and `Menu` via the `handle` prop. `Menu` no longer needs a `MenuTrigger`
109
+ child in this pattern.
110
+
111
+ ```tsx
112
+ <Flex gap="300" alignItems="center">
113
+ {/* MenuTrigger lives outside Menu — connected via handle */}
114
+ <MenuTrigger handle={detachedHandle}>
115
+ <Button variant="outline" colorScheme="functional">
116
+ Open menu
117
+ <ExpandSmallIcon />
118
+ </Button>
119
+ </MenuTrigger>
120
+ {/* Menu has no MenuTrigger child — it opens via the handle above */}
121
+ <Menu handle={detachedHandle}>
122
+ <MenuContent>
123
+ <MenuItem>Item</MenuItem>
124
+ <MenuItem>Item</MenuItem>
125
+ <MenuItem>Item</MenuItem>
126
+ </MenuContent>
127
+ </Menu>
128
+ </Flex>
129
+ ```
130
+
131
+ ```tsx
132
+ const menuHandle = Menu.createHandle();
133
+
134
+ // Somewhere in the tree:
135
+ <MenuTrigger handle={menuHandle}>
136
+ <Button variant="outline" colorScheme="functional">
137
+ Open menu
138
+ <ExpandSmallIcon />
139
+ </Button>
140
+ </MenuTrigger>
141
+
142
+ // Elsewhere in the tree:
143
+ <Menu handle={menuHandle}>
144
+ <MenuContent>
145
+ <MenuItem>Item</MenuItem>
146
+ <MenuItem>Item</MenuItem>
147
+ </MenuContent>
148
+ </Menu>
149
+ ```
150
+
151
+ ## Placement
90
152
 
91
153
  You can adjust the vertical and horizontal placement of the `MenuContent`.
92
154
 
@@ -139,12 +201,12 @@ You can adjust the vertical and horizontal placement of the `MenuContent`.
139
201
  </Flex>
140
202
  ```
141
203
 
142
- ## Menu item
204
+ ## MenuItem
143
205
 
144
206
  The `MenuItem` can be either `functional` or `destructive`, and can also contain icons.
145
207
 
146
- If a `MenuItem` is navigating to another page, you can use the `asChild` prop to
147
- render a semantic `<a>` element.
208
+ If a `MenuItem` is navigating to another page, use the `asChild` prop to render a
209
+ semantic `<a>` element — all accessible attributes are merged onto the child.
148
210
 
149
211
  ```tsx
150
212
  <MenuContent>
@@ -154,7 +216,6 @@ render a semantic `<a>` element.
154
216
  Destructive item
155
217
  <TrashSmallIcon />
156
218
  </MenuItem>
157
- {/* render a link element if navigating to somewhere else */}
158
219
  <MenuItem asChild>
159
220
  <a href="/another-page">
160
221
  Navigation Item
@@ -227,44 +288,111 @@ to manage focus movement among menu items.
227
288
 
228
289
  By default, `MenuContent` is removed from the DOM when the menu is closed, which
229
290
  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
291
+ for SEO — for example, primary navigation links — use the `keepMounted` prop on
231
292
  `MenuContent` to keep them in the DOM at all times.
232
293
 
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
294
  ```tsx
238
- <MenuContent forceMount>
295
+ <MenuContent keepMounted>
239
296
  <MenuItem>Item</MenuItem>
240
297
  </MenuContent>
241
298
  ```
242
299
 
243
- ## Menu API
300
+ ### Migrating from `v0.30.x`
301
+
302
+ **`MenuItem` deprecations:**
303
+
304
+ - `onSelect` is deprecated. Use `onClick` instead — it fires for both mouse and keyboard activation.
305
+ - `textValue` is deprecated. Use `label` instead — it is used for accessibility and keyboard navigation.
306
+
307
+ **`MenuContent` deprecation:**
308
+
309
+ - `forceMount` is deprecated. Use `keepMounted` instead.
310
+
311
+ ```tsx
312
+ {/* Before */}
313
+ <MenuItem onSelect={handleSelect} textValue="label">Item</MenuItem>
314
+ <MenuContent forceMount>...</MenuContent>
315
+
316
+ {/* After */}
317
+ <MenuItem onClick={handleSelect} label="label">Item</MenuItem>
318
+ <MenuContent keepMounted>...</MenuContent>
319
+ ```
320
+
321
+ ### Consumer migration prompt
244
322
 
245
- | Prop | Type | Default | Description |
246
- | -------------- | --------------------------- | ------- | ----------- |
247
- | `open` | `boolean` | — | |
248
- | `defaultOpen` | `boolean` | — | |
249
- | `onOpenChange` | `((open: boolean) => void)` | — | |
250
- | `modal` | `boolean` | — | |
323
+ Paste the following into an agent to update all Menu usages in your codebase:
251
324
 
252
- ## MenuContent API
325
+ ```
326
+ I'm upgrading @utilitywarehouse/hearth-react. The Menu component has migrated
327
+ from Radix UI to Base UI internally. The following prop changes affect consumers:
328
+
329
+ RENAMED (deprecated — old name still works but logs a dev-mode warning):
330
+ - `MenuItem: onSelect` → `onClick`: Base UI uses standard React onClick for
331
+ both mouse and keyboard activation.
332
+ - `MenuItem: textValue` → `label`: used for keyboard text navigation and
333
+ accessibility.
334
+ - `MenuContent: forceMount` → `keepMounted`: aligned with Base UI's API.
335
+
336
+ Please search this codebase for all usages of MenuItem and MenuContent imported
337
+ from '@utilitywarehouse/hearth-react' and apply the following changes:
338
+ - Replace every `onSelect=` on MenuItem with `onClick=`
339
+ - Replace every `textValue=` on MenuItem with `label=`
340
+ - Replace every `forceMount` on MenuContent with `keepMounted`
341
+
342
+ Do not change any other logic, styling, or structure. After making changes,
343
+ run TypeScript to confirm no type errors remain.
344
+ ```
253
345
 
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` | |
346
+ ## API
347
+
348
+ | Prop | Type | Default | Description |
349
+ | ------------------ | --------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
350
+ | `children` | `ReactNode` | — | The content of the Menu. Should contain `MenuTrigger` and `MenuContent`. |
351
+ | `open` | `boolean` | — | Whether the menu is currently open. |
352
+ | `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
353
+ | `modal` | `boolean` | `true` | Determines if the menu enters a modal state when open. - `true`: user interaction is limited to the menu: document page scroll is locked and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. |
354
+ | `defaultOpen` | `boolean` | `false` | Whether the menu is initially open. To render a controlled menu, use the `open` prop instead. |
355
+ | `onOpenChange` | `((open: boolean, eventDetails: MenuRootChangeEventDetails) => void)` | — | Event handler called when the menu is opened or closed. |
356
+ | `actionsRef` | `RefObject<MenuRootActions \| null>` | — | A ref to imperative actions. - `unmount`: When specified, the menu will not be unmounted when closed. Instead, the `unmount` function must be called to unmount the menu manually. Useful when the menu's animation is controlled by an external library. - `close`: When specified, the menu can be closed imperatively. |
357
+ | `triggerId` | `string \| null` | — | ID of the trigger that the popover is associated with. This is useful in conjunction with the `open` prop to create a controlled popover. There's no need to specify this prop when the popover is uncontrolled (that is, when the `open` prop is not set). |
358
+ | `defaultTriggerId` | `string \| null` | — | ID of the trigger that the popover is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open popover. |
359
+ | `handle` | `MenuHandle<unknown>` | — | A handle to associate the menu with a trigger. If specified, allows external triggers to control the menu's open state. |
360
+
361
+ ### MenuTrigger API
362
+
363
+ | Prop | Type | Default | Description |
364
+ | ------------- | ------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- |
365
+ | `style` | `CSSProperties` | — | |
366
+ | `className` | `string` | — | |
367
+ | `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
368
+ | `children` | `ReactElement<unknown, string \| JSXElementConstructor<any>>` | — | The content of the MenuTrigger. Should contain a single `Button` or `IconButton` that will be used as the trigger for the Menu. |
369
+ | `closeDelay` | `number` | `0` | How long to wait before closing the menu that was opened on hover. Specified in milliseconds. Requires the `openOnHover` prop. |
370
+ | `handle` | `MenuHandle<unknown>` | — | A handle to associate the trigger with a menu. |
371
+ | `delay` | `number` | `100` | How long to wait before the menu may be opened on hover. Specified in milliseconds. Requires the `openOnHover` prop. |
372
+ | `openOnHover` | `boolean` | — | Whether the menu should also open when the trigger is hovered. |
373
+
374
+ ### MenuContent API
375
+
376
+ | Prop | Type | Default | Description |
377
+ | ------------- | ---------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------ |
378
+ | `className` | `string` | — | |
379
+ | `style` | `CSSProperties` | — | |
380
+ | `placement` | `"bottomLeft" \| "bottomRight" \| "topLeft" \| "topRight"` | `bottomLeft` | The placement of the menu relative to the trigger element. Defaults to 'bottomLeft'. |
381
+ | `keepMounted` | `boolean` | `false` | Whether the menu should be kept mounted in the DOM when closed. Defaults to false. |
382
+ | `forceMount` | `true` | — | @deprecated Use `keepMounted` instead. Will be removed in next major. |
259
383
 
260
384
  ### MenuItem API
261
385
 
262
386
  This component is based on the `div` element.
263
387
 
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` | |
388
+ | Prop | Type | Default | Description |
389
+ | -------------- | ------------------------------------------------------------------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
390
+ | `label` | `string` | — | Overrides the text label to use when the item is matched during keyboard text navigation. |
391
+ | `className` | `string` | — | |
392
+ | `id` | `string` | — | @ignore |
393
+ | `onClick` | `((event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void)` | — | The click handler for the menu item. |
394
+ | `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
395
+ | `closeOnClick` | `boolean` | `true` | Whether to close the menu when the item is clicked. |
396
+ | `colorScheme` | `"functional" \| "destructive"` | `functional` | |
397
+ | `asChild` | `boolean` | — | Change the default rendered element for the one passed as a child, merging their props and behaviour. Useful for rendering MenuItem as a link. |
398
+ | `textValue` | `string` | — | Deprecated override of the text label to use when the item is matched during keyboard text navigation. @deprecated Use `label` instead. |
@@ -104,6 +104,60 @@ on mobile, and ensure the content scrolls correctly.
104
104
  Further terms apply see our Residential Products and Services{' '}
105
105
  <InlineLink href="#">terms and conditions.</InlineLink>
106
106
  </BodyText>
107
+ <BodyText paragraphSpacing size="md">
108
+ <Strong>Cheapest variable energy:</Strong> when you take energy and two other eligible
109
+ bundle services vs standard variable tariffs offered by major suppliers (large and medium
110
+ suppliers as defined by Ofgem), for sale nationally, excl. existing customer tariffs. Based
111
+ on Ofgem&apos;s typical domestic usage. Payment by Direct Debit. Correct as of 13/10/2025.
112
+ Contact us to verify. <InlineLink href="#">See UW terms and conditions.</InlineLink>
113
+ </BodyText>
114
+ <BodyText paragraphSpacing size="md">
115
+ <Strong>UW Price Pledge:</Strong> Eligible new customers (from 22/04/24) who take 3 or more
116
+ qualifying services and regularly use their Cashback Card. If a customer doesn&apos;t save
117
+ with UW (incl. Cashback) in their first year vs their previous provider (or cheapest major
118
+ provider where applicable), they can apply to claim the UW Price Pledge between 12 - 15
119
+ months after their sign-up date; the pledge & no exit fees only applies to qualifying
120
+ services taken at sign-up. When you claim, we&apos;ll assume an average Cashback Card saving
121
+ of £160 a year (based on customer usage data from 03.04.23 to 31.03.24, for users who earned
122
+ Cashback at least at least once a week, excluding promotional activities) or your actual
123
+ Cashback saving if higher. Full details, eligibility and terms available{' '}
124
+ <InlineLink href="#">here.</InlineLink>
125
+ </BodyText>
126
+ <BodyText paragraphSpacing size="md">
127
+ <Strong>£400 to help you switch:</Strong> When you take a 3 or 4+ Service Bundle, we&apos;ll
128
+ give you credit up to £400 towards any termination fees (excluding Home Insurance) you have
129
+ to pay your current providers. You&apos;ll need to return any equipment and pay for services
130
+ you used before you cancel. Additional requirements apply to customers who are tenants.
131
+ Further terms apply see our Residential Products and Services{' '}
132
+ <InlineLink href="#">terms and conditions.</InlineLink>
133
+ </BodyText>
134
+ <BodyText paragraphSpacing size="md">
135
+ <Strong>Cheapest variable energy:</Strong> when you take energy and two other eligible
136
+ bundle services vs standard variable tariffs offered by major suppliers (large and medium
137
+ suppliers as defined by Ofgem), for sale nationally, excl. existing customer tariffs. Based
138
+ on Ofgem&apos;s typical domestic usage. Payment by Direct Debit. Correct as of 13/10/2025.
139
+ Contact us to verify. <InlineLink href="#">See UW terms and conditions.</InlineLink>
140
+ </BodyText>
141
+ <BodyText paragraphSpacing size="md">
142
+ <Strong>UW Price Pledge:</Strong> Eligible new customers (from 22/04/24) who take 3 or more
143
+ qualifying services and regularly use their Cashback Card. If a customer doesn&apos;t save
144
+ with UW (incl. Cashback) in their first year vs their previous provider (or cheapest major
145
+ provider where applicable), they can apply to claim the UW Price Pledge between 12 - 15
146
+ months after their sign-up date; the pledge & no exit fees only applies to qualifying
147
+ services taken at sign-up. When you claim, we&apos;ll assume an average Cashback Card saving
148
+ of £160 a year (based on customer usage data from 03.04.23 to 31.03.24, for users who earned
149
+ Cashback at least at least once a week, excluding promotional activities) or your actual
150
+ Cashback saving if higher. Full details, eligibility and terms available{' '}
151
+ <InlineLink href="#">here.</InlineLink>
152
+ </BodyText>
153
+ <BodyText paragraphSpacing size="md">
154
+ <Strong>£400 to help you switch:</Strong> When you take a 3 or 4+ Service Bundle, we&apos;ll
155
+ give you credit up to £400 towards any termination fees (excluding Home Insurance) you have
156
+ to pay your current providers. You&apos;ll need to return any equipment and pay for services
157
+ you used before you cancel. Additional requirements apply to customers who are tenants.
158
+ Further terms apply see our Residential Products and Services{' '}
159
+ <InlineLink href="#">terms and conditions.</InlineLink>
160
+ </BodyText>
107
161
  </ModalContent>
108
162
  <ModalFooter>
109
163
  <ModalClose>
@@ -0,0 +1,295 @@
1
+ # SegmentedControl
2
+
3
+ Use a Segmented Control to switch between alternative views of closely related content.
4
+
5
+ - [Usage](#usage)
6
+ - [Sizes](#sizes)
7
+ - [Icons](#icons)
8
+ - [API](#api)
9
+
10
+ ```tsx
11
+ <Flex direction="column" gap="400">
12
+ <BodyText as="p" size="md" weight="semibold">
13
+ SM — labels
14
+ </BodyText>
15
+ <SegmentedControl defaultValue={['option-1']} size="sm">
16
+ <SegmentedControlOption value="option-1" label="Label" />
17
+ <SegmentedControlOption value="option-2" label="Label" />
18
+ <SegmentedControlOption value="option-3" label="Label" />
19
+ <SegmentedControlOption value="option-4" label="Label" />
20
+ </SegmentedControl>
21
+
22
+ <BodyText as="p" size="md" weight="semibold">
23
+ MD — labels
24
+ </BodyText>
25
+ <SegmentedControl defaultValue={['option-1']} size="md">
26
+ <SegmentedControlOption value="option-1" label="Label" />
27
+ <SegmentedControlOption value="option-2" label="Label" />
28
+ <SegmentedControlOption value="option-3" label="Label" />
29
+ <SegmentedControlOption value="option-4" label="Label" />
30
+ </SegmentedControl>
31
+
32
+ <BodyText as="p" size="md" weight="semibold">
33
+ SM — icons + labels
34
+ </BodyText>
35
+ <SegmentedControl defaultValue={['gas']} size="sm">
36
+ <SegmentedControlOption value="gas" label="Gas" icon={<GasSmallIcon />} />
37
+ <SegmentedControlOption
38
+ value="electricity"
39
+ label="Electricity"
40
+ icon={<ElectricitySmallIcon />}
41
+ />
42
+ <SegmentedControlOption value="mobile" label="Mobile" icon={<MobileSmallIcon />} />
43
+ <SegmentedControlOption value="broadband" label="Broadband" icon={<BroadbandSmallIcon />} />
44
+ <SegmentedControlOption value="insurance" label="Insurance" icon={<InsuranceSmallIcon />} />
45
+ <SegmentedControlOption value="cashback" label="Cashback" icon={<CashbackCardSmallIcon />} />
46
+ </SegmentedControl>
47
+
48
+ <BodyText as="p" size="md" weight="semibold">
49
+ MD — icons + labels
50
+ </BodyText>
51
+ <SegmentedControl defaultValue={['gas']} size="md">
52
+ <SegmentedControlOption value="gas" label="Gas" icon={<GasMediumIcon />} />
53
+ <SegmentedControlOption
54
+ value="electricity"
55
+ label="Electricity"
56
+ icon={<ElectricityMediumIcon />}
57
+ />
58
+ <SegmentedControlOption value="mobile" label="Mobile" icon={<MobileMediumIcon />} />
59
+ <SegmentedControlOption value="broadband" label="Broadband" icon={<BroadbandMediumIcon />} />
60
+ <SegmentedControlOption value="insurance" label="Insurance" icon={<InsuranceMediumIcon />} />
61
+ <SegmentedControlOption value="cashback" label="Cashback" icon={<CashbackCardMediumIcon />} />
62
+ </SegmentedControl>
63
+
64
+ <BodyText as="p" size="md" weight="semibold">
65
+ Disabled
66
+ </BodyText>
67
+ <SegmentedControl defaultValue={['option-1']} size="sm" disabled>
68
+ <SegmentedControlOption value="option-1" label="Label" />
69
+ <SegmentedControlOption value="option-2" label="Label" />
70
+ <SegmentedControlOption value="option-3" label="Label" />
71
+ </SegmentedControl>
72
+
73
+ <BodyText as="p" size="md" weight="semibold">
74
+ Per-option disabled
75
+ </BodyText>
76
+ <SegmentedControl defaultValue={['option-1']} size="sm">
77
+ <SegmentedControlOption value="option-1" label="Label" />
78
+ <SegmentedControlOption value="option-2" label="Label" disabled />
79
+ <SegmentedControlOption value="option-3" label="Label" />
80
+ </SegmentedControl>
81
+ </Flex>
82
+ ```
83
+
84
+ ## Usage
85
+
86
+ Wrap `SegmentedControlOption` components in a `SegmentedControl`. Each option requires a unique `value`. Always provide a `defaultValue` — a Segmented Control cannot be used without a selected option.
87
+
88
+ ```tsx
89
+ <SegmentedControl defaultValue={['gas']}>
90
+ <SegmentedControlOption value="gas" label="Gas" />
91
+ <SegmentedControlOption value="electricity" label="Electricity" />
92
+ </SegmentedControl>
93
+ ```
94
+
95
+ ```tsx
96
+ <SegmentedControl {...args}>
97
+ <SegmentedControlOption value="option-1" label="Option 1" />
98
+ <SegmentedControlOption value="option-2" label="Option 2" />
99
+ <SegmentedControlOption value="option-3" label="Option 3" />
100
+ <SegmentedControlOption value="option-4" label="Option 4" />
101
+ </SegmentedControl>
102
+ ```
103
+
104
+ ## Sizes
105
+
106
+ Two sizes are available: `sm` (32px, default) and `md` (48px). The `size` prop is responsive and can be used to display different sizes at different breakpoints.
107
+
108
+ ```tsx
109
+ <SegmentedControl size={{ mobile: 'sm', desktop: 'md' }} defaultValue={['gas']}>
110
+ ...
111
+ </SegmentedControl>
112
+ ```
113
+
114
+ ## Icons
115
+
116
+ Use the `icon` prop to display an icon before the label. Use the Small icon variant with `size="sm"` and the Medium icon variant with `size="md"`. Either add icons to all options or none — do not mix.
117
+
118
+ ```tsx
119
+ <SegmentedControl defaultValue={['gas']} size="sm">
120
+ <SegmentedControlOption value="gas" label="Gas" icon={<GasSmallIcon />} />
121
+ <SegmentedControlOption value="electricity" label="Electricity" icon={<ElectricitySmallIcon />} />
122
+ </SegmentedControl>
123
+ ```
124
+
125
+ ```tsx
126
+ <Flex direction="column" gap="300" alignItems="start">
127
+ <SegmentedControl defaultValue={['gas']} size="sm">
128
+ <SegmentedControlOption value="gas" label="Gas" icon={<GasSmallIcon />} />
129
+ <SegmentedControlOption
130
+ value="electricity"
131
+ label="Electricity"
132
+ icon={<ElectricitySmallIcon />}
133
+ />
134
+ <SegmentedControlOption value="mobile" label="Mobile" icon={<MobileSmallIcon />} />
135
+ <SegmentedControlOption value="broadband" label="Broadband" icon={<BroadbandSmallIcon />} />
136
+ <SegmentedControlOption value="insurance" label="Insurance" icon={<InsuranceSmallIcon />} />
137
+ <SegmentedControlOption value="cashback" label="Cashback" icon={<CashbackCardSmallIcon />} />
138
+ </SegmentedControl>
139
+ <SegmentedControl defaultValue={['gas']} size="md">
140
+ <SegmentedControlOption value="gas" label="Gas" icon={<GasMediumIcon />} />
141
+ <SegmentedControlOption
142
+ value="electricity"
143
+ label="Electricity"
144
+ icon={<ElectricityMediumIcon />}
145
+ />
146
+ <SegmentedControlOption value="mobile" label="Mobile" icon={<MobileMediumIcon />} />
147
+ <SegmentedControlOption value="broadband" label="Broadband" icon={<BroadbandMediumIcon />} />
148
+ <SegmentedControlOption value="insurance" label="Insurance" icon={<InsuranceMediumIcon />} />
149
+ <SegmentedControlOption value="cashback" label="Cashback" icon={<CashbackCardMediumIcon />} />
150
+ </SegmentedControl>
151
+ </Flex>
152
+ ```
153
+
154
+ ### Responsive icon sizes
155
+
156
+ If you need to render different sized icons in conjunction with the responsive `size` prop, use `Box` with display props to show the appropriate icon at each breakpoint.
157
+
158
+ ```tsx
159
+ import { GasMediumIcon, GasSmallIcon } from '@utilitywarehouse/hearth-react-icons';
160
+ import { Box, SegmentedControl, SegmentedControlOption } from '@utilitywarehouse/hearth-react';
161
+
162
+ <SegmentedControl defaultValue={['gas']} size={{ mobile: 'sm', desktop: 'md' }}>
163
+ <SegmentedControlOption
164
+ value="gas"
165
+ label="Gas"
166
+ icon={
167
+ <>
168
+ <Box asChild display={{ mobile: 'none', desktop: 'block' }}>
169
+ <GasMediumIcon />
170
+ </Box>
171
+ <Box asChild display={{ desktop: 'none' }}>
172
+ <GasSmallIcon />
173
+ </Box>
174
+ </>
175
+ }
176
+ />
177
+ </SegmentedControl>;
178
+ ```
179
+
180
+ ```tsx
181
+ <SegmentedControl defaultValue={['gas']} size={{ mobile: 'sm', desktop: 'md' }}>
182
+ <SegmentedControlOption
183
+ value="gas"
184
+ label="Gas"
185
+ icon={
186
+ <>
187
+ <Box asChild display={{ mobile: 'none', desktop: 'block' }}>
188
+ <GasMediumIcon />
189
+ </Box>
190
+ <Box asChild display={{ desktop: 'none' }}>
191
+ <GasSmallIcon />
192
+ </Box>
193
+ </>
194
+ }
195
+ />
196
+ <SegmentedControlOption
197
+ value="electricity"
198
+ label="Electricity"
199
+ icon={
200
+ <>
201
+ <Box asChild display={{ mobile: 'none', desktop: 'block' }}>
202
+ <ElectricityMediumIcon />
203
+ </Box>
204
+ <Box asChild display={{ desktop: 'none' }}>
205
+ <ElectricitySmallIcon />
206
+ </Box>
207
+ </>
208
+ }
209
+ />
210
+ <SegmentedControlOption
211
+ value="mobile"
212
+ label="Mobile"
213
+ icon={
214
+ <>
215
+ <Box asChild display={{ mobile: 'none', desktop: 'block' }}>
216
+ <MobileMediumIcon />
217
+ </Box>
218
+ <Box asChild display={{ desktop: 'none' }}>
219
+ <MobileSmallIcon />
220
+ </Box>
221
+ </>
222
+ }
223
+ />
224
+ <SegmentedControlOption
225
+ value="broadband"
226
+ label="Broadband"
227
+ icon={
228
+ <>
229
+ <Box asChild display={{ mobile: 'none', desktop: 'block' }}>
230
+ <BroadbandMediumIcon />
231
+ </Box>
232
+ <Box asChild display={{ desktop: 'none' }}>
233
+ <BroadbandSmallIcon />
234
+ </Box>
235
+ </>
236
+ }
237
+ />
238
+ <SegmentedControlOption
239
+ value="insurance"
240
+ label="Insurance"
241
+ icon={
242
+ <>
243
+ <Box asChild display={{ mobile: 'none', desktop: 'block' }}>
244
+ <InsuranceMediumIcon />
245
+ </Box>
246
+ <Box asChild display={{ desktop: 'none' }}>
247
+ <InsuranceSmallIcon />
248
+ </Box>
249
+ </>
250
+ }
251
+ />
252
+ <SegmentedControlOption
253
+ value="cashback"
254
+ label="Cashback"
255
+ icon={
256
+ <>
257
+ <Box asChild display={{ mobile: 'none', desktop: 'block' }}>
258
+ <CashbackCardMediumIcon />
259
+ </Box>
260
+ <Box asChild display={{ desktop: 'none' }}>
261
+ <CashbackCardSmallIcon />
262
+ </Box>
263
+ </>
264
+ }
265
+ />
266
+ </SegmentedControl>
267
+ ```
268
+
269
+ ## API
270
+
271
+ This component is based on the [Base UI ToggleGroup primitive](https://base-ui.com/react/components/toggle-group)
272
+ and supports the following common props:
273
+
274
+ - Margin
275
+
276
+ | Prop | Type | Default | Description |
277
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
278
+ | `value` | `readonly string[]` | — | The pressed state of the toggle group represented by an array of the values of all pressed toggle buttons. This is the controlled counterpart of `defaultValue`. |
279
+ | `defaultValue` | `(readonly string[] & (string \| number \| readonly string[]))` | — | The pressed state of the toggle group represented by an array of the values of all pressed toggle buttons. This is the uncontrolled counterpart of `value`. |
280
+ | `onValueChange` | `((groupValue: string[], eventDetails: { reason: "none"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element \| undefined; }) => void)` | — | Callback fired when the pressed states of the toggle group changes. |
281
+ | `disabled` | `boolean` | `false` | Whether the toggle group should ignore user interaction. |
282
+ | `size` | `Responsive<"sm" \| "md">` | `sm` | Sets the height of the control. `sm` is 32px and `md` is 48px. Accepts a responsive value to display different sizes at different breakpoints. |
283
+
284
+ ### SegmentedControlOption API
285
+
286
+ This component is based on the `button` element.
287
+
288
+ | Prop | Type | Default | Description |
289
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
290
+ | `label` | `string` | — | The text label displayed inside the option. |
291
+ | `value` | `string` | — | The value that identifies this option within the SegmentedControl. |
292
+ | `pressed` | `boolean` | — | Whether the toggle button is currently pressed. This is the controlled counterpart of `defaultPressed`. |
293
+ | `defaultPressed` | `boolean` | `false` | Whether the toggle button is currently pressed. This is the uncontrolled counterpart of `pressed`. |
294
+ | `onPressedChange` | `((pressed: boolean, eventDetails: { reason: "none"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element \| undefined; }) => void)` | — | Callback fired when the pressed state is changed. |
295
+ | `icon` | `ReactNode` | — | An icon to display before the label. Use the Small icon variant for `size="sm"` and the Medium icon variant for `size="md"`. When using the responsive `size` prop, see the docs for guidance on rendering responsive icons. |
@@ -1,6 +1,6 @@
1
1
  # Hearth React
2
2
 
3
- Current version: v0.30.1
3
+ Current version: v0.31.0
4
4
 
5
5
  - [Install Hearth](#install-hearth)
6
6
  - [Import CSS](#import-css)
package/public/llms.txt CHANGED
@@ -72,7 +72,7 @@
72
72
  - [Label](llms/components/label.md): ```tsx
73
73
  - [Link](llms/components/link.md): Links are used to navigate a user to another page or website, another place on the same page, or to open a link in a new tab.
74
74
  - [List](llms/components/list.md): The `List` component is a flexible and customizable component for displaying a
75
- - [Menu](llms/components/menu.md): 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.
75
+ - [Menu](llms/components/menu.md): Use the `Menu` component to present a short list of actions or options in
76
76
  - [Modal](llms/components/modal.md): A `Modal` overlays content to request a decision or inform users of important information. When
77
77
  - [Pagination](llms/components/pagination.md): Use the `Pagination` component to help users navigate through multiple pages of content.
78
78
  - [PasswordInput](llms/components/password-input.md): `PasswordInput` is an interactive field that is used to securely collect users passwords.
@@ -81,6 +81,7 @@
81
81
  - [RadioGroup](llms/components/radio-group.md): `RadioGroup` provides an accessible way to group and control a set of `Radio`, `RadioTile` or `RadioCard` components, allowing the user to select one option from a set. The `RadioGroup` is responsible for handling the value, helper text, validation status and text, as well as determining the presentation and selection of the items in the list. Follows the [WAI-ARIA Radio Group Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/radio/) for radio groups not contained in a toolbar.
82
82
  - [SearchInput](llms/components/search-input.md): `SearchInput` allows users to enter a specific keyword or phrase and obtain results related to the context in which it is placed.
83
83
  - [SectionHeader](llms/components/section-header.md): Use the `SectionHeader` component to help define and separate content within a
84
+ - [SegmentedControl](llms/components/segmented-control.md): Use a Segmented Control to switch between alternative views of closely related content.
84
85
  - [Select](llms/components/select.md): `Select` enables users to select an option from a list of choices, and generally requires a final submit action to confirm the selection.
85
86
  - [Skeleton](llms/components/skeleton.md): `Skeleton` is a collection of components that provide visual placeholders while
86
87
  - [Spinner](llms/components/spinner.md): Using a Spinner is a common method for indicating that an asynchronous process is ongoing.