You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

338 lines
10 KiB
TypeScript

1 year ago
import './index.less'
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue'
import { VueNodeOrRenderPropType, WithFalseVueNodeOrRenderPropType } from '#/types'
import { Layout, Menu, MenuItem } from 'ant-design-vue'
import BaseMenu, { baseMenuProps } from './BaseMenu'
import type { VueNodeOrRender } from '#/types'
import type { WithFalse } from '../../types'
import type { CSSProperties, ExtractPropTypes, FunctionalComponent, PropType, Slots } from 'vue'
import type { SiderProps } from 'ant-design-vue'
import type {
CollapsedButtonRender,
MenuContentRender,
MenuExtraReander,
MenuFootRender,
MenuHeaderRender
} from '../../renderTypes'
import { getVueNode, getRender } from '../../utils'
export const siderMenuProps = () => ({
...baseMenuProps(),
logo: {
type: VueNodeOrRenderPropType as PropType<VueNodeOrRender>,
default: undefined
},
siderWidth: { type: Number, default: 208 },
/**
* logo title
*
* @example logo : menuHeaderRender={(logo,title)=> title}
* @example title : menuHeaderRender={(logo,title)=> logo}
* @example title, logo menuHeaderRender={(logo,title,props)=> props.collapsed ? logo : title}
* @example : menuHeaderRender={false}
*/
menuHeaderRender: {
type: WithFalseVueNodeOrRenderPropType as PropType<WithFalse<MenuHeaderRender>>,
default: undefined
},
/**
*
*
* @example menuFooterRender={()=><a href="https://pro.ant.design">pro.ant.design</a>}
* @example dom menuFooterRender={()=>collapsed? null :<a href="https://pro.ant.design">pro.ant.design</a>}
*/
menuFooterRender: {
type: WithFalseVueNodeOrRenderPropType as PropType<WithFalse<MenuFootRender>>,
default: undefined
},
/**
* ,dom
*
* @example menuContentRender={(props,defaultDom)=><div style={{backgroundColor:"red"}}>{defaultDom}</div>}
* @example menuContentRender={(props)=> return <div></div>}
*/
menuContentRender: {
type: WithFalseVueNodeOrRenderPropType as PropType<WithFalse<MenuContentRender>>,
default: undefined
},
/**
* title logo
*
* @example menuExtraRender={()=>(<Search placeholder="请输入" />)}
* @example dom menuExtraRender={()=>collapsed? null : <Search placeholder="请输入" />}
*/
menuExtraRender: {
type: WithFalseVueNodeOrRenderPropType as PropType<WithFalse<MenuExtraReander>>,
default: false
},
/**
*
*
* @example 使 collapsedButtonRender={(collapsed)=>collapsed?"展开":"收起"})}
* @example 使icon collapsedButtonRender={(collapsed)=>collapsed?<MenuUnfoldOutlined />:<MenuFoldOutlined />}
* @example collapsedButtonRender={false}
*/
collapsedButtonRender: {
type: WithFalseVueNodeOrRenderPropType as PropType<WithFalse<CollapsedButtonRender>>,
default: undefined
},
/**
* false
*
* @example breakpoint={false}
* @example breakpoint={'xs'}
*/
breakpoint: {
type: [String, Boolean] as PropType<SiderProps['breakpoint'] | false>,
default: 'lg'
},
/**
* logo title
*
* @example onMenuHeaderClick={()=>{ history.push('/') }}
*/
onMenuHeaderClick: {
type: Function as PropType<(e: MouseEvent) => void>,
default: undefined
},
/**
*
*
* @example links={[<a href="ant.design"> 访 </a>,<a href="help.ant.design"> </a>]}
*/
links: {
type: VueNodeOrRenderPropType as PropType<VueNodeOrRender>,
default: undefined
},
// TODO 这个放到事件里面
onOpenChange: {
type: Function as PropType<(openKeys: WithFalse<string[]>) => void>,
default: undefined
},
getContainer: { type: Boolean, default: false },
logoStyle: {
type: Object as PropType<CSSProperties>,
default: () => undefined
},
hide: { type: Boolean, default: undefined }
})
export type SiderMenuProps = Partial<ExtractPropTypes<ReturnType<typeof siderMenuProps>>>
export const privateSiderMenuProps = () => ({
matchMenuKeys: Array as PropType<string[]>
})
export type PrivateSiderMenuProps = Partial<
ExtractPropTypes<ReturnType<typeof privateSiderMenuProps>>
>
export const defaultRenderLogo = (
logo: VueNodeOrRender | (() => VueNodeOrRender)
): VueNodeOrRender => {
if (typeof logo === 'string') {
return <img src={logo} alt="logo" />
}
if (typeof logo === 'function') {
return logo()
}
return logo
}
export const defaultRenderLogoAndTitle = (
props: SiderMenuProps,
slots: Slots,
renderKey = 'menuHeaderRender'
): VueNodeOrRender => {
if (props.layout === 'mix' && renderKey === 'menuHeaderRender') {
return null
}
const renderFunction = getRender<MenuHeaderRender>(props, slots, renderKey)
if (renderFunction === false) {
return null
}
const logRender = getRender<VueNodeOrRender>(props, slots, 'logo')
const logoDom = defaultRenderLogo(logRender)
const titleRender = getVueNode(props.title, slots.title)
const titleDom = <h1>{titleRender ?? 'Ball Cat'}</h1>
if (renderFunction) {
// when collapsed, no render title
return renderFunction(props, logoDom, props.collapsed ? null : titleDom)
}
return (
<a>
{logoDom}
{props.collapsed ? null : titleDom}
</a>
)
}
export const defaultRenderCollapsedButton = (collapsed?: boolean) =>
collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />
function getCollapsedButtonRender(props: SiderMenuProps & PrivateSiderMenuProps, slots: Slots) {
if (props.collapsedButtonRender == false) {
return false
}
const render = getRender<CollapsedButtonRender>(props, slots, 'collapsedButtonRender')
return render || defaultRenderCollapsedButton
}
const SiderMenu: FunctionalComponent<SiderMenuProps & PrivateSiderMenuProps> = (
props,
{ slots, attrs }
) => {
const baseClassName = `${props.prefixCls}-sider`
const siderClassName = {
[`${baseClassName}`]: true,
[`${baseClassName}-fixed`]: props.fixSiderbar,
[`${baseClassName}-layout-${props.layout}`]: props.layout && !props.isMobile,
[`${baseClassName}-light`]: props.theme !== 'dark'
}
const headerDom = defaultRenderLogoAndTitle(props, slots)
// const { flatMenuKeys } = MenuCounter.useContainer()
const flatMenuKeys: string[] = []
const extraDom = props.menuExtraRender && props.menuExtraRender(props)
const menuDom = props.menuContentRender !== false && flatMenuKeys && (
<BaseMenu
{...props}
key="base-menu"
mode="inline"
// @ts-ignore
handleOpenChange={props.onOpenChange}
style={{ width: '100%' }}
class={`${baseClassName}-menu`}
/>
)
const menuRenderDom = props.menuContentRender ? props.menuContentRender(props, menuDom) : menuDom
const collapsedButtonRender = getCollapsedButtonRender(props, slots)
const menuFooterRender = getRender<MenuFootRender>(props, slots, 'menuFooterRender')
return (
<>
{props.fixSiderbar && (
<div
class={`${baseClassName}-fix-place`}
style={{
width: `${props.collapsed ? 48 : props.siderWidth}px`,
overflow: 'hidden',
flex: `0 0 ${props.collapsed ? 48 : props.siderWidth}px`,
maxWidth: `${props.collapsed ? 48 : props.siderWidth}px`,
minWidth: `${props.collapsed ? 48 : props.siderWidth}px`,
transition: `background-color 0.3s, min-width 0.3s, max-width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1)`,
...(attrs.style as CSSProperties)
}}
/>
)}
<Layout.Sider
collapsible
trigger={null}
collapsed={props.collapsed}
breakpoint={props.breakpoint === false ? undefined : props.breakpoint}
onCollapse={collapse => {
if (props.isMobile) return
props.onCollapse?.(collapse)
}}
collapsedWidth={48}
style={{
overflow: 'hidden',
paddingTop:
props.layout === 'mix' && !props.isMobile ? `${props.headerHeight}px` : undefined,
...(attrs.style as CSSProperties)
}}
width={props.siderWidth}
theme={props.theme}
class={siderClassName}
>
{headerDom && (
<div
class={[
`${baseClassName}-logo`,
{
[`${baseClassName}-collapsed`]: props.collapsed
}
]}
onClick={props.layout !== 'mix' ? props.onMenuHeaderClick : undefined}
id="logo"
style={props.logoStyle}
>
{headerDom}
</div>
)}
{extraDom && (
<div class={`${baseClassName}-extra ${!headerDom && `${baseClassName}-extra-no-logo`}`}>
{extraDom}
</div>
)}
<div
style={{
flex: 1,
overflowY: 'auto',
overflowX: 'hidden'
}}
>
{menuRenderDom}
</div>
<div class={`${baseClassName}-links`}>
{collapsedButtonRender !== false && (
<Menu
theme={props.theme}
inlineIndent={16}
class={`${baseClassName}-link-menu`}
selectedKeys={[]}
openKeys={[]}
mode="inline"
>
<MenuItem
key={'collapsed'}
class={`${baseClassName}-collapsed-button`}
title={false}
onClick={() => {
if (props.onCollapse) {
props.onCollapse(!props.collapsed)
}
}}
>
{collapsedButtonRender(props.collapsed)}
</MenuItem>
</Menu>
)}
</div>
{menuFooterRender && (
<div
class={[
`${baseClassName}-footer`,
{ [`${baseClassName}-footer-collapsed`]: !props.collapsed }
]}
>
{menuFooterRender(props)}
</div>
)}
</Layout.Sider>
</>
)
}
export default SiderMenu