index.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import React, { AriaRole, ComponentClass, CSSProperties } from 'react';
  2. import cls from 'classnames';
  3. import PropTypes from 'prop-types';
  4. import { cssClasses } from '@douyinfe/semi-foundation/layout/constants';
  5. import '@douyinfe/semi-foundation/layout/layout.scss';
  6. import LayoutContext, { ContextType } from './layout-context';
  7. import Sider from './Sider';
  8. export type { ResponsiveMap, SiderProps } from './Sider';
  9. const htmlTag = {
  10. Header: 'header',
  11. Footer: 'footer',
  12. Content: 'main',
  13. Layout: 'section'
  14. };
  15. function generator<P extends { type?: string; tagName?: string; role?: AriaRole; 'aria-label'?: string }>(type: string): (ComponentType: ComponentClass<{ type?: string; tagName?: string } & P>) => ComponentClass<P> {
  16. const tagName = htmlTag[type];
  17. const typeName = type.toLowerCase();
  18. return (BasicComponent): ComponentClass<P> => class Adapter extends React.PureComponent<P> {
  19. render() {
  20. return <BasicComponent role={this.props.role} aria-label={this.props['aria-label']} type={typeName} tagName={tagName} {...this.props} />;
  21. }
  22. };
  23. }
  24. export interface BasicProps {
  25. prefixCls?: string;
  26. style?: CSSProperties;
  27. className?: string;
  28. tagName?: keyof HTMLElementTagNameMap;
  29. type?: string;
  30. children?: React.ReactNode | undefined
  31. }
  32. class Basic extends React.PureComponent<BasicProps> {
  33. static propTypes = {
  34. prefixCls: PropTypes.string,
  35. style: PropTypes.object,
  36. className: PropTypes.string,
  37. };
  38. static defaultProps = {
  39. prefixCls: cssClasses.PREFIX,
  40. };
  41. render() {
  42. const { prefixCls, type, className, children, tagName, ...others } = this.props;
  43. const classString = cls(className, `${prefixCls}-${type}`);
  44. return React.createElement(tagName, { className: classString, ...others }, children);
  45. }
  46. }
  47. const Header = generator<BasicProps>('Header')(Basic);
  48. const Footer = generator<BasicProps>('Footer')(Basic);
  49. const Content = generator<BasicProps>('Content')(Basic);
  50. export interface BasicLayoutProps {
  51. prefixCls?: string;
  52. style?: CSSProperties;
  53. className?: string;
  54. children?: React.ReactNode;
  55. hasSider?: boolean;
  56. tagName?: keyof HTMLElementTagNameMap
  57. }
  58. export interface BasicLayoutState {
  59. siders: Array<string>
  60. }
  61. class Layout extends React.Component<BasicLayoutProps, BasicLayoutState> {
  62. static propTypes = {
  63. prefixCls: PropTypes.string,
  64. style: PropTypes.object,
  65. className: PropTypes.string,
  66. };
  67. static defaultProps = {
  68. prefixCls: cssClasses.PREFIX,
  69. tagName: 'section'
  70. };
  71. static Header = Header;
  72. static Footer = Footer;
  73. static Content = Content;
  74. static Sider = Sider;
  75. constructor(props: BasicLayoutProps) {
  76. super(props);
  77. this.state = {
  78. siders: [],
  79. };
  80. }
  81. getSiderHook(): ContextType['siderHook'] {
  82. return {
  83. addSider: (id: string): void => {
  84. this.setState(state => ({
  85. siders: [...state.siders, id],
  86. }));
  87. },
  88. removeSider: (id: string): void => {
  89. this.setState(state => ({
  90. siders: state.siders.filter(curr => curr !== id),
  91. }));
  92. },
  93. };
  94. }
  95. render() {
  96. const { prefixCls, className, children, hasSider, tagName, ...others } = this.props;
  97. const { siders } = this.state;
  98. const classString = cls(className, prefixCls, {
  99. [`${prefixCls}-has-sider`]: typeof hasSider === 'boolean' && hasSider || siders.length > 0 || React.Children.toArray(children).some((child: React.ReactNode) => {
  100. return React.isValidElement(child) && child.type && (child.type as any).elementType === "Layout.Sider";
  101. }),
  102. });
  103. const Tag: any = tagName;
  104. return (
  105. <LayoutContext.Provider value={{ siderHook: this.getSiderHook() }}>
  106. <Tag className={classString} {...others}>
  107. {children}
  108. </Tag>
  109. </LayoutContext.Provider>
  110. );
  111. }
  112. }
  113. export { Layout };
  114. export default Layout;