@utilitywarehouse/hearth-react-native 0.31.0 → 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 +71 -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/Table/TableHeaderCell.js +10 -1
- 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/build/core/themes.d.ts +92 -88
- package/build/tokens/color.d.ts +82 -80
- package/build/tokens/color.js +41 -40
- package/build/tokens/components/dark/alert.d.ts +6 -6
- package/build/tokens/components/dark/alert.js +6 -6
- package/build/tokens/components/dark/bottom-navigation.d.ts +2 -2
- package/build/tokens/components/dark/bottom-navigation.js +2 -2
- package/build/tokens/components/dark/checkbox.d.ts +1 -1
- package/build/tokens/components/dark/checkbox.js +1 -1
- package/build/tokens/components/dark/icon-button.d.ts +3 -3
- package/build/tokens/components/dark/icon-button.js +3 -3
- package/build/tokens/components/dark/inline-link.d.ts +1 -1
- package/build/tokens/components/dark/inline-link.js +1 -1
- package/build/tokens/components/dark/link.d.ts +3 -3
- package/build/tokens/components/dark/link.js +3 -3
- package/build/tokens/components/dark/navigation.d.ts +2 -2
- package/build/tokens/components/dark/navigation.js +2 -2
- package/build/tokens/components/dark/parts.d.ts +2 -2
- package/build/tokens/components/dark/parts.js +2 -2
- package/build/tokens/components/dark/progress-bar.d.ts +3 -3
- package/build/tokens/components/dark/progress-bar.js +3 -3
- package/build/tokens/components/dark/progress-stepper.d.ts +1 -1
- package/build/tokens/components/dark/progress-stepper.js +1 -1
- package/build/tokens/components/dark/spinner.d.ts +1 -1
- package/build/tokens/components/dark/spinner.js +1 -1
- package/build/tokens/components/dark/table.d.ts +2 -0
- package/build/tokens/components/dark/table.js +2 -0
- package/build/tokens/components/dark/time-picker.d.ts +1 -0
- package/build/tokens/components/dark/time-picker.js +1 -0
- package/build/tokens/components/light/parts.d.ts +3 -3
- package/build/tokens/components/light/parts.js +3 -3
- package/build/tokens/components/light/table.d.ts +2 -0
- package/build/tokens/components/light/table.js +2 -0
- package/build/tokens/components/light/time-picker.d.ts +1 -0
- package/build/tokens/components/light/time-picker.js +1 -0
- package/build/tokens/semantic-dark.d.ts +40 -40
- package/build/tokens/semantic-dark.js +40 -40
- package/docs/adding-shadows.mdx +2 -2
- package/docs/changelog.mdx +165 -0
- package/docs/components/AllComponents.web.tsx +30 -1
- package/docs/dark-mode-best-practice.mdx +328 -0
- package/package.json +1 -1
- 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/Table/TableHeaderCell.tsx +10 -1
- 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
- package/src/tokens/color.ts +41 -40
- package/src/tokens/components/dark/alert.ts +6 -6
- package/src/tokens/components/dark/bottom-navigation.ts +2 -2
- package/src/tokens/components/dark/checkbox.ts +1 -1
- package/src/tokens/components/dark/icon-button.ts +3 -3
- package/src/tokens/components/dark/inline-link.ts +1 -1
- package/src/tokens/components/dark/link.ts +3 -3
- package/src/tokens/components/dark/navigation.ts +2 -2
- package/src/tokens/components/dark/parts.ts +2 -2
- package/src/tokens/components/dark/progress-bar.ts +3 -3
- package/src/tokens/components/dark/progress-stepper.ts +1 -1
- package/src/tokens/components/dark/spinner.ts +1 -1
- package/src/tokens/components/dark/table.ts +2 -0
- package/src/tokens/components/dark/time-picker.ts +1 -0
- package/src/tokens/components/light/parts.ts +3 -3
- package/src/tokens/components/light/table.ts +2 -0
- package/src/tokens/components/light/time-picker.ts +1 -0
- package/src/tokens/semantic-dark.ts +40 -40
- package/vercel.json +0 -21
package/docs/changelog.mdx
CHANGED
|
@@ -9,6 +9,171 @@ 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
|
+
|
|
28
|
+
## 0.31.0
|
|
29
|
+
|
|
30
|
+
### Minor Changes
|
|
31
|
+
|
|
32
|
+
- [#1108](https://github.com/utilitywarehouse/hearth/pull/1108) [`8c2f3b5`](https://github.com/utilitywarehouse/hearth/commit/8c2f3b5334de83a7dd799b857394a34209b748e7) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add margin, padding, and layout utility props to `Flex`.
|
|
33
|
+
|
|
34
|
+
`Flex` now exposes the shared margin and padding utility props, along with the existing layout utility prop surface, so it can be used more like other layout primitives such as `Card`.
|
|
35
|
+
|
|
36
|
+
**Component affected**:
|
|
37
|
+
- `Flex`
|
|
38
|
+
|
|
39
|
+
**Developer changes**:
|
|
40
|
+
|
|
41
|
+
You can now apply spacing and layout utility props directly on `Flex`:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<Flex direction="row" spacing="md" padding="300" marginTop="200" flex={1}>
|
|
45
|
+
<Button>Back</Button>
|
|
46
|
+
<Button>Continue</Button>
|
|
47
|
+
</Flex>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- [#1104](https://github.com/utilitywarehouse/hearth/pull/1104) [`91feeab`](https://github.com/utilitywarehouse/hearth/commit/91feeab091ae6bf80e543f9196214066ce8b29b0) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add custom trigger content options to `ExpandableCard`.
|
|
51
|
+
|
|
52
|
+
`ExpandableCard` now supports a `triggerContent` prop for replacing the default trigger layout while keeping the chevron. `ExpandableCardTrigger` also now supports `children` with an optional `showChevron` prop, so you can fully compose the trigger content yourself and still opt in to the built-in expand/collapse chevron.
|
|
53
|
+
|
|
54
|
+
**Components affected**:
|
|
55
|
+
- `ExpandableCard`
|
|
56
|
+
- `ExpandableCardTrigger`
|
|
57
|
+
|
|
58
|
+
**Developer changes**:
|
|
59
|
+
|
|
60
|
+
Use `triggerContent` when you want a single prop on `ExpandableCard` to replace the standard trigger content:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<ExpandableCard
|
|
64
|
+
triggerContent={<BodyText weight="semibold">March bill ready</BodyText>}
|
|
65
|
+
expandedContent={<BodyText>Balance: £89.50</BodyText>}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Use `ExpandableCardTrigger` children when you want full control over the trigger structure. Add `showChevron` if you still want the built-in chevron:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
<ExpandableCardTrigger isExpanded={expanded} onPress={toggleExpanded} showChevron>
|
|
73
|
+
<ExpandableCardContent>
|
|
74
|
+
<ExpandableCardText>Custom trigger</ExpandableCardText>
|
|
75
|
+
</ExpandableCardContent>
|
|
76
|
+
</ExpandableCardTrigger>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- [#1109](https://github.com/utilitywarehouse/hearth/pull/1109) [`8215550`](https://github.com/utilitywarehouse/hearth/commit/8215550745d08a8b4c18a6902e39d3199018092a) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `numberOfLines` support to `Badge` text.
|
|
80
|
+
|
|
81
|
+
`Badge` now renders its text content on a single line by default and supports a new `numberOfLines` prop when you want to allow more lines.
|
|
82
|
+
|
|
83
|
+
**Components affected**:
|
|
84
|
+
- `Badge`
|
|
85
|
+
|
|
86
|
+
**Developer changes**:
|
|
87
|
+
|
|
88
|
+
No changes are required unless you want a `Badge` to wrap onto more than one line. To opt in, pass `numberOfLines`:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<Badge numberOfLines={2} text="Long badge text that can wrap onto a second line" />
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- [#1108](https://github.com/utilitywarehouse/hearth/pull/1108) [`8c2f3b5`](https://github.com/utilitywarehouse/hearth/commit/8c2f3b5334de83a7dd799b857394a34209b748e7) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add custom footer support to `Modal` and `NavModal`.
|
|
95
|
+
|
|
96
|
+
`Modal` and `NavModal` now support a `footer` prop for replacing the built-in primary and secondary action buttons with custom content, plus a `footerStyle` prop for styling the footer container. When `footer` is provided, the button props are now disallowed at the type level.
|
|
97
|
+
|
|
98
|
+
**Components affected**:
|
|
99
|
+
- `Modal`
|
|
100
|
+
- `NavModal`
|
|
101
|
+
|
|
102
|
+
**Developer changes**:
|
|
103
|
+
|
|
104
|
+
Use `footer` when you need a custom footer layout, such as horizontal actions or extra decoration:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<Modal
|
|
108
|
+
heading="Update billing details"
|
|
109
|
+
footer={
|
|
110
|
+
<Flex direction="row" spacing="md" pt="250">
|
|
111
|
+
<Button variant="outline" colorScheme="functional" style={{ flex: 1 }}>
|
|
112
|
+
Cancel
|
|
113
|
+
</Button>
|
|
114
|
+
<Button style={{ flex: 1 }}>Save changes</Button>
|
|
115
|
+
</Flex>
|
|
116
|
+
}
|
|
117
|
+
footerStyle={{
|
|
118
|
+
boxShadow: '0px -6px 12px rgba(16, 24, 40, 0.12)',
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
- [#1103](https://github.com/utilitywarehouse/hearth/pull/1103) [`e375a44`](https://github.com/utilitywarehouse/hearth/commit/e375a442c507da1807e49f4d78e44edfff51d245) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add optional vertical resizing to `Textarea`.
|
|
124
|
+
|
|
125
|
+
`Textarea` now supports a new `resizable` prop that adds a bottom-right drag handle so people can increase the field height when they need more space for longer content.
|
|
126
|
+
|
|
127
|
+
**Components affected**:
|
|
128
|
+
- `Textarea`
|
|
129
|
+
|
|
130
|
+
**Developer changes**:
|
|
131
|
+
|
|
132
|
+
No changes are required unless you want to enable resizing for a textarea. To opt in, pass the new `resizable` prop:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<Textarea
|
|
136
|
+
label="Additional notes"
|
|
137
|
+
helperText="Drag the corner handle to resize"
|
|
138
|
+
placeholder="Enter your text here..."
|
|
139
|
+
resizable
|
|
140
|
+
/>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- [#1108](https://github.com/utilitywarehouse/hearth/pull/1108) [`8c2f3b5`](https://github.com/utilitywarehouse/hearth/commit/8c2f3b5334de83a7dd799b857394a34209b748e7) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add flex utility props to `Container`.
|
|
144
|
+
|
|
145
|
+
`Container` now exposes shared flex layout utility props, allowing layout properties such as `flex`, `alignItems`, `justifyContent`, and `flexDirection` to be applied directly through its public prop API.
|
|
146
|
+
|
|
147
|
+
**Component affected**:
|
|
148
|
+
- `Container`
|
|
149
|
+
|
|
150
|
+
**Developer changes**:
|
|
151
|
+
|
|
152
|
+
You can now combine `Container`'s existing spacing props with flex layout props:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<Container flex={1} justifyContent="center" alignItems="stretch" gap="200">
|
|
156
|
+
<BodyText>Content</BodyText>
|
|
157
|
+
</Container>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Patch Changes
|
|
161
|
+
|
|
162
|
+
- [#1103](https://github.com/utilitywarehouse/hearth/pull/1103) [`e375a44`](https://github.com/utilitywarehouse/hearth/commit/e375a442c507da1807e49f4d78e44edfff51d245) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Render FormField optional text with regular weight instead of inheriting the label weight.
|
|
163
|
+
|
|
164
|
+
This fixes FormField labels that append `(Optional)` so the optional text no longer inherits the main label weight.
|
|
165
|
+
|
|
166
|
+
**Components affected**:
|
|
167
|
+
- `FormField`
|
|
168
|
+
- `Textarea`
|
|
169
|
+
- `Input`
|
|
170
|
+
|
|
171
|
+
**Developer changes**:
|
|
172
|
+
|
|
173
|
+
No changes are required.
|
|
174
|
+
|
|
175
|
+
- [#1104](https://github.com/utilitywarehouse/hearth/pull/1104) [`91feeab`](https://github.com/utilitywarehouse/hearth/commit/91feeab091ae6bf80e543f9196214066ce8b29b0) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: `ExpandableCard` heading font size and weight
|
|
176
|
+
|
|
12
177
|
## 0.30.4
|
|
13
178
|
|
|
14
179
|
### Patch 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
|
@@ -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/).
|