@utilitywarehouse/hearth-react-native 0.31.1 → 0.32.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 +13 -13
- package/CHANGELOG.md +55 -0
- package/build/components/Rating/Rating.d.ts +6 -0
- package/build/components/Rating/Rating.js +76 -0
- package/build/components/Rating/Rating.props.d.ts +18 -0
- package/build/components/Rating/Rating.props.js +1 -0
- package/build/components/Rating/RatingStarEmpty.d.ts +6 -0
- package/build/components/Rating/RatingStarEmpty.js +9 -0
- package/build/components/Rating/RatingStarFilled.d.ts +6 -0
- package/build/components/Rating/RatingStarFilled.js +9 -0
- package/build/components/Rating/index.d.ts +2 -0
- package/build/components/Rating/index.js +1 -0
- package/build/components/Roundel/Roundel.d.ts +6 -0
- package/build/components/Roundel/Roundel.js +40 -0
- package/build/components/Roundel/Roundel.props.d.ts +6 -0
- package/build/components/Roundel/Roundel.props.js +1 -0
- package/build/components/Roundel/index.d.ts +2 -0
- package/build/components/Roundel/index.js +1 -0
- package/build/components/StepperInput/StepperButton.d.ts +22 -0
- package/build/components/StepperInput/StepperButton.js +55 -0
- package/build/components/StepperInput/StepperInput.d.ts +6 -0
- package/build/components/StepperInput/StepperInput.js +196 -0
- package/build/components/StepperInput/StepperInput.props.d.ts +31 -0
- package/build/components/StepperInput/StepperInput.props.js +1 -0
- package/build/components/StepperInput/index.d.ts +2 -0
- package/build/components/StepperInput/index.js +1 -0
- package/build/components/Textarea/Textarea.d.ts +1 -1
- package/build/components/Textarea/Textarea.js +10 -3
- package/build/components/Textarea/Textarea.props.d.ts +11 -0
- package/build/components/index.d.ts +3 -0
- package/build/components/index.js +3 -0
- package/docs/adding-shadows.mdx +2 -2
- package/docs/changelog.mdx +16 -0
- package/docs/components/AllComponents.web.tsx +30 -1
- package/docs/dark-mode-best-practice.mdx +328 -0
- package/package.json +3 -3
- package/src/components/Modal/Modal.docs.mdx +58 -4
- package/src/components/NavModal/NavModal.docs.mdx +2 -2
- package/src/components/Rating/Rating.docs.mdx +178 -0
- package/src/components/Rating/Rating.figma.tsx +20 -0
- package/src/components/Rating/Rating.props.ts +22 -0
- package/src/components/Rating/Rating.stories.tsx +95 -0
- package/src/components/Rating/Rating.tsx +140 -0
- package/src/components/Rating/RatingStarEmpty.tsx +22 -0
- package/src/components/Rating/RatingStarFilled.tsx +27 -0
- package/src/components/Rating/index.ts +2 -0
- package/src/components/Roundel/Roundel.docs.mdx +48 -0
- package/src/components/Roundel/Roundel.figma.tsx +17 -0
- package/src/components/Roundel/Roundel.props.ts +8 -0
- package/src/components/Roundel/Roundel.stories.tsx +49 -0
- package/src/components/Roundel/Roundel.tsx +51 -0
- package/src/components/Roundel/index.ts +2 -0
- package/src/components/StepperInput/StepperButton.tsx +83 -0
- package/src/components/StepperInput/StepperInput.docs.mdx +121 -0
- package/src/components/StepperInput/StepperInput.figma.tsx +45 -0
- package/src/components/StepperInput/StepperInput.props.ts +39 -0
- package/src/components/StepperInput/StepperInput.stories.tsx +270 -0
- package/src/components/StepperInput/StepperInput.tsx +349 -0
- package/src/components/StepperInput/index.ts +2 -0
- package/src/components/Textarea/Textarea.docs.mdx +2 -0
- package/src/components/Textarea/Textarea.props.ts +11 -0
- package/src/components/Textarea/Textarea.stories.tsx +14 -0
- package/src/components/Textarea/Textarea.tsx +11 -2
- package/src/components/index.ts +3 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as StepperInput } from './StepperInput';
|
|
@@ -7,5 +7,5 @@ export declare const TextareaComponent: import("@gluestack-ui/textarea/lib/types
|
|
|
7
7
|
};
|
|
8
8
|
}, import("react-native").TextInputProps>;
|
|
9
9
|
export declare const TextareaField: import("react").ForwardRefExoticComponent<import("react-native").TextInputProps & import("react").RefAttributes<import("react-native").TextInputProps> & import("@gluestack-ui/textarea/lib/typescript/types").IInputProps>;
|
|
10
|
-
declare const Textarea: ({ validationStatus, children, resizable, disabled, focused, readonly, label, labelVariant, helperText, validText, invalidText, required, helperIcon, onLayout, ...props }: TextareaProps) => import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
declare const Textarea: ({ validationStatus, children, resizable, defaultHeight, disabled, focused, readonly, label, labelVariant, helperText, validText, invalidText, required, helperIcon, onLayout, ...props }: TextareaProps) => import("react/jsx-runtime").JSX.Element;
|
|
11
11
|
export default Textarea;
|
|
@@ -18,7 +18,7 @@ export const TextareaField = TextareaComponent.Input;
|
|
|
18
18
|
const DEFAULT_TEXTAREA_HEIGHT = 96;
|
|
19
19
|
const RESIZE_HANDLE_TOUCH_SIZE = 28;
|
|
20
20
|
const RESIZE_HANDLE_ICON_SIZE = 9;
|
|
21
|
-
const Textarea = ({ validationStatus = 'initial', children, resizable = false, disabled, focused, readonly, label, labelVariant, helperText, validText, invalidText, required, helperIcon, onLayout, ...props }) => {
|
|
21
|
+
const Textarea = ({ validationStatus = 'initial', children, resizable = false, defaultHeight, disabled, focused, readonly, label, labelVariant, helperText, validText, invalidText, required, helperIcon, onLayout, ...props }) => {
|
|
22
22
|
const formFieldContext = useFormFieldContext();
|
|
23
23
|
const hasMeasuredHeight = useRef(false);
|
|
24
24
|
const textareaLabel = label ?? formFieldContext?.label;
|
|
@@ -29,14 +29,21 @@ const Textarea = ({ validationStatus = 'initial', children, resizable = false, d
|
|
|
29
29
|
const textareaDisabled = disabled ?? formFieldContext?.disabled;
|
|
30
30
|
const textareaReadonly = readonly ?? formFieldContext?.readonly;
|
|
31
31
|
const textareaValidationStatus = formFieldContext?.validationStatus ?? validationStatus;
|
|
32
|
-
const
|
|
33
|
-
const
|
|
32
|
+
const textareaDefaultHeight = defaultHeight ?? DEFAULT_TEXTAREA_HEIGHT;
|
|
33
|
+
const textareaHeight = useSharedValue(textareaDefaultHeight);
|
|
34
|
+
const resizeStartHeight = useSharedValue(textareaDefaultHeight);
|
|
34
35
|
const theme = useTheme();
|
|
35
36
|
useEffect(() => {
|
|
36
37
|
if (formFieldContext?.setShouldHandleAccessibility) {
|
|
37
38
|
formFieldContext.setShouldHandleAccessibility(true);
|
|
38
39
|
}
|
|
39
40
|
}, [formFieldContext]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!hasMeasuredHeight.current) {
|
|
43
|
+
textareaHeight.value = textareaDefaultHeight;
|
|
44
|
+
resizeStartHeight.value = textareaDefaultHeight;
|
|
45
|
+
}
|
|
46
|
+
}, [resizeStartHeight, textareaDefaultHeight, textareaHeight]);
|
|
40
47
|
const getAccessibilityLabel = () => {
|
|
41
48
|
let accessibilityLabel = '';
|
|
42
49
|
if (textareaLabel) {
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { TextInputProps, ViewProps } from 'react-native';
|
|
2
2
|
export interface TextareaBaseProps {
|
|
3
|
+
/**
|
|
4
|
+
* Sets the initial height of a resizable textarea in pixels.
|
|
5
|
+
* Has no effect unless `resizable` is enabled.
|
|
6
|
+
*
|
|
7
|
+
* @type number
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <Textarea resizable defaultHeight={140} />
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
defaultHeight?: number;
|
|
3
14
|
/**
|
|
4
15
|
* If true, the textarea can be resized vertically using a drag handle.
|
|
5
16
|
*
|
|
@@ -47,11 +47,14 @@ export * from './ProgressBar';
|
|
|
47
47
|
export * from './ProgressStepper';
|
|
48
48
|
export * from './Radio';
|
|
49
49
|
export * from './RadioCard';
|
|
50
|
+
export * from './Rating';
|
|
51
|
+
export * from './Roundel';
|
|
50
52
|
export * from './SectionHeader';
|
|
51
53
|
export * from './SegmentedControl';
|
|
52
54
|
export * from './Select';
|
|
53
55
|
export * from './Skeleton';
|
|
54
56
|
export * from './Spinner';
|
|
57
|
+
export * from './StepperInput';
|
|
55
58
|
export * from './Switch';
|
|
56
59
|
export * from './Table';
|
|
57
60
|
export * from './Tabs';
|
|
@@ -48,11 +48,14 @@ export * from './ProgressBar';
|
|
|
48
48
|
export * from './ProgressStepper';
|
|
49
49
|
export * from './Radio';
|
|
50
50
|
export * from './RadioCard';
|
|
51
|
+
export * from './Rating';
|
|
52
|
+
export * from './Roundel';
|
|
51
53
|
export * from './SectionHeader';
|
|
52
54
|
export * from './SegmentedControl';
|
|
53
55
|
export * from './Select';
|
|
54
56
|
export * from './Skeleton';
|
|
55
57
|
export * from './Spinner';
|
|
58
|
+
export * from './StepperInput';
|
|
56
59
|
export * from './Switch';
|
|
57
60
|
export * from './Table';
|
|
58
61
|
export * from './Tabs';
|
package/docs/adding-shadows.mdx
CHANGED
|
@@ -62,6 +62,6 @@ const MyComponent = () => <Card shadowColor="brand">{/* Card content */}</Card>;
|
|
|
62
62
|
<NextPrevPage
|
|
63
63
|
prevLink="all-components"
|
|
64
64
|
prevTitle="All Components"
|
|
65
|
-
nextLink="
|
|
66
|
-
nextTitle="
|
|
65
|
+
nextLink="guides-dark-mode-best-practice"
|
|
66
|
+
nextTitle="Dark Mode Best Practice"
|
|
67
67
|
/>
|
package/docs/changelog.mdx
CHANGED
|
@@ -9,6 +9,22 @@ import { BackToTopButton, NextPrevPage } from './components';
|
|
|
9
9
|
The changelog for the Hearth React Native library. Here you can find all the changes, improvements, and bug fixes for each version.
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
## 0.31.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- [#1119](https://github.com/utilitywarehouse/hearth/pull/1119) [`19415d4`](https://github.com/utilitywarehouse/hearth/commit/19415d4d54458b3fb019df6647b9a5e4c375b672) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Refresh dark mode tokens across components and semantic colors.
|
|
17
|
+
|
|
18
|
+
Dark mode color tokens have been updated across semantic and component tokens to improve contrast and visual consistency. This also fixes `TableHeaderCell` text colors so purple and white header variants resolve the correct foreground token.
|
|
19
|
+
|
|
20
|
+
**Components affected**:
|
|
21
|
+
- dark mode tokens
|
|
22
|
+
- `TableHeaderCell`
|
|
23
|
+
|
|
24
|
+
**Developer changes**:
|
|
25
|
+
|
|
26
|
+
No code changes are required unless you rely on the previous dark mode token values or visual snapshots.
|
|
27
|
+
|
|
12
28
|
## 0.31.0
|
|
13
29
|
|
|
14
30
|
### Minor Changes
|
|
@@ -87,12 +87,15 @@ import {
|
|
|
87
87
|
RadioCard,
|
|
88
88
|
RadioCardGroup,
|
|
89
89
|
RadioGroup,
|
|
90
|
+
Rating,
|
|
91
|
+
Roundel,
|
|
90
92
|
SectionHeader,
|
|
91
93
|
SegmentedControl,
|
|
92
94
|
SegmentedControlOption,
|
|
93
95
|
Select,
|
|
94
96
|
Skeleton,
|
|
95
97
|
Spinner,
|
|
98
|
+
StepperInput,
|
|
96
99
|
Switch,
|
|
97
100
|
Tab,
|
|
98
101
|
Table,
|
|
@@ -145,6 +148,8 @@ const ComponentWrapper = ({
|
|
|
145
148
|
const AllComponents: React.FC = () => {
|
|
146
149
|
const [comboboxValue, setComboboxValue] = React.useState<string | null>('uk');
|
|
147
150
|
const [selectValue, setSelectValue] = React.useState('1');
|
|
151
|
+
const [stepperValue, setStepperValue] = React.useState('10');
|
|
152
|
+
const [ratingValue, setRatingValue] = React.useState<0 | 1 | 2 | 3 | 4 | 5>(3);
|
|
148
153
|
const [toggleButtonValue, setToggleButtonValue] = React.useState('');
|
|
149
154
|
const bottomSheetRef = useRef<BottomSheet>(null);
|
|
150
155
|
const handleOpenPress = useCallback(() => {
|
|
@@ -233,6 +238,7 @@ const AllComponents: React.FC = () => {
|
|
|
233
238
|
</View>
|
|
234
239
|
</Center>
|
|
235
240
|
</ComponentWrapper>
|
|
241
|
+
|
|
236
242
|
<ComponentWrapper name="Banner" link="components-banner">
|
|
237
243
|
<Center flex={1} p="200">
|
|
238
244
|
<Banner
|
|
@@ -733,7 +739,20 @@ const AllComponents: React.FC = () => {
|
|
|
733
739
|
</RadioCardGroup>
|
|
734
740
|
</Center>
|
|
735
741
|
</ComponentWrapper>
|
|
736
|
-
|
|
742
|
+
<ComponentWrapper name="Rating" link="components-rating">
|
|
743
|
+
<Center flex={1} padding="200">
|
|
744
|
+
<Rating value={ratingValue} onChange={setRatingValue} />
|
|
745
|
+
</Center>
|
|
746
|
+
</ComponentWrapper>
|
|
747
|
+
<ComponentWrapper name="Roundel" link="components-roundel">
|
|
748
|
+
<Center flex={1}>
|
|
749
|
+
<Flex direction="row" spacing="md" alignItems="center">
|
|
750
|
+
<Roundel variant="success" />
|
|
751
|
+
<Roundel variant="pending" />
|
|
752
|
+
<Roundel variant="error" />
|
|
753
|
+
</Flex>
|
|
754
|
+
</Center>
|
|
755
|
+
</ComponentWrapper>
|
|
737
756
|
<ComponentWrapper name="Section Header" link="components-section-header">
|
|
738
757
|
<Center flex={1} p="300">
|
|
739
758
|
<SectionHeader
|
|
@@ -790,6 +809,16 @@ const AllComponents: React.FC = () => {
|
|
|
790
809
|
<Switch value={switchEnabled} onValueChange={toggleSwitch} />
|
|
791
810
|
</Center>
|
|
792
811
|
</ComponentWrapper>
|
|
812
|
+
<ComponentWrapper name="Stepper Input" link="forms-stepper-input">
|
|
813
|
+
<Center flex={1} padding="200">
|
|
814
|
+
<StepperInput
|
|
815
|
+
label="Label"
|
|
816
|
+
helperText="Helper text"
|
|
817
|
+
value={stepperValue}
|
|
818
|
+
onChangeText={setStepperValue}
|
|
819
|
+
/>
|
|
820
|
+
</Center>
|
|
821
|
+
</ComponentWrapper>
|
|
793
822
|
<ComponentWrapper name="Table" link="components-table">
|
|
794
823
|
<Center flex={1} px="300">
|
|
795
824
|
<Box style={{ width: 360 }}>
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { Meta } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import { SearchMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
3
|
+
import SceneBroadbandDark from '@utilitywarehouse/hearth-svg-assets/lib/scene-broadband-dark.svg';
|
|
4
|
+
import SceneBroadbandLight from '@utilitywarehouse/hearth-svg-assets/lib/scene-broadband-light.svg';
|
|
5
|
+
import SpotPiggyBankDark from '@utilitywarehouse/hearth-svg-assets/lib/spot-piggy-bank-dark.svg';
|
|
6
|
+
import SpotPiggyBankLight from '@utilitywarehouse/hearth-svg-assets/lib/spot-piggy-bank-light.svg';
|
|
7
|
+
import { ScopedTheme } from 'react-native-unistyles';
|
|
8
|
+
import StorybookLink from '../../../shared/storybook/StorybookLink';
|
|
9
|
+
import { Alert, BodyText, Box, Center, Flex, Heading, Icon } from '../src';
|
|
10
|
+
import { BackToTopButton, NextPrevPage, UsageWrap } from './components';
|
|
11
|
+
|
|
12
|
+
<Meta title="Guides / Dark Mode Best Practice" />
|
|
13
|
+
<BackToTopButton />
|
|
14
|
+
|
|
15
|
+
# Dark Mode Best Practice
|
|
16
|
+
|
|
17
|
+
When designing for dark mode in Hearth React Native, it's important to ensure that your UI remains visually appealing and accessible. Here are some best practices to follow:
|
|
18
|
+
|
|
19
|
+
- [Setting Up Dark Mode](#setting-up-dark-mode)
|
|
20
|
+
- [Use Semantic Theme Colours](#use-semantic-theme-colours)
|
|
21
|
+
- [Use Semantic Utility Props](#use-semantic-utility-props)
|
|
22
|
+
- [Use the asset libraries](#use-the-asset-libraries)
|
|
23
|
+
- [Icons](#icons)
|
|
24
|
+
- [Illustrations](#illustrations)
|
|
25
|
+
- [Animations](#animations)
|
|
26
|
+
- [`ThemedImage` component](#themedimage-component)
|
|
27
|
+
|
|
28
|
+
<br />
|
|
29
|
+
<Box my="400">
|
|
30
|
+
<Alert text="By default, all components adapt to the current themed colour mode. This guide will help you understand how to work with and easily support dark mode in your apps using Hearth React Native." />
|
|
31
|
+
</Box>
|
|
32
|
+
<br />
|
|
33
|
+
<br />
|
|
34
|
+
|
|
35
|
+
## Setting Up Dark Mode
|
|
36
|
+
|
|
37
|
+
By default Hearth React Native theme is set to light mode. To enable dark mode, you can set the `colorMode` property in your theme configuration to `'dark'`.
|
|
38
|
+
This will automatically apply the dark mode color palette and styles across your app.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { useEffect } from 'react';
|
|
42
|
+
import { Appearance } from 'react-native';
|
|
43
|
+
import { useColorMode } from '@utilitywarehouse/hearth-react-native';
|
|
44
|
+
|
|
45
|
+
const App = () => {
|
|
46
|
+
// To set the colour mode use the useColorMode hook and set the color mode
|
|
47
|
+
// to the current system preference on app load
|
|
48
|
+
const [colorMode, setColorMode] = useColorMode();
|
|
49
|
+
|
|
50
|
+
// You can optionally set the theme from the system preference on app load or
|
|
51
|
+
// load from async storage if you are persisting the user's theme choice
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
setColorMode(Appearance.getColorScheme() || 'light');
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
const toggleColorMode = () => {
|
|
57
|
+
setColorMode(colorMode === 'light' ? 'dark' : 'light');
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (/* Your app content */);
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
You can also use the Unistyles runtime to set the theme outside of React components, [read more here](https://www.unistyl.es/v3/guides/theming/#change-theme):
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { UnistylesRuntime } from 'react-native-unistyles';
|
|
68
|
+
|
|
69
|
+
const toggleTheme = () => {
|
|
70
|
+
const theme = UnistylesRuntime.getTheme();
|
|
71
|
+
UnistylesRuntime.setTheme(theme === 'dark' ? 'light' : 'dark');
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Use Semantic Theme Colours
|
|
76
|
+
|
|
77
|
+
When styling your components, it's best to use the semantic colour tokens provided by the Hearth theme. This ensures that your colours will
|
|
78
|
+
automatically adapt to both light and dark modes without needing to write custom styles for each mode.
|
|
79
|
+
|
|
80
|
+
To use the semantic colours, you can access them from the theme object in your styles:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { StyleSheet } from '@utilitywarehouse/hearth-react-native';
|
|
84
|
+
|
|
85
|
+
const styles = StyleSheet.create(theme => ({
|
|
86
|
+
text: {
|
|
87
|
+
color: theme.colors.text.primary, // Use semantic colour token
|
|
88
|
+
},
|
|
89
|
+
}));
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
You can also use the `useTheme` hook to access the theme colours directly in your components:
|
|
93
|
+
|
|
94
|
+
<UsageWrap>
|
|
95
|
+
<Center>
|
|
96
|
+
<Flex direction="row" spacing="lg">
|
|
97
|
+
<ScopedTheme name="dark">
|
|
98
|
+
<Box backgroundColor="primary" p="400">
|
|
99
|
+
<Center>
|
|
100
|
+
<BodyText>This text adapts to dark mode!</BodyText>
|
|
101
|
+
</Center>
|
|
102
|
+
</Box>
|
|
103
|
+
</ScopedTheme>
|
|
104
|
+
<ScopedTheme name="light">
|
|
105
|
+
<Box backgroundColor="primary" p="400">
|
|
106
|
+
<Center>
|
|
107
|
+
<BodyText>This text adapts to light mode!</BodyText>
|
|
108
|
+
</Center>
|
|
109
|
+
</Box>
|
|
110
|
+
</ScopedTheme>
|
|
111
|
+
</Flex>
|
|
112
|
+
</Center>
|
|
113
|
+
</UsageWrap>
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { useTheme } from '@utilitywarehouse/hearth-react-native';
|
|
117
|
+
|
|
118
|
+
const MyComponent = () => {
|
|
119
|
+
const theme = useTheme();
|
|
120
|
+
|
|
121
|
+
return <Text style={{ color: theme.colors.text.primary }}>This text adapts to dark mode!</Text>;
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
To learn more about the available colours in the Hearth theme, check out the <StorybookLink to="theme-tokens">theme tokens documentation</StorybookLink>.
|
|
126
|
+
|
|
127
|
+
## Use Semantic Utility Props
|
|
128
|
+
|
|
129
|
+
In addition to using semantic colour tokens, you should also use the semantic utility props provided by Hearth components.
|
|
130
|
+
These props are designed to work with the theme and will automatically adjust their styles based on the current colour mode.
|
|
131
|
+
|
|
132
|
+
For example, instead of setting a background colour directly, you can use the `backgroundColor` or, for text, the `color` prop with a semantic colour value:
|
|
133
|
+
|
|
134
|
+
<UsageWrap>
|
|
135
|
+
<Center spacing="lg">
|
|
136
|
+
<Box>
|
|
137
|
+
<BodyText weight="semibold" mb="100">
|
|
138
|
+
{'Light Mode'}
|
|
139
|
+
</BodyText>
|
|
140
|
+
<Flex direction="row" spacing="lg">
|
|
141
|
+
<ScopedTheme name="light">
|
|
142
|
+
<Box backgroundColor="brand" p="400">
|
|
143
|
+
<BodyText color="inverted">
|
|
144
|
+
This branded box and text adapt to light or dark mode!
|
|
145
|
+
</BodyText>
|
|
146
|
+
</Box>
|
|
147
|
+
<Box backgroundColor="secondary" p="400">
|
|
148
|
+
<BodyText color="primary">This box and text adapt to light or dark mode!</BodyText>
|
|
149
|
+
<BodyText color="secondary">This secondary text adapt to light or dark mode!</BodyText>
|
|
150
|
+
</Box>
|
|
151
|
+
</ScopedTheme>
|
|
152
|
+
</Flex>
|
|
153
|
+
</Box>
|
|
154
|
+
<Box>
|
|
155
|
+
<BodyText weight="semibold" mb="100">
|
|
156
|
+
{'Dark Mode'}
|
|
157
|
+
</BodyText>
|
|
158
|
+
<Flex direction="row" spacing="lg">
|
|
159
|
+
<ScopedTheme name="dark">
|
|
160
|
+
<Box backgroundColor="brand" p="400">
|
|
161
|
+
<BodyText color="inverted">
|
|
162
|
+
This branded box and text adapt to light or dark mode!
|
|
163
|
+
</BodyText>
|
|
164
|
+
</Box>
|
|
165
|
+
<Box backgroundColor="secondary" p="400">
|
|
166
|
+
<BodyText color="primary">This box and text adapt to light or dark mode!</BodyText>
|
|
167
|
+
<BodyText color="secondary">This secondary text adapt to light or dark mode!</BodyText>
|
|
168
|
+
</Box>
|
|
169
|
+
</ScopedTheme>
|
|
170
|
+
</Flex>
|
|
171
|
+
</Box>
|
|
172
|
+
</Center>
|
|
173
|
+
</UsageWrap>
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<Box backgroundColor="brand" p="400">
|
|
177
|
+
<BodyText color="inverted">This branded box and text adapt to light or dark mode!</BodyText>
|
|
178
|
+
</Box>
|
|
179
|
+
<Box backgroundColor="secondary" p="400">
|
|
180
|
+
<BodyText color="primary">This box and text adapt to light or dark mode!</BodyText>
|
|
181
|
+
<BodyText color="secondary">This secondary text adapt to light or dark mode!</BodyText>
|
|
182
|
+
</Box>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
By using these semantic utility props, you can ensure that your components will look great and be accessible in both light and dark
|
|
186
|
+
modes without needing to write custom styles for each mode.
|
|
187
|
+
|
|
188
|
+
## Use the asset libraries
|
|
189
|
+
|
|
190
|
+
Hearth provides asset libraries for icons and illustrations that are designed to work well in both light and dark modes.
|
|
191
|
+
When using these assets, make sure to choose the appropriate version (light or dark) based on the current colour mode.
|
|
192
|
+
|
|
193
|
+
### Icons
|
|
194
|
+
|
|
195
|
+
When using icons from the `@utilitywarehouse/hearth-react-native-icons` library, you can automatically handle light and dark mode
|
|
196
|
+
by using the `color` prop and the `Icon` component with a semantic colour token (by default the `Icon` will use the `theme.colors.icon.primary`
|
|
197
|
+
colour which adapts to light and dark mode):
|
|
198
|
+
|
|
199
|
+
<UsageWrap>
|
|
200
|
+
<Center>
|
|
201
|
+
<Flex direction="row" spacing="lg">
|
|
202
|
+
<ScopedTheme name="light">
|
|
203
|
+
<Box p="400" backgroundColor="secondary">
|
|
204
|
+
<Icon as={SearchMediumIcon} />
|
|
205
|
+
</Box>
|
|
206
|
+
</ScopedTheme>
|
|
207
|
+
<ScopedTheme name="dark">
|
|
208
|
+
<Box p="400" backgroundColor="secondary">
|
|
209
|
+
<Icon as={SearchMediumIcon} color="warmWhite0" />
|
|
210
|
+
</Box>
|
|
211
|
+
</ScopedTheme>
|
|
212
|
+
</Flex>
|
|
213
|
+
</Center>
|
|
214
|
+
</UsageWrap>
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { Icon , useTheme } from '@utilitywarehouse/hearth-react-native';
|
|
218
|
+
import { SearchMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
219
|
+
|
|
220
|
+
<Icon as={SearchMediumIcon} />;
|
|
221
|
+
|
|
222
|
+
// Or to specify a colour
|
|
223
|
+
const theme = useTheme();
|
|
224
|
+
...
|
|
225
|
+
<SearchMediumIcon color={theme.colors.icon.primary} />;
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
To learn more about the available icons, check out the [icon library documentation](https://hearth.prod.uw.systems/icons/?path=/docs/introduction--docs).
|
|
229
|
+
|
|
230
|
+
### Illustrations
|
|
231
|
+
|
|
232
|
+
When using illustrations from the `@utilitywarehouse/hearth-svg-assets` library, you'll need to import both the light and dark versions
|
|
233
|
+
of the illustration and conditionally render the appropriate one based on the current colour mode.
|
|
234
|
+
|
|
235
|
+
<UsageWrap>
|
|
236
|
+
<Center>
|
|
237
|
+
<Flex direction="row" spacing="lg">
|
|
238
|
+
<ScopedTheme name="light">
|
|
239
|
+
<Box p="400" backgroundColor="secondary">
|
|
240
|
+
<SceneBroadbandLight width={200} height={200} />
|
|
241
|
+
</Box>
|
|
242
|
+
</ScopedTheme>
|
|
243
|
+
<ScopedTheme name="dark">
|
|
244
|
+
<Box p="400" backgroundColor="secondary">
|
|
245
|
+
<SceneBroadbandDark width={200} height={200} />
|
|
246
|
+
</Box>
|
|
247
|
+
</ScopedTheme>
|
|
248
|
+
</Flex>
|
|
249
|
+
</Center>
|
|
250
|
+
</UsageWrap>
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
import { useColorMode } from '@utilitywarehouse/hearth-react-native';
|
|
254
|
+
import SceneBroadbandDark from '@utilitywarehouse/hearth-svg-assets/lib/scene-broadband-dark.svg';
|
|
255
|
+
import SceneBroadbandLight from '@utilitywarehouse/hearth-svg-assets/lib/scene-broadband-light.svg';
|
|
256
|
+
|
|
257
|
+
const MyComponent = () => {
|
|
258
|
+
const [colorMode] = useColorMode();
|
|
259
|
+
|
|
260
|
+
return colorMode === 'dark' ? (
|
|
261
|
+
<SceneBroadbandDark width={200} height={200} />
|
|
262
|
+
) : (
|
|
263
|
+
<SceneBroadbandLight width={200} height={200} />
|
|
264
|
+
);
|
|
265
|
+
};
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The above example is to illustrate the concept of handling light and dark mode with illustrations,
|
|
269
|
+
but you should use a reusable `ThemedImage` component to simplify this pattern, which we will cover in the next section.
|
|
270
|
+
|
|
271
|
+
You can view the available illustrations in the [Hearth SVG asset library documentation](https://hearth.prod.uw.systems/assets/?path=/docs/introduction--docs).
|
|
272
|
+
|
|
273
|
+
### Animations
|
|
274
|
+
|
|
275
|
+
When using animations from the `@utilitywarehouse/hearth-json-assets` library, you can also import both light and dark versions of the animation.
|
|
276
|
+
|
|
277
|
+
We currently only have the light variation of the animations available, but when the dark versions are added you can use a similar approach to
|
|
278
|
+
the illustrations example above to conditionally render the appropriate version based on the current colour mode.
|
|
279
|
+
|
|
280
|
+
You can view the available animations in the [Hearth JSON asset library documentation](https://hearth.prod.uw.systems/assets/?path=/docs/introduction--docs).
|
|
281
|
+
|
|
282
|
+
## `ThemedImage` component
|
|
283
|
+
|
|
284
|
+
To simplify the process of handling light and dark mode for images, you can create a reusable `ThemedImage` component that takes both light and dark versions of an image as props and automatically renders the correct one based on the current colour mode.
|
|
285
|
+
Here's an example implementation of a `ThemedImage` component:
|
|
286
|
+
|
|
287
|
+
<UsageWrap>
|
|
288
|
+
<Center>
|
|
289
|
+
<Flex direction="row" spacing="lg">
|
|
290
|
+
<ScopedTheme name="light">
|
|
291
|
+
<Box p="400" backgroundColor="secondary">
|
|
292
|
+
<SpotPiggyBankLight width={200} height={200} />
|
|
293
|
+
</Box>
|
|
294
|
+
</ScopedTheme>
|
|
295
|
+
<ScopedTheme name="dark">
|
|
296
|
+
<Box p="400" backgroundColor="secondary">
|
|
297
|
+
<SpotPiggyBankDark width={200} height={200} />
|
|
298
|
+
</Box>
|
|
299
|
+
</ScopedTheme>
|
|
300
|
+
</Flex>
|
|
301
|
+
</Center>
|
|
302
|
+
</UsageWrap>
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
import React from 'react';
|
|
306
|
+
import { ThemedImage } from '@utilitywarehouse/hearth-react-native';
|
|
307
|
+
import SpotPiggyBankLight from '@utilitywarehouse/hearth-svg-assets/lib/spot-piggy-bank-light.svg';
|
|
308
|
+
import SpotPiggyBankDark from '@utilitywarehouse/hearth-svg-assets/lib/spot-piggy-bank-dark.svg';
|
|
309
|
+
|
|
310
|
+
const MyComponent = () => {
|
|
311
|
+
return (
|
|
312
|
+
<ThemedImage
|
|
313
|
+
light={SpotPiggyBankLight}
|
|
314
|
+
dark={SpotPiggyBankDark}
|
|
315
|
+
style={{ width: 200, height: 200 }}
|
|
316
|
+
/>
|
|
317
|
+
);
|
|
318
|
+
};
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
See the full `ThemedImage` docs and implementation in the <StorybookLink to="utility-components-themed-image">`ThemedImage` documentation</StorybookLink>.
|
|
322
|
+
|
|
323
|
+
<NextPrevPage
|
|
324
|
+
prevLink="guides-adding-shadows"
|
|
325
|
+
prevTitle="Adding Shadows"
|
|
326
|
+
nextLink="primitives-box"
|
|
327
|
+
nextTitle="Box"
|
|
328
|
+
/>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilitywarehouse/hearth-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Utility Warehouse React Native UI library",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"vite-plugin-svgr": "^4.5.0",
|
|
59
59
|
"vitest": "^3.2.4",
|
|
60
60
|
"@utilitywarehouse/hearth-fonts": "^0.0.4",
|
|
61
|
+
"@utilitywarehouse/hearth-react-icons": "^0.8.0",
|
|
61
62
|
"@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
|
|
62
63
|
"@utilitywarehouse/hearth-svg-assets": "^0.6.0",
|
|
63
|
-
"@utilitywarehouse/hearth-tokens": "^0.2.4"
|
|
64
|
-
"@utilitywarehouse/hearth-react-icons": "^0.8.0"
|
|
64
|
+
"@utilitywarehouse/hearth-tokens": "^0.2.4"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"@gorhom/bottom-sheet": ">=5.0.0",
|
|
@@ -12,11 +12,14 @@ import * as Stories from './Modal.stories';
|
|
|
12
12
|
|
|
13
13
|
# Modal
|
|
14
14
|
|
|
15
|
-
The `Modal` component provides a versatile dialog interface that slides up from the bottom of the screen. It's built on top of
|
|
15
|
+
The `Modal` component provides a versatile dialog interface that slides up from the bottom of the screen. It's built on top of
|
|
16
|
+
the <StorybookLink to="components-bottom-sheet">`BottomSheetModal`</StorybookLink>
|
|
17
|
+
component and includes pre-configured layouts for common modal patterns including headers, content areas, and action buttons.
|
|
16
18
|
|
|
17
|
-
The Modal component is ideal for displaying important information, collecting user input, or presenting choices that require user attention
|
|
19
|
+
The Modal component is ideal for displaying important information, collecting user input, or presenting choices that require user attention
|
|
20
|
+
without navigating away from the current screen.
|
|
18
21
|
|
|
19
|
-
If you need a modal layout inside a React Navigation modal screen, use <StorybookLink to="components-
|
|
22
|
+
If you need a modal layout inside a React Navigation modal screen, use <StorybookLink to="components-nav-modal">`NavModal`</StorybookLink> instead.
|
|
20
23
|
|
|
21
24
|
- [Playground](#playground)
|
|
22
25
|
- [Usage](#usage)
|
|
@@ -32,6 +35,8 @@ If you need a modal layout inside a React Navigation modal screen, use <Storyboo
|
|
|
32
35
|
- [Loading State](#loading-state)
|
|
33
36
|
- [Without Close Button](#without-close-button)
|
|
34
37
|
- [Single Action Modal](#single-action-modal)
|
|
38
|
+
- [Navigation Modals](#navigation-modals)
|
|
39
|
+
- [Close handlers and state management](#close-handlers-and-state-management)
|
|
35
40
|
- [Integration Notes](#integration-notes)
|
|
36
41
|
- [External Resources](#external-resources)
|
|
37
42
|
|
|
@@ -514,6 +519,53 @@ const AlertModal = () => {
|
|
|
514
519
|
|
|
515
520
|
For React Navigation modal screens, use <StorybookLink to="components-navmodal">`NavModal`</StorybookLink>. It contains the extracted screen-based modal layout, background variants, scrollable content handling, and the Android `triggerCloseAnimation()` ref used during navigation dismissal.
|
|
516
521
|
|
|
522
|
+
### Close handlers and state management
|
|
523
|
+
|
|
524
|
+
The Modal component provides multiple ways to handle closing the modal and managing state:
|
|
525
|
+
|
|
526
|
+
- Use the `onPressPrimaryButton`, `onPressSecondaryButton`, and `onPressCloseButton` props to run custom logic when buttons are pressed, such as form validation or API calls, before closing the modal.
|
|
527
|
+
- Control whether the modal should automatically close when action buttons are pressed using the `closeOnPrimaryButtonPress` and `closeOnSecondaryButtonPress` props. This allows you to keep the modal
|
|
528
|
+
open while performing async operations and only close it when those operations are complete.
|
|
529
|
+
- Use the `onChange` prop to detect when the modal is opened or closed based on the index parameter (0 for open, -1 for closed) and manage state accordingly.
|
|
530
|
+
|
|
531
|
+
```tsx
|
|
532
|
+
const MyModal = () => {
|
|
533
|
+
const modalRef = useRef<BottomSheetModal>(null);
|
|
534
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
535
|
+
|
|
536
|
+
const handleSubmit = async () => {
|
|
537
|
+
setIsSubmitting(true);
|
|
538
|
+
// Simulate API call
|
|
539
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
540
|
+
setIsSubmitting(false);
|
|
541
|
+
modalRef.current?.dismiss();
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
return (
|
|
545
|
+
<>
|
|
546
|
+
<Button onPress={() => modalRef.current?.present()}>Open Modal</Button>
|
|
547
|
+
|
|
548
|
+
<Modal
|
|
549
|
+
ref={modalRef}
|
|
550
|
+
heading="Submit Data"
|
|
551
|
+
description="Please confirm your submission"
|
|
552
|
+
primaryButtonText="Submit"
|
|
553
|
+
secondaryButtonText="Cancel"
|
|
554
|
+
onPressPrimaryButton={handleSubmit}
|
|
555
|
+
closeOnPrimaryButtonPress={false} // Keep modal open while submitting
|
|
556
|
+
onChange={index => {
|
|
557
|
+
if (index === -1) {
|
|
558
|
+
// Modal closed, reset state if needed
|
|
559
|
+
setIsSubmitting(false);
|
|
560
|
+
}
|
|
561
|
+
}}
|
|
562
|
+
loading={isSubmitting}
|
|
563
|
+
/>
|
|
564
|
+
</>
|
|
565
|
+
);
|
|
566
|
+
};
|
|
567
|
+
```
|
|
568
|
+
|
|
517
569
|
## Integration Notes
|
|
518
570
|
|
|
519
571
|
### BottomSheetModalProvider
|
|
@@ -567,4 +619,6 @@ modalRef.current?.snapToIndex(1);
|
|
|
567
619
|
|
|
568
620
|
## External Resources
|
|
569
621
|
|
|
570
|
-
This component is built on top of [@gorhom/bottom-sheet](https://gorhom.github.io/react-native-bottom-sheet/) (v5). For more information about the
|
|
622
|
+
This component is built on top of [@gorhom/bottom-sheet](https://gorhom.github.io/react-native-bottom-sheet/) (v5). For more information about the
|
|
623
|
+
underlying BottomSheet functionality, please refer to the <StorybookLink to="components-bottomsheet">BottomSheet documentation</StorybookLink> and
|
|
624
|
+
the [official documentation](https://gorhom.dev/react-native-bottom-sheet/).
|
|
@@ -6,13 +6,13 @@ import modaliOSVideo from '../../../docs/assets/modal-ios.mp4';
|
|
|
6
6
|
import { BackToTopButton, ViewFigmaButton } from '../../../docs/components';
|
|
7
7
|
import * as Stories from './NavModal.stories';
|
|
8
8
|
|
|
9
|
-
<Meta title="Components /
|
|
9
|
+
<Meta title="Components / Nav Modal" />
|
|
10
10
|
|
|
11
11
|
<ViewFigmaButton url="https://www.figma.com/design/dLI9bmyMr42LV7dtFeW27J/Hearth-Patterns---Guides?node-id=6314-9103&t=oq3NaPLaAu3di6Db-4" />
|
|
12
12
|
|
|
13
13
|
<BackToTopButton />
|
|
14
14
|
|
|
15
|
-
#
|
|
15
|
+
# Nav Modal
|
|
16
16
|
|
|
17
17
|
The `NavModal` component is the screen-based modal layout for navigation flows. Use it when a screen is already being presented by React Navigation with `presentation: 'modal'` or `presentation: 'fullScreenModal'` and you want Hearth's modal structure, actions, and Android close animation support.
|
|
18
18
|
|