@ecohouse/ui 0.1.45 → 0.1.46
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/index.cjs +57 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +57 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SegmentedToggle/SegmentedToggle.tsx +46 -50
- package/src/theme/typography.ts +2 -2
package/package.json
CHANGED
|
@@ -47,7 +47,6 @@ const ANIMATION_DURATION = 200;
|
|
|
47
47
|
const OPTION_HORIZONTAL_PADDING = 16;
|
|
48
48
|
const OPTION_CONTENT_GAP = 12;
|
|
49
49
|
const OPTION_MIN_WIDTH = 97;
|
|
50
|
-
const OPTION_MEASUREMENT_TOLERANCE = 0.5;
|
|
51
50
|
const TRACK_PADDING = 4;
|
|
52
51
|
const OPTION_GAP = 4;
|
|
53
52
|
|
|
@@ -91,7 +90,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
91
90
|
const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
|
|
92
91
|
const progress = useRef(new Animated.Value(selectedIndex)).current;
|
|
93
92
|
const hasMounted = useRef(false);
|
|
94
|
-
const [
|
|
93
|
+
const [measuredOptionWidths, setMeasuredOptionWidths] = useState<number[]>(() =>
|
|
95
94
|
Array.from({ length: optionCount }, () => 0),
|
|
96
95
|
);
|
|
97
96
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
@@ -99,19 +98,12 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
99
98
|
// Track chrome: left+right padding + gaps between options.
|
|
100
99
|
const trackChrome = TRACK_PADDING * 2 + OPTION_GAP * (optionCount - 1);
|
|
101
100
|
const availableOptionWidth = Math.max(0, (containerWidth - trackChrome) / optionCount);
|
|
102
|
-
const resolveContentOptionWidth = (contentWidth: number) =>
|
|
103
|
-
contentWidth > 0
|
|
104
|
-
? Math.max(
|
|
105
|
-
OPTION_MIN_WIDTH,
|
|
106
|
-
contentWidth + OPTION_HORIZONTAL_PADDING * 2 + OPTION_MEASUREMENT_TOLERANCE,
|
|
107
|
-
)
|
|
108
|
-
: 0;
|
|
109
|
-
|
|
110
101
|
const optionWidths = fullWidth
|
|
111
102
|
? Array.from({ length: optionCount }, () => availableOptionWidth)
|
|
112
|
-
:
|
|
103
|
+
: measuredOptionWidths;
|
|
113
104
|
|
|
114
|
-
const
|
|
105
|
+
const measurementsReady = optionWidths.every((width) => width > 0);
|
|
106
|
+
const selectedOptionWidth = measurementsReady ? (optionWidths[selectedIndex] ?? 0) : 0;
|
|
115
107
|
const indicatorInputRange = options.map((_, index) => index);
|
|
116
108
|
const indicatorOutputRange = buildIndicatorOutputRange(optionWidths);
|
|
117
109
|
const indicatorTranslateX = progress.interpolate({
|
|
@@ -122,7 +114,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
122
114
|
useApplyWebClassName(containerRef, resolvedClassName);
|
|
123
115
|
|
|
124
116
|
useEffect(() => {
|
|
125
|
-
|
|
117
|
+
setMeasuredOptionWidths((current) => {
|
|
126
118
|
if (current.length === optionCount) return current;
|
|
127
119
|
return Array.from({ length: optionCount }, (_, index) => current[index] ?? 0);
|
|
128
120
|
});
|
|
@@ -171,32 +163,6 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
171
163
|
]}
|
|
172
164
|
accessibilityRole="radiogroup"
|
|
173
165
|
>
|
|
174
|
-
{!fullWidth
|
|
175
|
-
? options.map(({ value: optionValue, label, icon: Icon, showLabel = true }, index) => (
|
|
176
|
-
<View
|
|
177
|
-
key={`${optionValue}-measurement`}
|
|
178
|
-
pointerEvents="none"
|
|
179
|
-
aria-hidden
|
|
180
|
-
accessibilityElementsHidden
|
|
181
|
-
importantForAccessibility="no-hide-descendants"
|
|
182
|
-
onLayout={(event) => {
|
|
183
|
-
const nextWidth = event.nativeEvent.layout.width;
|
|
184
|
-
setContentWidths((currentWidths) => {
|
|
185
|
-
if (currentWidths[index] === nextWidth) return currentWidths;
|
|
186
|
-
const nextWidths = [...currentWidths];
|
|
187
|
-
nextWidths[index] = nextWidth;
|
|
188
|
-
return nextWidths;
|
|
189
|
-
});
|
|
190
|
-
}}
|
|
191
|
-
style={styles.optionMeasurement}
|
|
192
|
-
>
|
|
193
|
-
{Icon ? <Icon size={ICON_SIZE} /> : null}
|
|
194
|
-
{showLabel ? (
|
|
195
|
-
<Text style={[styles.label, styles.contentWidthLabel]}>{label}</Text>
|
|
196
|
-
) : null}
|
|
197
|
-
</View>
|
|
198
|
-
))
|
|
199
|
-
: null}
|
|
200
166
|
{selectedOptionWidth > 0 ? (
|
|
201
167
|
<Animated.View
|
|
202
168
|
pointerEvents="none"
|
|
@@ -210,7 +176,6 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
210
176
|
const selected = optionValue === selectedValue;
|
|
211
177
|
const iconColor = selected ? colors.primary : colors.whiteAlpha20;
|
|
212
178
|
const labelColor = selected ? colors.white : colors.grey150;
|
|
213
|
-
const optionWidth = optionWidths[index] ?? 0;
|
|
214
179
|
|
|
215
180
|
return (
|
|
216
181
|
<Pressable
|
|
@@ -218,18 +183,45 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
218
183
|
onPress={() => selectOption(optionValue)}
|
|
219
184
|
disabled={disabled}
|
|
220
185
|
hitSlop={hitSlop}
|
|
186
|
+
onLayout={
|
|
187
|
+
fullWidth
|
|
188
|
+
? undefined
|
|
189
|
+
: (event) => {
|
|
190
|
+
const nextWidth = event.nativeEvent.layout.width;
|
|
191
|
+
setMeasuredOptionWidths((currentWidths) => {
|
|
192
|
+
if (currentWidths[index] === nextWidth) return currentWidths;
|
|
193
|
+
const nextWidths = [...currentWidths];
|
|
194
|
+
nextWidths[index] = nextWidth;
|
|
195
|
+
return nextWidths;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
221
199
|
accessibilityRole="radio"
|
|
222
200
|
accessibilityLabel={label}
|
|
223
201
|
accessibilityState={{ selected, disabled }}
|
|
224
202
|
style={[
|
|
225
203
|
styles.option,
|
|
226
|
-
fullWidth
|
|
227
|
-
|
|
228
|
-
: [styles.optionContentWidth, optionWidth > 0 && { width: optionWidth }],
|
|
229
|
-
selected && selectedOptionWidth === 0 && styles.initialSelectedOption,
|
|
204
|
+
fullWidth ? styles.optionFullWidth : styles.optionContentWidth,
|
|
205
|
+
selected && !measurementsReady && styles.initialSelectedOption,
|
|
230
206
|
]}
|
|
231
207
|
>
|
|
232
|
-
|
|
208
|
+
{!fullWidth ? (
|
|
209
|
+
<View
|
|
210
|
+
aria-hidden
|
|
211
|
+
accessibilityElementsHidden
|
|
212
|
+
importantForAccessibility="no-hide-descendants"
|
|
213
|
+
pointerEvents="none"
|
|
214
|
+
style={[styles.optionContent, styles.optionSizeProbe]}
|
|
215
|
+
>
|
|
216
|
+
{Icon ? <Icon size={ICON_SIZE} /> : null}
|
|
217
|
+
{showLabel ? (
|
|
218
|
+
<Text style={[styles.label, styles.selectedLabel, styles.contentWidthLabel]}>
|
|
219
|
+
{label}
|
|
220
|
+
</Text>
|
|
221
|
+
) : null}
|
|
222
|
+
</View>
|
|
223
|
+
) : null}
|
|
224
|
+
<View style={[styles.optionContent, !fullWidth && styles.contentWidthVisibleContent]}>
|
|
233
225
|
{Icon ? <Icon size={ICON_SIZE} color={iconColor} /> : null}
|
|
234
226
|
{showLabel ? (
|
|
235
227
|
<Text
|
|
@@ -287,13 +279,17 @@ const styles = StyleSheet.create({
|
|
|
287
279
|
alignItems: "center",
|
|
288
280
|
gap: OPTION_CONTENT_GAP,
|
|
289
281
|
},
|
|
290
|
-
|
|
291
|
-
position: "absolute",
|
|
292
|
-
flexDirection: "row",
|
|
293
|
-
alignItems: "center",
|
|
294
|
-
gap: OPTION_CONTENT_GAP,
|
|
282
|
+
optionSizeProbe: {
|
|
295
283
|
opacity: 0,
|
|
296
284
|
},
|
|
285
|
+
contentWidthVisibleContent: {
|
|
286
|
+
position: "absolute",
|
|
287
|
+
top: 0,
|
|
288
|
+
left: 0,
|
|
289
|
+
width: "100%",
|
|
290
|
+
height: "100%",
|
|
291
|
+
justifyContent: "center",
|
|
292
|
+
},
|
|
297
293
|
optionFullWidth: {
|
|
298
294
|
flex: 1,
|
|
299
295
|
minWidth: 0,
|
package/src/theme/typography.ts
CHANGED
|
@@ -156,7 +156,7 @@ export const selectTypography = {
|
|
|
156
156
|
},
|
|
157
157
|
} as const;
|
|
158
158
|
|
|
159
|
-
/** Avatar name / email / menu-item typography —
|
|
159
|
+
/** Avatar name / email / menu-item typography — compact line heights, 0 letter-spacing. */
|
|
160
160
|
export const avatarTypography = {
|
|
161
161
|
name: {
|
|
162
162
|
fontFamily: fonts.sans,
|
|
@@ -176,7 +176,7 @@ export const avatarTypography = {
|
|
|
176
176
|
fontFamily: fonts.sans,
|
|
177
177
|
fontSize: 12,
|
|
178
178
|
fontWeight: "600" as const,
|
|
179
|
-
lineHeight:
|
|
179
|
+
lineHeight: 20,
|
|
180
180
|
letterSpacing: 0,
|
|
181
181
|
},
|
|
182
182
|
} as const;
|