@ledgerhq/lumen-ui-rnative 0.1.5 → 0.1.6
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/dist/package.json +1 -1
- package/dist/src/lib/Components/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/src/lib/Components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/src/lib/Components/SegmentedControl/SegmentedControl.js +34 -61
- package/dist/src/lib/Components/SegmentedControl/SegmentedControlContext.d.ts +1 -0
- package/dist/src/lib/Components/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
- package/dist/src/lib/Components/SegmentedControl/types.d.ts +12 -2
- package/dist/src/lib/Components/SegmentedControl/types.d.ts.map +1 -1
- package/dist/src/lib/Components/SegmentedControl/usePillLayout.d.ts +19 -0
- package/dist/src/lib/Components/SegmentedControl/usePillLayout.d.ts.map +1 -0
- package/dist/src/lib/Components/SegmentedControl/usePillLayout.js +46 -0
- package/dist/src/lib/Components/TileButton/TileButton.js +2 -2
- package/package.json +1 -1
- package/src/lib/Components/SegmentedControl/SegmentedControl.mdx +1 -38
- package/src/lib/Components/SegmentedControl/SegmentedControl.stories.tsx +35 -19
- package/src/lib/Components/SegmentedControl/SegmentedControl.tsx +61 -77
- package/src/lib/Components/SegmentedControl/SegmentedControlContext.tsx +1 -0
- package/src/lib/Components/SegmentedControl/types.ts +12 -2
- package/src/lib/Components/SegmentedControl/usePillLayout.ts +76 -0
- package/src/lib/Components/TileButton/TileButton.tsx +2 -2
package/dist/package.json
CHANGED
|
@@ -3,7 +3,7 @@ export declare function SegmentedControlButton({ value, children, icon: Icon, on
|
|
|
3
3
|
export declare namespace SegmentedControlButton {
|
|
4
4
|
var displayName: string;
|
|
5
5
|
}
|
|
6
|
-
export declare function SegmentedControl({ selectedValue, onSelectedChange, accessibilityLabel, children, ...props }: SegmentedControlProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function SegmentedControl({ selectedValue, onSelectedChange, accessibilityLabel, children, disabled, appearance, ...props }: SegmentedControlProps): import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export declare namespace SegmentedControl {
|
|
8
8
|
var displayName: string;
|
|
9
9
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentedControl.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/SegmentedControl/SegmentedControl.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SegmentedControl.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/SegmentedControl/SegmentedControl.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,2BAA2B,EAC3B,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAMjB,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,IAAI,EAAE,IAAI,EACV,OAAO,EACP,GAAG,KAAK,EACT,EAAE,2BAA2B,2CAqC7B;yBA3Ce,sBAAsB;;;AA2FtC,wBAAgB,gBAAgB,CAAC,EAC/B,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,QAAQ,EACR,QAAQ,EACR,UAAyB,EACzB,GAAG,KAAK,EACT,EAAE,qBAAqB,2CAkCvB;yBA1Ce,gBAAgB"}
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
3
|
-
import Animated, { useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated';
|
|
2
|
+
import Animated from 'react-native-reanimated';
|
|
4
3
|
import { useStyleSheet } from '../../../styles';
|
|
5
|
-
import { durations, easingCurves } from '../../Animations/constants';
|
|
6
4
|
import { Box, Pressable, Text } from '../Utility';
|
|
7
5
|
import { SegmentedControlContextProvider, useSegmentedControlContext, } from './SegmentedControlContext';
|
|
8
|
-
|
|
6
|
+
import { usePillLayout, useSegmentedControlSelectedIndex, } from './usePillLayout';
|
|
9
7
|
export function SegmentedControlButton({ value, children, icon: Icon, onPress, ...props }) {
|
|
10
|
-
const
|
|
11
|
-
const { selectedValue, onSelectedChange } = useSegmentedControlContext();
|
|
8
|
+
const { selectedValue, onSelectedChange, disabled } = useSegmentedControlContext();
|
|
12
9
|
const selected = selectedValue === value;
|
|
10
|
+
const styles = useButtonStyles({ selected, disabled });
|
|
13
11
|
function handlePress() {
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
if (!disabled) {
|
|
13
|
+
onSelectedChange(value);
|
|
14
|
+
onPress?.();
|
|
15
|
+
}
|
|
16
16
|
}
|
|
17
|
-
return (_jsx(Pressable, { onPress: handlePress, accessibilityState: { selected }, style: styles.button, ...props, children: _jsxs(Box, { style: styles.content, children: [Icon && (_jsx(Box, { style: styles.iconWrap, children: _jsx(Icon, { size:
|
|
17
|
+
return (_jsx(Pressable, { onPress: handlePress, disabled: disabled, accessibilityState: { selected, disabled }, style: styles.button, ...props, children: _jsxs(Box, { style: styles.content, children: [Icon && (_jsx(Box, { style: styles.iconWrap, children: _jsx(Icon, { size: 16, color: styles.textColor }) })), _jsx(Text, { typography: styles.typography, lx: { color: styles.textColor }, style: styles.label, children: children })] }) }));
|
|
18
18
|
}
|
|
19
19
|
SegmentedControlButton.displayName = 'SegmentedControlButton';
|
|
20
|
-
function useButtonStyles() {
|
|
21
|
-
|
|
20
|
+
function useButtonStyles({ selected, disabled, }) {
|
|
21
|
+
const styles = useStyleSheet((t) => ({
|
|
22
22
|
button: {
|
|
23
23
|
flex: 1,
|
|
24
24
|
flexDirection: 'row',
|
|
@@ -44,71 +44,44 @@ function useButtonStyles() {
|
|
|
44
44
|
alignItems: 'center',
|
|
45
45
|
},
|
|
46
46
|
}), []);
|
|
47
|
+
const typography = selected
|
|
48
|
+
? 'body2SemiBold'
|
|
49
|
+
: 'body2';
|
|
50
|
+
const textColor = selected && !disabled ? 'base' : 'muted';
|
|
51
|
+
return { ...styles, typography, textColor };
|
|
47
52
|
}
|
|
48
|
-
export function SegmentedControl({ selectedValue, onSelectedChange, accessibilityLabel, children, ...props }) {
|
|
49
|
-
const styles = useRootStyles(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return false;
|
|
60
|
-
});
|
|
61
|
-
}, [selectedValue, children]);
|
|
62
|
-
function onLayout(e) {
|
|
63
|
-
const { width, height } = e.nativeEvent.layout;
|
|
64
|
-
const count = React.Children.count(children);
|
|
65
|
-
const slotWidth = count > 0 ? width / count : 0;
|
|
66
|
-
pillWidth.value = slotWidth;
|
|
67
|
-
pillHeight.value = height;
|
|
68
|
-
if (!hasLayoutRef.current) {
|
|
69
|
-
hasLayoutRef.current = true;
|
|
70
|
-
const index = getSelectedIndex();
|
|
71
|
-
if (index >= 0) {
|
|
72
|
-
pillTranslateX.value = index * slotWidth;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
if (!hasLayoutRef.current)
|
|
78
|
-
return;
|
|
79
|
-
const index = getSelectedIndex();
|
|
80
|
-
if (index >= 0 && pillWidth.value > 0) {
|
|
81
|
-
pillTranslateX.value = withTiming(index * pillWidth.value, {
|
|
82
|
-
duration: durations['250'],
|
|
83
|
-
easing: easingCurves.bezier.default,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}, [pillWidth, pillTranslateX, getSelectedIndex]);
|
|
87
|
-
const animatedPillStyle = useAnimatedStyle(() => ({
|
|
88
|
-
transform: [{ translateX: pillTranslateX.value }],
|
|
89
|
-
width: pillWidth.value,
|
|
90
|
-
height: pillHeight.value,
|
|
91
|
-
}), [pillTranslateX, pillWidth, pillHeight]);
|
|
92
|
-
return (_jsx(SegmentedControlContextProvider, { value: { selectedValue, onSelectedChange }, children: _jsxs(Box, { accessibilityRole: 'radiogroup', accessibilityLabel: accessibilityLabel, onLayout: onLayout, style: styles.container, ...props, children: [children, _jsx(Animated.View, { style: [styles.pill, animatedPillStyle], pointerEvents: 'none' })] }) }));
|
|
53
|
+
export function SegmentedControl({ selectedValue, onSelectedChange, accessibilityLabel, children, disabled, appearance = 'background', ...props }) {
|
|
54
|
+
const styles = useRootStyles({
|
|
55
|
+
disabled: Boolean(disabled),
|
|
56
|
+
appearance,
|
|
57
|
+
});
|
|
58
|
+
const selectedIndex = useSegmentedControlSelectedIndex(selectedValue, children);
|
|
59
|
+
const { onLayout, animatedPillStyle } = usePillLayout({
|
|
60
|
+
selectedIndex,
|
|
61
|
+
children,
|
|
62
|
+
});
|
|
63
|
+
return (_jsx(SegmentedControlContextProvider, { value: { selectedValue, onSelectedChange, disabled }, children: _jsxs(Box, { accessibilityRole: 'radiogroup', accessibilityLabel: accessibilityLabel, accessibilityState: { disabled }, onLayout: onLayout, style: styles.container, ...props, children: [children, _jsx(Animated.View, { style: [styles.pill, animatedPillStyle], pointerEvents: 'none' })] }) }));
|
|
93
64
|
}
|
|
94
65
|
SegmentedControl.displayName = 'SegmentedControl';
|
|
95
|
-
function useRootStyles() {
|
|
66
|
+
function useRootStyles({ disabled, appearance, }) {
|
|
96
67
|
return useStyleSheet((t) => ({
|
|
97
68
|
container: {
|
|
98
69
|
flexDirection: 'row',
|
|
99
70
|
alignItems: 'center',
|
|
100
71
|
position: 'relative',
|
|
101
72
|
width: '100%',
|
|
102
|
-
borderRadius: t.borderRadius.
|
|
103
|
-
backgroundColor: t.colors.bg.
|
|
73
|
+
borderRadius: t.borderRadius.md,
|
|
74
|
+
backgroundColor: appearance === 'background' ? t.colors.bg.surface : 'transparent',
|
|
104
75
|
},
|
|
105
76
|
pill: {
|
|
106
77
|
position: 'absolute',
|
|
107
78
|
top: 0,
|
|
108
79
|
left: 0,
|
|
109
80
|
borderRadius: t.borderRadius.sm,
|
|
110
|
-
backgroundColor:
|
|
81
|
+
backgroundColor: disabled
|
|
82
|
+
? t.colors.bg.baseTransparentPressed
|
|
83
|
+
: t.colors.bg.mutedTransparent,
|
|
111
84
|
zIndex: 0,
|
|
112
85
|
},
|
|
113
|
-
}), []);
|
|
86
|
+
}), [disabled, appearance]);
|
|
114
87
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentedControlContext.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/SegmentedControl/SegmentedControlContext.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,4BAA4B,GAAG;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"SegmentedControlContext.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/SegmentedControl/SegmentedControlContext.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,4BAA4B,GAAG;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,QAAA,MAAO,+BAA+B;;;EAC+B,CAAC;AAEtE,eAAO,MAAM,0BAA0B,oCAInC,CAAC;AAEL,OAAO,EAAE,+BAA+B,EAAE,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ComponentType, ReactNode } from 'react';
|
|
2
|
-
import { StyledPressableProps } from '../../../styles';
|
|
2
|
+
import { LumenTextStyle, StyledPressableProps } from '../../../styles';
|
|
3
3
|
import { IconSize } from '../Icon';
|
|
4
4
|
import { BoxProps } from '../Utility';
|
|
5
5
|
export type SegmentedControlProps = {
|
|
@@ -11,17 +11,27 @@ export type SegmentedControlProps = {
|
|
|
11
11
|
* Callback when the selected segment value changes.
|
|
12
12
|
*/
|
|
13
13
|
onSelectedChange: (value: string) => void;
|
|
14
|
+
/**
|
|
15
|
+
* When true, the entire control is disabled (no interaction, selected uses muted styling).
|
|
16
|
+
*/
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Visual style of the control container: "background" shows the surface bg, "no-background" is transparent.
|
|
20
|
+
* @default 'background'
|
|
21
|
+
*/
|
|
22
|
+
appearance?: 'background' | 'no-background';
|
|
14
23
|
/**
|
|
15
24
|
* Accessible label for the control (e.g. "File view").
|
|
16
25
|
*/
|
|
17
26
|
accessibilityLabel?: string;
|
|
18
27
|
/**
|
|
19
|
-
* Segment buttons (SegmentedControlButton).
|
|
28
|
+
* Segment buttons (SegmentedControlButton).
|
|
20
29
|
*/
|
|
21
30
|
children: ReactNode;
|
|
22
31
|
} & Omit<BoxProps, 'children'>;
|
|
23
32
|
type IconComponent = ComponentType<{
|
|
24
33
|
size?: IconSize;
|
|
34
|
+
color?: LumenTextStyle['color'];
|
|
25
35
|
}>;
|
|
26
36
|
export type SegmentedControlButtonProps = {
|
|
27
37
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/SegmentedControl/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/SegmentedControl/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,MAAM,qBAAqB,GAAG;IAClC;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,YAAY,GAAG,eAAe,CAAC;IAC5C;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AAE/B,KAAK,aAAa,GAAG,aAAa,CAAC;IACjC,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;CACjC,CAAC,CAAC;AAEH,MAAM,MAAM,2BAA2B,GAAG;IACxC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB,GAAG,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LayoutChangeEvent } from 'react-native';
|
|
3
|
+
export declare function useSegmentedControlSelectedIndex(selectedValue: string, children: React.ReactNode): number;
|
|
4
|
+
type UsePillLayoutParams = {
|
|
5
|
+
selectedIndex: number;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
};
|
|
8
|
+
export declare function usePillLayout({ selectedIndex, children, }: UsePillLayoutParams): {
|
|
9
|
+
onLayout: (e: LayoutChangeEvent) => void;
|
|
10
|
+
animatedPillStyle: {
|
|
11
|
+
transform: {
|
|
12
|
+
translateX: number;
|
|
13
|
+
}[];
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=usePillLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePillLayout.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/SegmentedControl/usePillLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAQjD,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,GACxB,MAAM,CAWR;AAED,KAAK,mBAAmB,GAAG;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,QAAQ,GACT,EAAE,mBAAmB;kBAMC,iBAAiB,KAAG,IAAI;;;;;;;;EAoC9C"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated';
|
|
3
|
+
import { durations, easingCurves } from '../../Animations/constants';
|
|
4
|
+
export function useSegmentedControlSelectedIndex(selectedValue, children) {
|
|
5
|
+
return useMemo(() => React.Children.toArray(children).findIndex((child) => {
|
|
6
|
+
if (React.isValidElement(child) && child.props != null) {
|
|
7
|
+
return child.props.value === selectedValue;
|
|
8
|
+
}
|
|
9
|
+
return false;
|
|
10
|
+
}), [selectedValue, children]);
|
|
11
|
+
}
|
|
12
|
+
export function usePillLayout({ selectedIndex, children, }) {
|
|
13
|
+
const pillTranslateX = useSharedValue(0);
|
|
14
|
+
const pillWidth = useSharedValue(0);
|
|
15
|
+
const pillHeight = useSharedValue(0);
|
|
16
|
+
const hasLayoutRef = useRef(false);
|
|
17
|
+
const onLayout = (e) => {
|
|
18
|
+
const { width, height } = e.nativeEvent.layout;
|
|
19
|
+
const count = React.Children.count(children);
|
|
20
|
+
const slotWidth = count > 0 ? width / count : 0;
|
|
21
|
+
pillWidth.value = slotWidth;
|
|
22
|
+
pillHeight.value = height;
|
|
23
|
+
if (!hasLayoutRef.current) {
|
|
24
|
+
hasLayoutRef.current = true;
|
|
25
|
+
if (selectedIndex >= 0) {
|
|
26
|
+
pillTranslateX.value = selectedIndex * slotWidth;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!hasLayoutRef.current)
|
|
32
|
+
return;
|
|
33
|
+
if (selectedIndex >= 0 && pillWidth.value > 0) {
|
|
34
|
+
pillTranslateX.value = withTiming(selectedIndex * pillWidth.value, {
|
|
35
|
+
duration: durations['250'],
|
|
36
|
+
easing: easingCurves.bezier.default,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}, [selectedIndex, pillWidth, pillTranslateX]);
|
|
40
|
+
const animatedPillStyle = useAnimatedStyle(() => ({
|
|
41
|
+
transform: [{ translateX: pillTranslateX.value }],
|
|
42
|
+
width: pillWidth.value,
|
|
43
|
+
height: pillHeight.value,
|
|
44
|
+
}), [pillTranslateX, pillWidth, pillHeight]);
|
|
45
|
+
return { onLayout, animatedPillStyle };
|
|
46
|
+
}
|
|
@@ -21,10 +21,10 @@ const useStyles = ({ disabled, pressed, isFull, }) => {
|
|
|
21
21
|
gap: t.spacings.s8,
|
|
22
22
|
padding: t.spacings.s12,
|
|
23
23
|
borderRadius: t.borderRadius.md,
|
|
24
|
-
backgroundColor: t.colors.bg.
|
|
24
|
+
backgroundColor: t.colors.bg.surface,
|
|
25
25
|
},
|
|
26
26
|
isFull && { width: t.sizes.full },
|
|
27
|
-
pressed && !disabled && { backgroundColor: t.colors.bg.
|
|
27
|
+
pressed && !disabled && { backgroundColor: t.colors.bg.surfacePressed },
|
|
28
28
|
disabled && { backgroundColor: t.colors.bg.disabled },
|
|
29
29
|
]),
|
|
30
30
|
label: StyleSheet.flatten([
|
package/package.json
CHANGED
|
@@ -25,6 +25,7 @@ SegmentedControl is a tab bar–style component for switching between mutually e
|
|
|
25
25
|
- **Segments**: Individual options the user can select.
|
|
26
26
|
- **Selected state**: The active segment (sliding pill + semi-bold label).
|
|
27
27
|
- **Optional icon**: Icon to the left of the label (from Symbols).
|
|
28
|
+
- **Appearance**: Use `appearance="background"` (default) for a surface background, or `appearance="no-background"` for a transparent container.
|
|
28
29
|
|
|
29
30
|
## Properties
|
|
30
31
|
|
|
@@ -55,44 +56,6 @@ SegmentedControl lays out segments in a horizontal row with equal width per segm
|
|
|
55
56
|
|
|
56
57
|
Use the library as part of `@ledgerhq/lumen-ui-rnative`. See the [Setup Guide →](?path=/docs/getting-started-setup--docs).
|
|
57
58
|
|
|
58
|
-
## SegmentedControlButton
|
|
59
|
-
|
|
60
|
-
Use as a direct child of SegmentedControl (or inside wrappers such as Tooltip). Required: value (string) and children (label). Optional: icon (component from symbols), onPress (runs in addition to parent onSelectedChange).
|
|
61
|
-
|
|
62
|
-
<div className='my-24 overflow-hidden rounded-lg'>
|
|
63
|
-
<table className='w-full'>
|
|
64
|
-
<thead>
|
|
65
|
-
<tr className='border-b border-muted bg-muted'>
|
|
66
|
-
<th className='p-12 text-left text-on-accent body-2'>Prop</th>
|
|
67
|
-
<th className='p-12 text-left text-on-accent body-2'>Type</th>
|
|
68
|
-
<th className='p-12 text-left text-on-accent body-2'>Description</th>
|
|
69
|
-
</tr>
|
|
70
|
-
</thead>
|
|
71
|
-
<tbody className='bg-canvas'>
|
|
72
|
-
<tr className='border-b border-muted'>
|
|
73
|
-
<td className='text-accent p-12'>value</td>
|
|
74
|
-
<td className='p-12 text-muted'>string</td>
|
|
75
|
-
<td className='p-12 text-muted'>Unique value for this segment (e.g. "send", "receive")</td>
|
|
76
|
-
</tr>
|
|
77
|
-
<tr className='border-b border-muted'>
|
|
78
|
-
<td className='text-accent p-12'>children</td>
|
|
79
|
-
<td className='p-12 text-muted'>ReactNode</td>
|
|
80
|
-
<td className='p-12 text-muted'>Button label (e.g. "Send", "Tokens")</td>
|
|
81
|
-
</tr>
|
|
82
|
-
<tr className='border-b border-muted'>
|
|
83
|
-
<td className='text-accent p-12'>icon</td>
|
|
84
|
-
<td className='p-12 text-muted'>ComponentType</td>
|
|
85
|
-
<td className='p-12 text-muted'>Optional icon to the left of the label (from Symbols)</td>
|
|
86
|
-
</tr>
|
|
87
|
-
<tr>
|
|
88
|
-
<td className='text-accent p-12'>onPress</td>
|
|
89
|
-
<td className='p-12 text-muted'>() => void</td>
|
|
90
|
-
<td className='p-12 text-muted'>Optional callback when the button is pressed (in addition to parent onSelectedChange)</td>
|
|
91
|
-
</tr>
|
|
92
|
-
</tbody>
|
|
93
|
-
</table>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
59
|
## Basic Usage
|
|
97
60
|
|
|
98
61
|
Control selected value in state at the top level and pass it to SegmentedControl. Buttons use value to identify themselves; selected state is derived from context.
|
|
@@ -17,34 +17,27 @@ const meta = {
|
|
|
17
17
|
argTypes: {
|
|
18
18
|
onSelectedChange: {
|
|
19
19
|
action: 'change',
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
},
|
|
21
|
+
disabled: {
|
|
22
|
+
control: 'boolean',
|
|
23
|
+
},
|
|
24
|
+
appearance: {
|
|
25
|
+
options: ['background', 'no-background'],
|
|
26
|
+
control: 'radio',
|
|
24
27
|
},
|
|
25
28
|
accessibilityLabel: {
|
|
26
29
|
control: 'text',
|
|
27
|
-
description: 'Accessible label for the control',
|
|
28
|
-
table: {
|
|
29
|
-
type: { summary: 'string' },
|
|
30
|
-
},
|
|
31
30
|
},
|
|
32
31
|
selectedValue: {
|
|
33
32
|
control: 'text',
|
|
34
|
-
description:
|
|
35
|
-
'The value of the currently selected segment (drives the sliding pill)',
|
|
36
|
-
table: {
|
|
37
|
-
type: { summary: 'string' },
|
|
38
|
-
},
|
|
39
33
|
},
|
|
40
34
|
children: {
|
|
41
35
|
control: false,
|
|
42
|
-
description: 'SegmentedControlButton elements',
|
|
43
|
-
table: {
|
|
44
|
-
type: { summary: 'ReactNode' },
|
|
45
|
-
},
|
|
46
36
|
},
|
|
47
37
|
},
|
|
38
|
+
args: {
|
|
39
|
+
appearance: 'background',
|
|
40
|
+
},
|
|
48
41
|
} satisfies Meta<typeof SegmentedControl>;
|
|
49
42
|
|
|
50
43
|
export default meta;
|
|
@@ -52,12 +45,13 @@ type Story = StoryObj<typeof meta>;
|
|
|
52
45
|
|
|
53
46
|
export const Base: Story = {
|
|
54
47
|
args: {} as React.ComponentProps<typeof SegmentedControl>,
|
|
55
|
-
render: () => {
|
|
48
|
+
render: (args) => {
|
|
56
49
|
const [state, setState] = useState('send');
|
|
57
50
|
|
|
58
51
|
return (
|
|
59
52
|
<Box lx={{ width: 's256' }}>
|
|
60
53
|
<SegmentedControl
|
|
54
|
+
{...args}
|
|
61
55
|
selectedValue={state}
|
|
62
56
|
onSelectedChange={setState}
|
|
63
57
|
accessibilityLabel='Transaction type'
|
|
@@ -75,11 +69,12 @@ export const Base: Story = {
|
|
|
75
69
|
|
|
76
70
|
export const WithIcons: Story = {
|
|
77
71
|
args: {} as React.ComponentProps<typeof SegmentedControl>,
|
|
78
|
-
render: () => {
|
|
72
|
+
render: (args) => {
|
|
79
73
|
const [state, setState] = useState('tokens');
|
|
80
74
|
|
|
81
75
|
return (
|
|
82
76
|
<SegmentedControl
|
|
77
|
+
{...args}
|
|
83
78
|
selectedValue={state}
|
|
84
79
|
onSelectedChange={setState}
|
|
85
80
|
accessibilityLabel='Asset section'
|
|
@@ -100,3 +95,24 @@ export const WithIcons: Story = {
|
|
|
100
95
|
);
|
|
101
96
|
},
|
|
102
97
|
};
|
|
98
|
+
|
|
99
|
+
export const Disabled: Story = {
|
|
100
|
+
args: {} as React.ComponentProps<typeof SegmentedControl>,
|
|
101
|
+
render: (args) => (
|
|
102
|
+
<Box lx={{ width: 's256' }}>
|
|
103
|
+
<SegmentedControl
|
|
104
|
+
{...args}
|
|
105
|
+
selectedValue='receive'
|
|
106
|
+
onSelectedChange={() => {
|
|
107
|
+
/* empty */
|
|
108
|
+
}}
|
|
109
|
+
accessibilityLabel='Transaction type (disabled)'
|
|
110
|
+
disabled
|
|
111
|
+
>
|
|
112
|
+
<SegmentedControlButton value='send'>Send</SegmentedControlButton>
|
|
113
|
+
<SegmentedControlButton value='receive'>Receive</SegmentedControlButton>
|
|
114
|
+
<SegmentedControlButton value='buy'>Buy</SegmentedControlButton>
|
|
115
|
+
</SegmentedControl>
|
|
116
|
+
</Box>
|
|
117
|
+
),
|
|
118
|
+
};
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { LayoutChangeEvent } from 'react-native';
|
|
3
|
-
import Animated, {
|
|
4
|
-
useAnimatedStyle,
|
|
5
|
-
useSharedValue,
|
|
6
|
-
withTiming,
|
|
7
|
-
} from 'react-native-reanimated';
|
|
1
|
+
import Animated from 'react-native-reanimated';
|
|
8
2
|
import { useStyleSheet } from '../../../styles';
|
|
9
|
-
import {
|
|
3
|
+
import type { LumenTextStyle, LumenTypographyTokenName } from '../../../styles';
|
|
10
4
|
import { Box, Pressable, Text } from '../Utility';
|
|
11
5
|
import {
|
|
12
6
|
SegmentedControlContextProvider,
|
|
@@ -16,8 +10,10 @@ import type {
|
|
|
16
10
|
SegmentedControlButtonProps,
|
|
17
11
|
SegmentedControlProps,
|
|
18
12
|
} from './types';
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
import {
|
|
14
|
+
usePillLayout,
|
|
15
|
+
useSegmentedControlSelectedIndex,
|
|
16
|
+
} from './usePillLayout';
|
|
21
17
|
|
|
22
18
|
export function SegmentedControlButton({
|
|
23
19
|
value,
|
|
@@ -26,32 +22,35 @@ export function SegmentedControlButton({
|
|
|
26
22
|
onPress,
|
|
27
23
|
...props
|
|
28
24
|
}: SegmentedControlButtonProps) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
const { selectedValue, onSelectedChange, disabled } =
|
|
26
|
+
useSegmentedControlContext();
|
|
32
27
|
const selected = selectedValue === value;
|
|
28
|
+
const styles = useButtonStyles({ selected, disabled });
|
|
33
29
|
|
|
34
30
|
function handlePress() {
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
if (!disabled) {
|
|
32
|
+
onSelectedChange(value);
|
|
33
|
+
onPress?.();
|
|
34
|
+
}
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
return (
|
|
40
38
|
<Pressable
|
|
41
39
|
onPress={handlePress}
|
|
42
|
-
|
|
40
|
+
disabled={disabled}
|
|
41
|
+
accessibilityState={{ selected, disabled }}
|
|
43
42
|
style={styles.button}
|
|
44
43
|
{...props}
|
|
45
44
|
>
|
|
46
45
|
<Box style={styles.content}>
|
|
47
46
|
{Icon && (
|
|
48
47
|
<Box style={styles.iconWrap}>
|
|
49
|
-
<Icon size={
|
|
48
|
+
<Icon size={16} color={styles.textColor} />
|
|
50
49
|
</Box>
|
|
51
50
|
)}
|
|
52
51
|
<Text
|
|
53
|
-
typography={
|
|
54
|
-
lx={{ color:
|
|
52
|
+
typography={styles.typography}
|
|
53
|
+
lx={{ color: styles.textColor }}
|
|
55
54
|
style={styles.label}
|
|
56
55
|
>
|
|
57
56
|
{children}
|
|
@@ -63,8 +62,14 @@ export function SegmentedControlButton({
|
|
|
63
62
|
|
|
64
63
|
SegmentedControlButton.displayName = 'SegmentedControlButton';
|
|
65
64
|
|
|
66
|
-
function useButtonStyles(
|
|
67
|
-
|
|
65
|
+
function useButtonStyles({
|
|
66
|
+
selected,
|
|
67
|
+
disabled,
|
|
68
|
+
}: {
|
|
69
|
+
selected: boolean;
|
|
70
|
+
disabled?: boolean;
|
|
71
|
+
}) {
|
|
72
|
+
const styles = useStyleSheet(
|
|
68
73
|
(t) => ({
|
|
69
74
|
button: {
|
|
70
75
|
flex: 1,
|
|
@@ -93,6 +98,12 @@ function useButtonStyles() {
|
|
|
93
98
|
}),
|
|
94
99
|
[],
|
|
95
100
|
);
|
|
101
|
+
const typography: LumenTypographyTokenName = selected
|
|
102
|
+
? 'body2SemiBold'
|
|
103
|
+
: 'body2';
|
|
104
|
+
const textColor: LumenTextStyle['color'] =
|
|
105
|
+
selected && !disabled ? 'base' : 'muted';
|
|
106
|
+
return { ...styles, typography, textColor };
|
|
96
107
|
}
|
|
97
108
|
|
|
98
109
|
export function SegmentedControl({
|
|
@@ -100,67 +111,31 @@ export function SegmentedControl({
|
|
|
100
111
|
onSelectedChange,
|
|
101
112
|
accessibilityLabel,
|
|
102
113
|
children,
|
|
114
|
+
disabled,
|
|
115
|
+
appearance = 'background',
|
|
103
116
|
...props
|
|
104
117
|
}: SegmentedControlProps) {
|
|
105
|
-
const styles = useRootStyles(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return React.Children.toArray(children).findIndex((child) => {
|
|
113
|
-
if (React.isValidElement(child) && child.props != null) {
|
|
114
|
-
return (child.props as { value?: string }).value === selectedValue;
|
|
115
|
-
}
|
|
116
|
-
return false;
|
|
117
|
-
});
|
|
118
|
-
}, [selectedValue, children]);
|
|
119
|
-
|
|
120
|
-
function onLayout(e: LayoutChangeEvent) {
|
|
121
|
-
const { width, height } = e.nativeEvent.layout;
|
|
122
|
-
const count = React.Children.count(children);
|
|
123
|
-
const slotWidth = count > 0 ? width / count : 0;
|
|
124
|
-
|
|
125
|
-
pillWidth.value = slotWidth;
|
|
126
|
-
pillHeight.value = height;
|
|
127
|
-
|
|
128
|
-
if (!hasLayoutRef.current) {
|
|
129
|
-
hasLayoutRef.current = true;
|
|
130
|
-
const index = getSelectedIndex();
|
|
131
|
-
if (index >= 0) {
|
|
132
|
-
pillTranslateX.value = index * slotWidth;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
if (!hasLayoutRef.current) return;
|
|
139
|
-
const index = getSelectedIndex();
|
|
140
|
-
if (index >= 0 && pillWidth.value > 0) {
|
|
141
|
-
pillTranslateX.value = withTiming(index * pillWidth.value, {
|
|
142
|
-
duration: durations['250'],
|
|
143
|
-
easing: easingCurves.bezier.default,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}, [pillWidth, pillTranslateX, getSelectedIndex]);
|
|
147
|
-
|
|
148
|
-
const animatedPillStyle = useAnimatedStyle(
|
|
149
|
-
() => ({
|
|
150
|
-
transform: [{ translateX: pillTranslateX.value }],
|
|
151
|
-
width: pillWidth.value,
|
|
152
|
-
height: pillHeight.value,
|
|
153
|
-
}),
|
|
154
|
-
[pillTranslateX, pillWidth, pillHeight],
|
|
118
|
+
const styles = useRootStyles({
|
|
119
|
+
disabled: Boolean(disabled),
|
|
120
|
+
appearance,
|
|
121
|
+
});
|
|
122
|
+
const selectedIndex = useSegmentedControlSelectedIndex(
|
|
123
|
+
selectedValue,
|
|
124
|
+
children,
|
|
155
125
|
);
|
|
126
|
+
const { onLayout, animatedPillStyle } = usePillLayout({
|
|
127
|
+
selectedIndex,
|
|
128
|
+
children,
|
|
129
|
+
});
|
|
156
130
|
|
|
157
131
|
return (
|
|
158
132
|
<SegmentedControlContextProvider
|
|
159
|
-
value={{ selectedValue, onSelectedChange }}
|
|
133
|
+
value={{ selectedValue, onSelectedChange, disabled }}
|
|
160
134
|
>
|
|
161
135
|
<Box
|
|
162
136
|
accessibilityRole='radiogroup'
|
|
163
137
|
accessibilityLabel={accessibilityLabel}
|
|
138
|
+
accessibilityState={{ disabled }}
|
|
164
139
|
onLayout={onLayout}
|
|
165
140
|
style={styles.container}
|
|
166
141
|
{...props}
|
|
@@ -177,7 +152,13 @@ export function SegmentedControl({
|
|
|
177
152
|
|
|
178
153
|
SegmentedControl.displayName = 'SegmentedControl';
|
|
179
154
|
|
|
180
|
-
function useRootStyles(
|
|
155
|
+
function useRootStyles({
|
|
156
|
+
disabled,
|
|
157
|
+
appearance,
|
|
158
|
+
}: {
|
|
159
|
+
disabled: boolean;
|
|
160
|
+
appearance: 'background' | 'no-background';
|
|
161
|
+
}) {
|
|
181
162
|
return useStyleSheet(
|
|
182
163
|
(t) => ({
|
|
183
164
|
container: {
|
|
@@ -185,18 +166,21 @@ function useRootStyles() {
|
|
|
185
166
|
alignItems: 'center',
|
|
186
167
|
position: 'relative',
|
|
187
168
|
width: '100%',
|
|
188
|
-
borderRadius: t.borderRadius.
|
|
189
|
-
backgroundColor:
|
|
169
|
+
borderRadius: t.borderRadius.md,
|
|
170
|
+
backgroundColor:
|
|
171
|
+
appearance === 'background' ? t.colors.bg.surface : 'transparent',
|
|
190
172
|
},
|
|
191
173
|
pill: {
|
|
192
174
|
position: 'absolute',
|
|
193
175
|
top: 0,
|
|
194
176
|
left: 0,
|
|
195
177
|
borderRadius: t.borderRadius.sm,
|
|
196
|
-
backgroundColor:
|
|
178
|
+
backgroundColor: disabled
|
|
179
|
+
? t.colors.bg.baseTransparentPressed
|
|
180
|
+
: t.colors.bg.mutedTransparent,
|
|
197
181
|
zIndex: 0,
|
|
198
182
|
},
|
|
199
183
|
}),
|
|
200
|
-
[],
|
|
184
|
+
[disabled, appearance],
|
|
201
185
|
);
|
|
202
186
|
}
|
|
@@ -3,6 +3,7 @@ import { createSafeContext } from '@ledgerhq/lumen-utils-shared';
|
|
|
3
3
|
export type SegmentedControlContextValue = {
|
|
4
4
|
selectedValue: string;
|
|
5
5
|
onSelectedChange: (value: string) => void;
|
|
6
|
+
disabled?: boolean;
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
const [SegmentedControlContextProvider, _useSegmentedControlSafeContext] =
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ComponentType, ReactNode } from 'react';
|
|
2
|
-
import { StyledPressableProps } from '../../../styles';
|
|
2
|
+
import { LumenTextStyle, StyledPressableProps } from '../../../styles';
|
|
3
3
|
import { IconSize } from '../Icon';
|
|
4
4
|
import { BoxProps } from '../Utility';
|
|
5
5
|
|
|
@@ -12,18 +12,28 @@ export type SegmentedControlProps = {
|
|
|
12
12
|
* Callback when the selected segment value changes.
|
|
13
13
|
*/
|
|
14
14
|
onSelectedChange: (value: string) => void;
|
|
15
|
+
/**
|
|
16
|
+
* When true, the entire control is disabled (no interaction, selected uses muted styling).
|
|
17
|
+
*/
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Visual style of the control container: "background" shows the surface bg, "no-background" is transparent.
|
|
21
|
+
* @default 'background'
|
|
22
|
+
*/
|
|
23
|
+
appearance?: 'background' | 'no-background';
|
|
15
24
|
/**
|
|
16
25
|
* Accessible label for the control (e.g. "File view").
|
|
17
26
|
*/
|
|
18
27
|
accessibilityLabel?: string;
|
|
19
28
|
/**
|
|
20
|
-
* Segment buttons (SegmentedControlButton).
|
|
29
|
+
* Segment buttons (SegmentedControlButton).
|
|
21
30
|
*/
|
|
22
31
|
children: ReactNode;
|
|
23
32
|
} & Omit<BoxProps, 'children'>;
|
|
24
33
|
|
|
25
34
|
type IconComponent = ComponentType<{
|
|
26
35
|
size?: IconSize;
|
|
36
|
+
color?: LumenTextStyle['color'];
|
|
27
37
|
}>;
|
|
28
38
|
|
|
29
39
|
export type SegmentedControlButtonProps = {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { LayoutChangeEvent } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
useSharedValue,
|
|
6
|
+
withTiming,
|
|
7
|
+
} from 'react-native-reanimated';
|
|
8
|
+
import { durations, easingCurves } from '../../Animations/constants';
|
|
9
|
+
|
|
10
|
+
export function useSegmentedControlSelectedIndex(
|
|
11
|
+
selectedValue: string,
|
|
12
|
+
children: React.ReactNode,
|
|
13
|
+
): number {
|
|
14
|
+
return useMemo(
|
|
15
|
+
() =>
|
|
16
|
+
React.Children.toArray(children).findIndex((child) => {
|
|
17
|
+
if (React.isValidElement(child) && child.props != null) {
|
|
18
|
+
return (child.props as { value?: string }).value === selectedValue;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}),
|
|
22
|
+
[selectedValue, children],
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type UsePillLayoutParams = {
|
|
27
|
+
selectedIndex: number;
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function usePillLayout({
|
|
32
|
+
selectedIndex,
|
|
33
|
+
children,
|
|
34
|
+
}: UsePillLayoutParams) {
|
|
35
|
+
const pillTranslateX = useSharedValue(0);
|
|
36
|
+
const pillWidth = useSharedValue(0);
|
|
37
|
+
const pillHeight = useSharedValue(0);
|
|
38
|
+
const hasLayoutRef = useRef(false);
|
|
39
|
+
|
|
40
|
+
const onLayout = (e: LayoutChangeEvent): void => {
|
|
41
|
+
const { width, height } = e.nativeEvent.layout;
|
|
42
|
+
const count = React.Children.count(children);
|
|
43
|
+
const slotWidth = count > 0 ? width / count : 0;
|
|
44
|
+
|
|
45
|
+
pillWidth.value = slotWidth;
|
|
46
|
+
pillHeight.value = height;
|
|
47
|
+
|
|
48
|
+
if (!hasLayoutRef.current) {
|
|
49
|
+
hasLayoutRef.current = true;
|
|
50
|
+
if (selectedIndex >= 0) {
|
|
51
|
+
pillTranslateX.value = selectedIndex * slotWidth;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!hasLayoutRef.current) return;
|
|
58
|
+
if (selectedIndex >= 0 && pillWidth.value > 0) {
|
|
59
|
+
pillTranslateX.value = withTiming(selectedIndex * pillWidth.value, {
|
|
60
|
+
duration: durations['250'],
|
|
61
|
+
easing: easingCurves.bezier.default,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}, [selectedIndex, pillWidth, pillTranslateX]);
|
|
65
|
+
|
|
66
|
+
const animatedPillStyle = useAnimatedStyle(
|
|
67
|
+
() => ({
|
|
68
|
+
transform: [{ translateX: pillTranslateX.value }],
|
|
69
|
+
width: pillWidth.value,
|
|
70
|
+
height: pillHeight.value,
|
|
71
|
+
}),
|
|
72
|
+
[pillTranslateX, pillWidth, pillHeight],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return { onLayout, animatedPillStyle };
|
|
76
|
+
}
|
|
@@ -38,10 +38,10 @@ const useStyles = ({
|
|
|
38
38
|
gap: t.spacings.s8,
|
|
39
39
|
padding: t.spacings.s12,
|
|
40
40
|
borderRadius: t.borderRadius.md,
|
|
41
|
-
backgroundColor: t.colors.bg.
|
|
41
|
+
backgroundColor: t.colors.bg.surface,
|
|
42
42
|
},
|
|
43
43
|
isFull && { width: t.sizes.full },
|
|
44
|
-
pressed && !disabled && { backgroundColor: t.colors.bg.
|
|
44
|
+
pressed && !disabled && { backgroundColor: t.colors.bg.surfacePressed },
|
|
45
45
|
disabled && { backgroundColor: t.colors.bg.disabled },
|
|
46
46
|
]),
|
|
47
47
|
label: StyleSheet.flatten([
|