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 & { export default PropTypes as VueTypesInterface & {
readonly looseBool: VueTypeValidableDef<boolean> & { readonly looseBool: VueTypeValidableDef<boolean>;
default: undefined;
};
readonly style: VueTypeValidableDef<CSSProperties>; readonly style: VueTypeValidableDef<CSSProperties>;
readonly VNodeChild: VueTypeValidableDef<VNodeTypes>; readonly VNodeChild: VueTypeValidableDef<VNodeTypes>;
}; };

View File

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

View File

@ -1,10 +1,11 @@
import { App } from 'vue';
import Layout from './layout'; import Layout from './layout';
import Sider from './Sider'; import Sider from './Sider';
Layout.Sider = Sider; Layout.Sider = Sider;
/* istanbul ignore next */ /* istanbul ignore next */
Layout.install = function(app) { Layout.install = function(app: App) {
app.component(Layout.name, Layout); app.component(Layout.name, Layout);
app.component(Layout.Header.name, Layout.Header); app.component(Layout.Header.name, Layout.Header);
app.component(Layout.Footer.name, Layout.Footer); app.component(Layout.Footer.name, Layout.Footer);
@ -12,4 +13,6 @@ Layout.install = function(app) {
app.component(Layout.Content.name, Layout.Content); app.component(Layout.Content.name, Layout.Content);
return app; 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;
};