/* eslint-disable react/no-array-index-key */ import React, { FC, isValidElement } from 'react'; import styled from '@emotion/styled'; import { css } from '@emotion/react'; import { ArrowLeftOutlined, CaretDownOutlined, EllipsisOutlined } from '@ant-design/icons'; import useMedia from 'react-use/lib/useMedia'; import Button from '../../button'; import SimplePagination, { SimplePaginationProps } from '../../simple-pagination'; import { Title, TitleProps } from './Title'; import Dropdown from '../../dropdown'; import Menu from '../../menu'; import Space from '../../space'; import { ContentButtonProps, createButton } from '../../button/button'; interface MenuGroupDescriptor { /** 标题 */ title: React.ReactNode; /** 禁用 */ disabled?: boolean; /** 行为 */ actions: { /** 内容 */ content: React.ReactNode; /** 图标 */ icon?: React.ReactNode; /** 禁用 */ disabled?: boolean; /** 点击回调 */ onClick?: () => void; }[]; } export interface HeaderProps extends TitleProps { /** 返回回调 */ onBack?: () => void; /** 主按钮 */ primaryAction?: ContentButtonProps | React.ReactNode; /** 次级按钮组 */ secondaryActions?: ContentButtonProps[]; /** 额外的需要显示的东西 显示在左侧 */ additionalMetaData?: React.ReactNode | string; /** 下拉菜单 */ actionGroups?: MenuGroupDescriptor[]; /** 额外的需要显示的东西 显示在右侧 */ additionalNavigation?: React.ReactNode; /** 页面级分页 */ pagination?: SimplePaginationProps; breadcrumb?: React.ReactNode; headerStyle?: React.CSSProperties; } const StyledHeader = styled.div` position: relative; padding: 24px 24px 0; ${({ theme }) => css` @media (min-width: ${theme.breakpoints.sm}) { padding-right: 0; padding-left: 0; } `} `; const HeaderRowCondensed = styled.div<{ hasBack: boolean }>` display: flex; align-items: flex-start; justify-content: space-between; ${({ hasBack }) => hasBack && css` @media (max-width: 991px) { display: grid; grid-template-areas: 'breadcrumbs actions' 'title title'; grid-template-columns: auto 1fr; gap: 8px 16px; } `} `; const HeaderRow = styled.div` display: flex; justify-content: space-between; margin-top: 8px; `; const HeaderTitleWrapper = styled.div` flex: 1 1 auto; grid-area: title; align-self: center; `; const HeaderBreadcrumbWrapper = styled.div` flex: 0 0 auto; grid-area: breadcrumbs; max-width: 100%; margin-right: 16px; `; const HeaderLeftAlign = styled.div` display: flex; align-content: flex-start; align-items: center; `; const HeaderAdditionalMetaData = styled.div<{ hasOnBack: boolean }>` margin-left: 0; @media (min-width: 992px) { margin-left: ${({ hasOnBack }) => hasOnBack && '52px'}; } `; const HeaderRightAlign = styled.div` display: flex; flex: 1 1 auto; grid-area: actions; align-items: flex-start; justify-content: flex-end; margin-left: 16px; white-space: nowrap; `; const HeaderAdditionalNavigationWrapper = styled.div` display: flex; flex: 1 0 auto; justify-content: flex-end; margin: 0 8px; margin-right: 0; @media (min-width: 490px) { margin: 0 32px; margin-right: 0; } `; const HeaderPrimaryActionWrapper = styled.div` flex: 0 0 auto; margin-top: 0; margin-left: 10px; `; const IconButton = styled.div` position: relative; display: flex; align-items: center; justify-content: center; width: 36px; min-height: 36px; margin: 0; padding: 2px; cursor: pointer; ${({ theme }) => css` color: #6d7175; border: 1px solid #babfc3; border-radius: ${theme.radii.md}; :hover { background-color: ${theme.colors.gray[50]}; svg { fill: ${theme.colors.gray[600]}; } } :active { background-color: #f1f2f3; svg { fill: ${theme.colors.gray[700]}; } } `} `; export function isPrimaryAction(x: ContentButtonProps | React.ReactNode): x is ContentButtonProps { return !isValidElement(x) && x !== undefined; } function PrimaryActionMarkup({ primaryAction, }: { primaryAction: ContentButtonProps | React.ReactNode; }) { let content = primaryAction; if (isPrimaryAction(primaryAction)) { const type = primaryAction.type === undefined ? 'primary' : primaryAction.type; content = createButton(primaryAction, { type, }); } return {content}; } export const Header: FC = ({ subtitle, titleMetadata, title, pagination, thumbnail, onBack, secondaryActions = [], actionGroups = [], primaryAction, additionalMetaData, additionalNavigation, breadcrumb, headerStyle, }) => { const isNavigationCollapsed = useMedia('(max-width: 768px)'); let actionMenuMarkup; actionMenuMarkup = isNavigationCollapsed ? ( {secondaryActions.map(({ content, onClick, disabled, icon }, index) => ( onClick && onClick(domEvent)} > {icon} {content} ))} {actionGroups.map((actionItem, index) => ( {actionItem.actions.map((item, i) => ( {item.icon} {item.content} ))} ))} } trigger={['click']} > ) : ( {secondaryActions.map(({ content, ...rest }, index) => ( ))} {actionGroups.map((actionItem, index) => ( {actionItem.actions.map((item, i) => ( {item.icon} {item.content} ))} } trigger={['click']} > ))} ); if (actionGroups.length === 0 && secondaryActions.length === 0) { actionMenuMarkup = null; } const paginationMarkup = pagination ? ( ) : null; const primaryActionMarkup = primaryAction ? ( ) : null; return ( {onBack ? ( ) : null} </HeaderTitleWrapper> <HeaderRightAlign> {actionMenuMarkup} {primaryActionMarkup} {paginationMarkup} </HeaderRightAlign> </HeaderRowCondensed> {additionalMetaData || additionalNavigation ? ( <HeaderRow> <HeaderLeftAlign> <HeaderAdditionalMetaData hasOnBack={!!onBack}> {additionalMetaData} </HeaderAdditionalMetaData> </HeaderLeftAlign> <HeaderRightAlign> <HeaderAdditionalNavigationWrapper> {additionalNavigation} </HeaderAdditionalNavigationWrapper> </HeaderRightAlign> </HeaderRow> ) : null} </StyledHeader> ); };