Skip to content

Commit c72f523

Browse files
author
梁朝飞
committed
feat: add position props
1 parent 658632e commit c72f523

File tree

4 files changed

+152
-81
lines changed

4 files changed

+152
-81
lines changed

assets/index.less

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
@segmented-prefix-cls: rc-segmented;
22

3+
@disabled-color: fade(#000, 25%);
4+
@selected-bg-color: white;
5+
@text-color: #262626;
6+
@transition-duration: 0.3s;
7+
@transition-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1);
8+
39
.segmented-disabled-item() {
410
&,
511
&:hover,
612
&:focus {
7-
color: fade(#000, 25%);
13+
color: @disabled-color;
814
cursor: not-allowed;
915
}
1016
}
1117

1218
.segmented-item-selected() {
13-
background-color: white;
19+
background-color: @selected-bg-color;
1420
}
1521

1622
.@{segmented-prefix-cls} {
@@ -22,8 +28,7 @@
2228
position: relative;
2329
display: flex;
2430
align-items: stretch;
25-
justify-items: flex-start;
26-
31+
justify-content: flex-start;
2732
width: 100%;
2833
border-radius: 2px;
2934
}
@@ -32,19 +37,18 @@
3237
position: relative;
3338
min-height: 28px;
3439
padding: 4px 10px;
35-
3640
color: fade(#000, 85%);
3741
text-align: center;
3842
cursor: pointer;
3943

4044
&-selected {
4145
.segmented-item-selected();
42-
color: #262626;
46+
color: @text-color;
4347
}
4448

4549
&:hover,
4650
&:focus {
47-
color: #262626;
51+
color: @text-color;
4852
}
4953

5054
&-disabled {
@@ -60,37 +64,60 @@
6064
position: absolute;
6165
top: 0;
6266
left: 0;
63-
6467
width: 0;
6568
height: 0;
6669
opacity: 0;
6770
pointer-events: none;
6871
}
6972
}
7073

71-
// disabled styles
72-
&-disabled &-item,
73-
&-disabled &-item:hover,
74-
&-disabled &-item:focus {
75-
.segmented-disabled-item();
76-
}
77-
7874
&-thumb {
7975
.segmented-item-selected();
80-
8176
position: absolute;
82-
// top: 0;
83-
// left: 0;
77+
padding: 4px 0;
78+
transition: transform @transition-duration @transition-timing-function,
79+
width @transition-duration @transition-timing-function;
80+
}
81+
82+
&-group {
83+
flex-direction: row;
84+
}
85+
86+
&-thumb {
8487
width: 0;
8588
height: 100%;
86-
padding: 4px 0;
8789
}
8890

89-
// transition effect when `enter-active`
91+
&-vertical {
92+
&-group {
93+
flex-direction: column;
94+
}
95+
96+
&-item {
97+
width: 100%;
98+
text-align: left;
99+
}
100+
101+
&-thumb {
102+
width: 100%;
103+
height: 0;
104+
padding: 0 4px;
105+
transition: transform @transition-duration @transition-timing-function,
106+
height @transition-duration @transition-timing-function;
107+
}
108+
}
109+
110+
// disabled styles
111+
&-disabled &-item,
112+
&-disabled &-item:hover,
113+
&-disabled &-item:focus {
114+
.segmented-disabled-item();
115+
}
116+
90117
&-thumb-motion-appear-active,
91118
&-thumb-motion-enter-active {
92-
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
93-
width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
119+
transition: transform @transition-duration @transition-timing-function,
120+
width @transition-duration @transition-timing-function;
94121
will-change: transform, width;
95122
}
96123

docs/demo/basic.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import '../../assets/style.less';
2-
import React from 'react';
31
import Segmented from 'rc-segmented';
2+
import React from 'react';
3+
import '../../assets/style.less';
44

55
export default function App() {
66
return (
@@ -11,6 +11,13 @@ export default function App() {
1111
onChange={(value) => console.log(value, typeof value)}
1212
/>
1313
</div>
14+
<div className="wrapper">
15+
<Segmented
16+
position="vertical"
17+
options={['iOS', 'Android', 'Web']}
18+
onChange={(value) => console.log(value, typeof value)}
19+
/>
20+
</div>
1421
<div className="wrapper">
1522
<Segmented
1623
options={[13333333333, 157110000, 12110086]}

src/MotionThumb.tsx

Lines changed: 83 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ type ThumbReact = {
99
left: number;
1010
right: number;
1111
width: number;
12+
top: number;
13+
bottom: number;
14+
height: number;
1215
} | null;
1316

1417
export interface MotionThumbInterface {
@@ -20,23 +23,52 @@ export interface MotionThumbInterface {
2023
onMotionStart: VoidFunction;
2124
onMotionEnd: VoidFunction;
2225
direction?: 'ltr' | 'rtl';
26+
position?: 'horizontal' | 'vertical';
2327
}
2428

2529
const calcThumbStyle = (
2630
targetElement: HTMLElement | null | undefined,
27-
): ThumbReact =>
28-
targetElement
29-
? {
30-
left: targetElement.offsetLeft,
31-
right:
32-
(targetElement.parentElement!.clientWidth as number) -
33-
targetElement.clientWidth -
34-
targetElement.offsetLeft,
35-
width: targetElement.clientWidth,
36-
}
37-
: null;
31+
position: 'horizontal' | 'vertical',
32+
): ThumbReact => {
33+
if (!targetElement) return null;
34+
35+
const style: ThumbReact = {
36+
left: targetElement.offsetLeft,
37+
right:
38+
(targetElement.parentElement!.clientWidth as number) -
39+
targetElement.clientWidth -
40+
targetElement.offsetLeft,
41+
width: targetElement.clientWidth,
42+
top: targetElement.offsetTop,
43+
bottom:
44+
(targetElement.parentElement!.clientHeight as number) -
45+
targetElement.clientHeight -
46+
targetElement.offsetTop,
47+
height: targetElement.clientHeight,
48+
};
3849

39-
const toPX = (value: number) =>
50+
if (position === 'vertical') {
51+
return {
52+
left: 0,
53+
right: 0,
54+
width: 0,
55+
top: style.top,
56+
bottom: style.bottom,
57+
height: style.height,
58+
};
59+
}
60+
61+
return {
62+
left: style.left,
63+
right: style.right,
64+
width: style.width,
65+
top: 0,
66+
bottom: 0,
67+
height: 0,
68+
};
69+
};
70+
71+
const toPX = (value: number | undefined): string | undefined =>
4072
value !== undefined ? `${value}px` : undefined;
4173

4274
export default function MotionThumb(props: MotionThumbInterface) {
@@ -49,19 +81,17 @@ export default function MotionThumb(props: MotionThumbInterface) {
4981
onMotionStart,
5082
onMotionEnd,
5183
direction,
84+
position = 'horizontal',
5285
} = props;
5386

5487
const thumbRef = React.useRef<HTMLDivElement>(null);
5588
const [prevValue, setPrevValue] = React.useState(value);
5689

57-
// =========================== Effect ===========================
5890
const findValueElement = (val: SegmentedValue) => {
5991
const index = getValueIndex(val);
60-
6192
const ele = containerRef.current?.querySelectorAll<HTMLDivElement>(
6293
`.${prefixCls}-item`,
6394
)[index];
64-
6595
return ele?.offsetParent && ele;
6696
};
6797

@@ -73,8 +103,8 @@ export default function MotionThumb(props: MotionThumbInterface) {
73103
const prev = findValueElement(prevValue);
74104
const next = findValueElement(value);
75105

76-
const calcPrevStyle = calcThumbStyle(prev);
77-
const calcNextStyle = calcThumbStyle(next);
106+
const calcPrevStyle = calcThumbStyle(prev, position);
107+
const calcNextStyle = calcThumbStyle(next, position);
78108

79109
setPrevValue(value);
80110
setPrevStyle(calcPrevStyle);
@@ -90,40 +120,44 @@ export default function MotionThumb(props: MotionThumbInterface) {
90120

91121
const thumbStart = React.useMemo(
92122
() =>
93-
direction === 'rtl'
94-
? toPX(-(prevStyle?.right as number))
95-
: toPX(prevStyle?.left as number),
96-
[direction, prevStyle],
123+
position === 'vertical'
124+
? toPX(prevStyle?.top ?? 0)
125+
: toPX(prevStyle?.left ?? 0),
126+
[position, prevStyle],
97127
);
128+
98129
const thumbActive = React.useMemo(
99130
() =>
100-
direction === 'rtl'
101-
? toPX(-(nextStyle?.right as number))
102-
: toPX(nextStyle?.left as number),
103-
[direction, nextStyle],
131+
position === 'vertical'
132+
? toPX(nextStyle?.top ?? 0)
133+
: toPX(nextStyle?.left ?? 0),
134+
[position, nextStyle],
104135
);
105136

106-
// =========================== Motion ===========================
107-
const onAppearStart = () => {
108-
return {
109-
transform: `translateX(var(--thumb-start-left))`,
110-
width: `var(--thumb-start-width)`,
111-
};
112-
};
113-
const onAppearActive = () => {
114-
return {
115-
transform: `translateX(var(--thumb-active-left))`,
116-
width: `var(--thumb-active-width)`,
117-
};
118-
};
137+
const onAppearStart = () => ({
138+
transform: `translate${
139+
position === 'vertical' ? 'Y' : 'X'
140+
}(var(--thumb-start-${position === 'vertical' ? 'top' : 'left'}))`,
141+
[position === 'vertical' ? 'height' : 'width']: `var(--thumb-start-${
142+
position === 'vertical' ? 'height' : 'width'
143+
})`,
144+
});
145+
146+
const onAppearActive = () => ({
147+
transform: `translate${
148+
position === 'vertical' ? 'Y' : 'X'
149+
}(var(--thumb-active-${position === 'vertical' ? 'top' : 'left'}))`,
150+
[position === 'vertical' ? 'height' : 'width']: `var(--thumb-active-${
151+
position === 'vertical' ? 'height' : 'width'
152+
})`,
153+
});
154+
119155
const onVisibleChanged = () => {
120156
setPrevStyle(null);
121157
setNextStyle(null);
122158
onMotionEnd();
123159
};
124160

125-
// =========================== Render ===========================
126-
// No need motion when nothing exist in queue
127161
if (!prevStyle || !nextStyle) {
128162
return null;
129163
}
@@ -144,13 +178,20 @@ export default function MotionThumb(props: MotionThumbInterface) {
144178
'--thumb-start-width': toPX(prevStyle?.width),
145179
'--thumb-active-left': thumbActive,
146180
'--thumb-active-width': toPX(nextStyle?.width),
181+
'--thumb-start-top': thumbStart,
182+
'--thumb-start-height': toPX(prevStyle?.height),
183+
'--thumb-active-top': thumbActive,
184+
'--thumb-active-height': toPX(nextStyle?.height),
147185
} as React.CSSProperties;
148186

149-
// It's little ugly which should be refactor when @umi/test update to latest jsdom
150187
const motionProps = {
151188
ref: composeRef(thumbRef, ref),
152189
style: mergedStyle,
153-
className: classNames(`${prefixCls}-thumb`, motionClassName),
190+
className: classNames(
191+
`${prefixCls}-thumb`,
192+
`${prefixCls}-${position}-thumb`,
193+
motionClassName,
194+
),
154195
};
155196

156197
if (process.env.NODE_ENV === 'test') {

0 commit comments

Comments
 (0)