diff --git a/src/Page/Content/style.ts b/src/Page/Content/style.ts index 9646704..a6217d7 100644 --- a/src/Page/Content/style.ts +++ b/src/Page/Content/style.ts @@ -4,10 +4,7 @@ export const useStyles = createStyles( ({ css, token }) => { return { root: css` - padding: ${token.padding}px; - background-color: ${token.colorBgBase}; - border-radius: ${token.borderRadius}px; - box-shadow: ${token.boxShadowTertiary}; + padding-top: ${token.paddingXS}px; `, }; }, diff --git a/src/Page/Header/Icon.tsx b/src/Page/Header/Icon.tsx new file mode 100644 index 0000000..83241df --- /dev/null +++ b/src/Page/Header/Icon.tsx @@ -0,0 +1,42 @@ +import { Avatar } from 'antd'; +import React, { useMemo } from 'react'; + +export interface HeaderIconProps { + /** 样式名 */ + className?: string; + /** 图像无法显示时的替代文本 */ + alt?: string; + /** 图标的形状,默认为 square */ + shape?: 'circle' | 'square'; + /** 设置图标的大小 */ + size?: number | 'large' | 'small' | 'default'; + /** 图标的资源地址或者图片元素 */ + src: React.ReactNode; + /** 样式 */ + style?: React.CSSProperties; +} + +export const getIconSize = (size: HeaderIconProps['size']) => { + if (typeof size === 'number') { + return size; + } + switch (size) { + case 'small': { + return 40; + } + case 'large': { + return 104; + } + default: { + return 64; + } + } +}; + +export const HeaderIcon: React.FC = props => { + const { shape = 'square', size, src, ...otherProps } = props; + const sizeNumber = useMemo(() => { + return getIconSize(size); + }, [size]); + return ; +}; diff --git a/src/Page/Header/index.tsx b/src/Page/Header/index.tsx index 33a7a86..d82428f 100644 --- a/src/Page/Header/index.tsx +++ b/src/Page/Header/index.tsx @@ -1,13 +1,17 @@ import { Badge, Flex, Skeleton, Tooltip } from 'antd'; import type { BadgeProps } from 'antd'; -import React, { useCallback, useContext } from 'react'; +import React, { useCallback, useContext, useMemo } from 'react'; import Divider from '@/Divider'; +import Typography from '@/Typography'; import { PageContext } from '../PageContext'; import { HeaderButtonGroup, type HeaderButtonGroupProps } from './ButtonGroup'; +import { HeaderIcon, HeaderIconProps, getIconSize } from './Icon'; import { useStyles } from './style'; +const { Paragraph } = Typography; + export interface PageHeaderProps extends Omit, 'title'> { /** 是否有边框,默认没有 */ bordered?: boolean; @@ -17,12 +21,14 @@ export interface PageHeaderProps extends Omit React.ReactNode; + /** 副标题:例如描述等 */ + subTitle?: React.ReactNode; /** 描述列表 */ descriptions?: { icon: { @@ -39,6 +45,8 @@ export interface PageHeaderProps extends Omit React.ReactNode; + /** 控制 header 与 content 的分割线,当 bordered 为 true 时,divider 自动设置为 false */ + divider?: boolean; } export const PageHeader: React.FC = props => { @@ -47,15 +55,30 @@ export const PageHeader: React.FC = props => { icon, title, titleRender, + subTitle, status, descriptions = [], descriptionsRender, extraContent = {}, extraContentRender, bordered, + divider, ...otherProps } = props; - const { styles, cx } = useStyles({ bordered }); + const { styles, cx } = useStyles({ bordered, divider }); + + const iconProps = useMemo(() => { + if (!icon) { + return; + } + if (typeof icon === 'string') { + return { src: icon }; + } + if (subTitle && icon.size === undefined) { + icon.size = 'large'; + } + return icon; + }, [icon, subTitle]); const renderTitle = useCallback(() => { const titleElement = {title}; @@ -103,10 +126,15 @@ export const PageHeader: React.FC = props => { if (loading) { return ( - - + +
+ {subTitle && ( +
+ +
+ )}
@@ -123,9 +151,20 @@ export const PageHeader: React.FC = props => { return ( - {icon && icon} - -
{renderTitle()}
+ {iconProps && ( + + + + )} + + + {renderTitle()} + {subTitle && ( + + {subTitle} + + )} + {status && } {renderDescriptions()} diff --git a/src/Page/Header/style.ts b/src/Page/Header/style.ts index d30bf9c..de3c767 100644 --- a/src/Page/Header/style.ts +++ b/src/Page/Header/style.ts @@ -1,7 +1,10 @@ import { createStyles } from 'antd-style'; export const useStyles = createStyles( - ({ css, token, prefixCls }, { bordered = false }: { bordered?: boolean }) => { + ( + { css, token, prefixCls }, + { bordered = false, divider = true }: { bordered?: boolean; divider?: boolean } + ) => { return { root: bordered ? css` @@ -11,25 +14,36 @@ export const useStyles = createStyles( box-shadow: ${token.boxShadowTertiary}; ` : css` - padding: ${token.padding}px 0; + padding-top: ${token.paddingXS}px; + ${divider && + css` + padding-bottom: ${token.paddingLG}px; + border-bottom: 1px solid ${token.colorSplit}; + `} `, icon: css` - width: 64px; - height: 64px; - background-color: ${token.colorFillTertiary}; - border-radius: ${token.borderRadiusLG}px; - `, - content: css` - padding: ${token.paddingXXS}px 0; + &.${prefixCls}-avatar.${prefixCls}-avatar-square { + border-radius: 10px; + } `, titleBox: css` + margin-top: ${token.marginXXS}px; margin-bottom: ${token.marginSM}px; `, + subTitle: css` + margin-top: ${token.marginXXS}px; + &.${prefixCls}-typography { + margin-bottom: 0 !important; + font-size: ${token.fontSize}px; + color: ${token.colorTextSecondary}; + } + `, title: css` font-size: ${token.fontSizeHeading5}px; font-weight: 700; `, descriptions: css` + margin-bottom: ${token.marginXXS}px; font-size: ${token.fontSize}px; color: ${token.colorTextSecondary}; .${prefixCls}-badge.${prefixCls}-badge-status { diff --git a/src/Page/demos/BoderedHeader.tsx b/src/Page/demos/BoderedHeader.tsx index e65ccbf..2b59268 100644 --- a/src/Page/demos/BoderedHeader.tsx +++ b/src/Page/demos/BoderedHeader.tsx @@ -1,25 +1,53 @@ /** * compact: true */ +import { UserOutlined } from '@ant-design/icons'; import { Page } from '@yuntijs/ui'; import { useStyles } from './style'; -const { Breadcrumb, Header, Content } = Page; +const { Header } = Page; const PageBorderedHeaderDemo = () => { const { styles } = useStyles(); return ( -
, + tooltip: '创建者', + }, + text: '张萝卜', + }, + ]} + extraContent={{ + items: [ + { + key: 'edit', + label: '编辑', + }, + { + key: 'delete', + label: '删除', + danger: true, + }, + { + key: 'test', + label: '测试', + }, + ], + }} icon="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" + status={{ + status: 'processing', + text: '运行中', + title: '插件运行正常', + }} title="我是一个插件" /> - - <> - ); }; diff --git a/src/Page/demos/Loading.tsx b/src/Page/demos/Loading.tsx index 8109cf9..80aca4a 100644 --- a/src/Page/demos/Loading.tsx +++ b/src/Page/demos/Loading.tsx @@ -13,8 +13,10 @@ const PageLoadingDemo = () => {
diff --git a/src/Page/demos/index.tsx b/src/Page/demos/index.tsx index ba0c846..fd8b806 100644 --- a/src/Page/demos/index.tsx +++ b/src/Page/demos/index.tsx @@ -11,6 +11,7 @@ import { UserOutlined, } from '@ant-design/icons'; import { Button, Input, Page, Space } from '@yuntijs/ui'; +import { useEffect, useState } from 'react'; import { List } from './List'; import { useStyles } from './style'; @@ -19,9 +20,15 @@ const { Breadcrumb, Header, Content } = Page; const PageDemo = () => { const { styles } = useStyles(); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const timeout = setTimeout(() => setLoading(false), 1500); + return () => clearTimeout(timeout); + }, []); return ( - +
{ ], onClick: (key, e) => console.log('key', key, e), }} - icon={'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'} + icon={{ + src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + }} status={{ status: 'processing', text: '运行中', title: '插件运行正常', }} + subTitle="我是一个插件的描述" title="我是一个插件" titleRender={title => ( diff --git a/src/Page/index.md b/src/Page/index.md index 6606a13..79ceeb2 100644 --- a/src/Page/index.md +++ b/src/Page/index.md @@ -17,11 +17,8 @@ const { Breadcrumb, Header, Content } = Page; const IndexPage = () => { return ( - - + +
{ status: 'processing', text: '运行中', }} + subTitle="我是一个插件的描述" title="我是一个插件" - titleRender={title => ( - - {title} - - )} />