@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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +14 -14
- package/CHANGELOG.md +132 -0
- package/build/components/Card/CardAction/CardActionRoot.js +12 -2
- package/build/components/Card/CardActions.context.d.ts +6 -0
- package/build/components/Card/CardActions.context.js +5 -0
- package/build/components/Card/CardActions.d.ts +7 -0
- package/build/components/Card/CardActions.js +29 -0
- package/build/components/Card/CardRoot.js +16 -104
- package/build/components/Card/helpers.d.ts +8 -0
- package/build/components/Card/helpers.js +146 -0
- package/build/components/Card/index.d.ts +2 -0
- package/build/components/Card/index.js +2 -0
- package/build/components/ExpandableCard/ExpandableCardGroup.d.ts +1 -1
- package/build/components/ExpandableCard/ExpandableCardGroup.js +2 -2
- package/build/components/ExpandableCard/ExpandableCardGroup.props.d.ts +4 -0
- package/build/components/Input/Input.js +4 -3
- package/build/components/Input/Input.props.d.ts +9 -0
- package/build/components/List/List.context.d.ts +4 -2
- package/build/components/List/List.context.js +0 -2
- package/build/components/List/List.d.ts +1 -1
- package/build/components/List/List.js +25 -38
- package/build/components/List/List.props.d.ts +1 -0
- package/build/components/List/ListAction/ListAction.js +24 -7
- package/build/components/List/ListAction/ListAction.props.d.ts +1 -0
- package/build/components/List/ListItem/ListItemRoot.js +12 -4
- package/build/utils/isThemedImageProps.d.ts +1 -1
- package/package.json +3 -3
- package/src/components/Card/Card.docs.mdx +224 -66
- package/src/components/Card/Card.stories.tsx +29 -25
- package/src/components/Card/CardAction/CardAction.stories.tsx +239 -93
- package/src/components/Card/CardAction/CardActionRoot.tsx +15 -2
- package/src/components/Card/CardActions.context.ts +12 -0
- package/src/components/Card/CardActions.tsx +40 -0
- package/src/components/Card/CardRoot.tsx +27 -132
- package/src/components/Card/helpers.tsx +195 -0
- package/src/components/Card/index.ts +2 -0
- package/src/components/ExpandableCard/ExpandableCard.figma.tsx +33 -38
- package/src/components/ExpandableCard/ExpandableCardGroup.figma.tsx +34 -17
- package/src/components/ExpandableCard/ExpandableCardGroup.props.ts +5 -0
- package/src/components/ExpandableCard/ExpandableCardGroup.tsx +2 -0
- package/src/components/HighlightBanner/HighlightBanner.figma.tsx +46 -0
- package/src/components/IconButton/IconButton.figma.tsx +20 -30
- package/src/components/IconContainer/IconContainer.figma.tsx +7 -13
- package/src/components/IndicatorIconButton/IndicatorIconButton.figma.tsx +16 -0
- package/src/components/Input/Input.docs.mdx +55 -15
- package/src/components/Input/Input.figma.tsx +106 -40
- package/src/components/Input/Input.props.ts +9 -0
- package/src/components/Input/Input.tsx +21 -0
- package/src/components/Link/Link.figma.tsx +31 -38
- package/src/components/List/List.context.ts +2 -4
- package/src/components/List/List.docs.mdx +10 -5
- package/src/components/List/List.figma.tsx +42 -28
- package/src/components/List/List.props.ts +1 -0
- package/src/components/List/List.stories.tsx +43 -0
- package/src/components/List/List.tsx +38 -51
- package/src/components/List/ListAction/ListAction.figma.tsx +5 -13
- package/src/components/List/ListAction/ListAction.props.ts +1 -0
- package/src/components/List/ListAction/ListAction.tsx +40 -10
- package/src/components/List/ListItem/ListItem.figma.tsx +43 -27
- package/src/components/List/ListItem/ListItemRoot.tsx +15 -4
- package/src/utils/isThemedImageProps.ts +1 -1
- 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 {
|
|
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
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
<
|
|
304
|
-
<
|
|
305
|
-
<
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
<
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
},
|