250 lines
6.7 KiB
Vue
250 lines
6.7 KiB
Vue
import type {
|
|
Ref,
|
|
App,
|
|
PropType,
|
|
VNode,
|
|
HTMLAttributes,
|
|
ExtractPropTypes,
|
|
Plugin,
|
|
CSSProperties,
|
|
InjectionKey,
|
|
} from 'vue';
|
|
import {
|
|
onBeforeMount,
|
|
ref,
|
|
defineComponent,
|
|
onBeforeUnmount,
|
|
provide,
|
|
toRef,
|
|
computed,
|
|
} from 'vue';
|
|
import warning from '../_util/warning';
|
|
import type { Breakpoint, ScreenMap } from '../_util/responsiveObserve';
|
|
import ResponsiveObserve, { responsiveArray } from '../_util/responsiveObserve';
|
|
import Row from './Row';
|
|
import PropTypes from '../_util/vue-types';
|
|
import { cloneElement } from '../_util/vnode';
|
|
import { flattenChildren } from '../_util/props-util';
|
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
|
|
|
export const DescriptionsItemProps = {
|
|
prefixCls: String,
|
|
label: PropTypes.any,
|
|
span: Number,
|
|
};
|
|
|
|
const descriptionsItemProp = () => ({
|
|
prefixCls: String,
|
|
label: PropTypes.any,
|
|
labelStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
|
|
contentStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
|
|
span: { type: Number, default: 1 },
|
|
});
|
|
|
|
export type DescriptionsItemProp = Partial<
|
|
ExtractPropTypes<ReturnType<typeof descriptionsItemProp>>
|
|
>;
|
|
|
|
export const DescriptionsItem = defineComponent({
|
|
name: 'ADescriptionsItem',
|
|
props: descriptionsItemProp(),
|
|
slots: ['label'],
|
|
setup(_, { slots }) {
|
|
return () => slots.default?.();
|
|
},
|
|
});
|
|
|
|
const DEFAULT_COLUMN_MAP: Partial<Record<Breakpoint, number>> = {
|
|
xxxl: 3,
|
|
xxl: 3,
|
|
xl: 3,
|
|
lg: 3,
|
|
md: 3,
|
|
sm: 2,
|
|
xs: 1,
|
|
};
|
|
|
|
function getColumn(column: DescriptionsProps['column'], screens: ScreenMap): number {
|
|
if (typeof column === 'number') {
|
|
return column;
|
|
}
|
|
|
|
if (typeof column === 'object') {
|
|
for (let i = 0; i < responsiveArray.length; i++) {
|
|
const breakpoint: Breakpoint = responsiveArray[i];
|
|
if (screens[breakpoint] && column[breakpoint] !== undefined) {
|
|
return column[breakpoint] || DEFAULT_COLUMN_MAP[breakpoint];
|
|
}
|
|
}
|
|
}
|
|
|
|
return 3;
|
|
}
|
|
|
|
function getFilledItem(node: VNode, span: number | undefined, rowRestCol: number): VNode {
|
|
let clone = node;
|
|
|
|
if (span === undefined || span > rowRestCol) {
|
|
clone = cloneElement(node, {
|
|
span: rowRestCol,
|
|
});
|
|
warning(
|
|
span === undefined,
|
|
'Descriptions',
|
|
'Sum of column `span` in a line not match `column` of Descriptions.',
|
|
);
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
function getRows(children: VNode[], column: number) {
|
|
const childNodes = flattenChildren(children);
|
|
const rows: VNode[][] = [];
|
|
|
|
let tmpRow: VNode[] = [];
|
|
let rowRestCol = column;
|
|
childNodes.forEach((node, index) => {
|
|
const span: number | undefined = node.props?.span;
|
|
const mergedSpan = span || 1;
|
|
|
|
// Additional handle last one
|
|
if (index === childNodes.length - 1) {
|
|
tmpRow.push(getFilledItem(node, span, rowRestCol));
|
|
rows.push(tmpRow);
|
|
return;
|
|
}
|
|
|
|
if (mergedSpan < rowRestCol) {
|
|
rowRestCol -= mergedSpan;
|
|
tmpRow.push(node);
|
|
} else {
|
|
tmpRow.push(getFilledItem(node, mergedSpan, rowRestCol));
|
|
rows.push(tmpRow);
|
|
rowRestCol = column;
|
|
tmpRow = [];
|
|
}
|
|
});
|
|
|
|
return rows;
|
|
}
|
|
|
|
export const descriptionsProps = () => ({
|
|
prefixCls: String,
|
|
bordered: { type: Boolean, default: undefined },
|
|
size: { type: String as PropType<'default' | 'middle' | 'small'>, default: 'default' },
|
|
title: PropTypes.any,
|
|
extra: PropTypes.any,
|
|
column: {
|
|
type: [Number, Object] as PropType<number | Partial<Record<Breakpoint, number>>>,
|
|
default: (): number | Partial<Record<Breakpoint, number>> => DEFAULT_COLUMN_MAP,
|
|
},
|
|
layout: String as PropType<'horizontal' | 'vertical'>,
|
|
colon: { type: Boolean, default: undefined },
|
|
labelStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
|
|
contentStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
|
|
});
|
|
|
|
export type DescriptionsProps = HTMLAttributes &
|
|
Partial<ExtractPropTypes<ReturnType<typeof descriptionsProps>>>;
|
|
|
|
export interface DescriptionsContextProp {
|
|
labelStyle?: Ref<CSSProperties>;
|
|
contentStyle?: Ref<CSSProperties>;
|
|
}
|
|
|
|
export const descriptionsContext: InjectionKey<DescriptionsContextProp> =
|
|
Symbol('descriptionsContext');
|
|
|
|
const Descriptions = defineComponent({
|
|
name: 'ADescriptions',
|
|
props: descriptionsProps(),
|
|
slots: ['title', 'extra'],
|
|
Item: DescriptionsItem,
|
|
setup(props, { slots }) {
|
|
const { prefixCls, direction } = useConfigInject('descriptions', props);
|
|
let token: number;
|
|
const screens = ref<ScreenMap>({});
|
|
onBeforeMount(() => {
|
|
token = ResponsiveObserve.subscribe(screen => {
|
|
if (typeof props.column !== 'object') {
|
|
return;
|
|
}
|
|
screens.value = screen;
|
|
});
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
ResponsiveObserve.unsubscribe(token);
|
|
});
|
|
|
|
provide(descriptionsContext, {
|
|
labelStyle: toRef(props, 'labelStyle'),
|
|
contentStyle: toRef(props, 'contentStyle'),
|
|
});
|
|
|
|
const mergeColumn = computed(() => getColumn(props.column, screens.value));
|
|
|
|
return () => {
|
|
const {
|
|
size,
|
|
bordered = false,
|
|
layout = 'horizontal',
|
|
colon = true,
|
|
title = slots.title?.(),
|
|
extra = slots.extra?.(),
|
|
} = props;
|
|
|
|
const children = slots.default?.();
|
|
const rows = getRows(children, mergeColumn.value);
|
|
|
|
return (
|
|
<div
|
|
class={[
|
|
prefixCls.value,
|
|
{
|
|
[`${prefixCls.value}-${size}`]: size !== 'default',
|
|
[`${prefixCls.value}-bordered`]: !!bordered,
|
|
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
|
|
},
|
|
]}
|
|
>
|
|
{(title || extra) && (
|
|
<div class={`${prefixCls.value}-header`}>
|
|
{title && <div class={`${prefixCls.value}-title`}>{title}</div>}
|
|
{extra && <div class={`${prefixCls.value}-extra`}>{extra}</div>}
|
|
</div>
|
|
)}
|
|
<div class={`${prefixCls.value}-view`}>
|
|
<table>
|
|
<tbody>
|
|
{rows.map((row, index) => (
|
|
<Row
|
|
key={index}
|
|
index={index}
|
|
colon={colon}
|
|
prefixCls={prefixCls.value}
|
|
vertical={layout === 'vertical'}
|
|
bordered={bordered}
|
|
row={row}
|
|
/>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
},
|
|
});
|
|
|
|
Descriptions.install = function (app: App) {
|
|
app.component(Descriptions.name, Descriptions);
|
|
app.component(Descriptions.Item.name, Descriptions.Item);
|
|
return app;
|
|
};
|
|
export default Descriptions as typeof Descriptions &
|
|
Plugin & {
|
|
readonly Item: typeof DescriptionsItem;
|
|
};
|