@cleartrip/ct-design-segment 5.6.0 → 5.7.0-SNAPSHOT-native-main.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/dist/Segment.d.ts.map +1 -1
- package/dist/Segment.native.d.ts.map +1 -1
- package/dist/ct-design-segment.browser.cjs.js +1 -1
- package/dist/ct-design-segment.browser.cjs.js.map +1 -1
- package/dist/ct-design-segment.browser.esm.js +1 -1
- package/dist/ct-design-segment.browser.esm.js.map +1 -1
- package/dist/ct-design-segment.cjs.js +61 -27
- package/dist/ct-design-segment.cjs.js.map +1 -1
- package/dist/ct-design-segment.esm.js +61 -27
- package/dist/ct-design-segment.esm.js.map +1 -1
- package/dist/ct-design-segment.umd.js +65 -30
- package/dist/ct-design-segment.umd.js.map +1 -1
- package/dist/style.d.ts +26 -4
- package/dist/style.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/Segment.native.tsx +7 -1
- package/src/Segment.tsx +23 -21
- package/src/SegmentButton.native.tsx +2 -2
- package/src/SegmentButton.tsx +4 -4
- package/src/style.ts +52 -7
package/src/Segment.tsx
CHANGED
|
@@ -2,9 +2,8 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { useTheme } from '@cleartrip/ct-design-theme';
|
|
4
4
|
import { useStyles, useWebMergeStyles } from '@cleartrip/ct-design-style-manager';
|
|
5
|
-
import { Container } from '@cleartrip/ct-design-container';
|
|
6
5
|
|
|
7
|
-
import { getSegmentVariantStyles, segmentStaticStyles, segmentWebThumbStyles } from './style';
|
|
6
|
+
import { getSegmentVariantStyles, segmentStaticStyles, segmentWebLayoutStyles, segmentWebThumbStyles } from './style';
|
|
8
7
|
import { ISegmentProps } from './type';
|
|
9
8
|
import { SegmentVariant } from './constants';
|
|
10
9
|
import { SegmentButton } from './SegmentButton';
|
|
@@ -44,6 +43,7 @@ const Segment: React.FC<ISegmentProps> = ({
|
|
|
44
43
|
const variantStyles = useMemo(() => getSegmentVariantStyles(variant, theme), [variant, theme]);
|
|
45
44
|
|
|
46
45
|
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
46
|
+
const trackRef = useRef<HTMLDivElement | null>(null);
|
|
47
47
|
const tabRefs = useRef<Array<HTMLDivElement | null>>([]);
|
|
48
48
|
|
|
49
49
|
const sanitizedOptions = useMemo(
|
|
@@ -67,15 +67,15 @@ const Segment: React.FC<ISegmentProps> = ({
|
|
|
67
67
|
const baseSegmentTextRoot = useMemo(() => toStyleArray(baseSegmentTextConfig.root), [baseSegmentTextConfig.root]);
|
|
68
68
|
|
|
69
69
|
const measureTabs = useCallback(() => {
|
|
70
|
-
const
|
|
71
|
-
if (!
|
|
72
|
-
const
|
|
73
|
-
setTrackWidth(
|
|
70
|
+
const trackNode = trackRef.current;
|
|
71
|
+
if (!trackNode) return;
|
|
72
|
+
const trackRect = trackNode.getBoundingClientRect();
|
|
73
|
+
setTrackWidth(trackRect.width);
|
|
74
74
|
const layouts = sanitizedOptions.map((_, index) => {
|
|
75
75
|
const tabNode = tabRefs.current[index];
|
|
76
76
|
if (!tabNode) return { x: 0, width: 0 };
|
|
77
77
|
const rect = tabNode.getBoundingClientRect();
|
|
78
|
-
return { x: rect.left -
|
|
78
|
+
return { x: rect.left - trackRect.left, width: rect.width };
|
|
79
79
|
});
|
|
80
80
|
setTabLayouts(layouts);
|
|
81
81
|
}, [sanitizedOptions]);
|
|
@@ -89,11 +89,11 @@ const Segment: React.FC<ISegmentProps> = ({
|
|
|
89
89
|
}, [measureTabs]);
|
|
90
90
|
|
|
91
91
|
useEffect(() => {
|
|
92
|
-
if (typeof ResizeObserver === 'undefined' || !
|
|
92
|
+
if (typeof ResizeObserver === 'undefined' || !trackRef.current) return;
|
|
93
93
|
const observer = new ResizeObserver(() => {
|
|
94
94
|
measureTabs();
|
|
95
95
|
});
|
|
96
|
-
observer.observe(
|
|
96
|
+
observer.observe(trackRef.current);
|
|
97
97
|
tabRefs.current.forEach((tabNode) => {
|
|
98
98
|
if (tabNode) observer.observe(tabNode);
|
|
99
99
|
});
|
|
@@ -196,9 +196,13 @@ const Segment: React.FC<ISegmentProps> = ({
|
|
|
196
196
|
],
|
|
197
197
|
[segmentStaticStyles.root, dynamicStyles.root, variantStyles.root?.style, root],
|
|
198
198
|
);
|
|
199
|
+
const animatedContainerClassName = useWebMergeStyles(
|
|
200
|
+
[segmentStaticStyles.animatedContainer, segmentWebLayoutStyles.animatedContainer, ...animatedContainer],
|
|
201
|
+
[segmentStaticStyles.animatedContainer, segmentWebLayoutStyles.animatedContainer, animatedContainer],
|
|
202
|
+
);
|
|
199
203
|
const tabMeasureCellClassName = useWebMergeStyles(
|
|
200
|
-
[segmentStaticStyles.tabMeasureCell],
|
|
201
|
-
[segmentStaticStyles.tabMeasureCell],
|
|
204
|
+
[segmentStaticStyles.tabMeasureCell, segmentWebLayoutStyles.tabMeasureCell],
|
|
205
|
+
[segmentStaticStyles.tabMeasureCell, segmentWebLayoutStyles.tabMeasureCell],
|
|
202
206
|
);
|
|
203
207
|
const webThumbBaseClassName = useWebMergeStyles(
|
|
204
208
|
[
|
|
@@ -219,9 +223,11 @@ const Segment: React.FC<ISegmentProps> = ({
|
|
|
219
223
|
if (hasStableMeasurements && optionLength > 0) {
|
|
220
224
|
const safeIndex = Math.max(0, Math.min(activeIndex, optionLength - 1));
|
|
221
225
|
const activeLayout = tabLayouts[safeIndex];
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const
|
|
226
|
+
const rawWidth = Math.max(activeLayout?.width ?? trackWidth / Math.max(optionLength, 1), 0);
|
|
227
|
+
const rawLeft = Math.max(activeLayout?.x ?? 0, 0);
|
|
228
|
+
const maxLeft = Math.max(trackWidth - rawWidth, 0);
|
|
229
|
+
const left = Math.min(rawLeft, maxLeft);
|
|
230
|
+
const width = Math.min(rawWidth, Math.max(trackWidth - left, 0));
|
|
225
231
|
return css({
|
|
226
232
|
left: `${left}px`,
|
|
227
233
|
width: `${width}px`,
|
|
@@ -242,11 +248,8 @@ const Segment: React.FC<ISegmentProps> = ({
|
|
|
242
248
|
|
|
243
249
|
return (
|
|
244
250
|
<div className={rootClassName} ref={rootRef}>
|
|
245
|
-
<
|
|
246
|
-
|
|
247
|
-
root: [segmentStaticStyles.animatedContainer, ...animatedContainer],
|
|
248
|
-
}}
|
|
249
|
-
>
|
|
251
|
+
<div ref={trackRef} className={animatedContainerClassName}>
|
|
252
|
+
<div className={`${webThumbBaseClassName} ${dynamicWebStyles}`} aria-hidden />
|
|
250
253
|
{!isEmptyOptions(sanitizedOptions) &&
|
|
251
254
|
sanitizedOptions.map((option, index) => (
|
|
252
255
|
<div
|
|
@@ -268,8 +271,7 @@ const Segment: React.FC<ISegmentProps> = ({
|
|
|
268
271
|
/>
|
|
269
272
|
</div>
|
|
270
273
|
))}
|
|
271
|
-
</
|
|
272
|
-
<div className={`${webThumbBaseClassName} ${dynamicWebStyles}`} />
|
|
274
|
+
</div>
|
|
273
275
|
</div>
|
|
274
276
|
);
|
|
275
277
|
};
|
|
@@ -33,8 +33,8 @@ export const SegmentButton: React.FC<ISegmentButtonProps> = ({
|
|
|
33
33
|
minHeight: 0,
|
|
34
34
|
width: undefined,
|
|
35
35
|
alignSelf: 'stretch',
|
|
36
|
-
height:
|
|
37
|
-
maxHeight:
|
|
36
|
+
height: '100%',
|
|
37
|
+
maxHeight: '100%',
|
|
38
38
|
paddingHorizontal: 0,
|
|
39
39
|
paddingTop: 0,
|
|
40
40
|
paddingBottom: 0,
|
package/src/SegmentButton.tsx
CHANGED
|
@@ -31,10 +31,10 @@ export const SegmentButton: React.FC<ISegmentButtonProps> = ({
|
|
|
31
31
|
flexShrink: 1,
|
|
32
32
|
minWidth: 0,
|
|
33
33
|
minHeight: 0,
|
|
34
|
-
width:
|
|
34
|
+
width: t.size['100P'],
|
|
35
35
|
alignSelf: 'stretch',
|
|
36
|
-
height:
|
|
37
|
-
maxHeight:
|
|
36
|
+
height: '100%',
|
|
37
|
+
maxHeight: '100%',
|
|
38
38
|
paddingHorizontal: 0,
|
|
39
39
|
paddingTop: 0,
|
|
40
40
|
paddingBottom: 0,
|
|
@@ -91,7 +91,7 @@ export const SegmentButton: React.FC<ISegmentButtonProps> = ({
|
|
|
91
91
|
variant={ButtonVariant.BARE}
|
|
92
92
|
color={ButtonColor.TERTIARY}
|
|
93
93
|
size={ButtonSize.SMALL}
|
|
94
|
-
isFullWidth
|
|
94
|
+
isFullWidth
|
|
95
95
|
prefixIcon={option?.icon}
|
|
96
96
|
onClick={() => {
|
|
97
97
|
if (disabled) return;
|
package/src/style.ts
CHANGED
|
@@ -15,6 +15,7 @@ export const segmentThumbShadowNative = {
|
|
|
15
15
|
elevation: 3,
|
|
16
16
|
} as const;
|
|
17
17
|
|
|
18
|
+
/** Shared layout — do not add web-only flex rules here; native uses this as-is. */
|
|
18
19
|
export const segmentStaticStyles = makeStyles((theme) => {
|
|
19
20
|
const trackHeight = theme.size[10];
|
|
20
21
|
const borderWidth = theme.border.width.sm;
|
|
@@ -51,8 +52,6 @@ export const segmentStaticStyles = makeStyles((theme) => {
|
|
|
51
52
|
},
|
|
52
53
|
animatedBlock: {
|
|
53
54
|
position: 'absolute',
|
|
54
|
-
top: 0,
|
|
55
|
-
height: '100%',
|
|
56
55
|
borderRadius: thumbRadius,
|
|
57
56
|
backgroundColor: theme.color.background.neutral,
|
|
58
57
|
},
|
|
@@ -76,23 +75,69 @@ export const segmentStaticStyles = makeStyles((theme) => {
|
|
|
76
75
|
};
|
|
77
76
|
});
|
|
78
77
|
|
|
79
|
-
|
|
78
|
+
/** Web-only flex layout — `<div>` needs explicit display; native Container must not use these. */
|
|
79
|
+
export const segmentWebLayoutStyles = makeStyles(() => ({
|
|
80
|
+
animatedContainer: {
|
|
81
|
+
display: 'flex',
|
|
82
|
+
},
|
|
83
|
+
tabMeasureCell: {
|
|
84
|
+
display: 'flex',
|
|
85
|
+
flexDirection: 'row',
|
|
86
|
+
justifyContent: 'center',
|
|
87
|
+
alignItems: 'center',
|
|
88
|
+
position: 'relative',
|
|
89
|
+
zIndex: 1,
|
|
90
|
+
},
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Native thumb — inset within the padded track (web uses segmentWebThumbStyles instead).
|
|
95
|
+
*/
|
|
96
|
+
export const segmentNativeThumbStyles = makeStyles((theme) => {
|
|
80
97
|
const trackHeight = theme.size[10];
|
|
81
98
|
const borderWidth = theme.border.width.sm;
|
|
99
|
+
const trackPadding = theme.spacing[1];
|
|
82
100
|
const trackInnerHeight = Math.max(trackHeight - borderWidth * 2, 0);
|
|
101
|
+
const paddedInnerHeight = Math.max(trackInnerHeight - trackPadding * 2, 0);
|
|
83
102
|
const thumbInset = Math.max(theme.spacing[0.25], 0);
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const thumbRadius = Math.max(0, trackInnerRadius - thumbInset);
|
|
103
|
+
const paddedInnerRadius = Math.max(0, paddedInnerHeight / 2);
|
|
104
|
+
const thumbRadius = Math.max(0, paddedInnerRadius - thumbInset);
|
|
87
105
|
|
|
88
106
|
return {
|
|
89
107
|
root: {
|
|
90
108
|
position: 'absolute',
|
|
91
|
-
|
|
109
|
+
top: thumbInset,
|
|
110
|
+
bottom: thumbInset,
|
|
92
111
|
borderRadius: thumbRadius,
|
|
93
112
|
backgroundColor: theme.color.background.neutral,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Web-only (Segment.tsx) — native uses segmentNativeThumbStyles instead.
|
|
119
|
+
* Thumb fills animatedContainer via top/bottom inset, not a fixed height from trackHeight.
|
|
120
|
+
*/
|
|
121
|
+
export const segmentWebThumbStyles = makeStyles((theme) => {
|
|
122
|
+
const trackHeight = theme.size[10];
|
|
123
|
+
const borderWidth = theme.border.width.sm;
|
|
124
|
+
const trackPadding = theme.spacing[1];
|
|
125
|
+
const trackInnerHeight = Math.max(trackHeight - borderWidth * 2, 0);
|
|
126
|
+
const paddedInnerHeight = Math.max(trackInnerHeight - trackPadding * 2, 0);
|
|
127
|
+
const thumbInset = Math.max(theme.spacing[0.25], 0);
|
|
128
|
+
const paddedInnerRadius = Math.max(0, paddedInnerHeight / 2);
|
|
129
|
+
const thumbRadius = Math.max(0, paddedInnerRadius - thumbInset);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
root: {
|
|
133
|
+
position: 'absolute',
|
|
94
134
|
top: thumbInset,
|
|
135
|
+
bottom: thumbInset,
|
|
136
|
+
borderRadius: thumbRadius,
|
|
137
|
+
backgroundColor: theme.color.background.neutral,
|
|
95
138
|
boxShadow: SEGMENT_THUMB_BOX_SHADOW,
|
|
139
|
+
pointerEvents: 'none',
|
|
140
|
+
zIndex: 0,
|
|
96
141
|
},
|
|
97
142
|
};
|
|
98
143
|
});
|