diff --git a/components/shell/__docs__/demo/complicated/index.tsx b/components/shell/__docs__/demo/complicated/index.tsx index 480f4f94d5..ca4dfc8a93 100644 --- a/components/shell/__docs__/demo/complicated/index.tsx +++ b/components/shell/__docs__/demo/complicated/index.tsx @@ -1,16 +1,17 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Search, Icon, Nav, Shell, Radio } from '@alifd/next'; +import type { ShellProps } from '@alifd/next/types/shell'; +import type { GroupProps } from '@alifd/next/types/radio'; const { Item } = Nav; -type deviceType = 'tablet' | 'desktop' | 'phone'; class App extends React.Component { - state: { device: deviceType; navcollapse: boolean } = { + state: { device: ShellProps['device']; navcollapse: boolean } = { device: 'desktop', navcollapse: false, }; - onChange = (device: deviceType) => { + onChange: GroupProps['onChange'] = device => { this.setState({ device, }); diff --git a/components/shell/__docs__/demo/header-global-local/index.tsx b/components/shell/__docs__/demo/header-global-local/index.tsx index de52c7c7b1..77d95ab9a8 100644 --- a/components/shell/__docs__/demo/header-global-local/index.tsx +++ b/components/shell/__docs__/demo/header-global-local/index.tsx @@ -1,15 +1,16 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Search, Nav, Shell, Radio } from '@alifd/next'; +import type { ShellProps } from '@alifd/next/types/shell'; +import type { GroupProps } from '@alifd/next/types/radio'; const { Item } = Nav; -type deviceType = 'tablet' | 'desktop' | 'phone'; class App extends React.Component { - state: { device: deviceType } = { + state: { device: ShellProps['device'] } = { device: 'desktop', }; - onChange = (device: deviceType) => { + onChange: GroupProps['onChange'] = device => { this.setState({ device, }); diff --git a/components/shell/__docs__/demo/header-global/index.tsx b/components/shell/__docs__/demo/header-global/index.tsx index ffdfcbafb0..954637dffb 100644 --- a/components/shell/__docs__/demo/header-global/index.tsx +++ b/components/shell/__docs__/demo/header-global/index.tsx @@ -1,14 +1,14 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Search, Nav, Shell, Radio } from '@alifd/next'; - -type deviceType = 'tablet' | 'desktop' | 'phone'; +import type { ShellProps } from '@alifd/next/types/shell'; +import type { GroupProps } from '@alifd/next/types/radio'; class App extends React.Component { - state: { device: deviceType } = { + state: { device: ShellProps['device'] } = { device: 'desktop', }; - onChange = (device: deviceType) => { + onChange: GroupProps['onChange'] = device => { this.setState({ device, }); diff --git a/components/shell/__docs__/index.en-us.md b/components/shell/__docs__/index.en-us.md index c750958263..e790536cd8 100644 --- a/components/shell/__docs__/index.en-us.md +++ b/components/shell/__docs__/index.en-us.md @@ -44,11 +44,11 @@ Shell is the infrastructure framework of the whole application. It embodies the ### Shell -| Param | Description | Type | Default Value | Required | -| ----------- | ------------------------------------------------------------------------------------------------ | -------------------------------- | ------------- | -------- | -| device | Preset screen width, tt determines whether Navigation LocalNavigation Ancillarytake space or not | 'tablet' \| 'desktop' \| 'phone' | 'desktop' | | -| type | Type of Shell | 'light' \| 'dark' \| 'brand' | 'light' | | -| fixedHeader | Fixed header or not. Doesn't work under IE11 | boolean | false | | +| Param | Description | Type | Default Value | Required | +| ----------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------- | ------------- | -------- | +| device | Preset screen width, which determines whether `Navigation` `LocalNavigation` `Ancillary` take space or not | 'tablet' \| 'desktop' \| 'phone' | 'desktop' | | +| type | Type of Shell | 'light' \| 'dark' \| 'brand' | 'light' | | +| fixedHeader | Fixed header or not. Doesn't work under IE11 | boolean | false | | ### Shell.Navigation diff --git a/components/shell/__docs__/index.md b/components/shell/__docs__/index.md index 2a043a95f1..35f8570de2 100644 --- a/components/shell/__docs__/index.md +++ b/components/shell/__docs__/index.md @@ -23,11 +23,11 @@ ### Shell -| 参数 | 说明 | 类型 | 默认值 | 是否必填 | -| ----------- | ---------------------------------------------------------------------- | -------------------------------- | --------- | -------- | -| device | 预设屏幕宽度,会影响Navigation LocalNavigation Ancillary等是否占据空间 | 'tablet' \| 'desktop' \| 'phone' | 'desktop' | | -| type | 样式类型,分浅色主题、深色主题、主题色主题 | 'light' \| 'dark' \| 'brand' | 'light' | | -| fixedHeader | 是否固定Header,采用sticky布局,不支持 IE11 | boolean | false | | +| 参数 | 说明 | 类型 | 默认值 | 是否必填 | +| ----------- | -------------------------------------------------------------------------------- | -------------------------------- | --------- | -------- | +| device | 预设屏幕宽度,会影响 `Navigation`、`LocalNavigation`、`Ancillary` 等是否占据空间 | 'tablet' \| 'desktop' \| 'phone' | 'desktop' | | +| type | 样式类型,分浅色主题、深色主题、主题色主题 | 'light' \| 'dark' \| 'brand' | 'light' | | +| fixedHeader | 是否固定Header,采用sticky布局,不支持 IE11 | boolean | false | | ### Shell.Navigation @@ -49,11 +49,11 @@ ### Shell.ToolDock -| 参数 | 说明 | 类型 | 默认值 | 是否必填 | -| ---------------- | -------------------------------------------- | ---------------------------- | --------- | -------- | -| collapse | 是否折叠(完全收起) | boolean | false | | -| onCollapseChange | 默认按钮触发的展开收起状态 | (collapse?: boolean) => void | func.noop | | -| fixed | 是否固定,且需要在在 Shell fixedHeader时生效 | boolean | false | | +| 参数 | 说明 | 类型 | 默认值 | 是否必填 | +| ---------------- | --------------------------------------------- | ---------------------------- | --------- | -------- | +| collapse | 是否折叠(完全收起) | boolean | false | | +| onCollapseChange | 默认按钮触发的展开收起状态 | (collapse?: boolean) => void | func.noop | | +| fixed | 是否固定,且需要在在 Shell fixedHeader 时生效 | boolean | false | | ### Shell.Ancillary diff --git a/components/shell/__docs__/theme/index.tsx b/components/shell/__docs__/theme/index.tsx index f4c97c5086..67c9790d2f 100644 --- a/components/shell/__docs__/theme/index.tsx +++ b/components/shell/__docs__/theme/index.tsx @@ -89,11 +89,9 @@ interface FunctionDemoProps { } interface FunctionDemoState { - // demoFunction: DemoProps['demoFunction'] demoFunction: ShellThemeProps['demoFunction']; } -/* eslint-disable */ const i18nMap = { 'zh-cn': { shell: '布局框架', @@ -110,28 +108,23 @@ const i18nMap = { }; class RenderShell extends React.Component { render() { - const { type, i18n, demoFunction } = this.props; + const { type, demoFunction } = this.props; const device = demoFunction.device.value; const globalDir = demoFunction.navigation.value; - let globalNavType = demoFunction.navigationType.value, - // localNavType = demoFunction.localNavType.value, - // globalHozNavType = 'normal', - localNavType: 'normal' | 'primary' | 'secondary' | 'line' = 'normal', - logoStyle = {}, + const globalNavType = demoFunction.navigationType.value, + localNavType = demoFunction.localNavType?.value || 'normal'; + let logoStyle = {}, shellStyle = {}; switch (type) { case 'light': logoStyle = { width: 32, height: 32, background: '#000', opacity: '0.04' }; - // globalHozNavType = 'normal'; break; case 'dark': logoStyle = { width: 32, height: 32, background: '#FFF', opacity: '0.2' }; - // globalHozNavType = globalDir === 'hoz' ? 'primary' : 'normal'; break; case 'brand': logoStyle = { width: 32, height: 32, background: '#000', opacity: '0.04' }; - // globalHozNavType = globalDir === 'hoz' ? 'secondary' : 'normal'; break; default: break; @@ -226,16 +219,12 @@ class RenderShell extends React.Component { ) : null} - {demoFunction.appbar.value === 'true' ? ( - - ) : null} + {demoFunction.appbar.value === 'true' ? : null}
- {demoFunction.ancillary.value === 'true' ? ( - - ) : null} + {demoFunction.ancillary.value === 'true' ? : null} {demoFunction.tooldock.value === 'true' ? ( @@ -377,23 +366,28 @@ class FunctionDemo extends React.Component }, ], }, - // 'localNavType': { - // label: 'Local Nav Type', - // value: 'normal', - // enum: [{ - // label: 'normal', - // value: 'normal' - // }, { - // label: 'primary', - // value: 'primary' - // }, { - // label: 'secondary', - // value: 'secondary' - // }, { - // label: 'line', - // value: 'line' - // }] - // }, + localNavType: { + label: 'Local Nav Type', + value: 'normal', + enum: [ + { + label: 'normal', + value: 'normal', + }, + { + label: 'primary', + value: 'primary', + }, + { + label: 'secondary', + value: 'secondary', + }, + { + label: 'line', + value: 'line', + }, + ], + }, appbar: { label: 'Appbar', value: 'false', @@ -477,7 +471,7 @@ class FunctionDemo extends React.Component } function render(i18n: FunctionDemoProps['locale'], lang: string) { - return ReactDOM.render( + ReactDOM.render( // @ts-expect-error ConfigProvider 不存在 lang 属性
diff --git a/components/shell/__tests__/a11y-spec.tsx b/components/shell/__tests__/a11y-spec.tsx index c65d666cf8..9030c61602 100644 --- a/components/shell/__tests__/a11y-spec.tsx +++ b/components/shell/__tests__/a11y-spec.tsx @@ -6,7 +6,6 @@ import '../../search/style'; import './index.scss'; import { testReact } from '../../util/__tests__/a11y/validate'; -/* eslint-disable no-undef, react/jsx-filename-extension */ describe('Shell A11y', () => { it('should not have any violations', async () => { await testReact( diff --git a/components/shell/__tests__/index-spec.tsx b/components/shell/__tests__/index-spec.tsx index 4ab2b85be7..20e4aa5209 100644 --- a/components/shell/__tests__/index-spec.tsx +++ b/components/shell/__tests__/index-spec.tsx @@ -1,11 +1,7 @@ import React from 'react'; -// import ReactDOM from 'react-dom'; -// import Enzyme, { mount } from 'enzyme'; -// import Adapter from 'enzyme-adapter-react-16'; -// import assert from 'power-assert'; -import Shell from '../index'; +import Shell, { type ShellProps } from '../index'; import Search from '../../search/index'; -import Radio from '../../radio/index'; +import Radio, { type GroupProps } from '../../radio/index'; import Icon from '../../icon/index'; import Nav from '../../nav/index'; import '../style'; @@ -15,9 +11,7 @@ import '../../icon/style'; import './index.scss'; const { Item } = Nav; -// Enzyme.configure({ adapter: new Adapter() }); -/* eslint-disable */ describe('Shell', () => { describe('render', () => { it('default should work', () => { @@ -65,47 +59,42 @@ describe('Shell', () => { cy.contains('@ 2019 Alibaba Piecework 版权所有').should('exist'); }); - it('default collapse should work', async () => { - type Device = 'phone' | 'tablet' | 'desktop'; + it('default collapse should work', () => { interface AppProps { onCollapseNavigationChange: (visible: boolean) => void; onCollapseLocalNavChange: (visible: boolean) => void; onCollapseAncilleryChange: (visible: boolean) => void; } + interface AppState { + device: ShellProps['device']; + navcollapse: boolean; + } + class App extends React.Component { - state: { - device: Device; - navcollapse: boolean; - } = { + state: AppState = { device: 'desktop', navcollapse: false, }; - onChange = (device: Device) => { - this.setState({ - device, - }); + + onChange: GroupProps['onChange'] = device => { + this.setState({ device }); }; btnClick = () => { - this.setState({ - navcollapse: !this.state.navcollapse, - }); + this.setState({ navcollapse: !this.state.navcollapse }); + const { onCollapseNavigationChange } = this.props; + onCollapseNavigationChange(!this.state.navcollapse); }; onCollapseChange = (visible: boolean) => { console.log('onCollapseChange:', visible); const { onCollapseNavigationChange } = this.props; - - this.setState({ - navcollapse: visible, - }); - + this.setState({ navcollapse: visible }); onCollapseNavigationChange(visible); }; render() { const { onCollapseLocalNavChange, onCollapseAncilleryChange } = this.props; - // eslint-disable-next-line react/jsx-filename-extension return (
{ ); } } - let navCollapseCount = 0; - let localCollapseCount = 0; - let anciCollapseCount = 0; - let navCollapse = false; - let localCollapse = false; - let anciCollapse = false; + + // 使用 Cypress spy 来模拟回调函数 + const navCollapseSpy = cy.spy().as('navCollapseSpy'); + const localCollapseSpy = cy.spy().as('localCollapseSpy'); + const ancillaryCollapseSpy = cy.spy().as('ancillaryCollapseSpy'); + cy.mount( { - navCollapseCount++; - navCollapse = visible; - console.log('global nav:', navCollapseCount, navCollapse); - }} - onCollapseLocalNavChange={visible => { - localCollapseCount++; - localCollapse = visible; - console.log('local nav:', localCollapseCount, localCollapse); - }} - onCollapseAncilleryChange={visible => { - anciCollapseCount++; - anciCollapse = visible; - console.log('ancillery nav:', anciCollapseCount, anciCollapse); - }} + onCollapseNavigationChange={navCollapseSpy} + onCollapseLocalNavChange={localCollapseSpy} + onCollapseAncilleryChange={ancillaryCollapseSpy} /> ); + cy.get('.local-nav-trigger').click(); cy.get('.ancillary-trigger').click(); cy.get('.nav-trigger').click(); cy.get('.nav-trigger').click(); - await new Promise(resolve => setTimeout(resolve, 500)); - expect(navCollapse).to.be.false; - expect(localCollapse).to.be.true; - expect(anciCollapse).to.be.true; - expect(navCollapseCount).to.be.equal(2); - expect(localCollapseCount).to.be.equal(1); - expect(anciCollapseCount).to.be.equal(1); - cy.get('#custom-nav-trigger').click(); - cy.get('.next-shell-navigation.next-shell-collapse').should('not.exist'); + + cy.get('@navCollapseSpy').should('have.property', 'callCount', 3); + cy.get('@localCollapseSpy').should('have.property', 'callCount', 1); + cy.get('@ancillaryCollapseSpy').should('have.property', 'callCount', 1); + + cy.get('@navCollapseSpy').its('lastCall').its('args').should('deep.equal', [true]); + cy.get('@localCollapseSpy').its('lastCall').its('args').should('deep.equal', [true]); + cy.get('@ancillaryCollapseSpy') + .its('lastCall') + .its('args') + .should('deep.equal', [true]); }); it('should support no header', () => { @@ -347,7 +327,7 @@ describe('Shell', () => { }); it('only tooldock, show header only in phone', () => { - const testDevice = (device: 'phone' | 'tablet' | 'desktop', shouldExist: boolean) => { + const testDevice = (device: ShellProps['device'], shouldExist: boolean) => { // 创建一个通用的测试用例 cy.mount( { // 获取 .next-aside-navigation 元素并检查其样式 cy.get('.next-aside-navigation').as('navigationElement'); // 检查宽度是否为零 - cy.get('@navigationElement').invoke('width').should('eq', 0); + cy.get('@navigationElement').should('have.css', 'width', '0px'); // 检查 overflow 样式是否为 hidden - cy.get('@navigationElement').invoke('css', 'overflow').should('eq', 'hidden'); + cy.get('@navigationElement').should('have.css', 'overflow', 'hidden'); }); }); }); diff --git a/components/shell/base.tsx b/components/shell/base.tsx index e52e26182b..fa99324cc0 100644 --- a/components/shell/base.tsx +++ b/components/shell/base.tsx @@ -2,15 +2,11 @@ import React, { Component } from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; import ConfigProvider from '../config-provider'; -import type { ShellBaseProps } from './types'; - -interface BaseShellProps extends Omit { - children?: Array | React.ReactElement; -} +import type { BaseProps } from './types'; export default function Base(props: { componentName?: string }) { const { componentName } = props; - class Shell extends Component { + class Shell extends Component { static displayName = componentName; static _typeMark = `Shell_${componentName}`; @@ -65,7 +61,7 @@ export default function Base(props: { componentName?: string }) { ...others } = this.props; - const Tag = component; + const Tag = component as React.ElementType; const cls = classnames({ [`${prefix}shell-${componentName!.toLowerCase()}`]: true, @@ -86,7 +82,6 @@ export default function Base(props: { componentName?: string }) { } return ( - // @ts-expect-error Tag 不具有构造签名 {newChildren} diff --git a/components/shell/index.tsx b/components/shell/index.tsx index 535a54d6ed..6938a8bb0b 100644 --- a/components/shell/index.tsx +++ b/components/shell/index.tsx @@ -1,51 +1,40 @@ +import { assignSubComponent } from '../util/component'; import ShellBase from './shell'; import Base from './base'; import ConfigProvider from '../config-provider'; +import type { + ShellProps, + ShellNavigationProps, + ShellLocalNavigationProps, + ShellToolDockProps, + ShellAncillaryProps, +} from './types'; -const Shell = ShellBase({ - componentName: 'Shell', -}); - -[ - 'Branding', - 'Navigation', - 'Action', - 'MultiTask', - 'LocalNavigation', - 'AppBar', - 'Content', - 'Footer', - 'Ancillary', - 'ToolDock', - 'ToolDockItem', -].forEach( - ( - key: - | 'Branding' - | 'Navigation' - | 'Action' - | 'MultiTask' - | 'LocalNavigation' - | 'AppBar' - | 'Content' - | 'Footer' - | 'Ancillary' - | 'ToolDock' - | 'ToolDockItem' - ) => { - Shell[key] = Base({ - componentName: key, - }); - } -); +const Shell = ShellBase({ componentName: 'Shell' }); -Shell.Page = ConfigProvider.config( - ShellBase({ - componentName: 'Page', - }) -); +const WithSubShell = assignSubComponent(Shell, { + Branding: Base({ componentName: 'Branding' }), + Navigation: Base({ componentName: 'Navigation' }), + Action: Base({ componentName: 'Action' }), + MultiTask: Base({ componentName: 'MultiTask' }), + LocalNavigation: Base({ componentName: 'LocalNavigation' }), + AppBar: Base({ componentName: 'AppBar' }), + Content: Base({ componentName: 'Content' }), + Footer: Base({ componentName: 'Footer' }), + Ancillary: Base({ componentName: 'Ancillary' }), + ToolDock: Base({ componentName: 'ToolDock' }), + ToolDockItem: Base({ componentName: 'ToolDockItem' }), + Page: ConfigProvider.config(ShellBase({ componentName: 'Page' })), +}); -export default ConfigProvider.config(Shell, { +export type { + ShellProps, + ShellNavigationProps, + ShellLocalNavigationProps, + ShellToolDockProps, + ShellAncillaryProps, +}; +export default ConfigProvider.config(WithSubShell, { transform: (props, deprecated) => { if ('Component' in props) { deprecated('Component', 'component', 'Shell'); diff --git a/components/shell/shell.tsx b/components/shell/shell.tsx index 2e23144a29..66d36b8260 100644 --- a/components/shell/shell.tsx +++ b/components/shell/shell.tsx @@ -1,53 +1,26 @@ -import React, { - Component, - type MouseEvent, - type KeyboardEvent, - type ComponentType, - type HTMLAttributes, - type ReactElement, -} from 'react'; +import React, { Component, type MouseEvent, type KeyboardEvent, type ReactElement } from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; import { polyfill } from 'react-lifecycles-compat'; import ConfigProvider from '../config-provider'; import Affix from '../affix'; import Icon from '../icon'; -import { KEYCODE, dom, env, type CommonProps } from '../util'; +import { KEYCODE, dom, env } from '../util'; import { isBoolean, getCollapseMap } from './util'; import type { ShellBaseProps, ShellState, - LayoutItem, ShellNavigationProps, - ShellLocalNavigationProps, - ShellAncillaryProps, - ShellToolDockProps, + CollapseMap, + LayoutProps, + ChildElement, } from './types'; - -type CollapseKey = 'Navigation' | 'LocalNavigation' | 'Ancillary' | 'ToolDock'; -type CommonAttributes = HTMLAttributes & CommonProps; -type LayoutItemAttribute = Omit; -type LayoutProps = LayoutItemAttribute & { - header?: LayoutItem; -}; +import type { CustomCSSStyle } from '../util/dom'; /** Shell */ export default function ShellBase(props: { componentName?: string }) { const { componentName } = props; class Shell extends Component { - static Branding: ComponentType; - static Action: ComponentType; - static MultiTask: ComponentType; - static AppBar: ComponentType; - static Content: ComponentType; - static Footer: ComponentType; - static ToolDockItem: ComponentType; - static Page: ComponentType; - static Navigation: ComponentType; - static LocalNavigation: ComponentType; - static Ancillary: ComponentType; - static ToolDock: ComponentType; - static displayName = componentName; static _typeMark = componentName; @@ -67,14 +40,14 @@ export default function ShellBase(props: { componentName?: string }) { fixedHeader: false, }; - public layout: LayoutProps; - public headerRef: HTMLDivElement; - public navigationFixed: boolean; - public toolDockFixed: boolean; - public navRef: HTMLDivElement; - public localNavRef: HTMLDivElement; - public submainRef: HTMLDivElement; - public toolDockRef: HTMLDivElement; + layout: LayoutProps; + headerRef: HTMLDivElement; + navigationFixed: boolean; + toolDockFixed: boolean; + navRef: HTMLDivElement; + localNavRef: HTMLDivElement; + submainRef: HTMLDivElement; + toolDockRef: HTMLDivElement; constructor(props: ShellBaseProps) { super(props); @@ -116,8 +89,8 @@ export default function ShellBase(props: { componentName?: string }) { const deviceMapBefore = getCollapseMap(prevProps.device); const deviceMapAfter = getCollapseMap(this.props.device); - Object.keys(deviceMapAfter).forEach((block: CollapseKey) => { - const { props } = (this.layout[block] as ReactElement) || {}; + Object.keys(deviceMapAfter).forEach((block: keyof CollapseMap) => { + const { props } = this.layout[block] || {}; if (deviceMapBefore[block] !== deviceMapAfter[block]) { if (props && typeof props.onCollapseChange === 'function') { props.onCollapseChange(deviceMapAfter[block]); @@ -145,7 +118,7 @@ export default function ShellBase(props: { componentName?: string }) { } if (this.navigationFixed) { - const style: { marginLeft?: string | number } = {}; + const style: Partial = {}; style.marginLeft = dom.getStyle(this.navRef, 'width'); dom.addClass(this.navRef, 'fixed'); headerHeight && dom.setStyle(this.navRef, { top: headerHeight }); @@ -153,7 +126,7 @@ export default function ShellBase(props: { componentName?: string }) { } if (this.toolDockFixed) { - const style: { marginRight?: string | number } = {}; + const style: Partial = {}; style.marginRight = dom.getStyle(this.toolDockRef, 'width'); dom.addClass(this.toolDockRef, 'fixed'); headerHeight && dom.setStyle(this.toolDockRef, { top: headerHeight }); @@ -161,7 +134,7 @@ export default function ShellBase(props: { componentName?: string }) { } }; - setChildCollapse = (child: ReactElement, mark: CollapseKey) => { + setChildCollapse = (child: ReactElement, mark: keyof CollapseMap) => { const { device, collapseMap, controll } = this.state; const { collapse } = child.props; const deviceMap = getCollapseMap(device); @@ -181,7 +154,7 @@ export default function ShellBase(props: { componentName?: string }) { }; toggleAside = ( - mark: CollapseKey, + mark: keyof CollapseMap, props: { onCollapseChange?: (collapse?: boolean) => void; collapse?: boolean; @@ -213,10 +186,7 @@ export default function ShellBase(props: { componentName?: string }) { .filter( (child: ReactElement) => child && - (child.type as unknown as { _typeMark: string })._typeMark.replace( - 'Shell_', - '' - ) === mark && + (child as ChildElement).type._typeMark.replace('Shell_', '') === mark && child.props.direction !== 'hoz' ) .pop(); @@ -225,21 +195,12 @@ export default function ShellBase(props: { componentName?: string }) { .filter( child => child && - (child.type as unknown as { _typeMark: string })._typeMark.replace( - 'Shell_', - '' - ) === mark + (child as ChildElement).type._typeMark.replace('Shell_', '') === mark ) .pop(); } - const { - triggerProps = {}, - }: { - triggerProps?: { - onClick?: (e: KeyboardEvent | MouseEvent, collapsed: boolean) => void; - }; - } = com!.props; + const { triggerProps = {} } = com!.props; if (typeof triggerProps.onClick === 'function') { triggerProps.onClick(e, this.state.collapseMap[mark]); @@ -248,7 +209,7 @@ export default function ShellBase(props: { componentName?: string }) { toggleNavigation = (e: KeyboardEvent | MouseEvent) => { const mark = 'Navigation'; - const { props } = this.layout[mark] as ReactElement; + const { props } = this.layout[mark]!; if ('keyCode' in e && e.keyCode !== KEYCODE.ENTER) { return; @@ -259,7 +220,7 @@ export default function ShellBase(props: { componentName?: string }) { toggleLocalNavigation = (e: KeyboardEvent | MouseEvent) => { const mark = 'LocalNavigation'; - const { props } = this.layout[mark] as ReactElement; + const { props } = this.layout[mark]!; if ('keyCode' in e && e.keyCode !== KEYCODE.ENTER) { return; @@ -270,7 +231,7 @@ export default function ShellBase(props: { componentName?: string }) { toggleAncillary = (e: KeyboardEvent | MouseEvent) => { const mark = 'Ancillary'; - const { props } = this.layout[mark] as ReactElement; + const { props } = this.layout[mark]!; if ('keyCode' in e && e.keyCode !== KEYCODE.ENTER) { return; @@ -281,7 +242,7 @@ export default function ShellBase(props: { componentName?: string }) { toggleToolDock = (e: KeyboardEvent | MouseEvent) => { const mark = 'ToolDock'; - const { props } = this.layout[mark] as ReactElement; + const { props } = this.layout[mark]!; if ('keyCode' in e && e.keyCode !== KEYCODE.ENTER) { return; @@ -322,10 +283,7 @@ export default function ShellBase(props: { componentName?: string }) { React.Children.map(children, child => { if (child && typeof child.type === 'function') { - const mark = (child.type as unknown as { _typeMark: string })._typeMark.replace( - 'Shell_', - '' - ); + const mark = (child as ChildElement).type._typeMark.replace('Shell_', ''); switch (mark) { case 'Branding': case 'Action': @@ -336,12 +294,14 @@ export default function ShellBase(props: { componentName?: string }) { break; case 'LocalNavigation': if (!layout[mark]) { + // @ts-expect-error 不应该是[], LocalNavigation 应该是 ReactElement layout[mark] = []; } layout[mark] = this.setChildCollapse(child, mark); break; case 'Ancillary': if (!layout[mark]) { + // @ts-expect-error 不应该是[], Ancillary 应该是 ReactElement layout[mark] = []; } @@ -351,18 +311,20 @@ export default function ShellBase(props: { componentName?: string }) { hasToolDock = true; if (!layout[mark]) { + // @ts-expect-error 不应该是[], ToolDock 应该是 ReactElement layout[mark] = []; } this.toolDockFixed = child.props.fixed!; - layout[mark] = this.setChildCollapse(child, mark); + const childT = this.setChildCollapse(child, mark); + layout[mark] = childT; break; case 'AppBar': case 'Content': case 'Footer': layout.content || (layout.content = []); - (layout.content as Array).push(child); + layout.content.push(child); break; case 'Page': layout.page || (layout.page = []); @@ -373,11 +335,12 @@ export default function ShellBase(props: { componentName?: string }) { layout.header![mark] = child; } else { if (!layout[mark]) { + // @ts-expect-error 不应该是[], Navigation 应该是 ReactElement layout[mark] = []; } needNavigationTrigger = true; - this.navigationFixed = child.props.fixed!; + this.navigationFixed = child.props.fixed; const childN = this.setChildCollapse(child, mark); layout[mark] = childN; } @@ -415,8 +378,7 @@ export default function ShellBase(props: { componentName?: string }) { const navigationCls = classnames({ [`${prefix}aside-navigation`]: true, - [`${prefix}shell-collapse`]: - layout.Navigation && (layout.Navigation as ReactElement)!.props.collapse, + [`${prefix}shell-collapse`]: layout.Navigation && layout.Navigation.props.collapse, }); if (hasToolDock) { @@ -427,11 +389,11 @@ export default function ShellBase(props: { componentName?: string }) { // 如果存在垂直模式的 Navigation, 则需要在 Branding 上出现 trigger if (needNavigationTrigger) { - const branding = layout.header.Branding as ReactElement; - const collapse = (layout.Navigation as ReactElement)!.props.collapse; - let trigger = (layout.Navigation as ReactElement)!.props.trigger; + const branding = layout.header.Branding; + const collapse = layout.Navigation!.props.collapse; + let trigger = layout.Navigation!.props.trigger; - if ('trigger' in (layout.Navigation as ReactElement)!.props) { + if ('trigger' in layout.Navigation!.props) { trigger = (trigger && React.cloneElement(trigger, { @@ -475,11 +437,11 @@ export default function ShellBase(props: { componentName?: string }) { // 如果存在 toolDock, 则需要在 Action 上出现 trigger if (needDockTrigger) { - const action = layout.header.Action as ReactElement; - const collapse = (layout.ToolDock as ReactElement).props.collapse; - let trigger = (layout.ToolDock as ReactElement).props.trigger; + const action = layout.header.Action; + const collapse = layout.ToolDock!.props.collapse; + let trigger = layout.ToolDock!.props.trigger; - if ('trigger' in (layout.ToolDock as ReactElement).props) { + if ('trigger' in layout.ToolDock!.props) { trigger = (trigger && React.cloneElement(trigger, { @@ -536,10 +498,10 @@ export default function ShellBase(props: { componentName?: string }) { // 按照dom结构,innerArr 包括 LocalNavigation content Ancillary if (layout.LocalNavigation) { - const collapse = (layout.LocalNavigation as ReactElement).props.collapse; - let trigger = (layout.LocalNavigation as ReactElement).props.trigger; + const collapse = layout.LocalNavigation.props.collapse; + let trigger = layout.LocalNavigation.props.trigger; - if ('trigger' in (layout.LocalNavigation as ReactElement).props) { + if ('trigger' in layout.LocalNavigation.props) { trigger = (trigger && React.cloneElement(trigger, { @@ -577,9 +539,9 @@ export default function ShellBase(props: { componentName?: string }) { innerArr.push(