-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(layout): add layout component (#722)
Co-authored-by: Dan <liudan548326@yeah.net>
- Loading branch information
Lee Hon
and
Dan
authored
Jan 22, 2021
1 parent
66fa6a1
commit 31e4517
Showing
13 changed files
with
559 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Subtitle, ArgsTable } from '@storybook/addon-docs/blocks'; | ||
import Layout from './index'; | ||
|
||
# Layout 布局 | ||
|
||
<Subtitle>协助进行页面级整体布局。</Subtitle> | ||
|
||
## 参数说明 | ||
|
||
<ArgsTable of={Layout} /> | ||
|
||
## 代码演示 | ||
|
||
### 样式 - 无侧导航页面 | ||
[Example](/?path=/story/basic-components-layout--default) | ||
|
||
### 样式 - 收缩侧导航页面 | ||
|
||
[Example](/?path=/story/basic-components-layout--sider) | ||
|
||
### 样式 - 悬浮式展开侧导航 | ||
|
||
[Example](/?path=/story/basic-components-layout--suspend) | ||
|
||
### 样式 - 嵌入式展开侧导航 | ||
|
||
[Example](/?path=/story/basic-components-layout--embed) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import React, { useState } from 'react'; | ||
import { Story, Meta } from '@storybook/react/types-6-0'; | ||
import Docs from './Layout.mdx'; | ||
import { LayoutProps } from './interfaces'; | ||
import Layout from './index'; | ||
import Button from '../button'; | ||
import './style'; | ||
|
||
export default { | ||
title: 'Basic Components/Layout', | ||
component: Layout, | ||
parameters: { | ||
docs: { | ||
page: Docs, | ||
}, | ||
}, | ||
} as Meta; | ||
|
||
const Template: Story<LayoutProps> = (args) => <Layout {...args} />; | ||
|
||
export const Default = Template.bind({}); | ||
|
||
Default.args = { | ||
children: ( | ||
<> | ||
<Layout.Header style={{ backgroundColor: '#5F87FF' }} /> | ||
<Layout.Content style={{ backgroundColor: '#1248E9', height: 400 }} /> | ||
</> | ||
), | ||
}; | ||
|
||
export const Sider = Template.bind({}); | ||
|
||
Sider.args = { | ||
children: ( | ||
<> | ||
<Layout.Sider defaultCollapsed style={{ backgroundColor: '#3867F4' }} /> | ||
<Layout> | ||
<Layout.Header style={{ backgroundColor: '#5F87FF' }} /> | ||
<Layout.Content style={{ backgroundColor: '#1248E9', height: 400 }} /> | ||
</Layout> | ||
</> | ||
), | ||
}; | ||
|
||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
const DemoListTemplate: Story = (args) => <div {...args} />; | ||
|
||
export const Suspend = DemoListTemplate.bind({}); | ||
|
||
const SuspendDemo = ({ suspend }: { suspend?: 'left' | 'right' }) => { | ||
const [collapsed, setCollapsed] = useState<boolean>(false); | ||
return ( | ||
<Layout> | ||
<Layout.Sider collapsed={collapsed} suspendedPosition={suspend} style={{ backgroundColor: '#3867F4' }}> | ||
<Button onClick={() => setCollapsed(!collapsed)} type="secondary"> | ||
点我 | ||
</Button> | ||
</Layout.Sider> | ||
<Layout> | ||
<Layout.Header style={{ backgroundColor: '#5F87FF' }} /> | ||
<Layout.Content style={{ backgroundColor: '#1248E9', height: 400 }} /> | ||
</Layout> | ||
</Layout> | ||
); | ||
}; | ||
|
||
Suspend.args = { | ||
children: <SuspendDemo suspend="left" />, | ||
}; | ||
|
||
export const Embed = DemoListTemplate.bind({}); | ||
|
||
Embed.args = { | ||
children: <SuspendDemo />, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
import { renderHook, act } from '@testing-library/react-hooks'; | ||
import Layout from '../layout'; | ||
import useSiders from '../useSiders'; | ||
|
||
describe('Testing Layout', () => { | ||
it('should be stable', () => { | ||
const { asFragment } = render( | ||
<Layout> | ||
<Layout.Sider defaultCollapsed /> | ||
<Layout> | ||
<Layout.Header /> | ||
<Layout.Content /> | ||
</Layout> | ||
</Layout> | ||
); | ||
expect(asFragment()).toMatchSnapshot(); | ||
}); | ||
|
||
test('Content maxWidth', () => { | ||
const { getByText } = render( | ||
<Layout> | ||
<Layout.Content maxWidth="auto">content</Layout.Content> | ||
</Layout> | ||
); | ||
const contentElement = getByText('content'); | ||
expect(contentElement.style.getPropertyValue('--layout-content-maxWidth')).toBe('100%'); | ||
expect(contentElement.style.getPropertyValue('--layout-content-grow')).toBe('1'); | ||
expect(contentElement.style.getPropertyValue('margin')).toBe('0px 20px'); | ||
}); | ||
|
||
test('Content margin', () => { | ||
const { getByText } = render( | ||
<Layout> | ||
<Layout.Content maxWidth="auto" margin={10}> | ||
content | ||
</Layout.Content> | ||
</Layout> | ||
); | ||
expect(getByText('content').style.getPropertyValue('margin')).toBe('0px 10px'); | ||
}); | ||
|
||
test('useSiders', () => { | ||
const { result } = renderHook(() => useSiders()); | ||
const _updateSiders = result.current[3]; | ||
act(() => { | ||
_updateSiders({ id: '1', width: 200, collapsedWidth: 80, suspendedPosition: 'left' }); | ||
_updateSiders({ id: '2', width: 300, collapsedWidth: 100, suspendedPosition: 'right' }); | ||
_updateSiders({ id: '3', width: 100, collapsedWidth: 50 }); | ||
}); | ||
const [siders, sidersWidth, removeSider, updateSiders, margin] = result.current; | ||
expect(siders.length).toBe(3); | ||
expect(sidersWidth).toBe(600); | ||
expect(margin).toStrictEqual([80, 100]); | ||
act(() => { | ||
removeSider('1'); | ||
}); | ||
expect(result.current[0].length).toBe(2); | ||
expect(result.current[1]).toBe(400); | ||
expect(result.current[4]).toStrictEqual([0, 100]); | ||
act(() => { | ||
updateSiders({ id: '3', width: 100, collapsedWidth: 50, suspendedPosition: 'left' }); | ||
}); | ||
expect(result.current[4]).toStrictEqual([50, 100]); | ||
act(() => { | ||
removeSider('2'); | ||
}); | ||
expect(result.current[4]).toStrictEqual([50, 0]); | ||
}); | ||
}); |
27 changes: 27 additions & 0 deletions
27
src/components/layout/__tests__/__snapshots__/Layout.test.tsx.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Testing Layout should be stable 1`] = ` | ||
<DocumentFragment> | ||
<section | ||
class="gio-layout" | ||
style="margin-left: 0px; margin-right: 0px;" | ||
> | ||
<aside | ||
class="gio-layout-sider gio-layout-sider-collapsed" | ||
style="--layout-sider-width: 200px; --layout-sider-collapsedWidth: 80px; --layout-sider-collapsedWidth-negative: -80px;" | ||
/> | ||
<section | ||
class="gio-layout" | ||
style="margin-left: 0px; margin-right: 0px;" | ||
> | ||
<header | ||
class="gio-layout-header" | ||
/> | ||
<main | ||
class="gio-layout-content" | ||
style="--layout-content-maxWidth: 1200px; --layout-content-grow: 0; margin: 0px 20px;" | ||
/> | ||
</section> | ||
</section> | ||
</DocumentFragment> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import React, { useContext, useEffect, useMemo } from 'react'; | ||
import classNames from 'classnames'; | ||
import { isNumber } from 'lodash'; | ||
import { LayoutContentProps } from './interfaces'; | ||
import usePrefixCls from '../../utils/hooks/use-prefix-cls'; | ||
import { LayoutContext } from './layout'; | ||
|
||
const Content = ({ | ||
prefixCls: customizePrefixCls, | ||
className, | ||
style, | ||
children, | ||
maxWidth = 1200, | ||
margin = 20 | ||
}: LayoutContentProps) => { | ||
|
||
const { layoutState, setContentState } = useContext(LayoutContext); | ||
useEffect(() => { | ||
setContentState({ maxWidth: isNumber(maxWidth) ? maxWidth : 0, margin }); | ||
}, [margin, maxWidth, setContentState]); | ||
|
||
const prefixCls = usePrefixCls('layout-content', customizePrefixCls); | ||
|
||
const mergedStyle: React.CSSProperties = useMemo(() => ({ | ||
'--layout-content-maxWidth': maxWidth === 'auto' ? '100%' : `${maxWidth}px`, | ||
'--layout-content-grow': maxWidth === 'auto' ? 1 : 0, | ||
...((maxWidth === 'auto' || !layoutState.wide) ? { margin: `0 ${margin}px`} : {}), | ||
...style | ||
}), [maxWidth, layoutState.wide, margin, style]); | ||
|
||
return ( | ||
<main | ||
className={classNames(prefixCls, className)} | ||
style={mergedStyle} | ||
> | ||
{children} | ||
</main> | ||
); | ||
} | ||
|
||
export default Content; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import React from 'react'; | ||
import classNames from 'classnames'; | ||
import { LayoutProps } from './interfaces'; | ||
import usePrefixCls from '../../utils/hooks/use-prefix-cls'; | ||
|
||
const Header = ({ | ||
prefixCls: customizePrefixCls, | ||
className, | ||
style, | ||
children | ||
}: LayoutProps) => { | ||
|
||
const prefixCls = usePrefixCls('layout-header', customizePrefixCls); | ||
return ( | ||
<header className={classNames(prefixCls, className)} style={style}>{children}</header> | ||
); | ||
} | ||
|
||
export default Header; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import Layout from './layout'; | ||
|
||
export { LayoutProps } from './interfaces'; | ||
export default Layout; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
export interface LayoutProps { | ||
className?: string; | ||
prefixCls?: string; | ||
children?: React.ReactNode | React.ReactNode[]; | ||
style?: React.CSSProperties; | ||
} | ||
|
||
export interface LayoutContentProps extends LayoutProps { | ||
/** | ||
Content 区域的最大宽度 | ||
*/ | ||
maxWidth?: number | 'auto'; | ||
/* | ||
Content 区域的外边距 | ||
*/ | ||
margin?: number; | ||
} | ||
|
||
export interface LayoutSiderProps extends LayoutProps { | ||
/* | ||
收缩状态的宽度 | ||
*/ | ||
collapsedWidth?: number; | ||
/* | ||
展开状态的宽度 | ||
*/ | ||
width?: number; | ||
/* | ||
默认的伸缩状态 | ||
*/ | ||
defaultCollapsed?: boolean; | ||
/* | ||
控制展开与收缩 | ||
*/ | ||
collapsed?: boolean; | ||
/* | ||
当伸缩状态变化时的回调函数 | ||
*/ | ||
onCollapse?: (collapsed: boolean) => void; | ||
/* | ||
悬浮式侧边栏的位置 | ||
*/ | ||
suspendedPosition?: 'left' | 'right'; | ||
} | ||
|
||
export interface SiderState { | ||
id: string; | ||
width: number; | ||
collapsedWidth: number; | ||
suspendedPosition?: 'left' | 'right'; | ||
} | ||
|
||
export interface ContentState { | ||
maxWidth: number; | ||
margin: number; | ||
} | ||
|
||
export interface LayoutState { | ||
wide: boolean; | ||
} | ||
|
||
export interface LayoutContextType { | ||
layoutState: LayoutState; | ||
contentState: ContentState; | ||
setLayoutState: (layoutState: Partial<LayoutState> | (() => Partial<LayoutState>)) => void; | ||
setContentState: (contentState: Partial<ContentState> | (() => Partial<ContentState>)) => void; | ||
removeSider: (siderId: string) => void; | ||
updateSiders: (sider: SiderState) => void; | ||
} |
Oops, something went wrong.
31e4517
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs: