From 006286727416f661cf674e7a5b9929f13015e8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=81=8F=E5=8F=B3?= Date: Fri, 30 Dec 2016 21:41:28 +0800 Subject: [PATCH] Feature 2.6 (#4429) * new Layout Component (#4087) * change Anchor type * new Layout * Component update && add snap * Revert "new Layout Component" (#4131) * add Layout (#4169) * add Layout * update * fix snapshot * Improve layout component 1. update demo code 2. drop `position` of Sider 3. improve demo style --- components/anchor/index.en-US.md | 2 +- components/anchor/index.zh-CN.md | 2 +- components/breadcrumb/Breadcrumb.tsx | 9 +- components/index.tsx | 2 + components/layout/Sider.tsx | 102 ++++ .../__tests__/__snapshots__/demo.test.js.snap | 505 ++++++++++++++++++ components/layout/__tests__/demo.test.js | 3 + components/layout/demo/basic.md | 87 +++ components/layout/demo/custom-trigger.md | 100 ++++ components/layout/demo/side.md | 103 ++++ components/layout/demo/top-side.md | 90 ++++ components/layout/demo/top.md | 59 ++ components/layout/index.en-US.md | 59 ++ components/layout/index.tsx | 5 + components/layout/index.zh-CN.md | 60 +++ components/layout/layout.tsx | 55 ++ components/layout/style/index.less | 65 +++ components/layout/style/index.tsx | 2 + components/menu/style/index.less | 1 + components/style/themes/default.less | 12 +- tests/dekko/dist.test.js | 1 + tests/dekko/lib.test.js | 1 + 22 files changed, 1320 insertions(+), 5 deletions(-) create mode 100644 components/layout/Sider.tsx create mode 100644 components/layout/__tests__/__snapshots__/demo.test.js.snap create mode 100644 components/layout/__tests__/demo.test.js create mode 100644 components/layout/demo/basic.md create mode 100644 components/layout/demo/custom-trigger.md create mode 100644 components/layout/demo/side.md create mode 100644 components/layout/demo/top-side.md create mode 100644 components/layout/demo/top.md create mode 100644 components/layout/index.en-US.md create mode 100644 components/layout/index.tsx create mode 100644 components/layout/index.zh-CN.md create mode 100644 components/layout/layout.tsx create mode 100644 components/layout/style/index.less create mode 100644 components/layout/style/index.tsx diff --git a/components/anchor/index.en-US.md b/components/anchor/index.en-US.md index d5d6e60dc..fadf07ced 100644 --- a/components/anchor/index.en-US.md +++ b/components/anchor/index.en-US.md @@ -1,6 +1,6 @@ --- category: Components -type: Navigation +type: Other cols: 2 title: Anchor --- diff --git a/components/anchor/index.zh-CN.md b/components/anchor/index.zh-CN.md index baa07afc7..c4d0fb51a 100644 --- a/components/anchor/index.zh-CN.md +++ b/components/anchor/index.zh-CN.md @@ -2,7 +2,7 @@ category: Components subtitle: 锚点 cols: 2 -type: Navigation +type: Other title: Anchor --- diff --git a/components/breadcrumb/Breadcrumb.tsx b/components/breadcrumb/Breadcrumb.tsx index a933ab2a5..33dad7b75 100755 --- a/components/breadcrumb/Breadcrumb.tsx +++ b/components/breadcrumb/Breadcrumb.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { cloneElement } from 'react'; import warning from '../_util/warning'; import BreadcrumbItem from './BreadcrumbItem'; +import classNames from 'classnames'; export interface BreadcrumbProps { prefixCls?: string; @@ -10,6 +11,7 @@ export interface BreadcrumbProps { separator?: string | React.ReactNode; itemRender?: (route: any, params: any, routes: Array, paths: Array) => React.ReactNode; style?: React.CSSProperties; + className?: string; }; function getBreadcrumbName(route, params) { @@ -63,7 +65,10 @@ export default class Breadcrumb extends React.Component { render() { let crumbs; - const { separator, prefixCls, routes, params = {}, children, itemRender = defaultItemRender } = this.props; + const { + separator, prefixCls, style, className, routes, params = {}, + children, itemRender = defaultItemRender, + } = this.props; if (routes && routes.length > 0) { const paths: string[] = []; crumbs = routes.map((route) => { @@ -93,7 +98,7 @@ export default class Breadcrumb extends React.Component { }); } return ( -
+
{crumbs}
); diff --git a/components/index.tsx b/components/index.tsx index 6371c3655..78d6c16f8 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -49,6 +49,8 @@ export { default as Input } from './input'; export { default as InputNumber } from './input-number'; +export { default as Layout } from './layout'; + export { default as LocaleProvider } from './locale-provider'; export { default as message } from './message'; diff --git a/components/layout/Sider.tsx b/components/layout/Sider.tsx new file mode 100644 index 000000000..dc8a91653 --- /dev/null +++ b/components/layout/Sider.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import classNames from 'classnames'; +import omit from 'omit.js'; +import Icon from '../icon'; + +export interface SiderProps { + style?: React.CSSProperties; + prefixCls?: string; + className?: string; + collapsible?: boolean; + collapsed?: boolean; + defaultCollapsed?: boolean; + onCollapse?: (collapsed: boolean) => void; + trigger?: React.ReactNode; + width?: number | string; + collapsedWidth?: number; +} + +export default class Sider extends React.Component { + + static defaultProps = { + prefixCls: 'ant-layout-sider', + collapsible: false, + defaultCollapsed: false, + width: 200, + collapsedWidth: 64, + style: {}, + }; + + constructor(props) { + super(props); + let collapsed; + if ('collapsed' in props) { + collapsed = props.collapsed; + } else { + collapsed = props.defaultCollapsed; + } + this.state = { + collapsed, + }; + } + + componentWillReceiveProps(nextProps) { + if ('collapsed' in nextProps) { + this.setState({ + collapsed: nextProps.collapsed, + }); + } + } + + setCollapsed = (collapsed) => { + if (!('collapsed' in this.props)) { + this.setState({ + collapsed, + }); + } + const { onCollapse } = this.props; + if (onCollapse) { + onCollapse(collapsed); + } + } + + toggle = () => { + const collapsed = !this.state.collapsed; + this.setCollapsed(collapsed); + } + + render() { + const { + prefixCls, className, collapsible, trigger, style, width, collapsedWidth, + ...others, + } = this.props; + const divProps = omit(others, ['collapsed', 'defaultCollapsed', 'onCollapse']); + const siderCls = classNames(className, prefixCls, { + [`${prefixCls}-collapsed`]: !!this.state.collapsed, + [`${prefixCls}-has-trigger`]: !!trigger, + }); + const divStyle = { + ...style, + flex: `0 0 ${this.state.collapsed ? collapsedWidth : width}px`, + }; + const iconObj = { + 'expanded': , + 'collapsed': , + }; + const status = this.state.collapsed ? 'collapsed' : 'expanded'; + const defaultTrigger = iconObj[status]; + const triggerDom = ( + trigger !== null ? + (
+ {trigger || defaultTrigger} +
) + : null + ); + return ( +
+ {this.props.children} + {collapsible && triggerDom} +
+ ); + } +} diff --git a/components/layout/__tests__/__snapshots__/demo.test.js.snap b/components/layout/__tests__/__snapshots__/demo.test.js.snap new file mode 100644 index 000000000..c6c3e93fe --- /dev/null +++ b/components/layout/__tests__/__snapshots__/demo.test.js.snap @@ -0,0 +1,505 @@ +exports[`test renders ./components/layout/demo/custom-trigger.md correctly 1`] = ` +
+
+ +
+
+ +
+
+ content +
+
+
+`; + +exports[`test renders ./components/layout/demo/demo.md correctly 1`] = ` +
+
+ Header +
+
+
+ Sider +
+
+ Content +
+
+ +
+`; + +exports[`test renders ./components/layout/demo/side.md correctly 1`] = ` +
+
+ +
+
+ +
+ +
+ content +
+
+ +
+
+`; + +exports[`test renders ./components/layout/demo/top.md correctly 1`] = ` +
+
+ +
+ +
+ content +
+
+ +
+`; + +exports[`test renders ./components/layout/demo/top-side.md correctly 1`] = ` +
+
+
+ + +
+
+
+ +
+
+ content +
+
+ +
+`; diff --git a/components/layout/__tests__/demo.test.js b/components/layout/__tests__/demo.test.js new file mode 100644 index 000000000..fe6e16d22 --- /dev/null +++ b/components/layout/__tests__/demo.test.js @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('layout'); diff --git a/components/layout/demo/basic.md b/components/layout/demo/basic.md new file mode 100644 index 000000000..30d552ec5 --- /dev/null +++ b/components/layout/demo/basic.md @@ -0,0 +1,87 @@ +--- +order: 0 +title: + zh-CN: 基本结构 + en-US: Basic Structure +--- + +## zh-CN + +典型的页面布局。 + +## en-US + +Classic page layouts. + +````jsx +import { Layout } from 'antd'; +const { Header, Footer, Sider, Content } = Layout; + +ReactDOM.render( +
+ +
Header
+ Content +
Footer
+
+ + +
Header
+ + Sider + Content + +
Footer
+
+ + +
Header
+ + Content + Sider + +
Footer
+
+ + + Sider + +
Header
+ Content +
Footer
+
+
+
+, mountNode); +```` + + diff --git a/components/layout/demo/custom-trigger.md b/components/layout/demo/custom-trigger.md new file mode 100644 index 000000000..d5c90a3cf --- /dev/null +++ b/components/layout/demo/custom-trigger.md @@ -0,0 +1,100 @@ +--- +order: 4 +title: + zh-CN: 自定义触发器 + en-US: Custom trigger +--- + +## zh-CN + +要使用自定义触发器,可以设置 `trigger={null}` 来隐藏默认设定。 + +## en-US + +If you want to use a customized trigger, you can hide the default one by setting `trigger={null}`. + +````jsx +import { Layout, Menu, Icon } from 'antd'; +const { Header, Sider, Content } = Layout; + +class SiderDemo extends React.Component { + state = { + collapsed: false, + }; + toggle = () => { + this.setState({ + collapsed: !this.state.collapsed, + }); + } + render() { + return ( + + +
+ + + + nav 1 + + + + nav 2 + + + + nav 3 + + + + +
+ +
+ + Content + +
+ + ); + } +} + +ReactDOM.render(, mountNode); +```` + +````css +#components-layout-demo-custom-trigger .trigger { + font-size: 18px; + line-height: 64px; + padding: 0 16px; + cursor: pointer; + transition: color .3s; +} + +#components-layout-demo-custom-trigger .trigger:hover { + color: #108ee9; +} + +#components-layout-demo-custom-trigger .logo { + height: 32px; + background: #333; + border-radius: 6px; + margin: 16px; +} + +#components-layout-demo-custom-trigger .ant-layout-sider-collapsed .anticon { + font-size: 16px; +} + +#components-layout-demo-custom-trigger .ant-layout-sider-collapsed .nav-text { + display: none; +} +```` diff --git a/components/layout/demo/side.md b/components/layout/demo/side.md new file mode 100644 index 000000000..32c86ac56 --- /dev/null +++ b/components/layout/demo/side.md @@ -0,0 +1,103 @@ +--- +order: 3 +title: + zh-CN: 侧边布局 + en-US: Sider +--- + +## zh-CN + +多用在两列式布局。 + +## en-US + +Be used in the two-columns layout. + +````jsx +import { Layout, Menu, Breadcrumb, Icon } from 'antd'; +const { Header, Content, Footer, Sider } = Layout; + +class SiderDemo extends React.Component { + state = { + collapsed: false, + }; + onCollapse = (collapsed) => { + console.log(collapsed); + this.setState({ collapsed }); + } + render() { + return ( + + +
+ + + + nav 1 + + + + nav 2 + + + + nav 3 + + + + nav 4 + + + + nav 5 + + + + nav 6 + + + + +
+ + + Home + List + App + +
+ content +
+
+
+ Ant Design ©2016 Created by Ant UED +
+ + + ); + } +} + +ReactDOM.render(, mountNode); +```` + +````css +#components-layout-demo-side .logo { + height: 32px; + background: #333; + border-radius: 6px; + margin: 16px; +} + +#components-layout-demo-side .ant-layout-sider-collapsed .anticon { + font-size: 16px; +} + +#components-layout-demo-side .ant-layout-sider-collapsed .nav-text { + display: none; +} +```` diff --git a/components/layout/demo/top-side.md b/components/layout/demo/top-side.md new file mode 100644 index 000000000..a9196c5fd --- /dev/null +++ b/components/layout/demo/top-side.md @@ -0,0 +1,90 @@ +--- +order: 2 +title: + zh-CN: 顶部-侧边布局 + en-US: Header-Sider +--- + +## zh-CN + +多用在同时拥有顶部导航及侧边栏的页面。 + +## en-US + +Be used in the page which has both the top navigation and the sidebar. + +````jsx +import { Layout, Menu, Breadcrumb, Icon } from 'antd'; +const { SubMenu } = Menu; +const { Header, Content, Footer, Sider } = Layout; + +ReactDOM.render( + +
+
+ + nav 1 + nav 2 + nav 3 + +
+ + + Home + List + App + + + + + subnav 1}> + option1 + option2 + option3 + option4 + + subnav 2}> + option5 + option6 + option7 + option8 + + subnav 3}> + option9 + option10 + option11 + option12 + + + + + Content + + + +
+ Ant Design ©2016 Created by Ant UED +
+
+, mountNode); +```` + +````css +#components-layout-demo-top-side .logo { + width: 120px; + height: 31px; + background: #333; + border-radius: 6px; + margin: 16px 28px 16px 0; + float: left; +} +```` diff --git a/components/layout/demo/top.md b/components/layout/demo/top.md new file mode 100644 index 000000000..0e6d3eb38 --- /dev/null +++ b/components/layout/demo/top.md @@ -0,0 +1,59 @@ +--- +order: 1 +title: + zh-CN: 上中下布局 + en-US: Header-Content-Footer +--- + +## zh-CN + +最基本的『上-中-下』布局。 + +## en-US + +The most basic "header-content-footer" layout. + +````jsx +import { Layout, Menu, Breadcrumb } from 'antd'; +const { Header, Content, Footer } = Layout; + +ReactDOM.render( + +
+
+ + nav 1 + nav 2 + nav 3 + +
+ + + Home + List + App + +
Content
+
+
+ Ant Design ©2016 Created by Ant UED +
+
+, mountNode); +```` + +````css +#components-layout-demo-top .logo { + width: 120px; + height: 31px; + background: #333; + border-radius: 6px; + margin: 16px 24px 16px 0; + float: left; +} +```` diff --git a/components/layout/index.en-US.md b/components/layout/index.en-US.md new file mode 100644 index 000000000..adad92df3 --- /dev/null +++ b/components/layout/index.en-US.md @@ -0,0 +1,59 @@ +--- +category: Components +type: Layout +cols: 1 +title: Layout +--- + +When you are handling the overall layout of a page, this component might be helpfull. + +## Overview + +- `Layout`: The layout wrapper, in which `Header` `Sider` `Content` `Footer` or `Layout` itself can be nested. +- `Header`: The top layout with default style. +- `Sider`: The sidebar with default style and basic functions. +- `Content`: The content layout with default style. +- `Footer`: The bottom layout with default style. + +> Base on `flex layout`, please pay attention to the [compatibility](http://caniuse.com/#search=flex). + +## API + +```jsx + +
header
+ + left sidebar + main content + right sidebar + +
footer
+
+``` + +### Layout + +The wrapper. + +Property | Description | Type | Default +-----|-----|-----|------ +style | to custom the styles | Object | - +className | container className | string | - + +> API of `Layout.Header` `Layout.Footer` `Layout.Content` is the same with `Layout`. + +### Layout.Sider + +The sidebar. + +Property | Description | Type | Default +-----|-----|-----|------ +collapsible | whether can be collapsed | Boolean | false +defaultCollapsed | to set the initial status | Boolean | false | +collapsed | to set the current status | Boolean | - +onCollapse | the callback function, can be executed when you switch the sidebar, available only `collapsible: true` | (collapsed) => {} | - +trigger | specify the customized trigger, set to null to hide the trigger | React.ReactNode or null | - | +width | width of the sidebar | Number or String | 200 +collapsedWidth | width of the collapsed sidebar, available only `collapsible: true` | Number | 64 +style | to custom the styles | Object | - +className | container className | string | - diff --git a/components/layout/index.tsx b/components/layout/index.tsx new file mode 100644 index 000000000..fbc87672b --- /dev/null +++ b/components/layout/index.tsx @@ -0,0 +1,5 @@ +import Layout from './layout'; +import Sider from './sider'; + +Layout.Sider = Sider; +export default Layout; diff --git a/components/layout/index.zh-CN.md b/components/layout/index.zh-CN.md new file mode 100644 index 000000000..4cb0080a8 --- /dev/null +++ b/components/layout/index.zh-CN.md @@ -0,0 +1,60 @@ +--- +category: Components +subtitle: 布局 +type: Layout +cols: 1 +title: Layout +--- + +可协助进行页面级整体布局。 + +## 概述 + +- `Layout`:布局容器,其下可嵌套 `Header` `Sider` `Content` `Footer` 或 `Layout` 本身。 +- `Header`:顶部布局,自带默认样式。 +- `Sider`:侧边栏,自带默认样式及基本功能。 +- `Content`:内容部分,自带默认样式。 +- `Footer`:底部布局,自带默认样式。 + +> 注意:采用 flex 布局实现,请注意[浏览器兼容性](http://caniuse.com/#search=flex)问题。 + +## API + +```jsx + +
header
+ + left sidebar + main content + right sidebar + +
footer
+
+``` + +### Layout + +布局容器。 + +| 参数 | 说明 | 类型 | 默认值 | +|----------|------------------------------------------|-------------|-------| +| style | 指定样式 | Object | - | +| className | 容器 className | string | - | + +> `Layout.Header` `Layout.Footer` `Layout.Content` API 与 `Layout` 相同 + +### Layout.Sider + +侧边栏。 + +| 参数 | 说明 | 类型 | 默认值 | +|----------|-----------------------------------------|------------|-------| +| collapsible | 是否可收起 | Boolean | false | +| defaultCollapsed | 是否默认收起 | Boolean | false | +| collapsed | 当前收起状态 | Boolean | - | +| onCollapse | 展开-收起时的回调函数,仅当 `collapsible:true` 时生效 | (collapsed) => {} | - | +| trigger | 自定义 trigger,设置为 null 时隐藏 trigger | React.ReactNode or null | - | +| width | 宽度 | Number or String | 200 | +| collapsedWidth | 收缩宽度,仅当 `collapsible:true` 时生效 | Number | 64 | +| style | 指定样式 | Object | - | +| className | 容器 className | string | - | diff --git a/components/layout/layout.tsx b/components/layout/layout.tsx new file mode 100644 index 000000000..eb1181775 --- /dev/null +++ b/components/layout/layout.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import classNames from 'classnames'; + +export interface BasicProps { + style?: React.CSSProperties; + prefixCls?: string; + className?: string; +} + +function generator(props) { + return (Basic) : any => { + return class Adapter extends React.Component { + static Header: any; + static Footer: any; + static Content: any; + static Sider: any; + render() { + const { prefixCls } = props; + return ; + } + }; + }; +} + +class Basic extends React.Component { + render() { + const { prefixCls, className, ...others } = this.props; + const divCls = classNames(className, prefixCls); + return ( +
+ ); + } +} + +const Layout = generator({ + prefixCls: 'ant-layout', +})(Basic); + +const Header = generator({ + prefixCls: 'ant-layout-header', +})(Basic); + +const Footer = generator({ + prefixCls: 'ant-layout-footer', +})(Basic); + +const Content = generator({ + prefixCls: 'ant-layout-content', +})(Basic); + +Layout.Header = Header; +Layout.Footer = Footer; +Layout.Content = Content; + +export default Layout; diff --git a/components/layout/style/index.less b/components/layout/style/index.less new file mode 100644 index 000000000..552bae93b --- /dev/null +++ b/components/layout/style/index.less @@ -0,0 +1,65 @@ +@import "../../style/themes/default"; +@import "../../style/mixins/index"; + +@layout-prefix-cls: ~"@{ant-prefix}-layout"; + +.@{layout-prefix-cls} { + display: flex; + flex-wrap: wrap; + flex: auto; + overflow: auto; + background: @layout-body-background; + + &-header, + &-footer { + flex: 1 0 100%; + } + + &-header { + background: @layout-header-background; + padding: @layout-header-padding; + height: @layout-header-height; + line-height: @layout-header-height; + } + + &-footer { + padding: @layout-footer-padding; + color: @text-color; + font-size: @font-size-base; + } + + &-content { + flex: auto; + } + + &-sider { + flex: 0 0 200px; + transition: all .3s ease; + position: relative; + background: @layout-sider-background; + + &-has-trigger { + padding-bottom: @layout-trigger-height; + } + + &-right { + order: 1; + } + + &-collapsed { + flex: 0 0 64px; + } + + &-trigger { + position: absolute; + text-align: center; + width: 100%; + bottom: 0; + cursor: pointer; + height: @layout-trigger-height; + line-height: @layout-trigger-height; + background: tint(@heading-color, 20%); + color: #fff; + } + } +} diff --git a/components/layout/style/index.tsx b/components/layout/style/index.tsx new file mode 100644 index 000000000..3a3ab0de5 --- /dev/null +++ b/components/layout/style/index.tsx @@ -0,0 +1,2 @@ +import '../../style/index.less'; +import './index.less'; diff --git a/components/menu/style/index.less b/components/menu/style/index.less index 5faad0d0a..85f7b506b 100644 --- a/components/menu/style/index.less +++ b/components/menu/style/index.less @@ -178,6 +178,7 @@ .@{iconfont-css-prefix} { min-width: 14px; margin-right: 8px; + transition: all .3s; } } diff --git a/components/style/themes/default.less b/components/style/themes/default.less index 1e05feca8..5b8f28ae8 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -119,10 +119,20 @@ @screen-lg-min : @screen-lg; @screen-lg-max : (@screen-lg-min - 1); -// Layout and Grid system +// Grid system @grid-columns : 24; @grid-gutter-width : 0; +// Layout +@layout-body-background : #ececec; +@layout-header-background : @heading-color; +@layout-header-height : 64px; +@layout-header-padding : 0 50px; +@layout-footer-padding : 24px 50px; +@layout-sider-background : @heading-color; +@layout-content-margin : 24px; +@layout-trigger-height : 48px; + // z-index list @zindex-affix : 10; @zindex-back-top : 10; diff --git a/tests/dekko/dist.test.js b/tests/dekko/dist.test.js index cffcc298e..3f6570ed5 100644 --- a/tests/dekko/dist.test.js +++ b/tests/dekko/dist.test.js @@ -9,3 +9,4 @@ $('dist') // eslint-disable-next-line console.log('`dist` directory is valid.'); + diff --git a/tests/dekko/lib.test.js b/tests/dekko/lib.test.js index a67e68e2c..b83b26c24 100644 --- a/tests/dekko/lib.test.js +++ b/tests/dekko/lib.test.js @@ -25,3 +25,4 @@ $('lib/*/style') // eslint-disable-next-line console.log('`lib` directory is valid.'); +