@utilitywarehouse/hearth-react-native 0.10.0 → 0.11.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 (39) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/CHANGELOG.md +8 -0
  4. package/build/components/Avatar/Avatar.d.ts +6 -0
  5. package/build/components/Avatar/Avatar.js +80 -0
  6. package/build/components/Avatar/Avatar.props.d.ts +28 -0
  7. package/build/components/Avatar/Avatar.props.js +1 -0
  8. package/build/components/Avatar/index.d.ts +2 -0
  9. package/build/components/Avatar/index.js +1 -0
  10. package/build/components/DateInput/DateInput.d.ts +6 -0
  11. package/build/components/DateInput/DateInput.js +19 -0
  12. package/build/components/DateInput/DateInput.props.d.ts +79 -0
  13. package/build/components/DateInput/DateInput.props.js +1 -0
  14. package/build/components/DateInput/DateInputSegment.d.ts +20 -0
  15. package/build/components/DateInput/DateInputSegment.js +31 -0
  16. package/build/components/DateInput/index.d.ts +2 -0
  17. package/build/components/DateInput/index.js +1 -0
  18. package/build/components/index.d.ts +2 -0
  19. package/build/components/index.js +2 -0
  20. package/build/utils/getInitials.d.ts +1 -0
  21. package/build/utils/getInitials.js +8 -0
  22. package/build/utils/index.d.ts +1 -0
  23. package/build/utils/index.js +1 -0
  24. package/docs/components/AllComponents.web.tsx +18 -1
  25. package/package.json +1 -1
  26. package/src/components/Avatar/Avatar.docs.mdx +105 -0
  27. package/src/components/Avatar/Avatar.props.ts +31 -0
  28. package/src/components/Avatar/Avatar.stories.tsx +77 -0
  29. package/src/components/Avatar/Avatar.tsx +136 -0
  30. package/src/components/Avatar/index.ts +2 -0
  31. package/src/components/DateInput/DateInput.docs.mdx +163 -0
  32. package/src/components/DateInput/DateInput.props.ts +80 -0
  33. package/src/components/DateInput/DateInput.stories.tsx +269 -0
  34. package/src/components/DateInput/DateInput.tsx +117 -0
  35. package/src/components/DateInput/DateInputSegment.tsx +83 -0
  36. package/src/components/DateInput/index.ts +2 -0
  37. package/src/components/index.ts +2 -0
  38. package/src/utils/getInitials.ts +7 -0
  39. package/src/utils/index.ts +1 -0
@@ -1,4 +1,4 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.10.0 build /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.11.0 build /home/runner/work/hearth/hearth/packages/react-native
3
3
  > tsc
4
4
 
@@ -1,5 +1,5 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.10.0 lint /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.11.0 lint /home/runner/work/hearth/hearth/packages/react-native
3
3
  > TIMING=1 eslint --max-warnings 0
4
4
 
5
5
  Rule | Time (ms) | Relative
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @utilitywarehouse/hearth-react-native
2
2
 
3
+ ## 0.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#674](https://github.com/utilitywarehouse/hearth/pull/674) [`c617257`](https://github.com/utilitywarehouse/hearth/commit/c617257974b11c39d706d6fd46712a284ff5fe10) Thanks [@jordmccord](https://github.com/jordmccord)! - Adds `DateInput` component
8
+
9
+ - [#663](https://github.com/utilitywarehouse/hearth/pull/663) [`8889a07`](https://github.com/utilitywarehouse/hearth/commit/8889a07e347e9289928e679cee495d7656a4e9aa) Thanks [@Utakato](https://github.com/Utakato)! - Add Avatar component
10
+
3
11
  ## 0.10.0
4
12
 
5
13
  ### Minor Changes
@@ -0,0 +1,6 @@
1
+ import AvatarProps from './Avatar.props';
2
+ declare const Avatar: {
3
+ ({ src, name, size, delayMs, onLoadingStatusChange, style, ...props }: AvatarProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ export default Avatar;
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { UserMediumIcon, UserSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
3
+ import { useEffect, useState } from 'react';
4
+ import { Image, View } from 'react-native';
5
+ import { StyleSheet } from 'react-native-unistyles';
6
+ import { useTheme } from '../../hooks';
7
+ import { getInitials } from '../../utils';
8
+ import BodyText from '../BodyText/BodyText';
9
+ const Avatar = ({ src, name, size = 'md', delayMs = 0, onLoadingStatusChange, style, ...props }) => {
10
+ const [status, setStatus] = useState('idle');
11
+ const [isDelayed, setIsDelayed] = useState(delayMs > 0);
12
+ const { components } = useTheme();
13
+ useEffect(() => {
14
+ if (!src) {
15
+ setStatus('idle');
16
+ return;
17
+ }
18
+ setStatus('loading');
19
+ }, [src]);
20
+ useEffect(() => {
21
+ onLoadingStatusChange?.(status);
22
+ }, [status, onLoadingStatusChange]);
23
+ useEffect(() => {
24
+ if (delayMs <= 0) {
25
+ setIsDelayed(false);
26
+ return;
27
+ }
28
+ const timerId = setTimeout(() => {
29
+ setIsDelayed(false);
30
+ }, delayMs);
31
+ return () => clearTimeout(timerId);
32
+ }, [delayMs]);
33
+ styles.useVariants({ size });
34
+ const initials = getInitials(name);
35
+ const handleLoad = () => setStatus('loaded');
36
+ const handleError = () => setStatus('error');
37
+ const showImage = src && status === 'loaded';
38
+ const showFallback = !showImage && !isDelayed;
39
+ const textSize = size === 'sm' ? 'md' : 'lg';
40
+ const FallbackIcon = size === 'sm' ? UserSmallIcon : UserMediumIcon;
41
+ return (_jsxs(View, { ...props, style: [styles.container, style], accessibilityRole: showImage && name ? 'image' : undefined, accessibilityLabel: showImage ? name : undefined, children: [src && (_jsx(Image, { source: src, style: styles.image, onLoad: handleLoad, onError: handleError, accessibilityElementsHidden: true })), showFallback && (_jsx(View, { style: styles.fallback, children: name ? (_jsx(BodyText, { size: textSize, weight: "semibold", textTransform: "uppercase", style: styles.text, children: initials })) : (_jsx(FallbackIcon, {})) }))] }));
42
+ };
43
+ Avatar.displayName = 'Avatar';
44
+ const styles = StyleSheet.create(theme => ({
45
+ container: {
46
+ justifyContent: 'center',
47
+ alignItems: 'center',
48
+ overflow: 'hidden',
49
+ backgroundColor: theme.color.surface.pig.subtle,
50
+ variants: {
51
+ size: {
52
+ sm: {
53
+ width: theme.components.avatar.sm.width,
54
+ height: theme.components.avatar.sm.height,
55
+ borderRadius: theme.components.avatar.sm.borderRadius,
56
+ },
57
+ md: {
58
+ width: theme.components.avatar.md.width,
59
+ height: theme.components.avatar.md.height,
60
+ borderRadius: theme.components.avatar.md.borderRadius,
61
+ },
62
+ },
63
+ },
64
+ },
65
+ image: {
66
+ width: '100%',
67
+ height: '100%',
68
+ position: 'absolute',
69
+ },
70
+ fallback: {
71
+ width: '100%',
72
+ height: '100%',
73
+ justifyContent: 'center',
74
+ alignItems: 'center',
75
+ },
76
+ text: {
77
+ color: theme.color.text.primary,
78
+ },
79
+ }));
80
+ export default Avatar;
@@ -0,0 +1,28 @@
1
+ import type { ImageSourcePropType, ViewProps } from 'react-native';
2
+ export type AvatarLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error';
3
+ interface AvatarProps extends ViewProps {
4
+ /**
5
+ * The image source to display.
6
+ */
7
+ src?: ImageSourcePropType;
8
+ /**
9
+ * The name associated with the avatar, used for creating initials and accessibility label.
10
+ */
11
+ name?: string;
12
+ /**
13
+ * Sets the avatar size.
14
+ * @default md
15
+ */
16
+ size?: 'sm' | 'md';
17
+ /**
18
+ * Delay in milliseconds before the image is rendered.
19
+ * Useful to prevent flickering when the image loads very quickly.
20
+ * @default 0
21
+ */
22
+ delayMs?: number;
23
+ /**
24
+ * Callback fired when the loading status of the image changes.
25
+ */
26
+ onLoadingStatusChange?: (status: AvatarLoadingStatus) => void;
27
+ }
28
+ export default AvatarProps;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export { default as Avatar } from './Avatar';
2
+ export type { default as AvatarProps, AvatarLoadingStatus } from './Avatar.props';
@@ -0,0 +1 @@
1
+ export { default as Avatar } from './Avatar';
@@ -0,0 +1,6 @@
1
+ import type { DateInputProps } from './DateInput.props';
2
+ declare const DateInput: {
3
+ ({ label, helperText, helperIcon, validationStatus, validText, invalidText, disabled, readonly, required, hideDay, hideMonth, hideYear, dayPlaceholder, monthPlaceholder, yearPlaceholder, dayValue, monthValue, yearValue, onDayChange, onMonthChange, onYearChange, onDayFocus, onMonthFocus, onYearFocus, onDayBlur, onMonthBlur, onYearBlur, ...props }: DateInputProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ export default DateInput;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import { FormField } from '../FormField';
5
+ import DateInputSegment from './DateInputSegment';
6
+ const DateInput = ({ label, helperText, helperIcon, validationStatus = 'initial', validText, invalidText, disabled, readonly, required, hideDay = false, hideMonth = false, hideYear = false, dayPlaceholder = 'DD', monthPlaceholder = 'MM', yearPlaceholder = 'YYYY', dayValue, monthValue, yearValue, onDayChange, onMonthChange, onYearChange, onDayFocus, onMonthFocus, onYearFocus, onDayBlur, onMonthBlur, onYearBlur, ...props }) => {
7
+ return (_jsx(FormField, { label: label, helperText: helperText, helperIcon: helperIcon, validationStatus: validationStatus, validText: validText, invalidText: invalidText, disabled: disabled, readonly: readonly, required: required, style: styles.wrap, ...props, children: _jsxs(View, { style: styles.container, children: [!hideDay && (_jsx(DateInputSegment, { label: "Day", placeholder: dayPlaceholder, value: dayValue, onChange: onDayChange, onFocus: onDayFocus, onBlur: onDayBlur, disabled: disabled, required: required, readonly: readonly, validationStatus: validationStatus, maxLength: 2, testID: "date-input-day" })), !hideMonth && (_jsx(DateInputSegment, { label: "Month", placeholder: monthPlaceholder, value: monthValue, onChange: onMonthChange, onFocus: onMonthFocus, onBlur: onMonthBlur, disabled: disabled, required: required, readonly: readonly, validationStatus: validationStatus, maxLength: 2, testID: "date-input-month" })), !hideYear && (_jsx(DateInputSegment, { label: "Year", placeholder: yearPlaceholder, value: yearValue, onChange: onYearChange, onFocus: onYearFocus, onBlur: onYearBlur, disabled: disabled, required: required, readonly: readonly, validationStatus: validationStatus, maxLength: 4, testID: "date-input-year" }))] }) }));
8
+ };
9
+ DateInput.displayName = 'DateInput';
10
+ const styles = StyleSheet.create(theme => ({
11
+ wrap: {
12
+ gap: theme.components.input.gap,
13
+ },
14
+ container: {
15
+ flexDirection: 'row',
16
+ gap: theme.components.input.date.gap,
17
+ },
18
+ }));
19
+ export default DateInput;
@@ -0,0 +1,79 @@
1
+ import type { TextInputProps } from 'react-native';
2
+ import type { FormFieldBaseProps } from '../FormField/FormField.props';
3
+ export interface DateInputProps extends FormFieldBaseProps {
4
+ /**
5
+ * Whether the day segment is hidden.
6
+ * @default false
7
+ */
8
+ hideDay?: boolean;
9
+ /**
10
+ * Whether the month segment is hidden.
11
+ * @default false
12
+ */
13
+ hideMonth?: boolean;
14
+ /**
15
+ * Whether the year segment is hidden.
16
+ * @default false
17
+ */
18
+ hideYear?: boolean;
19
+ /**
20
+ * Placeholder text for the day segment.
21
+ */
22
+ dayPlaceholder?: string;
23
+ /**
24
+ * Placeholder text for the month segment.
25
+ */
26
+ monthPlaceholder?: string;
27
+ /**
28
+ * Placeholder text for the year segment.
29
+ */
30
+ yearPlaceholder?: string;
31
+ /**
32
+ * The controlled value for the day segment. Must be used with an `onDayChange` handler.
33
+ */
34
+ dayValue?: string;
35
+ /**
36
+ * The controlled value for the month segment. Must be used with an `onMonthChange` handler.
37
+ */
38
+ monthValue?: string;
39
+ /**
40
+ * The controlled value for the year segment. Must be used with an `onYearChange` handler.
41
+ */
42
+ yearValue?: string;
43
+ /**
44
+ * Callback fired when the day value changes.
45
+ */
46
+ onDayChange?: (text: string) => void;
47
+ /**
48
+ * Callback fired when the month value changes.
49
+ */
50
+ onMonthChange?: (text: string) => void;
51
+ /**
52
+ * Callback fired when the year value changes.
53
+ */
54
+ onYearChange?: (text: string) => void;
55
+ /**
56
+ * Callback fired when the day segment receives focus.
57
+ */
58
+ onDayFocus?: TextInputProps['onFocus'];
59
+ /**
60
+ * Callback fired when the month segment receives focus.
61
+ */
62
+ onMonthFocus?: TextInputProps['onFocus'];
63
+ /**
64
+ * Callback fired when the year segment receives focus.
65
+ */
66
+ onYearFocus?: TextInputProps['onFocus'];
67
+ /**
68
+ * Callback fired when the day segment loses focus.
69
+ */
70
+ onDayBlur?: TextInputProps['onBlur'];
71
+ /**
72
+ * Callback fired when the month segment loses focus.
73
+ */
74
+ onMonthBlur?: TextInputProps['onBlur'];
75
+ /**
76
+ * Callback fired when the year segment loses focus.
77
+ */
78
+ onYearBlur?: TextInputProps['onBlur'];
79
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import type { DateInputProps } from './DateInput.props';
2
+ interface DateInputSegmentProps {
3
+ label: string;
4
+ placeholder?: string;
5
+ value?: string;
6
+ onChange?: (text: string) => void;
7
+ onFocus?: DateInputProps['onDayFocus'];
8
+ onBlur?: DateInputProps['onDayBlur'];
9
+ disabled?: boolean;
10
+ required?: boolean;
11
+ validationStatus?: DateInputProps['validationStatus'];
12
+ maxLength?: number;
13
+ readonly?: boolean;
14
+ testID?: string;
15
+ }
16
+ declare const DateInputSegment: {
17
+ ({ label, placeholder, value, onChange, onFocus, onBlur, disabled, validationStatus, maxLength, readonly, testID, }: DateInputSegmentProps): import("react/jsx-runtime").JSX.Element;
18
+ displayName: string;
19
+ };
20
+ export default DateInputSegment;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import { BodyText } from '../BodyText';
5
+ import { Input } from '../Input';
6
+ const DateInputSegment = ({ label, placeholder, value, onChange, onFocus, onBlur, disabled, validationStatus, maxLength, readonly, testID, }) => {
7
+ styles.useVariants({ disabled });
8
+ return (_jsxs(View, { style: styles.container, children: [_jsx(BodyText, { size: "md", style: styles.label, children: label }), _jsx(Input, { value: value, onChangeText: onChange, onFocus: onFocus, onBlur: onBlur, placeholder: disabled ? undefined : placeholder, keyboardType: "number-pad", maxLength: maxLength, testID: testID, accessibilityLabel: label, disabled: disabled, validationStatus: validationStatus, readonly: readonly, style: styles.input })] }));
9
+ };
10
+ DateInputSegment.displayName = 'DateInputSegment';
11
+ const styles = StyleSheet.create(theme => ({
12
+ container: {
13
+ flex: 1,
14
+ gap: theme.components.input.gap,
15
+ maxWidth: 96,
16
+ },
17
+ label: {
18
+ variants: {
19
+ disabled: {
20
+ true: {
21
+ opacity: theme.opacity.disabled,
22
+ },
23
+ },
24
+ },
25
+ },
26
+ input: {
27
+ flex: 1,
28
+ maxWidth: 96,
29
+ },
30
+ }));
31
+ export default DateInputSegment;
@@ -0,0 +1,2 @@
1
+ export { default as DateInput } from './DateInput';
2
+ export type { DateInputProps } from './DateInput.props';
@@ -0,0 +1 @@
1
+ export { default as DateInput } from './DateInput';
@@ -1,5 +1,6 @@
1
1
  export * from './Accordion';
2
2
  export * from './Alert';
3
+ export * from './Avatar';
3
4
  export * from './Badge';
4
5
  export * from './Banner';
5
6
  export * from './BodyText';
@@ -12,6 +13,7 @@ export * from './Center';
12
13
  export * from './Checkbox';
13
14
  export * from './Container';
14
15
  export * from './CurrencyInput';
16
+ export * from './DateInput';
15
17
  export * from './DatePicker';
16
18
  export * from './DatePickerInput';
17
19
  export * from './DescriptionList';
@@ -1,6 +1,7 @@
1
1
  // Custom
2
2
  export * from './Accordion';
3
3
  export * from './Alert';
4
+ export * from './Avatar';
4
5
  export * from './Badge';
5
6
  export * from './Banner';
6
7
  export * from './BodyText';
@@ -13,6 +14,7 @@ export * from './Center';
13
14
  export * from './Checkbox';
14
15
  export * from './Container';
15
16
  export * from './CurrencyInput';
17
+ export * from './DateInput';
16
18
  export * from './DatePicker';
17
19
  export * from './DatePickerInput';
18
20
  export * from './DescriptionList';
@@ -0,0 +1 @@
1
+ export declare function getInitials(name?: string): string | undefined;
@@ -0,0 +1,8 @@
1
+ export function getInitials(name) {
2
+ if (!name)
3
+ return undefined;
4
+ const regex = new RegExp(/(\p{L}{1})\p{L}+/gu);
5
+ const names = [...name.matchAll(regex)];
6
+ const initials = (names.shift()?.[1] || '') + (names.pop()?.[1] || '');
7
+ return initials.toUpperCase();
8
+ }
@@ -3,6 +3,7 @@ export { default as formatThousands } from './formatThousands';
3
3
  export { default as getFlattenedColorValue } from './getFlattenedColorValue';
4
4
  export { default as getStyleValue } from './getStyleValue';
5
5
  export { default as hexWithOpacity } from './hexWithOpacity';
6
+ export { getInitials } from './getInitials';
6
7
  export { default as isEqual } from './isEqual';
7
8
  export * from './styleUtils';
8
9
  export * from './themeValueHelpers';
@@ -3,6 +3,7 @@ export { default as formatThousands } from './formatThousands';
3
3
  export { default as getFlattenedColorValue } from './getFlattenedColorValue';
4
4
  export { default as getStyleValue } from './getStyleValue';
5
5
  export { default as hexWithOpacity } from './hexWithOpacity';
6
+ export { getInitials } from './getInitials';
6
7
  export { default as isEqual } from './isEqual';
7
8
  export * from './styleUtils';
8
9
  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 value={pillValue} onChange={v => setPillValue(v as string[])} wrap={false} multiple>
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.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Utility Warehouse React Native UI library",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -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;