@utilitywarehouse/hearth-react-native 0.27.0 → 0.27.1
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 +42 -0
- package/build/components/Modal/Modal.js +1 -1
- package/build/components/SegmentedControl/SegmentedControlOption.js +21 -2
- package/build/components/SegmentedControl/SegmentedControlOption.props.d.ts +3 -1
- package/package.json +3 -3
- package/src/components/Modal/Modal.tsx +1 -1
- package/src/components/SegmentedControl/SegmentedControl.docs.mdx +42 -15
- package/src/components/SegmentedControl/SegmentedControl.figma.tsx +8 -9
- package/src/components/SegmentedControl/SegmentedControl.stories.tsx +21 -0
- package/src/components/SegmentedControl/SegmentedControlOption.props.ts +3 -1
- package/src/components/SegmentedControl/SegmentedControlOption.tsx +57 -34
- package/src/components/Select/SelectOption.figma.tsx +2 -2
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-lint.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @utilitywarehouse/hearth-react-native@0.27.
|
|
2
|
+
> @utilitywarehouse/hearth-react-native@0.27.1 lint /home/runner/work/hearth/hearth/packages/react-native
|
|
3
3
|
> TIMING=1 eslint .
|
|
4
4
|
|
|
5
5
|
|
|
@@ -58,15 +58,15 @@
|
|
|
58
58
|
|
|
59
59
|
✖ 25 problems (0 errors, 25 warnings)
|
|
60
60
|
|
|
61
|
-
Rule
|
|
62
|
-
|
|
63
|
-
@typescript-eslint/no-unused-vars
|
|
64
|
-
react-hooks/exhaustive-deps
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
no-
|
|
71
|
-
no-
|
|
72
|
-
no-
|
|
61
|
+
Rule | Time (ms) | Relative
|
|
62
|
+
:-----------------------------------------|----------:|--------:
|
|
63
|
+
@typescript-eslint/no-unused-vars | 1499.293 | 61.6%
|
|
64
|
+
react-hooks/exhaustive-deps | 127.430 | 5.2%
|
|
65
|
+
react-hooks/rules-of-hooks | 84.158 | 3.5%
|
|
66
|
+
no-global-assign | 65.981 | 2.7%
|
|
67
|
+
no-unexpected-multiline | 44.926 | 1.8%
|
|
68
|
+
@typescript-eslint/ban-ts-comment | 39.212 | 1.6%
|
|
69
|
+
@typescript-eslint/triple-slash-reference | 36.913 | 1.5%
|
|
70
|
+
no-misleading-character-class | 36.268 | 1.5%
|
|
71
|
+
no-useless-escape | 28.431 | 1.2%
|
|
72
|
+
@typescript-eslint/no-unused-expressions | 25.564 | 1.1%
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @utilitywarehouse/hearth-react-native
|
|
2
2
|
|
|
3
|
+
## 0.27.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#990](https://github.com/utilitywarehouse/hearth/pull/990) [`958e0e1`](https://github.com/utilitywarehouse/hearth/commit/958e0e1a9d5451d1e11fecadc69ae3c5ad9d42ca) Thanks [@declanelcocks](https://github.com/declanelcocks)! - 🐛 [FIX]: Fix `Modal` layout when `inNavModal` and `stickyFooter={false}`.
|
|
8
|
+
|
|
9
|
+
Corrects the container flex style for `inNavModal` modals with a non-sticky footer, where the UX was not great when scrolling.
|
|
10
|
+
|
|
11
|
+
**Components affected**:
|
|
12
|
+
- `Modal`
|
|
13
|
+
|
|
14
|
+
**Developer changes**:
|
|
15
|
+
|
|
16
|
+
No changes required.
|
|
17
|
+
|
|
18
|
+
- [#992](https://github.com/utilitywarehouse/hearth/pull/992) [`2560b3d`](https://github.com/utilitywarehouse/hearth/commit/2560b3dcba7ed4981fad585628f96afd07d8de4f) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Add optional leading `icon` support to `SegmentedControlOption`.
|
|
19
|
+
|
|
20
|
+
This adds an optional `icon` prop to `SegmentedControlOption`, allowing icons to be displayed before option labels in segmented controls.
|
|
21
|
+
|
|
22
|
+
Docs and stories were updated to include icon usage examples.
|
|
23
|
+
|
|
24
|
+
**Components affected**:
|
|
25
|
+
- `SegmentedControlOption`
|
|
26
|
+
|
|
27
|
+
**Developer changes**:
|
|
28
|
+
|
|
29
|
+
No changes required for existing usage.
|
|
30
|
+
|
|
31
|
+
To use the new optional icon prop:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { SegmentedControl, SegmentedControlOption } from '@utilitywarehouse/hearth-react-native';
|
|
35
|
+
import { ElectricitySmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
36
|
+
|
|
37
|
+
<SegmentedControl defaultValue="energy">
|
|
38
|
+
<SegmentedControlOption value="energy" icon={ElectricitySmallIcon}>
|
|
39
|
+
Energy
|
|
40
|
+
</SegmentedControlOption>
|
|
41
|
+
<SegmentedControlOption value="broadband">Broadband</SegmentedControlOption>
|
|
42
|
+
</SegmentedControl>;
|
|
43
|
+
```
|
|
44
|
+
|
|
3
45
|
## 0.27.0
|
|
4
46
|
|
|
5
47
|
### Minor Changes
|
|
@@ -120,7 +120,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
120
120
|
});
|
|
121
121
|
const footer = (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, inverted: isBrandBackground && inNavModal, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, inverted: isBrandBackground && inNavModal, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] }));
|
|
122
122
|
const InNavModalContainer = scrollable ? ScrollView : View;
|
|
123
|
-
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg", color: isBrandBackground && inNavModal ? theme.color.icon.inverted : undefined }), _jsx(Heading, { size: "lg", textAlign: "center", inverted: isBrandBackground && inNavModal, children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description && !image ? (_jsx(BodyText, { accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: isBrandBackground && inNavModal, ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] })] })) : null, inNavModal && (_jsxs(InNavModalContainer, { style: {
|
|
123
|
+
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg", color: isBrandBackground && inNavModal ? theme.color.icon.inverted : undefined }), _jsx(Heading, { size: "lg", textAlign: "center", inverted: isBrandBackground && inNavModal, children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description && !image ? (_jsx(BodyText, { accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: isBrandBackground && inNavModal, ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] })] })) : null, inNavModal && (_jsxs(InNavModalContainer, { style: { flex: stickyFooter ? 1 : 0 }, children: [children, !stickyFooter ? _jsx(View, { style: styles.inNavModalFooterContainer, children: footer }) : null] })), !inNavModal && children, ((!stickyFooter && !inNavModal) || (inNavModal && stickyFooter)) && !noButtons ? footer : null] })) }));
|
|
124
124
|
const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [
|
|
125
125
|
onPressPrimaryButton,
|
|
126
126
|
primaryButtonText,
|
|
@@ -5,9 +5,10 @@ import { Platform, Pressable, View } from 'react-native';
|
|
|
5
5
|
import Animated, { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated';
|
|
6
6
|
import { StyleSheet } from 'react-native-unistyles';
|
|
7
7
|
import { BodyText } from '../BodyText';
|
|
8
|
+
import { Icon } from '../Icon';
|
|
8
9
|
import { useSegmentedControlContext } from './SegmentedControl.context';
|
|
9
10
|
const AnimatedView = Animated.createAnimatedComponent(View);
|
|
10
|
-
const SegmentedControlOptionRoot = ({ value, children, accessibilityLabel, disabled = false, style, states = {}, ...props }) => {
|
|
11
|
+
const SegmentedControlOptionRoot = ({ value, children, icon, accessibilityLabel, disabled = false, style, states = {}, ...props }) => {
|
|
11
12
|
const { value: selectedValue, select, disabled: allDisabled, size, registerOptionLayout, } = useSegmentedControlContext();
|
|
12
13
|
const { active = false } = states;
|
|
13
14
|
const reducedMotion = useReducedMotion();
|
|
@@ -35,7 +36,7 @@ const SegmentedControlOptionRoot = ({ value, children, accessibilityLabel, disab
|
|
|
35
36
|
const accessibleLabel = typeof children === 'string' || typeof children === 'number' ? String(children) : value;
|
|
36
37
|
return (_jsx(Pressable, { ...props, accessibilityRole: "radio", accessibilityState: { checked: selected, disabled: isDisabled }, accessibilityLabel: accessibilityLabel ?? accessibleLabel, onPress: onPress, onLayout: e => registerOptionLayout(value, e.nativeEvent.layout), disabled: isDisabled, style: [styles.option, style], ...(Platform.OS === 'web'
|
|
37
38
|
? { 'aria-label': accessibilityLabel ?? accessibleLabel }
|
|
38
|
-
: null), children: _jsxs(View, { style: styles.
|
|
39
|
+
: null), children: _jsxs(View, { style: styles.contentWrap, accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: [icon ? _jsx(Icon, { as: icon, size: "sm", style: styles.icon }) : null, _jsxs(View, { style: styles.labelWrap, children: [_jsx(BodyText, { size: "md", weight: "semibold", style: styles.labelSizer, accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: children }), _jsx(AnimatedView, { pointerEvents: "none", style: [styles.textLayer, regularLabelStyle], accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: _jsx(BodyText, { size: "md", weight: "regular", style: styles.textRegular, children: children }) }), _jsx(AnimatedView, { pointerEvents: "none", style: [styles.textLayer, selectedLabelStyle], accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: _jsx(BodyText, { size: "md", weight: "semibold", style: styles.textSelected, children: children }) })] })] }) }));
|
|
39
40
|
};
|
|
40
41
|
const SegmentedControlOption = createPressable({ Root: SegmentedControlOptionRoot });
|
|
41
42
|
SegmentedControlOption.displayName = 'SegmentedControlOption';
|
|
@@ -90,6 +91,12 @@ const styles = StyleSheet.create(theme => ({
|
|
|
90
91
|
},
|
|
91
92
|
},
|
|
92
93
|
},
|
|
94
|
+
contentWrap: {
|
|
95
|
+
flexDirection: 'row',
|
|
96
|
+
alignItems: 'center',
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
gap: theme.components.segmentedControl.gap,
|
|
99
|
+
},
|
|
93
100
|
labelWrap: {
|
|
94
101
|
position: 'relative',
|
|
95
102
|
alignItems: 'center',
|
|
@@ -105,6 +112,18 @@ const styles = StyleSheet.create(theme => ({
|
|
|
105
112
|
alignItems: 'center',
|
|
106
113
|
justifyContent: 'center',
|
|
107
114
|
},
|
|
115
|
+
icon: {
|
|
116
|
+
variants: {
|
|
117
|
+
selected: {
|
|
118
|
+
true: {
|
|
119
|
+
color: theme.color.icon.inverted,
|
|
120
|
+
},
|
|
121
|
+
false: {
|
|
122
|
+
color: theme.color.icon.primary,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
108
127
|
textRegular: {
|
|
109
128
|
color: theme.color.text.primary,
|
|
110
129
|
},
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
2
|
import type { PressableProps, ViewProps } from 'react-native';
|
|
3
3
|
export interface SegmentedControlOptionProps extends Omit<PressableProps, 'children'> {
|
|
4
4
|
/** Unique option value. */
|
|
5
5
|
value: string;
|
|
6
6
|
/** Option label/content. */
|
|
7
7
|
children: ReactNode;
|
|
8
|
+
/** Optional leading icon. */
|
|
9
|
+
icon?: ComponentType<any>;
|
|
8
10
|
/** Disables only this option. */
|
|
9
11
|
disabled?: boolean;
|
|
10
12
|
style?: ViewProps['style'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilitywarehouse/hearth-react-native",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.1",
|
|
4
4
|
"description": "Utility Warehouse React Native UI library",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
"vite": "^7.1.3",
|
|
57
57
|
"vite-plugin-svgr": "^4.5.0",
|
|
58
58
|
"vitest": "^3.2.4",
|
|
59
|
+
"@utilitywarehouse/hearth-react-icons": "^0.8.0",
|
|
59
60
|
"@utilitywarehouse/hearth-fonts": "^0.0.4",
|
|
60
61
|
"@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
|
|
61
|
-
"@utilitywarehouse/hearth-svg-assets": "^0.5.0",
|
|
62
62
|
"@utilitywarehouse/hearth-tokens": "^0.2.3",
|
|
63
|
-
"@utilitywarehouse/hearth-
|
|
63
|
+
"@utilitywarehouse/hearth-svg-assets": "^0.5.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@gorhom/bottom-sheet": "^5.0.0",
|
|
@@ -292,7 +292,7 @@ const Modal = ({
|
|
|
292
292
|
</View>
|
|
293
293
|
) : null}
|
|
294
294
|
{inNavModal && (
|
|
295
|
-
<InNavModalContainer style={{
|
|
295
|
+
<InNavModalContainer style={{ flex: stickyFooter ? 1 : 0 }}>
|
|
296
296
|
{children}
|
|
297
297
|
{!stickyFooter ? <View style={styles.inNavModalFooterContainer}>{footer}</View> : null}
|
|
298
298
|
</InNavModalContainer>
|
|
@@ -16,8 +16,10 @@ Each option is presented as an equal-priority segment in a single horizontal gro
|
|
|
16
16
|
|
|
17
17
|
- [Playground](#playground)
|
|
18
18
|
- [Usage](#usage)
|
|
19
|
-
- [Sizes](#sizes)
|
|
20
19
|
- [Props](#props)
|
|
20
|
+
- [Examples](#examples)
|
|
21
|
+
- [Sizes](#sizes)
|
|
22
|
+
- [Icons](#icons)
|
|
21
23
|
- [Accessibility](#accessibility)
|
|
22
24
|
|
|
23
25
|
## Playground
|
|
@@ -50,15 +52,6 @@ const Example = () => {
|
|
|
50
52
|
};
|
|
51
53
|
```
|
|
52
54
|
|
|
53
|
-
## Sizes
|
|
54
|
-
|
|
55
|
-
Figma defines two size variants for Segmented Control:
|
|
56
|
-
|
|
57
|
-
- `sm` maps to `SM-32`
|
|
58
|
-
- `md` maps to `MD-48`
|
|
59
|
-
|
|
60
|
-
<Canvas of={Stories.Sizes} />
|
|
61
|
-
|
|
62
55
|
## Props
|
|
63
56
|
|
|
64
57
|
### SegmentedControl
|
|
@@ -74,11 +67,45 @@ Figma defines two size variants for Segmented Control:
|
|
|
74
67
|
|
|
75
68
|
### SegmentedControlOption
|
|
76
69
|
|
|
77
|
-
| Property | Type
|
|
78
|
-
| ---------- |
|
|
79
|
-
| `value` | `string`
|
|
80
|
-
| `children` | `ReactNode`
|
|
81
|
-
| `
|
|
70
|
+
| Property | Type | Description | Default |
|
|
71
|
+
| ---------- | --------------- | ------------------------------ | -------- |
|
|
72
|
+
| `value` | `string` | Unique value for this segment. | required |
|
|
73
|
+
| `children` | `ReactNode` | Option label/content. | required |
|
|
74
|
+
| `icon` | `ComponentType` | Optional leading icon. | - |
|
|
75
|
+
| `disabled` | `boolean` | Disables only this option. | `false` |
|
|
76
|
+
|
|
77
|
+
## Examples
|
|
78
|
+
|
|
79
|
+
### Sizes
|
|
80
|
+
|
|
81
|
+
Figma defines two size variants for Segmented Control:
|
|
82
|
+
|
|
83
|
+
- `sm` maps to `SM-32`
|
|
84
|
+
- `md` maps to `MD-48`
|
|
85
|
+
|
|
86
|
+
<Canvas of={Stories.Sizes} />
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<SegmentedControl size="sm">...</SegmentedControl>
|
|
90
|
+
<SegmentedControl size="md">...</SegmentedControl>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Icons
|
|
94
|
+
|
|
95
|
+
SegmentedControlOption supports an optional leading icon.
|
|
96
|
+
|
|
97
|
+
<Canvas of={Stories.WithIcons} />
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { MobileSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
101
|
+
|
|
102
|
+
<SegmentedControl>
|
|
103
|
+
<SegmentedControlOption value="mobile" icon={MobileSmallIcon}>
|
|
104
|
+
Mobile
|
|
105
|
+
</SegmentedControlOption>
|
|
106
|
+
...
|
|
107
|
+
</SegmentedControl>;
|
|
108
|
+
```
|
|
82
109
|
|
|
83
110
|
## Accessibility
|
|
84
111
|
|
|
@@ -3,20 +3,17 @@ import { SegmentedControl, SegmentedControlOption } from '../';
|
|
|
3
3
|
|
|
4
4
|
figma.connect(
|
|
5
5
|
SegmentedControl,
|
|
6
|
-
'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=6185-1021&
|
|
6
|
+
'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=6185-1021&m=dev',
|
|
7
7
|
{
|
|
8
8
|
props: {
|
|
9
9
|
size: figma.enum('Size', {
|
|
10
10
|
'SM-32': 'sm',
|
|
11
11
|
'MD-48': 'md',
|
|
12
12
|
}),
|
|
13
|
-
disabled: figma.enum('State', {
|
|
14
|
-
Disabled: true,
|
|
15
|
-
}),
|
|
16
13
|
options: figma.children('Option'),
|
|
17
14
|
},
|
|
18
15
|
example: props => (
|
|
19
|
-
<SegmentedControl defaultValue="option-1" size={props.size}
|
|
16
|
+
<SegmentedControl defaultValue="option-1" size={props.size}>
|
|
20
17
|
{props.options}
|
|
21
18
|
</SegmentedControl>
|
|
22
19
|
),
|
|
@@ -25,15 +22,17 @@ figma.connect(
|
|
|
25
22
|
|
|
26
23
|
figma.connect(
|
|
27
24
|
SegmentedControlOption,
|
|
28
|
-
'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=4340-1252&
|
|
25
|
+
'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=4340-1252&m=dev',
|
|
29
26
|
{
|
|
30
27
|
props: {
|
|
31
28
|
label: figma.string('Label'),
|
|
32
|
-
|
|
29
|
+
icon: figma.boolean('Icon?', {
|
|
30
|
+
true: figma.instance('Icon-20'),
|
|
31
|
+
}),
|
|
33
32
|
},
|
|
34
33
|
example: props => (
|
|
35
|
-
<SegmentedControlOption value={
|
|
36
|
-
{props.label
|
|
34
|
+
<SegmentedControlOption value={'option'} icon={props.icon}>
|
|
35
|
+
{props.label}
|
|
37
36
|
</SegmentedControlOption>
|
|
38
37
|
),
|
|
39
38
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BroadbandSmallIcon,
|
|
3
|
+
ElectricitySmallIcon,
|
|
4
|
+
MobileSmallIcon,
|
|
5
|
+
} from '@utilitywarehouse/hearth-react-native-icons';
|
|
1
6
|
import { useState } from 'react';
|
|
2
7
|
import { BodyText, Flex, SegmentedControl, SegmentedControlOption } from '../';
|
|
3
8
|
|
|
@@ -75,3 +80,19 @@ export const Disabled = {
|
|
|
75
80
|
</SegmentedControl>
|
|
76
81
|
),
|
|
77
82
|
};
|
|
83
|
+
|
|
84
|
+
export const WithIcons = {
|
|
85
|
+
render: () => (
|
|
86
|
+
<SegmentedControl defaultValue="energy" size="md">
|
|
87
|
+
<SegmentedControlOption value="energy" icon={ElectricitySmallIcon}>
|
|
88
|
+
Energy
|
|
89
|
+
</SegmentedControlOption>
|
|
90
|
+
<SegmentedControlOption value="broadband" icon={BroadbandSmallIcon}>
|
|
91
|
+
Broadband
|
|
92
|
+
</SegmentedControlOption>
|
|
93
|
+
<SegmentedControlOption value="mobile" icon={MobileSmallIcon}>
|
|
94
|
+
Mobile
|
|
95
|
+
</SegmentedControlOption>
|
|
96
|
+
</SegmentedControl>
|
|
97
|
+
),
|
|
98
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
2
|
import type { PressableProps, ViewProps } from 'react-native';
|
|
3
3
|
|
|
4
4
|
export interface SegmentedControlOptionProps extends Omit<PressableProps, 'children'> {
|
|
@@ -6,6 +6,8 @@ export interface SegmentedControlOptionProps extends Omit<PressableProps, 'child
|
|
|
6
6
|
value: string;
|
|
7
7
|
/** Option label/content. */
|
|
8
8
|
children: ReactNode;
|
|
9
|
+
/** Optional leading icon. */
|
|
10
|
+
icon?: ComponentType<any>;
|
|
9
11
|
/** Disables only this option. */
|
|
10
12
|
disabled?: boolean;
|
|
11
13
|
style?: ViewProps['style'];
|
|
@@ -10,6 +10,7 @@ import Animated, {
|
|
|
10
10
|
} from 'react-native-reanimated';
|
|
11
11
|
import { StyleSheet } from 'react-native-unistyles';
|
|
12
12
|
import { BodyText } from '../BodyText';
|
|
13
|
+
import { Icon } from '../Icon';
|
|
13
14
|
import { useSegmentedControlContext } from './SegmentedControl.context';
|
|
14
15
|
import type SegmentedControlOptionProps from './SegmentedControlOption.props';
|
|
15
16
|
|
|
@@ -18,6 +19,7 @@ const AnimatedView = Animated.createAnimatedComponent(View);
|
|
|
18
19
|
const SegmentedControlOptionRoot = ({
|
|
19
20
|
value,
|
|
20
21
|
children,
|
|
22
|
+
icon,
|
|
21
23
|
accessibilityLabel,
|
|
22
24
|
disabled = false,
|
|
23
25
|
style,
|
|
@@ -79,47 +81,50 @@ const SegmentedControlOptionRoot = ({
|
|
|
79
81
|
: null)}
|
|
80
82
|
>
|
|
81
83
|
<View
|
|
82
|
-
style={styles.
|
|
84
|
+
style={styles.contentWrap}
|
|
83
85
|
accessible={false}
|
|
84
86
|
accessibilityElementsHidden
|
|
85
87
|
importantForAccessibility="no-hide-descendants"
|
|
86
88
|
{...(Platform.OS === 'web' ? ({ 'aria-hidden': true } as any) : null)}
|
|
87
89
|
>
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<AnimatedView
|
|
100
|
-
pointerEvents="none"
|
|
101
|
-
style={[styles.textLayer, regularLabelStyle]}
|
|
102
|
-
accessible={false}
|
|
103
|
-
accessibilityElementsHidden
|
|
104
|
-
importantForAccessibility="no-hide-descendants"
|
|
105
|
-
{...(Platform.OS === 'web' ? ({ 'aria-hidden': true } as any) : null)}
|
|
106
|
-
>
|
|
107
|
-
<BodyText size="md" weight="regular" style={styles.textRegular}>
|
|
90
|
+
{icon ? <Icon as={icon} size="sm" style={styles.icon} /> : null}
|
|
91
|
+
<View style={styles.labelWrap}>
|
|
92
|
+
<BodyText
|
|
93
|
+
size="md"
|
|
94
|
+
weight="semibold"
|
|
95
|
+
style={styles.labelSizer}
|
|
96
|
+
accessible={false}
|
|
97
|
+
accessibilityElementsHidden
|
|
98
|
+
importantForAccessibility="no-hide-descendants"
|
|
99
|
+
{...(Platform.OS === 'web' ? ({ 'aria-hidden': true } as any) : null)}
|
|
100
|
+
>
|
|
108
101
|
{children}
|
|
109
102
|
</BodyText>
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
</
|
|
122
|
-
|
|
103
|
+
<AnimatedView
|
|
104
|
+
pointerEvents="none"
|
|
105
|
+
style={[styles.textLayer, regularLabelStyle]}
|
|
106
|
+
accessible={false}
|
|
107
|
+
accessibilityElementsHidden
|
|
108
|
+
importantForAccessibility="no-hide-descendants"
|
|
109
|
+
{...(Platform.OS === 'web' ? ({ 'aria-hidden': true } as any) : null)}
|
|
110
|
+
>
|
|
111
|
+
<BodyText size="md" weight="regular" style={styles.textRegular}>
|
|
112
|
+
{children}
|
|
113
|
+
</BodyText>
|
|
114
|
+
</AnimatedView>
|
|
115
|
+
<AnimatedView
|
|
116
|
+
pointerEvents="none"
|
|
117
|
+
style={[styles.textLayer, selectedLabelStyle]}
|
|
118
|
+
accessible={false}
|
|
119
|
+
accessibilityElementsHidden
|
|
120
|
+
importantForAccessibility="no-hide-descendants"
|
|
121
|
+
{...(Platform.OS === 'web' ? ({ 'aria-hidden': true } as any) : null)}
|
|
122
|
+
>
|
|
123
|
+
<BodyText size="md" weight="semibold" style={styles.textSelected}>
|
|
124
|
+
{children}
|
|
125
|
+
</BodyText>
|
|
126
|
+
</AnimatedView>
|
|
127
|
+
</View>
|
|
123
128
|
</View>
|
|
124
129
|
</Pressable>
|
|
125
130
|
);
|
|
@@ -180,6 +185,12 @@ const styles = StyleSheet.create(theme => ({
|
|
|
180
185
|
},
|
|
181
186
|
},
|
|
182
187
|
},
|
|
188
|
+
contentWrap: {
|
|
189
|
+
flexDirection: 'row',
|
|
190
|
+
alignItems: 'center',
|
|
191
|
+
justifyContent: 'center',
|
|
192
|
+
gap: theme.components.segmentedControl.gap,
|
|
193
|
+
},
|
|
183
194
|
labelWrap: {
|
|
184
195
|
position: 'relative',
|
|
185
196
|
alignItems: 'center',
|
|
@@ -195,6 +206,18 @@ const styles = StyleSheet.create(theme => ({
|
|
|
195
206
|
alignItems: 'center',
|
|
196
207
|
justifyContent: 'center',
|
|
197
208
|
},
|
|
209
|
+
icon: {
|
|
210
|
+
variants: {
|
|
211
|
+
selected: {
|
|
212
|
+
true: {
|
|
213
|
+
color: theme.color.icon.inverted,
|
|
214
|
+
},
|
|
215
|
+
false: {
|
|
216
|
+
color: theme.color.icon.primary,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
198
221
|
textRegular: {
|
|
199
222
|
color: theme.color.text.primary,
|
|
200
223
|
},
|
|
@@ -3,10 +3,10 @@ import { SelectOption } from '../';
|
|
|
3
3
|
|
|
4
4
|
figma.connect(
|
|
5
5
|
SelectOption,
|
|
6
|
-
'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=
|
|
6
|
+
'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=3394-3663&m=dev',
|
|
7
7
|
{
|
|
8
8
|
props: {
|
|
9
|
-
label: figma.string('
|
|
9
|
+
label: figma.string('Text'),
|
|
10
10
|
disabled: figma.enum('State', {
|
|
11
11
|
Disabled: true,
|
|
12
12
|
}),
|