Commit 99e8fe37 authored by Sendya's avatar Sendya Committed by 言肆

refactor: component props

parent 0c8e30a9
......@@ -44,6 +44,7 @@ const BasicLayout = defineComponent({
);
const updateSelectedMenu = () => {
console.log('route', route.matched.concat());
const matched = route.matched.concat().map(item => item.path);
matched.shift();
state.selectedKeys = matched;
......@@ -98,6 +99,9 @@ const BasicLayout = defineComponent({
state.openKeys = $event;
}}
onSelect={updateSelectedMenu}
onMenuHeaderClick={e => {
console.log('onMenuHeaderClick', e);
}}
rightContentRender={props => (
<div
class={['right-content', `${props.layout}-${props.navTheme}`]}
......@@ -108,13 +112,13 @@ const BasicLayout = defineComponent({
</span>
</div>
)}
menuHeaderRender={() => (
<a>
<img src="https://gw.alipayobjects.com/zos/antfincdn/PmY%24TNNDBI/logo.svg" />
{state.collapsed && state.layout !== 'mix' ? null : <h1>Pro Preview</h1>}
</a>
)}
menuItemRender={item => {
// menuHeaderRender={() => (
// <a>
// <img src="https://gw.alipayobjects.com/zos/antfincdn/PmY%24TNNDBI/logo.svg" />
// {state.collapsed && state.layout !== 'mix' ? null : <h1>Pro Preview</h1>}
// </a>
// )}
/* menuItemRender={item => {
return (
<Menu.Item inlineIndent={24} key={item.path}>
<router-link to={{ ...item.meta, path: item.path }}>
......@@ -130,7 +134,7 @@ const BasicLayout = defineComponent({
{children}
</Menu.SubMenu>
);
}}
}} */
breadcrumbRender={({ route: r, routes, paths }) =>
routes.indexOf(r) === routes.length - 1 ? (
<span>{i18n(r.breadcrumbName)}</span>
......
This diff is collapsed.
import { computed, CSSProperties, FunctionalComponent } from 'vue';
import { PureSettings } from '../defaultSettings';
import { CustomRender, MenuDataItem, WithFalse } from '../typings';
import { computed, FunctionalComponent, PropType, ExtractPropTypes } from 'vue';
import { defaultSettingProps } from '../defaultSettings';
import { CustomRender, Theme, MenuDataItem, WithFalse } from '../typings';
import {
siderMenuProps,
SiderMenuProps,
PrivateSiderMenuProps,
defaultRenderLogo,
defaultRenderLogoAndTitle,
defaultRenderCollapsedButton,
} from '../SiderMenu/SiderMenu';
import { TopNavHeader } from '../TopNavHeader';
import { clearMenuItem } from '../utils';
import { clearMenuItem, PropTypes } from '../utils';
import type { HeaderViewProps } from '../Header';
import './index.less';
import { useRouteContext } from '../RouteContext';
export interface GlobalHeaderProps extends Partial<PureSettings> {
collapsed?: boolean;
onCollapse?: (collapsed: boolean) => void;
isMobile?: boolean;
logo?: CustomRender;
menuRender?: WithFalse<(props: HeaderViewProps, defaultDom: CustomRender) => CustomRender>;
rightContentRender?: WithFalse<(props: HeaderViewProps) => CustomRender>;
className?: string;
prefixCls?: string;
menuData?: MenuDataItem[];
onMenuHeaderClick?: (e: MouseEvent) => void;
style?: CSSProperties;
menuHeaderRender?: SiderMenuProps['menuHeaderRender'];
collapsedButtonRender?: SiderMenuProps['collapsedButtonRender'];
splitMenus?: boolean;
onOpenKeys?: (openKeys: WithFalse<string[]>) => void;
onSelect?: (selectedKeys: WithFalse<string[]>) => void;
}
import './index.less';
export const globalHeaderProps = {
...defaultSettingProps,
prefixCls: PropTypes.string.def('ant-pro'),
collapsed: PropTypes.looseBool,
isMobile: PropTypes.looseBool,
logo: siderMenuProps.logo,
logoStyle: siderMenuProps.logoStyle,
headerTheme: {
type: String as PropType<Theme>,
default: 'dark',
},
menuData: {
type: Array as PropType<MenuDataItem[]>,
default: () => [],
},
splitMenus: siderMenuProps.splitMenus,
menuRender: {
type: [Object, Function] as PropType<
WithFalse<(props: any /* HeaderViewProps */, defaultDom: CustomRender) => CustomRender>
>,
default: () => undefined,
},
menuHeaderRender: siderMenuProps.menuHeaderRender,
rightContentRender: {
type: [Object, Function] as PropType<WithFalse<(props: any) => CustomRender>>,
default: () => undefined,
},
collapsedButtonRender: siderMenuProps.collapsedButtonRender,
matchMenuKeys: siderMenuProps.matchMenuKeys,
// events
onMenuHeaderClick: PropTypes.func,
onCollapse: siderMenuProps.onCollapse,
onOpenKeys: siderMenuProps.onOpenKeys,
onSelect: siderMenuProps.onSelect,
};
export type GlobalHeaderProps = ExtractPropTypes<typeof globalHeaderProps>;
const renderLogo = (
menuHeaderRender: SiderMenuProps['menuHeaderRender'],
......@@ -45,10 +68,7 @@ const renderLogo = (
return logoDom;
};
export const GlobalHeader: FunctionalComponent<GlobalHeaderProps & PrivateSiderMenuProps> = (
props,
{ slots },
) => {
export const GlobalHeader: FunctionalComponent<GlobalHeaderProps> = (props, { slots }) => {
const {
isMobile,
logo,
......@@ -126,5 +146,6 @@ export const GlobalHeader: FunctionalComponent<GlobalHeaderProps & PrivateSiderM
</div>
);
};
GlobalHeader.emits = ['menuHeaderClick', 'collapse', 'openKeys', 'select'];
export default GlobalHeader;
import { defineComponent, computed, toRefs } from 'vue';
import { defineComponent, computed, toRefs, PropType, ExtractPropTypes } from 'vue';
import 'ant-design-vue/es/layout/style';
import Layout from 'ant-design-vue/es/layout';
import { GlobalHeader, GlobalHeaderProps } from './GlobalHeader';
import { GlobalHeader, globalHeaderProps, GlobalHeaderProps } from './GlobalHeader';
import { TopNavHeader } from './TopNavHeader';
import { useRouteContext } from './RouteContext';
import { CustomRender, WithFalse } from './typings';
import { clearMenuItem } from './utils';
import { clearMenuItem, PropTypes } from './utils';
import './Header.less';
const { Header } = Layout;
interface HeaderViewState {
visible: boolean;
}
export type HeaderViewProps = GlobalHeaderProps & {
isMobile?: boolean;
collapsed?: boolean;
logo?: CustomRender;
headerRender?: WithFalse<(props: HeaderViewProps, defaultDom: CustomRender) => CustomRender>;
headerTitleRender?: WithFalse<(props: HeaderViewProps, defaultDom: CustomRender) => CustomRender>;
headerContentRender?: WithFalse<(props: HeaderViewProps) => CustomRender>;
siderWidth?: number;
hasSiderMenu?: boolean;
export const headerViewProps = {
...globalHeaderProps,
headerRender: {
type: [Object, Function] as PropType<
WithFalse<(props: any, defaultDom: CustomRender) => CustomRender>
>,
default: () => undefined,
},
headerTitleRender: {
type: [Object, Function] as PropType<
WithFalse<(props: any, defaultDom: CustomRender) => CustomRender>
>,
default: () => undefined,
},
headerContentRender: {
type: [Object, Function] as PropType<WithFalse<(props: any) => CustomRender>>,
default: () => undefined,
},
hasSiderMenu: PropTypes.looseBool,
siderWidth: PropTypes.number.def(208),
};
export const headerProps = [
'prefixCls',
'collapsed',
'onCollapse',
'openKeys',
'selectedKeys',
'isMobile',
'logo',
'title',
'menuRender',
'rightContentRender',
'menuData',
'menuHeaderRender',
'splitMenus',
'headerRender',
'headerTitleRender',
'headerContentRender',
'siderWidth',
'hasSiderMenu',
'fixedHeader',
'headerHeight',
'headerTheme',
'layout',
'navTheme',
'onSelect',
'onOpenChange',
'onOpenKeys',
];
export type HeaderViewProps = Partial<ExtractPropTypes<typeof headerViewProps> & GlobalHeaderProps>;
export const HeaderView = defineComponent({
inheritAttrs: false,
name: 'HeaderView',
props: headerProps,
setup(props /*Required<HeaderViewProps> */) {
props: headerViewProps,
setup(props) {
const {
prefixCls,
isMobile,
......
......@@ -10,26 +10,9 @@
.@{pro-layout-page-container} {
&-warp {
background-color: @component-background;
.@{ant-prefix}-tabs-nav {
.@{ant-prefix}-tabs-bar {
margin: 0;
}
// fix antd@4 upgrade PageHeader style.
.@{ant-prefix}-page-header-heading {
.@{ant-prefix}-page-header-back {
margin: 12px 0;
margin-right: 16px;
}
.@{ant-prefix}-page-header-heading-title,
.@{ant-prefix}-page-header-heading-extra {
margin: 4px 0;
}
.@{ant-prefix}-page-header-heading-sub-title {
margin: 9px 12px 9px 0;
}
.@{ant-prefix}-page-header-heading-tags {
margin: 9px 0;
}
}
}
&-ghost {
.@{pro-layout-page-container}-warp {
......
This diff is collapsed.
import { InjectionKey, reactive } from 'vue';
import { createContext, useContext } from './hooks/context';
import { MenuDataItem, CustomRender } from './typings';
import { MenuDataItem, FormatMessage, WithFalse, CustomRender } from './typings';
import { PureSettings } from './defaultSettings';
export interface Route {
path: string;
......@@ -35,7 +35,7 @@ export interface MenuState {
export interface RouteContextProps extends Partial<PureSettings>, MenuState {
getPrefixCls?: (suffixCls?: string, customizePrefixCls?: string) => string;
i18n?: (t: string) => string;
locale?: WithFalse<FormatMessage>;
breadcrumb?: BreadcrumbListReturn;
menuData: MenuDataItem[];
......@@ -62,7 +62,7 @@ export const getPrefixCls = (suffixCls?: string, customizePrefixCls?: string) =>
// set default context
const defaultRouteContext = reactive({
getPrefixCls,
i18n: (t: string) => t,
locale: (t: string) => t,
contentWidth: 'Fluid',
hasFooterToolbar: false,
setHasFooterToolbar: (bool: boolean) => (defaultRouteContext.hasFooterToolbar = bool),
......
......@@ -8,15 +8,22 @@ import {
PropType,
isVNode,
toRefs,
ExtractPropTypes,
} from 'vue';
import { createFromIconfontCN } from '@ant-design/icons-vue';
import 'ant-design-vue/es/menu/style';
import Menu from 'ant-design-vue/es/menu';
import defaultSettings, { PureSettings } from '../defaultSettings';
import { defaultSettingProps, defaultSettings } from '../defaultSettings';
import { isImg, isUrl } from '../utils';
import { MenuMode, SelectInfo, OpenEventHandler } from './typings';
import { MenuDataItem, MenuTheme, FormatMessage, CustomRender, WithFalse } from '../typings';
import { PrivateSiderMenuProps } from './SiderMenu';
import {
MenuDataItem,
MenuTheme,
FormatMessage,
CustomRender,
LayoutType,
WithFalse,
} from '../typings';
import './index.less';
export { MenuMode, SelectInfo, OpenEventHandler };
......@@ -25,53 +32,37 @@ export function useRootSubmenuKeys(menus: MenuDataItem[]): ComputedRef<string[]>
return computed(() => menus.map(it => it.path));
}
// ts typo
interface CustomMenuRender {
export interface CustomMenuRender {
menuItemRender?: WithFalse<(item: MenuDataItem) => CustomRender>;
subMenuItemRender?: WithFalse<(item: MenuDataItem, children?: CustomRender[]) => CustomRender>;
}
export interface BaseMenuProps
extends Partial<PureSettings>,
PrivateSiderMenuProps,
CustomMenuRender {
menuProps?: Record<string, any>;
prefixCls?: string;
collapsed?: boolean;
splitMenus?: boolean;
isMobile?: boolean;
menuData?: MenuDataItem[];
mode?: MenuMode;
onCollapse?: (collapsed: boolean) => void;
openKeys?: WithFalse<string[]> | undefined;
selectedKeys?: WithFalse<string[]> | undefined;
handleOpenChange?: (openKeys: string[]) => void;
theme?: MenuTheme | 'realDark';
i18n?: FormatMessage;
}
// vue props
export const baseMenuProps = {
locale: Boolean,
...defaultSettingProps,
prefixCls: {
type: String as PropType<string | undefined>,
default: () => 'ant-pro',
},
i18n: {
type: Function as PropType<FormatMessage>,
locale: {
type: [Function, Object, Boolean] as PropType<WithFalse<FormatMessage>>,
default: (t: string): string => t,
},
menuData: Array as PropType<MenuDataItem[]>,
menuData: {
type: Array as PropType<MenuDataItem[]>,
default: () => [],
},
// top-nav-header: horizontal
mode: {
type: String as PropType<MenuMode>,
default: 'inline',
},
theme: {
type: String as PropType<BaseMenuProps['theme']>,
type: String as PropType<MenuTheme | 'realDark'>,
default: 'dark',
},
layout: {
type: String as PropType<BaseMenuProps['layout']>,
type: String as PropType<LayoutType>,
default: 'side',
},
collapsed: {
......@@ -87,24 +78,30 @@ export const baseMenuProps = {
default: undefined,
},
menuProps: {
type: Object as PropType<BaseMenuProps['menuProps']>,
type: Object as PropType<Record<string, any>>,
default: () => null,
},
menuItemRender: {
type: [Function, Boolean] as PropType<BaseMenuProps['menuItemRender']>,
type: [Function, Boolean] as PropType<CustomMenuRender['menuItemRender']>,
default: () => false,
},
subMenuItemRender: {
type: [Function, Boolean] as PropType<BaseMenuProps['subMenuItemRender']>,
type: [Function, Boolean] as PropType<CustomMenuRender['subMenuItemRender']>,
default: () => false,
},
};
export type BaseMenuProps = ExtractPropTypes<typeof baseMenuProps>;
const IconFont = createFromIconfontCN({
scriptUrl: defaultSettings.iconfontUrl,
});
const LazyIcon = (props: { icon: VNode | string; iconPrefixes?: string; prefixCls?: string }) => {
const LazyIcon = (props: {
icon: VNodeChild | string;
iconPrefixes?: string;
prefixCls?: string;
}) => {
const { icon, iconPrefixes = 'icon-', prefixCls = 'ant-pro' } = props;
if (!icon) {
return null;
......@@ -120,7 +117,7 @@ const LazyIcon = (props: { icon: VNode | string; iconPrefixes?: string; prefixCl
if (isVNode(icon)) {
return icon;
}
const DynamicIcon = resolveComponent(icon) as any;
const DynamicIcon = resolveComponent(icon as string) as any;
return (typeof LazyIcon === 'function' && <DynamicIcon />) || null;
};
......@@ -156,8 +153,8 @@ class MenuUtil {
this.getNavMenuItems(item.children, true),
) as VNode;
}
const { prefixCls, i18n } = this.props;
const menuTitle = (i18n && i18n(item.meta?.title)) || item.meta?.title;
const { prefixCls, locale } = this.props;
const menuTitle = (locale && locale(item.meta?.title)) || item.meta?.title;
const defaultTitle = item.meta?.icon ? (
<span class={`${prefixCls}-menu-item`}>
<span class={`${prefixCls}-menu-item-title`}>{menuTitle}</span>
......@@ -166,12 +163,14 @@ class MenuUtil {
<span class={`${prefixCls}-menu-item`}>{menuTitle}</span>
);
const MenuComponent = item.meta?.type === 'group' ? Menu.ItemGroup : Menu.SubMenu;
const hasGroup = item.meta?.type === 'group';
const MenuComponent = hasGroup ? Menu.ItemGroup : Menu.SubMenu;
return (
<MenuComponent
title={defaultTitle}
key={item.path}
icon={item.meta?.type === 'group' ? null : <LazyIcon icon={item.meta?.icon} />}
icon={hasGroup ? null : <LazyIcon icon={item.meta?.icon} />}
>
{this.getNavMenuItems(item.children, true)}
</MenuComponent>
......@@ -201,8 +200,8 @@ class MenuUtil {
const props = { to: { name: item.name } };
const attrs = hasUrl || target ? { ...item.meta, href: item.path, target: target } : {};
const { prefixCls, i18n } = this.props;
const menuTitle = (i18n && i18n(item.meta?.title)) || item.meta?.title;
const { prefixCls, locale } = this.props;
const menuTitle = (locale && locale(item.meta?.title)) || item.meta?.title;
const defaultTitle = item.meta?.icon ? (
<CustomTag {...attrs} {...props} class={`${prefixCls}-menu-item`}>
<span class={`${prefixCls}-menu-item-title`}>{menuTitle}</span>
......@@ -234,7 +233,6 @@ export default defineComponent({
const menuUtil = new MenuUtil(props);
const handleOpenChange = (openKeys: string[]): void => {
console.log('openChange', '....');
emit('update:openKeys', openKeys);
};
const handleSelect = (params: {
......@@ -257,6 +255,7 @@ export default defineComponent({
theme={props.theme as 'dark' | 'light'}
openKeys={props.openKeys === false ? [] : props.openKeys}
selectedKeys={props.selectedKeys || []}
// @ts-ignore
onOpenChange={handleOpenChange}
onSelect={handleSelect}
{...props.menuProps}
......
import { FunctionalComponent, computed } from 'vue';
import {
FunctionalComponent as FC,
computed,
ExtractPropTypes,
PropType,
CSSProperties,
} from 'vue';
import 'ant-design-vue/es/layout/style';
import Layout from 'ant-design-vue/es/layout';
import 'ant-design-vue/es/menu/style';
import Menu from 'ant-design-vue/es/menu';
import BaseMenu, { BaseMenuProps } from './BaseMenu';
import BaseMenu, { baseMenuProps } from './BaseMenu';
import { FormatMessage, WithFalse, CustomRender } from '../typings';
import { SiderProps } from './typings';
import { defaultSettingProps } from '../defaultSettings';
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue';
import { useRouteContext } from '../RouteContext';
import { getMenuFirstChildren } from '../utils';
import { PropTypes, getMenuFirstChildren } from '../utils';
import './index.less';
const { Sider } = Layout;
......@@ -17,28 +24,73 @@ export type PrivateSiderMenuProps = {
matchMenuKeys?: string[];
};
export interface SiderMenuProps
extends Pick<BaseMenuProps, Exclude<keyof BaseMenuProps, ['onCollapse']>> {
logo?: CustomRender;
siderWidth?: number;
collapsedWidth?: number;
menuHeaderRender?: WithFalse<
(logo: CustomRender, title: CustomRender, props?: SiderMenuProps) => CustomRender
>;
menuFooterRender?: WithFalse<(props?: SiderMenuProps) => CustomRender>;
menuContentRender?: WithFalse<(props: SiderMenuProps, defaultDom: CustomRender) => CustomRender>;
menuExtraRender?: WithFalse<(props: SiderMenuProps) => CustomRender>;
collapsedButtonRender?: WithFalse<(collapsed?: boolean) => CustomRender>;
breakpoint?: SiderProps['breakpoint'] | false;
onMenuHeaderClick?: (e: MouseEvent) => void;
fixed?: boolean;
hide?: boolean;
onCollapse?: (collapsed: boolean) => void;
onOpenKeys?: (openKeys: WithFalse<string[]>) => void;
onSelect?: (selectedKeys: WithFalse<string[]>) => void;
}
export const siderMenuProps = {
...defaultSettingProps,
...baseMenuProps,
logo: {
type: [Object, String, Function] as PropType<CustomRender>,
default: () => undefined,
},
logoStyle: {
type: Object as PropType<CSSProperties>,
default: () => undefined,
},
siderWidth: PropTypes.number.def(208),
headerHeight: PropTypes.number.def(48),
collapsedWidth: PropTypes.number.def(48),
menuHeaderRender: {
type: [Function, Object] as PropType<
WithFalse<(logo: CustomRender, title: CustomRender, props?: any) => CustomRender>
>,
default: () => undefined,
},
menuFooterRender: {
type: [Function, Object] as PropType<WithFalse<(props?: any) => CustomRender>>,
default: () => undefined,
},
menuContentRender: {
type: [Function, Object] as PropType<
WithFalse<(props: any, defaultDom: CustomRender) => CustomRender>
>,
default: () => undefined,
},
menuExtraRender: {
type: [Function, Object] as PropType<WithFalse<(props?: any) => CustomRender>>,
default: () => undefined,
},
collapsedButtonRender: {
type: [Function, Object] as PropType<WithFalse<(collapsed?: boolean) => CustomRender>>,
default: () => undefined,
},
breakpoint: {
type: [Object, Boolean] as PropType<SiderProps['breakpoint'] | false>,
default: () => false,
},
isMobile: PropTypes.looseBool,
splitMenus: PropTypes.looseBool,
fixed: PropTypes.looseBool,
hide: PropTypes.looseBool,
matchMenuKeys: {
type: Array as PropType<string[]>,
default: () => [],
},
export const defaultRenderLogo = (logo?: CustomRender): CustomRender => {
// events
onMenuHeaderClick: PropTypes.func,
onCollapse: {
type: Function as PropType<(collapsed: boolean) => void>,
},
onOpenKeys: {
type: Function as PropType<(openKeys: WithFalse<string[]>) => void>,
},
onSelect: {
type: Function as PropType<(selectedKeys: WithFalse<string[]>) => void>,
},
};
export type SiderMenuProps = Partial<ExtractPropTypes<typeof siderMenuProps>>;
export const defaultRenderLogo = (logo?: CustomRender, logoStyle?: CSSProperties): CustomRender => {
if (!logo) {
return null;
}
......@@ -57,6 +109,7 @@ export const defaultRenderLogoAndTitle = (
): CustomRender | null => {
const {
logo = 'https://gw.alipayobjects.com/zos/antfincdn/PmY%24TNNDBI/logo.svg',
logoStyle,
title,
layout,
} = props;
......@@ -64,7 +117,7 @@ export const defaultRenderLogoAndTitle = (
if (renderFunction === false) {
return null;
}
const logoDom = defaultRenderLogo(logo);
const logoDom = defaultRenderLogo(logo, logoStyle);
const titleDom = <h1>{title}</h1>;
// call menuHeaderRender
if (renderFunction) {
......@@ -85,7 +138,7 @@ export const defaultRenderLogoAndTitle = (
export const defaultRenderCollapsedButton = (collapsed?: boolean): CustomRender =>
collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />;
const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) => {
const SiderMenu: FC<SiderMenuProps> = (props: SiderMenuProps) => {
const {
collapsed,
siderWidth,
......@@ -99,20 +152,19 @@ const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) =
onCollapse,
onOpenKeys,
onSelect,
onMenuHeaderClick,
} = props;
const context = useRouteContext();
const { getPrefixCls, i18n } = context;
const { getPrefixCls, locale } = context;
const baseClassName = getPrefixCls('sider');
// const isMix = computed(() => props.layout === 'mix');
// const fixed = computed(() => context.fixSiderbar);
const runtimeTheme = computed(() => (props.layout === 'mix' && 'light') || props.navTheme);
const runtimeSideWidth = computed(() =>
props.collapsed ? props.collapsedWidth : props.siderWidth,
);
const sTheme = computed(() => (props.layout === 'mix' && 'light') || props.navTheme);
const sSideWidth = computed(() => (props.collapsed ? props.collapsedWidth : props.siderWidth));
const classNames = computed(() => {
return {
[baseClassName]: true,
[`${baseClassName}-${runtimeTheme.value}`]: true,
[`${baseClassName}-${sTheme.value}`]: true,
[`${baseClassName}-${props.layout}`]: true,
[`${baseClassName}-fixed`]: context.fixSiderbar,
};
......@@ -130,8 +182,8 @@ const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) =
const defaultMenuDom = (
<BaseMenu
prefixCls={getPrefixCls()}
i18n={i18n as FormatMessage}
theme={runtimeTheme.value === 'realDark' ? 'dark' : runtimeTheme.value}
locale={props.locale}
theme={sTheme.value === 'realDark' ? 'dark' : sTheme.value}
mode="inline"
menuData={hasSide.value ? flatMenuData.value : context.menuData}
collapsed={props.collapsed}
......@@ -155,24 +207,44 @@ const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) =
{context.fixSiderbar && (
<div
style={{
width: `${runtimeSideWidth.value}px`,
width: `${sSideWidth.value}px`,
overflow: 'hidden',
flex: `0 0 ${runtimeSideWidth.value}px`,
maxWidth: `${runtimeSideWidth.value}px`,
minWidth: `${runtimeSideWidth.value}px`,
flex: `0 0 ${sSideWidth.value}px`,
maxWidth: `${sSideWidth.value}px`,
minWidth: `${sSideWidth.value}px`,
transition: `background-color 0.3s, min-width 0.3s, max-width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1)`,
}}
/>
)}
<Sider
class={classNames.value}
theme={runtimeTheme.value === 'realDark' ? 'dark' : runtimeTheme.value}
width={siderWidth}
breakpoint={breakpoint || undefined}
collapsible
trigger={null}
collapsed={collapsed}
collapsible={false}
breakpoint={breakpoint || undefined}
onCollapse={collapse => {
if (props.isMobile) return;
onCollapse?.(collapse);
}}
collapsedWidth={collapsedWidth}
style={{
overflow: 'hidden',
paddingTop:
props.layout === 'mix' && !props.isMobile ? `${props.headerHeight}px` : undefined,
}}
width={siderWidth}
theme={sTheme.value === 'realDark' ? 'dark' : sTheme.value}
class={classNames.value}
>
<div class={`${baseClassName}-logo`}>{headerDom}</div>
{headerDom && (
<div
class={`${baseClassName}-logo`}
onClick={props.layout !== 'mix' ? onMenuHeaderClick : undefined}
id="logo"
style={props?.logoStyle}
>
{headerDom}
</div>
)}
{extraDom && (
<div class={`${baseClassName}-extra ${!headerDom && `${baseClassName}-extra-no-logo`}`}>
{extraDom}
......@@ -185,7 +257,7 @@ const SiderMenu: FunctionalComponent<SiderMenuProps> = (props: SiderMenuProps) =
<Menu
class={`${baseClassName}-link-menu`}
inlineIndent={16}
theme={runtimeTheme.value as 'light' | 'dark'}
theme={sTheme.value as 'light' | 'dark'}
selectedKeys={[]}
openKeys={[]}
mode="inline"
......
import { FunctionalComponent } from 'vue';
import { FunctionalComponent as FC } from 'vue';
import 'ant-design-vue/es/drawer/style';
import Drawer from 'ant-design-vue/es/drawer';
import SiderMenu, { SiderMenuProps, PrivateSiderMenuProps } from './SiderMenu';
import SiderMenu, { siderMenuProps, SiderMenuProps, PrivateSiderMenuProps } from './SiderMenu';
export type SiderMenuWrapperProps = SiderMenuProps & Partial<PrivateSiderMenuProps>;
export type SiderMenuWrapperProps = Partial<SiderMenuProps> & Partial<PrivateSiderMenuProps>;
const SiderMenuWrapper: FunctionalComponent<SiderMenuWrapperProps> = (props, { attrs }) => {
const SiderMenuWrapper: FC<SiderMenuWrapperProps> = (props, { attrs }) => {
return props.isMobile ? (
<Drawer
visible={!props.collapsed}
......@@ -33,7 +33,15 @@ const SiderMenuWrapper: FunctionalComponent<SiderMenuWrapperProps> = (props, { a
);
};
SiderMenuWrapper.inheritAttrs = true;
SiderMenuWrapper.inheritAttrs = false;
SiderMenuWrapper.displayName = 'SiderMenuWrapper';
export {
SiderMenu,
SiderMenuProps,
PrivateSiderMenuProps,
// vue props
siderMenuProps,
};
export default SiderMenuWrapper;
import { ref, computed, FunctionalComponent } from 'vue';
import { ref, computed, FunctionalComponent, ExtractPropTypes } from 'vue';
import {
siderMenuProps,
SiderMenuProps,
defaultRenderLogoAndTitle,
PrivateSiderMenuProps,
} from '../SiderMenu/SiderMenu';
import BaseMenu from '../SiderMenu/BaseMenu';
import { GlobalHeaderProps } from '../GlobalHeader';
import { globalHeaderProps } from '../GlobalHeader';
import { default as ResizeObserver } from 'ant-design-vue/es/vc-resize-observer';
import { FormatMessage } from '../typings';
import './index.less';
import { useRouteContext } from '../RouteContext';
import './index.less';
export type TopNavHeaderProps = SiderMenuProps & GlobalHeaderProps & PrivateSiderMenuProps & {};
export const topNavHeaderProps = {
...siderMenuProps,
...globalHeaderProps,
};
export type TopNavHeaderProps = Partial<ExtractPropTypes<typeof topNavHeaderProps>> &
Partial<SiderMenuProps>;
const RightContent: FunctionalComponent<TopNavHeaderProps> = ({ rightContentRender, ...props }) => {
const rightSize = ref<number | string>('auto');
......@@ -63,7 +69,7 @@ export const TopNavHeader: FunctionalComponent<TopNavHeaderProps> = props => {
const context = useRouteContext();
const prefixCls = `${propPrefixCls || 'ant-pro'}-top-nav-header`;
const headerDom = defaultRenderLogoAndTitle(
{ ...props, collapsed: false },
{ ...props, collapsed: false } as SiderMenuProps,
// REMARK:: Any time render header title
// layout === 'mix' ? 'headerTitleRender' : undefined,
layout !== 'side' ? 'headerTitleRender' : undefined,
......@@ -87,7 +93,7 @@ export const TopNavHeader: FunctionalComponent<TopNavHeaderProps> = props => {
<div style={{ flex: 1 }} class={`${prefixCls}-menu`}>
<BaseMenu
prefixCls={propPrefixCls}
i18n={context.i18n as FormatMessage}
locale={context.locale as FormatMessage}
theme={props.theme === 'realDark' ? 'dark' : props.theme}
mode={props.mode}
collapsed={props.collapsed}
......@@ -106,3 +112,5 @@ export const TopNavHeader: FunctionalComponent<TopNavHeaderProps> = props => {
</div>
);
};
TopNavHeader.inheritAttrs = false;
import { MenuTheme, ContentWidth } from './typings';
import type { PropType, ExtractPropTypes } from 'vue';
import type { MenuTheme, ContentWidth } from './typings';
export interface RenderSetting {
headerRender?: false;
......@@ -6,7 +7,6 @@ export interface RenderSetting {
menuRender?: false;
menuHeaderRender?: false;
}
export interface RenderSetting {
headerRender?: false;
footerRender?: false;
......@@ -56,7 +56,7 @@ export interface PureSettings {
export type ProSettings = PureSettings & RenderSetting;
const defaultSettings: ProSettings = {
export const defaultSettings = {
navTheme: 'dark',
layout: 'side',
contentWidth: 'Fluid',
......@@ -71,4 +71,51 @@ const defaultSettings: ProSettings = {
primaryColor: '#1890ff',
};
export default defaultSettings;
export const defaultSettingProps = {
navTheme: {
type: String as PropType<PureSettings['navTheme']>,
default: defaultSettings.navTheme,
},
layout: {
type: String as PropType<PureSettings['layout']>,
default: defaultSettings.layout,
},
contentWidth: {
type: String as PropType<PureSettings['contentWidth']>,
default: defaultSettings.contentWidth,
},
fixedHeader: {
type: Boolean as PropType<PureSettings['fixedHeader']>,
default: defaultSettings.fixedHeader,
},
fixSiderbar: {
type: Boolean as PropType<PureSettings['fixSiderbar']>,
default: defaultSettings.fixSiderbar,
},
menu: {
type: Object as PropType<PureSettings['menu']>,
default: () => {
return {
locale: true,
};
},
},
headerHeight: {
type: Number as PropType<PureSettings['headerHeight']>,
default: defaultSettings.headerHeight,
},
title: {
type: String as PropType<PureSettings['title']>,
default: () => defaultSettings.title,
},
iconfontUrl: {
type: String as PropType<PureSettings['iconfontUrl']>,
default: () => defaultSettings.iconfontUrl,
},
primaryColor: {
type: String as PropType<PureSettings['primaryColor']>,
default: () => defaultSettings.primaryColor,
},
};
export type ProSettingsProps = ExtractPropTypes<typeof defaultSettingProps>;
......@@ -24,4 +24,4 @@ export {
} from './SiderMenu/BaseMenu';
export { default as WaterMark } from './WaterMark/index';
export { default } from './BasicLayout';
export { default, BasicLayoutProps } from './BasicLayout';
import type { Slot, VNode } from 'vue';
import type { Slot, VNode, VNodeChild } from 'vue';
export type MenuTheme = 'dark' | 'light';
export type Theme = 'dark' | 'light';
export type MenuTheme = Theme;
export type LayoutType = 'side' | 'top' | 'mix';
......@@ -66,6 +68,7 @@ export type WithFalse<T> = T | false;
export type CustomRender =
| Slot
| VNodeChild
| VNode
| ((...props: any[]) => Slot)
| ((...props: any[]) => VNode)
......
import 'vue';
type EventHandler = (...args: any[]) => void;
declare module 'vue' {
interface ComponentCustomProps {
role?: string;
tabindex?: number | string;
// should be removed after Vue supported component events typing
// see: https://github.com/vuejs/vue-next/issues/1553
// https://github.com/vuejs/vue-next/issues/3029
onBlur?: EventHandler;
onOpen?: EventHandler;
onEdit?: EventHandler;
onLoad?: EventHandler;
onClose?: EventHandler;
onFocus?: EventHandler;
onInput?: EventHandler;
onClick?: EventHandler;
onPress?: EventHandler;
onCancel?: EventHandler;
onChange?: EventHandler;
onDelete?: EventHandler;
onScroll?: EventHandler;
onSubmit?: EventHandler;
onSelect?: EventHandler;
onConfirm?: EventHandler;
onPreview?: EventHandler;
onKeypress?: EventHandler;
onTouchend?: EventHandler;
onTouchmove?: EventHandler;
onTouchstart?: EventHandler;
onTouchcancel?: EventHandler;
onMouseenter?: EventHandler;
onMouseleave?: EventHandler;
onMousemove?: EventHandler;
onKeydown?: EventHandler;
onKeyup?: EventHandler;
onDeselect?: EventHandler;
}
}
......@@ -4,22 +4,29 @@ exports[`BasicLayout 🥩 base use 1`] = `
<div class="ant-pro-basicLayout ant-pro-basicLayout-side">
<section class="ant-layout ant-pro-basicLayout">
<!---->
<aside class="ant-pro-sider ant-pro-sider-light ant-pro-sider-side ant-layout-sider ant-layout-sider-light" style="flex: 0 0 208px; max-width: 208px; min-width: 208px; width: 208px;">
<aside style="overflow: hidden; flex: 0 0 208px; max-width: 208px; min-width: 208px; width: 208px;" class="ant-layout-sider ant-layout-sider-light ant-pro-sider ant-pro-sider-light ant-pro-sider-side">
<div class="ant-layout-sider-children">
<div class="ant-pro-sider-logo"><a><img src="https://alicdn.antdv.com/v2/assets/logo.1ef800a8.svg" alt="logo">
<div class="ant-pro-sider-logo" id="logo"><a><img src="https://alicdn.antdv.com/v2/assets/logo.1ef800a8.svg" alt="logo">
<h1>Pro Tests</h1>
</a></div>
<!---->
<div style="flex: 1; overflow: hidden auto;">
<ul role="menu" class="ant-pro-sider-menu ant-menu-light ant-menu-root ant-menu ant-menu-inline" style="width: 100%;"></ul>
<ul class="ant-menu ant-menu-root ant-menu-inline ant-menu-light ant-pro-sider-menu" style="width: 100%;" data-hack-store-update="[object Object]" role="menu" data-menu-list="true">
<!---->
<!---->
</ul>
</div>
<div class="ant-pro-sider-links">
<ul role="menu" class="ant-pro-sider-link-menu ant-menu-light ant-menu-root ant-menu ant-menu-inline">
<ul class="ant-menu ant-menu-root ant-menu-inline ant-menu-light ant-pro-sider-link-menu" data-hack-store-update="[object Object]" role="menu" data-menu-list="true">
<!---->
<li role="menuitem" style="padding-left: 16px;" class="ant-pro-sider-collapsed-button ant-menu-item"><span role="img" aria-label="menu-fold" class="anticon anticon-menu-fold"><svg class="" data-icon="menu-fold" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896" focusable="false"><path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 000 13.8z"></path></svg></span>
<!---->
<li class="ant-menu-item ant-pro-sider-collapsed-button ant-menu-item-only-child" style="padding-left: 16px;" role="menuitem" tabindex="-1" data-menu-id="collapsed-button" aria-disabled="false">
<!----><span class="ant-menu-title-content"><span role="img" aria-label="menu-fold" class="anticon anticon-menu-fold"><svg focusable="false" class="" data-icon="menu-fold" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 000 13.8z"></path></svg></span></span>
</li>
<!---->
<!---->
</ul>
</div>
<!---->
</div>
<!---->
</aside>
......@@ -33,10 +40,10 @@ exports[`BasicLayout 🥩 base use 1`] = `
<div style="flex: 1;">
<!---->
</div>
<div class="custom-header-right-content"><span>custom-right-content</span></div>
<div><span>custom-right-content</span></div>
</div>
</header>
<main class="ant-layout-content ant-pro-basicLayout-content ant-pro-basicLayout-has-header" ischildrenlayout="false" style="min-height: 300px;">
<main class="ant-layout-content ant-pro-basicLayout-content ant-pro-basicLayout-has-header" style="min-height: 300px;">
<div>content</div>
</main>
<div>custom-footer</div>
......
import './_utils/mock-func';
import { mount } from '@vue/test-utils';
import { PropType } from 'vue';
import BasicLayout, { BasicLayoutProps } from '../src/BasicLayout';
import BasicLayout, { BasicLayoutProps } from '../src';
const title = 'Pro Tests';
const logoSrc = 'https://alicdn.antdv.com/v2/assets/logo.1ef800a8.svg';
......@@ -18,11 +18,13 @@ describe('BasicLayout', () => {
navTheme="light"
contentWidth="Fluid"
contentStyle={{ minHeight: '300px' }}
rightContentRender={() => (
<div class="custom-header-right-content">
<span>custom-right-content</span>
</div>
)}
rightContentRender={() => {
return (
<div>
<span>custom-right-content</span>
</div>
);
}}
footerRender={() => <div>custom-footer</div>}
>
<div>content</div>
......@@ -37,6 +39,7 @@ describe('BasicLayout', () => {
const wrapper = mount({
render() {
return (
// @ts-ignore
<BasicLayout title={title} logo={logoSrc}>
<div>content</div>
</BasicLayout>
......@@ -64,7 +67,10 @@ describe('BasicLayout', () => {
},
render() {
return (
<BasicLayout navTheme={this.theme} layout={this.layout}>
<BasicLayout
navTheme={this.theme as BasicLayoutProps['navTheme']}
layout={this.layout as BasicLayoutProps['layout']}
>
<div>content</div>
</BasicLayout>
);
......
import { mount } from '@vue/test-utils';
import PageContainer from '../src/PageContainer';
import { PageContainer } from '../src';
import { Tag, Button } from 'ant-design-vue';
import { sleep } from './utils';
......@@ -40,15 +40,30 @@ describe('PageContainer', () => {
};
it('🥩 base use', () => {
const wrapper = mount({
render() {
return (
<PageContainer {...props}>
<div>PageContent</div>
</PageContainer>
);
const wrapper = mount(
{
render() {
return (
<PageContainer {...props}>
<div>PageContent</div>
</PageContainer>
);
},
},
});
{
global: {
mocks: {
window: {
ResizeObserver: class {
observe() {}
unobserve() {}
disconnect() {}
},
},
},
},
},
);
expect(wrapper.html()).toMatchSnapshot();
});
......@@ -64,6 +79,12 @@ describe('PageContainer', () => {
});
it('😄 render content,extraContent', async () => {
mount(PageContainer, {
props: {
content: <div class="my-test-content">MyTestContent</div>,
extraContent: <span>extra right content</span>,
},
});
const wrapper = mount({
render() {
return (
......
......@@ -2417,7 +2417,7 @@
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.1.1.tgz#2287cfc3dc20e5b20aeb65c2c3a56533bdca801c"
integrity sha512-g+4pzAw7PYSjARtLBoDq6DmcblX8i9KJHSCnyM5VDDFFifUaUT9iHbFpOF/KOizQ9f7QAqU2JH3Y6aXjzUMhVA==
"@vue/test-utils@^2.0.0-beta.2":
"@vue/test-utils@^2.0.0-rc.6":
version "2.0.0-rc.6"
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.0.0-rc.6.tgz#d0aac24d20450d379e183f70542c0822670b8783"
integrity sha512-0cnQBVH589PwgqWpyv1fgCAz+9Ram/MsvN3ZEAEVXi1aPuhUa22EudGc0WezQ9PKwR+L40NrBmt3JBXE2tSRRQ==
......@@ -2835,10 +2835,10 @@ ansi-wrap@0.1.0, ansi-wrap@^0.1.0:
resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768=
ant-design-vue@^2.2.0-beta.2:
version "2.2.0-beta.2"
resolved "https://registry.yarnpkg.com/ant-design-vue/-/ant-design-vue-2.2.0-beta.2.tgz#f6a97245a81e4aabfb792c3ff17292919a2112a7"
integrity sha512-Dqpc1J3aG/beww/Hb8g3I+eDAW+zfxvQNFuyZBmlI7pKAIV6GZOOtQKI4oq8wsvxr32TP/Xl4/ffKN2Z03aefQ==
ant-design-vue@^2.2.0-beta.3:
version "2.2.0-beta.3"
resolved "https://registry.yarnpkg.com/ant-design-vue/-/ant-design-vue-2.2.0-beta.3.tgz#ef01b99b8b823a00ace4d373539bfd393b96a640"
integrity sha512-bFcAZK4mUaiHQwYLof+quY/6X2HBwEAYXcEEe2bOzAZZ8o3dTalpkEBAYSIPAGuQ+swdWRGGgEMIVJoeyW36oA==
dependencies:
"@ant-design-vue/use" "^0.0.1-0"
"@ant-design/icons-vue" "^6.0.0"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment