@utilitywarehouse/hearth-react-native 0.10.0 → 0.12.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 +1 -1
- package/CHANGELOG.md +18 -0
- package/build/components/Avatar/Avatar.d.ts +6 -0
- package/build/components/Avatar/Avatar.js +80 -0
- package/build/components/Avatar/Avatar.props.d.ts +28 -0
- package/build/components/Avatar/Avatar.props.js +1 -0
- package/build/components/Avatar/index.d.ts +2 -0
- package/build/components/Avatar/index.js +1 -0
- package/build/components/Banner/Banner.context.d.ts +7 -0
- package/build/components/Banner/Banner.context.js +8 -0
- package/build/components/Banner/Banner.js +10 -40
- package/build/components/Banner/Banner.props.d.ts +3 -5
- package/build/components/Banner/BannerIllustration.d.ts +4 -0
- package/build/components/Banner/BannerIllustration.js +53 -0
- package/build/components/Banner/BannerImage.d.ts +4 -0
- package/build/components/Banner/BannerImage.js +53 -0
- package/build/components/Banner/index.d.ts +2 -0
- package/build/components/Banner/index.js +2 -0
- package/build/components/Card/CardAction/CardAction.props.d.ts +2 -3
- package/build/components/Card/CardAction/CardActionRoot.js +1 -2
- package/build/components/Checkbox/Checkbox.js +1 -2
- package/build/components/Checkbox/Checkbox.props.d.ts +3 -3
- package/build/components/Checkbox/CheckboxImage.d.ts +2 -1
- package/build/components/Checkbox/CheckboxImage.js +8 -1
- package/build/components/DateInput/DateInput.d.ts +6 -0
- package/build/components/DateInput/DateInput.js +19 -0
- package/build/components/DateInput/DateInput.props.d.ts +79 -0
- package/build/components/DateInput/DateInput.props.js +1 -0
- package/build/components/DateInput/DateInputSegment.d.ts +20 -0
- package/build/components/DateInput/DateInputSegment.js +31 -0
- package/build/components/DateInput/index.d.ts +2 -0
- package/build/components/DateInput/index.js +1 -0
- package/build/components/ExpandableCard/ExpandableCard.props.d.ts +1 -2
- package/build/components/ExpandableCard/ExpandableCardTrigger.props.d.ts +4 -5
- package/build/components/ExpandableCard/ExpandableCardTriggerRoot.js +1 -14
- package/build/components/HighlightBanner/HighlightBanner.js +2 -6
- package/build/components/HighlightBanner/HighlightBanner.props.d.ts +2 -3
- package/build/components/HighlightBanner/HighlightBannerImage.d.ts +4 -0
- package/build/components/HighlightBanner/HighlightBannerImage.js +18 -0
- package/build/components/HighlightBanner/index.d.ts +1 -0
- package/build/components/HighlightBanner/index.js +1 -0
- package/build/components/Input/Input.d.ts +5 -7
- package/build/components/Input/Input.js +11 -4
- package/build/components/Input/InputField.d.ts +4 -7
- package/build/components/Input/InputField.js +6 -5
- package/build/components/List/ListItem/ListItem.props.d.ts +2 -2
- package/build/components/List/ListItem/ListItemRoot.js +1 -2
- package/build/components/Modal/Modal.js +2 -6
- package/build/components/Modal/Modal.props.d.ts +3 -2
- package/build/components/Modal/Modal.web.js +2 -6
- package/build/components/Modal/ModalImage.d.ts +4 -0
- package/build/components/Modal/ModalImage.js +18 -0
- package/build/components/Modal/index.d.ts +1 -0
- package/build/components/Modal/index.js +1 -0
- package/build/components/Radio/Radio.js +1 -2
- package/build/components/Radio/Radio.props.d.ts +3 -3
- package/build/components/Radio/RadioImage.d.ts +2 -1
- package/build/components/Radio/RadioImage.js +8 -1
- package/build/components/index.d.ts +2 -0
- package/build/components/index.js +2 -0
- package/build/utils/getInitials.d.ts +1 -0
- package/build/utils/getInitials.js +8 -0
- package/build/utils/index.d.ts +2 -0
- package/build/utils/index.js +2 -0
- package/build/utils/isThemedImageProps.d.ts +4 -0
- package/build/utils/isThemedImageProps.js +4 -0
- package/docs/components/AllComponents.web.tsx +18 -1
- package/package.json +2 -2
- package/src/components/Avatar/Avatar.docs.mdx +105 -0
- package/src/components/Avatar/Avatar.props.ts +31 -0
- package/src/components/Avatar/Avatar.stories.tsx +77 -0
- package/src/components/Avatar/Avatar.tsx +136 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Banner/Banner.context.ts +11 -0
- package/src/components/Banner/Banner.docs.mdx +55 -37
- package/src/components/Banner/Banner.props.ts +3 -5
- package/src/components/Banner/Banner.stories.tsx +86 -57
- package/src/components/Banner/Banner.tsx +24 -67
- package/src/components/Banner/BannerIllustration.tsx +63 -0
- package/src/components/Banner/BannerImage.tsx +63 -0
- package/src/components/Banner/index.ts +2 -0
- package/src/components/Card/Card.docs.mdx +4 -4
- package/src/components/Card/CardAction/CardAction.props.ts +2 -3
- package/src/components/Card/CardAction/CardAction.stories.tsx +4 -3
- package/src/components/Card/CardAction/CardActionRoot.tsx +4 -5
- package/src/components/Checkbox/Checkbox.docs.mdx +23 -4
- package/src/components/Checkbox/Checkbox.props.ts +3 -3
- package/src/components/Checkbox/Checkbox.stories.tsx +14 -8
- package/src/components/Checkbox/Checkbox.tsx +1 -2
- package/src/components/Checkbox/CheckboxImage.tsx +8 -3
- package/src/components/DateInput/DateInput.docs.mdx +163 -0
- package/src/components/DateInput/DateInput.props.ts +80 -0
- package/src/components/DateInput/DateInput.stories.tsx +269 -0
- package/src/components/DateInput/DateInput.tsx +117 -0
- package/src/components/DateInput/DateInputSegment.tsx +83 -0
- package/src/components/DateInput/index.ts +2 -0
- package/src/components/ExpandableCard/ExpandableCard.docs.mdx +2 -2
- package/src/components/ExpandableCard/ExpandableCard.props.ts +1 -2
- package/src/components/ExpandableCard/ExpandableCard.stories.tsx +3 -3
- package/src/components/ExpandableCard/ExpandableCardTrigger.props.ts +4 -5
- package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +2 -17
- package/src/components/HighlightBanner/HighlightBanner.docs.mdx +73 -42
- package/src/components/HighlightBanner/HighlightBanner.props.ts +2 -3
- package/src/components/HighlightBanner/HighlightBanner.stories.tsx +85 -60
- package/src/components/HighlightBanner/HighlightBanner.tsx +3 -10
- package/src/components/HighlightBanner/HighlightBannerImage.tsx +20 -0
- package/src/components/HighlightBanner/index.ts +1 -0
- package/src/components/Input/Input.stories.tsx +76 -3
- package/src/components/Input/Input.tsx +110 -98
- package/src/components/Input/InputField.tsx +27 -26
- package/src/components/List/List.docs.mdx +15 -9
- package/src/components/List/List.stories.tsx +2 -2
- package/src/components/List/ListItem/ListItem.props.ts +2 -2
- package/src/components/List/ListItem/ListItemRoot.tsx +2 -3
- package/src/components/Modal/Modal.docs.mdx +16 -4
- package/src/components/Modal/Modal.props.ts +3 -2
- package/src/components/Modal/Modal.stories.tsx +2 -5
- package/src/components/Modal/Modal.tsx +2 -6
- package/src/components/Modal/Modal.web.tsx +2 -6
- package/src/components/Modal/ModalImage.tsx +20 -0
- package/src/components/Modal/index.ts +1 -0
- package/src/components/PillGroup/PillGroup.stories.tsx +1 -1
- package/src/components/Radio/Radio.docs.mdx +21 -8
- package/src/components/Radio/Radio.props.ts +3 -3
- package/src/components/Radio/Radio.stories.tsx +15 -11
- package/src/components/Radio/Radio.tsx +1 -2
- package/src/components/Radio/RadioImage.tsx +8 -3
- package/src/components/index.ts +2 -0
- package/src/utils/getInitials.ts +7 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/isThemedImageProps.ts +8 -0
package/build/utils/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { default as coloursAsArray, extractLightColorValues } from './coloursAsArray';
|
|
2
2
|
export { default as formatThousands } from './formatThousands';
|
|
3
3
|
export { default as getFlattenedColorValue } from './getFlattenedColorValue';
|
|
4
|
+
export { getInitials } from './getInitials';
|
|
4
5
|
export { default as getStyleValue } from './getStyleValue';
|
|
5
6
|
export { default as hexWithOpacity } from './hexWithOpacity';
|
|
6
7
|
export { default as isEqual } from './isEqual';
|
|
8
|
+
export { default as isThemedImageProps } from './isThemedImageProps';
|
|
7
9
|
export * from './styleUtils';
|
|
8
10
|
export * from './themeValueHelpers';
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
Accordion,
|
|
24
24
|
AccordionItem,
|
|
25
25
|
Alert,
|
|
26
|
+
Avatar,
|
|
26
27
|
Badge,
|
|
27
28
|
Banner,
|
|
28
29
|
BodyText,
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
Checkbox,
|
|
40
41
|
Container,
|
|
41
42
|
CurrencyInput,
|
|
43
|
+
DateInput,
|
|
42
44
|
DatePicker,
|
|
43
45
|
DatePickerInput,
|
|
44
46
|
DateType,
|
|
@@ -196,6 +198,11 @@ const AllComponents: React.FC = () => {
|
|
|
196
198
|
<Alert text="This is an alert" />
|
|
197
199
|
</Center>
|
|
198
200
|
</ComponentWrapper>
|
|
201
|
+
<ComponentWrapper name="Avatar" link="/?path=/docs/components-avatar--docs">
|
|
202
|
+
<Center flex={1} gap="200">
|
|
203
|
+
<Avatar name="John Doe" />
|
|
204
|
+
</Center>
|
|
205
|
+
</ComponentWrapper>
|
|
199
206
|
<ComponentWrapper name="Badge" link="/?path=/docs/components-badge--docs">
|
|
200
207
|
<Center gap="200" flex={1}>
|
|
201
208
|
<View>
|
|
@@ -340,6 +347,11 @@ const AllComponents: React.FC = () => {
|
|
|
340
347
|
<CurrencyInput />
|
|
341
348
|
</Center>
|
|
342
349
|
</ComponentWrapper>
|
|
350
|
+
<ComponentWrapper name="Date Input" link="/?path=/docs/forms-date-input--docs">
|
|
351
|
+
<Center flex={1} padding="200">
|
|
352
|
+
<DateInput />
|
|
353
|
+
</Center>
|
|
354
|
+
</ComponentWrapper>
|
|
343
355
|
<ComponentWrapper name="Date Picker" link="/?path=/docs/components-date-picker--docs">
|
|
344
356
|
<Center flex={1}>
|
|
345
357
|
<Button onPress={handleDatePickerOpenPress}>Open Date Picker</Button>
|
|
@@ -635,7 +647,12 @@ const AllComponents: React.FC = () => {
|
|
|
635
647
|
{(() => {
|
|
636
648
|
const [pillValue, setPillValue] = React.useState<string[]>(['energy', 'mobile']);
|
|
637
649
|
return (
|
|
638
|
-
<PillGroup
|
|
650
|
+
<PillGroup
|
|
651
|
+
value={pillValue}
|
|
652
|
+
onChange={v => setPillValue(v as string[])}
|
|
653
|
+
wrap={false}
|
|
654
|
+
multiple
|
|
655
|
+
>
|
|
639
656
|
<Pill value="all" label="All" />
|
|
640
657
|
<Pill value="energy" label="Energy" icon={ElectricityMediumIcon} />
|
|
641
658
|
<Pill value="broadband" label="Broadband" icon={BroadbandMediumIcon} />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilitywarehouse/hearth-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Utility Warehouse React Native UI library",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@utilitywarehouse/hearth-react-icons": "^0.7.4",
|
|
61
61
|
"@utilitywarehouse/hearth-react-native-icons": "^0.7.4",
|
|
62
62
|
"@utilitywarehouse/hearth-svg-assets": "^0.2.0",
|
|
63
|
-
"@utilitywarehouse/hearth-tokens": "^0.2.
|
|
63
|
+
"@utilitywarehouse/hearth-tokens": "^0.2.1"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@gorhom/bottom-sheet": "^5.0.0",
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import { Avatar, Box, Center } from '../..';
|
|
3
|
+
import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
|
|
4
|
+
import * as Stories from './Avatar.stories';
|
|
5
|
+
|
|
6
|
+
<Meta title="Components / Avatar" />
|
|
7
|
+
|
|
8
|
+
<BackToTopButton />
|
|
9
|
+
|
|
10
|
+
<ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components-%26-Tokens?node-id=1%3A148" />
|
|
11
|
+
|
|
12
|
+
# Avatar
|
|
13
|
+
|
|
14
|
+
Avatars help humanise the product experience by connecting users with the product and to each other.
|
|
15
|
+
|
|
16
|
+
- [Playground](#playground)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [Props](#props)
|
|
19
|
+
- [Accessibility](#accessibility)
|
|
20
|
+
|
|
21
|
+
## Playground
|
|
22
|
+
|
|
23
|
+
<Canvas of={Stories.Playground} />
|
|
24
|
+
|
|
25
|
+
<Controls of={Stories.Playground} />
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Icon
|
|
30
|
+
|
|
31
|
+
The icon variant is the default `Avatar` type. It ensures UI remains balanced and visually complete, even with limited data.
|
|
32
|
+
|
|
33
|
+
<UsageWrap>
|
|
34
|
+
<Center>
|
|
35
|
+
<Box>
|
|
36
|
+
<Avatar />
|
|
37
|
+
</Box>
|
|
38
|
+
</Center>
|
|
39
|
+
</UsageWrap>
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { Avatar } from '@utilitywarehouse/hearth-react-native';
|
|
43
|
+
|
|
44
|
+
const MyComponent = () => <Avatar />;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Initials
|
|
48
|
+
|
|
49
|
+
The initials variant can be used when a profile image is not available, but you have the user's name.
|
|
50
|
+
|
|
51
|
+
<UsageWrap>
|
|
52
|
+
<Center>
|
|
53
|
+
<Box>
|
|
54
|
+
<Avatar name="Jane Doe" />
|
|
55
|
+
</Box>
|
|
56
|
+
</Center>
|
|
57
|
+
</UsageWrap>
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { Avatar } from '@utilitywarehouse/hearth-react-native';
|
|
61
|
+
|
|
62
|
+
const MyComponent = () => <Avatar name="Jane Doe" />;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Image
|
|
66
|
+
|
|
67
|
+
The image variant is available to use in journeys where a user is able to upload their own profile picture.
|
|
68
|
+
|
|
69
|
+
<UsageWrap>
|
|
70
|
+
<Center>
|
|
71
|
+
<Box>
|
|
72
|
+
<Avatar
|
|
73
|
+
name="Jane Doe"
|
|
74
|
+
src={{ uri: 'https://ca.slack-edge.com/T0HR00WDA-U05SHRATW7Q-3ad4ae7c75b8-512' }}
|
|
75
|
+
/>
|
|
76
|
+
</Box>
|
|
77
|
+
</Center>
|
|
78
|
+
</UsageWrap>
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { Avatar } from '@utilitywarehouse/hearth-react-native';
|
|
82
|
+
|
|
83
|
+
const MyComponent = () => (
|
|
84
|
+
<Avatar
|
|
85
|
+
name="Jane Doe"
|
|
86
|
+
src={{ uri: 'https://ca.slack-edge.com/T0HR00WDA-U05SHRATW7Q-3ad4ae7c75b8-512' }}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Props
|
|
92
|
+
|
|
93
|
+
| Property | Type | Description | Default |
|
|
94
|
+
| ----------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------- |
|
|
95
|
+
| `src` | `ImageSourcePropType` | The image source to display. | |
|
|
96
|
+
| `name` | `string` | The name associated with the avatar, used for creating initials and accessibility. | |
|
|
97
|
+
| `size` | `'sm' \| 'md'` | Sets the avatar size. | `md` |
|
|
98
|
+
| `delayMs` | `number` | Delay in milliseconds before the image is rendered to avoid flickering. | `0` |
|
|
99
|
+
| `onLoadingStatusChange` | `(status: 'idle' \| 'loading' \| 'loaded' \| 'error') => void` | Callback fired when the loading status of the image changes. | |
|
|
100
|
+
|
|
101
|
+
## Accessibility
|
|
102
|
+
|
|
103
|
+
When passing a user image to the `src` prop, you should also pass their name to the `name` prop so it can be used as an accessible description for the image.
|
|
104
|
+
|
|
105
|
+
If the image fails to load, the component will automatically fall back to displaying the initials (if `name` is provided) or the default user icon.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ImageSourcePropType, ViewProps } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type AvatarLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error';
|
|
4
|
+
|
|
5
|
+
interface AvatarProps extends ViewProps {
|
|
6
|
+
/**
|
|
7
|
+
* The image source to display.
|
|
8
|
+
*/
|
|
9
|
+
src?: ImageSourcePropType;
|
|
10
|
+
/**
|
|
11
|
+
* The name associated with the avatar, used for creating initials and accessibility label.
|
|
12
|
+
*/
|
|
13
|
+
name?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Sets the avatar size.
|
|
16
|
+
* @default md
|
|
17
|
+
*/
|
|
18
|
+
size?: 'sm' | 'md';
|
|
19
|
+
/**
|
|
20
|
+
* Delay in milliseconds before the image is rendered.
|
|
21
|
+
* Useful to prevent flickering when the image loads very quickly.
|
|
22
|
+
* @default 0
|
|
23
|
+
*/
|
|
24
|
+
delayMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Callback fired when the loading status of the image changes.
|
|
27
|
+
*/
|
|
28
|
+
onLoadingStatusChange?: (status: AvatarLoadingStatus) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default AvatarProps;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Avatar } from '.';
|
|
4
|
+
import { Flex } from '../Flex';
|
|
5
|
+
import { VariantTitle } from '../../../docs/components';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'Stories / Avatar',
|
|
9
|
+
component: Avatar,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
},
|
|
13
|
+
argTypes: {
|
|
14
|
+
size: {
|
|
15
|
+
options: ['sm', 'md'],
|
|
16
|
+
control: 'radio',
|
|
17
|
+
description: 'Size of the avatar.',
|
|
18
|
+
},
|
|
19
|
+
name: {
|
|
20
|
+
control: 'text',
|
|
21
|
+
description: 'Name used for initials and accessibility.',
|
|
22
|
+
},
|
|
23
|
+
src: {
|
|
24
|
+
control: 'object',
|
|
25
|
+
description: 'Image source object (e.g. { uri: "..." }).',
|
|
26
|
+
},
|
|
27
|
+
delayMs: {
|
|
28
|
+
control: 'number',
|
|
29
|
+
description: 'Delay in ms before showing the fallback.',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
args: {
|
|
33
|
+
size: 'md',
|
|
34
|
+
},
|
|
35
|
+
} satisfies Meta<typeof Avatar>;
|
|
36
|
+
|
|
37
|
+
export default meta;
|
|
38
|
+
type Story = StoryObj<typeof meta>;
|
|
39
|
+
|
|
40
|
+
export const Playground: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
name: 'Jane Doe',
|
|
43
|
+
src: { uri: 'https://ca.slack-edge.com/T0HR00WDA-U05SHRATW7Q-3ad4ae7c75b8-512' },
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const Icon: Story = {};
|
|
48
|
+
|
|
49
|
+
export const Initials: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
name: 'Jane Doe',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const Image: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
name: 'Jane Doe',
|
|
58
|
+
src: { uri: 'https://ca.slack-edge.com/T0HR00WDA-U05SHRATW7Q-3ad4ae7c75b8-512' },
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const Sizes: Story = {
|
|
63
|
+
render: () => (
|
|
64
|
+
<Flex direction="row" space="xl" align="center">
|
|
65
|
+
<Flex direction="column" space="md" align="center">
|
|
66
|
+
<VariantTitle title="SM">
|
|
67
|
+
<Avatar size="sm" name="Jane Doe" />
|
|
68
|
+
</VariantTitle>
|
|
69
|
+
</Flex>
|
|
70
|
+
<Flex direction="column" space="md" align="center">
|
|
71
|
+
<VariantTitle title="MD">
|
|
72
|
+
<Avatar size="md" name="Jane Doe" />
|
|
73
|
+
</VariantTitle>
|
|
74
|
+
</Flex>
|
|
75
|
+
</Flex>
|
|
76
|
+
),
|
|
77
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { UserMediumIcon, UserSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Image, View } from 'react-native';
|
|
4
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
5
|
+
import { useTheme } from '../../hooks';
|
|
6
|
+
import { getInitials } from '../../utils';
|
|
7
|
+
import BodyText from '../BodyText/BodyText';
|
|
8
|
+
import AvatarProps, { AvatarLoadingStatus } from './Avatar.props';
|
|
9
|
+
|
|
10
|
+
const Avatar = ({
|
|
11
|
+
src,
|
|
12
|
+
name,
|
|
13
|
+
size = 'md',
|
|
14
|
+
delayMs = 0,
|
|
15
|
+
onLoadingStatusChange,
|
|
16
|
+
style,
|
|
17
|
+
...props
|
|
18
|
+
}: AvatarProps) => {
|
|
19
|
+
const [status, setStatus] = useState<AvatarLoadingStatus>('idle');
|
|
20
|
+
const [isDelayed, setIsDelayed] = useState(delayMs > 0);
|
|
21
|
+
const { components } = useTheme();
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!src) {
|
|
25
|
+
setStatus('idle');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setStatus('loading');
|
|
30
|
+
}, [src]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
onLoadingStatusChange?.(status);
|
|
34
|
+
}, [status, onLoadingStatusChange]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (delayMs <= 0) {
|
|
38
|
+
setIsDelayed(false);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const timerId = setTimeout(() => {
|
|
43
|
+
setIsDelayed(false);
|
|
44
|
+
}, delayMs);
|
|
45
|
+
return () => clearTimeout(timerId);
|
|
46
|
+
}, [delayMs]);
|
|
47
|
+
|
|
48
|
+
styles.useVariants({ size });
|
|
49
|
+
|
|
50
|
+
const initials = getInitials(name);
|
|
51
|
+
|
|
52
|
+
const handleLoad = () => setStatus('loaded');
|
|
53
|
+
const handleError = () => setStatus('error');
|
|
54
|
+
|
|
55
|
+
const showImage = src && status === 'loaded';
|
|
56
|
+
const showFallback = !showImage && !isDelayed;
|
|
57
|
+
|
|
58
|
+
const textSize = size === 'sm' ? 'md' : 'lg';
|
|
59
|
+
const FallbackIcon = size === 'sm' ? UserSmallIcon : UserMediumIcon;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<View
|
|
63
|
+
{...props}
|
|
64
|
+
style={[styles.container, style]}
|
|
65
|
+
accessibilityRole={showImage && name ? 'image' : undefined}
|
|
66
|
+
accessibilityLabel={showImage ? name : undefined}
|
|
67
|
+
>
|
|
68
|
+
{src && (
|
|
69
|
+
<Image
|
|
70
|
+
source={src}
|
|
71
|
+
style={styles.image}
|
|
72
|
+
onLoad={handleLoad}
|
|
73
|
+
onError={handleError}
|
|
74
|
+
accessibilityElementsHidden
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
{showFallback && (
|
|
78
|
+
<View style={styles.fallback}>
|
|
79
|
+
{name ? (
|
|
80
|
+
<BodyText
|
|
81
|
+
size={textSize}
|
|
82
|
+
weight="semibold"
|
|
83
|
+
textTransform="uppercase"
|
|
84
|
+
style={styles.text}
|
|
85
|
+
>
|
|
86
|
+
{initials}
|
|
87
|
+
</BodyText>
|
|
88
|
+
) : (
|
|
89
|
+
<FallbackIcon />
|
|
90
|
+
)}
|
|
91
|
+
</View>
|
|
92
|
+
)}
|
|
93
|
+
</View>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
Avatar.displayName = 'Avatar';
|
|
98
|
+
|
|
99
|
+
const styles = StyleSheet.create(theme => ({
|
|
100
|
+
container: {
|
|
101
|
+
justifyContent: 'center',
|
|
102
|
+
alignItems: 'center',
|
|
103
|
+
overflow: 'hidden',
|
|
104
|
+
backgroundColor: theme.color.surface.pig.subtle,
|
|
105
|
+
variants: {
|
|
106
|
+
size: {
|
|
107
|
+
sm: {
|
|
108
|
+
width: theme.components.avatar.sm.width,
|
|
109
|
+
height: theme.components.avatar.sm.height,
|
|
110
|
+
borderRadius: theme.components.avatar.sm.borderRadius,
|
|
111
|
+
},
|
|
112
|
+
md: {
|
|
113
|
+
width: theme.components.avatar.md.width,
|
|
114
|
+
height: theme.components.avatar.md.height,
|
|
115
|
+
borderRadius: theme.components.avatar.md.borderRadius,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
image: {
|
|
121
|
+
width: '100%',
|
|
122
|
+
height: '100%',
|
|
123
|
+
position: 'absolute',
|
|
124
|
+
},
|
|
125
|
+
fallback: {
|
|
126
|
+
width: '100%',
|
|
127
|
+
height: '100%',
|
|
128
|
+
justifyContent: 'center',
|
|
129
|
+
alignItems: 'center',
|
|
130
|
+
},
|
|
131
|
+
text: {
|
|
132
|
+
color: theme.color.text.primary,
|
|
133
|
+
},
|
|
134
|
+
}));
|
|
135
|
+
|
|
136
|
+
export default Avatar;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
const BannerContext = createContext<{ direction: 'horizontal' | 'vertical' }>({
|
|
4
|
+
direction: 'horizontal',
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export const useBannerContext = () => {
|
|
8
|
+
return useContext(BannerContext);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default BannerContext;
|
|
@@ -63,26 +63,39 @@ const MyComponent = () => (
|
|
|
63
63
|
|
|
64
64
|
## Props
|
|
65
65
|
|
|
66
|
-
| Property | Type | Description
|
|
67
|
-
| -------------------- | -------------------------------------------------------------------------------------------------- |
|
|
68
|
-
| icon | `ComponentType` | Icon component to display (mutually exclusive with image/illustration)
|
|
69
|
-
| iconContainerVariant | `'subtle' \| 'emphasis'` | Icon container visual style
|
|
70
|
-
| iconContainerSize | `'sm' \| 'md' \| 'lg'` | Icon container size
|
|
71
|
-
| iconContainerColor | `'pig' \| 'energy' \| 'broadband' \| 'mobile' \|` <br />`'insurance' \| 'cashback' \| 'highlight'` | Icon container color scheme
|
|
72
|
-
| illustration | `
|
|
73
|
-
| image | `
|
|
74
|
-
| heading | `string` | Heading text
|
|
75
|
-
| description | `string` | Description text
|
|
76
|
-
| direction | `'horizontal' \| 'vertical'` | Layout direction for icon/image and content
|
|
77
|
-
| link | `ReactNode` | Link element to display
|
|
78
|
-
| button | `ReactNode` | Button element to display
|
|
79
|
-
| onPress | `() => void` | Makes the entire banner pressable (shows chevron)
|
|
80
|
-
| onClose | `() => void` | Shows close button with handler
|
|
81
|
-
| variant | `'subtle' \| 'emphasis'` | Card visual style variant
|
|
82
|
-
| colorScheme | `'pig' \| 'energy' \| 'broadband' \| 'mobile' \|` <br />`'insurance' \| 'cashback' \| 'highlight'` | Color scheme for buttons
|
|
66
|
+
| Property | Type | Description | Default |
|
|
67
|
+
| -------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------------- |
|
|
68
|
+
| icon | `ComponentType` | Icon component to display (mutually exclusive with image/illustration) | `-` |
|
|
69
|
+
| iconContainerVariant | `'subtle' \| 'emphasis'` | Icon container visual style | `'subtle'` |
|
|
70
|
+
| iconContainerSize | `'sm' \| 'md' \| 'lg'` | Icon container size | `'md'` |
|
|
71
|
+
| iconContainerColor | `'pig' \| 'energy' \| 'broadband' \| 'mobile' \|` <br />`'insurance' \| 'cashback' \| 'highlight'` | Icon container color scheme | `'pig'` |
|
|
72
|
+
| illustration | `ReactNode` | The illustration element to render (mutually exclusive with icon/image) | `-` |
|
|
73
|
+
| image | `ReactNode` | The image element to render (mutually exclusive with icon/illustration) | `-` |
|
|
74
|
+
| heading | `string` | Heading text | `-` (required) |
|
|
75
|
+
| description | `string` | Description text | `-` (required) |
|
|
76
|
+
| direction | `'horizontal' \| 'vertical'` | Layout direction for icon/image and content | `'horizontal'` |
|
|
77
|
+
| link | `ReactNode` | Link element to display | `-` |
|
|
78
|
+
| button | `ReactNode` | Button element to display | `-` |
|
|
79
|
+
| onPress | `() => void` | Makes the entire banner pressable (shows chevron) | `-` |
|
|
80
|
+
| onClose | `() => void` | Shows close button with handler | `-` |
|
|
81
|
+
| variant | `'subtle' \| 'emphasis'` | Card visual style variant | `'subtle'` |
|
|
82
|
+
| colorScheme | `'pig' \| 'energy' \| 'broadband' \| 'mobile' \|` <br />`'insurance' \| 'cashback' \| 'highlight'` | Color scheme for buttons | `'pig'` |
|
|
83
83
|
|
|
84
84
|
The component also accepts all standard Card props except `noPadding`, `space`, `gap`, `rowGap`, `columnGap`, `flexDirection`, `flexWrap`, `alignItems`, and `justifyContent`.
|
|
85
85
|
|
|
86
|
+
### `BannerImage` & `BannerIllustration` Props
|
|
87
|
+
|
|
88
|
+
The `BannerImage` & `BannerIllustration` component can be used to display an image within the Banner. It accepts the following props:
|
|
89
|
+
|
|
90
|
+
| Property | Type | Description |
|
|
91
|
+
| --------- | --------------------- | ------------------------------------------------------------------------ |
|
|
92
|
+
| `source` | `ImageSourcePropType` | The source of the image to display |
|
|
93
|
+
| `light` | `ImageSourcePropType` | The source of the image to display in light mode (use instead of source) |
|
|
94
|
+
| `dark` | `ImageSourcePropType` | The source of the image to display in dark mode (use instead of source) |
|
|
95
|
+
| `...rest` | `ImageProps` | Additional props to pass to the underlying Image component |
|
|
96
|
+
|
|
97
|
+
For more details about the ThemedImage component used internally, refer to the [`ThemedImage` documentation](/docs/utility-components-themed-image--docs).
|
|
98
|
+
|
|
86
99
|
## Layout Options
|
|
87
100
|
|
|
88
101
|
### Horizontal Layout (Default)
|
|
@@ -153,22 +166,28 @@ Display a themed image that automatically switches between light and dark modes:
|
|
|
153
166
|
<Canvas of={Stories.WithImage} />
|
|
154
167
|
|
|
155
168
|
```jsx
|
|
156
|
-
import { Banner } from '@utilitywarehouse/hearth-react-native';
|
|
169
|
+
import { Banner, BannerImage } from '@utilitywarehouse/hearth-react-native';
|
|
157
170
|
|
|
158
171
|
const MyComponent = () => (
|
|
159
172
|
<>
|
|
160
173
|
<Banner
|
|
161
|
-
image={
|
|
162
|
-
|
|
163
|
-
|
|
174
|
+
image={
|
|
175
|
+
<BannerImage
|
|
176
|
+
source={{
|
|
177
|
+
uri: 'https://example.com/image.jpg',
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
}
|
|
164
181
|
heading="Featured Content"
|
|
165
182
|
description="Discover amazing content curated just for you."
|
|
166
183
|
/>
|
|
167
184
|
<Banner
|
|
168
|
-
image={
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
185
|
+
image={
|
|
186
|
+
<BannerImage
|
|
187
|
+
light={{ uri: 'https://example.com/light-image.jpg' }}
|
|
188
|
+
dark={{ uri: 'https://example.com/dark-image.jpg' }}
|
|
189
|
+
/>
|
|
190
|
+
}
|
|
172
191
|
heading="Featured Content"
|
|
173
192
|
description="Discover amazing content curated just for you."
|
|
174
193
|
/>
|
|
@@ -183,18 +202,15 @@ Display a themed illustration that adapts to layout changes:
|
|
|
183
202
|
<Canvas of={Stories.WithIllustration} />
|
|
184
203
|
|
|
185
204
|
```jsx
|
|
186
|
-
import { Banner } from '@utilitywarehouse/hearth-react-native';
|
|
205
|
+
import { Banner, BannerIllustration } from '@utilitywarehouse/hearth-react-native';
|
|
187
206
|
import SpotBillingDark from '@utilitywarehouse/hearth-svg-assets/lib/spot-billing-dark.svg';
|
|
188
207
|
import SpotBillingLight from '@utilitywarehouse/hearth-svg-assets/lib/spot-billing-light.svg';
|
|
189
208
|
|
|
190
209
|
const MyComponent = () => (
|
|
191
210
|
<Banner
|
|
192
|
-
illustration={
|
|
193
|
-
light
|
|
194
|
-
|
|
195
|
-
width: 80,
|
|
196
|
-
height: 80,
|
|
197
|
-
}}
|
|
211
|
+
illustration={
|
|
212
|
+
<BannerIllustration light={SpotBillingLight} dark={SpotBillingDark} width={80} height={80} />
|
|
213
|
+
}
|
|
198
214
|
heading="Featured Content"
|
|
199
215
|
description="Discover amazing content curated just for you."
|
|
200
216
|
/>
|
|
@@ -358,7 +374,7 @@ Combine multiple features for rich interactive banners:
|
|
|
358
374
|
<Canvas of={Stories.ComplexExample} />
|
|
359
375
|
|
|
360
376
|
```jsx
|
|
361
|
-
import { Banner } from '@utilitywarehouse/hearth-react-native';
|
|
377
|
+
import { Banner, BannerImage } from '@utilitywarehouse/hearth-react-native';
|
|
362
378
|
import { InsuranceMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
363
379
|
|
|
364
380
|
const MyComponent = () => (
|
|
@@ -381,10 +397,12 @@ const MyComponent = () => (
|
|
|
381
397
|
|
|
382
398
|
{/* Dismissible banner with link */}
|
|
383
399
|
<Banner
|
|
384
|
-
image={
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
400
|
+
image={
|
|
401
|
+
<BannerImage
|
|
402
|
+
light={{ uri: 'https://example.com/light.jpg' }}
|
|
403
|
+
dark={{ uri: 'https://example.com/dark.jpg' }}
|
|
404
|
+
/>
|
|
405
|
+
}
|
|
388
406
|
heading="Exclusive Member Benefit"
|
|
389
407
|
description="As a valued member, you now have access to premium features at no extra cost."
|
|
390
408
|
variant="emphasis"
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import type { ComponentType, ReactElement } from 'react';
|
|
2
|
-
import { ImageProps } from 'react-native';
|
|
1
|
+
import type { ComponentType, ReactElement, ReactNode } from 'react';
|
|
3
2
|
import type CardProps from '../Card/Card.props';
|
|
4
|
-
import { ThemedImageProps } from '../ThemedImage';
|
|
5
3
|
|
|
6
4
|
export type BannerDirection = 'horizontal' | 'vertical';
|
|
7
5
|
|
|
@@ -51,12 +49,12 @@ export interface BannerProps
|
|
|
51
49
|
* Illustration to display in the banner
|
|
52
50
|
* Mutually exclusive with icon and image
|
|
53
51
|
*/
|
|
54
|
-
illustration?:
|
|
52
|
+
illustration?: ReactNode;
|
|
55
53
|
/**
|
|
56
54
|
* Image to display in the banner
|
|
57
55
|
* Mutually exclusive with icon and illustration
|
|
58
56
|
*/
|
|
59
|
-
image?:
|
|
57
|
+
image?: ReactNode;
|
|
60
58
|
/**
|
|
61
59
|
* Heading text
|
|
62
60
|
*/
|