Skip to content

Commit

Permalink
feat: add vertical props (#260)
Browse files Browse the repository at this point in the history
Co-authored-by: 梁朝飞 <liangchaofei@guokechuzhi.com>
  • Loading branch information
liangchaofei and 梁朝飞 authored Sep 5, 2024
1 parent 658632e commit 39d2267
Show file tree
Hide file tree
Showing 6 changed files with 367 additions and 62 deletions.
61 changes: 40 additions & 21 deletions assets/index.less
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
@segmented-prefix-cls: rc-segmented;

@disabled-color: fade(#000, 25%);
@selected-bg-color: white;
@text-color: #262626;
@transition-duration: 0.3s;
@transition-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1);

.segmented-disabled-item() {
&,
&:hover,
&:focus {
color: fade(#000, 25%);
color: @disabled-color;
cursor: not-allowed;
}
}

.segmented-item-selected() {
background-color: white;
background-color: @selected-bg-color;
}

.@{segmented-prefix-cls} {
Expand All @@ -21,9 +27,9 @@
&-group {
position: relative;
display: flex;
flex-direction: row;
align-items: stretch;
justify-items: flex-start;

justify-content: flex-start;
width: 100%;
border-radius: 2px;
}
Expand All @@ -32,19 +38,18 @@
position: relative;
min-height: 28px;
padding: 4px 10px;

color: fade(#000, 85%);
text-align: center;
cursor: pointer;

&-selected {
.segmented-item-selected();
color: #262626;
color: @text-color;
}

&:hover,
&:focus {
color: #262626;
color: @text-color;
}

&-disabled {
Expand All @@ -60,37 +65,51 @@
position: absolute;
top: 0;
left: 0;

width: 0;
height: 0;
opacity: 0;
pointer-events: none;
}
}

// disabled styles
&-disabled &-item,
&-disabled &-item:hover,
&-disabled &-item:focus {
.segmented-disabled-item();
}

&-thumb {
.segmented-item-selected();

position: absolute;
// top: 0;
// left: 0;
width: 0;
height: 100%;
padding: 4px 0;
transition: transform @transition-duration @transition-timing-function,
width @transition-duration @transition-timing-function;
}

&-vertical &-group {
flex-direction: column;
}

&-vertical &-item {
width: 100%;
text-align: left;
}

&-vertical &-thumb {
width: 100%;
height: 0;
padding: 0 4px;
transition: transform @transition-duration @transition-timing-function,
height @transition-duration @transition-timing-function;
}

// disabled styles
&-disabled &-item,
&-disabled &-item:hover,
&-disabled &-item:focus {
.segmented-disabled-item();
}

// transition effect when `enter-active`
&-thumb-motion-appear-active,
&-thumb-motion-enter-active {
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: transform @transition-duration @transition-timing-function,
width @transition-duration @transition-timing-function;
will-change: transform, width;
}

Expand Down
11 changes: 9 additions & 2 deletions docs/demo/basic.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '../../assets/style.less';
import React from 'react';
import Segmented from 'rc-segmented';
import React from 'react';
import '../../assets/style.less';

export default function App() {
return (
Expand All @@ -11,6 +11,13 @@ export default function App() {
onChange={(value) => console.log(value, typeof value)}
/>
</div>
<div className="wrapper">
<Segmented
vertical
options={['iOS', 'Android', 'Web']}
onChange={(value) => console.log(value, typeof value)}
/>
</div>
<div className="wrapper">
<Segmented
options={[13333333333, 157110000, 12110086]}
Expand Down
129 changes: 95 additions & 34 deletions src/MotionThumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ type ThumbReact = {
left: number;
right: number;
width: number;
top: number;
bottom: number;
height: number;
} | null;

export interface MotionThumbInterface {
Expand All @@ -20,23 +23,53 @@ export interface MotionThumbInterface {
onMotionStart: VoidFunction;
onMotionEnd: VoidFunction;
direction?: 'ltr' | 'rtl';
vertical?: boolean;
}

const calcThumbStyle = (
targetElement: HTMLElement | null | undefined,
): ThumbReact =>
targetElement
? {
left: targetElement.offsetLeft,
right:
(targetElement.parentElement!.clientWidth as number) -
targetElement.clientWidth -
targetElement.offsetLeft,
width: targetElement.clientWidth,
}
: null;
vertical?: boolean,
): ThumbReact => {
if (!targetElement) return null;

const style: ThumbReact = {
left: targetElement.offsetLeft,
right:
(targetElement.parentElement!.clientWidth as number) -
targetElement.clientWidth -
targetElement.offsetLeft,
width: targetElement.clientWidth,
top: targetElement.offsetTop,
bottom:
(targetElement.parentElement!.clientHeight as number) -
targetElement.clientHeight -
targetElement.offsetTop,
height: targetElement.clientHeight,
};

if (vertical) {
// Adjusts positioning and size for vertical layout by setting horizontal properties to 0 and using vertical properties from the style object.
return {
left: 0,
right: 0,
width: 0,
top: style.top,
bottom: style.bottom,
height: style.height,
};
}

return {
left: style.left,
right: style.right,
width: style.width,
top: 0,
bottom: 0,
height: 0,
};
};

const toPX = (value: number) =>
const toPX = (value: number | undefined): string | undefined =>
value !== undefined ? `${value}px` : undefined;

export default function MotionThumb(props: MotionThumbInterface) {
Expand All @@ -49,6 +82,7 @@ export default function MotionThumb(props: MotionThumbInterface) {
onMotionStart,
onMotionEnd,
direction,
vertical = false,
} = props;

const thumbRef = React.useRef<HTMLDivElement>(null);
Expand All @@ -57,11 +91,9 @@ export default function MotionThumb(props: MotionThumbInterface) {
// =========================== Effect ===========================
const findValueElement = (val: SegmentedValue) => {
const index = getValueIndex(val);

const ele = containerRef.current?.querySelectorAll<HTMLDivElement>(
`.${prefixCls}-item`,
)[index];

return ele?.offsetParent && ele;
};

Expand All @@ -73,8 +105,8 @@ export default function MotionThumb(props: MotionThumbInterface) {
const prev = findValueElement(prevValue);
const next = findValueElement(value);

const calcPrevStyle = calcThumbStyle(prev);
const calcNextStyle = calcThumbStyle(next);
const calcPrevStyle = calcThumbStyle(prev, vertical);
const calcNextStyle = calcThumbStyle(next, vertical);

setPrevValue(value);
setPrevStyle(calcPrevStyle);
Expand All @@ -88,34 +120,59 @@ export default function MotionThumb(props: MotionThumbInterface) {
}
}, [value]);

const thumbStart = React.useMemo(
() =>
direction === 'rtl'
? toPX(-(prevStyle?.right as number))
: toPX(prevStyle?.left as number),
[direction, prevStyle],
);
const thumbActive = React.useMemo(
() =>
direction === 'rtl'
? toPX(-(nextStyle?.right as number))
: toPX(nextStyle?.left as number),
[direction, nextStyle],
);
const thumbStart = React.useMemo(() => {
if (vertical) {
return toPX(prevStyle?.top ?? 0);
}

if (direction === 'rtl') {
return toPX(-(prevStyle?.right as number));
}

return toPX(prevStyle?.left as number);
}, [vertical, direction, prevStyle]);

const thumbActive = React.useMemo(() => {
if (vertical) {
return toPX(nextStyle?.top ?? 0);
}

if (direction === 'rtl') {
return toPX(-(nextStyle?.right as number));
}

return toPX(nextStyle?.left as number);
}, [vertical, direction, nextStyle]);

// =========================== Motion ===========================
const onAppearStart = () => {
if (vertical) {
return {
transform: 'translateY(var(--thumb-start-top))',
height: 'var(--thumb-start-height)',
};
}

return {
transform: `translateX(var(--thumb-start-left))`,
width: `var(--thumb-start-width)`,
transform: 'translateX(var(--thumb-start-left))',
width: 'var(--thumb-start-width)',
};
};

const onAppearActive = () => {
if (vertical) {
return {
transform: 'translateY(var(--thumb-active-top))',
height: 'var(--thumb-active-height)',
};
}

return {
transform: `translateX(var(--thumb-active-left))`,
width: `var(--thumb-active-width)`,
transform: 'translateX(var(--thumb-active-left))',
width: 'var(--thumb-active-width)',
};
};

const onVisibleChanged = () => {
setPrevStyle(null);
setNextStyle(null);
Expand Down Expand Up @@ -144,6 +201,10 @@ export default function MotionThumb(props: MotionThumbInterface) {
'--thumb-start-width': toPX(prevStyle?.width),
'--thumb-active-left': thumbActive,
'--thumb-active-width': toPX(nextStyle?.width),
'--thumb-start-top': thumbStart,
'--thumb-start-height': toPX(prevStyle?.height),
'--thumb-active-top': thumbActive,
'--thumb-active-height': toPX(nextStyle?.height),
} as React.CSSProperties;

// It's little ugly which should be refactor when @umi/test update to latest jsdom
Expand Down
Loading

0 comments on commit 39d2267

Please sign in to comment.