diff --git a/components/shell/__docs__/adaptor/index.jsx b/components/shell/__docs__/adaptor/index.jsx deleted file mode 100644 index f619a0e260..0000000000 --- a/components/shell/__docs__/adaptor/index.jsx +++ /dev/null @@ -1,205 +0,0 @@ -import React from 'react'; -import { Types } from '@alifd/adaptor-helper'; -import { Shell, Icon } from '@alifd/next'; - -export default { - name: 'Shell', - shape: ['normal'], - editor: (shape) => { - return { - props: [{ - name: 'level', - type: Types.enum, - default: 'light', - options: { - normal: ['light', 'dark', 'brand'], - }[shape], - },{ - name: 'device', - label: 'Device', - type: Types.enum, - options: ['desktop', 'tablet', 'phone'], - default: 'desktop', - },{ - name: 'branding', - label: 'Branding', - type: Types.bool, - default: true, - },{ - name: 'actions', - label: 'Actions', - type: Types.bool, - default: true, - },{ - name: 'navigation', - label: 'Navigation', - type: Types.enum, - options: ['ver', 'hoz', false], - default: 'ver', - },{ - name: 'localNav', - label: 'LocalNav', - type: Types.bool, - default: true, - },{ - name: 'appbar', - label: 'Appbar', - type: Types.bool, - default: true, - },{ - name: 'footer', - label: 'Footer', - type: Types.bool, - default: true, - },{ - name: 'tooldock', - label: 'Tooldock', - type: Types.bool, - default: true, - },{ - name: 'ancillary', - label: 'Ancillary', - type: Types.bool, - default: true, - }] - }; - }, - adaptor: ({ level, device, branding, actions, localNav, appbar, footer, tooldock, ancillary, navigation, ...others }) => { - let logoStyle = {}, - shellStyle = {}; - - switch(level) { - case 'light': - logoStyle = {width: 32, height: 32, background: '#000', opacity: '0.04'}; - break; - case 'dark': - logoStyle = {width: 32, height: 32, background: '#FFF', opacity: '0.2'}; - break; - case 'brand': - logoStyle = {width: 32, height: 32, background: '#000', opacity: '0.04'}; - break; - default: - break; - } - - switch(device) { - case 'phone': - shellStyle = {height: 500, width: 480, border: '1px solid #eee'}; - break; - case 'tablet': - shellStyle = {height: 500, width: 768, border: '1px solid #eee'}; - break; - case 'desktop': - shellStyle = {height: 500, width: 1000, border: '1px solid #eee'}; - break; - default: - break; - } - - return ( - - { - branding - ? -
- App Name -
- : null - } - - { - !navigation - ? - - - : null - } - { - actions - ? - - - 用户头像 - Name - - : null - } - { - localNav - ? - - - : null - } - { - appbar - ? - - - : null - } - -
-
- - { - ancillary - ? - - : null - } - { - tooldock - ? - - - - - - - - - - - : null - } - { - footer - ? - Alibaba Fusion - @ 2019 Alibaba Piecework 版权所有 - - : null - } -
- ); - } -}; diff --git a/components/shell/__docs__/adaptor/index.tsx b/components/shell/__docs__/adaptor/index.tsx new file mode 100644 index 0000000000..ae06bac171 --- /dev/null +++ b/components/shell/__docs__/adaptor/index.tsx @@ -0,0 +1,231 @@ +import React from 'react'; +import { Types } from '@alifd/adaptor-helper'; +import { Shell, Icon, Nav, Search } from '@alifd/next'; + +interface AdaptorProps { + level: 'light' | 'dark' | 'brand'; + device: 'desktop' | 'tablet' | 'phone'; + branding?: boolean; + actions: boolean; + localNav: boolean; + appbar: boolean; + footer: boolean; + tooldock: boolean; + ancillary: boolean; + navigation: 'ver' | 'hoz'; +} + +export default { + name: 'Shell', + shape: ['normal'], + editor: (shape: string) => { + return { + props: [ + { + name: 'level', + type: Types.enum, + default: 'light', + options: { + normal: ['light', 'dark', 'brand'], + }[shape], + }, + { + name: 'device', + label: 'Device', + type: Types.enum, + options: ['desktop', 'tablet', 'phone'], + default: 'desktop', + }, + { + name: 'branding', + label: 'Branding', + type: Types.bool, + default: true, + }, + { + name: 'actions', + label: 'Actions', + type: Types.bool, + default: true, + }, + { + name: 'navigation', + label: 'Navigation', + type: Types.enum, + options: ['ver', 'hoz', false], + default: 'ver', + }, + { + name: 'localNav', + label: 'LocalNav', + type: Types.bool, + default: true, + }, + { + name: 'appbar', + label: 'Appbar', + type: Types.bool, + default: true, + }, + { + name: 'footer', + label: 'Footer', + type: Types.bool, + default: true, + }, + { + name: 'tooldock', + label: 'Tooldock', + type: Types.bool, + default: true, + }, + { + name: 'ancillary', + label: 'Ancillary', + type: Types.bool, + default: true, + }, + ], + }; + }, + adaptor: ({ + level, + device, + branding, + actions, + localNav, + appbar, + footer, + tooldock, + ancillary, + navigation, + }: AdaptorProps) => { + let logoStyle = {}, + shellStyle = {}; + + switch (level) { + case 'light': + logoStyle = { width: 32, height: 32, background: '#000', opacity: '0.04' }; + break; + case 'dark': + logoStyle = { width: 32, height: 32, background: '#FFF', opacity: '0.2' }; + break; + case 'brand': + logoStyle = { width: 32, height: 32, background: '#000', opacity: '0.04' }; + break; + default: + break; + } + + switch (device) { + case 'phone': + shellStyle = { height: 500, width: 480, border: '1px solid #eee' }; + break; + case 'tablet': + shellStyle = { height: 500, width: 768, border: '1px solid #eee' }; + break; + case 'desktop': + shellStyle = { height: 500, width: 1000, border: '1px solid #eee' }; + break; + default: + break; + } + + return ( + + {branding ? ( + +
+ App Name +
+ ) : null} + + {!navigation ? ( + + + + ) : null} + {actions ? ( + + + + 用户头像 + Name + + ) : null} + {localNav ? ( + + + + ) : null} + {appbar ? : null} + +
+
+ + {ancillary ? : null} + {tooldock ? ( + + + + + + + + + + + + ) : null} + {footer ? ( + + Alibaba Fusion + @ 2019 Alibaba Piecework 版权所有 + + ) : null} +
+ ); + }, +}; diff --git a/components/shell/__docs__/demo/basic/index.tsx b/components/shell/__docs__/demo/basic/index.tsx index 2f74855537..58f01c623f 100644 --- a/components/shell/__docs__/demo/basic/index.tsx +++ b/components/shell/__docs__/demo/basic/index.tsx @@ -1,8 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Search, Icon, Nav, Shell } from '@alifd/next'; - -const { SubNav, Item, Group, Divider } = Nav; +import { Search, Icon, Shell } from '@alifd/next'; class App extends React.Component { render() { diff --git a/components/shell/__docs__/demo/complicated/index.tsx b/components/shell/__docs__/demo/complicated/index.tsx index cbae2840df..ca4dfc8a93 100644 --- a/components/shell/__docs__/demo/complicated/index.tsx +++ b/components/shell/__docs__/demo/complicated/index.tsx @@ -1,14 +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 { SubNav, Item, Group, Divider } = Nav; +const { Item } = Nav; class App extends React.Component { - state = { + state: { device: ShellProps['device']; navcollapse: boolean } = { device: 'desktop', + navcollapse: false, }; - onChange = device => { + onChange: GroupProps['onChange'] = device => { this.setState({ device, }); @@ -20,8 +23,8 @@ class App extends React.Component { }); }; - onCollapseChange = (visible, e) => { - console.log('onCollapseChange:', visible, e); + onCollapseChange = (visible: boolean) => { + console.log('onCollapseChange:', visible); this.setState({ navcollapse: visible, @@ -124,7 +127,7 @@ class App extends React.Component { @ 2019 Alibaba Piecework 版权所有 - + diff --git a/components/shell/__docs__/demo/header-global-local/index.tsx b/components/shell/__docs__/demo/header-global-local/index.tsx index 09d3e3bb2e..77d95ab9a8 100644 --- a/components/shell/__docs__/demo/header-global-local/index.tsx +++ b/components/shell/__docs__/demo/header-global-local/index.tsx @@ -1,14 +1,16 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Menu, Search, Nav, Shell, Radio } from '@alifd/next'; +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 { SubNav, Item, Group, Divider } = Nav; +const { Item } = Nav; class App extends React.Component { - state = { + state: { device: ShellProps['device'] } = { device: 'desktop', }; - onChange = device => { + 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 020f4baa94..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 { Menu, Search, Nav, Shell, Radio } from '@alifd/next'; - -const { SubNav, Item, Group, Divider } = Nav; +import { Search, Nav, Shell, Radio } from '@alifd/next'; +import type { ShellProps } from '@alifd/next/types/shell'; +import type { GroupProps } from '@alifd/next/types/radio'; class App extends React.Component { - state = { + state: { device: ShellProps['device'] } = { device: 'desktop', }; - onChange = device => { + 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 21ed5d84cf..e790536cd8 100644 --- a/components/shell/__docs__/index.en-us.md +++ b/components/shell/__docs__/index.en-us.md @@ -15,13 +15,13 @@ Shell is the infrastructure framework of the whole application. It embodies the ### 何时使用 -- Shell should be configured according to the actual business requirements. -- The same application uses a unified Shell framework to avoid confusion. +- Shell should be configured according to the actual business requirements. +- The same application uses a unified Shell framework to avoid confusion. -````jsx +```jsx - + @@ -36,47 +36,49 @@ Shell is the infrastructure framework of the whole application. It embodies the -```` +``` `` uses css-grid, others are `display: flex` ## API ### Shell -| Param | Description | Type | Default Value | -| -------------------- | ------------ | ----------------- | ------------------ | -| device | Preset screen width, tt determines whether `Navigation` `LocalNavigation` `Ancillary`take space or not

**option**:
'phone', 'tablet', 'desktop' | Enum | desktop | -| type | type of Shell

**可选值**:
'light', 'dark', 'brand' | Enum | 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 -It will tell his children whether it's collapse or not by `isCollapse` via Context. -| Param | Description | Type | Default Value | -| -------------------- | ------------ | ----------------- | ------------------ | -| collapse | collapse or not | Boolean | false | -| direction | header or asider

**option**:
'hoz', 'ver' | Enum | hoz | -| align | Arrangement of Navigation when direction is hoz

**option**:
'left', 'right', 'center' | Enum | right | -| onCollapseChange | this will be triggered when collapse changed by inner icon | Function | () => {} | -| trigger | trigger of Shell.Navigation, it placed on top and left of the page, you can set `null` to remove it | ReactNode | | -| fixed | fixed or not, only worked when Shell fixedHeader is true | Boolean | false | +| Param | Description | Type | Default Value | Required | +| ---------------- | ------------------------------------------------------------------------------------------------- | ----------------------------- | ------------- | -------- | +| direction | Header or asider | 'hoz' \| 'ver' | 'hoz' | | +| collapse | Collapse or not | boolean | false | | +| align | Arrangement of Navigation when direction is hoz | 'left' \| 'right' \| 'center' | 'right' | | +| onCollapseChange | This will be triggered when collapse changed by inner icon | (collapse?: boolean) => void | func.noop | | +| trigger | Trigger of Shell.Navigation, it placed on top and left of the page, you can set null to remove it | ReactNode | - | | +| fixed | Fixed or not, only worked when Shell fixedHeader is true | boolean | false | | ### Shell.LocalNavigation -| Param | Description | Type | Default Value | -| -------------------- | ------------ | ----------------- | ------------------ | -| collapse | collapse or not | Boolean | false | -| onCollapseChange | this will be triggered when collapse changed by inner icon | Function | () => {} | + +| Param | Description | Type | Default Value | Required | +| ---------------- | ---------------------------------------------------------- | ---------------------------- | ------------- | -------- | +| collapse | Collapse or not | boolean | false | | +| onCollapseChange | This will be triggered when collapse changed by inner icon | (collapse?: boolean) => void | func.noop | | ### Shell.ToolDock -| Param | Description | Type | Default Value | -| -------------------- | ------------ | ----------------- | ------------------ | -| collapse | collapse or not | Boolean | false | -| onCollapseChange | this will be triggered when collapse changed by inner icon | Function | () => {} | -| fixed | fixed or not, only worked when Shell fixedHeader is true | Boolean | false | + +| Param | Description | Type | Default Value | Required | +| ---------------- | ---------------------------------------------------------- | ---------------------------- | ------------- | -------- | +| collapse | Collapse or not | boolean | false | | +| onCollapseChange | This will be triggered when collapse changed by inner icon | (collapse?: boolean) => void | func.noop | | +| fixed | Fixed or not, only worked when Shell fixedHeader is true | boolean | false | | ### Shell.Ancillary -| Param | Description | Type | Default Value | -| -------------------- | ------------ | ----------------- | ------------------ | -| collapse | collapse or not | Boolean | false | -| onCollapseChange | this will be triggered when collapse changed by inner icon | Function | () => {} | + +| Param | Description | Type | Default Value | Required | +| ---------------- | ---------------------------------------------------------- | ---------------------------- | ------------- | -------- | +| collapse | Collapse or not | boolean | false | | +| onCollapseChange | This will be triggered when collapse changed by inner icon | (collapse?: boolean) => void | func.noop | | diff --git a/components/shell/__docs__/index.md b/components/shell/__docs__/index.md index 3712a45047..35f8570de2 100644 --- a/components/shell/__docs__/index.md +++ b/components/shell/__docs__/index.md @@ -10,66 +10,68 @@ 框架组件,仅支持IE10+。 ## 何时使用 -- Shell 是整个应用的基础结构框架。 -- 它体现应用的结构形式和承载应用的基本能力,让用户可以在同一套框架下完成自己所有的操作。 + +- Shell 是整个应用的基础结构框架。 +- 它体现应用的结构形式和承载应用的基本能力,让用户可以在同一套框架下完成自己所有的操作。 ## 如何使用 -- Shell 应该根据业务实际诉求的复杂度进行配置; -- 同一个应用统一使用一套 Shell 框架,避免出现混乱问题。 +- Shell 应该根据业务实际诉求的复杂度进行配置; +- 同一个应用统一使用一套 Shell 框架,避免出现混乱问题。 ## API ### Shell -| 参数 | 说明 | 类型 | 默认值 | -| -------------------- | ------------ | ----------------- | ------------------ | -| device | 预设屏幕宽度,会影响`Navigation` `LocalNavigation` `Ancillary`等是否占据空间

**可选值**:
'phone', 'tablet', 'desktop' | Enum | desktop | -| type | 样式类型,分浅色主题、深色主题、主题色主题

**可选值**:
'light', 'dark', 'brand' | Enum | light | -| fixedHeader | 是否固定Header,采用sticky布局,不支持 IE11 | Boolean | false | -### Shell.Navigation -向子组件透传 isCollapse 的Context,表示当前是否处于折叠状态 +| 参数 | 说明 | 类型 | 默认值 | 是否必填 | +| ----------- | -------------------------------------------------------------------------------- | -------------------------------- | --------- | -------- | +| device | 预设屏幕宽度,会影响 `Navigation`、`LocalNavigation`、`Ancillary` 等是否占据空间 | 'tablet' \| 'desktop' \| 'phone' | 'desktop' | | +| type | 样式类型,分浅色主题、深色主题、主题色主题 | 'light' \| 'dark' \| 'brand' | 'light' | | +| fixedHeader | 是否固定Header,采用sticky布局,不支持 IE11 | boolean | false | | -| 参数 | 说明 | 类型 | 默认值 | -| -------------------- | ------------ | ----------------- | ------------------ | -| direction | 方向

**可选值**:
'hoz', 'ver' | Enum | hoz | -| collapse | 是否折叠(折叠成只有icon状态) | Boolean | false | -| align | 横向模式下,导航排布的位置

**可选值**:
'left', 'right', 'center' | Enum | right | -| onCollapseChange | 默认按钮触发的展开收起状态 | Function | () => {} | -| trigger | 菜单展开、收起的触发元素,默认在左上角,不想要可以设置 `null` 来去掉 | ReactNode | | -| fixed | 是否固定,且需要在在 Shell fixedHeader时生效 | Boolean | false | +### Shell.Navigation +| 参数 | 说明 | 类型 | 默认值 | 是否必填 | +| ---------------- | ------------------------------------------------------------------ | ----------------------------- | --------- | -------- | +| direction | 方向 | 'hoz' \| 'ver' | 'hoz' | | +| collapse | 是否折叠(折叠成只有icon状态) | boolean | false | | +| align | 横向模式下,导航排布的位置 | 'left' \| 'right' \| 'center' | 'right' | | +| onCollapseChange | 默认按钮触发的展开收起状态 | (collapse?: boolean) => void | func.noop | | +| trigger | 菜单展开、收起的触发元素,默认在左上角,不想要可以设置 null 来去掉 | ReactNode | - | | +| fixed | 是否固定,且需要在在 Shell fixedHeader时生效 | boolean | false | | ### Shell.LocalNavigation -| 参数 | 说明 | 类型 | 默认值 | -| -------------------- | ------------ | ----------------- | ------------------ | -| collapse | 是否折叠(完全收起) | Boolean | false | -| onCollapseChange | 默认按钮触发的展开收起状态 | Function | () => {} | + +| 参数 | 说明 | 类型 | 默认值 | 是否必填 | +| ---------------- | -------------------------- | ---------------------------- | --------- | -------- | +| collapse | 是否折叠(完全收起) | boolean | false | | +| onCollapseChange | 默认按钮触发的展开收起状态 | (collapse?: boolean) => void | func.noop | | ### Shell.ToolDock -| 参数 | 说明 | 类型 | 默认值 | -| -------------------- | ------------ | ----------------- | ------------------ | -| collapse | 是否折叠(完全收起) | Boolean | false | -| onCollapseChange | 默认按钮触发的展开收起状态 | Function | () => {} | -| fixed | 是否固定,且需要在在 Shell fixedHeader时生效 | Boolean | false | + +| 参数 | 说明 | 类型 | 默认值 | 是否必填 | +| ---------------- | --------------------------------------------- | ---------------------------- | --------- | -------- | +| collapse | 是否折叠(完全收起) | boolean | false | | +| onCollapseChange | 默认按钮触发的展开收起状态 | (collapse?: boolean) => void | func.noop | | +| fixed | 是否固定,且需要在在 Shell fixedHeader 时生效 | boolean | false | | ### Shell.Ancillary -| 参数 | 说明 | 类型 | 默认值 | -| -------------------- | ------------ | ----------------- | ------------------ | -| collapse | 是否折叠(完全收起) | Boolean | false | -| onCollapseChange | 默认按钮触发的展开收起状态 | Function | () => {} | +| 参数 | 说明 | 类型 | 默认值 | 是否必填 | +| ---------------- | -------------------------- | ---------------------------- | --------- | -------- | +| collapse | 是否折叠(完全收起) | boolean | false | | +| onCollapseChange | 默认按钮触发的展开收起状态 | (collapse?: boolean) => void | func.noop | | ## FAQ - ### 有侧边栏的情况下,如何去掉左上角的side bar 展开、收起触发器? + 设置 `` -````jsx +```jsx - + @@ -84,6 +86,6 @@ -```` +``` -其中 `` 采用Grid布局, 其他均为 Flex布局。 \ No newline at end of file +其中 `` 采用Grid布局, 其他均为 Flex布局。 diff --git a/components/shell/__docs__/theme/index.jsx b/components/shell/__docs__/theme/index.jsx deleted file mode 100644 index 338a58fdf7..0000000000 --- a/components/shell/__docs__/theme/index.jsx +++ /dev/null @@ -1,368 +0,0 @@ -import { Demo, DemoGroup, DemoHead, initDemo } from '../../../demo-helper'; -import Shell from '../../index'; -import Nav from '../../../nav'; -import Search from '../../../search'; -import Icon from '../../../icon'; -import ConfigProvider from '../../../config-provider'; -import zhCN from '../../../locale/zh-cn'; -import enUS from '../../../locale/en-us'; -import '../../../demo-helper/style'; -import '../../style'; -import '../../../search/style'; -import '../../../nav/style'; - -/* eslint-disable */ -const i18nMap = { - 'zh-cn': { - shell: '布局框架', - light: 'Shell模版1 - light', - dark: 'Shell模版2 - dark', - brand: 'Shell模版3 - brand', - }, - 'en-us': { - shell: 'Shell', - light: 'Template 1 - light', - dark: 'Template 2 - dark', - brand: 'Template 3 - brand', - } -}; -class RenderShell extends React.Component { - render() { - const { type, i18n, 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', - 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; - } - - switch(device) { - case 'phone': - shellStyle = {height: 500, width: 480, border: '1px solid #eee'}; - break; - case 'tablet': - shellStyle = {height: 500, width: 768, border: '1px solid #eee'}; - break; - case 'desktop': - shellStyle = {height: 500, width: 1000, border: '1px solid #eee'}; - break; - default: - break; - } - return ( - - - { - demoFunction.branding.value === 'true' - ? -
- App Name -
- : null - } - - { - demoFunction.navigation.value !== 'false' - ? - - - : null - } - { - demoFunction.actions.value === 'true' - ? - - - 用户头像 - Name - - : null - } - { - demoFunction.localNav.value === 'true' - ? - - - : null - } - { - demoFunction.appbar.value === 'true' - ? - - - : null - } - -
-
- - { - demoFunction.ancillary.value === 'true' - ? - - : null - } - { - demoFunction.tooldock.value === 'true' - ? - - - - - - - - - - - : null - } - { - demoFunction.footer.value === 'true' - ? - Alibaba Fusion - @ 2019 Alibaba Piecework 版权所有 - - : null - } -
-
-
); - } -} - -const renderShell = (type, i18n, demoFunction) => { - return ; -} - -class FunctionDemo extends React.Component { - constructor(props) { - super(props); - const navigationType = props.type === 'dark' ? 'primary' : 'normal'; - - this.state = { - demoFunction: { - 'device': { - label: 'Device', - value: 'desktop', - enum: [{ - label: 'desktop', - value: 'desktop' - }, { - label: 'tablet', - value: 'tablet' - }, { - label: 'phone', - value: 'phone' - }] - }, - 'branding': { - label: 'Branding', - value: 'true', - enum: [{ - label: '有', - value: 'true' - }, { - label: '无', - value: 'false' - }] - }, - 'actions': { - label: 'Actions', - value: 'true', - enum: [{ - label: '有', - value: 'true' - }, { - label: '无', - value: 'false' - }] - }, - 'navigation': { - label: 'Applicaitoin Nav', - value: 'ver', - enum: [{ - label: '侧栏', - value: 'ver' - }, { - label: '顶部', - value: 'hoz' - }, { - label: '无', - value: 'false' - }] - }, - 'navigationType': { - label: 'App Nav Type', - value: navigationType, - enum: [{ - label: 'normal', - value: 'normal' - }, { - label: 'primary', - value: 'primary' - }, { - label: 'secondary', - value: 'secondary' - }, { - label: 'line', - value: 'line' - }] - }, - 'localNav': { - label: 'Local Nav', - value: 'false', - enum: [{ - label: '有', - value: 'true' - }, { - label: '无', - value: 'false' - }] - }, - // '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', - enum: [{ - label: '有', - value: 'true' - }, { - label: '无', - value: 'false' - }] - }, - 'footer': { - label: 'Footer', - value: 'false', - enum: [{ - label: '有', - value: 'true' - }, { - label: '无', - value: 'false' - }] - }, - 'ancillary': { - label: 'Ancillary', - value: 'false', - enum: [{ - label: '有', - value: 'true' - }, { - label: '无', - value: 'false' - }] - }, - 'tooldock': { - label: 'Tooldock', - value: 'false', - enum: [{ - label: '有', - value: 'true' - }, { - label: '无', - value: 'false' - }] - }, - } - } - } - - onFunctionChange = (ret) => { - this.setState({ - demoFunction: ret, - }); - } - - render() { - const { title, locale, type, shellRender } = this.props; - const { demoFunction } = this.state; - - return ( - { shellRender(type, locale, demoFunction) } - ) - } -} - - -function render(i18n, lang) { - return ReactDOM.render(
- { - ['light', 'dark', 'brand'].map(type => { - return - }) - } -
, document.getElementById('container')); -} - -window.renderDemo = function (lang = 'en-us') { - render(i18nMap[lang], lang); -}; - -renderDemo(); - -initDemo('shell'); diff --git a/components/shell/__docs__/theme/index.tsx b/components/shell/__docs__/theme/index.tsx new file mode 100644 index 0000000000..67c9790d2f --- /dev/null +++ b/components/shell/__docs__/theme/index.tsx @@ -0,0 +1,501 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Demo, DemoGroup, initDemo, type DemoProps } from '../../../demo-helper'; +import Shell from '../../index'; +import Nav from '../../../nav'; +import Search from '../../../search'; +import Icon from '../../../icon'; +import ConfigProvider from '../../../config-provider'; +import zhCN from '../../../locale/zh-cn'; +import enUS from '../../../locale/en-us'; +import '../../../demo-helper/style'; +import '../../style'; +import '../../../search/style'; +import '../../../nav/style'; + +interface ShellThemeProps { + type: 'light' | 'dark' | 'brand'; + i18n: { + [key: string]: string; + }; + demoFunction: { + device: { + value: 'phone' | 'tablet' | 'desktop'; + label: string; + enum: Array<{ label: string; value: string }>; + }; + navigation: { + value?: 'hoz' | 'ver'; + label: string; + enum: Array<{ label: string; value: string }>; + }; + navigationType: { + value?: 'normal' | 'primary' | 'secondary' | 'line'; + label: string; + enum: Array<{ label: string; value: string }>; + }; + localNav: { + value: string; + label: string; + enum: Array<{ label: string; value: string }>; + }; + localNavType?: { + value?: 'normal' | 'primary' | 'secondary' | 'line'; + label: string; + enum: Array<{ label: string; value: string }>; + }; + branding: { + value: string; + label: string; + enum: Array<{ label: string; value: string }>; + }; + actions: { + value: string; + label: string; + enum: Array<{ label: string; value: string }>; + }; + appbar: { + value: string; + label: string; + enum: Array<{ label: string; value: string }>; + }; + ancillary: { + value: string; + label: string; + enum: Array<{ label: string; value: string }>; + }; + tooldock: { + value: string; + label: string; + enum: Array<{ label: string; value: string }>; + }; + footer: { + value: string; + label: string; + enum: Array<{ label: string; value: string }>; + }; + }; +} + +interface FunctionDemoProps { + title: DemoProps['title']; + type: ShellThemeProps['type']; + locale: ShellThemeProps['i18n']; + shellRender: ( + type: ShellThemeProps['type'], + locale: ShellThemeProps['i18n'], + demoFunction: ShellThemeProps['demoFunction'] + ) => React.ReactNode; +} + +interface FunctionDemoState { + demoFunction: ShellThemeProps['demoFunction']; +} + +const i18nMap = { + 'zh-cn': { + shell: '布局框架', + light: 'Shell模版1 - light', + dark: 'Shell模版2 - dark', + brand: 'Shell模版3 - brand', + }, + 'en-us': { + shell: 'Shell', + light: 'Template 1 - light', + dark: 'Template 2 - dark', + brand: 'Template 3 - brand', + }, +}; +class RenderShell extends React.Component { + render() { + const { type, demoFunction } = this.props; + const device = demoFunction.device.value; + const globalDir = demoFunction.navigation.value; + 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' }; + break; + case 'dark': + logoStyle = { width: 32, height: 32, background: '#FFF', opacity: '0.2' }; + break; + case 'brand': + logoStyle = { width: 32, height: 32, background: '#000', opacity: '0.04' }; + break; + default: + break; + } + + switch (device) { + case 'phone': + shellStyle = { height: 500, width: 480, border: '1px solid #eee' }; + break; + case 'tablet': + shellStyle = { height: 500, width: 768, border: '1px solid #eee' }; + break; + case 'desktop': + shellStyle = { height: 500, width: 1000, border: '1px solid #eee' }; + break; + default: + break; + } + return ( + + + + {demoFunction.branding.value === 'true' ? ( + +
+ App Name +
+ ) : null} + {/* @ts-expect-error navigation.value 不会为'false',此判断无意义 */} + {demoFunction.navigation.value !== 'false' ? ( + + + + ) : null} + {demoFunction.actions.value === 'true' ? ( + + + + 用户头像 + Name + + ) : null} + {demoFunction.localNav.value === 'true' ? ( + + + + ) : null} + {demoFunction.appbar.value === 'true' ? : null} + +
+
+ + {demoFunction.ancillary.value === 'true' ? : null} + {demoFunction.tooldock.value === 'true' ? ( + + + + + + + + + + + + ) : null} + {demoFunction.footer.value === 'true' ? ( + + Alibaba Fusion + @ 2019 Alibaba Piecework 版权所有 + + ) : null} +
+
+
+ ); + } +} + +const renderShell = ( + type: ShellThemeProps['type'], + i18n: ShellThemeProps['i18n'], + demoFunction: ShellThemeProps['demoFunction'] +) => { + return ; +}; + +class FunctionDemo extends React.Component { + constructor(props: FunctionDemoProps) { + super(props); + const navigationType = props.type === 'dark' ? 'primary' : 'normal'; + + this.state = { + demoFunction: { + device: { + label: 'Device', + value: 'desktop', + enum: [ + { + label: 'desktop', + value: 'desktop', + }, + { + label: 'tablet', + value: 'tablet', + }, + { + label: 'phone', + value: 'phone', + }, + ], + }, + branding: { + label: 'Branding', + value: 'true', + enum: [ + { + label: '有', + value: 'true', + }, + { + label: '无', + value: 'false', + }, + ], + }, + actions: { + label: 'Actions', + value: 'true', + enum: [ + { + label: '有', + value: 'true', + }, + { + label: '无', + value: 'false', + }, + ], + }, + navigation: { + label: 'Applicaitoin Nav', + value: 'ver', + enum: [ + { + label: '侧栏', + value: 'ver', + }, + { + label: '顶部', + value: 'hoz', + }, + { + label: '无', + value: 'false', + }, + ], + }, + navigationType: { + label: 'App Nav Type', + value: navigationType, + enum: [ + { + label: 'normal', + value: 'normal', + }, + { + label: 'primary', + value: 'primary', + }, + { + label: 'secondary', + value: 'secondary', + }, + { + label: 'line', + value: 'line', + }, + ], + }, + localNav: { + label: 'Local Nav', + value: 'false', + enum: [ + { + label: '有', + value: 'true', + }, + { + label: '无', + value: 'false', + }, + ], + }, + 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', + enum: [ + { + label: '有', + value: 'true', + }, + { + label: '无', + value: 'false', + }, + ], + }, + footer: { + label: 'Footer', + value: 'false', + enum: [ + { + label: '有', + value: 'true', + }, + { + label: '无', + value: 'false', + }, + ], + }, + ancillary: { + label: 'Ancillary', + value: 'false', + enum: [ + { + label: '有', + value: 'true', + }, + { + label: '无', + value: 'false', + }, + ], + }, + tooldock: { + label: 'Tooldock', + value: 'false', + enum: [ + { + label: '有', + value: 'true', + }, + { + label: '无', + value: 'false', + }, + ], + }, + }, + }; + } + + onFunctionChange = (ret: FunctionDemoState['demoFunction']) => { + this.setState({ + demoFunction: ret, + }); + }; + + render() { + const { title, locale, type, shellRender } = this.props; + const { demoFunction } = this.state; + + return ( + + {shellRender(type, locale, demoFunction)} + + ); + } +} + +function render(i18n: FunctionDemoProps['locale'], lang: string) { + ReactDOM.render( + // @ts-expect-error ConfigProvider 不存在 lang 属性 + +
+ {['light', 'dark', 'brand'].map((type: 'light' | 'dark' | 'brand') => { + return ( + + ); + })} +
+
, + document.getElementById('container') + ); +} + +window.renderDemo = function (lang = 'en-us') { + render(i18nMap[lang], lang); +}; + +renderDemo(); + +initDemo('shell'); diff --git a/components/shell/__tests__/a11y-spec.js b/components/shell/__tests__/a11y-spec.tsx similarity index 69% rename from components/shell/__tests__/a11y-spec.js rename to components/shell/__tests__/a11y-spec.tsx index 64c886398c..9030c61602 100644 --- a/components/shell/__tests__/a11y-spec.js +++ b/components/shell/__tests__/a11y-spec.tsx @@ -1,36 +1,27 @@ import React from 'react'; -import Enzyme from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; -import { unmount, testReact } from '../../util/__tests__/legacy/a11y/validate'; import Shell from '../index'; import Search from '../../search/index'; import '../style'; import '../../search/style'; import './index.scss'; +import { testReact } from '../../util/__tests__/a11y/validate'; -Enzyme.configure({ adapter: new Adapter() }); - -/* eslint-disable no-undef, react/jsx-filename-extension */ describe('Shell A11y', () => { - let wrapper; - - afterEach(() => { - if (wrapper) { - wrapper.unmount(); - wrapper = null; - } - unmount(); - }); - it('should not have any violations', async () => { - wrapper = await testReact( + await testReact(
App Name - + { /> MyName -
- Alibaba Fusion @ 2019 Alibaba Piecework 版权所有 ); - return wrapper; }); }); diff --git a/components/shell/__tests__/index-spec.js b/components/shell/__tests__/index-spec.js deleted file mode 100644 index 4f46967b41..0000000000 --- a/components/shell/__tests__/index-spec.js +++ /dev/null @@ -1,436 +0,0 @@ -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 Search from '../../search/index'; -import Radio from '../../radio/index'; -import Icon from '../../icon/index'; -import Nav from '../../nav/index'; -import '../style'; -import '../../search/style'; -import '../../nav/style'; -import '../../icon/style'; -import './index.scss'; - -const Item = Nav.Item; -Enzyme.configure({ adapter: new Adapter() }); - -const render = element => { - let inc; - const container = document.createElement('div'); - document.body.appendChild(container); - ReactDOM.render(element, container, function() { - inc = this; - }); - return { - setProps: props => { - const clonedElement = React.cloneElement(element, props); - ReactDOM.render(clonedElement, container); - }, - unmount: () => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - }, - instance: () => { - return inc; - }, - find: selector => { - return container.querySelectorAll(selector); - }, - }; -}; - -class App extends React.Component { - state = { - device: 'desktop', - }; - onChange = device => { - this.setState({ - device, - }); - }; - - btnClick = () => { - this.setState({ - navcollapse: !this.state.navcollapse, - }); - }; - - onCollapseChange = (visible, e) => { - console.log('onCollapseChange:', visible, e); - const { onCollapseNavigationChange } = this.props; - - this.setState({ - navcollapse: visible, - }); - - onCollapseNavigationChange(visible); - }; - - render() { - const { onCollapseLocalNavChange, onCollapseAncilleryChange } = this.props; - // eslint-disable-next-line react/jsx-filename-extension - return ( -
- - phone - tablet - desktop - - - -
- App Name - - - - - - 用户头像 - MyName - - - - -
- {this.state.navcollapse ? ( - - ) : ( - - )} -
-
- - - - - - -
- - - Alibaba Fusion - @ 2019 Alibaba Piecework 版权所有 - - - - - - - - - - - - - - - - -
- ); - } -} - -/* eslint-disable */ -describe('Shell', () => { - describe('render', () => { - let wrapper; - - afterEach(() => { - wrapper.unmount(); - }); - - it('default should work', () => { - wrapper = render( - - -
- App Name - - - - - - 用户头像 - MyName - - - -
- - - - Alibaba Fusion - @ 2019 Alibaba Piecework 版权所有 - - - ); - }); - - it('default collapse should work', () => { - let navCollapseCount = 0, - localCollapseCount = 0, - anciCollapseCount = 0; - let navCollapse = false, - localCollapse = false, - anciCollapse = false; - wrapper = render( - { - 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); - }} - /> - ); - - wrapper.find('.local-nav-trigger')[0].click(); - wrapper.find('.ancillary-trigger')[0].click(); - wrapper.find('.nav-trigger')[0].click(); - wrapper.find('.nav-trigger')[0].click(); - - assert( - navCollapseCount === 2 && - localCollapseCount === 1 && - anciCollapseCount === 1 && - !navCollapse && - localCollapse && - anciCollapse - ); - - wrapper.find('#custom-nav-trigger')[0].click(); - - assert(wrapper.find('.next-shell-navigation.next-shell-collapse')); - }); - - it('should support no header', () => { - wrapper = render( - - - - - - -
- - - - Alibaba Fusion - @ 2019 Alibaba Piecework 版权所有 - - - ); - - assert(wrapper.find('.next-shell-header').length === 0); - }); - - it('should support fixed header', () => { - wrapper = render( - - -
- App Name - - - - - - -
- - - - Alibaba Fusion - @ 2019 Alibaba Piecework 版权所有 - - - ); - - assert(wrapper.find('.next-shell-header.next-shell-fixed-header').length > 0); - assert(wrapper.find('.next-aside-navigation.fixed').length > 0); - }); - - it('should support nothing', () => { - wrapper = render( - - -
- - - - Alibaba Fusion - @ 2019 Alibaba Piecework 版权所有 - - - ); - - assert(wrapper.find('.next-shell-header').length === 0); - }); - - it('should support nothing', () => { - wrapper = render( - - -
- - - - Alibaba Fusion - @ 2019 Alibaba Piecework 版权所有 - - - ); - - assert(wrapper.find('.next-shell-header').length === 0); - }); - - it('only tooldock, show header only in phone', () => { - wrapper = render( - - -
- - - - - - - - - - - - - - - ); - - wrapper.setProps({ - device: 'tablet', - }); - assert(wrapper.find('.next-shell-header').length === 0); - - wrapper.setProps({ - device: 'phone', - }); - assert(wrapper.find('.next-shell-header').length === 1); - }); - - // issue: - it('should hidden menu & menu-items-icon in phone', () => { - const div = document.createElement('div'); - document.body.appendChild(div); - wrapper = mount( - - - - - -
- - - - - - - - - - - - - , - {attachTo: div} - ); - const element = wrapper.find('.next-aside-navigation').at(0).instance(); - assert(element.offsetWidth === 0); - assert(window.getComputedStyle(element).overflow === 'hidden'); - }); - }); -}); diff --git a/components/shell/__tests__/index-spec.tsx b/components/shell/__tests__/index-spec.tsx new file mode 100644 index 0000000000..20e4aa5209 --- /dev/null +++ b/components/shell/__tests__/index-spec.tsx @@ -0,0 +1,412 @@ +import React from 'react'; +import Shell, { type ShellProps } from '../index'; +import Search from '../../search/index'; +import Radio, { type GroupProps } from '../../radio/index'; +import Icon from '../../icon/index'; +import Nav from '../../nav/index'; +import '../style'; +import '../../search/style'; +import '../../nav/style'; +import '../../icon/style'; +import './index.scss'; + +const { Item } = Nav; + +describe('Shell', () => { + describe('render', () => { + it('default should work', () => { + cy.mount( + + +
+ App Name + + + + + + 用户头像 + MyName + + + +
+ + + + Alibaba Fusion + @ 2019 Alibaba Piecework 版权所有 + + + ); + // 检查各部分是否存在 + cy.get('.iframe-hack').should('exist'); + cy.get('.rectangular').should('exist'); + cy.contains('App Name').should('exist'); + cy.get('.avatar').should('exist'); + cy.contains('MyName').should('exist'); + cy.contains('Alibaba Fusion').should('exist'); + cy.contains('@ 2019 Alibaba Piecework 版权所有').should('exist'); + }); + + 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: AppState = { + device: 'desktop', + navcollapse: false, + }; + + onChange: GroupProps['onChange'] = device => { + this.setState({ device }); + }; + + btnClick = () => { + 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 }); + onCollapseNavigationChange(visible); + }; + + render() { + const { onCollapseLocalNavChange, onCollapseAncilleryChange } = this.props; + return ( +
+ + phone + tablet + desktop + + + +
+ App Name + + + + + + 用户头像 + MyName + + + + +
+ {this.state.navcollapse ? ( + + ) : ( + + )} +
+
+ + + + + + +
+ + + Alibaba Fusion + @ 2019 Alibaba Piecework 版权所有 + + + + + + + + + + + + + + + + +
+ ); + } + } + + // 使用 Cypress spy 来模拟回调函数 + const navCollapseSpy = cy.spy().as('navCollapseSpy'); + const localCollapseSpy = cy.spy().as('localCollapseSpy'); + const ancillaryCollapseSpy = cy.spy().as('ancillaryCollapseSpy'); + + cy.mount( + + ); + + cy.get('.local-nav-trigger').click(); + cy.get('.ancillary-trigger').click(); + cy.get('.nav-trigger').click(); + cy.get('.nav-trigger').click(); + cy.get('#custom-nav-trigger').click(); + + 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', () => { + cy.mount( + + + + + + +
+ + + + Alibaba Fusion + @ 2019 Alibaba Piecework 版权所有 + + + ); + + cy.get('.next-shell-header').should('not.exist'); + }); + + it('should support fixed header', () => { + cy.mount( + + +
+ App Name + + + + + + +
+ + + + Alibaba Fusion + @ 2019 Alibaba Piecework 版权所有 + + + ); + + cy.get('.next-shell-header.next-shell-fixed-header').should('exist'); + cy.get('.next-aside-navigation.fixed').should('exist'); + }); + + it('should support nothing', () => { + cy.mount( + + +
+ + + + Alibaba Fusion + @ 2019 Alibaba Piecework 版权所有 + + + ); + + cy.get('.next-shell-header').should('not.exist'); + cy.get('.next-aside-navigation').should('not.exist'); + cy.get('.next-shell-aside.next-aside-localnavigation').should('not.exist'); + cy.get('.next-shell-aside.next-aside-ancillary').should('not.exist'); + cy.get('.next-aside-tooldock').should('not.exist'); + }); + + it('only tooldock, show header only in phone', () => { + const testDevice = (device: ShellProps['device'], shouldExist: boolean) => { + // 创建一个通用的测试用例 + cy.mount( + + +
+ + + + + + + + + + + + + + ); + + if (shouldExist) { + cy.get('.next-shell-header').should('exist'); + } else { + cy.get('.next-shell-header').should('not.exist'); + } + }; + // 仅显示工具栏时,在桌面模式下不应显示头部 + testDevice('desktop', false); + // 仅显示工具栏时,在平板模式下不应显示头部 + testDevice('tablet', false); + // 仅显示工具栏时,在手机模式下应显示头部 + testDevice('phone', true); + }); + + // issue: + it('should hidden menu & menu-items-icon in phone', () => { + cy.mount( + + + + + +
+ + + + + + + + + + + + + + ); + // 获取 .next-aside-navigation 元素并检查其样式 + cy.get('.next-aside-navigation').as('navigationElement'); + // 检查宽度是否为零 + cy.get('@navigationElement').should('have.css', 'width', '0px'); + // 检查 overflow 样式是否为 hidden + cy.get('@navigationElement').should('have.css', 'overflow', 'hidden'); + }); + }); +}); diff --git a/components/shell/__tests__/index.scss b/components/shell/__tests__/index.scss index f1fbb2b9e5..4d8d030904 100644 --- a/components/shell/__tests__/index.scss +++ b/components/shell/__tests__/index.scss @@ -1,16 +1,17 @@ .avatar { - width: 24px; - height: 24px; - border-radius: 50%; - vertical-align: middle; - } - .rectangular { - width: 32px; - height: 32px; - background: rgba(0, 0, 0, 0.04); - } + width: 24px; + height: 24px; + border-radius: 50%; + vertical-align: middle; +} - .iframe-hack { - width: 100%; - height: 500px; - } +.rectangular { + width: 32px; + height: 32px; + background: rgba(0, 0, 0, 0.04); +} + +.iframe-hack { + width: 100%; + height: 500px; +} \ No newline at end of file diff --git a/components/shell/base.jsx b/components/shell/base.tsx similarity index 78% rename from components/shell/base.jsx rename to components/shell/base.tsx index 2d69770156..fa99324cc0 100644 --- a/components/shell/base.jsx +++ b/components/shell/base.tsx @@ -2,10 +2,11 @@ import React, { Component } from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; import ConfigProvider from '../config-provider'; +import type { BaseProps } from './types'; -export default function Base(props) { +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}`; @@ -20,14 +21,7 @@ export default function Base(props) { triggerProps: PropTypes.object, direction: PropTypes.oneOf(['hoz', 'ver']), align: PropTypes.oneOf(['left', 'right', 'center']), - /** - * 弹层显示或隐藏时触发的回调函数 - * @param {Boolean} collapse 弹层是否显示 - */ onCollapseChange: PropTypes.func, - /** - * 是否固定,仅对 Shell.Navigation Shell.ToolDock 生效,且需要在在 Shell fixedHeader时生效 - */ fixed: PropTypes.bool, }; @@ -67,14 +61,15 @@ export default function Base(props) { ...others } = this.props; - const Tag = component; + const Tag = component as React.ElementType; const cls = classnames({ - [`${prefix}shell-${componentName.toLowerCase()}`]: true, + [`${prefix}shell-${componentName!.toLowerCase()}`]: true, [`${prefix}shell-collapse`]: !!collapse, [`${prefix}shell-mini`]: miniable, - [`${prefix}shell-nav-${align}`]: componentName === 'Navigation' && direction === 'hoz' && align, - [className]: !!className, + [`${prefix}shell-nav-${align}`]: + componentName === 'Navigation' && direction === 'hoz' && align, + [className!]: !!className, }); let newChildren = children; diff --git a/components/shell/index.d.ts b/components/shell/index.d.ts deleted file mode 100644 index cd4d9cc021..0000000000 --- a/components/shell/index.d.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { HTMLAttributes, ElementType, Component, ComponentType } from 'react'; -import { CommonProps } from '../util'; - -export interface ShellProps extends HTMLAttributes, CommonProps { - /** - * 设备类型,针对不同的设备类型组件做出对应的响应式变化 - */ - device?: 'tablet' | 'desktop' | 'phone'; - type?: 'light' | 'dark' | 'brand'; - fixedHeader?: boolean; -} - -export interface ShellCommonProps extends HTMLAttributes, CommonProps {} - -export interface ShellNavigationProps extends ShellCommonProps { - collapse?: boolean; - direction?: 'hoz' | 'ver'; - align?: 'left' | 'right' | 'center'; - fixed?: boolean; - trigger?: any; - onCollapseChange?: (collapse?: boolean) => void; -} - -export interface ShellLocalNavigationProps extends ShellCommonProps { - collapse?: boolean; - onCollapseChange?: (collapse?: boolean) => void; -} - -export interface ShellToolDockProps extends ShellCommonProps { - collapse?: boolean; - fixed?: boolean; - onCollapseChange?: (collapse?: boolean) => void; -} - -export interface ShellAncillaryProps extends ShellCommonProps { - collapse?: boolean; - onCollapseChange?: (collapse?: boolean) => void; -} - -export default 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 Navigation: ComponentType; - static LocalNavigation: ComponentType; - static Ancillary: ComponentType; - static ToolDock: ComponentType; -} diff --git a/components/shell/index.jsx b/components/shell/index.jsx deleted file mode 100644 index 86223abc50..0000000000 --- a/components/shell/index.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import ShellBase from './shell'; -import Base from './base'; -import ConfigProvider from '../config-provider'; - -const Shell = ShellBase({ - componentName: 'Shell', -}); - -[ - 'Branding', - 'Navigation', - 'Action', - 'MultiTask', - 'LocalNavigation', - 'AppBar', - 'Content', - 'Footer', - 'Ancillary', - 'ToolDock', - 'ToolDockItem', -].forEach(key => { - Shell[key] = Base({ - componentName: key, - }); -}); - -Shell.Page = ConfigProvider.config( - ShellBase({ - componentName: 'Page', - }) -); - -export default ConfigProvider.config(Shell, { - transform: /* istanbul ignore next */ (props, deprecated) => { - if ('Component' in props) { - deprecated('Component', 'component', 'Shell'); - const { Component, component, ...others } = props; - if ('component' in props) { - props = { component, ...others }; - } else { - props = { component: Component, ...others }; - } - } - return props; - }, -}); diff --git a/components/shell/index.tsx b/components/shell/index.tsx new file mode 100644 index 0000000000..6938a8bb0b --- /dev/null +++ b/components/shell/index.tsx @@ -0,0 +1,50 @@ +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' }); + +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 type { + ShellProps, + ShellNavigationProps, + ShellLocalNavigationProps, + ShellToolDockProps, + ShellAncillaryProps, +}; +export default ConfigProvider.config(WithSubShell, { + transform: (props, deprecated) => { + if ('Component' in props) { + deprecated('Component', 'component', 'Shell'); + const { Component, component, ...others } = props; + if ('component' in props) { + props = { component, ...others }; + } else { + props = { component: Component, ...others }; + } + } + return props; + }, +}); diff --git a/components/shell/mobile/index.jsx b/components/shell/mobile/index.tsx similarity index 75% rename from components/shell/mobile/index.jsx rename to components/shell/mobile/index.tsx index 1cf592de2f..ebb2ad3291 100644 --- a/components/shell/mobile/index.jsx +++ b/components/shell/mobile/index.tsx @@ -1,3 +1,4 @@ +// @ts-expect-error meet-react 未导出Shell组件 import { Shell as MeetShell } from '@alifd/meet-react'; import NextShell from '../index'; diff --git a/components/shell/shell.jsx b/components/shell/shell.tsx similarity index 81% rename from components/shell/shell.jsx rename to components/shell/shell.tsx index 7a0433f065..eb3eac064a 100644 --- a/components/shell/shell.jsx +++ b/components/shell/shell.tsx @@ -1,4 +1,4 @@ -import React, { Component } 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'; @@ -6,14 +6,21 @@ import ConfigProvider from '../config-provider'; import Affix from '../affix'; import Icon from '../icon'; import { KEYCODE, dom, env } from '../util'; - import { isBoolean, getCollapseMap } from './util'; -/** - * Shell - */ -export default function ShellBase(props) { +import type { + ShellBaseProps, + ShellState, + ShellNavigationProps, + CollapseMap, + LayoutProps, + ChildElement, +} from './types'; +import type { CustomCSSStyle } from '../util/dom'; + +/** Shell */ +export default function ShellBase(props: { componentName?: string }) { const { componentName } = props; - class Shell extends Component { + class Shell extends Component { static displayName = componentName; static _typeMark = componentName; @@ -21,19 +28,8 @@ export default function ShellBase(props) { static propTypes = { ...ConfigProvider.propTypes, prefix: PropTypes.string, - /** - * 设备类型 - * @enumdesc 手机, 平板, PC电脑 - */ device: PropTypes.oneOf(['phone', 'tablet', 'desktop']), - /** - * 设备类型 - * @enumdesc 浅色, 深色, 主题色 - */ type: PropTypes.oneOf(['light', 'dark', 'brand']), - /** - * 是否固定 header, 用sticky实现,IE下降级为Affix - */ fixedHeader: PropTypes.bool, }; @@ -44,7 +40,16 @@ export default function ShellBase(props) { fixedHeader: false, }; - constructor(props) { + layout: LayoutProps; + headerRef: HTMLDivElement; + navigationFixed: boolean; + toolDockFixed: boolean; + navRef: HTMLDivElement; + localNavRef: HTMLDivElement; + submainRef: HTMLDivElement; + toolDockRef: HTMLDivElement; + + constructor(props: ShellBaseProps) { super(props); const deviceMap = getCollapseMap(props.device); @@ -57,7 +62,10 @@ export default function ShellBase(props) { }; } - static getDerivedStateFromProps(nextProps, prevState) { + static getDerivedStateFromProps( + nextProps: Readonly, + prevState: Readonly + ) { const { device } = prevState; if (nextProps.device !== device) { @@ -76,12 +84,12 @@ export default function ShellBase(props) { this.checkAsideFixed(); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: Readonly) { if (prevProps.device !== this.props.device) { const deviceMapBefore = getCollapseMap(prevProps.device); const deviceMapAfter = getCollapseMap(this.props.device); - Object.keys(deviceMapAfter).forEach(block => { + Object.keys(deviceMapAfter).forEach((block: keyof CollapseMap) => { const { props } = this.layout[block] || {}; if (deviceMapBefore[block] !== deviceMapAfter[block]) { if (props && typeof props.onCollapseChange === 'function') { @@ -110,7 +118,7 @@ export default function ShellBase(props) { } if (this.navigationFixed) { - const style = {}; + const style: Partial = {}; style.marginLeft = dom.getStyle(this.navRef, 'width'); dom.addClass(this.navRef, 'fixed'); headerHeight && dom.setStyle(this.navRef, { top: headerHeight }); @@ -118,7 +126,7 @@ export default function ShellBase(props) { } if (this.toolDockFixed) { - const style = {}; + const style: Partial = {}; style.marginRight = dom.getStyle(this.toolDockRef, 'width'); dom.addClass(this.toolDockRef, 'fixed'); headerHeight && dom.setStyle(this.toolDockRef, { top: headerHeight }); @@ -126,11 +134,11 @@ export default function ShellBase(props) { } }; - setChildCollapse = (child, mark) => { + setChildCollapse = (child: ReactElement, mark: keyof CollapseMap) => { const { device, collapseMap, controll } = this.state; const { collapse } = child.props; const deviceMap = getCollapseMap(device); - const props = {}; + const props: { collapse?: boolean; miniable?: boolean } = {}; // 非受控模式 if (isBoolean(collapse) === false) { @@ -145,7 +153,14 @@ export default function ShellBase(props) { return React.cloneElement(child, props); }; - toggleAside = (mark, props, e) => { + toggleAside = ( + mark: keyof CollapseMap, + props: { + onCollapseChange?: (collapse?: boolean) => void; + collapse?: boolean; + }, + e: KeyboardEvent | MouseEvent + ) => { const { device, collapseMap } = this.state; const deviceMap = getCollapseMap(device); const current = props.collapse; @@ -167,28 +182,34 @@ export default function ShellBase(props) { const { children } = this.props; let com; if (mark === 'Navigation') { - com = children + com = children! .filter( - child => + (child: ReactElement) => child && - child.type._typeMark.replace('Shell_', '') === mark && + (child as ChildElement).type._typeMark.replace('Shell_', '') === mark && child.props.direction !== 'hoz' ) .pop(); } else { - com = children.filter(child => child && child.type._typeMark.replace('Shell_', '') === mark).pop(); + com = children! + .filter( + child => + child && + (child as ChildElement).type._typeMark.replace('Shell_', '') === mark + ) + .pop(); } - const { triggerProps = {} } = com.props; + const { triggerProps = {} } = com!.props; if (typeof triggerProps.onClick === 'function') { triggerProps.onClick(e, this.state.collapseMap[mark]); } }; - toggleNavigation = e => { + toggleNavigation = (e: KeyboardEvent | MouseEvent) => { const mark = 'Navigation'; - const { props } = this.layout[mark]; + const { props } = this.layout[mark]!; if ('keyCode' in e && e.keyCode !== KEYCODE.ENTER) { return; @@ -197,9 +218,9 @@ export default function ShellBase(props) { this.toggleAside(mark, props, e); }; - toggleLocalNavigation = e => { + toggleLocalNavigation = (e: KeyboardEvent | MouseEvent) => { const mark = 'LocalNavigation'; - const { props } = this.layout[mark]; + const { props } = this.layout[mark]!; if ('keyCode' in e && e.keyCode !== KEYCODE.ENTER) { return; @@ -208,9 +229,9 @@ export default function ShellBase(props) { this.toggleAside(mark, props, e); }; - toggleAncillary = e => { + toggleAncillary = (e: KeyboardEvent | MouseEvent) => { const mark = 'Ancillary'; - const { props } = this.layout[mark]; + const { props } = this.layout[mark]!; if ('keyCode' in e && e.keyCode !== KEYCODE.ENTER) { return; @@ -219,9 +240,9 @@ export default function ShellBase(props) { this.toggleAside(mark, props, e); }; - toggleToolDock = e => { + toggleToolDock = (e: KeyboardEvent | MouseEvent) => { const mark = 'ToolDock'; - const { props } = this.layout[mark]; + const { props } = this.layout[mark]!; if ('keyCode' in e && e.keyCode !== KEYCODE.ENTER) { return; @@ -230,31 +251,31 @@ export default function ShellBase(props) { this.toggleAside(mark, props, e); }; - saveHeaderRef = ref => { + saveHeaderRef = (ref: HTMLDivElement) => { this.headerRef = ref; }; - saveLocalNavRef = ref => { + saveLocalNavRef = (ref: HTMLDivElement) => { this.localNavRef = ref; }; - saveNavRef = ref => { + saveNavRef = (ref: HTMLDivElement) => { this.navRef = ref; }; - saveSubmainRef = ref => { + saveSubmainRef = (ref: HTMLDivElement) => { this.submainRef = ref; }; - saveToolDockRef = ref => { + saveToolDockRef = (ref: HTMLDivElement) => { this.toolDockRef = ref; }; - renderShell = props => { + renderShell = (props: ShellBaseProps) => { const { prefix, children, className, type, fixedHeader, ...others } = props; const { device } = this.state; - const layout = {}; + const layout: LayoutProps = {}; layout.header = {}; let hasToolDock = false, needNavigationTrigger = false, @@ -262,23 +283,25 @@ export default function ShellBase(props) { React.Children.map(children, child => { if (child && typeof child.type === 'function') { - const mark = child.type._typeMark.replace('Shell_', ''); + const mark = (child as ChildElement).type._typeMark.replace('Shell_', ''); switch (mark) { case 'Branding': case 'Action': - layout.header[mark] = child; + layout.header![mark] = child; break; case 'MultiTask': layout.taskHeader = child; 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] = []; } @@ -288,10 +311,11 @@ export default function ShellBase(props) { hasToolDock = true; if (!layout[mark]) { + // @ts-expect-error 不应该是[], ToolDock 应该是 ReactElement layout[mark] = []; } - this.toolDockFixed = child.props.fixed; + this.toolDockFixed = child.props.fixed!; const childT = this.setChildCollapse(child, mark); layout[mark] = childT; @@ -308,9 +332,10 @@ export default function ShellBase(props) { break; case 'Navigation': if (child.props.direction === 'hoz') { - layout.header[mark] = child; + layout.header![mark] = child; } else { if (!layout[mark]) { + // @ts-expect-error 不应该是[], Navigation 应该是 ReactElement layout[mark] = []; } @@ -365,9 +390,10 @@ export default function ShellBase(props) { // 如果存在垂直模式的 Navigation, 则需要在 Branding 上出现 trigger if (needNavigationTrigger) { const branding = layout.header.Branding; - let { trigger, collapse } = layout.Navigation.props; + const collapse = layout.Navigation!.props.collapse; + let trigger = layout.Navigation!.props.trigger; - if ('trigger' in layout.Navigation.props) { + if ('trigger' in layout.Navigation!.props) { trigger = (trigger && React.cloneElement(trigger, { @@ -382,7 +408,7 @@ export default function ShellBase(props) { role="button" tabIndex={0} aria-expanded={!collapse} - aria-label={'toggle'} + aria-label="toggle" className="nav-trigger" onClick={this.toggleNavigation} onKeyDown={this.toggleNavigation} @@ -399,16 +425,20 @@ export default function ShellBase(props) { if (!branding) { trigger && (layout.header.Branding = trigger); } else { - layout.header.Branding = React.cloneElement(branding, {}, [trigger, branding.props.children]); + layout.header.Branding = React.cloneElement(branding, {}, [ + trigger, + branding.props.children, + ]); } } // 如果存在 toolDock, 则需要在 Action 上出现 trigger if (needDockTrigger) { const action = layout.header.Action; - let { trigger, collapse } = layout.ToolDock.props; + const collapse = layout.ToolDock!.props.collapse; + let trigger = layout.ToolDock!.props.trigger; - if ('trigger' in layout.ToolDock.props) { + if ('trigger' in layout.ToolDock!.props) { trigger = (trigger && React.cloneElement(trigger, { @@ -423,7 +453,7 @@ export default function ShellBase(props) { tabIndex={0} role="button" aria-expanded={!collapse} - aria-label={'toggle'} + aria-label="toggle" className="dock-trigger" onClick={this.toggleToolDock} onKeyDown={this.toggleToolDock} @@ -436,14 +466,17 @@ export default function ShellBase(props) { if (!action) { layout.header.Action = trigger; } else { - layout.header.Action = React.cloneElement(action, {}, [action.props.children, trigger]); + layout.header.Action = React.cloneElement(action, {}, [ + action.props.children, + trigger, + ]); } } - let headerDom = [], + let headerDom: ReactElement | [] = [], contentArr = [], - innerArr = [], taskHeaderDom = null; + const innerArr = []; if (layout.taskHeader) { const taskHeaderCls = classnames({ @@ -459,7 +492,8 @@ export default function ShellBase(props) { // 按照dom结构,innerArr 包括 LocalNavigation content Ancillary if (layout.LocalNavigation) { - let { trigger, collapse } = layout.LocalNavigation.props; + const collapse = layout.LocalNavigation.props.collapse; + let trigger = layout.LocalNavigation.props.trigger; if ('trigger' in layout.LocalNavigation.props) { trigger = @@ -476,7 +510,7 @@ export default function ShellBase(props) { role="button" tabIndex={0} aria-expanded={!collapse} - aria-label={'toggle'} + aria-label="toggle" className="local-nav-trigger aside-trigger" onClick={this.toggleLocalNavigation} onKeyDown={this.toggleLocalNavigation} @@ -515,7 +549,8 @@ export default function ShellBase(props) { } if (layout.Ancillary) { - let { trigger, collapse } = layout.Ancillary.props; + const collapse = layout.Ancillary.props.collapse; + let trigger = layout.Ancillary.props.trigger; if ('trigger' in layout.Ancillary.props) { trigger = @@ -532,7 +567,7 @@ export default function ShellBase(props) { role="button" tabIndex={0} aria-expanded={!collapse} - aria-label={'toggle'} + aria-label="toggle" className="ancillary-trigger aside-trigger" onClick={this.toggleAncillary} onKeyDown={this.toggleAncillary} @@ -616,7 +651,7 @@ export default function ShellBase(props) { [`${prefix}shell`]: true, [`${prefix}shell-${device}`]: true, [`${prefix}shell-${type}`]: true, - [className]: !!className, + [className!]: !!className, }); if (componentName === 'Page') { diff --git a/components/shell/style.js b/components/shell/style.ts similarity index 100% rename from components/shell/style.js rename to components/shell/style.ts diff --git a/components/shell/types.ts b/components/shell/types.ts new file mode 100644 index 0000000000..b6b9081e47 --- /dev/null +++ b/components/shell/types.ts @@ -0,0 +1,180 @@ +import type { HTMLAttributes, ReactElement, ReactNode } from 'react'; +import type { CommonProps } from '../util'; + +/** + * @api Shell + */ +export interface ShellProps extends HTMLAttributes, CommonProps { + /** + * 预设屏幕宽度,会影响 `Navigation`、`LocalNavigation`、`Ancillary` 等是否占据空间 + * @en Preset screen width, which determines whether `Navigation` `LocalNavigation` `Ancillary` take space or not + * @defaultValue 'desktop' + */ + device?: 'tablet' | 'desktop' | 'phone'; + /** + * 样式类型,分浅色主题、深色主题、主题色主题 + * @en type of Shell + * @defaultValue 'light' + */ + type?: 'light' | 'dark' | 'brand'; + /** + * 是否固定Header,采用sticky布局,不支持 IE11 + * @en fixed header or not. Doesn't work under IE11 + * @defaultValue false + */ + fixedHeader?: boolean; +} + +/** + * @api Shell.Navigation + */ +export interface ShellNavigationProps extends HTMLAttributes, CommonProps { + /** + * 方向 + * @en header or asider + * @defaultValue 'hoz' + */ + direction?: 'hoz' | 'ver'; + /** + * 是否折叠(折叠成只有icon状态) + * @en collapse or not + * @defaultValue false + */ + collapse?: boolean; + /** + * 横向模式下,导航排布的位置 + * @en Arrangement of Navigation when direction is hoz + * @defaultValue 'right' + */ + align?: 'left' | 'right' | 'center'; + /** + * 默认按钮触发的展开收起状态 + * @en this will be triggered when collapse changed by inner icon + * @defaultValue func.noop + */ + onCollapseChange?: (collapse?: boolean) => void; + /** + * 菜单展开、收起的触发元素,默认在左上角,不想要可以设置 null 来去掉 + * @en trigger of Shell.Navigation, it placed on top and left of the page, you can set null to remove it + */ + trigger?: ReactNode; + /** + * 是否固定,且需要在在 Shell fixedHeader时生效 + * @en fixed or not, only worked when Shell fixedHeader is true + * @defaultValue false + */ + fixed?: boolean; +} + +/** + * @api Shell.LocalNavigation + */ +export interface ShellLocalNavigationProps extends HTMLAttributes, CommonProps { + /** + * 是否折叠(完全收起) + * @en collapse or not + * @defaultValue false + */ + collapse?: boolean; + /** + * 默认按钮触发的展开收起状态 + * @en this will be triggered when collapse changed by inner icon + * @defaultValue func.noop + */ + onCollapseChange?: (collapse?: boolean) => void; +} + +/** + * @api Shell.ToolDock + */ +export interface ShellToolDockProps extends HTMLAttributes, CommonProps { + /** + * 是否折叠(完全收起) + * @en collapse or not + * @defaultValue false + */ + collapse?: boolean; + /** + * 默认按钮触发的展开收起状态 + * @en this will be triggered when collapse changed by inner icon + * @defaultValue func.noop + */ + onCollapseChange?: (collapse?: boolean) => void; + /** + * 是否固定,且需要在在 Shell fixedHeader 时生效 + * @en fixed or not, only worked when Shell fixedHeader is true + * @defaultValue false + */ + fixed?: boolean; +} + +/** + * @api Shell.Ancillary + */ +export interface ShellAncillaryProps extends HTMLAttributes, CommonProps { + /** + * 是否折叠(完全收起) + * @en collapse or not + * @defaultValue false + */ + collapse?: boolean; + /** + * 默认按钮触发的展开收起状态 + * @en this will be triggered when collapse changed by inner icon + * @defaultValue func.noop + */ + onCollapseChange?: (collapse?: boolean) => void; +} + +export type CommonAttributes = HTMLAttributes & CommonProps; + +export type CollapseMap = { + Navigation: boolean; + LocalNavigation: boolean; + Ancillary: boolean; + ToolDock: boolean; +}; + +export type LayoutProps = { + header?: { + Action?: ReactElement; + Branding?: ReactElement; + Navigation?: ReactElement; + }; + Navigation?: ReactElement; + LocalNavigation?: ReactElement; + MultiTask?: ReactElement; + Ancillary?: ReactElement; + ToolDock?: ReactElement; + taskHeader?: ReactElement; + content?: Array; + page?: ReactElement | []; +}; + +export interface ShellBaseProps extends ShellProps { + component?: ReactElement | unknown; + children?: Array; +} + +export interface BaseProps + extends HTMLAttributes, + CommonProps, + ShellNavigationProps, + ShellLocalNavigationProps, + ShellToolDockProps, + ShellAncillaryProps { + triggerProps?: object; + miniable?: boolean; + component?: ReactElement | unknown; +} + +export interface ShellState { + controll: boolean; + collapseMap: CollapseMap; + device?: 'tablet' | 'desktop' | 'phone'; +} + +export type ChildElement = React.ReactElement< + ShellProps, + (string | React.JSXElementConstructor) & { _typeMark: string } +>; diff --git a/components/shell/util.js b/components/shell/util.ts similarity index 63% rename from components/shell/util.js rename to components/shell/util.ts index 9271b868b4..1c05b64770 100644 --- a/components/shell/util.js +++ b/components/shell/util.ts @@ -1,22 +1,24 @@ +import type { CollapseMap } from './types'; + /** * 判断是否为布尔类型 - * @param {any} val 例:'str' / undefined / null / true / false / 0 - * @return {bool} 例: false / false / false / true / false / false + * @param val - 要判断的值,例如 'str', undefined, null, true, false, 0 等 + * @returns boolean */ -export function isBoolean(val) { +export function isBoolean(val?: unknown): val is boolean { return typeof val === 'boolean'; } -export function getCollapseMap(device) { +export function getCollapseMap(device?: string) { // by default all of them are collapsed - const origin = { + const origin: CollapseMap = { Navigation: true, LocalNavigation: true, Ancillary: true, ToolDock: true, }; - let map = []; + let map: string[] = []; switch (device) { case 'phone': @@ -32,7 +34,7 @@ export function getCollapseMap(device) { break; } - Object.keys(origin).forEach(key => { + Object.keys(origin).forEach((key: keyof CollapseMap) => { if (map.indexOf(key) > -1) { origin[key] = false; }