@utilitywarehouse/hearth-react-native 0.16.2 → 0.17.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 +14 -14
  3. package/CHANGELOG.md +132 -0
  4. package/build/components/Card/CardAction/CardActionRoot.js +12 -2
  5. package/build/components/Card/CardActions.context.d.ts +6 -0
  6. package/build/components/Card/CardActions.context.js +5 -0
  7. package/build/components/Card/CardActions.d.ts +7 -0
  8. package/build/components/Card/CardActions.js +29 -0
  9. package/build/components/Card/CardRoot.js +16 -104
  10. package/build/components/Card/helpers.d.ts +8 -0
  11. package/build/components/Card/helpers.js +146 -0
  12. package/build/components/Card/index.d.ts +2 -0
  13. package/build/components/Card/index.js +2 -0
  14. package/build/components/ExpandableCard/ExpandableCardGroup.d.ts +1 -1
  15. package/build/components/ExpandableCard/ExpandableCardGroup.js +2 -2
  16. package/build/components/ExpandableCard/ExpandableCardGroup.props.d.ts +4 -0
  17. package/build/components/Input/Input.js +4 -3
  18. package/build/components/Input/Input.props.d.ts +9 -0
  19. package/build/components/List/List.context.d.ts +4 -2
  20. package/build/components/List/List.context.js +0 -2
  21. package/build/components/List/List.d.ts +1 -1
  22. package/build/components/List/List.js +25 -38
  23. package/build/components/List/List.props.d.ts +1 -0
  24. package/build/components/List/ListAction/ListAction.js +24 -7
  25. package/build/components/List/ListAction/ListAction.props.d.ts +1 -0
  26. package/build/components/List/ListItem/ListItemRoot.js +12 -4
  27. package/build/utils/isThemedImageProps.d.ts +1 -1
  28. package/package.json +3 -3
  29. package/src/components/Card/Card.docs.mdx +224 -66
  30. package/src/components/Card/Card.stories.tsx +29 -25
  31. package/src/components/Card/CardAction/CardAction.stories.tsx +239 -93
  32. package/src/components/Card/CardAction/CardActionRoot.tsx +15 -2
  33. package/src/components/Card/CardActions.context.ts +12 -0
  34. package/src/components/Card/CardActions.tsx +40 -0
  35. package/src/components/Card/CardRoot.tsx +27 -132
  36. package/src/components/Card/helpers.tsx +195 -0
  37. package/src/components/Card/index.ts +2 -0
  38. package/src/components/ExpandableCard/ExpandableCard.figma.tsx +33 -38
  39. package/src/components/ExpandableCard/ExpandableCardGroup.figma.tsx +34 -17
  40. package/src/components/ExpandableCard/ExpandableCardGroup.props.ts +5 -0
  41. package/src/components/ExpandableCard/ExpandableCardGroup.tsx +2 -0
  42. package/src/components/HighlightBanner/HighlightBanner.figma.tsx +46 -0
  43. package/src/components/IconButton/IconButton.figma.tsx +20 -30
  44. package/src/components/IconContainer/IconContainer.figma.tsx +7 -13
  45. package/src/components/IndicatorIconButton/IndicatorIconButton.figma.tsx +16 -0
  46. package/src/components/Input/Input.docs.mdx +55 -15
  47. package/src/components/Input/Input.figma.tsx +106 -40
  48. package/src/components/Input/Input.props.ts +9 -0
  49. package/src/components/Input/Input.tsx +21 -0
  50. package/src/components/Link/Link.figma.tsx +31 -38
  51. package/src/components/List/List.context.ts +2 -4
  52. package/src/components/List/List.docs.mdx +10 -5
  53. package/src/components/List/List.figma.tsx +42 -28
  54. package/src/components/List/List.props.ts +1 -0
  55. package/src/components/List/List.stories.tsx +43 -0
  56. package/src/components/List/List.tsx +38 -51
  57. package/src/components/List/ListAction/ListAction.figma.tsx +5 -13
  58. package/src/components/List/ListAction/ListAction.props.ts +1 -0
  59. package/src/components/List/ListAction/ListAction.tsx +40 -10
  60. package/src/components/List/ListItem/ListItem.figma.tsx +43 -27
  61. package/src/components/List/ListItem/ListItemRoot.tsx +15 -4
  62. package/src/utils/isThemedImageProps.ts +1 -1
  63. package/src/components/InlineLink/InlineLink.figma.tsx +0 -33
@@ -172,29 +172,38 @@ You can use the `CardAction` component within a `Card` to create actionable item
172
172
  <Canvas of={Stories.WithActions} />
173
173
 
174
174
  ```jsx
175
- import { Card, CardAction, BodyText, Heading, Flex } from '@utilitywarehouse/hearth-react-native';
175
+ import {
176
+ Card,
177
+ CardAction,
178
+ CardActions,
179
+ BodyText,
180
+ Heading,
181
+ Flex,
182
+ } from '@utilitywarehouse/hearth-react-native';
176
183
 
177
184
  const MyComponent = () => (
178
185
  <Card variant="emphasis">
179
186
  <BodyText>Content</BodyText>
180
- <CardAction
181
- onPress={() => console.log('Card action pressed')}
182
- heading="Card Action Head"
183
- helperText="Some helper text"
184
- leadingIcon={BellMediumIcon}
185
- />
186
- <CardAction
187
- onPress={() => console.log('Card action pressed')}
188
- heading="Card Action Head"
189
- leadingIcon={BellMediumIcon}
190
- />
191
- <CardAction
192
- onPress={() => console.log('Card action pressed')}
193
- heading="Card Action Head"
194
- helperText="Testing"
195
- leadingIcon={BellMediumIcon}
196
- iconContainer={false}
197
- />
187
+ <CardActions>
188
+ <CardAction
189
+ onPress={() => console.log('Card action pressed')}
190
+ heading="Card Action Head"
191
+ helperText="Some helper text"
192
+ leadingIcon={BellMediumIcon}
193
+ />
194
+ <CardAction
195
+ onPress={() => console.log('Card action pressed')}
196
+ heading="Card Action Head"
197
+ leadingIcon={BellMediumIcon}
198
+ />
199
+ <CardAction
200
+ onPress={() => console.log('Card action pressed')}
201
+ heading="Card Action Head"
202
+ helperText="Testing"
203
+ leadingIcon={BellMediumIcon}
204
+ iconContainer={false}
205
+ />
206
+ </CardActions>
198
207
  </Card>
199
208
  );
200
209
  ```
@@ -209,41 +218,47 @@ const MyComponent = () => (
209
218
  The badge can be positioned in three different locations:
210
219
 
211
220
  ```tsx
212
- import { Card, CardAction, Badge } from '@utilitywarehouse/hearth-react-native';
221
+ import { Card, CardAction, CardActions, Badge } from '@utilitywarehouse/hearth-react-native';
213
222
 
214
223
  const MyComponent = () => (
215
224
  <>
216
225
  {/* Badge at bottom (default) */}
217
226
  <Card>
218
- <CardAction
219
- heading="Action"
220
- helperText="Helper text"
221
- badge={<Badge text="New" />}
222
- badgePosition="bottom"
223
- onPress={() => console.log('pressed')}
224
- />
227
+ <CardActions>
228
+ <CardAction
229
+ heading="Action"
230
+ helperText="Helper text"
231
+ badge={<Badge text="New" />}
232
+ badgePosition="bottom"
233
+ onPress={() => console.log('pressed')}
234
+ />
235
+ </CardActions>
225
236
  </Card>
226
237
 
227
238
  {/* Badge in middle (between heading and helper text) */}
228
239
  <Card>
229
- <CardAction
230
- heading="Action"
231
- helperText="Helper text"
232
- badge={<Badge text="New" />}
233
- badgePosition="middle"
234
- onPress={() => console.log('pressed')}
235
- />
240
+ <CardActions>
241
+ <CardAction
242
+ heading="Action"
243
+ helperText="Helper text"
244
+ badge={<Badge text="New" />}
245
+ badgePosition="middle"
246
+ onPress={() => console.log('pressed')}
247
+ />
248
+ </CardActions>
236
249
  </Card>
237
250
 
238
251
  {/* Badge on the right */}
239
252
  <Card>
240
- <CardAction
241
- heading="Action"
242
- helperText="Helper text"
243
- badge={<Badge text="New" />}
244
- badgePosition="right"
245
- onPress={() => console.log('pressed')}
246
- />
253
+ <CardActions>
254
+ <CardAction
255
+ heading="Action"
256
+ helperText="Helper text"
257
+ badge={<Badge text="New" />}
258
+ badgePosition="right"
259
+ onPress={() => console.log('pressed')}
260
+ />
261
+ </CardActions>
247
262
  </Card>
248
263
  </>
249
264
  );
@@ -252,26 +267,30 @@ const MyComponent = () => (
252
267
  #### `CardAction` Size Variants
253
268
 
254
269
  ```tsx
255
- import { Card, CardAction } from '@utilitywarehouse/hearth-react-native';
270
+ import { Card, CardAction, CardActions } from '@utilitywarehouse/hearth-react-native';
256
271
 
257
272
  const MyComponent = () => (
258
273
  <>
259
274
  <Card>
260
- <CardAction
261
- heading="Medium heading"
262
- helperText="Default md size"
263
- size="md"
264
- onPress={() => console.log('pressed')}
265
- />
275
+ <CardActions>
276
+ <CardAction
277
+ heading="Medium heading"
278
+ helperText="Default md size"
279
+ size="md"
280
+ onPress={() => console.log('pressed')}
281
+ />
282
+ </CardActions>
266
283
  </Card>
267
284
 
268
285
  <Card>
269
- <CardAction
270
- heading="Large heading"
271
- helperText="Larger lg size"
272
- size="lg"
273
- onPress={() => console.log('pressed')}
274
- />
286
+ <CardActions>
287
+ <CardAction
288
+ heading="Large heading"
289
+ helperText="Larger lg size"
290
+ size="lg"
291
+ onPress={() => console.log('pressed')}
292
+ />
293
+ </CardActions>
275
294
  </Card>
276
295
  </>
277
296
  );
@@ -285,6 +304,7 @@ For more complex layouts, you can use the component parts directly:
285
304
  import {
286
305
  Card,
287
306
  CardAction,
307
+ CardActions,
288
308
  CardActionLeadingContent,
289
309
  CardActionContent,
290
310
  CardActionText,
@@ -300,22 +320,160 @@ import {
300
320
 
301
321
  const MyComponent = () => (
302
322
  <Card>
303
- <CardAction onPress={() => console.log('pressed')}>
304
- <CardActionLeadingContent>
305
- <CardActionIcon as={ElectricityMediumIcon} />
306
- </CardActionLeadingContent>
307
- <CardActionContent>
308
- <CardActionText>Custom Layout</CardActionText>
309
- <CardActionHelperText>With complete control</CardActionHelperText>
310
- </CardActionContent>
311
- <CardActionTrailingContent>
312
- <CardActionTrailingIcon as={ChevronRightSmallIcon} />
313
- </CardActionTrailingContent>
314
- </CardAction>
323
+ <CardActions>
324
+ <CardAction onPress={() => console.log('pressed')}>
325
+ <CardActionLeadingContent>
326
+ <CardActionIcon as={ElectricityMediumIcon} />
327
+ </CardActionLeadingContent>
328
+ <CardActionContent>
329
+ <CardActionText>Custom Layout</CardActionText>
330
+ <CardActionHelperText>With complete control</CardActionHelperText>
331
+ </CardActionContent>
332
+ <CardActionTrailingContent>
333
+ <CardActionTrailingIcon as={ChevronRightSmallIcon} />
334
+ </CardActionTrailingContent>
335
+ </CardAction>
336
+ </CardActions>
337
+ </Card>
338
+ );
339
+ ```
340
+
341
+ #### `CardAction` Usage Patterns and Best Practices
342
+
343
+ Understanding how `Card` detects and renders `CardAction` components will help you structure your code correctly.
344
+
345
+ ##### Using `CardActions`
346
+
347
+ `CardAction` components must be wrapped in a `CardActions` container. Place `CardActions` as a
348
+ direct child of `Card` to give the component a clear boundary for actions and remove heuristics.
349
+
350
+ ```tsx
351
+ import { Card, CardAction, CardActions, BodyText } from '@utilitywarehouse/hearth-react-native';
352
+
353
+ const MyComponent = () => (
354
+ <Card>
355
+ <BodyText>Card content here</BodyText>
356
+ <CardActions>
357
+ <CardAction heading="First Action" onPress={() => {}} />
358
+ <CardAction heading="Second Action" onPress={() => {}} />
359
+ </CardActions>
360
+ </Card>
361
+ );
362
+ ```
363
+
364
+ **Using `CardContent` wrapper (For complex mixed content):**
365
+
366
+ ```tsx
367
+ import {
368
+ Card,
369
+ CardContent,
370
+ CardAction,
371
+ CardActions,
372
+ BodyText,
373
+ Heading,
374
+ } from '@utilitywarehouse/hearth-react-native';
375
+
376
+ const MyComponent = () => (
377
+ <Card>
378
+ <CardContent>
379
+ <Heading>Title</Heading>
380
+ <BodyText>Complex content that shouldn't be auto-wrapped</BodyText>
381
+ </CardContent>
382
+ <CardActions>
383
+ <CardAction heading="Action 1" onPress={() => {}} />
384
+ <CardAction heading="Action 2" onPress={() => {}} />
385
+ </CardActions>
315
386
  </Card>
316
387
  );
317
388
  ```
318
389
 
390
+ ##### How Card Detects Actions
391
+
392
+ The `Card` component determines whether it contains only actions or mixed content using the following rules:
393
+
394
+ - **Type comparison**: Directly checks if children include a `CardActions` container
395
+ - **Action boundary**: Only `CardActions` is treated as the action block
396
+ - **Automatic wrapping**: When mixing content and actions, non-action content is automatically wrapped in `CardContent`
397
+
398
+ ##### When to Use `CardContent`
399
+
400
+ Use the explicit `CardContent` wrapper when:
401
+
402
+ 1. **Complex content structure**: You have multiple components and want precise control over what gets wrapped
403
+ 2. **Avoiding auto-wrap heuristics**: The automatic detection isn't working as expected for your use case
404
+ 3. **Nested components**: You have deeply nested content structures that might confuse the detection
405
+
406
+ ```tsx
407
+ // Scenario: Complex nested structure with custom wrappers
408
+ const MyComponent = () => (
409
+ <Card>
410
+ <CardContent>
411
+ <CustomHeader />
412
+ <CustomBody />
413
+ <CustomFooter />
414
+ </CardContent>
415
+ <CardAction heading="Learn More" onPress={() => {}} />
416
+ </Card>
417
+ );
418
+ ```
419
+
420
+ ##### Mapped Actions Best Practices
421
+
422
+ When mapping over data to create `CardAction` components:
423
+
424
+ 1. **Wrap actions in `CardActions`** for better organisation:
425
+
426
+ ```tsx
427
+ const ActionItem = ({ item }) => (
428
+ <CardAction
429
+ heading={item.label}
430
+ helperText={item.description}
431
+ onPress={() => handlePress(item)}
432
+ />
433
+ );
434
+
435
+ <Card>
436
+ <CardActions>
437
+ {actions.map(action => (
438
+ <ActionItem key={action.id} item={action} />
439
+ ))}
440
+ </CardActions>
441
+ </Card>;
442
+ ```
443
+
444
+ 2. **Keep action items simple** - avoid adding extra content alongside `CardAction` items:
445
+
446
+ ```tsx
447
+ // ✅ Good: Simple action item
448
+ const ActionItem = ({ label }) => <CardAction heading={label} onPress={() => {}} />;
449
+
450
+ // ❌ Avoid: Adding extra content inside CardActions
451
+ const ActionItem = ({ label }) => (
452
+ <View>
453
+ <Text>Extra content</Text>
454
+ <CardAction heading={label} onPress={() => {}} />
455
+ </View>
456
+ );
457
+ ```
458
+
459
+ 3. **Mix mapped actions with direct actions when needed**:
460
+
461
+ ```tsx
462
+ <Card>
463
+ <BodyText>Content</BodyText>
464
+ <CardActions>
465
+ {dynamicActions.map(action => (
466
+ <CardAction key={action.id} {...action} />
467
+ ))}
468
+ <CardAction heading="Static Action" onPress={() => {}} />
469
+ </CardActions>
470
+ </Card>
471
+ ```
472
+
473
+ ##### First Action Styling
474
+
475
+ The first rendered `CardAction` in a card automatically has no top border. This applies even when actions are wrapped in custom components, and follows render order to maintain visual consistency.
476
+
319
477
  #### `CardAction` Component Parts
320
478
 
321
479
  - `CardAction` - Main component wrapper
@@ -1,6 +1,6 @@
1
1
  import { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { BellMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
3
- import { Card, CardAction, CardPressHandler } from '.';
3
+ import { Card, CardAction, CardActions, CardPressHandler } from '.';
4
4
  import { VariantTitle } from '../../../docs/components';
5
5
  import { BodyText } from '../BodyText';
6
6
  import { Button } from '../Button';
@@ -79,24 +79,26 @@ export const WithActions: Story = {
79
79
  return (
80
80
  <Card {...props} flexDirection="column" alignItems="stretch" space="md" variant="emphasis">
81
81
  <BodyText>{children as string}</BodyText>
82
- <CardAction
83
- onPress={() => console.log('Card action pressed')}
84
- heading="Card Action Head"
85
- helperText="Some helper text"
86
- leadingIcon={BellMediumIcon}
87
- />
88
- <CardAction
89
- onPress={() => console.log('Card action pressed')}
90
- heading="Card Action Head"
91
- leadingIcon={BellMediumIcon}
92
- />
93
- <CardAction
94
- onPress={() => console.log('Card action pressed')}
95
- heading="Card Action Head"
96
- helperText="Testing"
97
- leadingIcon={BellMediumIcon}
98
- iconContainer={false}
99
- />
82
+ <CardActions>
83
+ <CardAction
84
+ onPress={() => console.log('Card action pressed')}
85
+ heading="Card Action Head"
86
+ helperText="Some helper text"
87
+ leadingIcon={BellMediumIcon}
88
+ />
89
+ <CardAction
90
+ onPress={() => console.log('Card action pressed')}
91
+ heading="Card Action Head"
92
+ leadingIcon={BellMediumIcon}
93
+ />
94
+ <CardAction
95
+ onPress={() => console.log('Card action pressed')}
96
+ heading="Card Action Head"
97
+ helperText="Testing"
98
+ leadingIcon={BellMediumIcon}
99
+ iconContainer={false}
100
+ />
101
+ </CardActions>
100
102
  </Card>
101
103
  );
102
104
  },
@@ -109,12 +111,14 @@ export const WithOnlyAction: Story = {
109
111
  render: ({ ...props }) => {
110
112
  return (
111
113
  <Card {...props} flexDirection="column" alignItems="stretch" space="md">
112
- <CardAction
113
- onPress={() => console.log('Card action pressed')}
114
- heading="Card Action Head"
115
- helperText="Some helper text"
116
- leadingIcon={BellMediumIcon}
117
- />
114
+ <CardActions>
115
+ <CardAction
116
+ onPress={() => console.log('Card action pressed')}
117
+ heading="Card Action Head"
118
+ helperText="Some helper text"
119
+ leadingIcon={BellMediumIcon}
120
+ />
121
+ </CardActions>
118
122
  </Card>
119
123
  );
120
124
  },