@ecohouse/ui 0.1.23 → 0.1.25
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
3
3
|
import { StyleSheet, View } from "react-native";
|
|
4
|
-
import { LayoutGridIcon } from "../../icons";
|
|
4
|
+
import { LayoutGridIcon, ListViewIcon, MapViewIcon } from "../../icons";
|
|
5
5
|
import { SegmentedToggle } from "./SegmentedToggle";
|
|
6
6
|
|
|
7
7
|
const optionsWithIcons = [
|
|
@@ -14,6 +14,21 @@ const optionsWithoutIcons = [
|
|
|
14
14
|
{ value: "second", label: "Label" },
|
|
15
15
|
] as const;
|
|
16
16
|
|
|
17
|
+
const multilingualOptions = [
|
|
18
|
+
{ value: "daily", label: "Օրավարձ" },
|
|
19
|
+
{ value: "overnight", label: "Գիշերակաց երկար տարբերակ" },
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
const multilingualIconOptions = [
|
|
23
|
+
{ value: "list", label: "Ցուցակի տարբերակ", icon: LayoutGridIcon },
|
|
24
|
+
{ value: "map", label: "Քարտեզի շատ երկար տարբերակ", icon: LayoutGridIcon },
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
const armenianViewOptions = [
|
|
28
|
+
{ value: "list", label: "Ցանկ", icon: ListViewIcon },
|
|
29
|
+
{ value: "map", label: "Քարտեզ", icon: MapViewIcon },
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
17
32
|
const meta = {
|
|
18
33
|
title: "Components/SegmentedToggle",
|
|
19
34
|
component: SegmentedToggle,
|
|
@@ -39,6 +54,38 @@ export const ContentWidth: Story = {
|
|
|
39
54
|
args: { fullWidth: false },
|
|
40
55
|
};
|
|
41
56
|
|
|
57
|
+
export const MultilingualContentWidth: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
options: multilingualOptions,
|
|
60
|
+
defaultValue: "daily",
|
|
61
|
+
fullWidth: false,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const MultilingualIconsContentWidth: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
options: multilingualIconOptions,
|
|
68
|
+
defaultValue: "list",
|
|
69
|
+
fullWidth: false,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const ArmenianViewMode: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
options: armenianViewOptions,
|
|
76
|
+
defaultValue: "list",
|
|
77
|
+
fullWidth: false,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const ArmenianViewModeMapSelected: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
options: armenianViewOptions,
|
|
84
|
+
defaultValue: "map",
|
|
85
|
+
fullWidth: false,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
42
89
|
export const In400PixelContainer: Story = {
|
|
43
90
|
render: () => (
|
|
44
91
|
<View style={storyStyles.container}>
|
|
@@ -37,7 +37,9 @@ export interface SegmentedToggleProps extends Omit<ViewProps, "style" | "childre
|
|
|
37
37
|
const ICON_SIZE = 20;
|
|
38
38
|
const ANIMATION_DURATION = 200;
|
|
39
39
|
const OPTION_HORIZONTAL_PADDING = 16;
|
|
40
|
+
const OPTION_CONTENT_GAP = 12;
|
|
40
41
|
const OPTION_MIN_WIDTH = 97;
|
|
42
|
+
const OPTION_MEASUREMENT_TOLERANCE = 0.5;
|
|
41
43
|
|
|
42
44
|
export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedToggleProps>(
|
|
43
45
|
function SegmentedToggle(
|
|
@@ -67,12 +69,23 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
67
69
|
const resolvedClassName = className?.trim() || undefined;
|
|
68
70
|
const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
|
|
69
71
|
const progress = useRef(new Animated.Value(selectedIndex)).current;
|
|
72
|
+
const [contentWidths, setContentWidths] = useState<[number, number]>([0, 0]);
|
|
70
73
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
71
74
|
const availableOptionWidth = Math.max(0, (containerWidth - 12) / 2);
|
|
72
|
-
const
|
|
75
|
+
const resolveContentOptionWidth = (contentWidth: number) =>
|
|
76
|
+
contentWidth > 0
|
|
77
|
+
? Math.max(
|
|
78
|
+
OPTION_MIN_WIDTH,
|
|
79
|
+
contentWidth + OPTION_HORIZONTAL_PADDING * 2 + OPTION_MEASUREMENT_TOLERANCE,
|
|
80
|
+
)
|
|
81
|
+
: 0;
|
|
82
|
+
const optionWidths: [number, number] = fullWidth
|
|
83
|
+
? [availableOptionWidth, availableOptionWidth]
|
|
84
|
+
: [resolveContentOptionWidth(contentWidths[0]), resolveContentOptionWidth(contentWidths[1])];
|
|
85
|
+
const selectedOptionWidth = optionWidths[selectedIndex];
|
|
73
86
|
const indicatorTranslateX = progress.interpolate({
|
|
74
87
|
inputRange: [0, 1],
|
|
75
|
-
outputRange: [0,
|
|
88
|
+
outputRange: [0, optionWidths[0] + 4],
|
|
76
89
|
});
|
|
77
90
|
|
|
78
91
|
useApplyWebClassName(containerRef, resolvedClassName);
|
|
@@ -114,16 +127,40 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
114
127
|
]}
|
|
115
128
|
accessibilityRole="radiogroup"
|
|
116
129
|
>
|
|
117
|
-
{
|
|
130
|
+
{!fullWidth
|
|
131
|
+
? options.map(({ value: optionValue, label, icon: Icon }, index) => (
|
|
132
|
+
<View
|
|
133
|
+
key={`${optionValue}-measurement`}
|
|
134
|
+
pointerEvents="none"
|
|
135
|
+
aria-hidden
|
|
136
|
+
accessibilityElementsHidden
|
|
137
|
+
importantForAccessibility="no-hide-descendants"
|
|
138
|
+
onLayout={(event) => {
|
|
139
|
+
const nextWidth = event.nativeEvent.layout.width;
|
|
140
|
+
setContentWidths((currentWidths) => {
|
|
141
|
+
if (currentWidths[index] === nextWidth) return currentWidths;
|
|
142
|
+
const nextWidths: [number, number] = [currentWidths[0], currentWidths[1]];
|
|
143
|
+
nextWidths[index] = nextWidth;
|
|
144
|
+
return nextWidths;
|
|
145
|
+
});
|
|
146
|
+
}}
|
|
147
|
+
style={styles.optionMeasurement}
|
|
148
|
+
>
|
|
149
|
+
{Icon ? <Icon size={ICON_SIZE} /> : null}
|
|
150
|
+
<Text style={[styles.label, styles.contentWidthLabel]}>{label}</Text>
|
|
151
|
+
</View>
|
|
152
|
+
))
|
|
153
|
+
: null}
|
|
154
|
+
{selectedOptionWidth > 0 ? (
|
|
118
155
|
<Animated.View
|
|
119
156
|
pointerEvents="none"
|
|
120
157
|
style={[
|
|
121
158
|
styles.activeIndicator,
|
|
122
|
-
{ width:
|
|
159
|
+
{ width: selectedOptionWidth, transform: [{ translateX: indicatorTranslateX }] },
|
|
123
160
|
]}
|
|
124
161
|
/>
|
|
125
162
|
) : null}
|
|
126
|
-
{options.map(({ value: optionValue, label, icon: Icon }) => {
|
|
163
|
+
{options.map(({ value: optionValue, label, icon: Icon }, index) => {
|
|
127
164
|
const selected = optionValue === selectedValue;
|
|
128
165
|
const iconColor = selected ? colors.primary : colors.whiteAlpha20;
|
|
129
166
|
const labelColor = selected ? colors.whiteAlpha86 : colors.whiteAlpha40;
|
|
@@ -141,12 +178,22 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
141
178
|
styles.option,
|
|
142
179
|
fullWidth
|
|
143
180
|
? styles.optionFullWidth
|
|
144
|
-
:
|
|
181
|
+
: [
|
|
182
|
+
styles.optionContentWidth,
|
|
183
|
+
optionWidths[index] > 0 && { width: optionWidths[index] },
|
|
184
|
+
],
|
|
145
185
|
]}
|
|
146
186
|
>
|
|
147
187
|
<View style={styles.optionContent}>
|
|
148
188
|
{Icon ? <Icon size={ICON_SIZE} color={iconColor} /> : null}
|
|
149
|
-
<Text
|
|
189
|
+
<Text
|
|
190
|
+
numberOfLines={fullWidth ? 1 : undefined}
|
|
191
|
+
style={[
|
|
192
|
+
styles.label,
|
|
193
|
+
!fullWidth && styles.contentWidthLabel,
|
|
194
|
+
{ color: labelColor },
|
|
195
|
+
]}
|
|
196
|
+
>
|
|
150
197
|
{label}
|
|
151
198
|
</Text>
|
|
152
199
|
</View>
|
|
@@ -190,12 +237,22 @@ const styles = StyleSheet.create({
|
|
|
190
237
|
maxWidth: "100%",
|
|
191
238
|
flexDirection: "row",
|
|
192
239
|
alignItems: "center",
|
|
193
|
-
gap:
|
|
240
|
+
gap: OPTION_CONTENT_GAP,
|
|
241
|
+
},
|
|
242
|
+
optionMeasurement: {
|
|
243
|
+
position: "absolute",
|
|
244
|
+
flexDirection: "row",
|
|
245
|
+
alignItems: "center",
|
|
246
|
+
gap: OPTION_CONTENT_GAP,
|
|
247
|
+
opacity: 0,
|
|
194
248
|
},
|
|
195
249
|
optionFullWidth: {
|
|
196
250
|
flex: 1,
|
|
197
251
|
minWidth: 0,
|
|
198
252
|
},
|
|
253
|
+
optionContentWidth: {
|
|
254
|
+
minWidth: OPTION_MIN_WIDTH,
|
|
255
|
+
},
|
|
199
256
|
activeIndicator: {
|
|
200
257
|
position: "absolute",
|
|
201
258
|
top: 4,
|
|
@@ -208,4 +265,7 @@ const styles = StyleSheet.create({
|
|
|
208
265
|
...segmentedToggleTypography,
|
|
209
266
|
flexShrink: 1,
|
|
210
267
|
},
|
|
268
|
+
contentWidthLabel: {
|
|
269
|
+
flexShrink: 0,
|
|
270
|
+
},
|
|
211
271
|
});
|