@ecohouse/ui 0.1.44 → 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 +257 -239
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +173 -156
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SegmentedToggle/SegmentedToggle.tsx +46 -50
- package/src/icons/NoReview/NoReviewIcon.tsx +20 -0
- package/src/icons/NoReview/NoReviewIcon.web.tsx +19 -0
- package/src/icons/NoReview/index.ts +1 -0
- package/src/icons/NoReview/noReviewPath.ts +3 -0
- package/src/icons/index.ts +1 -0
- package/src/icons/registry.ts +3 -0
- package/src/index.ts +1 -0
- 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,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Svg, { Path } from "react-native-svg";
|
|
2
|
+
import { colors } from "../../theme";
|
|
3
|
+
import type { IconProps } from "../types";
|
|
4
|
+
import { NO_REVIEW_PATH } from "./noReviewPath";
|
|
5
|
+
|
|
6
|
+
export { NO_REVIEW_PATH } from "./noReviewPath";
|
|
7
|
+
|
|
8
|
+
export function NoReviewIcon({ size = 20, color = colors.grey150, strokeWidth = 2 }: IconProps) {
|
|
9
|
+
return (
|
|
10
|
+
<Svg width={size} height={size} viewBox="0 0 20 20" fill="none">
|
|
11
|
+
<Path
|
|
12
|
+
d={NO_REVIEW_PATH}
|
|
13
|
+
stroke={color}
|
|
14
|
+
strokeWidth={strokeWidth}
|
|
15
|
+
strokeLinecap="round"
|
|
16
|
+
strokeLinejoin="round"
|
|
17
|
+
/>
|
|
18
|
+
</Svg>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { colors } from "../../theme";
|
|
2
|
+
import type { IconProps } from "../types";
|
|
3
|
+
import { NO_REVIEW_PATH } from "./noReviewPath";
|
|
4
|
+
|
|
5
|
+
export function NoReviewIcon({ size = 20, color = colors.grey150, strokeWidth = 2 }: IconProps) {
|
|
6
|
+
return (
|
|
7
|
+
<svg width={size} height={size} viewBox="0 0 20 20" fill="none" aria-hidden>
|
|
8
|
+
<path
|
|
9
|
+
d={NO_REVIEW_PATH}
|
|
10
|
+
stroke={color}
|
|
11
|
+
strokeWidth={strokeWidth}
|
|
12
|
+
strokeLinecap="round"
|
|
13
|
+
strokeLinejoin="round"
|
|
14
|
+
/>
|
|
15
|
+
</svg>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { NO_REVIEW_PATH } from "./noReviewPath";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NoReviewIcon, NO_REVIEW_PATH } from "./NoReviewIcon";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** No-review / empty comment path — 20×20 viewBox. */
|
|
2
|
+
export const NO_REVIEW_PATH =
|
|
3
|
+
"M8.33333 10.833L9.99999 9.16633M9.99999 9.16633L11.6666 7.49967M9.99999 9.16633L8.33333 7.49967M9.99999 9.16633L11.6666 10.833M5.93631 15.584L4.66634 16.6C3.97287 17.1547 3.62596 17.4322 3.33415 17.4326C3.08036 17.4328 2.84037 17.3174 2.68205 17.1191C2.5 16.891 2.5 16.4471 2.5 15.559V5.99984C2.5 5.06642 2.5 4.59936 2.68166 4.24284C2.84144 3.92924 3.09623 3.67445 3.40983 3.51466C3.76635 3.33301 4.23341 3.33301 5.16683 3.33301H14.8335C15.7669 3.33301 16.233 3.33301 16.5895 3.51466C16.9031 3.67445 17.1587 3.92924 17.3185 4.24284C17.5 4.59901 17.5 5.0655 17.5 5.9971V12.336C17.5 13.2676 17.5 13.7334 17.3185 14.0896C17.1587 14.4032 16.9036 14.6584 16.59 14.8182C16.2338 14.9997 15.7675 14.9997 14.8359 14.9997H7.60173C7.25503 14.9997 7.08231 14.9997 6.9165 15.0337C6.7694 15.0639 6.6268 15.1136 6.49307 15.182C6.34401 15.2581 6.21041 15.365 5.94565 15.5768L5.93631 15.584Z";
|
package/src/icons/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export { ChevronLeftIcon } from "./ChevronLeft";
|
|
|
27
27
|
export { ClockIcon } from "./Clock";
|
|
28
28
|
export { CloseIcon } from "./Close";
|
|
29
29
|
export { CloudUploadIcon } from "./CloudUpload";
|
|
30
|
+
export { NoReviewIcon } from "./NoReview";
|
|
30
31
|
export { CurrentLocationIcon } from "./CurrentLocation";
|
|
31
32
|
export { EditIcon } from "./Edit";
|
|
32
33
|
export { EyeIcon } from "./Eye";
|
package/src/icons/registry.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { ChevronLeftIcon } from "./ChevronLeft";
|
|
|
17
17
|
import { ClockIcon } from "./Clock";
|
|
18
18
|
import { CloseIcon } from "./Close";
|
|
19
19
|
import { CloudUploadIcon } from "./CloudUpload";
|
|
20
|
+
import { NoReviewIcon } from "./NoReview";
|
|
20
21
|
import { EditIcon } from "./Edit";
|
|
21
22
|
import { EyeIcon } from "./Eye";
|
|
22
23
|
import {
|
|
@@ -117,6 +118,7 @@ export const icons = {
|
|
|
117
118
|
clock: ClockIcon,
|
|
118
119
|
close: CloseIcon,
|
|
119
120
|
cloudUpload: CloudUploadIcon,
|
|
121
|
+
noReview: NoReviewIcon,
|
|
120
122
|
edit: EditIcon,
|
|
121
123
|
clockFilled: ClockFilledIcon,
|
|
122
124
|
copy: CopyIcon,
|
|
@@ -235,6 +237,7 @@ export const iconGroups = {
|
|
|
235
237
|
"calendarOutline",
|
|
236
238
|
"close",
|
|
237
239
|
"cloudUpload",
|
|
240
|
+
"noReview",
|
|
238
241
|
"edit",
|
|
239
242
|
"ratingStar",
|
|
240
243
|
"saveHeart",
|
package/src/index.ts
CHANGED
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;
|