refactor: collapse
parent
af182533c1
commit
0ade597b55
|
@ -5,3 +5,5 @@ tree、tree-slelct: 新增虚拟滚动、title 逻辑变动
|
|||
date 相关组件: dayjs, UI 变动
|
||||
|
||||
steps: add responsive、percent
|
||||
|
||||
collapse: add ghost、collapsible
|
||||
|
|
|
@ -1,87 +1,179 @@
|
|||
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import animation from '../_util/openAnimation';
|
||||
import { getOptionProps, getComponent, isValidElement, getSlot } from '../_util/props-util';
|
||||
import {
|
||||
isEmptyElement,
|
||||
initDefaultProps,
|
||||
flattenChildren,
|
||||
isValidElement,
|
||||
} from '../_util/props-util';
|
||||
import { cloneElement } from '../_util/vnode';
|
||||
import VcCollapse from '../vc-collapse';
|
||||
import type { CollapsibleType } from './commonProps';
|
||||
import { collapseProps } from './commonProps';
|
||||
import { getDataAndAriaProps } from '../_util/util';
|
||||
import type { ExtractPropTypes } from 'vue';
|
||||
import { computed, defineComponent, ref, watch } from 'vue';
|
||||
import RightOutlined from '@ant-design/icons-vue/RightOutlined';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import type { VueNode } from '../_util/type';
|
||||
import { tuple } from '../_util/type';
|
||||
import firstNotUndefined from '../_util/firstNotUndefined';
|
||||
import classNames from '../_util/classNames';
|
||||
import animation from '../_util/openAnimation';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import type { CollapsePanelProps } from './CollapsePanel';
|
||||
|
||||
export interface PanelProps {
|
||||
isActive?: boolean;
|
||||
header?: VueNode;
|
||||
className?: string;
|
||||
class?: string;
|
||||
style?: CSSProperties;
|
||||
showArrow?: boolean;
|
||||
forceRender?: boolean;
|
||||
disabled?: boolean;
|
||||
extra?: VueNode;
|
||||
type Key = number | string;
|
||||
|
||||
function getActiveKeysArray(activeKey: Key | Key[]) {
|
||||
let currentActiveKey = activeKey;
|
||||
if (!Array.isArray(currentActiveKey)) {
|
||||
const activeKeyType = typeof currentActiveKey;
|
||||
currentActiveKey =
|
||||
activeKeyType === 'number' || activeKeyType === 'string' ? [currentActiveKey] : [];
|
||||
}
|
||||
return currentActiveKey.map(key => String(key));
|
||||
}
|
||||
type ActiveKeyType = Array<string | number> | string | number;
|
||||
|
||||
const collapseProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
activeKey: { type: [Array, Number, String] as PropType<ActiveKeyType> },
|
||||
defaultActiveKey: { type: [Array, Number, String] as PropType<ActiveKeyType> },
|
||||
accordion: PropTypes.looseBool,
|
||||
destroyInactivePanel: PropTypes.looseBool,
|
||||
bordered: PropTypes.looseBool.def(true),
|
||||
expandIcon: PropTypes.func,
|
||||
openAnimation: PropTypes.object.def(animation),
|
||||
expandIconPosition: PropTypes.oneOf(tuple('left', 'right')).def('left'),
|
||||
'onUpdate:activeKey': PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export type CollapseProps = Partial<ExtractPropTypes<typeof collapseProps>>;
|
||||
|
||||
export { collapseProps };
|
||||
export type CollapseProps = Partial<ExtractPropTypes<ReturnType<typeof collapseProps>>>;
|
||||
export default defineComponent({
|
||||
name: 'ACollapse',
|
||||
inheritAttrs: false,
|
||||
props: collapseProps,
|
||||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
renderExpandIcon(panelProps: PanelProps = {}, prefixCls: string) {
|
||||
const expandIcon = getComponent(this, 'expandIcon', panelProps);
|
||||
const icon = expandIcon || <RightOutlined rotate={panelProps.isActive ? 90 : undefined} />;
|
||||
props: initDefaultProps(collapseProps(), {
|
||||
accordion: false,
|
||||
destroyInactivePanel: false,
|
||||
bordered: true,
|
||||
openAnimation: animation,
|
||||
expandIconPosition: 'left',
|
||||
}),
|
||||
slots: ['expandIcon'],
|
||||
emits: ['change', 'update:activeKey'],
|
||||
setup(props, { attrs, slots, emit }) {
|
||||
const stateActiveKey = ref<Key[]>(
|
||||
getActiveKeysArray(firstNotUndefined([props.activeKey, props.defaultActiveKey])),
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.activeKey,
|
||||
() => {
|
||||
stateActiveKey.value = getActiveKeysArray(props.activeKey);
|
||||
},
|
||||
);
|
||||
const { prefixCls, direction } = useConfigInject('collapse', props);
|
||||
const iconPosition = computed(() => {
|
||||
const { expandIconPosition } = props;
|
||||
if (expandIconPosition !== undefined) {
|
||||
return expandIconPosition;
|
||||
}
|
||||
return direction.value === 'rtl' ? 'right' : 'left';
|
||||
});
|
||||
|
||||
const renderExpandIcon = (panelProps: CollapsePanelProps) => {
|
||||
const { expandIcon = slots.expandIcon } = props;
|
||||
const icon = expandIcon ? (
|
||||
expandIcon(panelProps)
|
||||
) : (
|
||||
<RightOutlined rotate={panelProps.isActive ? 90 : undefined} />
|
||||
);
|
||||
|
||||
return isValidElement(Array.isArray(expandIcon) ? icon[0] : icon)
|
||||
? cloneElement(icon, {
|
||||
class: `${prefixCls}-arrow`,
|
||||
})
|
||||
? cloneElement(
|
||||
icon,
|
||||
{
|
||||
class: `${prefixCls.value}-arrow`,
|
||||
},
|
||||
false,
|
||||
)
|
||||
: icon;
|
||||
},
|
||||
handleChange(activeKey: ActiveKeyType) {
|
||||
this.$emit('update:activeKey', activeKey);
|
||||
this.$emit('change', activeKey);
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const { prefixCls: customizePrefixCls, bordered, expandIconPosition } = this;
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('collapse', customizePrefixCls);
|
||||
const { class: className, ...restAttrs } = this.$attrs;
|
||||
const collapseClassName = {
|
||||
[className as string]: className,
|
||||
[`${prefixCls}-borderless`]: !bordered,
|
||||
[`${prefixCls}-icon-position-${expandIconPosition}`]: true,
|
||||
};
|
||||
const rcCollapeProps = {
|
||||
...getOptionProps(this),
|
||||
prefixCls,
|
||||
expandIcon: (panelProps: PanelProps) => this.renderExpandIcon(panelProps, prefixCls),
|
||||
class: collapseClassName,
|
||||
...restAttrs,
|
||||
onChange: this.handleChange,
|
||||
const setActiveKey = (activeKey: Key[]) => {
|
||||
if (props.activeKey === undefined) {
|
||||
stateActiveKey.value = activeKey;
|
||||
}
|
||||
const newKey = props.accordion ? activeKey[0] : activeKey;
|
||||
emit('update:activeKey', newKey);
|
||||
emit('change', newKey);
|
||||
};
|
||||
const onClickItem = (key: Key) => {
|
||||
let activeKey = stateActiveKey.value;
|
||||
if (props.accordion) {
|
||||
activeKey = activeKey[0] === key ? [] : [key];
|
||||
} else {
|
||||
activeKey = [...activeKey];
|
||||
const index = activeKey.indexOf(key);
|
||||
const isActive = index > -1;
|
||||
if (isActive) {
|
||||
// remove active state
|
||||
activeKey.splice(index, 1);
|
||||
} else {
|
||||
activeKey.push(key);
|
||||
}
|
||||
}
|
||||
setActiveKey(activeKey);
|
||||
};
|
||||
|
||||
return <VcCollapse {...rcCollapeProps}>{getSlot(this)}</VcCollapse>;
|
||||
const getNewChild = (child, index) => {
|
||||
if (isEmptyElement(child)) return;
|
||||
const activeKey = stateActiveKey.value;
|
||||
const { accordion, destroyInactivePanel, collapsible, openAnimation } = props;
|
||||
|
||||
// If there is no key provide, use the panel order as default key
|
||||
const key = String(child.key ?? index);
|
||||
const {
|
||||
header = child.children?.header?.(),
|
||||
headerClass,
|
||||
collapsible: childCollapsible,
|
||||
disabled,
|
||||
} = child.props || {};
|
||||
let isActive = false;
|
||||
|
||||
if (accordion) {
|
||||
isActive = activeKey[0] === key;
|
||||
} else {
|
||||
isActive = activeKey.indexOf(key) > -1;
|
||||
}
|
||||
|
||||
let mergeCollapsible: CollapsibleType = childCollapsible ?? collapsible;
|
||||
// legacy 2.x
|
||||
if (disabled || disabled === '') {
|
||||
mergeCollapsible = 'disabled';
|
||||
}
|
||||
const newProps = {
|
||||
key,
|
||||
panelKey: key,
|
||||
header,
|
||||
headerClass,
|
||||
isActive,
|
||||
prefixCls: prefixCls.value,
|
||||
destroyInactivePanel,
|
||||
openAnimation,
|
||||
accordion,
|
||||
onItemClick: mergeCollapsible === 'disabled' ? null : onClickItem,
|
||||
expandIcon: renderExpandIcon,
|
||||
collapsible: mergeCollapsible,
|
||||
};
|
||||
|
||||
return cloneElement(child, newProps);
|
||||
};
|
||||
|
||||
const getItems = () => {
|
||||
return flattenChildren(slots.default?.()).map(getNewChild);
|
||||
};
|
||||
|
||||
return () => {
|
||||
const { accordion, bordered, ghost } = props;
|
||||
const collapseClassName = classNames({
|
||||
[prefixCls.value]: true,
|
||||
[`${prefixCls.value}-borderless`]: !bordered,
|
||||
[`${prefixCls.value}-icon-position-${iconPosition.value}`]: true,
|
||||
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
|
||||
[`${prefixCls.value}-ghost`]: !!ghost,
|
||||
[attrs.class as string]: !!attrs.class,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
class={collapseClassName}
|
||||
{...getDataAndAriaProps(attrs)}
|
||||
style={attrs.style}
|
||||
role={accordion ? 'tablist' : null}
|
||||
>
|
||||
{getItems()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,54 +1,114 @@
|
|||
import PanelContent from './PanelContent';
|
||||
import { initDefaultProps } from '../_util/props-util';
|
||||
import { collapsePanelProps } from './commonProps';
|
||||
import type { ExtractPropTypes } from 'vue';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
|
||||
import VcCollapse from '../vc-collapse';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { defineComponent } from 'vue';
|
||||
import Transition from '../_util/transition';
|
||||
import classNames from '../_util/classNames';
|
||||
import devWarning from '../vc-util/devWarning';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
|
||||
const collapsePanelProps = {
|
||||
openAnimation: PropTypes.object,
|
||||
prefixCls: PropTypes.string,
|
||||
header: PropTypes.VNodeChild,
|
||||
headerClass: PropTypes.string,
|
||||
showArrow: PropTypes.looseBool,
|
||||
isActive: PropTypes.looseBool,
|
||||
destroyInactivePanel: PropTypes.looseBool,
|
||||
disabled: PropTypes.looseBool,
|
||||
accordion: PropTypes.looseBool,
|
||||
forceRender: PropTypes.looseBool,
|
||||
expandIcon: PropTypes.func,
|
||||
extra: PropTypes.VNodeChild,
|
||||
panelKey: PropTypes.VNodeChild,
|
||||
};
|
||||
|
||||
export type CollapsePanelProps = Partial<ExtractPropTypes<typeof collapsePanelProps>>;
|
||||
export { collapsePanelProps };
|
||||
export type CollapsePanelProps = Partial<ExtractPropTypes<ReturnType<typeof collapsePanelProps>>>;
|
||||
export default defineComponent({
|
||||
name: 'ACollapsePanel',
|
||||
inheritAttrs: false,
|
||||
props: collapsePanelProps,
|
||||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
props: initDefaultProps(collapsePanelProps(), {
|
||||
showArrow: true,
|
||||
isActive: false,
|
||||
onItemClick() {},
|
||||
headerClass: '',
|
||||
forceRender: false,
|
||||
}),
|
||||
slots: ['expandIcon', 'extra', 'header'],
|
||||
emits: ['itemClick'],
|
||||
setup(props, { slots, emit }) {
|
||||
devWarning(
|
||||
props.disabled === undefined,
|
||||
'Collapse.Panel',
|
||||
'`disabled` is deprecated. Please use `collapsible="disabled"` instead.',
|
||||
);
|
||||
const { prefixCls } = useConfigInject('collapse', props);
|
||||
const handleItemClick = () => {
|
||||
emit('itemClick', props.panelKey);
|
||||
};
|
||||
},
|
||||
render() {
|
||||
const { prefixCls: customizePrefixCls, showArrow = true } = this;
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('collapse', customizePrefixCls);
|
||||
const { class: className, ...restAttrs } = this.$attrs;
|
||||
const collapsePanelClassName = {
|
||||
[className as string]: className,
|
||||
[`${prefixCls}-no-arrow`]: !showArrow,
|
||||
const handleKeyPress = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.keyCode === 13 || e.which === 13) {
|
||||
handleItemClick();
|
||||
}
|
||||
};
|
||||
return () => {
|
||||
const {
|
||||
header = slots.header?.(),
|
||||
headerClass,
|
||||
isActive,
|
||||
showArrow,
|
||||
destroyInactivePanel,
|
||||
accordion,
|
||||
forceRender,
|
||||
openAnimation,
|
||||
expandIcon = slots.expandIcon,
|
||||
extra = slots.extra?.(),
|
||||
collapsible,
|
||||
} = props;
|
||||
const disabled = collapsible === 'disabled';
|
||||
const prefixClsValue = prefixCls.value;
|
||||
const headerCls = classNames(`${prefixClsValue}-header`, {
|
||||
[headerClass]: headerClass,
|
||||
[`${prefixClsValue}-header-collapsible-only`]: collapsible === 'header',
|
||||
});
|
||||
const itemCls = classNames({
|
||||
[`${prefixClsValue}-item`]: true,
|
||||
[`${prefixClsValue}-item-active`]: isActive,
|
||||
[`${prefixClsValue}-item-disabled`]: disabled,
|
||||
[`${prefixClsValue}-no-arrow`]: !showArrow,
|
||||
});
|
||||
|
||||
const rcCollapePanelProps = {
|
||||
...getOptionProps(this),
|
||||
header: getComponent(this, 'header'),
|
||||
prefixCls,
|
||||
extra: getComponent(this, 'extra'),
|
||||
class: collapsePanelClassName,
|
||||
...restAttrs,
|
||||
let icon = <i class="arrow" />;
|
||||
if (showArrow && typeof expandIcon === 'function') {
|
||||
icon = expandIcon(props);
|
||||
}
|
||||
|
||||
const panelContent = (
|
||||
<PanelContent
|
||||
v-show={isActive}
|
||||
prefixCls={prefixClsValue}
|
||||
isActive={isActive}
|
||||
forceRender={forceRender}
|
||||
role={accordion ? 'tabpanel' : null}
|
||||
v-slots={{ default: slots.default }}
|
||||
></PanelContent>
|
||||
);
|
||||
const transitionProps = {
|
||||
appear: true,
|
||||
css: false,
|
||||
...openAnimation,
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={itemCls}>
|
||||
<div
|
||||
class={headerCls}
|
||||
onClick={() => collapsible !== 'header' && handleItemClick()}
|
||||
role={accordion ? 'tab' : 'button'}
|
||||
tabindex={disabled ? -1 : 0}
|
||||
aria-expanded={isActive}
|
||||
onKeypress={handleKeyPress}
|
||||
>
|
||||
{showArrow && icon}
|
||||
{collapsible === 'header' ? (
|
||||
<span onClick={handleItemClick} class={`${prefixClsValue}-header-text`}>
|
||||
{header}
|
||||
</span>
|
||||
) : (
|
||||
header
|
||||
)}
|
||||
{extra && <div class={`${prefixClsValue}-extra`}>{extra}</div>}
|
||||
</div>
|
||||
<Transition {...transitionProps}>
|
||||
{!destroyInactivePanel || isActive ? panelContent : null}
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return <VcCollapse.Panel {...rcCollapePanelProps}>{getSlot(this)}</VcCollapse.Panel>;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
import { panelProps } from './commonProps';
|
||||
import { collapsePanelProps } from './commonProps';
|
||||
import classNames from '../_util/classNames';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PanelContent',
|
||||
props: panelProps(),
|
||||
props: collapsePanelProps(),
|
||||
setup(props, { slots }) {
|
||||
const rendered = ref(false);
|
||||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
exports[`Collapse should support remove expandIcon 1`] = `
|
||||
<div class="ant-collapse ant-collapse-icon-position-left">
|
||||
<div class="ant-collapse-item" role="tablist">
|
||||
<div class="ant-collapse-header" role="button" tabindex="0" aria-expanded="false"><span role="img" aria-label="right" class="anticon anticon-right ant-collapse-arrow"><svg focusable="false" class="" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"></path></svg></span>header
|
||||
<div class="ant-collapse-item">
|
||||
<div class="ant-collapse-header" role="button" tabindex="0" aria-expanded="false">
|
||||
<!---->header
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
|
|
|
@ -1,31 +1,25 @@
|
|||
import type { PropType } from 'vue';
|
||||
import { tuple } from '../_util/type';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
|
||||
export type CollapsibleType = 'header' | 'disabled';
|
||||
|
||||
export type ActiveKeyType = Array<string | number> | string | number;
|
||||
const collapseProps = () => ({
|
||||
prefixCls: PropTypes.string,
|
||||
activeKey: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
||||
]),
|
||||
defaultActiveKey: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
||||
]),
|
||||
activeKey: { type: [Array, Number, String] as PropType<ActiveKeyType> },
|
||||
defaultActiveKey: { type: [Array, Number, String] as PropType<ActiveKeyType> },
|
||||
accordion: PropTypes.looseBool,
|
||||
destroyInactivePanel: PropTypes.looseBool,
|
||||
bordered: PropTypes.looseBool,
|
||||
expandIcon: PropTypes.func,
|
||||
openAnimation: PropTypes.object,
|
||||
expandIconPosition: PropTypes.oneOf(['left', 'right']),
|
||||
onChange: PropTypes.func,
|
||||
expandIconPosition: PropTypes.oneOf(tuple('left', 'right')),
|
||||
collapsible: { type: String as PropType<CollapsibleType> },
|
||||
ghost: PropTypes.looseBool,
|
||||
});
|
||||
|
||||
const panelProps = () => ({
|
||||
const collapsePanelProps = () => ({
|
||||
openAnimation: PropTypes.object,
|
||||
prefixCls: PropTypes.string,
|
||||
header: PropTypes.any,
|
||||
|
@ -33,6 +27,7 @@ const panelProps = () => ({
|
|||
showArrow: PropTypes.looseBool,
|
||||
isActive: PropTypes.looseBool,
|
||||
destroyInactivePanel: PropTypes.looseBool,
|
||||
/** @deprecated Use `collapsible="disabled"` instead */
|
||||
disabled: PropTypes.looseBool,
|
||||
accordion: PropTypes.looseBool,
|
||||
forceRender: PropTypes.looseBool,
|
||||
|
@ -44,4 +39,4 @@ const panelProps = () => ({
|
|||
onItemClick: { type: Function as PropType<(panelKey: string | number) => void> },
|
||||
});
|
||||
|
||||
export { collapseProps, panelProps };
|
||||
export { collapseProps, collapsePanelProps };
|
|
@ -1,6 +1,6 @@
|
|||
import type { App, Plugin } from 'vue';
|
||||
import Collapse from './Collapse';
|
||||
import CollapsePanel from './CollapsePanel';
|
||||
import Collapse, { collapseProps } from './Collapse';
|
||||
import CollapsePanel, { collapsePanelProps } from './CollapsePanel';
|
||||
export type { CollapseProps } from './Collapse';
|
||||
export type { CollapsePanelProps } from './CollapsePanel';
|
||||
|
||||
|
@ -13,7 +13,7 @@ Collapse.install = function (app: App) {
|
|||
return app;
|
||||
};
|
||||
|
||||
export { CollapsePanel };
|
||||
export { CollapsePanel, collapseProps, collapsePanelProps };
|
||||
export default Collapse as typeof Collapse &
|
||||
Plugin & {
|
||||
readonly Panel: typeof CollapsePanel;
|
||||
|
|
|
@ -24,21 +24,17 @@
|
|||
> .@{collapse-prefix-cls}-header {
|
||||
position: relative;
|
||||
padding: @collapse-header-padding;
|
||||
padding-left: @collapse-header-padding-extra;
|
||||
color: @heading-color;
|
||||
line-height: 22px;
|
||||
line-height: @line-height-base;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
transition: all 0.3s, visibility 0s;
|
||||
.clearfix();
|
||||
|
||||
.@{collapse-prefix-cls}-arrow {
|
||||
.iconfont-mixin();
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: @padding-md;
|
||||
display: inline-block;
|
||||
margin-right: 12px;
|
||||
font-size: @font-size-sm;
|
||||
transform: translateY(-50%);
|
||||
vertical-align: -1px;
|
||||
|
||||
& svg {
|
||||
transition: transform 0.24s;
|
||||
|
@ -54,6 +50,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.@{collapse-prefix-cls}-header-collapsible-only {
|
||||
cursor: default;
|
||||
.@{collapse-prefix-cls}-header-text {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&.@{collapse-prefix-cls}-no-arrow {
|
||||
> .@{collapse-prefix-cls}-header {
|
||||
padding-left: 12px;
|
||||
|
@ -69,19 +72,18 @@
|
|||
padding-right: @collapse-header-padding-extra;
|
||||
|
||||
.@{collapse-prefix-cls}-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: @padding-md;
|
||||
left: auto;
|
||||
margin: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-anim-active {
|
||||
transition: height 0.2s @ease-out;
|
||||
}
|
||||
|
||||
&-content {
|
||||
overflow: hidden;
|
||||
color: @text-color;
|
||||
background-color: @collapse-content-bg;
|
||||
border-top: @border-width-base @border-style-base @border-color-base;
|
||||
|
@ -90,7 +92,7 @@
|
|||
padding: @collapse-content-padding;
|
||||
}
|
||||
|
||||
&-inactive {
|
||||
&-hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +126,22 @@
|
|||
padding-top: 4px;
|
||||
}
|
||||
|
||||
&-ghost {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
> .@{collapse-prefix-cls}-item {
|
||||
border-bottom: 0;
|
||||
> .@{collapse-prefix-cls}-content {
|
||||
background-color: transparent;
|
||||
border-top: 0;
|
||||
> .@{collapse-prefix-cls}-content-box {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& &-item-disabled > &-header {
|
||||
&,
|
||||
& > .arrow {
|
||||
|
@ -132,3 +150,5 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import './rtl';
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
@import '../../style/themes/index';
|
||||
@import '../../style/mixins/index';
|
||||
|
||||
@collapse-prefix-cls: ~'@{ant-prefix}-collapse';
|
||||
|
||||
.@{collapse-prefix-cls} {
|
||||
&-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
& > &-item {
|
||||
> .@{collapse-prefix-cls}-header {
|
||||
.@{collapse-prefix-cls}-rtl & {
|
||||
padding: @collapse-header-padding;
|
||||
padding-right: @collapse-header-padding-extra;
|
||||
}
|
||||
|
||||
.@{collapse-prefix-cls}-arrow {
|
||||
& svg {
|
||||
.@{collapse-prefix-cls}-rtl& {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{collapse-prefix-cls}-extra {
|
||||
.@{collapse-prefix-cls}-rtl& {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.@{collapse-prefix-cls}-no-arrow {
|
||||
> .@{collapse-prefix-cls}-header {
|
||||
.@{collapse-prefix-cls}-rtl& {
|
||||
padding-right: 12px;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
import { isEmptyElement, initDefaultProps, flattenChildren } from '../_util/props-util';
|
||||
import { cloneElement } from '../_util/vnode';
|
||||
import openAnimationFactory from './openAnimationFactory';
|
||||
import { collapseProps, CollapsibleType } from './commonProps';
|
||||
import { getDataAndAriaProps } from '../_util/util';
|
||||
import { computed, defineComponent, ref, watch } from 'vue';
|
||||
import firstNotUndefined from '../_util/firstNotUndefined';
|
||||
import classNames from '../_util/classNames';
|
||||
|
||||
type Key = number | string;
|
||||
|
||||
function getActiveKeysArray(activeKey: Key | Key[]) {
|
||||
let currentActiveKey = activeKey;
|
||||
if (!Array.isArray(currentActiveKey)) {
|
||||
const activeKeyType = typeof currentActiveKey;
|
||||
currentActiveKey =
|
||||
activeKeyType === 'number' || activeKeyType === 'string' ? [currentActiveKey] : [];
|
||||
}
|
||||
return currentActiveKey.map(key => String(key));
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Collapse',
|
||||
inheritAttrs: false,
|
||||
props: initDefaultProps(collapseProps(), {
|
||||
prefixCls: 'rc-collapse',
|
||||
accordion: false,
|
||||
destroyInactivePanel: false,
|
||||
}),
|
||||
slots: ['expandIcon'],
|
||||
emits: ['change'],
|
||||
setup(props, { attrs, slots, emit }) {
|
||||
const stateActiveKey = ref<Key[]>(
|
||||
getActiveKeysArray(firstNotUndefined([props.activeKey, props.defaultActiveKey])),
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.activeKey,
|
||||
() => {
|
||||
stateActiveKey.value = getActiveKeysArray(props.activeKey);
|
||||
},
|
||||
);
|
||||
const currentOpenAnimations = computed(
|
||||
() => props.openAnimation || openAnimationFactory(props.prefixCls),
|
||||
);
|
||||
|
||||
const setActiveKey = (activeKey: Key[]) => {
|
||||
if (props.activeKey === undefined) {
|
||||
stateActiveKey.value = activeKey;
|
||||
}
|
||||
emit('change', props.accordion ? activeKey[0] : activeKey);
|
||||
};
|
||||
const onClickItem = (key: Key) => {
|
||||
let activeKey = stateActiveKey.value;
|
||||
if (props.accordion) {
|
||||
activeKey = activeKey[0] === key ? [] : [key];
|
||||
} else {
|
||||
activeKey = [...activeKey];
|
||||
const index = activeKey.indexOf(key);
|
||||
const isActive = index > -1;
|
||||
if (isActive) {
|
||||
// remove active state
|
||||
activeKey.splice(index, 1);
|
||||
} else {
|
||||
activeKey.push(key);
|
||||
}
|
||||
}
|
||||
setActiveKey(activeKey);
|
||||
};
|
||||
|
||||
const getNewChild = (child, index) => {
|
||||
if (isEmptyElement(child)) return;
|
||||
const activeKey = stateActiveKey.value;
|
||||
const {
|
||||
prefixCls,
|
||||
accordion,
|
||||
destroyInactivePanel,
|
||||
expandIcon = slots.expandIcon,
|
||||
collapsible,
|
||||
} = props;
|
||||
|
||||
// If there is no key provide, use the panel order as default key
|
||||
const key = String(child.key ?? index);
|
||||
const {
|
||||
header = child.children?.header?.(),
|
||||
headerClass,
|
||||
collapsible: childCollapsible,
|
||||
disabled,
|
||||
} = child.props || {};
|
||||
let isActive = false;
|
||||
|
||||
if (accordion) {
|
||||
isActive = activeKey[0] === key;
|
||||
} else {
|
||||
isActive = activeKey.indexOf(key) > -1;
|
||||
}
|
||||
|
||||
let mergeCollapsible: CollapsibleType = childCollapsible ?? collapsible;
|
||||
// legacy 2.x
|
||||
if (disabled || disabled === '') {
|
||||
mergeCollapsible = 'disabled';
|
||||
}
|
||||
const newProps = {
|
||||
key,
|
||||
panelKey: key,
|
||||
header,
|
||||
headerClass,
|
||||
isActive,
|
||||
prefixCls,
|
||||
destroyInactivePanel,
|
||||
openAnimation: currentOpenAnimations.value,
|
||||
accordion,
|
||||
onItemClick: mergeCollapsible === 'disabled' ? null : onClickItem,
|
||||
expandIcon,
|
||||
collapsible: mergeCollapsible,
|
||||
};
|
||||
|
||||
return cloneElement(child, newProps);
|
||||
};
|
||||
|
||||
const getItems = () => {
|
||||
return flattenChildren(slots.default?.()).map(getNewChild);
|
||||
};
|
||||
|
||||
return () => {
|
||||
const { prefixCls, accordion } = props;
|
||||
const collapseClassName = classNames({
|
||||
[prefixCls]: true,
|
||||
[attrs.class as string]: !!attrs.class,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
class={collapseClassName}
|
||||
{...getDataAndAriaProps(attrs)}
|
||||
style={attrs.style}
|
||||
role={accordion ? 'tablist' : null}
|
||||
>
|
||||
{getItems()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,109 +0,0 @@
|
|||
import PanelContent from './PanelContent';
|
||||
import { initDefaultProps } from '../_util/props-util';
|
||||
import { panelProps } from './commonProps';
|
||||
import { defineComponent } from 'vue';
|
||||
import Transition from '../_util/transition';
|
||||
import classNames from '../_util/classNames';
|
||||
import devWarning from '../vc-util/devWarning';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Panel',
|
||||
props: initDefaultProps(panelProps(), {
|
||||
showArrow: true,
|
||||
isActive: false,
|
||||
onItemClick() {},
|
||||
headerClass: '',
|
||||
forceRender: false,
|
||||
}),
|
||||
slots: ['expandIcon', 'extra', 'header'],
|
||||
emits: ['itemClick'],
|
||||
setup(props, { slots, emit }) {
|
||||
devWarning(
|
||||
!('disabled' in props),
|
||||
'Collapse.Panel',
|
||||
'`disabled` is deprecated. Please use `collapsible="disabled"` instead.',
|
||||
);
|
||||
const handleItemClick = () => {
|
||||
emit('itemClick', props.panelKey);
|
||||
};
|
||||
const handleKeyPress = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.keyCode === 13 || e.which === 13) {
|
||||
handleItemClick();
|
||||
}
|
||||
};
|
||||
return () => {
|
||||
const {
|
||||
prefixCls,
|
||||
header = slots.header?.(),
|
||||
headerClass,
|
||||
isActive,
|
||||
showArrow,
|
||||
destroyInactivePanel,
|
||||
accordion,
|
||||
forceRender,
|
||||
openAnimation,
|
||||
expandIcon = slots.expandIcon,
|
||||
extra = slots.extra?.(),
|
||||
collapsible,
|
||||
} = props;
|
||||
const disabled = collapsible === 'disabled';
|
||||
|
||||
const headerCls = classNames(`${prefixCls}-header`, {
|
||||
[headerClass]: headerClass,
|
||||
[`${prefixCls}-header-collapsible-only`]: collapsible === 'header',
|
||||
});
|
||||
const itemCls = classNames({
|
||||
[`${prefixCls}-item`]: true,
|
||||
[`${prefixCls}-item-active`]: isActive,
|
||||
[`${prefixCls}-item-disabled`]: disabled,
|
||||
});
|
||||
|
||||
let icon = <i class="arrow" />;
|
||||
if (showArrow && typeof expandIcon === 'function') {
|
||||
icon = expandIcon(props);
|
||||
}
|
||||
|
||||
const panelContent = (
|
||||
<PanelContent
|
||||
v-show={isActive}
|
||||
prefixCls={prefixCls}
|
||||
isActive={isActive}
|
||||
forceRender={forceRender}
|
||||
role={accordion ? 'tabpanel' : null}
|
||||
v-slots={{ default: slots.default }}
|
||||
></PanelContent>
|
||||
);
|
||||
const transitionProps = {
|
||||
appear: true,
|
||||
css: false,
|
||||
...openAnimation,
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={itemCls}>
|
||||
<div
|
||||
class={headerCls}
|
||||
onClick={() => collapsible !== 'header' && handleItemClick()}
|
||||
role={accordion ? 'tab' : 'button'}
|
||||
tabindex={disabled ? -1 : 0}
|
||||
aria-expanded={isActive}
|
||||
onKeypress={handleKeyPress}
|
||||
>
|
||||
{showArrow && icon}
|
||||
{collapsible === 'header' ? (
|
||||
<span onClick={handleItemClick} class={`${prefixCls}-header-text`}>
|
||||
{header}
|
||||
</span>
|
||||
) : (
|
||||
header
|
||||
)}
|
||||
{extra && <div class={`${prefixCls}-extra`}>{extra}</div>}
|
||||
</div>
|
||||
<Transition {...transitionProps}>
|
||||
{!destroyInactivePanel || isActive ? panelContent : null}
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
// based on rc-collapse 3.1.1
|
||||
import CollapsePanel from './Panel';
|
||||
import Collapse from './Collapse';
|
||||
import { collapseProps, panelProps } from './commonProps';
|
||||
|
||||
Collapse.Panel = CollapsePanel;
|
||||
|
||||
export { collapseProps, panelProps };
|
||||
export default Collapse;
|
|
@ -1,35 +0,0 @@
|
|||
import cssAnimation from '../_util/css-animation';
|
||||
|
||||
function animate(node: HTMLElement, show: boolean, transitionName: string, done: () => void) {
|
||||
let height: number;
|
||||
return cssAnimation(node, transitionName, {
|
||||
start() {
|
||||
if (!show) {
|
||||
node.style.height = `${node.offsetHeight}px`;
|
||||
} else {
|
||||
height = node.offsetHeight;
|
||||
node.style.height = '0px';
|
||||
}
|
||||
},
|
||||
active() {
|
||||
node.style.height = `${show ? height : 0}px`;
|
||||
},
|
||||
end() {
|
||||
node.style.height = '';
|
||||
done();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function animation(prefixCls: string) {
|
||||
return {
|
||||
onEnter(node: HTMLElement, done: () => void) {
|
||||
return animate(node, true, `${prefixCls}-anim`, done);
|
||||
},
|
||||
onLeave(node: HTMLElement, done: () => void) {
|
||||
return animate(node, false, `${prefixCls}-anim`, done);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default animation;
|
2
v2-doc
2
v2-doc
|
@ -1 +1 @@
|
|||
Subproject commit c703df2dd803ee5097b414e2a32482b8fc2225d7
|
||||
Subproject commit dcd8922ce91de1a48c515d3b06b898d27fcb30f4
|
Loading…
Reference in New Issue