refactor(layout): layout to ts (#2991)

* refactor(layout): layout to ts

* refactor(layout): update types

* refactor: update code style

* refactor(vue-types): fix PropTypes looseBool types error

* refactor: remove BasicProps types

* refactor(layout): extends component types
pull/2996/head
John60676 2020-10-15 15:54:52 +08:00 committed by GitHub
parent 147845b55f
commit e95bac3d87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 143 additions and 141 deletions

View File

@ -36,9 +36,7 @@ export function withUndefined<T extends { default?: any }>(type: T): T {
}
export default PropTypes as VueTypesInterface & {
readonly looseBool: VueTypeValidableDef<boolean> & {
default: undefined;
};
readonly looseBool: VueTypeValidableDef<boolean>;
readonly style: VueTypeValidableDef<CSSProperties>;
readonly VNodeChild: VueTypeValidableDef<VNodeTypes>;
};

View File

@ -1,13 +1,9 @@
import classNames from '../_util/classNames';
import { inject, provide } from 'vue';
import { inject, provide, PropType, defineComponent } from 'vue';
import PropTypes from '../_util/vue-types';
import {
initDefaultProps,
getOptionProps,
hasProp,
getComponent,
getSlot,
} from '../_util/props-util';
import { tuple } from '../_util/type';
import { getOptionProps, hasProp, getComponent, getSlot } from '../_util/props-util';
import initDefaultProps from '../_util/props-util/initDefaultProps';
import BaseMixin from '../_util/BaseMixin';
import isNumeric from '../_util/isNumeric';
import { defaultConfigProvider } from '../config-provider';
@ -15,6 +11,7 @@ import BarsOutlined from '@ant-design/icons-vue/BarsOutlined';
import RightOutlined from '@ant-design/icons-vue/RightOutlined';
import LeftOutlined from '@ant-design/icons-vue/LeftOutlined';
import omit from 'omit.js';
import { SiderHookProvider } from './layout';
const dimensionMaxMap = {
xs: '479.98px',
@ -25,7 +22,7 @@ const dimensionMaxMap = {
xxl: '1599.98px',
};
// export type CollapseType = 'clickTrigger' | 'responsive';
export type CollapseType = 'clickTrigger' | 'responsive';
export const SiderProps = {
prefixCls: PropTypes.string,
@ -33,16 +30,14 @@ export const SiderProps = {
collapsed: PropTypes.looseBool,
defaultCollapsed: PropTypes.looseBool,
reverseArrow: PropTypes.looseBool,
// onCollapse?: (collapsed: boolean, type: CollapseType) => void;
zeroWidthTriggerStyle: PropTypes.object,
trigger: PropTypes.any,
zeroWidthTriggerStyle: PropTypes.style,
trigger: PropTypes.VNodeChild,
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
collapsedWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
breakpoint: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl', 'xxl']),
theme: PropTypes.oneOf(['light', 'dark']).def('dark'),
onBreakpoint: PropTypes.func,
onCollapse: PropTypes.func,
'onUpdate:collapsed': PropTypes.func,
breakpoint: PropTypes.oneOf(tuple('xs', 'sm', 'md', 'lg', 'xl', 'xxl')),
theme: PropTypes.oneOf(tuple('light', 'dark')).def('dark'),
onBreakpoint: Function as PropType<(broken: boolean) => void>,
onCollapse: Function as PropType<(collapsed: boolean, type: CollapseType) => void>,
};
// export interface SiderState {
@ -63,7 +58,7 @@ const generateId = (() => {
};
})();
export default {
export default defineComponent({
name: 'ALayoutSider',
__ANT_LAYOUT_SIDER: true,
mixins: [BaseMixin],
@ -74,17 +69,19 @@ export default {
width: 200,
collapsedWidth: 80,
}),
emits: ['breakpoint', 'update:collapsed', 'collapse'],
data() {
this.uniqueId = generateId('ant-sider-');
let matchMedia;
const uniqueId = generateId('ant-sider-');
let matchMedia: typeof window.matchMedia;
if (typeof window !== 'undefined') {
matchMedia = window.matchMedia;
}
const props = getOptionProps(this);
const props = getOptionProps(this) as any;
let mql: MediaQueryList;
if (matchMedia && props.breakpoint && props.breakpoint in dimensionMaxMap) {
this.mql = matchMedia(`(max-width: ${dimensionMaxMap[props.breakpoint]})`);
mql = matchMedia(`(max-width: ${dimensionMaxMap[props.breakpoint]})`);
}
let sCollapsed;
let sCollapsed: boolean;
if ('collapsed' in props) {
sCollapsed = props.collapsed;
} else {
@ -94,6 +91,8 @@ export default {
sCollapsed,
below: false,
belowShow: false,
uniqueId,
mql,
};
},
watch: {
@ -108,7 +107,7 @@ export default {
},
setup() {
return {
siderHook: inject('siderHook', {}),
siderHook: inject<SiderHookProvider>('siderHook', {}),
configProvider: inject('configProvider', defaultConfigProvider),
};
},
@ -136,7 +135,7 @@ export default {
}
},
methods: {
responsiveHandler(mql) {
responsiveHandler(mql: MediaQueryListEvent | MediaQueryList) {
this.setState({ below: mql.matches });
this.$emit('breakpoint', mql.matches);
if (this.sCollapsed !== mql.matches) {
@ -144,7 +143,7 @@ export default {
}
},
setCollapsed(collapsed, type) {
setCollapsed(collapsed: boolean, type: CollapseType) {
if (!hasProp(this, 'collapsed')) {
this.setState({
sCollapsed: collapsed,
@ -176,7 +175,7 @@ export default {
collapsedWidth,
zeroWidthTriggerStyle,
...others
} = { ...getOptionProps(this), ...this.$attrs };
} = { ...getOptionProps(this), ...this.$attrs } as any;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('layout-sider', customizePrefixCls);
const divProps = omit(others, [
@ -188,7 +187,6 @@ export default {
'siderHook',
'zeroWidthTriggerStyle',
'trigger',
'onUpdate:collapse',
]);
const trigger = getComponent(this, 'trigger');
const rawWidth = this.sCollapsed ? collapsedWidth : width;
@ -241,4 +239,4 @@ export default {
</aside>
);
},
};
});

View File

@ -1,10 +1,11 @@
import { App } from 'vue';
import Layout from './layout';
import Sider from './Sider';
Layout.Sider = Sider;
/* istanbul ignore next */
Layout.install = function(app) {
Layout.install = function(app: App) {
app.component(Layout.name, Layout);
app.component(Layout.Header.name, Layout.Header);
app.component(Layout.Footer.name, Layout.Footer);
@ -12,4 +13,6 @@ Layout.install = function(app) {
app.component(Layout.Content.name, Layout.Content);
return app;
};
export default Layout;
export default Layout as typeof Layout & {
readonly Sider: typeof Sider;
};

View File

@ -1,107 +0,0 @@
import { inject, provide } from 'vue';
import PropTypes from '../_util/vue-types';
import classNames from '../_util/classNames';
import { getOptionProps, getSlot } from '../_util/props-util';
import { defaultConfigProvider } from '../config-provider';
export const BasicProps = {
prefixCls: PropTypes.string,
hasSider: PropTypes.looseBool,
tagName: PropTypes.string,
};
function generator({ suffixCls, tagName, name }) {
return BasicComponent => {
return {
name,
props: BasicComponent.props,
setup() {
return {
configProvider: inject('configProvider', defaultConfigProvider),
};
},
render() {
const { prefixCls: customizePrefixCls } = this.$props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls(suffixCls, customizePrefixCls);
const basicComponentProps = {
prefixCls,
...getOptionProps(this),
tagName,
};
return <BasicComponent {...basicComponentProps}>{getSlot(this)}</BasicComponent>;
},
};
};
}
const Basic = {
props: BasicProps,
render() {
const { prefixCls, tagName: Tag } = this;
const divProps = {
class: prefixCls,
};
return <Tag {...divProps}>{getSlot(this)}</Tag>;
},
};
const BasicLayout = {
props: BasicProps,
data() {
return {
siders: [],
};
},
created() {
provide('siderHook', {
addSider: id => {
this.siders = [...this.siders, id];
},
removeSider: id => {
this.siders = this.siders.filter(currentId => currentId !== id);
},
});
},
render() {
const { prefixCls, hasSider, tagName: Tag } = this;
const divCls = classNames(prefixCls, {
[`${prefixCls}-has-sider`]: typeof hasSider === 'boolean' ? hasSider : this.siders.length > 0,
});
const divProps = {
class: divCls,
};
return <Tag {...divProps}>{getSlot(this)}</Tag>;
},
};
const Layout = generator({
suffixCls: 'layout',
tagName: 'section',
name: 'ALayout',
})(BasicLayout);
const Header = generator({
suffixCls: 'layout-header',
tagName: 'header',
name: 'ALayoutHeader',
})(Basic);
const Footer = generator({
suffixCls: 'layout-footer',
tagName: 'footer',
name: 'ALayoutFooter',
})(Basic);
const Content = generator({
suffixCls: 'layout-content',
tagName: 'main',
name: 'ALayoutContent',
})(Basic);
Layout.Header = Header;
Layout.Footer = Footer;
Layout.Content = Content;
export default Layout;

View File

@ -0,0 +1,110 @@
import { createVNode, defineComponent, inject, provide, toRefs, ref } from 'vue';
import PropTypes from '../_util/vue-types';
import classNames from '../_util/classNames';
import { defaultConfigProvider } from '../config-provider';
export const BasicProps = {
prefixCls: PropTypes.string,
hasSider: PropTypes.looseBool,
tagName: PropTypes.string,
};
export interface SiderHookProvider {
addSider?: (id: string) => void;
removeSider?: (id: string) => void;
}
type GeneratorArgument = {
suffixCls: string;
tagName: string;
name: string;
};
function generator({ suffixCls, tagName, name }: GeneratorArgument) {
return (BasicComponent: typeof Basic) => {
return defineComponent({
name,
props: BasicProps,
setup(props, { slots }) {
const { getPrefixCls } = inject('configProvider', defaultConfigProvider);
return () => {
const { prefixCls: customizePrefixCls } = props;
const prefixCls = getPrefixCls(suffixCls, customizePrefixCls);
const basicComponentProps = {
prefixCls,
...props,
tagName,
};
return <BasicComponent {...basicComponentProps}>{slots.default?.()}</BasicComponent>;
};
},
});
};
}
const Basic = defineComponent({
props: BasicProps,
setup(props, { slots }) {
const { prefixCls, tagName } = toRefs(props);
return () => createVNode(tagName.value, { class: prefixCls.value }, slots.default?.());
},
});
const BasicLayout = defineComponent({
props: BasicProps,
setup(props, { slots }) {
const siders = ref<string[]>([]);
const siderHookProvider: SiderHookProvider = {
addSider: id => {
siders.value = [...siders.value, id];
},
removeSider: id => {
siders.value = siders.value.filter(currentId => currentId !== id);
},
};
provide('siderHook', siderHookProvider);
return () => {
const { prefixCls, hasSider, tagName } = props;
const divCls = classNames(prefixCls, {
[`${prefixCls}-has-sider`]:
typeof hasSider === 'boolean' ? hasSider : siders.value.length > 0,
});
return createVNode(tagName, { class: divCls }, slots.default?.());
};
},
});
const Layout = generator({
suffixCls: 'layout',
tagName: 'section',
name: 'ALayout',
})(BasicLayout);
const Header = generator({
suffixCls: 'layout-header',
tagName: 'header',
name: 'ALayoutHeader',
})(Basic);
const Footer = generator({
suffixCls: 'layout-footer',
tagName: 'footer',
name: 'ALayoutFooter',
})(Basic);
const Content = generator({
suffixCls: 'layout-content',
tagName: 'main',
name: 'ALayoutContent',
})(Basic);
Layout.Header = Header;
Layout.Footer = Footer;
Layout.Content = Content;
export default Layout as typeof Layout & {
readonly Header: typeof Header;
readonly Footer: typeof Footer;
readonly Content: typeof Content;
};