@utilitywarehouse/hearth-react-native 0.30.4 → 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 (63) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +12 -15
  3. package/CHANGELOG.md +149 -0
  4. package/build/components/Badge/Badge.js +2 -2
  5. package/build/components/Badge/Badge.props.d.ts +1 -0
  6. package/build/components/Badge/BadgeText.d.ts +1 -1
  7. package/build/components/Badge/BadgeText.js +2 -2
  8. package/build/components/Container/Container.props.d.ts +2 -2
  9. package/build/components/ExpandableCard/ExpandableCard.d.ts +1 -1
  10. package/build/components/ExpandableCard/ExpandableCard.js +13 -2
  11. package/build/components/ExpandableCard/ExpandableCard.props.d.ts +43 -23
  12. package/build/components/ExpandableCard/ExpandableCardText.js +1 -1
  13. package/build/components/ExpandableCard/ExpandableCardTrigger.d.ts +3 -3
  14. package/build/components/ExpandableCard/ExpandableCardTrigger.props.d.ts +31 -6
  15. package/build/components/ExpandableCard/ExpandableCardTriggerRoot.d.ts +1 -1
  16. package/build/components/ExpandableCard/ExpandableCardTriggerRoot.js +13 -2
  17. package/build/components/Flex/Flex.props.d.ts +2 -2
  18. package/build/components/FormField/FormField.d.ts +5 -5
  19. package/build/components/FormField/FormField.js +3 -2
  20. package/build/components/Modal/Modal.d.ts +1 -1
  21. package/build/components/Modal/Modal.js +33 -39
  22. package/build/components/Modal/Modal.props.d.ts +8 -3
  23. package/build/components/Modal/Modal.shared.types.d.ts +19 -4
  24. package/build/components/Modal/Modal.web.d.ts +1 -1
  25. package/build/components/Modal/Modal.web.js +6 -3
  26. package/build/components/NavModal/NavModal.d.ts +1 -1
  27. package/build/components/NavModal/NavModal.js +10 -7
  28. package/build/components/NavModal/NavModal.props.d.ts +4 -3
  29. package/build/components/Textarea/Textarea.d.ts +1 -1
  30. package/build/components/Textarea/Textarea.js +64 -5
  31. package/build/components/Textarea/Textarea.props.d.ts +10 -0
  32. package/build/components/Textarea/TextareaRoot.js +4 -1
  33. package/docs/changelog.mdx +21 -0
  34. package/package.json +1 -1
  35. package/src/components/Badge/Badge.props.ts +1 -0
  36. package/src/components/Badge/Badge.tsx +6 -1
  37. package/src/components/Badge/BadgeText.tsx +8 -2
  38. package/src/components/Container/Container.props.ts +10 -1
  39. package/src/components/ExpandableCard/ExpandableCard.docs.mdx +89 -37
  40. package/src/components/ExpandableCard/ExpandableCard.props.ts +51 -27
  41. package/src/components/ExpandableCard/ExpandableCard.stories.tsx +67 -17
  42. package/src/components/ExpandableCard/ExpandableCard.tsx +15 -7
  43. package/src/components/ExpandableCard/ExpandableCardText.tsx +1 -1
  44. package/src/components/ExpandableCard/ExpandableCardTrigger.props.ts +37 -7
  45. package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +36 -2
  46. package/src/components/Flex/Flex.props.ts +16 -2
  47. package/src/components/FormField/FormField.tsx +2 -1
  48. package/src/components/List/List.stories.tsx +35 -0
  49. package/src/components/Modal/Modal.docs.mdx +52 -1
  50. package/src/components/Modal/Modal.props.ts +21 -6
  51. package/src/components/Modal/Modal.shared.types.ts +23 -4
  52. package/src/components/Modal/Modal.stories.tsx +165 -1
  53. package/src/components/Modal/Modal.tsx +101 -81
  54. package/src/components/Modal/Modal.web.tsx +29 -23
  55. package/src/components/NavModal/NavModal.docs.mdx +29 -0
  56. package/src/components/NavModal/NavModal.props.ts +11 -3
  57. package/src/components/NavModal/NavModal.stories.tsx +29 -0
  58. package/src/components/NavModal/NavModal.tsx +39 -33
  59. package/src/components/Textarea/Textarea.docs.mdx +33 -1
  60. package/src/components/Textarea/Textarea.props.ts +11 -2
  61. package/src/components/Textarea/Textarea.stories.tsx +21 -1
  62. package/src/components/Textarea/Textarea.tsx +107 -3
  63. package/src/components/Textarea/TextareaRoot.tsx +6 -2
@@ -22,6 +22,7 @@ The `ExpandableCard` component is an interactive card that expands to reveal add
22
22
  - [With Leading Icon](#with-leading-icon)
23
23
  - [With `Badge`](#with-badge)
24
24
  - [With Numeric Value](#with-numeric-value)
25
+ - [With Custom Trigger Content](#with-custom-trigger-content)
25
26
  - [`CardGroup`](#cardgroup)
26
27
  - [Advanced Usage](#advanced-usage)
27
28
  - [Accessibility](#accessibility)
@@ -58,24 +59,27 @@ const MyComponent = () => (
58
59
 
59
60
  ### `ExpandableCard`
60
61
 
61
- | Prop | Type | Default | Description |
62
- | ------------------ | ----------------------------- | ------------------- | ------------------------------------------------ |
63
- | `heading` | `string` | - | The heading text displayed in the trigger |
64
- | `helperText` | `string` | - | Optional helper text displayed below the heading |
65
- | `leadingIcon` | `ComponentType` | - | Leading icon component (automatically wrapped) |
66
- | `leadingContent` | `ReactNode` | - | Leading content (icon or custom element) |
67
- | `badge` | `ReactNode` | - | Badge to display |
68
- | `badgePosition` | `'top' \| 'bottom'` | `'bottom'` | Badge position |
69
- | `numericValue` | `string \| number` | - | Numeric value to display on the right |
70
- | `expandedContent` | `ReactNode` | - | The content to show when expanded |
71
- | `expanded` | `boolean` | - | Whether the card is expanded (controlled) |
72
- | `onExpandedChange` | `(expanded: boolean) => void` | - | Callback when expanded state changes |
73
- | `duration` | `number` | `200` | Duration of expansion animation in milliseconds |
74
- | `animateOpacity` | `boolean` | `true` | Whether to animate opacity during expansion |
75
- | `disabled` | `boolean` | `false` | Whether the card is disabled |
76
- | `colorScheme` | `CardColorScheme` | - | Color scheme (inherits from Card component) |
77
- | `variant` | `CardVariant` | - | Variant (inherits from Card component) |
78
- | `testID` | `string` | `'expandable-card'` | Test ID for testing |
62
+ | Prop | Type | Default | Description |
63
+ | ------------------ | ----------------------------- | ------------------- | -------------------------------------------------------------- |
64
+ | `heading` | `string` | - | The heading text displayed in the trigger |
65
+ | `helperText` | `string` | - | Optional helper text displayed below the heading |
66
+ | `leadingIcon` | `ComponentType` | - | Leading icon component (automatically wrapped) |
67
+ | `leadingContent` | `ReactNode` | - | Leading content (icon or custom element) |
68
+ | `badge` | `ReactNode` | - | Badge to display |
69
+ | `badgePosition` | `'top' \| 'bottom'` | `'bottom'` | Badge position |
70
+ | `numericValue` | `string \| number` | - | Numeric value to display on the right |
71
+ | `triggerContent` | `ReactNode` | - | Replaces the default trigger content while keeping the chevron |
72
+ | `expandedContent` | `ReactNode` | - | The content to show when expanded |
73
+ | `expanded` | `boolean` | - | Whether the card is expanded (controlled) |
74
+ | `onExpandedChange` | `(expanded: boolean) => void` | - | Callback when expanded state changes |
75
+ | `duration` | `number` | `200` | Duration of expansion animation in milliseconds |
76
+ | `animateOpacity` | `boolean` | `true` | Whether to animate opacity during expansion |
77
+ | `disabled` | `boolean` | `false` | Whether the card is disabled |
78
+ | `colorScheme` | `CardColorScheme` | - | Color scheme (inherits from Card component) |
79
+ | `variant` | `CardVariant` | - | Variant (inherits from Card component) |
80
+ | `testID` | `string` | `'expandable-card'` | Test ID for testing |
81
+
82
+ When `triggerContent` is provided, the default trigger props such as `heading`, `helperText`, `leadingIcon`, `leadingContent`, `badge`, `badgePosition`, and `numericValue` are not available.
79
83
 
80
84
  ### `ExpandableCardGroup`
81
85
 
@@ -175,6 +179,34 @@ const MyComponent = () => (
175
179
  />
176
180
  ```
177
181
 
182
+ ### With Custom Trigger Content
183
+
184
+ Use `triggerContent` when you need to replace the standard trigger layout entirely while keeping the expandable chevron.
185
+
186
+ <Canvas of={Stories.WithCustomTriggerContent} />
187
+
188
+ ```tsx
189
+ import { ExpandableCard, BodyText } from '@utilitywarehouse/hearth-react-native';
190
+ import { View } from 'react-native';
191
+
192
+ const MyComponent = () => (
193
+ <ExpandableCard
194
+ triggerContent={
195
+ <View style={{ flex: 1, gap: 4 }}>
196
+ <BodyText weight="semibold">March bill ready</BodyText>
197
+ <BodyText color="secondary">Due on 18 April 2026</BodyText>
198
+ </View>
199
+ }
200
+ expandedContent={
201
+ <>
202
+ <BodyText>Balance: £89.50</BodyText>
203
+ <BodyText>Payment method: Direct Debit</BodyText>
204
+ </>
205
+ }
206
+ />
207
+ );
208
+ ```
209
+
178
210
  ### `CardGroup`
179
211
 
180
212
  Use `ExpandableCardGroup` to group multiple expandable cards with an optional header.
@@ -239,34 +271,52 @@ import {
239
271
  ExpandableCardTrigger,
240
272
  ExpandableCardExpandedContent,
241
273
  ExpandableCardContent,
242
- IconContainer,
274
+ ExpandableCardHelperText,
275
+ ExpandableCardText,
243
276
  BodyText,
244
277
  } from '@utilitywarehouse/hearth-react-native';
245
- import { BillMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
246
278
  import { useState } from 'react';
279
+ import { View } from 'react-native';
247
280
 
248
281
  const MyComponent = () => {
249
- const [expanded, setExpanded] = useState(false);
282
+ const [triggerContentExpanded, setTriggerContentExpanded] = useState(false);
283
+ const [customChildrenExpanded, setCustomChildrenExpanded] = useState(false);
250
284
 
251
285
  return (
252
- <ExpandableCard expanded={expanded} onExpandedChange={setExpanded}>
253
- <ExpandableCardTrigger
254
- onPress={() => setExpanded(!expanded)}
255
- heading="Custom Account Details"
256
- helperText="View your detailed account information"
257
- leadingContent={
258
- <IconContainer icon={BillMediumIcon} size="md" variant="emphasis" color="pig" />
286
+ <>
287
+ <ExpandableCard
288
+ expanded={triggerContentExpanded}
289
+ onExpandedChange={setTriggerContentExpanded}
290
+ expandedContent={<BodyText>Use `triggerContent` for a full replacement.</BodyText>}
291
+ triggerContent={
292
+ <View style={{ flex: 1, gap: 4 }}>
293
+ <BodyText weight="semibold">Use `triggerContent`</BodyText>
294
+ <BodyText color="secondary">Replace the built-in trigger layout</BodyText>
295
+ </View>
259
296
  }
260
- numericValue="£123.45"
261
- isExpanded={expanded}
262
297
  />
263
- <ExpandableCardExpandedContent isExpanded={expanded}>
264
- <BodyText>Account Number: 1234567890</BodyText>
265
- <BodyText>Sort Code: 12-34-56</BodyText>
266
- <BodyText>Balance: £123.45</BodyText>
267
- <BodyText>Last Updated: 12/11/25</BodyText>
268
- </ExpandableCardExpandedContent>
269
- </ExpandableCard>
298
+
299
+ <ExpandableCard
300
+ expanded={customChildrenExpanded}
301
+ onExpandedChange={setCustomChildrenExpanded}
302
+ >
303
+ <ExpandableCardTrigger
304
+ onPress={() => setCustomChildrenExpanded(!customChildrenExpanded)}
305
+ isExpanded={customChildrenExpanded}
306
+ showChevron
307
+ >
308
+ <ExpandableCardContent>
309
+ <ExpandableCardText>Use `children` + `showChevron`</ExpandableCardText>
310
+ <ExpandableCardHelperText>
311
+ Build the trigger yourself and keep the chevron
312
+ </ExpandableCardHelperText>
313
+ </ExpandableCardContent>
314
+ </ExpandableCardTrigger>
315
+ <ExpandableCardExpandedContent isExpanded={customChildrenExpanded}>
316
+ <BodyText>Use `children` when you want full trigger composition control.</BodyText>
317
+ </ExpandableCardExpandedContent>
318
+ </ExpandableCard>
319
+ </>
270
320
  );
271
321
  };
272
322
  ```
@@ -285,6 +335,8 @@ When using advanced composition, you have access to these components:
285
335
  - **`ExpandableCardTrailingContent`**: Container for trailing content
286
336
  - **`ExpandableCardTrailingIcon`**: Styled icon for trailing position
287
337
 
338
+ Use `triggerContent` when you want a simpler one-prop replacement on `ExpandableCard`. Use `ExpandableCardTrigger` children when you want to compose the trigger structure yourself. The built-in chevron is shown by default, and you can set `showChevron={false}` if you want to hide it.
339
+
288
340
  ## Accessibility
289
341
 
290
342
  The ExpandableCard component includes built-in accessibility support:
@@ -1,7 +1,7 @@
1
1
  import type { ComponentType, ReactNode } from 'react';
2
2
  import type CardProps from '../Card/Card.props';
3
3
 
4
- export interface ExpandableCardProps extends Omit<CardProps, 'children'> {
4
+ interface ExpandableCardSharedProps extends Omit<CardProps, 'children'> {
5
5
  /**
6
6
  * Whether the card is expanded
7
7
  */
@@ -12,6 +12,40 @@ export interface ExpandableCardProps extends Omit<CardProps, 'children'> {
12
12
  */
13
13
  onExpandedChange?: (expanded: boolean) => void;
14
14
 
15
+ /**
16
+ * The heading text displayed in the trigger
17
+ */
18
+ expandedContent?: ReactNode;
19
+
20
+ /**
21
+ * Duration of the expansion animation in milliseconds
22
+ * @default 200
23
+ */
24
+ duration?: number;
25
+
26
+ /**
27
+ * Whether to animate opacity during expansion
28
+ * @default true
29
+ */
30
+ animateOpacity?: boolean;
31
+
32
+ /**
33
+ * Whether the card is disabled
34
+ */
35
+ disabled?: boolean;
36
+
37
+ /**
38
+ * Test ID for testing
39
+ */
40
+ testID?: string;
41
+
42
+ /**
43
+ * Custom children for advanced composition
44
+ */
45
+ children?: ReactNode;
46
+ }
47
+
48
+ interface ExpandableCardDefaultTriggerProps {
15
49
  /**
16
50
  * The heading text displayed in the trigger
17
51
  */
@@ -49,36 +83,26 @@ export interface ExpandableCardProps extends Omit<CardProps, 'children'> {
49
83
  numericValue?: string | number;
50
84
 
51
85
  /**
52
- * The content to show when expanded
53
- */
54
- expandedContent?: ReactNode;
55
-
56
- /**
57
- * Duration of the expansion animation in milliseconds
58
- * @default 200
59
- */
60
- duration?: number;
61
-
62
- /**
63
- * Whether to animate opacity during expansion
64
- * @default true
65
- */
66
- animateOpacity?: boolean;
67
-
68
- /**
69
- * Whether the card is disabled
86
+ * Custom trigger content that replaces the default trigger layout.
70
87
  */
71
- disabled?: boolean;
72
-
73
- /**
74
- * Test ID for testing
75
- */
76
- testID?: string;
88
+ triggerContent?: never;
89
+ }
77
90
 
91
+ interface ExpandableCardCustomTriggerProps {
78
92
  /**
79
- * Custom children for advanced composition
93
+ * Custom trigger content that replaces the default trigger layout.
80
94
  */
81
- children?: ReactNode;
95
+ triggerContent: ReactNode;
96
+ heading?: never;
97
+ helperText?: never;
98
+ leadingIcon?: never;
99
+ leadingContent?: never;
100
+ badge?: never;
101
+ badgePosition?: never;
102
+ numericValue?: never;
82
103
  }
83
104
 
105
+ type ExpandableCardProps = ExpandableCardSharedProps &
106
+ (ExpandableCardDefaultTriggerProps | ExpandableCardCustomTriggerProps);
107
+
84
108
  export default ExpandableCardProps;
@@ -7,11 +7,15 @@ import {
7
7
  SettingsMediumIcon,
8
8
  } from '@utilitywarehouse/hearth-react-native-icons';
9
9
  import React from 'react';
10
+ import { View } from 'react-native';
10
11
  import { Badge, BodyText, IconContainer, Link } from '../../components';
11
12
  import ExpandableCard from './ExpandableCard';
13
+ import ExpandableCardContent from './ExpandableCardContent';
12
14
  import ExpandableCardExpandedContent from './ExpandableCardExpandedContent';
13
15
  import ExpandableCardGroup from './ExpandableCardGroup';
16
+ import ExpandableCardHelperText from './ExpandableCardHelperText';
14
17
  import ExpandableCardIcon from './ExpandableCardIcon';
18
+ import ExpandableCardText from './ExpandableCardText';
15
19
  import ExpandableCardTrigger from './ExpandableCardTrigger';
16
20
 
17
21
  const meta = {
@@ -161,6 +165,26 @@ export const WithNumericValue: Story = {
161
165
  ),
162
166
  };
163
167
 
168
+ export const WithCustomTriggerContent: Story = {
169
+ render: () => (
170
+ <ExpandableCard
171
+ triggerContent={
172
+ <View style={{ flex: 1, gap: 4 }}>
173
+ <BodyText weight="semibold">March bill ready</BodyText>
174
+ <BodyText color="secondary">Due on 18 April 2026</BodyText>
175
+ </View>
176
+ }
177
+ expandedContent={
178
+ <>
179
+ <BodyText>Balance: £89.50</BodyText>
180
+ <BodyText>Payment method: Direct Debit</BodyText>
181
+ </>
182
+ }
183
+ style={{ width: 350 }}
184
+ />
185
+ ),
186
+ };
187
+
164
188
  export const Disabled: Story = {
165
189
  render: () => (
166
190
  <ExpandableCard
@@ -300,27 +324,53 @@ export const ColorSchemes: Story = {
300
324
 
301
325
  export const AdvancedComposition: Story = {
302
326
  render: () => {
303
- const [expanded, setExpanded] = React.useState(false);
327
+ const [triggerContentExpanded, setTriggerContentExpanded] = React.useState(false);
328
+ const [customChildrenExpanded, setCustomChildrenExpanded] = React.useState(false);
304
329
 
305
330
  return (
306
- <ExpandableCard expanded={expanded} onExpandedChange={setExpanded}>
307
- <ExpandableCardTrigger
308
- onPress={() => setExpanded(!expanded)}
309
- heading="Custom Account Details"
310
- helperText="View your detailed account information"
311
- leadingContent={
312
- <IconContainer icon={BillMediumIcon} size="md" variant="emphasis" color="pig" />
331
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: 350 }}>
332
+ <ExpandableCard
333
+ expanded={triggerContentExpanded}
334
+ onExpandedChange={setTriggerContentExpanded}
335
+ expandedContent={
336
+ <>
337
+ <BodyText>Account Number: 1234567890</BodyText>
338
+ <BodyText>Sort Code: 12-34-56</BodyText>
339
+ <BodyText>Balance: £123.45</BodyText>
340
+ </>
341
+ }
342
+ triggerContent={
343
+ <View style={{ flex: 1, gap: 4 }}>
344
+ <BodyText weight="semibold">Use `triggerContent`</BodyText>
345
+ <BodyText color="secondary">Replace the built-in trigger layout</BodyText>
346
+ </View>
313
347
  }
314
- numericValue="£123.45"
315
- isExpanded={expanded}
316
348
  />
317
- <ExpandableCardExpandedContent isExpanded={expanded}>
318
- <BodyText>Account Number: 1234567890</BodyText>
319
- <BodyText>Sort Code: 12-34-56</BodyText>
320
- <BodyText>Balance: £123.45</BodyText>
321
- <BodyText>Last Updated: 12/11/25</BodyText>
322
- </ExpandableCardExpandedContent>
323
- </ExpandableCard>
349
+
350
+ <ExpandableCard
351
+ expanded={customChildrenExpanded}
352
+ onExpandedChange={setCustomChildrenExpanded}
353
+ >
354
+ <ExpandableCardTrigger
355
+ onPress={() => setCustomChildrenExpanded(!customChildrenExpanded)}
356
+ isExpanded={customChildrenExpanded}
357
+ showChevron
358
+ >
359
+ <ExpandableCardContent>
360
+ <ExpandableCardText>Use `children` + `showChevron`</ExpandableCardText>
361
+ <ExpandableCardHelperText>
362
+ Build the trigger yourself and keep the chevron
363
+ </ExpandableCardHelperText>
364
+ </ExpandableCardContent>
365
+ </ExpandableCardTrigger>
366
+ <ExpandableCardExpandedContent isExpanded={customChildrenExpanded}>
367
+ <BodyText>Account Number: 1234567890</BodyText>
368
+ <BodyText>Sort Code: 12-34-56</BodyText>
369
+ <BodyText>Balance: £123.45</BodyText>
370
+ <BodyText>Last Updated: 12/11/25</BodyText>
371
+ </ExpandableCardExpandedContent>
372
+ </ExpandableCard>
373
+ </div>
324
374
  );
325
375
  },
326
376
  };
@@ -14,6 +14,7 @@ const ExpandableCard = ({
14
14
  badge,
15
15
  badgePosition = 'bottom',
16
16
  numericValue,
17
+ triggerContent,
17
18
  expandedContent,
18
19
  duration = 200,
19
20
  animateOpacity = true,
@@ -39,18 +40,25 @@ const ExpandableCard = ({
39
40
  onExpandedChange?.(newExpanded);
40
41
  };
41
42
 
43
+ const triggerProps =
44
+ triggerContent !== undefined
45
+ ? { triggerContent }
46
+ : {
47
+ heading,
48
+ helperText,
49
+ leadingIcon,
50
+ leadingContent,
51
+ badge,
52
+ badgePosition,
53
+ numericValue,
54
+ };
55
+
42
56
  const renderDefaultContent = () => (
43
57
  <>
44
58
  <ExpandableCardTrigger
45
59
  onPress={handlePress}
46
60
  disabled={disabled}
47
- heading={heading}
48
- helperText={helperText}
49
- leadingIcon={leadingIcon}
50
- leadingContent={leadingContent}
51
- badge={badge}
52
- badgePosition={badgePosition}
53
- numericValue={numericValue}
61
+ {...triggerProps}
54
62
  isExpanded={isExpanded}
55
63
  testID={`${testID}-trigger`}
56
64
  />
@@ -3,7 +3,7 @@ import { BodyText } from '../BodyText';
3
3
 
4
4
  const ExpandableCardText = ({ children, ...props }: TextProps) => {
5
5
  return (
6
- <BodyText size="lg" {...props}>
6
+ <BodyText size="md" weight="semibold" {...props}>
7
7
  {children}
8
8
  </BodyText>
9
9
  );
@@ -1,7 +1,25 @@
1
1
  import type { ComponentType, ReactNode } from 'react';
2
2
  import type { PressableProps } from 'react-native';
3
3
 
4
- export interface ExpandableCardTriggerProps extends Omit<PressableProps, 'children'> {
4
+ interface ExpandableCardTriggerSharedProps extends Omit<PressableProps, 'children'> {
5
+ /**
6
+ * Whether the expandable card is expanded
7
+ */
8
+ isExpanded: boolean;
9
+ /**
10
+ * Whether to show the chevron when providing custom children.
11
+ * @default true
12
+ */
13
+ showChevron?: boolean;
14
+ /**
15
+ * Whether the trigger is disabled
16
+ */
17
+ disabled?: boolean;
18
+ /* Optional children */
19
+ children?: ReactNode;
20
+ }
21
+
22
+ interface ExpandableCardTriggerDefaultContentProps {
5
23
  /**
6
24
  * The main heading text
7
25
  */
@@ -31,16 +49,28 @@ export interface ExpandableCardTriggerProps extends Omit<PressableProps, 'childr
31
49
  * Optional numeric value to display
32
50
  */
33
51
  numericValue?: string | number;
52
+
34
53
  /**
35
- * Whether the expandable card is expanded
54
+ * Custom trigger content that replaces the default trigger layout.
36
55
  */
37
- isExpanded: boolean;
56
+ triggerContent?: never;
57
+ }
58
+
59
+ interface ExpandableCardTriggerCustomContentProps {
38
60
  /**
39
- * Whether the trigger is disabled
61
+ * Custom trigger content that replaces the default trigger layout.
40
62
  */
41
- disabled?: boolean;
42
- /* Optional children */
43
- children?: ReactNode;
63
+ triggerContent: ReactNode;
64
+ heading?: never;
65
+ helperText?: never;
66
+ leadingIcon?: never;
67
+ leadingContent?: never;
68
+ badge?: never;
69
+ badgePosition?: never;
70
+ numericValue?: never;
44
71
  }
45
72
 
73
+ type ExpandableCardTriggerProps = ExpandableCardTriggerSharedProps &
74
+ (ExpandableCardTriggerDefaultContentProps | ExpandableCardTriggerCustomContentProps);
75
+
46
76
  export default ExpandableCardTriggerProps;
@@ -22,7 +22,9 @@ const ExpandableCardTriggerRoot = ({
22
22
  badge,
23
23
  badgePosition = 'bottom',
24
24
  numericValue,
25
+ triggerContent,
25
26
  isExpanded,
27
+ showChevron = true,
26
28
  disabled,
27
29
  states,
28
30
  children,
@@ -53,6 +55,8 @@ const ExpandableCardTriggerRoot = ({
53
55
  return null;
54
56
  };
55
57
 
58
+ const defaultAccessibilityLabel = [heading, helperText].filter(Boolean).join(', ');
59
+
56
60
  const renderDefaultContent = () => (
57
61
  <>
58
62
  {renderLeadingContent()}
@@ -73,6 +77,36 @@ const ExpandableCardTriggerRoot = ({
73
77
  </>
74
78
  );
75
79
 
80
+ const renderChevron = () => (
81
+ <ExpandableCardTrailingContent style={styles.chevron}>
82
+ <ExpandableCardTrailingIcon as={isExpanded ? ChevronUpSmallIcon : ChevronDownSmallIcon} />
83
+ </ExpandableCardTrailingContent>
84
+ );
85
+
86
+ const renderCustomTriggerContent = () => (
87
+ <>
88
+ {triggerContent}
89
+ {showChevron ? renderChevron() : null}
90
+ </>
91
+ );
92
+
93
+ const renderChildrenContent = () => (
94
+ <>
95
+ {children}
96
+ {showChevron ? renderChevron() : null}
97
+ </>
98
+ );
99
+
100
+ let triggerBody = renderDefaultContent();
101
+
102
+ if (triggerContent !== undefined) {
103
+ triggerBody = renderCustomTriggerContent();
104
+ }
105
+
106
+ if (children) {
107
+ triggerBody = renderChildrenContent();
108
+ }
109
+
76
110
  return (
77
111
  <Pressable
78
112
  {...props}
@@ -81,9 +115,9 @@ const ExpandableCardTriggerRoot = ({
81
115
  disabled={disabled}
82
116
  accessibilityRole="button"
83
117
  accessibilityState={{ expanded: isExpanded, disabled }}
84
- accessibilityLabel={`${heading}${helperText ? `, ${helperText}` : ''}`}
118
+ accessibilityLabel={props.accessibilityLabel || defaultAccessibilityLabel || undefined}
85
119
  >
86
- {children || renderDefaultContent()}
120
+ {triggerBody}
87
121
  </Pressable>
88
122
  );
89
123
  };
@@ -1,7 +1,21 @@
1
1
  import type { FlexAlignType, ViewProps, ViewStyle } from 'react-native';
2
- import { FlexLayoutProps, GapProps, SpacingValues } from '../../types';
2
+ import {
3
+ DisplayProps,
4
+ FlexLayoutProps,
5
+ GapProps,
6
+ MarginProps,
7
+ PaddingProps,
8
+ SpacingValues,
9
+ } from '../../types';
3
10
 
4
- interface FlexProps extends ViewProps, FlexLayoutProps, GapProps {
11
+ interface FlexProps
12
+ extends
13
+ ViewProps,
14
+ MarginProps,
15
+ PaddingProps,
16
+ FlexLayoutProps,
17
+ GapProps,
18
+ Omit<DisplayProps, 'direction'> {
5
19
  direction?: ViewStyle['flexDirection'];
6
20
  align?: FlexAlignType;
7
21
  justify?: ViewStyle['justifyContent'];
@@ -1,6 +1,7 @@
1
1
  import { createFormControl } from '@gluestack-ui/form-control';
2
2
  import { useMemo, useState } from 'react';
3
3
  import { View } from 'react-native';
4
+ import { BodyText } from '../BodyText';
4
5
  import { HelperIcon, HelperText } from '../Helper';
5
6
  import { FormFieldContext } from './FormField.context';
6
7
  import FormFieldProps from './FormField.props';
@@ -94,7 +95,7 @@ const FormField = ({
94
95
  accessibilityElementsHidden={shouldHandleAccessibility}
95
96
  >
96
97
  {label}
97
- {!required ? ` (Optional)` : ''}
98
+ {!required ? <BodyText weight="regular"> (Optional)</BodyText> : ''}
98
99
  </FormFieldLabelText>
99
100
  )}
100
101
  {!!helperText && (
@@ -10,6 +10,7 @@ import {
10
10
  UserMediumIcon,
11
11
  } from '@utilitywarehouse/hearth-react-native-icons';
12
12
  import { useState } from 'react';
13
+ import { FlatList } from 'react-native';
13
14
  import { List, ListAction, ListItem, ListItemIcon, ListItemTrailingIcon } from '.';
14
15
  import { VariantTitle } from '../../../docs/components';
15
16
  import { Badge } from '../Badge';
@@ -398,6 +399,40 @@ export const Loading: Story = {
398
399
  ),
399
400
  };
400
401
 
402
+ export const WithFlatList: Story = {
403
+ parameters: {
404
+ controls: { include: [] },
405
+ },
406
+ render: () => {
407
+ const listData = Array.from({ length: 200 }).map((_, index) => ({
408
+ id: index.toString(),
409
+ heading: `List Item ${index + 1}`,
410
+ helperText: `Supporting Text ${index + 1}`,
411
+ }));
412
+
413
+ return (
414
+ <List
415
+ container="subtleWhite"
416
+ heading="FlatList Example"
417
+ helperText="This list is rendered using FlatList for performance"
418
+ >
419
+ <FlatList
420
+ data={listData}
421
+ keyExtractor={item => item.id}
422
+ renderItem={({ item }) => (
423
+ <ListItem
424
+ heading={item.heading}
425
+ helperText={item.helperText}
426
+ leadingContent={<ListItemIcon as={SettingsMediumIcon} />}
427
+ onPress={() => console.log(`${item.heading} pressed`)}
428
+ />
429
+ )}
430
+ />
431
+ </List>
432
+ );
433
+ },
434
+ };
435
+
401
436
  export const KitchenSink: Story = {
402
437
  parameters: {
403
438
  controls: { include: [] },