refactor: segmented #6286
parent
62e7f94aba
commit
9df8317ece
@ -1,35 +0,0 @@
|
|||||||
<docs>
|
|
||||||
---
|
|
||||||
order: 4
|
|
||||||
title:
|
|
||||||
zh-CN: 受控模式
|
|
||||||
en-US: Controlled mode
|
|
||||||
---
|
|
||||||
|
|
||||||
## zh-CN
|
|
||||||
|
|
||||||
受控的 Segmented
|
|
||||||
|
|
||||||
## en-US
|
|
||||||
Controlled Segmented.
|
|
||||||
</docs>
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<a-segmented :options="data" default-value="1" @change="handle" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, reactive } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
setup() {
|
|
||||||
const data = reactive(['Map', 'Transit', 'Satellite']);
|
|
||||||
const handle = v => console.log(v);
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
handle,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -1,49 +0,0 @@
|
|||||||
<docs>
|
|
||||||
---
|
|
||||||
order: 7
|
|
||||||
title:
|
|
||||||
zh-CN: 设置图标
|
|
||||||
en-US: With Icon
|
|
||||||
---
|
|
||||||
|
|
||||||
## zh-CN
|
|
||||||
|
|
||||||
给 Segmented Item 设置 Icon。
|
|
||||||
|
|
||||||
## en-US
|
|
||||||
Set `icon` for Segmented Item.
|
|
||||||
</docs>
|
|
||||||
<template>
|
|
||||||
<a-segmented :options="data">
|
|
||||||
<template #icon="index">
|
|
||||||
<template v-if="index == 0">
|
|
||||||
<unordered-list-outlined />
|
|
||||||
</template>
|
|
||||||
<template v-if="index == 1">
|
|
||||||
<appstore-outlined />
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</a-segmented>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, reactive } from 'vue';
|
|
||||||
import { UnorderedListOutlined, AppstoreOutlined } from '@ant-design/icons-vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { UnorderedListOutlined, AppstoreOutlined },
|
|
||||||
setup() {
|
|
||||||
const data = reactive([
|
|
||||||
{
|
|
||||||
value: 'List',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'Kanban',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -1,10 +1,6 @@
|
|||||||
import type { App } from 'vue';
|
|
||||||
import Segmented from './src';
|
import Segmented from './src';
|
||||||
import type { SegmentedProps } from './src';
|
import type { SegmentedProps } from './src';
|
||||||
|
import { withInstall } from '../_util/type';
|
||||||
|
|
||||||
Segmented.install = function (app: App) {
|
export default withInstall(Segmented);
|
||||||
app.component(Segmented.name, Segmented);
|
|
||||||
return app;
|
|
||||||
};
|
|
||||||
export default Segmented;
|
|
||||||
export type { SegmentedProps };
|
export type { SegmentedProps };
|
||||||
|
@ -0,0 +1,161 @@
|
|||||||
|
import { addClass, removeClass } from 'ant-design-vue/es/vc-util/Dom/class';
|
||||||
|
import type { CSSProperties, Ref, TransitionProps } from 'vue';
|
||||||
|
import { onBeforeUnmount, nextTick, Transition, watch, defineComponent, computed, ref } from 'vue';
|
||||||
|
import { anyType } from '../../_util/type';
|
||||||
|
import type { SegmentedValue } from './segmented';
|
||||||
|
|
||||||
|
type ThumbReact = {
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
width: number;
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
export interface MotionThumbInterface {
|
||||||
|
value: SegmentedValue;
|
||||||
|
getValueIndex: (value: SegmentedValue) => number;
|
||||||
|
prefixCls: string;
|
||||||
|
motionName: string;
|
||||||
|
onMotionStart: VoidFunction;
|
||||||
|
onMotionEnd: VoidFunction;
|
||||||
|
direction?: 'ltr' | 'rtl';
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const toPX = (value?: number) => (value !== undefined ? `${value}px` : undefined);
|
||||||
|
|
||||||
|
const MotionThumb = defineComponent({
|
||||||
|
props: {
|
||||||
|
value: anyType<SegmentedValue>(),
|
||||||
|
getValueIndex: anyType<(value: SegmentedValue) => number>(),
|
||||||
|
prefixCls: anyType<string>(),
|
||||||
|
motionName: anyType<string>(),
|
||||||
|
onMotionStart: anyType<VoidFunction>(),
|
||||||
|
onMotionEnd: anyType<VoidFunction>(),
|
||||||
|
direction: anyType<'ltr' | 'rtl'>(),
|
||||||
|
containerRef: anyType<Ref<HTMLDivElement>>(),
|
||||||
|
},
|
||||||
|
emits: ['motionStart', 'motionEnd'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const thumbRef = ref<HTMLDivElement>();
|
||||||
|
// =========================== Effect ===========================
|
||||||
|
const findValueElement = (val: SegmentedValue) => {
|
||||||
|
const index = props.getValueIndex(val);
|
||||||
|
|
||||||
|
const ele = props.containerRef.value?.querySelectorAll<HTMLDivElement>(
|
||||||
|
`.${props.prefixCls}-item`,
|
||||||
|
)[index];
|
||||||
|
return ele?.offsetParent && ele;
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevStyle = ref<ThumbReact>(null);
|
||||||
|
const nextStyle = ref<ThumbReact>(null);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(value, prevValue) => {
|
||||||
|
const prev = findValueElement(prevValue);
|
||||||
|
const next = findValueElement(value);
|
||||||
|
|
||||||
|
const calcPrevStyle = calcThumbStyle(prev);
|
||||||
|
const calcNextStyle = calcThumbStyle(next);
|
||||||
|
|
||||||
|
prevStyle.value = calcPrevStyle;
|
||||||
|
nextStyle.value = calcNextStyle;
|
||||||
|
|
||||||
|
if (prev && next) {
|
||||||
|
emit('motionStart');
|
||||||
|
} else {
|
||||||
|
emit('motionEnd');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'post' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const thumbStart = computed(() =>
|
||||||
|
props.direction === 'rtl'
|
||||||
|
? toPX(-(prevStyle.value?.right as number))
|
||||||
|
: toPX(prevStyle.value?.left as number),
|
||||||
|
);
|
||||||
|
const thumbActive = computed(() =>
|
||||||
|
props.direction === 'rtl'
|
||||||
|
? toPX(-(nextStyle.value?.right as number))
|
||||||
|
: toPX(nextStyle.value?.left as number),
|
||||||
|
);
|
||||||
|
|
||||||
|
// =========================== Motion ===========================
|
||||||
|
let timeid: any;
|
||||||
|
const onAppearStart: TransitionProps['onBeforeEnter'] = (el: HTMLDivElement) => {
|
||||||
|
clearTimeout(timeid);
|
||||||
|
nextTick(() => {
|
||||||
|
if (el) {
|
||||||
|
el.style.transform = `translateX(var(--thumb-start-left))`;
|
||||||
|
el.style.width = `var(--thumb-start-width)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAppearActive: TransitionProps['onEnter'] = (el: HTMLDivElement) => {
|
||||||
|
timeid = setTimeout(() => {
|
||||||
|
if (el) {
|
||||||
|
addClass(el, `${props.motionName}-appear-active`);
|
||||||
|
el.style.transform = `translateX(var(--thumb-active-left))`;
|
||||||
|
el.style.width = `var(--thumb-active-width)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onAppearEnd: TransitionProps['onAfterEnter'] = (el: HTMLDivElement) => {
|
||||||
|
prevStyle.value = null;
|
||||||
|
nextStyle.value = null;
|
||||||
|
if (el) {
|
||||||
|
el.style.transform = null;
|
||||||
|
el.style.width = null;
|
||||||
|
removeClass(el, `${props.motionName}-appear-active`);
|
||||||
|
}
|
||||||
|
emit('motionEnd');
|
||||||
|
};
|
||||||
|
const mergedStyle = computed<CSSProperties>(() => ({
|
||||||
|
'--thumb-start-left': thumbStart.value,
|
||||||
|
'--thumb-start-width': toPX(prevStyle.value?.width),
|
||||||
|
'--thumb-active-left': thumbActive.value,
|
||||||
|
'--thumb-active-width': toPX(nextStyle.value?.width),
|
||||||
|
}));
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearTimeout(timeid);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
// It's little ugly which should be refactor when @umi/test update to latest jsdom
|
||||||
|
const motionProps = {
|
||||||
|
ref: thumbRef,
|
||||||
|
style: mergedStyle.value,
|
||||||
|
class: [`${props.prefixCls}-thumb`],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
(motionProps as any)['data-test-style'] = JSON.stringify(mergedStyle.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transition
|
||||||
|
appear
|
||||||
|
onBeforeEnter={onAppearStart}
|
||||||
|
onEnter={onAppearActive}
|
||||||
|
onAfterEnter={onAppearEnd}
|
||||||
|
>
|
||||||
|
{!prevStyle.value || !nextStyle.value ? null : <div {...motionProps}></div>}
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export default MotionThumb;
|
Loading…
Reference in new issue