diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json deleted file mode 100644 index 16798ee9558c..000000000000 --- a/.codesandbox/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "installCommand": "npm-install", - "sandboxes": ["antd-reproduction-template-forked-jyh2k9"], - "node": "18" -} diff --git a/.dumi/components/SemanticPreview.tsx b/.dumi/components/SemanticPreview.tsx index 8641dc25ea5b..c9a77d7b643b 100644 --- a/.dumi/components/SemanticPreview.tsx +++ b/.dumi/components/SemanticPreview.tsx @@ -43,7 +43,7 @@ const useStyle = createStyles(({ token }, markPos: [number, number, number, numb z-index: 999999; box-shadow: 0 0 0 1px #fff; pointer-events: none; - left: ${markPos[0] - MARK_BORDER_SIZE}px; + inset-inline-start: ${markPos[0] - MARK_BORDER_SIZE}px; top: ${markPos[1] - MARK_BORDER_SIZE}px; width: ${markPos[2] + MARK_BORDER_SIZE * 2}px; height: ${markPos[3] + MARK_BORDER_SIZE * 2}px; diff --git a/.dumi/global.css b/.dumi/global.css index e56892774b3e..6c32b20c426b 100644 --- a/.dumi/global.css +++ b/.dumi/global.css @@ -13,3 +13,8 @@ background: rgba(255, 255, 255, 0.2); border-radius: 6px; } + +html { + scrollbar-width: thin; + scrollbar-color: unset; +} diff --git a/.dumi/hooks/use.ts b/.dumi/hooks/use.ts index 2502dcd1fa4e..2e9bc4a0c64f 100644 --- a/.dumi/hooks/use.ts +++ b/.dumi/hooks/use.ts @@ -9,22 +9,22 @@ function use(promise: PromiseLike): T { } if (internal.status === 'rejected') { throw internal.reason; - } else if (internal.status === 'pending') { - throw internal; - } else { - internal.status = 'pending'; - internal.then( - (result) => { - internal.status = 'fulfilled'; - internal.value = result; - }, - (reason) => { - internal.status = 'rejected'; - internal.reason = reason; - }, - ); + } + if (internal.status === 'pending') { throw internal; } + internal.status = 'pending'; + internal.then( + (result) => { + internal.status = 'fulfilled'; + internal.value = result; + }, + (reason) => { + internal.status = 'rejected'; + internal.reason = reason; + }, + ); + throw internal; } export default use; diff --git a/.dumi/hooks/useFetch/index.ts b/.dumi/hooks/useFetch/index.ts index 99aa763ab3c3..d020df56a92a 100644 --- a/.dumi/hooks/useFetch/index.ts +++ b/.dumi/hooks/useFetch/index.ts @@ -1,4 +1,5 @@ import fetch from 'cross-fetch'; + import use from '../use'; import FetchCache from './cache'; diff --git a/.dumi/hooks/useLocale.ts b/.dumi/hooks/useLocale.ts index 4cd075541a94..3df87cd06e05 100644 --- a/.dumi/hooks/useLocale.ts +++ b/.dumi/hooks/useLocale.ts @@ -1,16 +1,22 @@ import { useLocale as useDumiLocale } from 'dumi'; -export interface LocaleMap { - cn: Record; - en: Record; +export interface LocaleMap< + K extends PropertyKey = PropertyKey, + V extends string | ((...params: any[]) => string) = string, +> { + cn: Record; + en: Record; } -function useLocale( - localeMap?: LocaleMap, -): [Record, 'cn' | 'en'] { +const useLocale = < + K extends PropertyKey = PropertyKey, + V extends string | ((...params: any[]) => string) = string, +>( + localeMap?: LocaleMap, +): [Record, 'cn' | 'en'] => { const { id } = useDumiLocale(); const localeType = id === 'zh-CN' ? 'cn' : 'en'; - return [localeMap?.[localeType]!, localeType]; -} + return [localeMap?.[localeType]!, localeType] as const; +}; export default useLocale; diff --git a/.dumi/hooks/useLocation.ts b/.dumi/hooks/useLocation.ts index 3499a3bbe225..0c5d2e73448e 100644 --- a/.dumi/hooks/useLocation.ts +++ b/.dumi/hooks/useLocation.ts @@ -1,5 +1,6 @@ -import { useLocation as useDumiLocation } from 'dumi'; import * as React from 'react'; +import { useLocation as useDumiLocation } from 'dumi'; + import useLocale from './useLocale'; function clearPath(path: string) { diff --git a/.dumi/hooks/useMenu.tsx b/.dumi/hooks/useMenu.tsx index ac1bbf887061..933851076711 100644 --- a/.dumi/hooks/useMenu.tsx +++ b/.dumi/hooks/useMenu.tsx @@ -1,24 +1,73 @@ import React, { useMemo } from 'react'; import type { MenuProps } from 'antd'; -import { Tag, version } from 'antd'; +import { Space, Tag, version } from 'antd'; +import { createStyles } from 'antd-style'; +import classnames from 'classnames'; import { useFullSidebarData, useSidebarData } from 'dumi'; import Link from '../theme/common/Link'; import useLocation from './useLocation'; -const ItemTag: React.FC<{ tag?: string; show?: boolean }> = (props) => { - const { tag, show = true } = props; - if (!show || !tag) { - return null; +function isVersionNumber(value?: string) { + return value && /^\d+\.\d+\.\d+$/.test(value); +} + +const useStyle = createStyles(({ css, token }) => ({ + link: css` + display: flex; + align-items: center; + justify-content: space-between; + `, + tag: css` + margin-inline-end: 0; + `, + subtitle: css` + font-weight: normal; + font-size: ${token.fontSizeSM}px; + opacity: 0.8; + `, +})); + +interface MenuItemLabelProps { + before?: React.ReactNode; + after?: React.ReactNode; + link: string; + title: React.ReactNode; + subtitle?: React.ReactNode; + search?: string; + tag?: string; + className?: string; +} + +const MenuItemLabelWithTag: React.FC = (props) => { + const { styles } = useStyle(); + const { before, after, link, title, subtitle, search, tag, className } = props; + if (!before && !after) { + return ( + + + {title} + {subtitle && {subtitle}} + + {tag && ( + + {tag.replace('VERSION', version)} + + )} + + ); } return ( - - {tag.replace('VERSION', version)} - + + {before} + {title} + {subtitle && {subtitle}} + {after} + ); }; @@ -27,7 +76,7 @@ export interface UseMenuOptions { after?: React.ReactNode; } -const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] => { +const useMenu = (options: UseMenuOptions = {}): readonly [MenuProps['items'], string] => { const fullData = useFullSidebarData(); const { pathname, search } = useLocation(); const sidebarData = useSidebarData(); @@ -120,18 +169,15 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] => key: group?.title, children: group.children?.map((item) => ({ label: ( - - {before} - {item?.title} - - {item.frontmatter?.subtitle} - - - {after} - + ), key: item.link.replace(/(-cn$)/g, ''), })), @@ -146,15 +192,14 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] => result.push( ...list.map((item) => ({ label: ( - - {before} - {item?.title} - - {after} - + ), key: item.link.replace(/(-cn$)/g, ''), })), @@ -165,7 +210,7 @@ const useMenu = (options: UseMenuOptions = {}): [MenuProps['items'], string] => ); }, [sidebarData, fullData, pathname, search, options]); - return [menuItems, pathname]; + return [menuItems, pathname] as const; }; export default useMenu; diff --git a/.dumi/pages/404/index.tsx b/.dumi/pages/404/index.tsx index 502df93dc827..15229c04c8e7 100644 --- a/.dumi/pages/404/index.tsx +++ b/.dumi/pages/404/index.tsx @@ -1,7 +1,9 @@ -import { HomeOutlined } from '@ant-design/icons'; -import { Link, useLocation } from 'dumi'; import React, { useEffect } from 'react'; +import { HomeOutlined } from '@ant-design/icons'; import { Button, Result } from 'antd'; +import { useLocation } from 'dumi'; + +import Link from '../../theme/common/Link'; import * as utils from '../../theme/utils'; export interface NotFoundProps { diff --git a/.dumi/pages/index/components/Banner.tsx b/.dumi/pages/index/components/Banner.tsx deleted file mode 100644 index 5fe6274b70a6..000000000000 --- a/.dumi/pages/index/components/Banner.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import * as React from 'react'; -import { Button, Flex, Typography } from 'antd'; -import { createStyles, css, useTheme } from 'antd-style'; -import classNames from 'classnames'; -import { Link, useLocation } from 'dumi'; - -import useLocale from '../../../hooks/useLocale'; -import SiteContext from '../../../theme/slots/SiteContext'; -import * as utils from '../../../theme/utils'; -import { GroupMask } from './Group'; - -const locales = { - cn: { - slogan: '助力设计开发者「更灵活」地搭建出「更美」的产品,让用户「快乐工作」~', - start: '开始使用', - designLanguage: '设计语言', - }, - en: { - slogan: - 'Help designers/developers building beautiful products more flexible and working with happiness', - start: 'Getting Started', - designLanguage: 'Design Language', - }, -}; - -const useStyle = () => { - const { isMobile } = React.useContext(SiteContext); - return createStyles(({ token }) => ({ - titleBase: css` - h1& { - font-family: AliPuHui, ${token.fontFamily}; - } - `, - title: isMobile - ? css` - h1& { - margin-bottom: ${token.margin}px; - font-weight: normal; - font-size: ${token.fontSizeHeading1 + 2}px; - line-height: ${token.lineHeightHeading2}; - } - ` - : css` - h1& { - margin-bottom: ${token.marginMD}px; - font-weight: 900; - font-size: 68px; - } - `, - btnWrap: css` - margin-bottom: ${token.marginXL}px; - `, - }))(); -}; - -const Banner: React.FC = ({ children }) => { - const [locale] = useLocale(locales); - const { pathname, search } = useLocation(); - const token = useTheme(); - const { styles } = useStyle(); - const { isMobile } = React.useContext(SiteContext); - const isZhCN = utils.isZhCN(pathname); - return ( - <> - {/* Banner Placeholder Motion */} - {isMobile ? ( - - ) : ( -
-
- - - -
-
- )} - - {/* Logo */} -
- {/* Image Bottom Right */} - Ant Design - - - {/* Image Left Top */} - bg - {/* Image Right Top */} - bg - - - Ant Design 5.0 - - -
{locale.slogan}
-
- - - - - - - - - {children} -
-
- - ); -}; - -export default Banner; diff --git a/.dumi/pages/index/components/BannerRecommends.tsx b/.dumi/pages/index/components/BannerRecommends.tsx index 03160fcaf1da..cc223f3b4460 100644 --- a/.dumi/pages/index/components/BannerRecommends.tsx +++ b/.dumi/pages/index/components/BannerRecommends.tsx @@ -1,8 +1,6 @@ -import * as React from 'react'; -import type { FC } from 'react'; -import { useContext } from 'react'; -import { Badge, Carousel, Skeleton, Typography } from 'antd'; -import { createStyles, useTheme } from 'antd-style'; +import React, { useContext } from 'react'; +import { Badge, Carousel, Flex, Skeleton, Typography } from 'antd'; +import { createStyles } from 'antd-style'; import classNames from 'classnames'; import useLocale from '../../../hooks/useLocale'; @@ -46,10 +44,10 @@ const useStyle = createStyles(({ token, css, cx }) => { `, container: css` display: flex; - max-width: 1208px; + width: 100%; + max-width: 100%; margin-inline: auto; box-sizing: border-box; - padding-inline: ${token.marginXXL}px; column-gap: ${token.paddingMD * 2}px; align-items: stretch; text-align: start; @@ -59,6 +57,9 @@ const useStyle = createStyles(({ token, css, cx }) => { } `, carousel, + bannerBg: css` + height: ${token.fontSize}px; + `, }; }); @@ -68,8 +69,8 @@ interface RecommendItemProps { icons: Icon[]; className?: string; } + const RecommendItem: React.FC = ({ extra, index, icons, className }) => { - const token = useTheme(); const { styles } = useStyle(); if (!extra) { @@ -89,10 +90,10 @@ const RecommendItem: React.FC = ({ extra, index, icons, clas {extra.description} -
+ {extra.date} - {icon && banner} -
+ {icon && banner} + ); @@ -107,7 +108,7 @@ const RecommendItem: React.FC = ({ extra, index, icons, clas return card; }; -export const BannerRecommendsFallback: FC = () => { +export const BannerRecommendsFallback: React.FC = () => { const { isMobile } = useContext(SiteContext); const { styles } = useStyle(); @@ -145,34 +146,34 @@ const BannerRecommends: React.FC = () => { return ; } - return ( -
- {isMobile ? ( - - {first3.map((extra, index) => ( -
- -
- ))} -
- ) : ( -
- {first3.map((extra, index) => ( + if (isMobile) { + return ( + + {first3.map((extra, index) => ( +
- ))} -
- )} +
+ ))} + + ); + } + + return ( +
+ {first3.map((extra, index) => ( + + ))}
); }; diff --git a/.dumi/pages/index/components/ComponentsList.tsx b/.dumi/pages/index/components/ComponentsList.tsx index 059cd8ce9e3a..7aae7b788113 100644 --- a/.dumi/pages/index/components/ComponentsList.tsx +++ b/.dumi/pages/index/components/ComponentsList.tsx @@ -12,7 +12,7 @@ import { Tour, Typography, } from 'antd'; -import { createStyles, css, useTheme } from 'antd-style'; +import { createStyles, css } from 'antd-style'; import classNames from 'classnames'; import dayjs from 'dayjs'; @@ -96,7 +96,21 @@ const useStyle = () => { mobileCard: css` height: 395px; `, + nodeWrap: css` + margin-top: ${token.paddingLG}px; + flex: auto; + display: flex; + align-items: center; + justify-content: center; + `, carousel, + componentsList: css` + width: 100%; + overflow: hidden; + `, + mobileComponentsList: css` + margin: 0 ${token.margin}px; + `, }; })(); }; @@ -107,14 +121,12 @@ const ComponentItem: React.FC = ({ title, node, type, index const tagText = type === 'new' ? locale.new : locale.update; const { styles } = useStyle(); const { isMobile } = useContext(SiteContext); - const token = useTheme(); - return (
{/* Decorator */}
{/* Title */} @@ -124,18 +136,7 @@ const ComponentItem: React.FC = ({ title, node, type, index {tagText} - -
- {node} -
+
{node}
); }; @@ -148,7 +149,6 @@ interface ComponentItemProps { } const ComponentsList: React.FC = () => { - const token = useTheme(); const { styles } = useStyle(); const [locale] = useLocale(locales); const { isMobile } = useContext(SiteContext); @@ -266,21 +266,33 @@ const ComponentsList: React.FC = () => { ); return isMobile ? ( -
+
{COMPONENTS.map(({ title, node, type }, index) => ( - + ))}
) : ( -
-
+ + {COMPONENTS.map(({ title, node, type }, index) => ( - + ))} -
-
+ + ); }; diff --git a/.dumi/pages/index/components/DesignFramework.tsx b/.dumi/pages/index/components/DesignFramework.tsx index 1f744b343329..e2816d9118a6 100644 --- a/.dumi/pages/index/components/DesignFramework.tsx +++ b/.dumi/pages/index/components/DesignFramework.tsx @@ -1,10 +1,11 @@ import React, { useContext } from 'react'; import { Col, Row, Typography } from 'antd'; import { createStyles, useTheme } from 'antd-style'; -import { Link, useLocation } from 'dumi'; +import { useLocation } from 'dumi'; import useDark from '../../../hooks/useDark'; import useLocale from '../../../hooks/useLocale'; +import Link from '../../../theme/common/Link'; import SiteContext from '../../../theme/slots/SiteContext'; import * as utils from '../../../theme/utils'; @@ -69,7 +70,7 @@ const useStyle = () => { card: css` padding: ${token.paddingSM}px; border-radius: ${token.borderRadius * 2}px; - background: ${isRootDark ? 'rgba(0,0,0,0.45)' : token.colorBgElevated}; + background: ${isRootDark ? 'rgba(0, 0, 0, 0.45)' : token.colorBgElevated}; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02), @@ -86,8 +87,8 @@ const useStyle = () => { display: block; border-radius: ${token.borderRadius * 2}px; padding: ${token.paddingMD}px ${token.paddingLG}px; - background: ${isRootDark ? 'rgba(0,0,0,0.25)' : 'rgba(0, 0, 0, 0.02)'}; - border: 1px solid ${isRootDark ? 'rgba(255,255,255, 0.45)' : 'rgba(0, 0, 0, 0.06)'}; + background: ${isRootDark ? 'rgba(0, 0, 0, 0.25)' : 'rgba(0, 0, 0, 0.02)'}; + border: 1px solid ${isRootDark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.06)'}; img { height: 48px; @@ -96,7 +97,7 @@ const useStyle = () => { }))(); }; -export default function DesignFramework() { +const DesignFramework: React.FC = () => { const [locale] = useLocale(locales); const token = useTheme(); const { styles } = useStyle(); @@ -133,7 +134,7 @@ export default function DesignFramework() {
- {title} + {title} - {title} + {title} ); -} +}; + +export default DesignFramework; diff --git a/.dumi/pages/index/components/Group.tsx b/.dumi/pages/index/components/Group.tsx index f007bb6f77ac..437fdcaada2e 100644 --- a/.dumi/pages/index/components/Group.tsx +++ b/.dumi/pages/index/components/Group.tsx @@ -1,68 +1,46 @@ import * as React from 'react'; import { useContext } from 'react'; import { Typography } from 'antd'; -import { useTheme } from 'antd-style'; +import { createStyles, useTheme } from 'antd-style'; +import classNames from 'classnames'; import SiteContext from '../../../theme/slots/SiteContext'; +import GroupMaskLayer from './GroupMaskLayer'; -export interface GroupMaskProps { - style?: React.CSSProperties; - children?: React.ReactNode; - disabled?: boolean; - onMouseMove?: React.MouseEventHandler; - onMouseEnter?: React.MouseEventHandler; - onMouseLeave?: React.MouseEventHandler; -} - -export const GroupMask: React.FC = (props) => { - const { children, style, disabled, onMouseMove, onMouseEnter, onMouseLeave } = props; - const additionalStyle: React.CSSProperties = disabled - ? {} - : { - position: 'relative', - zIndex: 1, - }; - - return ( -
- {children} -
- ); -}; +const useStyle = createStyles(({ css, token }) => ({ + box: css` + position: relative; + transition: all ${token.motionDurationSlow}; + `, + marginStyle: css` + max-width: 1208px; + margin-inline: auto; + box-sizing: border-box; + padding-inline: ${token.marginXXL}px; + `, + withoutChildren: css` + min-height: 300px; + border-radius: ${token.borderRadiusLG}px; + background-color: '#e9e9e9'; + `, +})); export interface GroupProps { id?: string; title?: React.ReactNode; titleColor?: string; description?: React.ReactNode; - children?: React.ReactNode; background?: string; - /** 是否不使用两侧 margin */ collapse?: boolean; - decoration?: React.ReactNode; } -const Group: React.FC = (props) => { +const Group: React.FC> = (props) => { const { id, title, titleColor, description, children, decoration, background, collapse } = props; const token = useTheme(); + const { styles } = useStyle(); const { isMobile } = useContext(SiteContext); - - const marginStyle: React.CSSProperties = collapse - ? {} - : { - maxWidth: 1208, - marginInline: 'auto', - boxSizing: 'border-box', - paddingInline: isMobile ? token.margin : token.marginXXL, - }; const childNode = ( <>
@@ -82,38 +60,22 @@ const Group: React.FC = (props) => { {description}
- -
- {children ? ( -
{children}
- ) : ( -
- )} +
+ {children ?
{children}
:
}
); return ( -
+
{decoration}
- - {childNode} - + {childNode}
); }; diff --git a/.dumi/pages/index/components/GroupMaskLayer.tsx b/.dumi/pages/index/components/GroupMaskLayer.tsx new file mode 100644 index 000000000000..2a5ca9aec14f --- /dev/null +++ b/.dumi/pages/index/components/GroupMaskLayer.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { createStyles } from 'antd-style'; +import classNames from 'classnames'; + +const useStyle = createStyles(({ css }) => ({ + siteMask: css` + z-index: 1; + position: relative; + `, +})); + +export interface GroupMaskLayerProps { + className?: string; + style?: React.CSSProperties; + onMouseMove?: React.MouseEventHandler; + onMouseEnter?: React.MouseEventHandler; + onMouseLeave?: React.MouseEventHandler; +} + +const GroupMaskLayer: React.FC> = (props) => { + const { children, className, style, onMouseMove, onMouseEnter, onMouseLeave } = props; + const { styles } = useStyle(); + return ( +
+ {children} +
+ ); +}; + +export default GroupMaskLayer; diff --git a/.dumi/pages/index/components/PreviewBanner/index.tsx b/.dumi/pages/index/components/PreviewBanner/index.tsx index f875a6afcfd7..38ec15b560ad 100644 --- a/.dumi/pages/index/components/PreviewBanner/index.tsx +++ b/.dumi/pages/index/components/PreviewBanner/index.tsx @@ -1,12 +1,14 @@ import React, { Suspense } from 'react'; -import { Button, ConfigProvider, Flex, Typography } from 'antd'; +import { ConfigProvider, Flex, Typography } from 'antd'; import { createStyles } from 'antd-style'; -import { Link, useLocation } from 'dumi'; +import classNames from 'classnames'; +import { useLocation } from 'dumi'; import useLocale from '../../../../hooks/useLocale'; +import LinkButton from '../../../../theme/common/LinkButton'; import SiteContext from '../../../../theme/slots/SiteContext'; import * as utils from '../../../../theme/utils'; -import { GroupMask } from '../Group'; +import GroupMaskLayer from '../GroupMaskLayer'; const ComponentsBlock = React.lazy(() => import('./ComponentsBlock')); @@ -26,9 +28,10 @@ const locales = { const useStyle = () => { const { direction } = React.useContext(ConfigProvider.ConfigContext); + const { isMobile } = React.useContext(SiteContext); const isRTL = direction === 'rtl'; return createStyles(({ token, css, cx }) => { - const textShadow = `0 0 3px ${token.colorBgContainer}`; + const textShadow = `0 0 4px ${token.colorBgContainer}`; const mask = cx(css` position: absolute; @@ -91,20 +94,33 @@ const useStyle = () => { top: -38px; transform: ${isRTL ? 'rotate3d(24, 83, -45, 57deg)' : 'rotate3d(24, -83, 45, 57deg)'}; `, - child: css` position: relative; width: 100%; + max-width: 1200px; + margin: 0 auto; z-index: 1; `, btnWrap: css` margin-bottom: ${token.marginXL}px; `, + bgImg: css` + position: absolute; + width: 240px; + `, + bgImgTop: css` + top: 0; + inset-inline-start: ${isMobile ? '-120px' : 0}; + `, + bgImgBottom: css` + bottom: 120px; + inset-inline-end: ${isMobile ? 0 : '40%'}; + `, }; })(); }; -const PreviewBanner: React.FC = (props) => { +const PreviewBanner: React.FC> = (props) => { const { children } = props; const [locale] = useLocale(locales); const { styles } = useStyle(); @@ -113,18 +129,20 @@ const PreviewBanner: React.FC = (props) => { const isZhCN = utils.isZhCN(pathname); return ( - + {/* Image Left Top */} bg {/* Image Right Top */} bg
@@ -142,18 +160,23 @@ const PreviewBanner: React.FC = (props) => {

{locale.slogan}

- - - - - - + + {locale.start} + + + {locale.designLanguage} +
{children}
-
+ ); }; diff --git a/.dumi/pages/index/components/Theme/BackgroundImage.tsx b/.dumi/pages/index/components/Theme/BackgroundImage.tsx index 077f34dc2899..5875571cac06 100644 --- a/.dumi/pages/index/components/Theme/BackgroundImage.tsx +++ b/.dumi/pages/index/components/Theme/BackgroundImage.tsx @@ -14,7 +14,7 @@ const useStyle = createStyles(({ token }) => ({ image: css` transition: all ${token.motionDurationSlow}; position: absolute; - left: 0; + inset-inline-start: 0; top: 0; height: 100%; width: 100%; @@ -23,12 +23,9 @@ const useStyle = createStyles(({ token }) => ({ `, })); -const onShow = () => ({ - opacity: 1, -}); -const onHide = () => ({ - opacity: 0, -}); +const onShow = () => ({ opacity: 1 }); + +const onHide = () => ({ opacity: 0 }); const BackgroundImage: React.FC = ({ colorPrimary, isLight }) => { const activeColor = useMemo(() => getClosetColor(colorPrimary), [colorPrimary]); @@ -67,13 +64,11 @@ const BackgroundImage: React.FC = ({ colorPrimary, isLight ); diff --git a/.dumi/pages/index/components/Theme/ColorPicker.tsx b/.dumi/pages/index/components/Theme/ColorPicker.tsx index 34c6835c112c..f41295a9e5f8 100644 --- a/.dumi/pages/index/components/Theme/ColorPicker.tsx +++ b/.dumi/pages/index/components/Theme/ColorPicker.tsx @@ -1,12 +1,14 @@ import React, { useEffect, useState } from 'react'; import { ColorPicker, Flex, Input } from 'antd'; import { createStyles } from 'antd-style'; -import type { Color } from 'antd/es/color-picker'; +import type { ColorPickerProps, GetProp } from 'antd'; import { generateColor } from 'antd/es/color-picker/util'; import classNames from 'classnames'; import { PRESET_COLORS } from './colorUtil'; +type Color = Extract, string | { cleared: any }>; + const useStyle = createStyles(({ token, css }) => ({ color: css` width: ${token.controlHeightLG / 2}px; @@ -34,14 +36,13 @@ const useStyle = createStyles(({ token, css }) => ({ `, })); -export interface ColorPickerProps { +export interface ThemeColorPickerProps { id?: string; - children?: React.ReactNode; value?: string | Color; onChange?: (value?: Color | string) => void; } -const DebouncedColorPicker: React.FC = (props) => { +const DebouncedColorPicker: React.FC> = (props) => { const { value: color, children, onChange } = props; const [value, setValue] = useState(color); @@ -67,7 +68,7 @@ const DebouncedColorPicker: React.FC = (props) => { ); }; -const ThemeColorPicker: React.FC = ({ value, onChange, id }) => { +const ThemeColorPicker: React.FC = ({ value, onChange, id }) => { const { styles } = useStyle(); const matchColors = React.useMemo(() => { @@ -91,7 +92,7 @@ const ThemeColorPicker: React.FC = ({ value, onChange, id }) = }, [value]); return ( - + onChange?.(event.target.value)} @@ -115,6 +116,7 @@ const ThemeColorPicker: React.FC = ({ value, onChange, id }) = e.stopPropagation()} /> diff --git a/.dumi/pages/index/components/Theme/MobileCarousel.tsx b/.dumi/pages/index/components/Theme/MobileCarousel.tsx index 6833309cbc83..97aca0bc2ae4 100644 --- a/.dumi/pages/index/components/Theme/MobileCarousel.tsx +++ b/.dumi/pages/index/components/Theme/MobileCarousel.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import { useState } from 'react'; +import { Carousel, Typography } from 'antd'; import { createStyles, css, useTheme } from 'antd-style'; -import { Typography, Carousel } from 'antd'; + import { getCarouselStyle } from '../util'; const useStyle = createStyles(() => { @@ -18,6 +19,9 @@ const useStyle = createStyles(() => { width: 100%; text-align: center; `, + img: css` + width: 100%; + `, }; }); @@ -75,7 +79,7 @@ export interface MobileCarouselProps { description?: React.ReactNode; } -export default function MobileCarousel(props: MobileCarouselProps) { +const MobileCarousel: React.FC = (props) => { const { styles } = useStyle(); const { id, title, description } = props; const token = useTheme(); @@ -109,10 +113,12 @@ export default function MobileCarousel(props: MobileCarouselProps) { {mobileImageConfigList.map((item, index) => (
- + carousel
))}
); -} +}; + +export default MobileCarousel; diff --git a/.dumi/pages/index/components/Theme/RadiusPicker.tsx b/.dumi/pages/index/components/Theme/RadiusPicker.tsx index 78acd67a51f6..f21460577e5b 100644 --- a/.dumi/pages/index/components/Theme/RadiusPicker.tsx +++ b/.dumi/pages/index/components/Theme/RadiusPicker.tsx @@ -15,7 +15,7 @@ const RadiusPicker: React.FC = ({ id, value, onChange }) => ( style={{ width: 120 }} min={0} formatter={(val) => `${val}px`} - parser={(str) => (str ? parseFloat(str) : (str as any))} + parser={(str) => str?.replace('px', '') as unknown as number} id={id} /> = (props) => { const { styles } = useStyle(); const [locale] = useLocale(locales); return ( - - {Object.keys(THEMES).map((theme: THEME, index) => ( + + {(Object.keys(THEMES) as (keyof typeof THEMES)[]).map((theme, index) => (
- + - - - - + {locale.toDef} + + - - + {locale.toUse} + } > @@ -521,7 +541,6 @@ export default function Theme() { - @@ -546,22 +565,6 @@ export default function Theme() { ); - const posStyle: React.CSSProperties = { - position: 'absolute', - }; - const leftTopImageStyle: React.CSSProperties = { - left: '50%', - transform: 'translate3d(-900px, 0, 0)', - top: -100, - height: 500, - }; - const rightBottomImageStyle: React.CSSProperties = { - right: '50%', - transform: 'translate3d(750px, 0, 0)', - bottom: -100, - height: 287, - }; - return isMobile ? ( ) : ( @@ -576,52 +579,44 @@ export default function Theme() { <> {/* >>>>>> Default <<<<<< */}
{/* Image Left Top */} {/* Image Right Bottom */}
- {/* >>>>>> Dark <<<<<< */}
{/* Image Left Top */} {/* Image Right Bottom */}
- {/* >>>>>> Background Image <<<<<< */} @@ -630,4 +625,6 @@ export default function Theme() { {themeNode} ); -} +}; + +export default Theme; diff --git a/.dumi/pages/index/components/util.ts b/.dumi/pages/index/components/util.ts index a4cacaf55d8b..242442847c63 100644 --- a/.dumi/pages/index/components/util.ts +++ b/.dumi/pages/index/components/util.ts @@ -1,5 +1,5 @@ -import { css } from 'antd-style'; import { useEffect, useState } from 'react'; +import { css } from 'antd-style'; import fetch from 'cross-fetch'; export interface Author { diff --git a/.dumi/pages/index/index.tsx b/.dumi/pages/index/index.tsx index a528f4347e00..9e5a23705eb8 100644 --- a/.dumi/pages/index/index.tsx +++ b/.dumi/pages/index/index.tsx @@ -5,8 +5,8 @@ import { createStyles, css } from 'antd-style'; import useDark from '../../hooks/useDark'; import useLocale from '../../hooks/useLocale'; import BannerRecommends from './components/BannerRecommends'; -import PreviewBanner from './components/PreviewBanner'; import Group from './components/Group'; +import PreviewBanner from './components/PreviewBanner'; const ComponentsList = React.lazy(() => import('./components/ComponentsList')); const DesignFramework = React.lazy(() => import('./components/DesignFramework')); @@ -15,7 +15,7 @@ const Theme = React.lazy(() => import('./components/Theme')); const useStyle = createStyles(() => ({ image: css` position: absolute; - left: 0; + inset-inline-start: 0; top: -50px; height: 160px; `, @@ -78,12 +78,13 @@ const Homepage: React.FC = () => { } > diff --git a/.dumi/pages/theme-editor/index.tsx b/.dumi/pages/theme-editor/index.tsx index 33e4f3f437c9..1082acd64279 100644 --- a/.dumi/pages/theme-editor/index.tsx +++ b/.dumi/pages/theme-editor/index.tsx @@ -1,8 +1,9 @@ -import { enUS, zhCN } from 'antd-token-previewer'; -import { Helmet } from 'dumi'; import React, { Suspense, useEffect } from 'react'; +import { Button, App, Skeleton } from 'antd'; +import { enUS, zhCN } from 'antd-token-previewer'; import type { ThemeConfig } from 'antd/es/config-provider/context'; -import { Button, message, Skeleton } from 'antd'; +import { Helmet } from 'dumi'; + import useLocale from '../../hooks/useLocale'; const ThemeEditor = React.lazy(() => import('antd-token-previewer/lib/ThemeEditor')); @@ -34,8 +35,8 @@ const locales = { const ANT_DESIGN_V5_THEME_EDITOR_THEME = 'ant-design-v5-theme-editor-theme'; -const CustomTheme = () => { - const [messageApi, contextHolder] = message.useMessage(); +const CustomTheme: React.FC = () => { + const { message } = App.useApp(); const [locale, lang] = useLocale(locales); const [theme, setTheme] = React.useState({}); @@ -50,7 +51,7 @@ const CustomTheme = () => { const handleSave = () => { localStorage.setItem(ANT_DESIGN_V5_THEME_EDITOR_THEME, JSON.stringify(theme)); - messageApi.success(locale.saveSuccessfully); + message.success(locale.saveSuccessfully); }; return ( @@ -59,7 +60,6 @@ const CustomTheme = () => { {`${locale.title} - Ant Design`} - {contextHolder} }> { const code = (node.children[0] as any).value as string; const configRegx = /^const sandpackConfig = ([\S\s]*?});/; const [configString] = code.match(configRegx) || []; - // eslint-disable-next-line no-eval + /* biome-ignore lint/security/noGlobalEval: used in documentation */ /* eslint-disable-next-line no-eval */ const config = configString && eval(`(${configString.replace(configRegx, '$1')})`); Object.keys(config || {}).forEach((key) => { if (typeof config[key] === 'object') { diff --git a/.dumi/scripts/clarity.js b/.dumi/scripts/clarity.js new file mode 100644 index 000000000000..b36cab890a78 --- /dev/null +++ b/.dumi/scripts/clarity.js @@ -0,0 +1,14 @@ +/* eslint-disable */ +// https://clarity.microsoft.com +(function (c, l, a, r, i, t, y) { + c[a] = + c[a] || + function () { + (c[a].q = c[a].q || []).push(arguments); + }; + t = l.createElement(r); + t.async = 1; + t.src = 'https://www.clarity.ms/tag/' + i; + y = l.getElementsByTagName(r)[0]; + y.parentNode.insertBefore(t, y); +})(window, document, 'clarity', 'script', 'lyia7jfwui'); diff --git a/.dumi/mirror-modal.js b/.dumi/scripts/mirror-modal.js similarity index 95% rename from .dumi/mirror-modal.js rename to .dumi/scripts/mirror-modal.js index f58546c372cd..8a310ea1005e 100644 --- a/.dumi/mirror-modal.js +++ b/.dumi/scripts/mirror-modal.js @@ -3,7 +3,8 @@ (navigator.languages.includes('zh') || navigator.languages.includes('zh-CN')) && /-cn\/?$/.test(window.location.pathname) && !['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(window.location.hostname) && - !window.location.host.includes('surge') + !window.location.host.includes('surge') && + window.location.hostname !== 'localhost' ) { const ANTD_DOT_NOT_SHOW_MIRROR_MODAL = 'ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL'; @@ -49,10 +50,9 @@ .mirror-modal-dialog { position: fixed; top: 120px; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; + inset-inline-start: 0; + inset-inline-end: 0; + margin: 0 auto; width: 420px; display: flex; align-items: center; @@ -117,7 +117,7 @@ .mirror-modal-cancel-btn { border: 1px solid #eee; color: #000; - margin-right: 8px; + margin-inline-end: 8px; } .mirror-modal-cancel-btn:hover { diff --git a/.dumi/theme/builtins/ColorChunk/index.tsx b/.dumi/theme/builtins/ColorChunk/index.tsx index 598abb34b2b1..26d815145fda 100644 --- a/.dumi/theme/builtins/ColorChunk/index.tsx +++ b/.dumi/theme/builtins/ColorChunk/index.tsx @@ -1,31 +1,31 @@ -import { TinyColor, type ColorInput } from '@ctrl/tinycolor'; -import { createStyles } from 'antd-style'; import * as React from 'react'; - -interface ColorChunkProps { - children?: React.ReactNode; - value?: ColorInput; -} +import type { ColorInput } from '@ctrl/tinycolor'; +import { TinyColor } from '@ctrl/tinycolor'; +import { createStyles } from 'antd-style'; const useStyle = createStyles(({ token, css }) => ({ codeSpan: css` - padding: 0.2em 0.4em; - font-size: 0.9em; - background: ${token.siteMarkdownCodeBg}; - border-radius: ${token.borderRadius}px; - font-family: monospace; - `, + padding: 0.2em 0.4em; + font-size: 0.9em; + background: ${token.siteMarkdownCodeBg}; + border-radius: ${token.borderRadius}px; + font-family: monospace; + `, dot: css` - display: inline-block; - width: 6px; - height: 6px; - border-radius: 50%; - margin-inline-end: 4px; - border: 1px solid ${token.colorSplit}; - `, + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + margin-inline-end: ${token.marginXXS}px; + border: 1px solid ${token.colorSplit}; + `, })); -const ColorChunk: React.FC = (props) => { +interface ColorChunkProps { + value?: ColorInput; +} + +const ColorChunk: React.FC> = (props) => { const { styles } = useStyle(); const { value, children } = props; diff --git a/.dumi/theme/builtins/ComponentMeta/index.tsx b/.dumi/theme/builtins/ComponentMeta/index.tsx new file mode 100644 index 000000000000..06e7ec51fcee --- /dev/null +++ b/.dumi/theme/builtins/ComponentMeta/index.tsx @@ -0,0 +1,206 @@ +import React from 'react'; +import { EditOutlined, GithubOutlined } from '@ant-design/icons'; +import type { GetProp } from 'antd'; +import { Descriptions, theme, Tooltip, Typography } from 'antd'; +import { createStyles, css } from 'antd-style'; +import kebabCase from 'lodash/kebabCase'; +import CopyToClipboard from 'react-copy-to-clipboard'; + +import useLocale from '../../../hooks/useLocale'; + +const locales = { + cn: { + import: '使用', + copy: '复制', + copied: '已复制', + source: '源码', + docs: '文档', + edit: '编辑此页', + version: '版本', + }, + en: { + import: 'Import', + copy: 'Copy', + copied: 'Copied', + source: 'Source', + docs: 'Docs', + edit: 'Edit this page', + version: 'Version', + }, +}; + +const branchUrl = 'https://github.com/ant-design/ant-design/edit/master/'; + +function isVersionNumber(value?: string) { + return value && /^\d+\.\d+\.\d+$/.test(value); +} + +const useStyle = createStyles(({ token }) => ({ + code: css` + cursor: pointer; + position: relative; + display: inline-flex; + align-items: center; + column-gap: ${token.paddingXXS}px; + border-radius: ${token.borderRadiusSM}px; + padding-inline: ${token.paddingXXS}px; + transition: all ${token.motionDurationSlow} !important; + font-family: ${token.codeFamily}; + color: ${token.colorTextSecondary} !important; + &:hover { + background: ${token.controlItemBgHover}; + } + a&:hover { + text-decoration: underline !important; + } + `, + import: css` + color: ${token.magenta8}; + `, + component: css` + color: ${token.colorText}; + `, + from: css` + color: ${token.magenta8}; + margin-inline-end: 0.5em; + `, + antd: css` + color: ${token.green8}; + `, + semicolon: css` + color: ${token.colorText}; + `, +})); + +export interface ComponentMetaProps { + component: string; + source: string | true; + filename?: string; + version?: string; +} + +const ComponentMeta: React.FC = (props) => { + const { component, source, filename, version } = props; + const { token } = theme.useToken(); + const [locale, lang] = useLocale(locales); + const isZhCN = lang === 'cn'; + const { styles } = useStyle(); + + // ========================= Copy ========================= + const [copied, setCopied] = React.useState(false); + + const onCopy = () => { + setCopied(true); + }; + + const onOpenChange = (open: boolean) => { + if (open) { + setCopied(false); + } + }; + + // ======================== Source ======================== + const [filledSource, abbrSource] = React.useMemo(() => { + if (String(source) === 'true') { + const kebabComponent = kebabCase(component); + return [ + `https://github.com/ant-design/ant-design/blob/master/components/${kebabComponent}`, + `components/${kebabComponent}`, + ]; + } + + if (typeof source !== 'string') { + return [null, null]; + } + + return [source, source]; + }, [component, source]); + + const transformComponentName = (componentName: string) => { + if (componentName === 'Notifiction' || componentName === 'Message') { + return componentName.toLowerCase(); + } + return componentName; + }; + + // ======================== Render ======================== + const importList = [ + + import + , + {`{ ${transformComponentName( + component, + )} }`}, + + from + , + + {`"antd"`} + , + + ; + , + ]; + + return ( + + + + {importList} + + + + ), + }, + filledSource && { + label: locale.source, + children: ( + + + {abbrSource} + + ), + }, + filename && { + label: locale.docs, + children: ( + + + {locale.edit} + + ), + }, + isVersionNumber(version) && { + label: locale.version, + children: ( + + {isZhCN ? `自 ${version} 后支持` : `supported since ${version}`} + + ), + }, + ].filter(Boolean) as GetProp + } + /> + ); +}; + +export default ComponentMeta; diff --git a/.dumi/theme/builtins/ComponentOverview/index.tsx b/.dumi/theme/builtins/ComponentOverview/index.tsx index 55ed2f37bff5..6b12828c7ee7 100644 --- a/.dumi/theme/builtins/ComponentOverview/index.tsx +++ b/.dumi/theme/builtins/ComponentOverview/index.tsx @@ -14,7 +14,7 @@ import proComponentsList from './ProComponentsList'; const useStyle = createStyles(({ token, css }) => ({ componentsOverviewGroupTitle: css` - margin-bottom: 24px !important; + margin-bottom: ${token.marginLG}px !important; `, componentsOverviewTitle: css` overflow: hidden; @@ -39,7 +39,7 @@ const useStyle = createStyles(({ token, css }) => ({ `, componentsOverviewAffix: css` display: flex; - transition: all 0.3s; + transition: all ${token.motionDurationSlow}; justify-content: space-between; `, componentsOverviewSearch: css` @@ -52,7 +52,7 @@ const useStyle = createStyles(({ token, css }) => ({ componentsOverviewContent: css` &:empty:after { display: block; - padding: 16px 0 40px; + padding: ${token.padding}px 0 ${token.paddingMD * 2}px; color: ${token.colorTextDisabled}; text-align: center; border-bottom: 1px solid ${token.colorSplit}; @@ -198,44 +198,59 @@ const Overview: React.FC = () => { {components.map((component) => { + let url = component.link; /** 是否是外链 */ - const isExternalLink = component.link.startsWith('http'); - let url = `${component.link}`; + const isExternalLink = url.startsWith('http'); if (!isExternalLink) { url += urlSearch; } - return ( - - - onClickCard(url)} - bodyStyle={{ - backgroundRepeat: 'no-repeat', - backgroundPosition: 'bottom right', - backgroundImage: `url(${component?.tag || ''})`, - }} - size="small" - className={styles.componentsOverviewCard} - title={ -
- {component?.title} {component.subtitle} -
+ const cardContent = ( + onClickCard(url)} + styles={{ + body: { + backgroundRepeat: 'no-repeat', + backgroundPosition: 'bottom right', + backgroundImage: `url(${component.tag || ''})`, + }, + }} + size="small" + className={styles.componentsOverviewCard} + title={ +
+ {component.title} {component.subtitle} +
+ } + > +
+ -
- {component?.title} -
- - + alt={component.title} + /> +
+
+ ); + + const linkContent = isExternalLink ? ( +
+ {cardContent} + + ) : ( + + {cardContent} + + ); + + return ( + + {linkContent} ); })} diff --git a/.dumi/theme/builtins/ComponentTokenTable/index.tsx b/.dumi/theme/builtins/ComponentTokenTable/index.tsx index 45134cdca312..e9ae85f12b30 100644 --- a/.dumi/theme/builtins/ComponentTokenTable/index.tsx +++ b/.dumi/theme/builtins/ComponentTokenTable/index.tsx @@ -52,7 +52,7 @@ const locales = { }, }; -const useStyle = createStyles(() => ({ +const useStyle = createStyles(({ token }) => ({ tableTitle: css` cursor: pointer; position: relative; @@ -62,15 +62,15 @@ const useStyle = createStyles(() => ({ line-height: 40px; `, arrowIcon: css` - font-size: 16px; - margin-inline-end: 8px; + font-size: ${token.fontSizeLG}px; + margin-inline-end: ${token.marginXS}px; & svg { - transition: all 0.3s; + transition: all ${token.motionDurationSlow}; } `, help: css` - margin-inline-start: 8px; - font-size: 12px; + margin-inline-start: ${token.marginXS}px; + font-size: ${token.fontSizeSM}px; font-weight: normal; color: #999; a { @@ -98,7 +98,7 @@ const SubTokenTable: React.FC = (props) => { const token = useTheme(); const columns = useColumns(); - const [open, setOpen] = useState(defaultOpen || process.env.NODE_ENV !== 'production'); + const [open, setOpen] = useState(defaultOpen ?? process.env.NODE_ENV !== 'production'); const { styles } = useStyle(); @@ -156,7 +156,7 @@ const SubTokenTable: React.FC = (props) => { {title} {/* {code} */} diff --git a/.dumi/theme/builtins/Container/index.tsx b/.dumi/theme/builtins/Container/index.tsx index 0d299bf90379..94e4bc9f424d 100644 --- a/.dumi/theme/builtins/Container/index.tsx +++ b/.dumi/theme/builtins/Container/index.tsx @@ -3,14 +3,19 @@ */ import * as React from 'react'; import { Alert } from 'antd'; -import { type FC, type ReactNode } from 'react'; + import useStyles from './style'; -const Container: FC<{ +interface ContainerProps { type: 'info' | 'warning' | 'success' | 'error'; title?: string; - children: ReactNode; -}> = ({ type, title, children }) => { +} + +const Container: React.FC> = ({ + type, + title, + children, +}) => { const { styles, cx } = useStyles(); return ( diff --git a/.dumi/theme/builtins/Container/style.ts b/.dumi/theme/builtins/Container/style.ts index eea3d77bd073..9391de2f7baa 100644 --- a/.dumi/theme/builtins/Container/style.ts +++ b/.dumi/theme/builtins/Container/style.ts @@ -1,22 +1,22 @@ import { createStyles } from 'antd-style'; -const useStyles = createStyles(({ prefixCls, css }) => ({ +const useStyles = createStyles(({ token, prefixCls, css }) => ({ container: css` - margin: 8px 0; - `, + margin: ${token.marginXS}px 0; + `, alert: css` - .${prefixCls}-alert-message { - font-weight: bold; - } - `, + .${prefixCls}-alert-message { + font-weight: bold; + } + `, /* 使用 `&&` 加一点点权重 */ desc: css` - && p { - margin: 0; - } - `, + && p { + margin: 0; + } + `, })); export default useStyles; diff --git a/.dumi/theme/builtins/DemoWrapper/index.tsx b/.dumi/theme/builtins/DemoWrapper/index.tsx index 5ce92c457083..ebcafc24fefc 100644 --- a/.dumi/theme/builtins/DemoWrapper/index.tsx +++ b/.dumi/theme/builtins/DemoWrapper/index.tsx @@ -1,5 +1,4 @@ import React, { useContext } from 'react'; -import { DumiDemoGrid, FormattedMessage } from 'dumi'; import { BugFilled, BugOutlined, @@ -8,11 +7,13 @@ import { ExperimentFilled, ExperimentOutlined, } from '@ant-design/icons'; -import classNames from 'classnames'; import { ConfigProvider, Tooltip } from 'antd'; -import DemoContext from '../../slots/DemoContext'; +import classNames from 'classnames'; +import { DumiDemoGrid, FormattedMessage } from 'dumi'; + import useLayoutState from '../../../hooks/useLayoutState'; import useLocale from '../../../hooks/useLocale'; +import DemoContext from '../../slots/DemoContext'; const locales = { cn: { @@ -50,30 +51,27 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => { const demos = React.useMemo( () => - items.reduce( - (acc, item) => { - const { previewerProps } = item; - const { debug } = previewerProps; - - if (debug && !showDebug) return acc; - - return acc.concat({ - ...item, - previewerProps: { - ...previewerProps, - expand: expandAll, - // always override debug property, because dumi will hide debug demo in production - debug: false, - /** - * antd extra marker for the original debug - * @see https://github.com/ant-design/ant-design/pull/40130#issuecomment-1380208762 - */ - originDebug: debug, - }, - }); - }, - [] as typeof items, - ), + items.reduce((acc, item) => { + const { previewerProps } = item; + const { debug } = previewerProps; + if (debug && !showDebug) { + return acc; + } + return acc.concat({ + ...item, + previewerProps: { + ...previewerProps, + expand: expandAll, + // always override debug property, because dumi will hide debug demo in production + debug: false, + /** + * antd extra marker for the original debug + * @see https://github.com/ant-design/ant-design/pull/40130#issuecomment-1380208762 + */ + originDebug: debug, + }, + }); + }, []), [expandAll, showDebug], ); @@ -110,7 +108,7 @@ const DemoWrapper: typeof DumiDemoGrid = ({ items }) => { )} - +
diff --git a/.dumi/theme/builtins/IconSearch/Category.tsx b/.dumi/theme/builtins/IconSearch/Category.tsx index 1cbbdf4c57c6..0a306b256ac4 100644 --- a/.dumi/theme/builtins/IconSearch/Category.tsx +++ b/.dumi/theme/builtins/IconSearch/Category.tsx @@ -1,9 +1,30 @@ import * as React from 'react'; +import { App } from 'antd'; +import { createStyles } from 'antd-style'; import { useIntl } from 'dumi'; -import { message } from 'antd'; + import CopyableIcon from './CopyableIcon'; -import type { ThemeType } from './index'; import type { CategoriesKeys } from './fields'; +import type { ThemeType } from './IconSearch'; + +const useStyle = createStyles(({ token, css }) => ({ + anticonsList: css` + margin: ${token.margin}px 0; + overflow: hidden; + direction: ltr; + list-style: none; + display: grid; + grid-gap: ${token.margin}px; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + padding: 0; + `, + copiedCode: css` + padding: 0 ${token.paddingXXS}px; + font-size: ${token.fontSizeSM}px; + background-color: ${token.colorBgLayout}; + border-radius: ${token.borderRadiusXS}px; + `, +})); interface CategoryProps { title: CategoriesKeys; @@ -13,14 +34,16 @@ interface CategoryProps { } const Category: React.FC = (props) => { + const { message } = App.useApp(); const { icons, title, newIcons, theme } = props; + const { styles } = useStyle(); const intl = useIntl(); const [justCopied, setJustCopied] = React.useState(null); const copyId = React.useRef | null>(null); const onCopied = React.useCallback((type: string, text: string) => { message.success( - {text} copied 🎉 + {text} copied 🎉 , ); setJustCopied(type); @@ -39,7 +62,7 @@ const Category: React.FC = (props) => { return (

{intl.formatMessage({ id: `app.docs.components.icon.category.${title}` })}

-
    +
      {icons.map((name) => ( { + const { antCls, iconCls } = token; + return { + iconItem: css` + display: inline-flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-inline-start: 0 !important; + margin-inline-end: 0 !important; + padding-inline-start: 0 !important; + padding-inline-end: 0 !important; + position: relative; + width: 200px; + height: 100px; + overflow: hidden; + color: #555; + text-align: center; + list-style: none; + background-color: inherit; + border-radius: ${token.borderRadiusSM}px; + cursor: pointer; + transition: all ${token.motionDurationSlow} ease-in-out; + ${token.iconCls} { + margin: ${token.marginXS}px 0; + font-size: 36px; + transition: transform ${token.motionDurationSlow} ease-in-out; + will-change: transform; + } + &:hover { + color: ${token.colorWhite}; + background-color: ${token.colorPrimary}; + ${iconCls} { + transform: scale(1.3); + } + ${antCls}-badge { + color: ${token.colorWhite}; + } + } + &.TwoTone:hover { + background-color: #8ecafe; + } + &.copied:hover { + color: rgba(255, 255, 255, 0.2); + } + &::after { + content: 'Copied!'; + position: absolute; + top: 0; + inset-inline-start: 0; + width: 100%; + height: 100%; + line-height: 100px; + color: ${token.colorTextLightSolid}; + text-align: center; + background-color: ${token.colorPrimary}; + opacity: 0; + transition: all ${token.motionDurationSlow} cubic-bezier(0.18, 0.89, 0.32, 1.28); + } + &.copied::after { + opacity: 1; + } + `, + anticonCls: css` + display: block; + font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + white-space: nowrap; + text-align: center; + transform: scale(0.8); + ${antCls}-badge { + transition: color ${token.motionDurationSlow} ease-in-out; + } + `, + }; +}); export interface CopyableIconProps { name: string; isNew: boolean; theme: ThemeType; justCopied: string | null; - onCopied: (type: string, text: string) => any; + onCopied: (type: string, text: string) => void; } -const CopyableIcon: React.FC = ({ - name, - isNew, - justCopied, - onCopied, - theme, -}) => { - const className = classNames({ - copied: justCopied === name, - [theme]: !!theme, - }); +const CopyableIcon: React.FC = (props) => { + const { message } = App.useApp(); + const { name, isNew, justCopied, theme, onCopied } = props; + const { styles } = useStyle(); const onCopy = (text: string, result: boolean) => { if (result) { onCopied(name, text); @@ -37,9 +106,9 @@ const CopyableIcon: React.FC = ({ }; return ( `} onCopy={onCopy}> -
    • +
    • {React.createElement(allIcons[name])} - + {name}
    • diff --git a/.dumi/theme/builtins/IconSearch/IconSearch.tsx b/.dumi/theme/builtins/IconSearch/IconSearch.tsx index 30063ee132e4..4857172ad9b2 100644 --- a/.dumi/theme/builtins/IconSearch/IconSearch.tsx +++ b/.dumi/theme/builtins/IconSearch/IconSearch.tsx @@ -1,15 +1,16 @@ import type { CSSProperties } from 'react'; import React, { useCallback, useMemo, useState } from 'react'; import Icon, * as AntdIcons from '@ant-design/icons'; +import type { SegmentedProps } from 'antd'; +import { Affix, Empty, Input, Segmented } from 'antd'; import { createStyles, useTheme } from 'antd-style'; import { useIntl } from 'dumi'; import debounce from 'lodash/debounce'; -import type { SegmentedProps } from 'antd'; -import { Affix, Empty, Input, Segmented } from 'antd'; + import Category from './Category'; -import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons'; import type { CategoriesKeys } from './fields'; import { categories } from './fields'; +import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons'; export enum ThemeType { Filled = 'Filled', @@ -19,10 +20,10 @@ export enum ThemeType { const allIcons: { [key: string]: any } = AntdIcons; -const useStyle = createStyles(({ css }) => ({ +const useStyle = createStyles(({ token, css }) => ({ iconSearchAffix: css` display: flex; - transition: all 0.3s; + transition: all ${token.motionDurationSlow}; justify-content: space-between; `, })); @@ -80,7 +81,7 @@ const IconSearch: React.FC = () => { if (searchKey) { const matchKey = searchKey // eslint-disable-next-line prefer-regex-literals - .replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name) + .replace(/^<([a-z]*)\s\/>$/gi, (_, name) => name) .replace(/(Filled|Outlined|TwoTone)$/, '') .toLowerCase(); iconList = iconList.filter((iconName) => iconName.toLowerCase().includes(matchKey)); diff --git a/.dumi/theme/builtins/IconSearch/index.tsx b/.dumi/theme/builtins/IconSearch/index.tsx index 7ad79fce79a3..d6a4c3eaaffe 100644 --- a/.dumi/theme/builtins/IconSearch/index.tsx +++ b/.dumi/theme/builtins/IconSearch/index.tsx @@ -1,13 +1,13 @@ import React, { Suspense } from 'react'; -import { createStyles } from 'antd-style'; import { Skeleton } from 'antd'; +import { createStyles } from 'antd-style'; const IconSearch = React.lazy(() => import('./IconSearch')); -const useStyle = createStyles(({ css }) => ({ +const useStyle = createStyles(({ token, css }) => ({ searchWrapper: css` display: flex; - gap: 16px; + gap: ${token.padding}px; > *:first-child { flex: 0 0 328px; } @@ -21,7 +21,7 @@ const useStyle = createStyles(({ css }) => ({ justify-content: space-between; > * { flex: 0 0 15%; - margin: 3px 0; + margin: ${token.marginXXS}px 0; } `, skeletonWrapper: css` @@ -33,7 +33,7 @@ const useStyle = createStyles(({ css }) => ({ `, })); -const IconSearchFallback = () => { +const IconSearchFallback: React.FC = () => { const { styles } = useStyle(); return ( diff --git a/.dumi/theme/builtins/IconSearch/themeIcons.tsx b/.dumi/theme/builtins/IconSearch/themeIcons.tsx index 892a95dd2941..bea1ef7509f8 100644 --- a/.dumi/theme/builtins/IconSearch/themeIcons.tsx +++ b/.dumi/theme/builtins/IconSearch/themeIcons.tsx @@ -12,6 +12,7 @@ export const FilledIcon: CustomIconComponent = (props) => { '0c0-53-43-96-96-96z'; return ( + Filled Icon ); @@ -26,6 +27,7 @@ export const OutlinedIcon: CustomIconComponent = (props) => { ' 12 12v680c0 6.6-5.4 12-12 12z'; return ( + Outlined Icon ); @@ -39,6 +41,7 @@ export const TwoToneIcon: CustomIconComponent = (props) => { '68 368 0 203.41-164.622 368-368 368z'; return ( + TwoTone Icon ); diff --git a/.dumi/theme/builtins/ImagePreview/index.tsx b/.dumi/theme/builtins/ImagePreview/index.tsx index a364c1844ebf..7fe41f0af48d 100644 --- a/.dumi/theme/builtins/ImagePreview/index.tsx +++ b/.dumi/theme/builtins/ImagePreview/index.tsx @@ -1,10 +1,9 @@ import React from 'react'; +import { Image } from 'antd'; import classNames from 'classnames'; import toArray from 'rc-util/lib/Children/toArray'; -import { Image } from 'antd'; interface ImagePreviewProps { - children: React.ReactNode[]; className?: string; /** Do not show padding & background */ pure?: boolean; @@ -29,11 +28,22 @@ function isGoodBadImg(imgMeta: any): boolean { function isCompareImg(imgMeta: any): boolean { return isGoodBadImg(imgMeta) || imgMeta.inline; } -const ImagePreview: React.FC = (props) => { + +interface MateType { + className: string; + alt: string; + description: string; + src: string; + isGood: boolean; + isBad: boolean; + inline: boolean; +} + +const ImagePreview: React.FC> = (props) => { const { children, className: rootClassName, pure } = props; const imgs = toArray(children).filter((ele) => ele.type === 'img'); - const imgsMeta = imgs.map((img) => { + const imgsMeta = imgs.map>((img) => { const { alt, description, src, className } = img.props; return { className, @@ -107,7 +117,8 @@ const ImagePreview: React.FC = (props) => {
      {coverMeta.alt}
      ); diff --git a/.dumi/theme/builtins/InlinePopover/index.tsx b/.dumi/theme/builtins/InlinePopover/index.tsx index 2621eb0d2d45..74852b02e5d0 100644 --- a/.dumi/theme/builtins/InlinePopover/index.tsx +++ b/.dumi/theme/builtins/InlinePopover/index.tsx @@ -1,6 +1,7 @@ -import { PictureOutlined } from '@ant-design/icons'; import React from 'react'; +import { PictureOutlined } from '@ant-design/icons'; import { Image, Tooltip, Typography } from 'antd'; + import useLocale from '../../../hooks/useLocale'; const locales = { diff --git a/.dumi/theme/builtins/InstallDependencies/bun.tsx b/.dumi/theme/builtins/InstallDependencies/bun.tsx new file mode 100644 index 000000000000..562c25997dd9 --- /dev/null +++ b/.dumi/theme/builtins/InstallDependencies/bun.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import { createStyles, css } from 'antd-style'; +import classNames from 'classnames'; + +interface IconProps { + className?: string; + style?: React.CSSProperties; +} + +const useStyle = createStyles(() => ({ + iconWrap: css` + display: inline-flex; + align-items: center; + line-height: 0; + text-align: center; + vertical-align: -0.125em; + `, +})); + +const BunIcon: React.FC = (props) => { + const { className, style } = props; + const { styles } = useStyle(); + return ( + + + Bun Logo + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default BunIcon; diff --git a/.dumi/theme/builtins/InstallDependencies/index.tsx b/.dumi/theme/builtins/InstallDependencies/index.tsx index 777241401fca..36bcd4dcc34f 100644 --- a/.dumi/theme/builtins/InstallDependencies/index.tsx +++ b/.dumi/theme/builtins/InstallDependencies/index.tsx @@ -3,6 +3,7 @@ import { ConfigProvider, Tabs } from 'antd'; import SourceCode from 'dumi/theme-default/builtins/SourceCode'; import type { Tab } from 'rc-tabs/lib/interface'; +import BunLogo from './bun'; import NpmLogo from './npm'; import PnpmLogo from './pnpm'; import YarnLogo from './yarn'; @@ -11,10 +12,11 @@ interface InstallProps { npm?: string; yarn?: string; pnpm?: string; + bun?: string; } const InstallDependencies: React.FC = (props) => { - const { npm, yarn, pnpm } = props; + const { npm, yarn, pnpm, bun } = props; const items: Tab[] = [ { key: 'npm', @@ -34,6 +36,12 @@ const InstallDependencies: React.FC = (props) => { children: pnpm ? {pnpm} : null, icon: , }, + { + key: 'bun', + label: 'Bun', + children: bun ? {bun} : null, + icon: , + }, ].filter((item) => item.children); return ( diff --git a/.dumi/theme/builtins/InstallDependencies/npm.tsx b/.dumi/theme/builtins/InstallDependencies/npm.tsx index 63937b032244..923e18e71013 100644 --- a/.dumi/theme/builtins/InstallDependencies/npm.tsx +++ b/.dumi/theme/builtins/InstallDependencies/npm.tsx @@ -31,6 +31,7 @@ const NpmIcon: React.FC = (props) => { viewBox="0 0 16 16" width="1em" > + npm icon diff --git a/.dumi/theme/builtins/InstallDependencies/pnpm.tsx b/.dumi/theme/builtins/InstallDependencies/pnpm.tsx index 67cb132e2eb5..ea8fd55de889 100644 --- a/.dumi/theme/builtins/InstallDependencies/pnpm.tsx +++ b/.dumi/theme/builtins/InstallDependencies/pnpm.tsx @@ -33,6 +33,7 @@ const PnpmIcon: React.FC = (props) => { viewBox="0 0 24 24" width="1em" > + pnpm icon diff --git a/.dumi/theme/builtins/InstallDependencies/yarn.tsx b/.dumi/theme/builtins/InstallDependencies/yarn.tsx index 7f73b97efd81..e4ffa3514270 100644 --- a/.dumi/theme/builtins/InstallDependencies/yarn.tsx +++ b/.dumi/theme/builtins/InstallDependencies/yarn.tsx @@ -32,6 +32,7 @@ const YarnIcon: React.FC = (props) => { viewBox="0 0 496 512" width="1em" > + yarn icon diff --git a/.dumi/theme/builtins/LocaleLink/index.tsx b/.dumi/theme/builtins/LocaleLink/index.tsx index 67b7e49e44bb..3086392ddfe8 100644 --- a/.dumi/theme/builtins/LocaleLink/index.tsx +++ b/.dumi/theme/builtins/LocaleLink/index.tsx @@ -1,15 +1,19 @@ -import { Link } from 'dumi'; import * as React from 'react'; +import { Link } from 'dumi'; + import useLocale from '../../../hooks/useLocale'; type LinkProps = Parameters[0]; export interface LocaleLinkProps extends LinkProps { sourceType: 'a' | 'Link'; - children?: React.ReactNode; } -export default function LocaleLink({ sourceType, to, ...props }: LocaleLinkProps) { +const LocaleLink: React.FC> = ({ + sourceType, + to, + ...props +}) => { const Component = sourceType === 'a' ? 'a' : Link; const [, localeType] = useLocale(); @@ -46,4 +50,6 @@ export default function LocaleLink({ sourceType, to, ...props }: LocaleLinkProps } return ; -} +}; + +export default LocaleLink; diff --git a/.dumi/theme/builtins/Previewer/CodePreviewer.tsx b/.dumi/theme/builtins/Previewer/CodePreviewer.tsx index 605cb00f63a1..2e9100e49da4 100644 --- a/.dumi/theme/builtins/Previewer/CodePreviewer.tsx +++ b/.dumi/theme/builtins/Previewer/CodePreviewer.tsx @@ -11,12 +11,13 @@ import LZString from 'lz-string'; import useLocation from '../../../hooks/useLocation'; import BrowserFrame from '../../common/BrowserFrame'; import ClientOnly from '../../common/ClientOnly'; -import CodePenIcon from '../../common/CodePenIcon'; import CodePreview from '../../common/CodePreview'; -import CodeSandboxIcon from '../../common/CodeSandboxIcon'; import EditButton from '../../common/EditButton'; -import ExternalLinkIcon from '../../common/ExternalLinkIcon'; -import RiddleIcon from '../../common/RiddleIcon'; +import CodePenIcon from '../../icons/CodePenIcon'; +import CodeSandboxIcon from '../../icons/CodeSandboxIcon'; +import ExternalLinkIcon from '../../icons/ExternalLinkIcon'; +import RiddleIcon from '../../icons/RiddleIcon'; +import DemoContext from '../../slots/DemoContext'; import type { SiteContextProps } from '../../slots/SiteContext'; import SiteContext from '../../slots/SiteContext'; import { ping } from '../../utils'; @@ -74,14 +75,14 @@ const useStyle = createStyles(({ token }) => { border-radius: 0 0 ${borderRadius}px ${borderRadius}px; border-top: 1px solid ${token.colorSplit}; color: ${token.colorTextSecondary}; - transition: all 0.2s ease-in-out; + transition: all ${token.motionDurationMid} ease-in-out; background-color: ${token.colorBgElevated}; cursor: pointer; &:hover { color: ${token.colorPrimary}; } span { - margin-right: ${token.marginXXS}px; + margin-inline-end: ${token.marginXXS}px; } `, }; @@ -107,6 +108,7 @@ const CodePreviewer: React.FC = (props) => { clientOnly, pkgDependencyList, } = props; + const { showDebug, codeType } = useContext(DemoContext); const { pkg } = useSiteData(); const location = useLocation(); @@ -132,7 +134,6 @@ const CodePreviewer: React.FC = (props) => { const riddleIconRef = useRef(null); const codepenIconRef = useRef(null); const [codeExpand, setCodeExpand] = useState(false); - const [codeType, setCodeType] = useState('tsx'); const { theme } = useContext(SiteContext); const { hash, pathname, search } = location; @@ -307,7 +308,9 @@ ${parsedSourceCode} .trim() .replace(new RegExp(`#${asset.id}\\s*`, 'g'), '') .replace('', '') - .replace(' diff --git a/components/affix/demo/target.tsx b/components/affix/demo/target.tsx index 290ec81a2d3a..c5a028a93d47 100644 --- a/components/affix/demo/target.tsx +++ b/components/affix/demo/target.tsx @@ -5,7 +5,9 @@ const containerStyle: React.CSSProperties = { width: '100%', height: 100, overflow: 'auto', - border: '1px solid #40a9ff', + boxShadow: '0 0 0 1px #1677ff', + scrollbarWidth: 'thin', + scrollbarColor: 'unset', }; const style: React.CSSProperties = { diff --git a/components/affix/index.en-US.md b/components/affix/index.en-US.md index 2b208c8bdfa1..cde8d59966ad 100644 --- a/components/affix/index.en-US.md +++ b/components/affix/index.en-US.md @@ -1,6 +1,7 @@ --- category: Components title: Affix +description: Stick an element to the viewport. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YSm4RI3iOJ8AAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*03dxS64LxeQAAAAAAAAAAAAADrJ8AQ/original demo: @@ -10,8 +11,6 @@ group: order: 7 --- -Wrap Affix around another component to make it stick the viewport. - ## When To Use On longer web pages, it's helpful to stick component into the viewport. This is common for menus and actions. diff --git a/components/affix/index.tsx b/components/affix/index.tsx index 7230e9aaecc1..e9fdf4c38f53 100644 --- a/components/affix/index.tsx +++ b/components/affix/index.tsx @@ -9,7 +9,7 @@ import { ConfigContext } from '../config-provider'; import useStyle from './style'; import { getFixedBottom, getFixedTop, getTargetRect } from './utils'; -const TRIGGER_EVENTS = [ +const TRIGGER_EVENTS: (keyof WindowEventMap)[] = [ 'resize', 'scroll', 'touchstart', @@ -17,7 +17,7 @@ const TRIGGER_EVENTS = [ 'touchend', 'pageshow', 'load', -] as const; +]; function getDefaultTarget() { return typeof window !== 'undefined' ? window : null; @@ -39,11 +39,10 @@ export interface AffixProps { rootClassName?: string; children: React.ReactNode; } +const AFFIX_STATUS_NONE = 0; +const AFFIX_STATUS_PREPARE = 1; -enum AffixStatus { - None, - Prepare, -} +type AffixStatus = typeof AFFIX_STATUS_NONE | typeof AFFIX_STATUS_PREPARE; interface AffixState { affixStyle?: React.CSSProperties; @@ -53,7 +52,7 @@ interface AffixState { prevTarget: Window | HTMLElement | null; } -interface AffixRef { +export interface AffixRef { updatePosition: ReturnType; } @@ -78,7 +77,7 @@ const Affix = React.forwardRef((props, ref) => { const [affixStyle, setAffixStyle] = React.useState(); const [placeholderStyle, setPlaceholderStyle] = React.useState(); - const status = React.useRef(AffixStatus.None); + const status = React.useRef(AFFIX_STATUS_NONE); const prevTarget = React.useRef(null); const prevListener = React.useRef(); @@ -94,7 +93,7 @@ const Affix = React.forwardRef((props, ref) => { // =================== Measure =================== const measure = () => { if ( - status.current !== AffixStatus.Prepare || + status.current !== AFFIX_STATUS_PREPARE || !fixedNodeRef.current || !placeholderNodeRef.current || !targetFunc @@ -105,7 +104,7 @@ const Affix = React.forwardRef((props, ref) => { const targetNode = targetFunc(); if (targetNode) { const newState: Partial = { - status: AffixStatus.None, + status: AFFIX_STATUS_NONE, }; const placeholderRect = getTargetRect(placeholderNodeRef.current); @@ -160,7 +159,7 @@ const Affix = React.forwardRef((props, ref) => { }; const prepareMeasure = () => { - status.current = AffixStatus.Prepare; + status.current = AFFIX_STATUS_PREPARE; measure(); if (process.env.NODE_ENV === 'test') { (props as any)?.onTestUpdatePosition?.(); diff --git a/components/affix/index.zh-CN.md b/components/affix/index.zh-CN.md index 411237c649c6..bb6ca7ade609 100644 --- a/components/affix/index.zh-CN.md +++ b/components/affix/index.zh-CN.md @@ -2,6 +2,7 @@ category: Components title: Affix subtitle: 固钉 +description: 将页面元素钉在可视范围。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YSm4RI3iOJ8AAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*03dxS64LxeQAAAAAAAAAAAAADrJ8AQ/original demo: @@ -11,8 +12,6 @@ group: order: 7 --- -将页面元素钉在可视范围。 - ## 何时使用 当内容区域比较长,需要滚动页面时,这部分内容对应的操作或者导航需要在滚动范围内始终展现。常用于侧边菜单和按钮组合。 diff --git a/components/affix/style/index.ts b/components/affix/style/index.ts index aaaedb9bb881..0cd9757b3447 100644 --- a/components/affix/style/index.ts +++ b/components/affix/style/index.ts @@ -4,6 +4,10 @@ import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/inte import { genStyleHooks } from '../../theme/internal'; export interface ComponentToken { + /** + * @desc 弹出层的 z-index + * @descEN z-index of popup + */ zIndexPopup: number; } diff --git a/components/alert/Alert.tsx b/components/alert/Alert.tsx index c3b5bf4e8842..3f646976362c 100644 --- a/components/alert/Alert.tsx +++ b/components/alert/Alert.tsx @@ -8,17 +8,23 @@ import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled'; import classNames from 'classnames'; import CSSMotion from 'rc-motion'; import pickAttrs from 'rc-util/lib/pickAttrs'; +import { composeRef } from 'rc-util/lib/ref'; +import type { ClosableType } from '../_util/hooks/useClosable'; import { replaceElement } from '../_util/reactNode'; import { devUseWarning } from '../_util/warning'; import { ConfigContext } from '../config-provider'; import useStyle from './style'; +export interface AlertRef { + nativeElement: HTMLDivElement; +} + export interface AlertProps { /** Type of Alert styles, options:`success`, `info`, `warning`, `error` */ type?: 'success' | 'info' | 'warning' | 'error'; /** Whether Alert can be closed */ - closable?: boolean | ({ closeIcon?: React.ReactNode } & React.AriaAttributes); + closable?: ClosableType; /** * @deprecated please use `closable.closeIcon` instead. * Close text to show @@ -47,6 +53,8 @@ export interface AlertProps { onMouseEnter?: React.MouseEventHandler; onMouseLeave?: React.MouseEventHandler; onClick?: React.MouseEventHandler; + + id?: string; } const iconMapFilled = { @@ -101,7 +109,7 @@ const CloseIconNode: React.FC = (props) => { ) : null; }; -const Alert: React.FC = (props) => { +const Alert = React.forwardRef((props, ref) => { const { description, prefixCls: customizePrefixCls, @@ -119,6 +127,7 @@ const Alert: React.FC = (props) => { closeText, closeIcon, action, + id, ...otherProps } = props; @@ -129,7 +138,12 @@ const Alert: React.FC = (props) => { warning.deprecated(!closeText, 'closeText', 'closable.closeIcon'); } - const ref = React.useRef(null); + const internalRef = React.useRef(null); + + React.useImperativeHandle(ref, () => ({ + nativeElement: internalRef.current!, + })); + const { getPrefixCls, direction, alert } = React.useContext(ConfigContext); const prefixCls = getPrefixCls('alert', customizePrefixCls); @@ -202,7 +216,7 @@ const Alert: React.FC = (props) => { return alert?.closeIcon; }, [closeIcon, closable, closeText, alert?.closeIcon]); - const mergeAriaProps = React.useMemo(() => { + const mergedAriaProps = React.useMemo(() => { const merged = closable ?? alert?.closable; if (typeof merged === 'object') { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -221,9 +235,10 @@ const Alert: React.FC = (props) => { onLeaveStart={(node) => ({ maxHeight: node.offsetHeight })} onLeaveEnd={afterClose} > - {({ className: motionClassName, style: motionStyle }) => ( + {({ className: motionClassName, style: motionStyle }, setRef) => (
      = (props) => { prefixCls={prefixCls} closeIcon={mergedCloseIcon} handleClose={handleClose} - ariaProps={mergeAriaProps} + ariaProps={mergedAriaProps} />
      )} , ); -}; +}); if (process.env.NODE_ENV !== 'production') { Alert.displayName = 'Alert'; diff --git a/components/alert/ErrorBoundary.tsx b/components/alert/ErrorBoundary.tsx index 8c8b6b2bd9a9..995f7de8eb53 100644 --- a/components/alert/ErrorBoundary.tsx +++ b/components/alert/ErrorBoundary.tsx @@ -1,10 +1,12 @@ import * as React from 'react'; + import Alert from './Alert'; interface ErrorBoundaryProps { message?: React.ReactNode; description?: React.ReactNode; children?: React.ReactNode; + id?: string; } interface ErrorBoundaryStates { @@ -27,14 +29,15 @@ class ErrorBoundary extends React.Component +Array [ -
      - -
      + +
, +
, +
, +
, -
-`; - -exports[`renders components/alert/demo/action.tsx extend context correctly 2`] = ` -[ - "Warning: [antd: Button] \`link\` or \`text\` button can't be a \`ghost\` button.", -] -`; - -exports[`renders components/alert/demo/banner.tsx extend context correctly 1`] = ` -
-
- , +
, + -
- , +] +`; + +exports[`renders components/alert/demo/action.tsx extend context correctly 2`] = ` +[ + "Warning: [antd: Button] \`link\` or \`text\` button can't be a \`ghost\` button.", +] +`; + +exports[`renders components/alert/demo/banner.tsx extend context correctly 1`] = ` +Array [ + +
, +
, -
- , +
, + -
+
, +
, + , +] `; exports[`renders components/alert/demo/banner.tsx extend context correctly 2`] = `[]`; @@ -484,616 +452,556 @@ exports[`renders components/alert/demo/basic.tsx extend context correctly 1`] = exports[`renders components/alert/demo/basic.tsx extend context correctly 2`] = `[]`; exports[`renders components/alert/demo/closable.tsx extend context correctly 1`] = ` -
+Array [ + + + + +
, +
, -
-
- -
-
-`; - -exports[`renders components/alert/demo/closable.tsx extend context correctly 2`] = `[]`; - -exports[`renders components/alert/demo/custom-icon.tsx extend context correctly 1`] = ` -
-
- -
-
- , +
, + -
- , +] +`; + +exports[`renders components/alert/demo/closable.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/alert/demo/custom-icon.tsx extend context correctly 1`] = ` +Array [ + +
, +
, +
, +
, + +
, +
, +
, +
, + +
, +
, -
+
, +
, -
+
, +
, - + , +
, - - + , +] `; exports[`renders components/alert/demo/custom-icon.tsx extend context correctly 2`] = `[]`; exports[`renders components/alert/demo/description.tsx extend context correctly 1`] = ` -
+Array [ +
, +
, + , +
, + , +
, - + , +] `; exports[`renders components/alert/demo/description.tsx extend context correctly 2`] = `[]`; @@ -1105,173 +1013,130 @@ exports[`renders components/alert/demo/error-boundary.tsx extend context correct > Click me to throw a error - - -`; - -exports[`renders components/alert/demo/error-boundary.tsx extend context correctly 2`] = `[]`; - -exports[`renders components/alert/demo/icon.tsx extend context correctly 1`] = ` -
-
- , +
, +
, +
, -
- , +
, + +
, +
, -
+ , +
, - + , +
, - -
- , +
, + -
+ , +] `; exports[`renders components/alert/demo/icon.tsx extend context correctly 2`] = `[]`; exports[`renders components/alert/demo/smooth-closed.tsx extend context correctly 1`] = ` -
+Array [ -
-

- click the close button to see the effect -

-
-
-
-
+ , +

+ click the close button to see the effect +

, + , +] `; exports[`renders components/alert/demo/smooth-closed.tsx extend context correctly 2`] = `[]`; exports[`renders components/alert/demo/style.tsx extend context correctly 1`] = ` -
+Array [ +
, +
, + , +
, + , +
, - + , +] `; exports[`renders components/alert/demo/style.tsx extend context correctly 2`] = `[]`; diff --git a/components/alert/__tests__/__snapshots__/demo.test.ts.snap b/components/alert/__tests__/__snapshots__/demo.test.ts.snap index 45e372512b91..7d8edd3e61bf 100644 --- a/components/alert/__tests__/__snapshots__/demo.test.ts.snap +++ b/components/alert/__tests__/__snapshots__/demo.test.ts.snap @@ -1,102 +1,65 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders components/alert/demo/action.tsx correctly 1`] = ` -
+Array [ -
- -
+ +
, +
, + , +
, - -`; - -exports[`renders components/alert/demo/banner.tsx correctly 1`] = ` -
-
- , +
, + -
- , +] +`; + +exports[`renders components/alert/demo/banner.tsx correctly 1`] = ` +Array [ + +
, +
, -
- , +
, + -
+
, +
, + , +] `; exports[`renders components/alert/demo/basic.tsx correctly 1`] = ` @@ -474,612 +442,552 @@ exports[`renders components/alert/demo/basic.tsx correctly 1`] = ` `; exports[`renders components/alert/demo/closable.tsx correctly 1`] = ` -
+Array [ + + + + +
, +
, -
+ + , +
, - -`; - -exports[`renders components/alert/demo/custom-icon.tsx correctly 1`] = ` -
-
- -
-
- , +] +`; + +exports[`renders components/alert/demo/custom-icon.tsx correctly 1`] = ` +Array [ + +
, +
, +
, +
, + , +
, + , +
, + + , +
, - + , +
, - + , +
, - + , +
, - - + , +] `; exports[`renders components/alert/demo/description.tsx correctly 1`] = ` -
+Array [ +
, +
, + , +
, + , +
, - + , +] `; exports[`renders components/alert/demo/error-boundary.tsx correctly 1`] = ` @@ -1094,166 +1002,123 @@ exports[`renders components/alert/demo/error-boundary.tsx correctly 1`] = ` `; exports[`renders components/alert/demo/icon.tsx correctly 1`] = ` -
+Array [ +
, +
, + , +
, -
- , +
, + +
, +
, - + , +
, - + , +
, , +
, + - + , +] +`; + +exports[`renders components/alert/demo/smooth-closed.tsx correctly 1`] = ` +Array [ - - -`; - -exports[`renders components/alert/demo/smooth-closed.tsx correctly 1`] = ` -
-
+
, +

+ click the close button to see the effect +

, + -
- -
-

- click the close button to see the effect -

-
-
- -
- + class="ant-switch-inner-unchecked" + /> + + , +] `; exports[`renders components/alert/demo/style.tsx correctly 1`] = ` -
+Array [ +
, +
, + , +
, + , +
, - + , +] `; diff --git a/components/alert/__tests__/index.test.tsx b/components/alert/__tests__/index.test.tsx index 2c170b49b09f..fe3f81dca634 100644 --- a/components/alert/__tests__/index.test.tsx +++ b/components/alert/__tests__/index.test.tsx @@ -1,13 +1,15 @@ +import React from 'react'; import userEvent from '@testing-library/user-event'; import { resetWarned } from 'rc-util/lib/warning'; -import React from 'react'; + import Alert from '..'; import accessibilityTest from '../../../tests/shared/accessibilityTest'; import rtlTest from '../../../tests/shared/rtlTest'; -import { act, render, screen } from '../../../tests/utils'; +import { act, render, screen, waitFakeTimer } from '../../../tests/utils'; import Button from '../../button'; import Popconfirm from '../../popconfirm'; import Tooltip from '../../tooltip'; +import type { AlertRef } from '../Alert'; const { ErrorBoundary } = Alert; @@ -24,6 +26,7 @@ describe('Alert', () => { }); it('should show close button and could be closed', async () => { + const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const onClose = jest.fn(); render( { />, ); - await userEvent.click(screen.getByRole('button', { name: /close/i })); - - act(() => { + await act(async () => { + await userEvent.click(screen.getByRole('button', { name: /close/i })); jest.runAllTimers(); }); expect(onClose).toHaveBeenCalledTimes(1); + expect(errSpy).not.toHaveBeenCalled(); + errSpy.mockRestore(); }); it('custom action', () => { @@ -106,11 +110,9 @@ describe('Alert', () => { await userEvent.hover(screen.getByRole('alert')); - act(() => { - jest.runAllTimers(); - }); + await waitFakeTimer(); - expect(screen.getByRole('tooltip')).toBeInTheDocument(); + expect(document.querySelector('.ant-tooltip')).toBeInTheDocument(); }); it('could be used with Popconfirm', async () => { @@ -193,4 +195,13 @@ describe('Alert', () => { warnSpy.mockRestore(); }); + + it('should support id and ref', () => { + const alertRef = React.createRef(); + const { container } = render(); + const element = container.querySelector('#test-id'); + expect(element).toBeTruthy(); + expect(alertRef.current?.nativeElement).toBeTruthy(); + expect(alertRef.current?.nativeElement).toBe(element); + }); }); diff --git a/components/alert/demo/action.tsx b/components/alert/demo/action.tsx index 3715c1337a26..823860195b85 100644 --- a/components/alert/demo/action.tsx +++ b/components/alert/demo/action.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Alert, Button, Space } from 'antd'; const App: React.FC = () => ( - + <> ( } closable /> +
( } /> +
( } closable /> +
( } closable /> -
+ ); export default App; diff --git a/components/alert/demo/banner.tsx b/components/alert/demo/banner.tsx index 552d6ca9ee65..cc8bcb6a6786 100644 --- a/components/alert/demo/banner.tsx +++ b/components/alert/demo/banner.tsx @@ -1,17 +1,20 @@ import React from 'react'; -import { Alert, Space } from 'antd'; +import { Alert } from 'antd'; const App: React.FC = () => ( - + <> +
+
+
-
+ ); export default App; diff --git a/components/alert/demo/closable.tsx b/components/alert/demo/closable.tsx index d88ac700ceea..46b55573402d 100644 --- a/components/alert/demo/closable.tsx +++ b/components/alert/demo/closable.tsx @@ -1,19 +1,20 @@ import React from 'react'; -import { Alert, Space } from 'antd'; import { CloseSquareFilled } from '@ant-design/icons'; +import { Alert } from 'antd'; const onClose = (e: React.MouseEvent) => { console.log(e, 'I was closed.'); }; const App: React.FC = () => ( - + <> +
( closable onClose={onClose} /> +
( }} onClose={onClose} /> -
+ ); export default App; diff --git a/components/alert/demo/component-token.tsx b/components/alert/demo/component-token.tsx index 4e9af03e5204..2a655b15e6ca 100644 --- a/components/alert/demo/component-token.tsx +++ b/components/alert/demo/component-token.tsx @@ -1,5 +1,5 @@ -import { SmileOutlined } from '@ant-design/icons'; import React from 'react'; +import { SmileOutlined } from '@ant-design/icons'; import { Alert, ConfigProvider } from 'antd'; const icon = ; @@ -18,7 +18,7 @@ const App: React.FC = () => ( diff --git a/components/alert/demo/custom-icon.tsx b/components/alert/demo/custom-icon.tsx index e1767a937e98..415fc064fdd8 100644 --- a/components/alert/demo/custom-icon.tsx +++ b/components/alert/demo/custom-icon.tsx @@ -1,30 +1,37 @@ import React from 'react'; import { SmileOutlined } from '@ant-design/icons'; -import { Alert, Space } from 'antd'; +import { Alert } from 'antd'; const icon = ; const App: React.FC = () => ( - + <> +
+
+
+
+
+
+
( type="warning" showIcon /> +
( type="error" showIcon /> -
+ ); export default App; diff --git a/components/alert/demo/description.tsx b/components/alert/demo/description.tsx index 0abd7b08b1a4..4b15cb9c9549 100644 --- a/components/alert/demo/description.tsx +++ b/components/alert/demo/description.tsx @@ -1,29 +1,32 @@ import React from 'react'; -import { Alert, Space } from 'antd'; +import { Alert } from 'antd'; const App: React.FC = () => ( - + <> +
+
+
-
+ ); export default App; diff --git a/components/alert/demo/icon.tsx b/components/alert/demo/icon.tsx index bc9baca870a7..37e19b3e2088 100644 --- a/components/alert/demo/icon.tsx +++ b/components/alert/demo/icon.tsx @@ -1,24 +1,30 @@ import React from 'react'; -import { Alert, Space } from 'antd'; +import { Alert } from 'antd'; const App: React.FC = () => ( - + <> +
+
+
+
+
+
( showIcon closable /> +
-
+ ); export default App; diff --git a/components/alert/demo/loop-banner.tsx b/components/alert/demo/loop-banner.tsx index 7324268df85a..1f9a460d3115 100644 --- a/components/alert/demo/loop-banner.tsx +++ b/components/alert/demo/loop-banner.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import Marquee from 'react-fast-marquee'; import { Alert } from 'antd'; +import Marquee from 'react-fast-marquee'; const App: React.FC = () => ( { const [visible, setVisible] = useState(true); @@ -9,13 +9,13 @@ const App: React.FC = () => { }; return ( - + <> {visible && ( )}

click the close button to see the effect

-
+ ); }; diff --git a/components/alert/demo/style.tsx b/components/alert/demo/style.tsx index f89bc3f879d2..82fcc683c7c8 100644 --- a/components/alert/demo/style.tsx +++ b/components/alert/demo/style.tsx @@ -1,13 +1,16 @@ import React from 'react'; -import { Alert, Space } from 'antd'; +import { Alert } from 'antd'; const App: React.FC = () => ( - + <> +
+
+
-
+ ); export default App; diff --git a/components/alert/design/behavior-pattern.tsx b/components/alert/design/behavior-pattern.tsx index 631ea0630419..98bc6d80b7db 100644 --- a/components/alert/design/behavior-pattern.tsx +++ b/components/alert/design/behavior-pattern.tsx @@ -16,12 +16,12 @@ const BehaviorPattern: React.FC = () => ( { id: '707000085', label: '了解提示内容', - link: 'components-alert-index-tab-design-demo-content', + link: 'alert-index-tab-design-demo-content', }, { id: '707000086', label: '了解提示类型', - link: 'components-alert-index-tab-design-demo-type', + link: 'alert-index-tab-design-demo-type', }, ], }, @@ -29,7 +29,7 @@ const BehaviorPattern: React.FC = () => ( id: '200000005', label: '针对提示进行操作', targetType: 'extension', - link: 'components-alert-index-tab-design-demo-action', + link: 'alert-index-tab-design-demo-action', }, ], }} diff --git a/components/alert/index.en-US.md b/components/alert/index.en-US.md index 9812a873b920..c96b78f9158a 100644 --- a/components/alert/index.en-US.md +++ b/components/alert/index.en-US.md @@ -1,6 +1,7 @@ --- category: Components title: Alert +description: Display warning messages that require attention. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*QsvtS41m45UAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*gOTISoMFZV0AAAAAAAAAAAAADrJ8AQ/original demo: @@ -10,8 +11,6 @@ group: order: 6 --- -Alert component for feedback. - ## When To Use - When you need to show alert messages to users. diff --git a/components/alert/index.ts b/components/alert/index.tsx similarity index 69% rename from components/alert/index.ts rename to components/alert/index.tsx index 6b3a78f121c2..a9f01e8343c8 100644 --- a/components/alert/index.ts +++ b/components/alert/index.tsx @@ -1,11 +1,9 @@ -import type React from 'react'; -import type { AlertProps } from './Alert'; import InternalAlert from './Alert'; import ErrorBoundary from './ErrorBoundary'; export type { AlertProps } from './Alert'; -type CompoundedComponent = React.FC & { +type CompoundedComponent = typeof InternalAlert & { ErrorBoundary: typeof ErrorBoundary; }; diff --git a/components/alert/index.zh-CN.md b/components/alert/index.zh-CN.md index d1be592b0c46..e587661adaf2 100644 --- a/components/alert/index.zh-CN.md +++ b/components/alert/index.zh-CN.md @@ -1,7 +1,8 @@ --- category: Components -subtitle: 警告提示 title: Alert +subtitle: 警告提示 +description: 警告提示,展现需要关注的信息。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*QsvtS41m45UAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*gOTISoMFZV0AAAAAAAAAAAAADrJ8AQ/original demo: @@ -11,8 +12,6 @@ group: order: 6 --- -警告提示,展现需要关注的信息。 - ## 何时使用 - 当某个页面需要向用户显示警告的信息时。 diff --git a/components/alert/style/index.ts b/components/alert/style/index.ts index 02bcce8d5bf5..681feea84ea0 100644 --- a/components/alert/style/index.ts +++ b/components/alert/style/index.ts @@ -85,7 +85,7 @@ export const genBaseStyle: GenerateStyle = (token: AlertToken): CSSO lineHeight: 0, }, - [`&-description`]: { + '&-description': { display: 'none', fontSize, lineHeight, @@ -204,7 +204,7 @@ export const genActionStyle: GenerateStyle = (token: AlertToken): CS return { [componentCls]: { - [`&-action`]: { + '&-action': { marginInlineStart: marginXS, }, diff --git a/components/anchor/Anchor.tsx b/components/anchor/Anchor.tsx index 03fb706f2a69..d00890e038d3 100644 --- a/components/anchor/Anchor.tsx +++ b/components/anchor/Anchor.tsx @@ -7,6 +7,7 @@ import getScroll from '../_util/getScroll'; import scrollTo from '../_util/scrollTo'; import { devUseWarning } from '../_util/warning'; import Affix from '../affix'; +import type { AffixProps } from '../affix'; import type { ConfigConsumerProps } from '../config-provider'; import { ConfigContext } from '../config-provider'; import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; @@ -35,8 +36,7 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number if (rect.width || rect.height) { if (container === window) { - container = element.ownerDocument!.documentElement!; - return rect.top - container.clientTop; + return rect.top - element.ownerDocument!.documentElement!.clientTop; } return rect.top - (container as HTMLElement).getBoundingClientRect().top; } @@ -62,7 +62,7 @@ export interface AnchorProps { children?: React.ReactNode; offsetTop?: number; bounds?: number; - affix?: boolean; + affix?: boolean | Omit; showInkInFixed?: boolean; getContainer?: () => AnchorContainer; /** Return customize highlight anchor */ @@ -256,7 +256,7 @@ const Anchor: React.FC = (props) => { } const container = getCurrentContainer(); - const scrollTop = getScroll(container, true); + const scrollTop = getScroll(container); const eleOffsetTop = getOffsetTop(targetElement, container); let y = scrollTop + eleOffsetTop; y -= targetOffset !== undefined ? targetOffset : offsetTop || 0; @@ -348,10 +348,12 @@ const Anchor: React.FC = (props) => { [activeLink, onClick, handleScrollTo, anchorDirection], ); + const affixProps = affix && typeof affix === 'object' ? affix : undefined; + return wrapCSSVar( {affix ? ( - + {anchorContent} ) : ( diff --git a/components/anchor/AnchorLink.tsx b/components/anchor/AnchorLink.tsx index deb2968ceb87..2aaf4f6d5f9e 100644 --- a/components/anchor/AnchorLink.tsx +++ b/components/anchor/AnchorLink.tsx @@ -42,12 +42,12 @@ const AnchorLink: React.FC = (props) => { }, [href]); const handleClick = (e: React.MouseEvent) => { + onClick?.(e, { title, href }); + scrollTo?.(href); if (replace) { e.preventDefault(); window.location.replace(href); } - onClick?.(e, { title, href }); - scrollTo?.(href); }; // =================== Warning ===================== diff --git a/components/anchor/__tests__/Anchor.test.tsx b/components/anchor/__tests__/Anchor.test.tsx index 7c21c8ec1977..ab584d7f18a1 100644 --- a/components/anchor/__tests__/Anchor.test.tsx +++ b/components/anchor/__tests__/Anchor.test.tsx @@ -1,5 +1,5 @@ -import { resetWarned } from 'rc-util/lib/warning'; import React, { useState } from 'react'; +import { resetWarned } from 'rc-util/lib/warning'; import scrollIntoView from 'scroll-into-view-if-needed'; import Anchor from '..'; @@ -67,12 +67,12 @@ describe('Anchor Render', () => { items={[ { key: '1', - href: '#components-anchor-demo-basic', + href: '#anchor-demo-basic', title: 'Item Basic Demo', }, { key: '2', - href: '#components-anchor-demo-static', + href: '#anchor-demo-static', title: 'Static demo', }, { @@ -101,8 +101,8 @@ describe('Anchor Render', () => { const linkTitles = Array.from(container.querySelector('.ant-anchor')?.childNodes!).map((n) => (n as HTMLElement).querySelector('.ant-anchor-link-title'), ); - expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#components-anchor-demo-basic'); - expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#components-anchor-demo-static'); + expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#anchor-demo-basic'); + expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#anchor-demo-static'); expect((linkTitles[3] as HTMLAnchorElement).href).toContain('#api'); expect( ( @@ -127,12 +127,12 @@ describe('Anchor Render', () => { items={[ { key: '1', - href: '#components-anchor-demo-basic', + href: '#anchor-demo-basic', title: 'Item Basic Demo', }, { key: '2', - href: '#components-anchor-demo-static', + href: '#anchor-demo-static', title: 'Static demo', }, { @@ -147,8 +147,8 @@ describe('Anchor Render', () => { const linkTitles = Array.from(container.querySelector('.ant-anchor')?.childNodes!).map((n) => (n as HTMLElement).querySelector('.ant-anchor-link-title'), ); - expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#components-anchor-demo-basic'); - expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#components-anchor-demo-static'); + expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#anchor-demo-basic'); + expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#anchor-demo-static'); expect((linkTitles[3] as HTMLAnchorElement).href).toContain('#api'); expect(asFragment().firstChild).toMatchSnapshot(); }); @@ -159,7 +159,7 @@ describe('Anchor Render', () => { items={[ { key: '1', - href: '#components-anchor-demo-basic', + href: '#anchor-demo-basic', title: 'Item Basic Demo', }, ]} @@ -170,7 +170,7 @@ describe('Anchor Render', () => { expect(container.querySelectorAll('.ant-anchor .ant-anchor-link').length).toBe(1); expect( (container.querySelector('.ant-anchor .ant-anchor-link-title') as HTMLAnchorElement).href, - ).toContain('#components-anchor-demo-basic'); + ).toContain('#anchor-demo-basic'); expect(asFragment().firstChild).toMatchSnapshot(); }); @@ -694,12 +694,12 @@ describe('Anchor Render', () => { items={[ { key: '1', - href: '#components-anchor-demo-basic', + href: '#anchor-demo-basic', title: 'Item Basic Demo', }, { key: '2', - href: '#components-anchor-demo-static', + href: '#anchor-demo-static', title: 'Static demo', }, { @@ -725,12 +725,12 @@ describe('Anchor Render', () => { items={[ { key: '1', - href: '#components-anchor-demo-basic', + href: '#anchor-demo-basic', title: 'Item Basic Demo', }, { key: '2', - href: '#components-anchor-demo-static', + href: '#anchor-demo-static', title: 'Static demo', }, { @@ -759,8 +759,8 @@ describe('Anchor Render', () => { it('nested children via jsx should be filtered out when direction is horizontal', () => { const { container } = render( - - + + @@ -904,12 +904,12 @@ describe('Anchor Render', () => { items={[ { key: '1', - href: '#components-anchor-demo-basic', + href: '#anchor-demo-basic', title: 'Item Basic Demo', }, { key: '2', - href: '#components-anchor-demo-static', + href: '#anchor-demo-static', title: 'Static demo', }, { @@ -935,8 +935,8 @@ describe('Anchor Render', () => { it('deprecated jsx style', () => { render( - - + + , ); expect(errSpy).toHaveBeenCalledWith( @@ -947,8 +947,8 @@ describe('Anchor Render', () => { it('deprecated jsx style for direction#vertical', () => { render( - - + + , ); expect(errSpy).toHaveBeenCalledWith( diff --git a/components/anchor/__tests__/__snapshots__/Anchor.test.tsx.snap b/components/anchor/__tests__/__snapshots__/Anchor.test.tsx.snap index aa6da33ae783..44b7d8b28f62 100644 --- a/components/anchor/__tests__/__snapshots__/Anchor.test.tsx.snap +++ b/components/anchor/__tests__/__snapshots__/Anchor.test.tsx.snap @@ -20,7 +20,7 @@ exports[`Anchor Render render items and ignore jsx children 1`] = ` > Item Basic Demo @@ -52,7 +52,7 @@ exports[`Anchor Render renders items correctly 1`] = ` > Item Basic Demo @@ -63,7 +63,7 @@ exports[`Anchor Render renders items correctly 1`] = ` > Static demo @@ -128,7 +128,7 @@ exports[`Anchor Render renders items correctly#horizontal 1`] = ` > Item Basic Demo @@ -139,7 +139,7 @@ exports[`Anchor Render renders items correctly#horizontal 1`] = ` > Static demo diff --git a/components/anchor/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/anchor/__tests__/__snapshots__/demo-extend.test.ts.snap index f51fde5aca95..62d0b81ab01d 100644 --- a/components/anchor/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/anchor/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -179,7 +179,7 @@ exports[`renders components/anchor/demo/customizeHighlight.tsx extend context co > Basic demo @@ -190,7 +190,7 @@ exports[`renders components/anchor/demo/customizeHighlight.tsx extend context co > Static demo @@ -328,7 +328,7 @@ exports[`renders components/anchor/demo/legacy-anchor.tsx extend context correct > Basic demo @@ -339,7 +339,7 @@ exports[`renders components/anchor/demo/legacy-anchor.tsx extend context correct > Static demo @@ -404,7 +404,7 @@ exports[`renders components/anchor/demo/onChange.tsx extend context correctly 1` > Basic demo @@ -415,7 +415,7 @@ exports[`renders components/anchor/demo/onChange.tsx extend context correctly 1` > Static demo @@ -476,7 +476,7 @@ exports[`renders components/anchor/demo/onClick.tsx extend context correctly 1`] > Basic demo @@ -487,7 +487,7 @@ exports[`renders components/anchor/demo/onClick.tsx extend context correctly 1`] > Static demo @@ -629,7 +629,7 @@ exports[`renders components/anchor/demo/static.tsx extend context correctly 1`] > Basic demo @@ -640,7 +640,7 @@ exports[`renders components/anchor/demo/static.tsx extend context correctly 1`] > Static demo @@ -770,7 +770,7 @@ exports[`renders components/anchor/demo/targetOffset.tsx extend context correctl
Fixed Top Block diff --git a/components/anchor/__tests__/__snapshots__/demo.test.tsx.snap b/components/anchor/__tests__/__snapshots__/demo.test.tsx.snap index d388bea8199b..01a8a8f5e67d 100644 --- a/components/anchor/__tests__/__snapshots__/demo.test.tsx.snap +++ b/components/anchor/__tests__/__snapshots__/demo.test.tsx.snap @@ -172,7 +172,7 @@ exports[`renders components/anchor/demo/customizeHighlight.tsx correctly 1`] = ` > Basic demo @@ -183,7 +183,7 @@ exports[`renders components/anchor/demo/customizeHighlight.tsx correctly 1`] = ` > Static demo @@ -316,7 +316,7 @@ exports[`renders components/anchor/demo/legacy-anchor.tsx correctly 1`] = ` > Basic demo @@ -327,7 +327,7 @@ exports[`renders components/anchor/demo/legacy-anchor.tsx correctly 1`] = ` > Static demo @@ -386,7 +386,7 @@ exports[`renders components/anchor/demo/onChange.tsx correctly 1`] = ` > Basic demo @@ -397,7 +397,7 @@ exports[`renders components/anchor/demo/onChange.tsx correctly 1`] = ` > Static demo @@ -456,7 +456,7 @@ exports[`renders components/anchor/demo/onClick.tsx correctly 1`] = ` > Basic demo @@ -467,7 +467,7 @@ exports[`renders components/anchor/demo/onClick.tsx correctly 1`] = ` > Static demo @@ -604,7 +604,7 @@ exports[`renders components/anchor/demo/static.tsx correctly 1`] = ` > Basic demo @@ -615,7 +615,7 @@ exports[`renders components/anchor/demo/static.tsx correctly 1`] = ` > Static demo @@ -742,7 +742,7 @@ exports[`renders components/anchor/demo/targetOffset.tsx correctly 1`] = `
Fixed Top Block diff --git a/components/anchor/__tests__/cached-context.test.tsx b/components/anchor/__tests__/cached-context.test.tsx index 8db4f3e27c76..de16715b52a7 100644 --- a/components/anchor/__tests__/cached-context.test.tsx +++ b/components/anchor/__tests__/cached-context.test.tsx @@ -1,4 +1,5 @@ import React, { memo, useContext } from 'react'; + import { fireEvent, pureRender } from '../../../tests/utils'; import Anchor from '../Anchor'; import AnchorContext from '../context'; diff --git a/components/anchor/__tests__/demo.test.tsx b/components/anchor/__tests__/demo.test.tsx index 3d68d0fd2cf1..e1bfd40963f7 100644 --- a/components/anchor/__tests__/demo.test.tsx +++ b/components/anchor/__tests__/demo.test.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; + import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest'; demoTest('anchor', { diff --git a/components/anchor/context.ts b/components/anchor/context.ts index ab5c0474e2e1..7413ce37b3cb 100644 --- a/components/anchor/context.ts +++ b/components/anchor/context.ts @@ -1,4 +1,5 @@ import * as React from 'react'; + import type { AntAnchor } from './Anchor'; const AnchorContext = React.createContext(undefined); diff --git a/components/anchor/demo/basic.tsx b/components/anchor/demo/basic.tsx index 325709cb5400..812f475a0bb3 100644 --- a/components/anchor/demo/basic.tsx +++ b/components/anchor/demo/basic.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Anchor, Row, Col } from 'antd'; +import { Anchor, Col, Row } from 'antd'; const App: React.FC = () => ( diff --git a/components/anchor/demo/customizeHighlight.tsx b/components/anchor/demo/customizeHighlight.tsx index fed1ebb93213..dc0f4dd03b77 100644 --- a/components/anchor/demo/customizeHighlight.tsx +++ b/components/anchor/demo/customizeHighlight.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Anchor } from 'antd'; -const getCurrentAnchor = () => '#components-anchor-demo-static'; +const getCurrentAnchor = () => '#anchor-demo-static'; const App: React.FC = () => ( ( items={[ { key: '1', - href: '#components-anchor-demo-basic', + href: '#anchor-demo-basic', title: 'Basic demo', }, { key: '2', - href: '#components-anchor-demo-static', + href: '#anchor-demo-static', title: 'Static demo', }, { diff --git a/components/anchor/demo/legacy-anchor.tsx b/components/anchor/demo/legacy-anchor.tsx index a5dabdc510cd..fffb844a2e10 100644 --- a/components/anchor/demo/legacy-anchor.tsx +++ b/components/anchor/demo/legacy-anchor.tsx @@ -5,8 +5,8 @@ const { Link } = Anchor; const App: React.FC = () => ( - - + + diff --git a/components/anchor/demo/onChange.tsx b/components/anchor/demo/onChange.tsx index da2f7b587e6b..0fb21dcf3ef0 100644 --- a/components/anchor/demo/onChange.tsx +++ b/components/anchor/demo/onChange.tsx @@ -12,12 +12,12 @@ const App: React.FC = () => ( items={[ { key: '1', - href: '#components-anchor-demo-basic', + href: '#anchor-demo-basic', title: 'Basic demo', }, { key: '2', - href: '#components-anchor-demo-static', + href: '#anchor-demo-static', title: 'Static demo', }, { diff --git a/components/anchor/demo/onClick.tsx b/components/anchor/demo/onClick.tsx index 4f9866572db8..73bbcbf9c885 100644 --- a/components/anchor/demo/onClick.tsx +++ b/components/anchor/demo/onClick.tsx @@ -19,12 +19,12 @@ const App: React.FC = () => ( items={[ { key: '1', - href: '#components-anchor-demo-basic', + href: '#anchor-demo-basic', title: 'Basic demo', }, { key: '2', - href: '#components-anchor-demo-static', + href: '#anchor-demo-static', title: 'Static demo', }, { diff --git a/components/anchor/demo/static.tsx b/components/anchor/demo/static.tsx index be2d34c05303..2be1d6102881 100644 --- a/components/anchor/demo/static.tsx +++ b/components/anchor/demo/static.tsx @@ -7,12 +7,12 @@ const App: React.FC = () => ( items={[ { key: '1', - href: '#components-anchor-demo-basic', + href: '#anchor-demo-basic', title: 'Basic demo', }, { key: '2', - href: '#components-anchor-demo-static', + href: '#anchor-demo-static', title: 'Static demo', }, { diff --git a/components/anchor/demo/targetOffset.tsx b/components/anchor/demo/targetOffset.tsx index 79428a8e5906..2a4b91a0a1b4 100644 --- a/components/anchor/demo/targetOffset.tsx +++ b/components/anchor/demo/targetOffset.tsx @@ -1,5 +1,15 @@ import React, { useEffect, useState } from 'react'; -import { Anchor, Row, Col } from 'antd'; +import { Anchor, Col, Row } from 'antd'; + +const style: React.CSSProperties = { + height: '30vh', + backgroundColor: 'rgba(0, 0, 0, 0.85)', + position: 'fixed', + top: 0, + insetInlineStart: 0, + width: '75%', + color: '#fff', +}; const App: React.FC = () => { const topRef = React.useRef(null); @@ -30,38 +40,14 @@ const App: React.FC = () => { - -
+
Fixed Top Block
diff --git a/components/anchor/index.en-US.md b/components/anchor/index.en-US.md index 3b14d3a940b4..5aaf25f4fb26 100644 --- a/components/anchor/index.en-US.md +++ b/components/anchor/index.en-US.md @@ -1,6 +1,7 @@ --- category: Components title: Anchor +description: Hyperlinks to scroll on one page. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ufP1TLS5VvIAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*_9_eTrgvHNQAAAAAAAAAAAAADrJ8AQ/original demo: @@ -9,8 +10,6 @@ group: order: 3 --- -Hyperlinks to scroll on one page. - ## When To Use For displaying anchor hyperlinks on page and jumping between them. @@ -41,13 +40,13 @@ Common props ref:[Common props](/docs/react/common-props) | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | -| affix | Fixed mode of Anchor | boolean | true | | +| affix | Fixed mode of Anchor | boolean \| Omit | true | object: 5.19.0 | | bounds | Bounding distance of anchor area | number | 5 | | | getContainer | Scrolling container | () => HTMLElement | () => window | | | getCurrentAnchor | Customize the anchor highlight | (activeLink: string) => string | - | | | offsetTop | Pixels to offset from top when calculating position of scroll | number | 0 | | | showInkInFixed | Whether show ink-square when `affix={false}` | boolean | false | | -| targetOffset | Anchor scroll offset, default as `offsetTop`, [example](#components-anchor-demo-targetoffset) | number | - | | +| targetOffset | Anchor scroll offset, default as `offsetTop`, [example](#anchor-demo-targetoffset) | number | - | | | onChange | Listening for anchor link change | (currentActiveLink: string) => void | | | | onClick | Set the handler to handle `click` event | (e: MouseEvent, link: object) => void | - | | | items | Data configuration option content, support nesting through children | { key, href, title, target, children }\[] [see](#anchoritem) | - | 5.1.0 | diff --git a/components/anchor/index.ts b/components/anchor/index.tsx similarity index 100% rename from components/anchor/index.ts rename to components/anchor/index.tsx diff --git a/components/anchor/index.zh-CN.md b/components/anchor/index.zh-CN.md index 150d93f8e7dc..733f3ade86a4 100644 --- a/components/anchor/index.zh-CN.md +++ b/components/anchor/index.zh-CN.md @@ -2,6 +2,7 @@ category: Components title: Anchor subtitle: 锚点 +description: 用于跳转到页面指定位置。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ufP1TLS5VvIAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*_9_eTrgvHNQAAAAAAAAAAAAADrJ8AQ/original demo: @@ -10,8 +11,6 @@ group: order: 3 --- -用于跳转到页面指定位置。 - ## 何时使用 需要展现当前页面上可供跳转的锚点链接,以及快速在锚点之间跳转。 @@ -42,13 +41,13 @@ group: | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| affix | 固定模式 | boolean | true | | +| affix | 固定模式 | boolean \| Omit | true | object: 5.19.0 | | bounds | 锚点区域边界 | number | 5 | | | getContainer | 指定滚动的容器 | () => HTMLElement | () => window | | | getCurrentAnchor | 自定义高亮的锚点 | (activeLink: string) => string | - | | | offsetTop | 距离窗口顶部达到指定偏移量后触发 | number | | | | showInkInFixed | `affix={false}` 时是否显示小方块 | boolean | false | | -| targetOffset | 锚点滚动偏移量,默认与 offsetTop 相同,[例子](#components-anchor-demo-targetoffset) | number | - | | +| targetOffset | 锚点滚动偏移量,默认与 offsetTop 相同,[例子](#anchor-demo-targetoffset) | number | - | | | onChange | 监听锚点链接改变 | (currentActiveLink: string) => void | - | | | onClick | `click` 事件的 handler | (e: MouseEvent, link: object) => void | - | | | items | 数据化配置选项内容,支持通过 children 嵌套 | { key, href, title, target, children }\[] [具体见](#anchoritem) | - | 5.1.0 | diff --git a/components/anchor/style/index.ts b/components/anchor/style/index.ts index 4e5a66f30fbb..646c796925cb 100644 --- a/components/anchor/style/index.ts +++ b/components/anchor/style/index.ts @@ -17,10 +17,30 @@ export interface ComponentToken { linkPaddingInlineStart: number; } +/** + * @desc Anchor 组件的 Token + * @descEN Token for Anchor component + */ interface AnchorToken extends FullToken<'Anchor'> { + /** + * @desc 容器块偏移量 + * @descEN Holder block offset + */ holderOffsetBlock: number; + /** + * @desc 次级锚点块内间距 + * @descEN Secondary anchor block padding + */ anchorPaddingBlockSecondary: number | string; + /** + * @desc 锚点球大小 + * @descEN Anchor ball size + */ anchorBallSize: number | string; + /** + * @desc 锚点标题块 + * @descEN Anchor title block + */ anchorTitleBlock: number | string; } diff --git a/components/app/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/app/__tests__/__snapshots__/demo-extend.test.ts.snap index 8f2b149cf8df..cecadc885356 100644 --- a/components/app/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/app/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -6,6 +6,7 @@ exports[`renders components/app/demo/basic.tsx extend context correctly 1`] = ` >
{ }); describe('component', () => { + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + afterEach(() => { + errorSpy.mockReset(); + }); + + afterAll(() => { + errorSpy.mockRestore(); + }); + it('replace', () => { const { container } = render( @@ -222,15 +233,25 @@ describe('App', () => { }); it('to false', () => { - const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const { container } = render(

, ); - expect(warnSpy).not.toHaveBeenCalled(); + expect(errorSpy).not.toHaveBeenCalled(); expect(container.querySelector('.ant-app')).toBeFalsy(); - warnSpy.mockRestore(); + }); + + it('should warn if component is false and cssVarCls is not empty', () => { + render( + + + , + ); + + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: [antd: App] When using cssVar, ensure `component` is assigned a valid React component string.', + ); }); }); }); diff --git a/components/app/demo/basic.tsx b/components/app/demo/basic.tsx index 62692e0c73b4..ec7de9548eb3 100644 --- a/components/app/demo/basic.tsx +++ b/components/app/demo/basic.tsx @@ -18,14 +18,14 @@ const MyPage = () => { const showNotification = () => { notification.info({ - message: `Notification topLeft`, + message: 'Notification topLeft', description: 'Hello, Ant Design!!', placement: 'topLeft', }); }; return ( - + diff --git a/components/app/demo/config.tsx b/components/app/demo/config.tsx index fdc25a6e70c4..a4b5d431caa2 100644 --- a/components/app/demo/config.tsx +++ b/components/app/demo/config.tsx @@ -11,13 +11,13 @@ const MyPage = () => { const showNotification = () => { notification.info({ - message: `Notification`, + message: 'Notification', description: 'Hello, Ant Design!!', }); }; return ( - + diff --git a/components/app/index.en-US.md b/components/app/index.en-US.md index ddbd04af366e..9b6c9e43f7c7 100644 --- a/components/app/index.en-US.md +++ b/components/app/index.en-US.md @@ -2,19 +2,18 @@ category: Components group: Other title: App +description: Application wrapper for some global usages. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*HJz8SZos2wgAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*oC92TK44Ex8AAAAAAAAAAAAADrJ8AQ/original demo: cols: 2 -tag: New +tag: 5.1.0 --- -Application wrapper for some global usages. - ## When To Use - Provide reset styles based on `.ant-app` element. -- You could use static methods of `message/notification/Modal` form `useApp` without writing `contextHolder` manually. +- You could use static methods of `message/notification/Modal` from `useApp` without writing `contextHolder` manually. ## Examples @@ -126,14 +125,22 @@ export default () => { Common props ref:[Common props](/docs/react/common-props) +> This component is available since `antd@5.1.0`. + ### App | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | -| component | Config render element, if `false` will not create DOM node | ComponentType | div | 5.11.0 | +| component | Config render element, if `false` will not create DOM node | ComponentType \| false | div | 5.11.0 | | message | Global config for Message | [MessageConfig](/components/message/#messageconfig) | - | 5.3.0 | | notification | Global config for Notification | [NotificationConfig](/components/notification/#notificationconfig) | - | 5.3.0 | ## Design Token + +## FAQ + +### CSS Var doesn't work inside `` + +Make sure the App `component` is a legit React component string, so when you're turning on CSS variables, there's a container to hold the CSS class name. diff --git a/components/app/index.tsx b/components/app/index.tsx index dafb414f11ff..b84ba221ee76 100644 --- a/components/app/index.tsx +++ b/components/app/index.tsx @@ -3,6 +3,7 @@ import React, { useContext } from 'react'; import classNames from 'classnames'; import type { AnyObject, CustomComponent } from '../_util/type'; +import { devUseWarning } from '../_util/warning'; import type { ConfigConsumerProps } from '../config-provider'; import { ConfigContext } from '../config-provider'; import useMessage from '../message/useMessage'; @@ -64,6 +65,13 @@ const App: React.FC & { useApp: () => useAppProps } = (props) => { [messageApi, notificationApi, ModalApi], ); + // https://github.com/ant-design/ant-design/issues/48802#issuecomment-2097813526 + devUseWarning('App')( + !(cssVarCls && component === false), + 'usage', + 'When using cssVar, ensure `component` is assigned a valid React component string.', + ); + // ============================ Render ============================ const Component = component === false ? React.Fragment : component; const rootProps: AppProps = { diff --git a/components/app/index.zh-CN.md b/components/app/index.zh-CN.md index 056c54716058..74f04ae9b2aa 100644 --- a/components/app/index.zh-CN.md +++ b/components/app/index.zh-CN.md @@ -1,17 +1,16 @@ --- category: Components -subtitle: 包裹组件 group: 其他 title: App +subtitle: 包裹组件 +description: 提供重置样式和提供消费上下文的默认环境。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*HJz8SZos2wgAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*oC92TK44Ex8AAAAAAAAAAAAADrJ8AQ/original demo: cols: 2 -tag: New +tag: 5.1.0 --- -新的包裹组件,提供重置样式和提供消费上下文的默认环境。 - ## 何时使用 - 提供可消费 React context 的 `message.xxx`、`Modal.xxx`、`notification.xxx` 的静态方法,可以简化 useMessage 等方法需要手动植入 `contextHolder` 的问题。 @@ -127,14 +126,22 @@ export default () => { 通用属性参考:[通用属性](/docs/react/common-props) +> 自 `antd@5.1.0` 版本开始提供该组件。 + ### App | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| component | 设置渲染元素,为 `false` 则不创建 DOM 节点 | ComponentType | div | 5.11.0 | +| component | 设置渲染元素,为 `false` 则不创建 DOM 节点 | ComponentType \| false | div | 5.11.0 | | message | App 内 Message 的全局配置 | [MessageConfig](/components/message-cn/#messageconfig) | - | 5.3.0 | | notification | App 内 Notification 的全局配置 | [NotificationConfig](/components/notification-cn/#notificationconfig) | - | 5.3.0 | ## 主题变量(Design Token) + +## FAQ + +### CSS Var 在 `` 内不起作用 + +请确保 App 的 `component` 是一个有效的 React 组件字符串,以便在启用 CSS 变量时,有一个容器来承载 CSS 类名。 diff --git a/components/app/style/index.ts b/components/app/style/index.ts index bd56801eca2e..a2580d54dffb 100644 --- a/components/app/style/index.ts +++ b/components/app/style/index.ts @@ -1,7 +1,8 @@ import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal'; import { genStyleHooks } from '../../theme/internal'; -export type ComponentToken = {}; +// biome-ignore lint/suspicious/noEmptyInterface: ComponentToken need to be empty by default +export interface ComponentToken {} interface AppToken extends FullToken<'App'> {} diff --git a/components/auto-complete/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/auto-complete/__tests__/__snapshots__/demo-extend.test.ts.snap index 1491ae112b24..ee67ca94c7b5 100644 --- a/components/auto-complete/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/auto-complete/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -292,17 +292,18 @@ exports[`renders components/auto-complete/demo/certain-category.tsx extend conte

- +
Libraries more - +
AntDesign @@ -354,7 +355,7 @@ exports[`renders components/auto-complete/demo/certain-category.tsx extend conte class="ant-select-item-option-content" >
AntDesign UI @@ -391,17 +392,18 @@ exports[`renders components/auto-complete/demo/certain-category.tsx extend conte
AntDesign UI FAQ @@ -453,7 +455,7 @@ exports[`renders components/auto-complete/demo/certain-category.tsx extend conte class="ant-select-item-option-content" >
AntDesign FAQ @@ -490,17 +492,18 @@ exports[`renders components/auto-complete/demo/certain-category.tsx extend conte
- +
Articles more - +
AntDesign design language @@ -757,6 +760,9 @@ exports[`renders components/auto-complete/demo/form-debug.tsx extend context cor width="64" xmlns="http://www.w3.org/2000/svg" > + + Simple Empty + + + Simple Empty + + + Simple Empty + + + Simple Empty + + + Simple Empty + { beforeAll(() => { diff --git a/components/auto-complete/__tests__/index.test.tsx b/components/auto-complete/__tests__/index.test.tsx index 6dbb9c11b71a..687ac09efb9f 100644 --- a/components/auto-complete/__tests__/index.test.tsx +++ b/components/auto-complete/__tests__/index.test.tsx @@ -1,10 +1,11 @@ -import userEvent from '@testing-library/user-event'; import React from 'react'; +import userEvent from '@testing-library/user-event'; + import AutoComplete from '..'; +import { resetWarned } from '../../_util/warning'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; import { render, screen } from '../../../tests/utils'; -import { resetWarned } from '../../_util/warning'; import Input from '../../input'; describe('AutoComplete', () => { diff --git a/components/auto-complete/demo/allowClear.tsx b/components/auto-complete/demo/allowClear.tsx index 39ebaed713ed..01ee7283359d 100644 --- a/components/auto-complete/demo/allowClear.tsx +++ b/components/auto-complete/demo/allowClear.tsx @@ -1,13 +1,14 @@ import React, { useState } from 'react'; import { CloseSquareFilled } from '@ant-design/icons'; import { AutoComplete } from 'antd'; +import type { AutoCompleteProps } from 'antd'; const mockVal = (str: string, repeat = 1) => ({ value: str.repeat(repeat), }); const App: React.FC = () => { - const [options, setOptions] = useState<{ value: string }[]>([]); + const [options, setOptions] = useState([]); const getPanelValue = (searchText: string) => !searchText ? [] : [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)]; diff --git a/components/auto-complete/demo/basic.tsx b/components/auto-complete/demo/basic.tsx index 42238fc6e25c..c37fe6959ea7 100644 --- a/components/auto-complete/demo/basic.tsx +++ b/components/auto-complete/demo/basic.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { AutoComplete } from 'antd'; +import type { AutoCompleteProps } from 'antd'; const mockVal = (str: string, repeat = 1) => ({ value: str.repeat(repeat), @@ -7,8 +8,8 @@ const mockVal = (str: string, repeat = 1) => ({ const App: React.FC = () => { const [value, setValue] = useState(''); - const [options, setOptions] = useState<{ value: string }[]>([]); - const [anotherOptions, setAnotherOptions] = useState<{ value: string }[]>([]); + const [options, setOptions] = useState([]); + const [anotherOptions, setAnotherOptions] = useState([]); const getPanelValue = (searchText: string) => !searchText ? [] : [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)]; diff --git a/components/auto-complete/demo/certain-category.md b/components/auto-complete/demo/certain-category.md index 6f309f94220e..f03c1200020e 100644 --- a/components/auto-complete/demo/certain-category.md +++ b/components/auto-complete/demo/certain-category.md @@ -17,7 +17,7 @@ Demonstration of [Lookup Patterns: Certain Category](https://ant.design/docs/spe } .certain-category-search-dropdown .ant-select-dropdown-menu-item { - padding-left: 16px; + padding-inline-start: 16px; } .certain-category-search-dropdown .ant-select-dropdown-menu-item.show-all { diff --git a/components/auto-complete/demo/certain-category.tsx b/components/auto-complete/demo/certain-category.tsx index 78201281eb78..0ad3c78fc68b 100644 --- a/components/auto-complete/demo/certain-category.tsx +++ b/components/auto-complete/demo/certain-category.tsx @@ -1,49 +1,39 @@ import React from 'react'; import { UserOutlined } from '@ant-design/icons'; -import { AutoComplete, Input } from 'antd'; +import { AutoComplete, Flex, Input } from 'antd'; -const renderTitle = (title: string) => ( - - {title} - +const Title: React.FC> = (props) => ( + + {props.title} + more - + ); const renderItem = (title: string, count: number) => ({ value: title, label: ( -
+ {title} {count} -
+ ), }); const options = [ { - label: renderTitle('Libraries'), + label: , options: [renderItem('AntDesign', 10000), renderItem('AntDesign UI', 10600)], }, { - label: renderTitle('Solutions'), + label: <Title title="Solutions" />, options: [renderItem('AntDesign UI FAQ', 60100), renderItem('AntDesign FAQ', 30010)], }, { - label: renderTitle('Articles'), + label: <Title title="Articles" />, options: [renderItem('AntDesign design language', 100000)], }, ]; diff --git a/components/auto-complete/demo/custom.tsx b/components/auto-complete/demo/custom.tsx index 6fc062f392b0..f7fe431b0269 100644 --- a/components/auto-complete/demo/custom.tsx +++ b/components/auto-complete/demo/custom.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; import { AutoComplete, Input } from 'antd'; +import type { AutoCompleteProps } from 'antd'; const { TextArea } = Input; const App: React.FC = () => { - const [options, setOptions] = useState<{ value: string }[]>([]); + const [options, setOptions] = useState<AutoCompleteProps['options']>([]); const handleSearch = (value: string) => { setOptions( diff --git a/components/auto-complete/demo/options.tsx b/components/auto-complete/demo/options.tsx index 337f0d5b0b7d..8ef609724e48 100644 --- a/components/auto-complete/demo/options.tsx +++ b/components/auto-complete/demo/options.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { AutoComplete } from 'antd'; -import type { DefaultOptionType } from 'antd/es/select'; +import type { AutoCompleteProps } from 'antd'; const App: React.FC = () => { - const [options, setOptions] = React.useState<DefaultOptionType[]>([]); + const [options, setOptions] = React.useState<AutoCompleteProps['options']>([]); const handleSearch = (value: string) => { setOptions(() => { if (!value || value.includes('@')) { return []; } - return ['gmail.com', '163.com', 'qq.com'].map<DefaultOptionType>((domain) => ({ + return ['gmail.com', '163.com', 'qq.com'].map((domain) => ({ label: `${value}@${domain}`, value: `${value}@${domain}`, })); diff --git a/components/auto-complete/demo/render-panel.tsx b/components/auto-complete/demo/render-panel.tsx index 7eb320ce3566..fb1947d4d07a 100644 --- a/components/auto-complete/demo/render-panel.tsx +++ b/components/auto-complete/demo/render-panel.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { AutoComplete, Switch, Space } from 'antd'; +import { AutoComplete, Space, Switch } from 'antd'; const { _InternalPanelDoNotUseOrYouWillBeFired: InternalAutoComplete } = AutoComplete; diff --git a/components/auto-complete/demo/status.tsx b/components/auto-complete/demo/status.tsx index 4eeffdf077c1..c7eb9d869f7e 100644 --- a/components/auto-complete/demo/status.tsx +++ b/components/auto-complete/demo/status.tsx @@ -1,13 +1,14 @@ import React, { useState } from 'react'; import { AutoComplete, Space } from 'antd'; +import type { AutoCompleteProps } from 'antd'; const mockVal = (str: string, repeat = 1) => ({ value: str.repeat(repeat), }); const App: React.FC = () => { - const [options, setOptions] = useState<{ value: string }[]>([]); - const [anotherOptions, setAnotherOptions] = useState<{ value: string }[]>([]); + const [options, setOptions] = useState<AutoCompleteProps['options']>([]); + const [anotherOptions, setAnotherOptions] = useState<AutoCompleteProps['options']>([]); const getPanelValue = (searchText: string) => !searchText ? [] : [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)]; diff --git a/components/auto-complete/demo/uncertain-category.tsx b/components/auto-complete/demo/uncertain-category.tsx index c01f5d356e3c..f00e46cf9772 100644 --- a/components/auto-complete/demo/uncertain-category.tsx +++ b/components/auto-complete/demo/uncertain-category.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { AutoComplete, Input } from 'antd'; -import type { SelectProps } from 'antd'; +import type { AutoCompleteProps } from 'antd'; const getRandomInt = (max: number, min = 0) => Math.floor(Math.random() * (max - min + 1)) + min; @@ -36,7 +36,7 @@ const searchResult = (query: string) => }); const App: React.FC = () => { - const [options, setOptions] = useState<SelectProps<object>['options']>([]); + const [options, setOptions] = useState<AutoCompleteProps['options']>([]); const handleSearch = (value: string) => { setOptions(value ? searchResult(value) : []); diff --git a/components/auto-complete/demo/variant.md b/components/auto-complete/demo/variant.md index 4ba16eaa6d20..7d07b411ff37 100644 --- a/components/auto-complete/demo/variant.md +++ b/components/auto-complete/demo/variant.md @@ -4,4 +4,4 @@ ## en-US -There are `outlined` `fille` and `borderless`, totally three variants to choose from. +There are `outlined` `filled` and `borderless`, totally three variants to choose from. diff --git a/components/auto-complete/demo/variant.tsx b/components/auto-complete/demo/variant.tsx index 058a32219e7f..ae2a86a7f531 100644 --- a/components/auto-complete/demo/variant.tsx +++ b/components/auto-complete/demo/variant.tsx @@ -1,12 +1,13 @@ import React, { useState } from 'react'; import { AutoComplete, Flex } from 'antd'; +import type { AutoCompleteProps } from 'antd'; const mockVal = (str: string, repeat = 1) => ({ value: str.repeat(repeat), }); const App: React.FC = () => { - const [options, setOptions] = useState<{ value: string }[]>([]); + const [options, setOptions] = useState<AutoCompleteProps['options']>([]); const getPanelValue = (searchText: string) => !searchText ? [] : [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)]; diff --git a/components/auto-complete/index.en-US.md b/components/auto-complete/index.en-US.md index c89f273555f4..9151e2f5cefa 100644 --- a/components/auto-complete/index.en-US.md +++ b/components/auto-complete/index.en-US.md @@ -1,6 +1,7 @@ --- category: Components title: AutoComplete +description: Autocomplete function of input field. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*g8THS4NpV6sAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*WERTQ6qvgEYAAAAAAAAAAAAADrJ8AQ/original group: @@ -10,8 +11,6 @@ demo: cols: 2 --- -Autocomplete function of input field. - ## When To Use - When you need an input box instead of a selector. @@ -52,9 +51,11 @@ Common props ref:[Common props](/docs/react/common-props) | defaultOpen | Initial open state of dropdown | boolean | - | | | defaultValue | Initial selected option | string | - | | | disabled | Whether disabled select | boolean | false | | +| dropdownRender | Customize dropdown content | (menus: ReactNode) => ReactNode | - | 4.24.0 | | popupClassName | The className of dropdown menu | string | - | 4.23.0 | -| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | true | | +| popupMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | true | | | filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns true, the option will be included in the filtered set; Otherwise, it will be excluded | boolean \| function(inputValue, option) | true | | +| getPopupContainer | Parent node of the dropdown. Default to body, if you encountered positioning problems during scroll, try changing to the scrollable area and position relative to it. [Example](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | | | notFoundContent | Specify content to show when no result matches | ReactNode | - | | | open | Controlled open state of dropdown | boolean | - | | | options | Select options. Will get better perf than jsx definition | { label, value }\[] | - | | diff --git a/components/auto-complete/index.tsx b/components/auto-complete/index.tsx index 31cca78d9f9b..eaf4ff937252 100755 --- a/components/auto-complete/index.tsx +++ b/components/auto-complete/index.tsx @@ -45,8 +45,8 @@ export interface AutoCompleteProps< popupMatchSelectWidth?: boolean | number; } -function isSelectOptionOrSelectOptGroup(child: any): Boolean { - return child && child.type && (child.type.isSelectOption || child.type.isSelectOptGroup); +function isSelectOptionOrSelectOptGroup(child: any): boolean { + return child?.type && (child.type.isSelectOption || child.type.isSelectOptGroup); } const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteProps> = ( diff --git a/components/auto-complete/index.zh-CN.md b/components/auto-complete/index.zh-CN.md index 4b387c3cbff6..10dd176f1270 100644 --- a/components/auto-complete/index.zh-CN.md +++ b/components/auto-complete/index.zh-CN.md @@ -1,7 +1,8 @@ --- category: Components -subtitle: 自动完成 title: AutoComplete +subtitle: 自动完成 +description: 输入框自动完成功能。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*g8THS4NpV6sAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*WERTQ6qvgEYAAAAAAAAAAAAADrJ8AQ/original group: @@ -11,8 +12,6 @@ demo: cols: 2 --- -输入框自动完成功能。 - ## 何时使用 - 需要一个输入框而不是选择器。 @@ -47,15 +46,15 @@ demo: | allowClear | 支持清除 | boolean \| { clearIcon?: ReactNode } | false | 5.8.0: 支持对象形式 | | autoFocus | 自动获取焦点 | boolean | false | | | backfill | 使用键盘选择选项的时候把选中项回填到输入框中 | boolean | false | | -| children (自动完成的数据源) | 自动完成的数据源 | React.ReactElement<OptionProps> \| Array<React.ReactElement<OptionProps>> | - | | -| children (自定义输入框) | 自定义输入框 | HTMLInputElement \| HTMLTextAreaElement \| React.ReactElement<InputProps> | <Input /> | | +| children (自动完成的数据源) | 自动完成的数据源,不能和自定义输入框同时配置 | React.ReactElement<OptionProps> \| Array<React.ReactElement<OptionProps>> | - | | +| children (自定义输入框) | 自定义输入框,不能和自动完成的数据源同时配置 | HTMLInputElement \| HTMLTextAreaElement \| React.ReactElement<InputProps> | <Input /> | | | defaultActiveFirstOption | 是否默认高亮第一个选项 | boolean | true | | | defaultOpen | 是否默认展开下拉菜单 | boolean | - | | | defaultValue | 指定默认选中的条目 | string | - | | | disabled | 是否禁用 | boolean | false | | | dropdownRender | 自定义下拉框内容 | (menus: ReactNode) => ReactNode | - | 4.24.0 | | popupClassName | 下拉菜单的 className 属性 | string | - | 4.23.0 | -| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | | +| popupMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。false 时会关闭虚拟滚动 | boolean \| number | true | | | filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 true,反之则返回 false | boolean \| function(inputValue, option) | true | | | getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | | | notFoundContent | 当下拉列表为空时显示的内容 | ReactNode | - | | diff --git a/components/avatar/AvatarContext.ts b/components/avatar/AvatarContext.ts index b56bfeab71d3..64f064193957 100644 --- a/components/avatar/AvatarContext.ts +++ b/components/avatar/AvatarContext.ts @@ -1,4 +1,5 @@ import * as React from 'react'; + import type { ScreenSizeMap } from '../_util/responsiveObserver'; export type AvatarSize = 'large' | 'small' | 'default' | number | ScreenSizeMap; diff --git a/components/avatar/__tests__/Avatar.test.tsx b/components/avatar/__tests__/Avatar.test.tsx index 2cf2899a46c6..dfbdf9f8a5c9 100644 --- a/components/avatar/__tests__/Avatar.test.tsx +++ b/components/avatar/__tests__/Avatar.test.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; + import Avatar from '..'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; -import { fireEvent, render } from '../../../tests/utils'; -import useBreakpoint from '../../grid/hooks/useBreakpoint'; +import { fireEvent, render, waitFakeTimer } from '../../../tests/utils'; import ConfigProvider from '../../config-provider'; +import useBreakpoint from '../../grid/hooks/useBreakpoint'; jest.mock('../../grid/hooks/useBreakpoint'); @@ -221,4 +222,83 @@ describe('Avatar Render', () => { expect(container.querySelector('.ant-avatar-sm')).toBeTruthy(); expect(container.querySelector('.ant-avatar-lg')).toBeTruthy(); }); + + it('Avatar.Group support max series props and prompt to deprecated', async () => { + const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.useFakeTimers(); + const { container } = render( + <Avatar.Group maxCount={2} maxStyle={{ color: 'blue' }} maxPopoverPlacement="bottom"> + <Avatar>A</Avatar> + <Avatar>B</Avatar> + <Avatar>C</Avatar> + <Avatar>D</Avatar> + </Avatar.Group>, + ); + + const avatars = container?.querySelectorAll<HTMLSpanElement>('.ant-avatar-group .ant-avatar'); + fireEvent.mouseEnter(avatars?.[2]); + await waitFakeTimer(); + + /* check style */ + expect(container.querySelector('.ant-popover-open')).toBeTruthy(); + expect(container.querySelector('.ant-popover-open')).toHaveStyle('color: blue'); + + /* check count */ + expect(avatars.length).toBe(3); + + /* check popover */ + const popover = container.querySelector('.ant-avatar-group-popover'); + expect(popover).toBeTruthy(); + expect(popover).toHaveClass('ant-popover-placement-bottom'); + + expect(errSpy).toHaveBeenNthCalledWith( + 1, + 'Warning: [antd: Avatar.Group] `maxCount` is deprecated. Please use `max={{ count: number }}` instead.', + ); + expect(errSpy).toHaveBeenNthCalledWith( + 2, + 'Warning: [antd: Avatar.Group] `maxStyle` is deprecated. Please use `max={{ style: CSSProperties }}` instead.', + ); + expect(errSpy).toHaveBeenNthCalledWith( + 3, + 'Warning: [antd: Avatar.Group] `maxPopoverPlacement` is deprecated. Please use `max={{ popover: PopoverProps }}` instead.', + ); + }); + it('Avatar.Group support max object props', () => { + const { container } = render( + <Avatar.Group + max={{ + count: 2, + popover: { + placement: 'bottomRight', + overlayClassName: 'wanpan-111', + overlayStyle: { background: 'red' }, + content: 'Avatar.Group', + open: true, + }, + style: { + color: 'blue', + }, + }} + > + <Avatar>A</Avatar> + <Avatar>B</Avatar> + <Avatar>C</Avatar> + <Avatar>D</Avatar> + </Avatar.Group>, + ); + + /* check count */ + expect(container.querySelectorAll('.ant-avatar-group .ant-avatar').length).toBe(3); + + /* check popover */ + const popover = container.querySelector('.ant-avatar-group-popover'); + expect(popover).toBeTruthy(); + expect(popover).toHaveStyle('background: red'); + expect(popover).toHaveClass('wanpan-111 ant-popover-placement-bottomRight'); + expect(container.querySelector('.ant-popover-inner-content')).toHaveTextContent('Avatar.Group'); + + /* check style */ + expect(container.querySelector('.ant-popover-open')).toHaveStyle('color: blue'); + }); }); diff --git a/components/avatar/__tests__/demo.test.tsx b/components/avatar/__tests__/demo.test.tsx index 71d78cbd055c..313f94e1f2db 100644 --- a/components/avatar/__tests__/demo.test.tsx +++ b/components/avatar/__tests__/demo.test.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; + import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest'; demoTest('avatar'); @@ -6,7 +7,7 @@ demoTest('avatar'); rootPropsTest( 'avatar', (Avatar, props) => ( - <Avatar.Group {...props} maxCount={1}> + <Avatar.Group {...props} max={{ count: 1 }}> <Avatar>Bamboo</Avatar> <Avatar>Light</Avatar> </Avatar.Group> diff --git a/components/avatar/avatar.tsx b/components/avatar/avatar.tsx index beb1ff69f2c3..d14f57f8dce5 100644 --- a/components/avatar/avatar.tsx +++ b/components/avatar/avatar.tsx @@ -7,12 +7,12 @@ import type { Breakpoint } from '../_util/responsiveObserver'; import { responsiveArray } from '../_util/responsiveObserver'; import { devUseWarning } from '../_util/warning'; import { ConfigContext } from '../config-provider'; +import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; import useSize from '../config-provider/hooks/useSize'; import useBreakpoint from '../grid/hooks/useBreakpoint'; import type { AvatarContextType, AvatarSize } from './AvatarContext'; import AvatarContext from './AvatarContext'; import useStyle from './style'; -import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; export interface AvatarProps { /** Shape of avatar, options: `circle`, `square` */ @@ -53,7 +53,7 @@ const InternalAvatar: React.ForwardRefRenderFunction<HTMLSpanElement, AvatarProp const avatarNodeRef = React.useRef<HTMLSpanElement>(null); const avatarChildrenRef = React.useRef<HTMLSpanElement>(null); - const avatarNodeMergeRef = composeRef<HTMLSpanElement>(ref, avatarNodeRef); + const avatarNodeMergedRef = composeRef<HTMLSpanElement>(ref, avatarNodeRef); const { getPrefixCls, avatar } = React.useContext(ConfigContext); @@ -234,7 +234,7 @@ const InternalAvatar: React.ForwardRefRenderFunction<HTMLSpanElement, AvatarProp {...others} style={{ ...sizeStyle, ...responsiveSizeStyle, ...avatar?.style, ...others.style }} className={classString} - ref={avatarNodeMergeRef} + ref={avatarNodeMergedRef} > {childrenToRender} </span>, diff --git a/components/avatar/demo/badge.tsx b/components/avatar/demo/badge.tsx index d920d1f63370..fe59e4b6c83e 100644 --- a/components/avatar/demo/badge.tsx +++ b/components/avatar/demo/badge.tsx @@ -1,5 +1,5 @@ -import { UserOutlined } from '@ant-design/icons'; import React from 'react'; +import { UserOutlined } from '@ant-design/icons'; import { Avatar, Badge, Space } from 'antd'; const App: React.FC = () => ( diff --git a/components/avatar/demo/basic.tsx b/components/avatar/demo/basic.tsx index 9fc6b6bcba71..9b7aad98d7f9 100644 --- a/components/avatar/demo/basic.tsx +++ b/components/avatar/demo/basic.tsx @@ -1,5 +1,5 @@ -import { UserOutlined } from '@ant-design/icons'; import React from 'react'; +import { UserOutlined } from '@ant-design/icons'; import { Avatar, Space } from 'antd'; const App: React.FC = () => ( diff --git a/components/avatar/demo/comonent-token.md b/components/avatar/demo/component-token.md similarity index 100% rename from components/avatar/demo/comonent-token.md rename to components/avatar/demo/component-token.md diff --git a/components/avatar/demo/component-token.tsx b/components/avatar/demo/component-token.tsx index 11d686bec18b..1028101cc2a7 100644 --- a/components/avatar/demo/component-token.tsx +++ b/components/avatar/demo/component-token.tsx @@ -1,5 +1,5 @@ -import { AntDesignOutlined, UserOutlined } from '@ant-design/icons'; import React from 'react'; +import { AntDesignOutlined, UserOutlined } from '@ant-design/icons'; import { Avatar, Badge, ConfigProvider, Space, Tooltip } from 'antd'; const App: React.FC = () => ( @@ -28,7 +28,12 @@ const App: React.FC = () => ( </Avatar> </Space> <Space> - <Avatar.Group maxCount={2} maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}> + <Avatar.Group + max={{ + count: 2, + style: { color: '#f56a00', backgroundColor: '#fde3cf' }, + }} + > <Avatar src="https://api.dicebear.com/7.x/miniavs/svg?seed=2" /> <Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar> <Tooltip title="Ant User" placement="top"> diff --git a/components/avatar/demo/group.tsx b/components/avatar/demo/group.tsx index 6519f12eed80..06f2b17c67d9 100644 --- a/components/avatar/demo/group.tsx +++ b/components/avatar/demo/group.tsx @@ -1,5 +1,5 @@ -import { AntDesignOutlined, UserOutlined } from '@ant-design/icons'; import React from 'react'; +import { AntDesignOutlined, UserOutlined } from '@ant-design/icons'; import { Avatar, Divider, Tooltip } from 'antd'; const App: React.FC = () => ( @@ -15,7 +15,12 @@ const App: React.FC = () => ( <Avatar style={{ backgroundColor: '#1677ff' }} icon={<AntDesignOutlined />} /> </Avatar.Group> <Divider /> - <Avatar.Group maxCount={2} maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}> + <Avatar.Group + max={{ + count: 2, + style: { color: '#f56a00', backgroundColor: '#fde3cf' }, + }} + > <Avatar src="https://api.dicebear.com/7.x/miniavs/svg?seed=2" /> <Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar> <Tooltip title="Ant User" placement="top"> @@ -25,9 +30,11 @@ const App: React.FC = () => ( </Avatar.Group> <Divider /> <Avatar.Group - maxCount={2} size="large" - maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }} + max={{ + count: 2, + style: { color: '#f56a00', backgroundColor: '#fde3cf' }, + }} > <Avatar src="https://api.dicebear.com/7.x/miniavs/svg?seed=3" /> <Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar> @@ -38,10 +45,12 @@ const App: React.FC = () => ( </Avatar.Group> <Divider /> <Avatar.Group - maxCount={2} - maxPopoverTrigger="click" size="large" - maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf', cursor: 'pointer' }} + max={{ + count: 2, + style: { color: '#f56a00', backgroundColor: '#fde3cf', cursor: 'pointer' }, + popover: { trigger: 'click' }, + }} > <Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" /> <Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar> diff --git a/components/avatar/demo/type.tsx b/components/avatar/demo/type.tsx index 79288b85a5df..65ffe670c123 100644 --- a/components/avatar/demo/type.tsx +++ b/components/avatar/demo/type.tsx @@ -1,5 +1,5 @@ -import { UserOutlined } from '@ant-design/icons'; import React from 'react'; +import { UserOutlined } from '@ant-design/icons'; import { Avatar, Space } from 'antd'; const url = 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg'; diff --git a/components/avatar/group.tsx b/components/avatar/group.tsx index 984408719705..cccae8c40dd1 100644 --- a/components/avatar/group.tsx +++ b/components/avatar/group.tsx @@ -1,14 +1,17 @@ +import * as React from 'react'; import classNames from 'classnames'; import toArray from 'rc-util/lib/Children/toArray'; -import * as React from 'react'; + +import { cloneElement } from '../_util/reactNode'; +import { devUseWarning } from '../_util/warning'; import { ConfigContext } from '../config-provider'; +import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; +import type { PopoverProps } from '../popover'; import Popover from '../popover'; -import { cloneElement } from '../_util/reactNode'; import Avatar from './avatar'; import AvatarContext from './AvatarContext'; import type { AvatarContextType, AvatarSize } from './AvatarContext'; import useStyle from './style'; -import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; interface ContextProps { children?: React.ReactNode; @@ -31,10 +34,19 @@ export interface GroupProps { children?: React.ReactNode; style?: React.CSSProperties; prefixCls?: string; + /** @deprecated Please use `max={{ count: number }}` */ maxCount?: number; + /** @deprecated Please use `max={{ style: CSSProperties }}` */ maxStyle?: React.CSSProperties; + /** @deprecated Please use `max={{ popover: PopoverProps }}` */ maxPopoverPlacement?: 'top' | 'bottom'; + /** @deprecated Please use `max={{ popover: PopoverProps }}` */ maxPopoverTrigger?: 'hover' | 'focus' | 'click'; + max?: { + count?: number; + style?: React.CSSProperties; + popover?: PopoverProps; + }; /* * Size of avatar, options: `large`, `small`, `default` * or a custom number size @@ -54,11 +66,24 @@ const Group: React.FC<GroupProps> = (props) => { maxStyle, size, shape, - maxPopoverPlacement = 'top', - maxPopoverTrigger = 'hover', + maxPopoverPlacement, + maxPopoverTrigger, children, + max, } = props; + if (process.env.NODE_ENV !== 'production') { + const warning = devUseWarning('Avatar.Group'); + warning.deprecated(!maxCount, 'maxCount', 'max={{ count: number }}'); + warning.deprecated(!maxStyle, 'maxStyle', 'max={{ style: CSSProperties }}'); + warning.deprecated( + !maxPopoverPlacement, + 'maxPopoverPlacement', + 'max={{ popover: PopoverProps }}', + ); + warning.deprecated(!maxPopoverTrigger, 'maxPopoverTrigger', 'max={{ popover: PopoverProps }}'); + } + const prefixCls = getPrefixCls('avatar', customizePrefixCls); const groupPrefixCls = `${prefixCls}-group`; const rootCls = useCSSVarCls(prefixCls); @@ -80,22 +105,30 @@ const Group: React.FC<GroupProps> = (props) => { cloneElement(child, { key: `avatar-key-${index}` }), ); + const mergeCount = max?.count || maxCount; const numOfChildren = childrenWithProps.length; - if (maxCount && maxCount < numOfChildren) { - const childrenShow = childrenWithProps.slice(0, maxCount); - const childrenHidden = childrenWithProps.slice(maxCount, numOfChildren); + if (mergeCount && mergeCount < numOfChildren) { + const childrenShow = childrenWithProps.slice(0, mergeCount); + const childrenHidden = childrenWithProps.slice(mergeCount, numOfChildren); + + const mergeStyle = max?.style || maxStyle; + const mergePopoverTrigger = max?.popover?.trigger || maxPopoverTrigger || 'hover'; + const mergePopoverPlacement = max?.popover?.placement || maxPopoverPlacement || 'top'; + + const mergeProps = { + content: childrenHidden, + ...max?.popover, + overlayClassName: classNames(`${groupPrefixCls}-popover`, max?.popover?.overlayClassName), + placement: mergePopoverPlacement, + trigger: mergePopoverTrigger, + }; + childrenShow.push( - <Popover - key="avatar-popover-key" - content={childrenHidden} - trigger={maxPopoverTrigger} - placement={maxPopoverPlacement} - overlayClassName={`${groupPrefixCls}-popover`} - destroyTooltipOnHide - > - <Avatar style={maxStyle}>{`+${numOfChildren - maxCount}`}</Avatar> + <Popover key="avatar-popover-key" destroyTooltipOnHide {...mergeProps}> + <Avatar style={mergeStyle}>{`+${numOfChildren - mergeCount}`}</Avatar> </Popover>, ); + return wrapCSSVar( <AvatarContextProvider shape={shape} size={size}> <div className={cls} style={style}> diff --git a/components/avatar/index.en-US.md b/components/avatar/index.en-US.md index 4222ba5b57ef..e8ece462c312 100644 --- a/components/avatar/index.en-US.md +++ b/components/avatar/index.en-US.md @@ -1,6 +1,7 @@ --- category: Components title: Avatar +description: Used to represent users or things, supporting the display of images, icons, or characters. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*JJBSS5lBG4IAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YbgyQaRGz-UAAAAAAAAAAAAADrJ8AQ/original demo: @@ -10,8 +11,6 @@ group: order: 5 --- -Avatars can be used to represent people or objects. It supports images, `Icon`s, or letters. - ## Examples <!-- prettier-ignore --> @@ -50,10 +49,7 @@ Common props ref:[Common props](/docs/react/common-props) | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | -| maxCount | Max avatars to show | number | - | | -| maxPopoverPlacement | The placement of excess avatar Popover | `top` \| `bottom` | `top` | | -| maxPopoverTrigger | Set the trigger of excess avatar Popover | `hover` \| `focus` \| `click` | `hover` | 4.17.0 | -| maxStyle | The style of excess avatar style | CSSProperties | - | | +| max | Set maximum display related configurations, Before `5.18.0` you can use [parameters](https://github.com/ant-design/ant-design/blob/9d134859becbdae5b9ce276f6d9af4264691d81f/components/avatar/group.tsx#L35-L38) | `{ count?: number; style?: CSSProperties; popover?: PopoverProps }` | - | 5.18.0 | | size | The size of the avatar | number \| `large` \| `small` \| `default` \| { xs: number, sm: number, ...} | `default` | 4.8.0 | | shape | The shape of the avatar | `circle` \| `square` | `circle` | 5.8.0 | diff --git a/components/avatar/index.ts b/components/avatar/index.tsx similarity index 57% rename from components/avatar/index.ts rename to components/avatar/index.tsx index ea2fa4358021..e227042af1fa 100644 --- a/components/avatar/index.ts +++ b/components/avatar/index.tsx @@ -1,5 +1,3 @@ -import type { ForwardRefExoticComponent, RefAttributes } from 'react'; -import type { AvatarProps } from './avatar'; import InternalAvatar from './avatar'; import Group from './group'; @@ -7,9 +5,7 @@ export type { AvatarProps } from './avatar'; export type { GroupProps } from './group'; export { Group }; -type CompoundedComponent = ForwardRefExoticComponent< - AvatarProps & RefAttributes<HTMLSpanElement> -> & { +type CompoundedComponent = typeof InternalAvatar & { Group: typeof Group; }; diff --git a/components/avatar/index.zh-CN.md b/components/avatar/index.zh-CN.md index 1424b00085b2..1b8b9630f3e2 100644 --- a/components/avatar/index.zh-CN.md +++ b/components/avatar/index.zh-CN.md @@ -1,7 +1,8 @@ --- category: Components -subtitle: 头像 title: Avatar +subtitle: 头像 +description: 用来代表用户或事物,支持图片、图标或字符展示。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*JJBSS5lBG4IAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YbgyQaRGz-UAAAAAAAAAAAAADrJ8AQ/original demo: @@ -11,8 +12,6 @@ group: order: 5 --- -用来代表用户或事物,支持图片、图标或字符展示。 - ## 设计师专属 安装 [Kitchen Sketch 插件 💎](https://kitchen.alipay.com),一键填充高逼格头像和文本。 @@ -55,10 +54,7 @@ group: | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| maxCount | 显示的最大头像个数 | number | - | | -| maxPopoverPlacement | 多余头像气泡弹出位置 | `top` \| `bottom` | `top` | | -| maxPopoverTrigger | 设置多余头像 Popover 的触发方式 | `hover` \| `focus` \| `click` | `hover` | 4.17.0 | -| maxStyle | 多余头像样式 | CSSProperties | - | | +| max | 设置最多显示相关配置,`5.18.0` 前可使用 [参数](https://github.com/ant-design/ant-design/blob/9d134859becbdae5b9ce276f6d9af4264691d81f/components/avatar/group.tsx#L35-L38) | `{ count?: number; style?: CSSProperties; popover?: PopoverProps }` | - | 5.18.0 | | size | 设置头像的大小 | number \| `large` \| `small` \| `default` \| { xs: number, sm: number, ...} | `default` | 4.8.0 | | shape | 设置头像的形状 | `circle` \| `square` | `circle` | 5.8.0 | diff --git a/components/avatar/style/index.ts b/components/avatar/style/index.ts index 8412d89fedd6..2d24db08c538 100644 --- a/components/avatar/style/index.ts +++ b/components/avatar/style/index.ts @@ -1,4 +1,5 @@ -import { type CSSObject, unit } from '@ant-design/cssinjs'; +import { unit } from '@ant-design/cssinjs'; +import type { CSSObject } from '@ant-design/cssinjs'; import { resetComponent } from '../../style'; import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal'; @@ -52,6 +53,10 @@ export interface ComponentToken { groupBorderColor: string; } +/** + * @desc Avatar 组件的 Token + * @descEN Token for Avatar component + */ type AvatarToken = FullToken<'Avatar'> & { avatarBgColor: string; avatarBg: string; @@ -111,7 +116,7 @@ const genBaseStyle: GenerateStyle<AvatarToken> = (token) => { background: avatarBg, border: `${unit(lineWidth)} ${lineType} transparent`, - [`&-image`]: { + '&-image': { background: 'transparent', }, @@ -121,11 +126,11 @@ const genBaseStyle: GenerateStyle<AvatarToken> = (token) => { ...avatarSizeStyle(containerSize, textFontSize, borderRadius), - [`&-lg`]: { + '&-lg': { ...avatarSizeStyle(containerSizeLG, textFontSizeLG, borderRadiusLG), }, - [`&-sm`]: { + '&-sm': { ...avatarSizeStyle(containerSizeSM, textFontSizeSM, borderRadiusSM), }, @@ -146,11 +151,11 @@ const genGroupStyle: GenerateStyle<AvatarToken> = (token) => { [`${componentCls}-group`]: { display: 'inline-flex', - [`${componentCls}`]: { + [componentCls]: { borderColor: groupBorderColor, }, - [`> *:not(:first-child)`]: { + '> *:not(:first-child)': { marginInlineStart: groupOverlapping, }, }, diff --git a/components/back-top/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/back-top/__tests__/__snapshots__/demo-extend.test.ts.snap index 9692dcb13f27..a40ca977d98b 100644 --- a/components/back-top/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/back-top/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -9,7 +9,7 @@ Array [ <strong class="site-back-top-basic" > - gray + gray </strong>, button., ] diff --git a/components/back-top/__tests__/__snapshots__/demo.test.ts.snap b/components/back-top/__tests__/__snapshots__/demo.test.ts.snap index 84345cfb3196..f184cc5b7c68 100644 --- a/components/back-top/__tests__/__snapshots__/demo.test.ts.snap +++ b/components/back-top/__tests__/__snapshots__/demo.test.ts.snap @@ -9,7 +9,7 @@ Array [ <strong class="site-back-top-basic" > - gray + gray </strong>, button., ] diff --git a/components/back-top/__tests__/index.test.tsx b/components/back-top/__tests__/index.test.tsx index 1f658f00fee2..7a959b45731e 100644 --- a/components/back-top/__tests__/index.test.tsx +++ b/components/back-top/__tests__/index.test.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import BackTop from '..'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; diff --git a/components/back-top/index.tsx b/components/back-top/index.tsx index ef1f1f9c77e3..8961b5495955 100644 --- a/components/back-top/index.tsx +++ b/components/back-top/index.tsx @@ -40,11 +40,11 @@ const BackTop: React.FC<BackTopProps> = (props) => { const ref = React.useRef<HTMLDivElement>(null); const getDefaultTarget = (): HTMLElement | Document | Window => - ref.current && ref.current.ownerDocument ? ref.current.ownerDocument : window; + ref.current?.ownerDocument || window; const handleScroll = throttleByAnimationFrame( (e: React.UIEvent<HTMLElement, UIEvent> | { target: any }) => { - const scrollTop = getScroll(e.target, true); + const scrollTop = getScroll(e.target); setVisible(scrollTop >= visibilityHeight); }, ); diff --git a/components/back-top/style/index.ts b/components/back-top/style/index.ts index 29e7ea5775c6..33ac631c8ab2 100644 --- a/components/back-top/style/index.ts +++ b/components/back-top/style/index.ts @@ -1,4 +1,5 @@ -import { type CSSObject, unit } from '@ant-design/cssinjs'; +import { unit } from '@ant-design/cssinjs'; +import type { CSSObject } from '@ant-design/cssinjs'; import { resetComponent } from '../../style'; import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal'; @@ -6,20 +7,60 @@ import { genStyleHooks, mergeToken } from '../../theme/internal'; /** Component only token. Which will handle additional calculation of alias token */ export interface ComponentToken { + /** + * @desc 弹出层的 z-index + * @descEN z-index of popup + */ zIndexPopup: number; } type BackTopToken = FullToken<'BackTop'> & { + /** + * @desc BackTop 背景颜色 + * @descEN Background color of BackTop + */ backTopBackground: string; + /** + * @desc BackTop 文字颜色 + * @descEN Text color of BackTop + */ backTopColor: string; + /** + * @desc BackTop 悬停背景颜色 + * @descEN Hover background color of BackTop + */ backTopHoverBackground: string; + /** + * @desc BackTop 字体大小 + * @descEN Font size of BackTop + */ backTopFontSize: number; + /** + * @desc BackTop 尺寸 + * @descEN Size of BackTop + */ backTopSize: number; // Position + /** + * @desc BackTop 底部偏移量 + * @descEN Bottom offset of BackTop + */ backTopBlockEnd: number | string; + /** + * @desc BackTop 右侧偏移量 + * @descEN Right offset of BackTop + */ backTopInlineEnd: number | string; + /** + * @desc BackTop 中等屏幕右侧偏移量 + * @descEN Right offset of BackTop on medium screens + */ backTopInlineEndMD: number | string; + /** + * @desc BackTop 小屏幕右侧偏移量 + * @descEN Right offset of BackTop on small screens + */ backTopInlineEndXS: number | string; }; diff --git a/components/badge/ScrollNumber.tsx b/components/badge/ScrollNumber.tsx index 79414bcde678..4324f7f57581 100644 --- a/components/badge/ScrollNumber.tsx +++ b/components/badge/ScrollNumber.tsx @@ -1,5 +1,6 @@ -import classNames from 'classnames'; import * as React from 'react'; +import classNames from 'classnames'; + import { cloneElement } from '../_util/reactNode'; import { ConfigContext } from '../config-provider'; import SingleNumber from './SingleNumber'; @@ -69,7 +70,7 @@ const ScrollNumber = React.forwardRef<HTMLElement, ScrollNumberProps>((props, re // allow specify the border // mock border-color by box-shadow for compatible with old usage: // <Badge count={4} style={{ backgroundColor: '#fff', color: '#999', borderColor: '#d9d9d9' }} /> - if (style && style.borderColor) { + if (style?.borderColor) { newProps.style = { ...style, boxShadow: `0 0 0 1px ${style.borderColor} inset`, diff --git a/components/badge/SingleNumber.tsx b/components/badge/SingleNumber.tsx index 3973a1e82557..829569c74e53 100644 --- a/components/badge/SingleNumber.tsx +++ b/components/badge/SingleNumber.tsx @@ -1,5 +1,5 @@ -import classNames from 'classnames'; import * as React from 'react'; +import classNames from 'classnames'; export interface UnitNumberProps { prefixCls: string; @@ -8,28 +8,18 @@ export interface UnitNumberProps { current?: boolean; } -function UnitNumber({ prefixCls, value, current, offset = 0 }: UnitNumberProps) { +const UnitNumber: React.FC<Readonly<UnitNumberProps>> = (props) => { + const { prefixCls, value, current, offset = 0 } = props; let style: React.CSSProperties | undefined; - if (offset) { - style = { - position: 'absolute', - top: `${offset}00%`, - left: 0, - }; + style = { position: 'absolute', top: `${offset}00%`, left: 0 }; } - return ( - <span - style={style} - className={classNames(`${prefixCls}-only-unit`, { - current, - })} - > + <span style={style} className={classNames(`${prefixCls}-only-unit`, { current })}> {value} </span> ); -} +}; export interface SingleNumberProps { prefixCls: string; @@ -49,7 +39,7 @@ function getOffset(start: number, end: number, unit: -1 | 1) { return offset; } -export default function SingleNumber(props: SingleNumberProps) { +const SingleNumber: React.FC<Readonly<SingleNumberProps>> = (props) => { const { prefixCls, count: originCount, value: originValue } = props; const value = Number(originValue); const count = Math.abs(originCount); @@ -57,20 +47,15 @@ export default function SingleNumber(props: SingleNumberProps) { const [prevCount, setPrevCount] = React.useState(count); // ============================= Events ============================= - const onTransitionEnd = () => { + const onTransitionEnd: React.TransitionEventHandler<HTMLSpanElement> = () => { setPrevValue(value); setPrevCount(count); }; // Fallback if transition events are not supported React.useEffect(() => { - const timeout = setTimeout(() => { - onTransitionEnd(); - }, 1000); - - return () => { - clearTimeout(timeout); - }; + const timer = setTimeout(onTransitionEnd, 1000); + return () => clearTimeout(timer); }, [value]); // ============================= Render ============================= @@ -121,4 +106,6 @@ export default function SingleNumber(props: SingleNumberProps) { {unitNodes} </span> ); -} +}; + +export default SingleNumber; diff --git a/components/badge/__tests__/demo.test.tsx b/components/badge/__tests__/demo.test.tsx index 18e41f6256fb..7177e192bdca 100644 --- a/components/badge/__tests__/demo.test.tsx +++ b/components/badge/__tests__/demo.test.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; + import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest'; demoTest('badge'); diff --git a/components/badge/__tests__/index.test.tsx b/components/badge/__tests__/index.test.tsx index 9a23c6b35110..cffee5fcec00 100644 --- a/components/badge/__tests__/index.test.tsx +++ b/components/badge/__tests__/index.test.tsx @@ -1,8 +1,9 @@ -import { fireEvent, render } from '@testing-library/react'; import React from 'react'; -import { act } from 'react-dom/test-utils'; + +import type { GetRef } from '../../_util/type'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; +import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils'; import Tooltip from '../../tooltip'; import Badge from '../index'; @@ -25,19 +26,31 @@ describe('Badge', () => { jest.useRealTimers(); }); - it('no strict warning', () => { + it('no strict warning', async () => { const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - const { rerender } = render( - <Badge dot> - <span /> - </Badge>, - ); - - rerender( - <Badge> - <span /> - </Badge>, - ); + const Comp = () => { + const [count, setCount] = React.useState<number | null>(9999); + + return ( + <> + <Badge count={count}> + <span>Badge</span> + </Badge> + + <br /> + <br /> + <br /> + + <button type="button" onClick={() => setCount(null)}> + click + </button> + </> + ); + }; + const { container } = render(<Comp />); + + fireEvent.click(container.querySelector('button')!); + await waitFakeTimer(); expect(errSpy).not.toHaveBeenCalled(); errSpy.mockRestore(); @@ -74,7 +87,7 @@ describe('Badge', () => { // https://github.com/ant-design/ant-design/issues/10626 it('should be composable with Tooltip', () => { - const ref = React.createRef<typeof Tooltip>(); + const ref = React.createRef<GetRef<typeof Tooltip>>(); const { container } = render( <Tooltip title="Fix the error" ref={ref}> <Badge status="error" /> diff --git a/components/badge/__tests__/ribbon.test.tsx b/components/badge/__tests__/ribbon.test.tsx index 9206bf43b870..042b3b438ab1 100644 --- a/components/badge/__tests__/ribbon.test.tsx +++ b/components/badge/__tests__/ribbon.test.tsx @@ -1,5 +1,6 @@ -import { render } from '@testing-library/react'; import React from 'react'; +import { render } from '@testing-library/react'; + import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; import Badge from '../index'; diff --git a/components/badge/demo/change.tsx b/components/badge/demo/change.tsx index aec8bbbf617b..2e1a9d2c08dd 100644 --- a/components/badge/demo/change.tsx +++ b/components/badge/demo/change.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { MinusOutlined, PlusOutlined, QuestionOutlined } from '@ant-design/icons'; -import { Avatar, Badge, Button, Switch, Space } from 'antd'; +import { Avatar, Badge, Button, Space, Switch } from 'antd'; const ButtonGroup = Button.Group; diff --git a/components/badge/demo/component-token.tsx b/components/badge/demo/component-token.tsx index f9f32ad0370e..dda8817d1708 100644 --- a/components/badge/demo/component-token.tsx +++ b/components/badge/demo/component-token.tsx @@ -1,5 +1,5 @@ -import { NotificationOutlined } from '@ant-design/icons'; import React from 'react'; +import { NotificationOutlined } from '@ant-design/icons'; import { Avatar, Badge, ConfigProvider, Space } from 'antd'; /** Test usage. Do not use in your production. */ diff --git a/components/badge/demo/no-wrapper.tsx b/components/badge/demo/no-wrapper.tsx index 259596a61c3c..56c14dc7c04f 100644 --- a/components/badge/demo/no-wrapper.tsx +++ b/components/badge/demo/no-wrapper.tsx @@ -8,7 +8,7 @@ const App: React.FC = () => { return ( <Space> <Switch checked={show} onChange={() => setShow(!show)} /> - <Badge count={show ? 11 : 0} showZero color='#faad14' /> + <Badge count={show ? 11 : 0} showZero color="#faad14" /> <Badge count={show ? 25 : 0} /> <Badge count={show ? <ClockCircleOutlined style={{ color: '#f5222d' }} /> : 0} /> <Badge diff --git a/components/badge/index.en-US.md b/components/badge/index.en-US.md index 1635d8614c54..325d290187c3 100644 --- a/components/badge/index.en-US.md +++ b/components/badge/index.en-US.md @@ -1,6 +1,7 @@ --- category: Components title: Badge +description: Small numerical value or status descriptor for UI elements. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*e0qITYqF394AAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*v8EQT7KoGbcAAAAAAAAAAAAADrJ8AQ/original demo: @@ -8,8 +9,6 @@ demo: group: Data Display --- -Small numerical value or status descriptor for UI elements. - ## When To Use Badge normally appears in proximity to notifications or user avatars with eye-catching appeal, typically displaying unread messages count. diff --git a/components/badge/index.tsx b/components/badge/index.tsx index 375e5fd7db75..fd3c9eb21592 100644 --- a/components/badge/index.tsx +++ b/components/badge/index.tsx @@ -15,12 +15,6 @@ import useStyle from './style'; export type { ScrollNumberProps } from './ScrollNumber'; -type CompoundedComponent = React.ForwardRefExoticComponent< - BadgeProps & React.RefAttributes<HTMLSpanElement> -> & { - Ribbon: typeof Ribbon; -}; - export interface BadgeProps { /** Number to show in badge */ count?: React.ReactNode; @@ -51,7 +45,7 @@ export interface BadgeProps { }; } -const InternalBadge: React.ForwardRefRenderFunction<HTMLSpanElement, BadgeProps> = (props, ref) => { +const InternalBadge = React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref) => { const { prefixCls: customizePrefixCls, scrollNumberPrefixCls: customizeScrollNumberPrefixCls, @@ -222,7 +216,7 @@ const InternalBadge: React.ForwardRefRenderFunction<HTMLSpanElement, BadgeProps> motionAppear={false} motionDeadline={1000} > - {({ className: motionClassName, ref: scrollNumberRef }) => { + {({ className: motionClassName }) => { const scrollNumberPrefixCls = getPrefixCls( 'scroll-number', customizeScrollNumberPrefixCls, @@ -261,7 +255,6 @@ const InternalBadge: React.ForwardRefRenderFunction<HTMLSpanElement, BadgeProps> title={titleNode} style={scrollNumberStyle} key="scrollNumber" - ref={scrollNumberRef} > {displayNode} </ScrollNumber> @@ -271,9 +264,13 @@ const InternalBadge: React.ForwardRefRenderFunction<HTMLSpanElement, BadgeProps> {statusTextNode} </span>, ); +}); + +type CompoundedComponent = typeof InternalBadge & { + Ribbon: typeof Ribbon; }; -const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(InternalBadge) as CompoundedComponent; +const Badge = InternalBadge as CompoundedComponent; Badge.Ribbon = Ribbon; diff --git a/components/badge/index.zh-CN.md b/components/badge/index.zh-CN.md index cc774abf8526..b53b26acc228 100644 --- a/components/badge/index.zh-CN.md +++ b/components/badge/index.zh-CN.md @@ -1,7 +1,8 @@ --- category: Components -subtitle: 徽标数 title: Badge +subtitle: 徽标数 +description: 图标右上角的圆形徽标数字。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*e0qITYqF394AAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*v8EQT7KoGbcAAAAAAAAAAAAADrJ8AQ/original demo: @@ -9,8 +10,6 @@ demo: group: 数据展示 --- -图标右上角的圆形徽标数字。 - ## 何时使用 一般出现在通知图标或头像的右上角,用于显示需要处理的消息条数,通过醒目视觉形式吸引用户处理。 diff --git a/components/badge/style/index.ts b/components/badge/style/index.ts index f35313711e16..10cd60e1c9b4 100644 --- a/components/badge/style/index.ts +++ b/components/badge/style/index.ts @@ -1,9 +1,8 @@ import { Keyframes, unit } from '@ant-design/cssinjs'; import { resetComponent } from '../../style'; -import type { FullToken, GenerateStyle } from '../../theme/internal'; +import type { FullToken, GenerateStyle, GenStyleFn, GetDefaultToken } from '../../theme/internal'; import { genPresetColor, genStyleHooks, mergeToken } from '../../theme/internal'; -import type { GenStyleFn, GetDefaultToken } from '../../theme/util/genComponentStyleHook'; /** Component only token. Which will handle additional calculation of alias token */ export interface ComponentToken { @@ -17,12 +16,12 @@ export interface ComponentToken { * @desc 徽标高度 * @descEN Height of badge */ - indicatorHeight: number; + indicatorHeight: number | string; /** * @desc 小号徽标高度 * @descEN Height of small badge */ - indicatorHeightSM: number; + indicatorHeightSM: number | string; /** * @desc 点状徽标尺寸 * @descEN Size of dot badge @@ -50,16 +49,60 @@ export interface ComponentToken { statusSize: number; } +/** + * @desc Badge 组件的 Token + * @descEN Token for Badge component + */ export interface BadgeToken extends FullToken<'Badge'> { + /** + * @desc 徽标字体高度 + * @descEN Font height of badge + */ badgeFontHeight: number; + /** + * @desc 徽标文本颜色 + * @descEN Text color of badge + */ badgeTextColor: string; + /** + * @desc 徽标颜色 + * @descEN Color of badge + */ badgeColor: string; + /** + * @desc 徽标悬停颜色 + * @descEN Hover color of badge + */ badgeColorHover: string; + /** + * @desc 徽标阴影尺寸 + * @descEN Shadow size of badge + */ badgeShadowSize: number; + /** + * @desc 徽标阴影颜色 + * @descEN Shadow color of badge + */ badgeShadowColor: string; + /** + * @desc 徽标处理持续时间 + * @descEN Processing duration of badge + */ badgeProcessingDuration: string; + /** + * @desc 徽标丝带偏移量 + * @descEN Ribbon offset of badge + */ badgeRibbonOffset: number; + /** + * @desc 徽标丝带角变换 + * @descEN Ribbon corner transform of badge + */ badgeRibbonCornerTransform: string; + /** + * @desc 徽标丝带角滤镜 + * @descEN Ribbon corner filter of badge + */ badgeRibbonCornerFilter: string; } @@ -102,7 +145,6 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token) => { iconCls, antCls, badgeShadowSize, - motionDurationSlow, textFontSize, textFontSizeSM, statusSize, @@ -121,6 +163,9 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token) => { [`&:not(${componentCls}-count)`]: { color: darkColor, }, + 'a:hover &': { + background: darkColor, + }, }, })); @@ -133,6 +178,8 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token) => { lineHeight: 1, [`${componentCls}-count`]: { + display: 'inline-flex', + justifyContent: 'center', zIndex: token.indicatorZIndex, minWidth: indicatorHeight, height: indicatorHeight, @@ -183,9 +230,6 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token) => { borderRadius: '100%', boxShadow: `0 0 0 ${unit(badgeShadowSize)} ${token.badgeShadowColor}`, }, - [`${componentCls}-dot${numberPrefixCls}`]: { - transition: `background ${motionDurationSlow}`, - }, [`${componentCls}-count, ${componentCls}-dot, ${numberPrefixCls}-custom-component`]: { position: 'absolute', top: 0, @@ -218,8 +262,9 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token) => { }, [`${componentCls}-status-processing`]: { overflow: 'visible', - color: token.colorPrimary, - backgroundColor: token.colorPrimary, + color: token.colorInfo, + backgroundColor: token.colorInfo, + borderColor: 'currentcolor', '&::after': { position: 'absolute', @@ -293,8 +338,9 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token) => { transformOrigin: '50% 50%', }, }, - [`${numberPrefixCls}`]: { + [numberPrefixCls]: { overflow: 'hidden', + transition: `all ${token.motionDurationMid} ${token.motionEaseOutBack}`, [`${numberPrefixCls}-only`]: { position: 'relative', display: 'inline-block', diff --git a/components/badge/style/ribbon.ts b/components/badge/style/ribbon.ts index cc5f65d2479a..7675066a1dfa 100644 --- a/components/badge/style/ribbon.ts +++ b/components/badge/style/ribbon.ts @@ -1,6 +1,7 @@ import { unit } from '@ant-design/cssinjs'; -import { type BadgeToken, prepareComponentToken, prepareToken } from '.'; +import { prepareComponentToken, prepareToken } from '.'; +import type { BadgeToken } from '.'; import { resetComponent } from '../../style'; import type { GenerateStyle } from '../../theme/internal'; import { genPresetColor, genStyleHooks } from '../../theme/internal'; @@ -19,10 +20,10 @@ const genRibbonStyle: GenerateStyle<BadgeToken> = (token) => { })); return { - [`${ribbonWrapperPrefixCls}`]: { + [ribbonWrapperPrefixCls]: { position: 'relative', }, - [`${ribbonPrefixCls}`]: { + [ribbonPrefixCls]: { ...resetComponent(token), position: 'absolute', top: marginXS, diff --git a/components/breadcrumb/BreadcrumbItem.tsx b/components/breadcrumb/BreadcrumbItem.tsx index 80386901c72b..28f7e95cb483 100644 --- a/components/breadcrumb/BreadcrumbItem.tsx +++ b/components/breadcrumb/BreadcrumbItem.tsx @@ -1,12 +1,13 @@ -import DownOutlined from '@ant-design/icons/DownOutlined'; import * as React from 'react'; +import DownOutlined from '@ant-design/icons/DownOutlined'; + +import { devUseWarning } from '../_util/warning'; import { ConfigContext } from '../config-provider'; import type { DropdownProps } from '../dropdown/dropdown'; import Dropdown from '../dropdown/dropdown'; import type { ItemType } from './Breadcrumb'; import BreadcrumbSeparator from './BreadcrumbSeparator'; import { renderItem } from './useItemRender'; -import { devUseWarning } from '../_util/warning'; export interface SeparatorType { separator?: React.ReactNode; @@ -68,7 +69,7 @@ export const InternalBreadcrumbItem: React.FC<BreadcrumbItemProps> = (props) => return { ...itemProps, key: key ?? index, - label: mergedLabel as string, + label: mergedLabel, }; }), }; diff --git a/components/breadcrumb/BreadcrumbSeparator.tsx b/components/breadcrumb/BreadcrumbSeparator.tsx index 380eb5cbbaa8..35fafbd49e39 100644 --- a/components/breadcrumb/BreadcrumbSeparator.tsx +++ b/components/breadcrumb/BreadcrumbSeparator.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; + import { ConfigContext } from '../config-provider'; -type CompoundedComponent = React.FC<{ children?: React.ReactNode }> & { +type CompoundedComponent = React.FC<React.PropsWithChildren> & { /** @internal */ __ANT_BREADCRUMB_SEPARATOR: boolean; }; @@ -9,7 +10,6 @@ type CompoundedComponent = React.FC<{ children?: React.ReactNode }> & { const BreadcrumbSeparator: CompoundedComponent = ({ children }) => { const { getPrefixCls } = React.useContext(ConfigContext); const prefixCls = getPrefixCls('breadcrumb'); - return ( <li className={`${prefixCls}-separator`} aria-hidden="true"> {children === '' ? children : children || '/'} diff --git a/components/breadcrumb/__tests__/Breadcrumb.test.tsx b/components/breadcrumb/__tests__/Breadcrumb.test.tsx index ccebb9152148..22f8bd12595e 100644 --- a/components/breadcrumb/__tests__/Breadcrumb.test.tsx +++ b/components/breadcrumb/__tests__/Breadcrumb.test.tsx @@ -1,9 +1,10 @@ import React from 'react'; + +import { resetWarned } from '../../_util/warning'; import accessibilityTest from '../../../tests/shared/accessibilityTest'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; import { render } from '../../../tests/utils'; -import { resetWarned } from '../../_util/warning'; import type { ItemType } from '../Breadcrumb'; import Breadcrumb from '../index'; diff --git a/components/breadcrumb/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/breadcrumb/__tests__/__snapshots__/demo-extend.test.ts.snap index 9654605bbb2b..3e68bb4869d5 100644 --- a/components/breadcrumb/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/breadcrumb/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -252,59 +252,7 @@ exports[`renders components/breadcrumb/demo/component-token.tsx extend context c <div aria-hidden="true" style="display: none;" - > - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - </div> + /> </div> </li> <li @@ -523,42 +471,7 @@ exports[`renders components/breadcrumb/demo/debug-routes.tsx extend context corr <div aria-hidden="true" style="display: none;" - > - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - </div> + /> </div> </li> </ol> @@ -758,59 +671,7 @@ exports[`renders components/breadcrumb/demo/overlay.tsx extend context correctly <div aria-hidden="true" style="display: none;" - > - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - </div> + /> </div> </li> <li @@ -1034,3 +895,35 @@ exports[`renders components/breadcrumb/demo/withIcon.tsx extend context correctl `; exports[`renders components/breadcrumb/demo/withIcon.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/breadcrumb/demo/withParams.tsx extend context correctly 1`] = ` +<nav + class="ant-breadcrumb" +> + <ol> + <li> + <span + class="ant-breadcrumb-link" + > + Users + </span> + </li> + <li + aria-hidden="true" + class="ant-breadcrumb-separator" + > + / + </li> + <li> + <a + class="ant-breadcrumb-link" + href="" + > + 1 + </a> + </li> + </ol> +</nav> +`; + +exports[`renders components/breadcrumb/demo/withParams.tsx extend context correctly 2`] = `[]`; diff --git a/components/breadcrumb/__tests__/__snapshots__/demo.test.ts.snap b/components/breadcrumb/__tests__/__snapshots__/demo.test.ts.snap index 93121935649a..836981ae0d4e 100644 --- a/components/breadcrumb/__tests__/__snapshots__/demo.test.ts.snap +++ b/components/breadcrumb/__tests__/__snapshots__/demo.test.ts.snap @@ -553,3 +553,33 @@ exports[`renders components/breadcrumb/demo/withIcon.tsx correctly 1`] = ` </ol> </nav> `; + +exports[`renders components/breadcrumb/demo/withParams.tsx correctly 1`] = ` +<nav + class="ant-breadcrumb" +> + <ol> + <li> + <span + class="ant-breadcrumb-link" + > + Users + </span> + </li> + <li + aria-hidden="true" + class="ant-breadcrumb-separator" + > + / + </li> + <li> + <a + class="ant-breadcrumb-link" + href="" + > + 1 + </a> + </li> + </ol> +</nav> +`; diff --git a/components/breadcrumb/__tests__/itemRender.test.tsx b/components/breadcrumb/__tests__/itemRender.test.tsx index d476dede1960..d9cc89120c4f 100644 --- a/components/breadcrumb/__tests__/itemRender.test.tsx +++ b/components/breadcrumb/__tests__/itemRender.test.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { render } from '../../../tests/utils'; import Breadcrumb from '../index'; diff --git a/components/breadcrumb/__tests__/router.test.tsx b/components/breadcrumb/__tests__/router.test.tsx index 87d22b994ed8..caa48d9b6188 100644 --- a/components/breadcrumb/__tests__/router.test.tsx +++ b/components/breadcrumb/__tests__/router.test.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; import type { RouterProps } from 'react-router-dom'; import { Link, MemoryRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; + import { fireEvent, render } from '../../../tests/utils'; import Breadcrumb from '../index'; @@ -51,11 +52,11 @@ describe('react router', () => { </Breadcrumb.Item>, ].concat(extraBreadcrumbItems); const componentProps = useMemo<RouterProps>( - () => ({ component: Apps } as unknown as RouterProps), + () => ({ component: Apps }) as unknown as RouterProps, [], ); const renderProps = useMemo<RouterProps>( - () => ({ render: () => <span>Home Page</span> } as unknown as RouterProps), + () => ({ render: () => <span>Home Page</span> }) as unknown as RouterProps, [], ); return ( diff --git a/components/breadcrumb/demo/component-token.tsx b/components/breadcrumb/demo/component-token.tsx index b3d2f28e10ef..ac9e1f99947e 100644 --- a/components/breadcrumb/demo/component-token.tsx +++ b/components/breadcrumb/demo/component-token.tsx @@ -1,5 +1,5 @@ -import { HomeOutlined, UserOutlined } from '@ant-design/icons'; import React from 'react'; +import { HomeOutlined, UserOutlined } from '@ant-design/icons'; import { Breadcrumb, ConfigProvider } from 'antd'; const menuItems = [ diff --git a/components/breadcrumb/demo/withIcon.tsx b/components/breadcrumb/demo/withIcon.tsx index bde49477fe32..9e1ff86ddce5 100644 --- a/components/breadcrumb/demo/withIcon.tsx +++ b/components/breadcrumb/demo/withIcon.tsx @@ -1,5 +1,5 @@ -import { HomeOutlined, UserOutlined } from '@ant-design/icons'; import React from 'react'; +import { HomeOutlined, UserOutlined } from '@ant-design/icons'; import { Breadcrumb } from 'antd'; const App: React.FC = () => ( diff --git a/components/breadcrumb/demo/withParams.md b/components/breadcrumb/demo/withParams.md new file mode 100644 index 000000000000..5837effadff3 --- /dev/null +++ b/components/breadcrumb/demo/withParams.md @@ -0,0 +1,7 @@ +## zh-CN + +带有路由参数的。 + +## en-US + +With route params. diff --git a/components/breadcrumb/demo/withParams.tsx b/components/breadcrumb/demo/withParams.tsx new file mode 100644 index 000000000000..ee9bed4a3ebc --- /dev/null +++ b/components/breadcrumb/demo/withParams.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Breadcrumb } from 'antd'; + +const App: React.FC = () => ( + <Breadcrumb + items={[ + { + title: 'Users', + }, + { + title: ':id', + href: '', + }, + ]} + params={{ id: 1 }} + /> +); + +export default App; diff --git a/components/breadcrumb/design/behavior-pattern.tsx b/components/breadcrumb/design/behavior-pattern.tsx index da5ea9f5e9e9..e6447fa51b08 100644 --- a/components/breadcrumb/design/behavior-pattern.tsx +++ b/components/breadcrumb/design/behavior-pattern.tsx @@ -16,12 +16,12 @@ const BehaviorPattern: React.FC = () => ( { id: '707000085', label: '了解当前页面的位置', - link: 'components-breadcrumb-index-tab-design-demo-basic', + link: 'breadcrumb-index-tab-design-demo-basic', }, { id: '707000086', label: '了解系统层级结构', - link: 'components-breadcrumb-index-tab-design-demo-basic', + link: 'breadcrumb-index-tab-design-demo-basic', }, ], }, @@ -29,13 +29,13 @@ const BehaviorPattern: React.FC = () => ( id: '200000005', label: '向上导航', targetType: 'mvp', - link: 'components-breadcrumb-index-tab-design-demo-basic', + link: 'breadcrumb-index-tab-design-demo-basic', }, { id: '200000006', label: '快捷导航', targetType: 'extension', - link: 'components-breadcrumb-index-tab-design-demo-overlay', + link: 'breadcrumb-index-tab-design-demo-overlay', }, ], }} diff --git a/components/breadcrumb/index.en-US.md b/components/breadcrumb/index.en-US.md index 1885dd61069d..1d0b026480fe 100644 --- a/components/breadcrumb/index.en-US.md +++ b/components/breadcrumb/index.en-US.md @@ -2,14 +2,13 @@ category: Components group: Navigation title: Breadcrumb +description: Display the current location within a hierarchy. And allow going back to states higher up in the hierarchy. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*I5a2Tpqs3y0AAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*Tr90QKrE_LcAAAAAAAAAAAAADrJ8AQ/original demo: cols: 2 --- -A breadcrumb displays the current location within a hierarchy. It allows going back to states higher up in the hierarchy. - ## When To Use - When the system has more than two layers in a hierarchy. @@ -37,6 +36,7 @@ return <Breadcrumb routes={[{ breadcrumbName: 'sample' }]} />; <!-- prettier-ignore --> <code src="./demo/basic.tsx">Basic Usage</code> <code src="./demo/withIcon.tsx">With an Icon</code> +<code src="./demo/withParams.tsx">With Params</code> <code src="./demo/separator.tsx">Configuring the Separator</code> <code src="./demo/overlay.tsx">Bread crumbs with drop down menu</code> <code src="./demo/separator-component.tsx">Configuring the Separator Independently</code> @@ -53,12 +53,12 @@ Common props ref:[Common props](/docs/react/common-props) | --- | --- | --- | --- | --- | | itemRender | Custom item renderer | (route, params, routes, paths) => ReactNode | - | | | params | Routing parameters | object | - | | -| items | The routing stack information of router | [ItemType\[\]](#ItemType) | - | 5.3.0 | +| items | The routing stack information of router | [ItemType\[\]](#itemtype) | - | 5.3.0 | | separator | Custom separator | ReactNode | `/` | | ### ItemType -> type ItemType = Omit<[RouteItemType](#RouteItemType), 'title' | 'path'> | [SeparatorType](#SeparatorType) +> type ItemType = Omit<[RouteItemType](#routeitemtype), 'title' | 'path'> | [SeparatorType](#separatortype) ### RouteItemType diff --git a/components/breadcrumb/index.ts b/components/breadcrumb/index.tsx similarity index 100% rename from components/breadcrumb/index.ts rename to components/breadcrumb/index.tsx diff --git a/components/breadcrumb/index.zh-CN.md b/components/breadcrumb/index.zh-CN.md index 8c141ccccef7..5bb44942b0db 100644 --- a/components/breadcrumb/index.zh-CN.md +++ b/components/breadcrumb/index.zh-CN.md @@ -1,16 +1,15 @@ --- category: Components -subtitle: 面包屑 group: 导航 title: Breadcrumb +subtitle: 面包屑 +description: 显示当前页面在系统层级结构中的位置,并能向上返回。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*I5a2Tpqs3y0AAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*Tr90QKrE_LcAAAAAAAAAAAAADrJ8AQ/original demo: cols: 2 --- -显示当前页面在系统层级结构中的位置,并能向上返回。 - ## 何时使用 - 当系统拥有超过两级以上的层级结构时; @@ -38,6 +37,7 @@ return <Breadcrumb routes={[{ breadcrumbName: 'sample' }]} />; <!-- prettier-ignore --> <code src="./demo/basic.tsx">基本</code> <code src="./demo/withIcon.tsx">带有图标的</code> +<code src="./demo/withParams.tsx">带有参数的</code> <code src="./demo/separator.tsx">分隔符</code> <code src="./demo/overlay.tsx">带下拉菜单的面包屑</code> <code src="./demo/separator-component.tsx">独立的分隔符</code> @@ -54,12 +54,12 @@ return <Breadcrumb routes={[{ breadcrumbName: 'sample' }]} />; | --- | --- | --- | --- | --- | | itemRender | 自定义链接函数,和 react-router 配置使用 | (route, params, routes, paths) => ReactNode | - | | | params | 路由的参数 | object | - | | -| items | 路由栈信息 | [items\[\]](#ItemType) | - | 5.3.0 | +| items | 路由栈信息 | [items\[\]](#itemtype) | - | 5.3.0 | | separator | 分隔符自定义 | ReactNode | `/` | | ### ItemType -> type ItemType = Omit<[RouteItemType](#RouteItemType), 'title' | 'path'> | [SeparatorType](#SeparatorType) +> type ItemType = Omit<[RouteItemType](#routeitemtype), 'title' | 'path'> | [SeparatorType](#separatortype) ### RouteItemType diff --git a/components/breadcrumb/style/index.ts b/components/breadcrumb/style/index.ts index 8c2e7f178ca1..80707c0ac0b4 100644 --- a/components/breadcrumb/style/index.ts +++ b/components/breadcrumb/style/index.ts @@ -1,4 +1,5 @@ -import { type CSSObject, unit } from '@ant-design/cssinjs'; +import { unit } from '@ant-design/cssinjs'; +import type { CSSObject } from '@ant-design/cssinjs'; import { genFocusStyle, resetComponent } from '../../style'; import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal'; @@ -82,7 +83,7 @@ const genBreadcrumbStyle: GenerateStyle<BreadcrumbToken, CSSObject> = (token) => ...genFocusStyle(token), }, - [`li:last-child`]: { + 'li:last-child': { color: token.lastItemColor, }, diff --git a/components/breadcrumb/useItemRender.tsx b/components/breadcrumb/useItemRender.tsx index 0a43958c9b26..e3b0a9f810d7 100644 --- a/components/breadcrumb/useItemRender.tsx +++ b/components/breadcrumb/useItemRender.tsx @@ -1,6 +1,7 @@ +import * as React from 'react'; import classNames from 'classnames'; import pickAttrs from 'rc-util/lib/pickAttrs'; -import * as React from 'react'; + import type { BreadcrumbProps, InternalRouteType, ItemType } from './Breadcrumb'; type AddParameters<TFunction extends (...args: any) => any, TParameters extends [...args: any]> = ( diff --git a/components/breadcrumb/useItems.ts b/components/breadcrumb/useItems.ts index ac0e74abfe29..e7e71ccbc6bc 100644 --- a/components/breadcrumb/useItems.ts +++ b/components/breadcrumb/useItems.ts @@ -1,4 +1,5 @@ import { useMemo } from 'react'; + import type { BreadcrumbItemType, BreadcrumbSeparatorType, ItemType } from './Breadcrumb'; type MergedType = BreadcrumbItemType & { diff --git a/components/button/LoadingIcon.tsx b/components/button/LoadingIcon.tsx index 74918bb19c6e..5497f2abbcf4 100644 --- a/components/button/LoadingIcon.tsx +++ b/components/button/LoadingIcon.tsx @@ -1,7 +1,8 @@ +import React, { forwardRef } from 'react'; import LoadingOutlined from '@ant-design/icons/LoadingOutlined'; import classNames from 'classnames'; import CSSMotion from 'rc-motion'; -import React, { forwardRef } from 'react'; + import IconWrapper from './IconWrapper'; type InnerLoadingIconProps = { @@ -11,25 +12,24 @@ type InnerLoadingIconProps = { iconClassName?: string; }; -const InnerLoadingIcon = forwardRef<HTMLSpanElement, InnerLoadingIconProps>( - ({ prefixCls, className, style, iconClassName }, ref) => { - const mergedIconCls = classNames(`${prefixCls}-loading-icon`, className); +const InnerLoadingIcon = forwardRef<HTMLSpanElement, InnerLoadingIconProps>((props, ref) => { + const { prefixCls, className, style, iconClassName } = props; + const mergedIconCls = classNames(`${prefixCls}-loading-icon`, className); - return ( - <IconWrapper prefixCls={prefixCls} className={mergedIconCls} style={style} ref={ref}> - <LoadingOutlined className={iconClassName} /> - </IconWrapper> - ); - }, -); + return ( + <IconWrapper prefixCls={prefixCls} className={mergedIconCls} style={style} ref={ref}> + <LoadingOutlined className={iconClassName} /> + </IconWrapper> + ); +}); -export interface LoadingIconProps { +export type LoadingIconProps = { prefixCls: string; existIcon: boolean; loading?: boolean | object; className?: string; style?: React.CSSProperties; -} +}; const getCollapsedWidth = (): React.CSSProperties => ({ width: 0, diff --git a/components/button/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/button/__tests__/__snapshots__/demo-extend.test.ts.snap index 0c49239da227..27f2614188ee 100644 --- a/components/button/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/button/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -1520,6 +1520,467 @@ exports[`renders components/button/demo/icon.tsx extend context correctly 1`] = exports[`renders components/button/demo/icon.tsx extend context correctly 2`] = `[]`; +exports[`renders components/button/demo/icon-position.tsx extend context correctly 1`] = ` +Array [ + <div + class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small" + > + <div + class="ant-space-item" + > + <div + class="ant-radio-group ant-radio-group-outline" + > + <label + class="ant-radio-button-wrapper" + > + <span + class="ant-radio-button" + > + <input + class="ant-radio-button-input" + type="radio" + value="start" + /> + <span + class="ant-radio-button-inner" + /> + </span> + <span> + start + </span> + </label> + <label + class="ant-radio-button-wrapper ant-radio-button-wrapper-checked" + > + <span + class="ant-radio-button ant-radio-button-checked" + > + <input + checked="" + class="ant-radio-button-input" + type="radio" + value="end" + /> + <span + class="ant-radio-button-inner" + /> + </span> + <span> + end + </span> + </label> + </div> + </div> + </div>, + <div + class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left ant-divider-plain" + role="separator" + > + <span + class="ant-divider-inner-text" + > + Preview + </span> + </div>, + <div + class="ant-flex ant-flex-align-stretch ant-flex-gap-small ant-flex-vertical" + > + <div + class="ant-flex ant-flex-wrap-wrap ant-flex-gap-small" + > + <button + class="ant-btn ant-btn-circle ant-btn-primary ant-btn-icon-only" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + </button> + <div + class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-placement-top" + style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" + > + <div + class="ant-tooltip-arrow" + style="position: absolute; bottom: 0px; left: 0px;" + /> + <div + class="ant-tooltip-content" + > + <div + class="ant-tooltip-inner" + role="tooltip" + > + search + </div> + </div> + </div> + <button + class="ant-btn ant-btn-circle ant-btn-primary" + type="button" + > + <span> + A + </span> + </button> + <button + class="ant-btn ant-btn-primary ant-btn-icon-end" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + <span> + Search + </span> + </button> + <button + class="ant-btn ant-btn-circle ant-btn-default ant-btn-icon-only" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + </button> + <div + class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-placement-top" + style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" + > + <div + class="ant-tooltip-arrow" + style="position: absolute; bottom: 0px; left: 0px;" + /> + <div + class="ant-tooltip-content" + > + <div + class="ant-tooltip-inner" + role="tooltip" + > + search + </div> + </div> + </div> + <button + class="ant-btn ant-btn-default ant-btn-icon-end" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + <span> + Search + </span> + </button> + </div> + <div + class="ant-flex ant-flex-wrap-wrap ant-flex-gap-small" + > + <button + class="ant-btn ant-btn-circle ant-btn-default ant-btn-icon-only" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + </button> + <div + class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-placement-top" + style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" + > + <div + class="ant-tooltip-arrow" + style="position: absolute; bottom: 0px; left: 0px;" + /> + <div + class="ant-tooltip-content" + > + <div + class="ant-tooltip-inner" + role="tooltip" + > + search + </div> + </div> + </div> + <button + class="ant-btn ant-btn-text ant-btn-icon-end" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + <span> + Search + </span> + </button> + <button + class="ant-btn ant-btn-circle ant-btn-dashed ant-btn-icon-only" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + </button> + <div + class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-placement-top" + style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" + > + <div + class="ant-tooltip-arrow" + style="position: absolute; bottom: 0px; left: 0px;" + /> + <div + class="ant-tooltip-content" + > + <div + class="ant-tooltip-inner" + role="tooltip" + > + search + </div> + </div> + </div> + <button + class="ant-btn ant-btn-dashed ant-btn-icon-end" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + <span> + Search + </span> + </button> + <a + class="ant-btn ant-btn-default ant-btn-icon-only ant-btn-icon-end" + href="https://www.google.com" + tabindex="0" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + </a> + <button + class="ant-btn ant-btn-primary ant-btn-loading ant-btn-icon-end" + type="button" + > + <span + class="ant-btn-icon ant-btn-loading-icon" + style="width: 0px; opacity: 0; transform: scale(0);" + > + <span + aria-label="loading" + class="anticon anticon-loading anticon-spin ant-btn-loading-icon-motion-appear ant-btn-loading-icon-motion-appear-start ant-btn-loading-icon-motion" + role="img" + > + <svg + aria-hidden="true" + data-icon="loading" + fill="currentColor" + focusable="false" + height="1em" + viewBox="0 0 1024 1024" + width="1em" + > + <path + d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z" + /> + </svg> + </span> + </span> + <span> + Loading + </span> + </button> + </div> + </div>, +] +`; + +exports[`renders components/button/demo/icon-position.tsx extend context correctly 2`] = `[]`; + exports[`renders components/button/demo/legacy-group.tsx extend context correctly 1`] = ` Array [ <div @@ -1882,11 +2343,34 @@ exports[`renders components/button/demo/linear-gradient.tsx extend context corre class="ant-space-item" > <button - class="ant-btn ant-btn-primary" + class="ant-btn ant-btn-primary ant-btn-lg acss-9mi5l3" type="button" > + <span + class="ant-btn-icon" + > + <span + aria-label="ant-design" + class="anticon anticon-ant-design" + role="img" + > + <svg + aria-hidden="true" + data-icon="ant-design" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M716.3 313.8c19-18.9 19-49.7 0-68.6l-69.9-69.9.1.1c-18.5-18.5-50.3-50.3-95.3-95.2-21.2-20.7-55.5-20.5-76.5.5L80.9 474.2a53.84 53.84 0 000 76.4L474.6 944a54.14 54.14 0 0076.5 0l165.1-165c19-18.9 19-49.7 0-68.6a48.7 48.7 0 00-68.7 0l-125 125.2c-5.2 5.2-13.3 5.2-18.5 0L189.5 521.4c-5.2-5.2-5.2-13.3 0-18.5l314.4-314.2c.4-.4.9-.7 1.3-1.1 5.2-4.1 12.4-3.7 17.2 1.1l125.2 125.1c19 19 49.8 19 68.7 0zM408.6 514.4a106.3 106.2 0 10212.6 0 106.3 106.2 0 10-212.6 0zm536.2-38.6L821.9 353.5c-19-18.9-49.8-18.9-68.7.1a48.4 48.4 0 000 68.6l83 82.9c5.2 5.2 5.2 13.3 0 18.5l-81.8 81.7a48.4 48.4 0 000 68.6 48.7 48.7 0 0068.7 0l121.8-121.7a53.93 53.93 0 00-.1-76.4z" + /> + </svg> + </span> + </span> <span> - Primary Button + Gradient Button </span> </button> </div> @@ -1894,11 +2378,11 @@ exports[`renders components/button/demo/linear-gradient.tsx extend context corre class="ant-space-item" > <button - class="ant-btn ant-btn-default" + class="ant-btn ant-btn-default ant-btn-lg acss-9mi5l3" type="button" > <span> - Default Button + Button </span> </button> </div> @@ -2243,59 +2727,7 @@ exports[`renders components/button/demo/multiple.tsx extend context correctly 1` <div aria-hidden="true" style="display: none;" - > - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - <div - class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-dropdown-menu-inline-collapsed-tooltip ant-tooltip-placement-right" - style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;" - > - <div - class="ant-tooltip-arrow" - style="position: absolute; top: 0px; left: 0px;" - /> - <div - class="ant-tooltip-content" - > - <div - class="ant-tooltip-inner" - role="tooltip" - /> - </div> - </div> - </div> + /> </div> </div> </div> @@ -2303,6 +2735,31 @@ exports[`renders components/button/demo/multiple.tsx extend context correctly 1` exports[`renders components/button/demo/multiple.tsx extend context correctly 2`] = `[]`; +exports[`renders components/button/demo/noSpace.tsx extend context correctly 1`] = ` +<div + class="ant-flex ant-flex-wrap-wrap ant-flex-gap-middle" +> + <button + class="ant-btn ant-btn-primary" + type="button" + > + <span> + 确定 + </span> + </button> + <button + class="ant-btn ant-btn-primary" + type="button" + > + <span> + 确 定 + </span> + </button> +</div> +`; + +exports[`renders components/button/demo/noSpace.tsx extend context correctly 2`] = `[]`; + exports[`renders components/button/demo/size.tsx extend context correctly 1`] = ` Array [ <div diff --git a/components/button/__tests__/__snapshots__/demo.test.ts.snap b/components/button/__tests__/__snapshots__/demo.test.ts.snap index 8b2b0dbadb0f..60447047df60 100644 --- a/components/button/__tests__/__snapshots__/demo.test.ts.snap +++ b/components/button/__tests__/__snapshots__/demo.test.ts.snap @@ -1345,6 +1345,388 @@ exports[`renders components/button/demo/icon.tsx correctly 1`] = ` </div> `; +exports[`renders components/button/demo/icon-position.tsx correctly 1`] = ` +Array [ + <div + class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small" + > + <div + class="ant-space-item" + > + <div + class="ant-radio-group ant-radio-group-outline" + > + <label + class="ant-radio-button-wrapper" + > + <span + class="ant-radio-button" + > + <input + class="ant-radio-button-input" + type="radio" + value="start" + /> + <span + class="ant-radio-button-inner" + /> + </span> + <span> + start + </span> + </label> + <label + class="ant-radio-button-wrapper ant-radio-button-wrapper-checked" + > + <span + class="ant-radio-button ant-radio-button-checked" + > + <input + checked="" + class="ant-radio-button-input" + type="radio" + value="end" + /> + <span + class="ant-radio-button-inner" + /> + </span> + <span> + end + </span> + </label> + </div> + </div> + </div>, + <div + class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left ant-divider-plain" + role="separator" + > + <span + class="ant-divider-inner-text" + > + Preview + </span> + </div>, + <div + class="ant-flex ant-flex-align-stretch ant-flex-gap-small ant-flex-vertical" + > + <div + class="ant-flex ant-flex-wrap-wrap ant-flex-gap-small" + > + <button + class="ant-btn ant-btn-circle ant-btn-primary ant-btn-icon-only" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + </button> + <button + class="ant-btn ant-btn-circle ant-btn-primary" + type="button" + > + <span> + A + </span> + </button> + <button + class="ant-btn ant-btn-primary ant-btn-icon-end" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + <span> + Search + </span> + </button> + <button + class="ant-btn ant-btn-circle ant-btn-default ant-btn-icon-only" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + </button> + <button + class="ant-btn ant-btn-default ant-btn-icon-end" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + <span> + Search + </span> + </button> + </div> + <div + class="ant-flex ant-flex-wrap-wrap ant-flex-gap-small" + > + <button + class="ant-btn ant-btn-circle ant-btn-default ant-btn-icon-only" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + </button> + <button + class="ant-btn ant-btn-text ant-btn-icon-end" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + <span> + Search + </span> + </button> + <button + class="ant-btn ant-btn-circle ant-btn-dashed ant-btn-icon-only" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + </button> + <button + class="ant-btn ant-btn-dashed ant-btn-icon-end" + type="button" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + <span> + Search + </span> + </button> + <a + class="ant-btn ant-btn-default ant-btn-icon-only ant-btn-icon-end" + href="https://www.google.com" + tabindex="0" + > + <span + class="ant-btn-icon" + > + <span + aria-label="search" + class="anticon anticon-search" + role="img" + > + <svg + aria-hidden="true" + data-icon="search" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" + /> + </svg> + </span> + </span> + </a> + <button + class="ant-btn ant-btn-primary ant-btn-loading ant-btn-icon-end" + type="button" + > + <span + class="ant-btn-icon ant-btn-loading-icon" + > + <span + aria-label="loading" + class="anticon anticon-loading anticon-spin" + role="img" + > + <svg + aria-hidden="true" + data-icon="loading" + fill="currentColor" + focusable="false" + height="1em" + viewBox="0 0 1024 1024" + width="1em" + > + <path + d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z" + /> + </svg> + </span> + </span> + <span> + Loading + </span> + </button> + </div> + </div>, +] +`; + exports[`renders components/button/demo/legacy-group.tsx correctly 1`] = ` Array [ <div @@ -1591,11 +1973,34 @@ exports[`renders components/button/demo/linear-gradient.tsx correctly 1`] = ` class="ant-space-item" > <button - class="ant-btn ant-btn-primary" + class="ant-btn ant-btn-primary ant-btn-lg acss-9mi5l3" type="button" > + <span + class="ant-btn-icon" + > + <span + aria-label="ant-design" + class="anticon anticon-ant-design" + role="img" + > + <svg + aria-hidden="true" + data-icon="ant-design" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M716.3 313.8c19-18.9 19-49.7 0-68.6l-69.9-69.9.1.1c-18.5-18.5-50.3-50.3-95.3-95.2-21.2-20.7-55.5-20.5-76.5.5L80.9 474.2a53.84 53.84 0 000 76.4L474.6 944a54.14 54.14 0 0076.5 0l165.1-165c19-18.9 19-49.7 0-68.6a48.7 48.7 0 00-68.7 0l-125 125.2c-5.2 5.2-13.3 5.2-18.5 0L189.5 521.4c-5.2-5.2-5.2-13.3 0-18.5l314.4-314.2c.4-.4.9-.7 1.3-1.1 5.2-4.1 12.4-3.7 17.2 1.1l125.2 125.1c19 19 49.8 19 68.7 0zM408.6 514.4a106.3 106.2 0 10212.6 0 106.3 106.2 0 10-212.6 0zm536.2-38.6L821.9 353.5c-19-18.9-49.8-18.9-68.7.1a48.4 48.4 0 000 68.6l83 82.9c5.2 5.2 5.2 13.3 0 18.5l-81.8 81.7a48.4 48.4 0 000 68.6 48.7 48.7 0 0068.7 0l121.8-121.7a53.93 53.93 0 00-.1-76.4z" + /> + </svg> + </span> + </span> <span> - Primary Button + Gradient Button </span> </button> </div> @@ -1603,11 +2008,11 @@ exports[`renders components/button/demo/linear-gradient.tsx correctly 1`] = ` class="ant-space-item" > <button - class="ant-btn ant-btn-default" + class="ant-btn ant-btn-default ant-btn-lg acss-9mi5l3" type="button" > <span> - Default Button + Button </span> </button> </div> @@ -1849,6 +2254,29 @@ exports[`renders components/button/demo/multiple.tsx correctly 1`] = ` </div> `; +exports[`renders components/button/demo/noSpace.tsx correctly 1`] = ` +<div + class="ant-flex ant-flex-wrap-wrap ant-flex-gap-middle" +> + <button + class="ant-btn ant-btn-primary" + type="button" + > + <span> + 确定 + </span> + </button> + <button + class="ant-btn ant-btn-primary" + type="button" + > + <span> + 确 定 + </span> + </button> +</div> +`; + exports[`renders components/button/demo/size.tsx correctly 1`] = ` Array [ <div diff --git a/components/button/__tests__/delay-timer.test.tsx b/components/button/__tests__/delay-timer.test.tsx index 42bdca920705..02f86f448559 100644 --- a/components/button/__tests__/delay-timer.test.tsx +++ b/components/button/__tests__/delay-timer.test.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { act } from 'react-dom/test-utils'; -import { fireEvent, render } from '../../../tests/utils'; + +import { act, fireEvent, render } from '../../../tests/utils'; import Button from '../button'; const specialDelay = 9529; diff --git a/components/button/__tests__/index.test.tsx b/components/button/__tests__/index.test.tsx index 48f2ebfa622f..e9ce7151939c 100644 --- a/components/button/__tests__/index.test.tsx +++ b/components/button/__tests__/index.test.tsx @@ -1,12 +1,12 @@ import React, { Suspense, useRef, useState } from 'react'; import { SearchOutlined } from '@ant-design/icons'; import { resetWarned } from 'rc-util/lib/warning'; -import { act } from 'react-dom/test-utils'; import Button from '..'; +import type { GetRef } from '../../_util/type'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; -import { fireEvent, render, waitFakeTimer } from '../../../tests/utils'; +import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils'; import ConfigProvider from '../../config-provider'; import type { BaseButtonProps } from '../button'; @@ -88,7 +88,7 @@ describe('Button', () => { }); it('renders Chinese characters correctly in HOC', () => { - const Text = ({ children }: { children: React.ReactNode }) => <span>{children}</span>; + const Text: React.FC<React.PropsWithChildren> = ({ children }) => <span>{children}</span>; const { container, rerender } = render( <Button> <Text>按钮</Text> @@ -292,7 +292,7 @@ describe('Button', () => { }); it('skip check 2 words when ConfigProvider disable this', () => { - const buttonInstance = React.createRef<HTMLElement>(); + const buttonInstance = React.createRef<GetRef<typeof Button>>(); render( <ConfigProvider autoInsertSpaceInButton={false}> <Button ref={buttonInstance}>test</Button> @@ -369,13 +369,13 @@ describe('Button', () => { /> ); - const btnRef = React.createRef<HTMLButtonElement>(); + const btnRef = React.createRef<GetRef<typeof Button>>(); const refBtn = <Button ref={btnRef} />; - const anchorRef = React.createRef<HTMLAnchorElement>(); + const anchorRef = React.createRef<GetRef<typeof Button>>(); const refAnchor = <Button ref={anchorRef} />; - const htmlRef = React.createRef<HTMLElement>(); + const htmlRef = React.createRef<GetRef<typeof Button>>(); const refHtml = <Button ref={htmlRef} />; const btnAttr = <Button name="hello" />; @@ -401,9 +401,11 @@ describe('Button', () => { promiseCache.resolve = resolve; }); throw promiseCache.promise; - } else if (freeze) { + } + if (freeze) { throw promiseCache.promise; - } else if (promiseCache.promise) { + } + if (promiseCache.promise) { promiseCache.resolve?.(); promiseCache.promise = undefined; } @@ -441,4 +443,10 @@ describe('Button', () => { const { container } = render(<Button type={'' as any} />); expect(container.querySelector('.ant-btn-default')).toBeTruthy(); }); + + it('should support autoInsertSpace', () => { + const text = '确定'; + const { container } = render(<Button autoInsertSpace={false}>{text}</Button>); + expect(container.querySelector<HTMLButtonElement>('button')?.textContent).toBe(text); + }); }); diff --git a/components/button/__tests__/wave.test.tsx b/components/button/__tests__/wave.test.tsx index 80da394ba8dc..9347c65f9702 100644 --- a/components/button/__tests__/wave.test.tsx +++ b/components/button/__tests__/wave.test.tsx @@ -1,5 +1,6 @@ -import userEvent from '@testing-library/user-event'; import React from 'react'; +import userEvent from '@testing-library/user-event'; + import Button from '..'; import { act, fireEvent, render } from '../../../tests/utils'; diff --git a/components/button/button-group.tsx b/components/button/button-group.tsx index 5b10440e200a..056b7eca2e0e 100644 --- a/components/button/button-group.tsx +++ b/components/button/button-group.tsx @@ -33,7 +33,6 @@ const ButtonGroup: React.FC<ButtonGroupProps> = (props) => { case 'small': sizeCls = 'sm'; break; - case 'middle': default: // Do nothing } diff --git a/components/button/button.tsx b/components/button/button.tsx index b078c11fa455..b87ec58101a8 100644 --- a/components/button/button.tsx +++ b/components/button/button.tsx @@ -1,13 +1,5 @@ /* eslint-disable react/button-has-type */ -import React, { - Children, - createRef, - forwardRef, - useContext, - useEffect, - useMemo, - useState, -} from 'react'; +import React, { Children, createRef, useContext, useEffect, useMemo, useState } from 'react'; import classNames from 'classnames'; import omit from 'rc-util/lib/omit'; import { composeRef } from 'rc-util/lib/ref'; @@ -32,6 +24,7 @@ export type LegacyButtonType = ButtonType | 'danger'; export interface BaseButtonProps { type?: ButtonType; icon?: React.ReactNode; + iconPosition?: 'start' | 'end'; shape?: ButtonShape; size?: SizeType; disabled?: boolean; @@ -58,16 +51,9 @@ type MergedHTMLAttributes = Omit< export interface ButtonProps extends BaseButtonProps, MergedHTMLAttributes { href?: string; htmlType?: ButtonHTMLType; + autoInsertSpace?: boolean; } -type CompoundedComponent = React.ForwardRefExoticComponent< - ButtonProps & React.RefAttributes<HTMLElement> -> & { - Group: typeof Group; - /** @internal */ - __ANT_BUTTON: boolean; -}; - type LoadingConfigType = { loading: boolean; delay: number; @@ -89,15 +75,15 @@ function getLoadingConfig(loading: BaseButtonProps['loading']): LoadingConfigTyp }; } -const InternalButton: React.ForwardRefRenderFunction< +const InternalCompoundedButton = React.forwardRef< HTMLButtonElement | HTMLAnchorElement, ButtonProps -> = (props, ref) => { +>((props, ref) => { const { loading = false, prefixCls: customizePrefixCls, type, - danger, + danger = false, shape = 'default', size: customizeSize, styles, @@ -106,12 +92,14 @@ const InternalButton: React.ForwardRefRenderFunction< rootClassName, children, icon, + iconPosition = 'start', ghost = false, block = false, // React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`. htmlType = 'button', classNames: customClassNames, style: customStyle = {}, + autoInsertSpace, ...rest } = props; @@ -119,7 +107,10 @@ const InternalButton: React.ForwardRefRenderFunction< // Compatible with original `type` behavior const mergedType = type || 'default'; - const { getPrefixCls, autoInsertSpaceInButton, direction, button } = useContext(ConfigContext); + const { getPrefixCls, direction, button } = useContext(ConfigContext); + + const mergedInsertSpace = autoInsertSpace ?? button?.autoInsertSpace ?? true; + const prefixCls = getPrefixCls('btn', customizePrefixCls); const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls); @@ -165,7 +156,7 @@ const InternalButton: React.ForwardRefRenderFunction< useEffect(() => { // FIXME: for HOC usage like <FormatMessage /> - if (!buttonRef || !(buttonRef as any).current || autoInsertSpaceInButton === false) { + if (!buttonRef || !(buttonRef as any).current || !mergedInsertSpace) { return; } const buttonText = (buttonRef as any).current.textContent; @@ -204,7 +195,6 @@ const InternalButton: React.ForwardRefRenderFunction< ); } - const autoInsertSpace = autoInsertSpaceInButton !== false; const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction); const sizeClassNameMap = { large: 'lg', small: 'sm', middle: undefined }; @@ -228,10 +218,11 @@ const InternalButton: React.ForwardRefRenderFunction< [`${prefixCls}-icon-only`]: !children && children !== 0 && !!iconType, [`${prefixCls}-background-ghost`]: ghost && !isUnBorderedButtonType(mergedType), [`${prefixCls}-loading`]: innerLoading, - [`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && autoInsertSpace && !innerLoading, + [`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && mergedInsertSpace && !innerLoading, [`${prefixCls}-block`]: block, - [`${prefixCls}-dangerous`]: !!danger, + [`${prefixCls}-dangerous`]: danger, [`${prefixCls}-rtl`]: direction === 'rtl', + [`${prefixCls}-icon-end`]: iconPosition === 'end', }, compactItemClassnames, className, @@ -253,11 +244,11 @@ const InternalButton: React.ForwardRefRenderFunction< {icon} </IconWrapper> ) : ( - <LoadingIcon existIcon={!!icon} prefixCls={prefixCls} loading={!!innerLoading} /> + <LoadingIcon existIcon={!!icon} prefixCls={prefixCls} loading={innerLoading} /> ); const kids = - children || children === 0 ? spaceChildren(children, needInserted && autoInsertSpace) : null; + children || children === 0 ? spaceChildren(children, needInserted && mergedInsertSpace) : null; if (linkButtonRestProps.href !== undefined) { return wrapCSSVar( @@ -290,7 +281,6 @@ const InternalButton: React.ForwardRefRenderFunction< > {iconNode} {kids} - {/* Styles: compact */} {!!compactItemClassnames && <CompactCmp key="compact" prefixCls={prefixCls} />} </button> @@ -298,24 +288,27 @@ const InternalButton: React.ForwardRefRenderFunction< if (!isUnBorderedButtonType(mergedType)) { buttonNode = ( - <Wave component="Button" disabled={!!innerLoading}> + <Wave component="Button" disabled={innerLoading}> {buttonNode} </Wave> ); } - return wrapCSSVar(buttonNode); +}); + +type CompoundedComponent = typeof InternalCompoundedButton & { + Group: typeof Group; + /** @internal */ + __ANT_BUTTON: boolean; }; -const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>( - InternalButton, -) as CompoundedComponent; +const Button = InternalCompoundedButton as CompoundedComponent; + +Button.Group = Group; +Button.__ANT_BUTTON = true; if (process.env.NODE_ENV !== 'production') { Button.displayName = 'Button'; } -Button.Group = Group; -Button.__ANT_BUTTON = true; - export default Button; diff --git a/components/button/buttonHelpers.tsx b/components/button/buttonHelpers.tsx index a429debdf89a..4a13c9ef1a96 100644 --- a/components/button/buttonHelpers.tsx +++ b/components/button/buttonHelpers.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { cloneElement, isFragment } from '../_util/reactNode'; import type { BaseButtonProps, LegacyButtonType } from './button'; @@ -52,7 +53,7 @@ function splitCNCharsBySpace(child: React.ReactElement | string | number, needIn } export function spaceChildren(children: React.ReactNode, needInserted: boolean) { - let isPrevChildPure: boolean = false; + let isPrevChildPure = false; const childList: React.ReactNode[] = []; React.Children.forEach(children, (child) => { @@ -74,11 +75,14 @@ export function spaceChildren(children: React.ReactNode, needInserted: boolean) ); } +// eslint-disable-next-line @typescript-eslint/no-unused-vars const ButtonTypes = ['default', 'primary', 'dashed', 'link', 'text'] as const; -export type ButtonType = typeof ButtonTypes[number]; +export type ButtonType = (typeof ButtonTypes)[number]; +// eslint-disable-next-line @typescript-eslint/no-unused-vars const ButtonShapes = ['default', 'circle', 'round'] as const; -export type ButtonShape = typeof ButtonShapes[number]; +export type ButtonShape = (typeof ButtonShapes)[number]; +// eslint-disable-next-line @typescript-eslint/no-unused-vars const ButtonHTMLTypes = ['submit', 'button', 'reset'] as const; -export type ButtonHTMLType = typeof ButtonHTMLTypes[number]; +export type ButtonHTMLType = (typeof ButtonHTMLTypes)[number]; diff --git a/components/button/demo/basic.md b/components/button/demo/basic.md index f42f59988c60..dc335dc58baa 100644 --- a/components/button/demo/basic.md +++ b/components/button/demo/basic.md @@ -4,4 +4,4 @@ ## en-US -There are `primary` button, `default` button, `dashed` button, `text` button and `link` button in antd. +There are five type of buttons in antd, namely they are: `primary` buttons, `default` buttons, `dashed` buttons, `text` buttons, and `link` buttons. diff --git a/components/button/demo/basic.tsx b/components/button/demo/basic.tsx index 0cda77ee32a3..c0ac0563d6fe 100644 --- a/components/button/demo/basic.tsx +++ b/components/button/demo/basic.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Button, Flex } from 'antd'; const App: React.FC = () => ( - <Flex gap="small" wrap="wrap"> + <Flex gap="small" wrap> <Button type="primary">Primary Button</Button> <Button>Default Button</Button> <Button type="dashed">Dashed Button</Button> diff --git a/components/button/demo/block.md b/components/button/demo/block.md index 746d5c366425..70c50646e564 100644 --- a/components/button/demo/block.md +++ b/components/button/demo/block.md @@ -4,4 +4,4 @@ ## en-US -`block` property will make the button fit to its parent width. +The `block` property will make a button fit to its parent width. diff --git a/components/button/demo/chinese-chars-loading.tsx b/components/button/demo/chinese-chars-loading.tsx index ab6feab6b608..b184e97fa5cc 100644 --- a/components/button/demo/chinese-chars-loading.tsx +++ b/components/button/demo/chinese-chars-loading.tsx @@ -7,7 +7,7 @@ const Text2 = () => <span>部署</span>; const Text3 = () => <>Submit</>; const App: React.FC = () => ( - <Flex wrap="wrap" gap="small"> + <Flex wrap gap="small"> <Button> <span> <span>部署</span> diff --git a/components/button/demo/component-token.tsx b/components/button/demo/component-token.tsx index f197fc259688..bde16b00400e 100644 --- a/components/button/demo/component-token.tsx +++ b/components/button/demo/component-token.tsx @@ -24,12 +24,12 @@ const App: React.FC = () => ( }} > <Flex gap="small" vertical> - <Flex wrap="wrap" gap="small"> + <Flex wrap gap="small"> <Button type="text">TEXT</Button> <Button type="primary">CONTAINED</Button> <Button>OUTLINED</Button> </Flex> - <Flex wrap="wrap" gap="small"> + <Flex wrap gap="small"> <Button type="text" disabled> TEXT </Button> @@ -38,7 +38,7 @@ const App: React.FC = () => ( </Button> <Button disabled>OUTLINED</Button> </Flex> - <Flex wrap="wrap" gap="small"> + <Flex wrap gap="small"> <Button type="text" size="small"> TEXT </Button> diff --git a/components/button/demo/danger.md b/components/button/demo/danger.md index ca88b6475be3..e8e1fb897f98 100644 --- a/components/button/demo/danger.md +++ b/components/button/demo/danger.md @@ -4,4 +4,4 @@ ## en-US -`danger` is a property of button after antd 4.0. +The `danger` is a property of buttons after antd 4.0. diff --git a/components/button/demo/danger.tsx b/components/button/demo/danger.tsx index d205355e2a74..0846bcbf4ca1 100644 --- a/components/button/demo/danger.tsx +++ b/components/button/demo/danger.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Button, Flex } from 'antd'; const App: React.FC = () => ( - <Flex wrap="wrap" gap="small"> + <Flex wrap gap="small"> <Button type="primary" danger> Primary </Button> diff --git a/components/button/demo/debug-block.tsx b/components/button/demo/debug-block.tsx index 10641e59406f..fea84bd1f8f6 100644 --- a/components/button/demo/debug-block.tsx +++ b/components/button/demo/debug-block.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { DownloadOutlined } from '@ant-design/icons'; -import { Form, Button } from 'antd'; +import { Button, Form } from 'antd'; const App: React.FC = () => ( <Form> diff --git a/components/button/demo/debug-icon.tsx b/components/button/demo/debug-icon.tsx index 7dca5e6669e8..a39d9a1eb4cd 100644 --- a/components/button/demo/debug-icon.tsx +++ b/components/button/demo/debug-icon.tsx @@ -1,14 +1,7 @@ import React from 'react'; import { SearchOutlined } from '@ant-design/icons'; -import { - Button, - ConfigProvider, - Divider, - Flex, - Radio, - Tooltip, - type ConfigProviderProps, -} from 'antd'; +import { Button, ConfigProvider, Divider, Flex, Radio, Tooltip } from 'antd'; +import type { ConfigProviderProps } from 'antd'; type SizeType = ConfigProviderProps['componentSize']; @@ -26,7 +19,7 @@ const App: React.FC = () => { </Divider> <ConfigProvider componentSize={size}> <Flex gap="small" vertical> - <Flex gap="small" wrap="wrap"> + <Flex gap="small" wrap> <Tooltip title="search"> <Button type="primary" shape="circle" icon={<SearchOutlined />} /> </Tooltip> @@ -41,7 +34,7 @@ const App: React.FC = () => { </Tooltip> <Button icon={<SearchOutlined />}>Search</Button> </Flex> - <Flex gap="small" wrap="wrap"> + <Flex gap="small" wrap> <Tooltip title="search"> <Button shape="circle" icon={<SearchOutlined />} /> </Tooltip> diff --git a/components/button/demo/ghost.md b/components/button/demo/ghost.md index 4c0ebaaca8b5..0b48a21b2642 100644 --- a/components/button/demo/ghost.md +++ b/components/button/demo/ghost.md @@ -4,4 +4,4 @@ ## en-US -`ghost` property will make button's background transparent, it is commonly used in colored background. +The `ghost` property will make a button's background transparent, this is commonly used in colored background. diff --git a/components/button/demo/ghost.tsx b/components/button/demo/ghost.tsx index fe0da63cc443..4615df9ec2b5 100644 --- a/components/button/demo/ghost.tsx +++ b/components/button/demo/ghost.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Button, Flex } from 'antd'; const App: React.FC = () => ( - <Flex wrap="wrap" gap="small" className="site-button-ghost-wrapper"> + <Flex wrap gap="small" className="site-button-ghost-wrapper"> <Button type="primary" ghost> Primary </Button> diff --git a/components/button/demo/icon-position.md b/components/button/demo/icon-position.md new file mode 100644 index 000000000000..a0120668fff7 --- /dev/null +++ b/components/button/demo/icon-position.md @@ -0,0 +1,7 @@ +## zh-CN + +通过设置 `iconPosition` 为 `start` 或 `end` 分别设置按钮图标的位置。 + +## en-US + +You can set the position of a button's icon by setting the `iconPosition` to `start` or `end` respectively. diff --git a/components/button/demo/icon-position.tsx b/components/button/demo/icon-position.tsx new file mode 100644 index 000000000000..bda8aa508990 --- /dev/null +++ b/components/button/demo/icon-position.tsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; +import { SearchOutlined } from '@ant-design/icons'; +import { Button, Divider, Flex, Radio, Space, Tooltip } from 'antd'; + +const App: React.FC = () => { + const [position, setPosition] = useState<'start' | 'end'>('end'); + + return ( + <> + <Space> + <Radio.Group value={position} onChange={(e) => setPosition(e.target.value)}> + <Radio.Button value="start">start</Radio.Button> + <Radio.Button value="end">end</Radio.Button> + </Radio.Group> + </Space> + <Divider orientation="left" plain> + Preview + </Divider> + <Flex gap="small" vertical> + <Flex wrap gap="small"> + <Tooltip title="search"> + <Button type="primary" shape="circle" icon={<SearchOutlined />} /> + </Tooltip> + <Button type="primary" shape="circle"> + A + </Button> + <Button type="primary" icon={<SearchOutlined />} iconPosition={position}> + Search + </Button> + <Tooltip title="search"> + <Button shape="circle" icon={<SearchOutlined />} /> + </Tooltip> + <Button icon={<SearchOutlined />} iconPosition={position}> + Search + </Button> + </Flex> + <Flex wrap gap="small"> + <Tooltip title="search"> + <Button shape="circle" icon={<SearchOutlined />} /> + </Tooltip> + <Button icon={<SearchOutlined />} type="text" iconPosition={position}> + Search + </Button> + <Tooltip title="search"> + <Button type="dashed" shape="circle" icon={<SearchOutlined />} /> + </Tooltip> + <Button type="dashed" icon={<SearchOutlined />} iconPosition={position}> + Search + </Button> + <Button icon={<SearchOutlined />} href="https://www.google.com" iconPosition={position} /> + <Button type="primary" loading iconPosition={position}> + Loading + </Button> + </Flex> + </Flex> + </> + ); +}; + +export default App; diff --git a/components/button/demo/icon.md b/components/button/demo/icon.md index a41ab8ac5115..25841bd04983 100644 --- a/components/button/demo/icon.md +++ b/components/button/demo/icon.md @@ -1,11 +1,7 @@ ## zh-CN -当需要在 `Button` 内嵌入 `Icon` 时,可以设置 `icon` 属性,或者直接在 `Button` 内使用 `Icon` 组件。 - -如果想控制 `Icon` 具体的位置,只能直接使用 `Icon` 组件,而非 `icon` 属性。 +可以通过 `icon`属性添加图标。 ## en-US -`Button` components can contain an `Icon`. This is done by setting the `icon` property or placing an `Icon` component within the `Button`. - -If you want specific control over the positioning and placement of the `Icon`, then that should be done by placing the `Icon` component within the `Button` rather than using the `icon` property. +You can add an icon using the `icon` property. diff --git a/components/button/demo/icon.tsx b/components/button/demo/icon.tsx index 855e8d639350..d6b0695024b2 100644 --- a/components/button/demo/icon.tsx +++ b/components/button/demo/icon.tsx @@ -4,7 +4,7 @@ import { Button, Flex, Tooltip } from 'antd'; const App: React.FC = () => ( <Flex gap="small" vertical> - <Flex wrap="wrap" gap="small"> + <Flex wrap gap="small"> <Tooltip title="search"> <Button type="primary" shape="circle" icon={<SearchOutlined />} /> </Tooltip> @@ -19,7 +19,7 @@ const App: React.FC = () => ( </Tooltip> <Button icon={<SearchOutlined />}>Search</Button> </Flex> - <Flex wrap="wrap" gap="small"> + <Flex wrap gap="small"> <Tooltip title="search"> <Button shape="circle" icon={<SearchOutlined />} /> </Tooltip> diff --git a/components/button/demo/linear-gradient.md b/components/button/demo/linear-gradient.md index a09d28b3ff38..9cca440160e4 100644 --- a/components/button/demo/linear-gradient.md +++ b/components/button/demo/linear-gradient.md @@ -4,4 +4,4 @@ ## en-US -Buttons with gradient background. +Buttons with a gradient background. diff --git a/components/button/demo/linear-gradient.tsx b/components/button/demo/linear-gradient.tsx index 87742e652ff8..bf9172fdf515 100644 --- a/components/button/demo/linear-gradient.tsx +++ b/components/button/demo/linear-gradient.tsx @@ -1,26 +1,51 @@ import React from 'react'; +import { AntDesignOutlined } from '@ant-design/icons'; import { Button, ConfigProvider, Space } from 'antd'; +import { createStyles } from 'antd-style'; -const App: React.FC = () => ( - <ConfigProvider - theme={{ - components: { - Button: { - colorPrimary: 'linear-gradient(90deg, #FF4E50, #F9D423) !important', - primaryShadow: 'none', - defaultBg: 'linear-gradient(90deg, #aea4e3, #d3ffe8) !important', - defaultShadow: 'none', - defaultColor: '#fff !important', - lineWidth: 0, - }, - }, - }} - > - <Space> - <Button type="primary">Primary Button</Button> - <Button>Default Button</Button> - </Space> - </ConfigProvider> -); +const useStyle = createStyles(({ prefixCls, css }) => ({ + linearGradientButton: css` + &.${prefixCls}-btn-primary:not([disabled]):not(.${prefixCls}-btn-dangerous) { + border-width: 0; + + > span { + position: relative; + } + + &::before { + content: ''; + background: linear-gradient(135deg, #6253e1, #04befe); + position: absolute; + inset: 0; + opacity: 1; + transition: all 0.3s; + border-radius: inherit; + } + + &:hover::before { + opacity: 0; + } + } + `, +})); + +const App: React.FC = () => { + const { styles } = useStyle(); + + return ( + <ConfigProvider + button={{ + className: styles.linearGradientButton, + }} + > + <Space> + <Button type="primary" size="large" icon={<AntDesignOutlined />}> + Gradient Button + </Button> + <Button size="large">Button</Button> + </Space> + </ConfigProvider> + ); +}; export default App; diff --git a/components/button/demo/loading.tsx b/components/button/demo/loading.tsx index 0562c4516efe..50b5b62153ee 100644 --- a/components/button/demo/loading.tsx +++ b/components/button/demo/loading.tsx @@ -23,7 +23,7 @@ const App: React.FC = () => { return ( <Flex gap="small" vertical> - <Flex gap="small" align="center" wrap="wrap"> + <Flex gap="small" align="center" wrap> <Button type="primary" loading> Loading </Button> @@ -32,7 +32,7 @@ const App: React.FC = () => { </Button> <Button type="primary" icon={<PoweroffOutlined />} loading /> </Flex> - <Flex gap="small" wrap="wrap"> + <Flex gap="small" wrap> <Button type="primary" loading={loadings[0]} onClick={() => enterLoading(0)}> Click me! </Button> diff --git a/components/button/demo/multiple.md b/components/button/demo/multiple.md index 7d6f419cb20d..c0a9b1159b83 100644 --- a/components/button/demo/multiple.md +++ b/components/button/demo/multiple.md @@ -1,7 +1,7 @@ ## zh-CN -按钮组合使用时,推荐使用 1 个主操作 + n 个次操作,3 个以上操作时把更多操作放到 [Dropdown.Button](/components/dropdown/#components-dropdown-demo-dropdown-button) 中组合使用。 +按钮组合使用时,推荐使用 1 个主操作 + n 个次操作,3 个以上操作时把更多操作放到 [Dropdown.Button](/components/dropdown-cn/#dropdown-demo-dropdown-button) 中组合使用。 ## en-US -If you need several buttons, we recommend that you use 1 primary button + n secondary buttons, and if there are more than three operations, you can group some of them into [Dropdown.Button](/components/dropdown/#components-dropdown-demo-dropdown-button). +If you need several buttons, we recommend that you use 1 primary button + n secondary buttons. If there are more than three operations, you can group some of them into a [Dropdown.Button](/components/dropdown/#dropdown-demo-dropdown-button). diff --git a/components/button/demo/noSpace.md b/components/button/demo/noSpace.md new file mode 100644 index 000000000000..8a5d3a85706d --- /dev/null +++ b/components/button/demo/noSpace.md @@ -0,0 +1,7 @@ +## zh-CN + +我们默认在两个汉字之间添加空格,可以通过设置 `autoInsertSpace` 为 `false` 关闭。 + +## en-US + +We add a space between two Chinese characters by default, which can be removed by setting `autoInsertSpace` to `false`. diff --git a/components/button/demo/noSpace.tsx b/components/button/demo/noSpace.tsx new file mode 100644 index 000000000000..5994ec07b31c --- /dev/null +++ b/components/button/demo/noSpace.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Button, Flex } from 'antd'; + +const App: React.FC = () => ( + <Flex gap="middle" wrap> + <Button type="primary" autoInsertSpace={false}> + 确定 + </Button> + <Button type="primary" autoInsertSpace> + 确定 + </Button> + </Flex> +); + +export default App; diff --git a/components/button/demo/size.md b/components/button/demo/size.md index 118f1f64b17f..72edaee17e08 100644 --- a/components/button/demo/size.md +++ b/components/button/demo/size.md @@ -2,10 +2,10 @@ 按钮有大、中、小三种尺寸。 -通过设置 `size` 为 `large` `small` 分别把按钮设为大、小尺寸。若不设置 `size`,则尺寸为中。 +通过设置 `size` 为 `large` `small` 分别把按钮设为大、小尺寸。若不设置 `size`,则尺寸默认为中。 ## en-US -Ant Design supports a default button size as well as a large and small size. +Ant Design supports three sizes of buttons: small, default and large. If a large or small button is desired, set the `size` property to either `large` or `small` respectively. Omit the `size` property for a button with the default size. diff --git a/components/button/demo/size.tsx b/components/button/demo/size.tsx index 2831519db517..7a874bfd6677 100644 --- a/components/button/demo/size.tsx +++ b/components/button/demo/size.tsx @@ -18,7 +18,7 @@ const App: React.FC = () => { Preview </Divider> <Flex gap="small" align="flex-start" vertical> - <Flex gap="small" wrap="wrap"> + <Flex gap="small" wrap> <Button type="primary" size={size}> Primary </Button> @@ -30,7 +30,7 @@ const App: React.FC = () => { <Button type="link" size={size}> Link </Button> - <Flex gap="small" wrap="wrap"> + <Flex gap="small" wrap> <Button type="primary" icon={<DownloadOutlined />} size={size} /> <Button type="primary" shape="circle" icon={<DownloadOutlined />} size={size} /> <Button type="primary" shape="round" icon={<DownloadOutlined />} size={size} /> diff --git a/components/button/index.en-US.md b/components/button/index.en-US.md index 2b098d76211d..23ef9130fdbb 100644 --- a/components/button/index.en-US.md +++ b/components/button/index.en-US.md @@ -1,6 +1,7 @@ --- category: Components title: Button +description: To trigger an operation. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*7va7RKs3YzIAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3T4cRqxH9-8AAAAAAAAAAAAADrJ8AQ/original demo: @@ -10,16 +11,14 @@ group: order: 1 --- -To trigger an operation. - ## When To Use -A button means an operation (or a series of operations). Clicking a button will trigger corresponding business logic. +A button means an operation (or a series of operations). Clicking a button will trigger its corresponding business logic. In Ant Design we provide 5 types of button. -- Primary button: indicate the main action, one primary button at most in one section. -- Default button: indicate a series of actions without priority. +- Primary button: used for the main action, there can be at most one primary button in a section. +- Default button: used for a series of actions without priority. - Dashed button: commonly used for adding more actions. - Text button: used for the most secondary action. - Link button: used for external links. @@ -28,14 +27,15 @@ And 4 other properties additionally. - `danger`: used for actions of risk, like deletion or authorization. - `ghost`: used in situations with complex background, home pages usually. -- `disabled`: when actions are not available. -- `loading`: add loading spinner in button, avoiding multiple submits too. +- `disabled`: used when actions are not available. +- `loading`: adds a loading spinner in button, avoids multiple submits too. ## Examples <!-- prettier-ignore --> <code src="./demo/basic.tsx">Type</code> <code src="./demo/icon.tsx">Icon</code> +<code src="./demo/icon-position.tsx" version="5.17.0">Icon Position</code> <code src="./demo/debug-icon.tsx" debug>Debug Icon</code> <code src="./demo/debug-block.tsx" debug>Debug Block</code> <code src="./demo/size.tsx">Size</code> @@ -48,7 +48,7 @@ And 4 other properties additionally. <code src="./demo/legacy-group.tsx" debug>Deprecated Button Group</code> <code src="./demo/chinese-chars-loading.tsx" debug>Loading style bug</code> <code src="./demo/component-token.tsx" debug>Component Token</code> -<code src="./demo/linear-gradient.tsx" debug>Gradient Button</code> +<code src="./demo/linear-gradient.tsx">Gradient Button</code> ## API @@ -58,6 +58,7 @@ Different button styles can be generated by setting Button properties. The recom | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | +| autoInsertSpace | We add a space between two Chinese characters by default, which can be removed by setting `autoInsertSpace` to `false`. | boolean | `true` | 5.17.0 | | block | Option to fit button width to its parent width | boolean | false | | | classNames | Semantic DOM class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.4.0 | | danger | Set the danger status of button | boolean | false | | @@ -66,13 +67,14 @@ Different button styles can be generated by setting Button properties. The recom | href | Redirect url of link button | string | - | | | htmlType | Set the original html `type` of `button`, see: [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type) | string | `button` | | | icon | Set the icon component of button | ReactNode | - | | +| iconPosition | Set the icon position of button | `start` \| `end` | `start` | 5.17.0 | | loading | Set the loading status of button | boolean \| { delay: number } | false | | | shape | Can be set button shape | `default` \| `circle` \| `round` | `default` | | | size | Set the size of button | `large` \| `middle` \| `small` | `middle` | | | styles | Semantic DOM style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.4.0 | | target | Same as target attribute of a, works when href is specified | string | - | | | type | Set button type | `primary` \| `dashed` \| `link` \| `text` \| `default` | `default` | | -| onClick | Set the handler to handle `click` event | (event: MouseEvent) => void | - | | +| onClick | Set the handler to handle `click` event | (event: React.MouseEvent<HTMLElement, MouseEvent>) => void | - | | It accepts all props which native buttons support. @@ -86,18 +88,16 @@ It accepts all props which native buttons support. ## FAQ -### How to remove space between 2 chinese characters? +### How to close the click wave effect? -Following the Ant Design specification, we will add one space between if Button (exclude Text button and Link button) contains two Chinese characters only. If you don't need that, you can use [ConfigProvider](/components/config-provider/#api) to set `autoInsertSpaceInButton` as `false`. +If you don't need this feature, you can set `disabled` of `wave` in [ConfigProvider](/components/config-provider#api) as `true`. -```tsx -<ConfigProvider autoInsertSpaceInButton={false}> - <Button>按钮</Button> +```jsx +<ConfigProvider wave={{ disabled: true }}> + <Button>click</Button> </ConfigProvider> ``` -<img src="https://gw.alipayobjects.com/zos/antfincdn/MY%26THAPZrW/38f06cb9-293a-4b42-b183-9f443e79ffea.png" width="100px" height="64px" style="box-shadow: none; margin: 0;" alt="Button with two Chinese characters" /> - <style> .site-button-ghost-wrapper { padding: 16px; diff --git a/components/button/index.ts b/components/button/index.tsx similarity index 100% rename from components/button/index.ts rename to components/button/index.tsx diff --git a/components/button/index.zh-CN.md b/components/button/index.zh-CN.md index 0971b8788fe1..938247c5f7c3 100644 --- a/components/button/index.zh-CN.md +++ b/components/button/index.zh-CN.md @@ -2,6 +2,7 @@ category: Components title: Button subtitle: 按钮 +description: 按钮用于开始一个即时操作。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*7va7RKs3YzIAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*3T4cRqxH9-8AAAAAAAAAAAAADrJ8AQ/original demo: @@ -11,8 +12,6 @@ group: order: 1 --- -按钮用于开始一个即时操作。 - ## 何时使用 标记了一个(或封装一组)操作命令,响应用户点击行为,触发相应的业务逻辑。 @@ -38,7 +37,8 @@ group: <!-- prettier-ignore --> <code src="./demo/basic.tsx">按钮类型</code> -<code src="./demo/icon.tsx">图标按钮</code> +<code src="./demo/icon.tsx">按钮图标</code> +<code src="./demo/icon-position.tsx" version="5.17.0">按钮图标位置</code> <code src="./demo/debug-icon.tsx" debug>调试图标按钮</code> <code src="./demo/debug-block.tsx" debug>调试按钮block属性</code> <code src="./demo/size.tsx">按钮尺寸</code> @@ -51,7 +51,8 @@ group: <code src="./demo/legacy-group.tsx" debug>废弃的 Block 组</code> <code src="./demo/chinese-chars-loading.tsx" debug>加载中状态 bug 还原</code> <code src="./demo/component-token.tsx" debug>组件 Token</code> -<code src="./demo/linear-gradient.tsx" debug>渐变按钮</code> +<code src="./demo/linear-gradient.tsx">渐变按钮</code> +<code src="./demo/noSpace.tsx" version="5.17.0">移除两个汉字之间的空格</code> ## API @@ -63,6 +64,7 @@ group: | 属性 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | +| autoInsertSpace | 我们默认提供两个汉字之间的空格,可以设置 `autoInsertSpace` 为 `false` 关闭 | boolean | `true` | 5.17.0 | | block | 将按钮宽度调整为其父宽度的选项 | boolean | false | | | classNames | 语义化结构 class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.4.0 | | danger | 设置危险按钮 | boolean | false | | @@ -71,13 +73,14 @@ group: | href | 点击跳转的地址,指定此属性 button 的行为和 a 链接一致 | string | - | | | htmlType | 设置 `button` 原生的 `type` 值,可选值请参考 [HTML 标准](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type) | string | `button` | | | icon | 设置按钮的图标组件 | ReactNode | - | | +| iconPosition | 设置按钮图标组件的位置 | `start` \| `end` | `start` | 5.17.0 | | loading | 设置按钮载入状态 | boolean \| { delay: number } | false | | | shape | 设置按钮形状 | `default` \| `circle` \| `round` | `default` | | | size | 设置按钮大小 | `large` \| `middle` \| `small` | `middle` | | | styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.4.0 | | target | 相当于 a 链接的 target 属性,href 存在时生效 | string | - | | | type | 设置按钮类型 | `primary` \| `dashed` \| `link` \| `text` \| `default` | `default` | | -| onClick | 点击按钮时的回调 | (event: MouseEvent) => void | - | | +| onClick | 点击按钮时的回调 | (event: React.MouseEvent<HTMLElement, MouseEvent>) => void | - | | 支持原生 button 的其他所有属性。 @@ -91,18 +94,16 @@ group: ## FAQ -### 如何移除两个汉字之间的空格? +### 如何关闭点击波纹效果? -根据 Ant Design 设计规范要求,我们会在按钮内(文本按钮和链接按钮除外)只有两个汉字时自动添加空格,如果你不需要这个特性,可以设置 [ConfigProvider](/components/config-provider-cn#api) 的 `autoInsertSpaceInButton` 为 `false`。 +如果你不需要这个特性,可以设置 [ConfigProvider](/components/config-provider-cn#api) 的 `wave` 的 `disabled` 为 `true`。 -```tsx -<ConfigProvider autoInsertSpaceInButton={false}> - <Button>按钮</Button> +```jsx +<ConfigProvider wave={{ disabled: true }}> + <Button>click</Button> </ConfigProvider> ``` -<img src="https://gw.alipayobjects.com/zos/antfincdn/MY%26THAPZrW/38f06cb9-293a-4b42-b183-9f443e79ffea.png" style="box-shadow: none; margin: 0" width="100px" height="64px" alt="移除两个汉字之间的空格" /> - <style> .site-button-ghost-wrapper { padding: 16px; diff --git a/components/button/style/compactCmp.ts b/components/button/style/compactCmp.ts index c0922a0851b7..a480dc4a9439 100644 --- a/components/button/style/compactCmp.ts +++ b/components/button/style/compactCmp.ts @@ -1,11 +1,12 @@ // Style as inline component -import type { ButtonToken } from './token'; -import { prepareComponentToken, prepareToken } from './token'; +import { unit } from '@ant-design/cssinjs'; + import { genCompactItemStyle } from '../../style/compact-item'; import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical'; import type { GenerateStyle } from '../../theme/internal'; import { genSubStyleComponent } from '../../theme/internal'; -import { unit } from '@ant-design/cssinjs'; +import type { ButtonToken } from './token'; +import { prepareComponentToken, prepareToken } from './token'; const genButtonCompactStyle: GenerateStyle<ButtonToken> = (token) => { const { componentCls, calc } = token; diff --git a/components/button/style/group.ts b/components/button/style/group.ts index c13e199e6167..2f7a5741333b 100644 --- a/components/button/style/group.ts +++ b/components/button/style/group.ts @@ -1,5 +1,5 @@ -import type { ButtonToken } from './token'; import type { GenerateStyle } from '../../theme/internal'; +import type { ButtonToken } from './token'; const genButtonBorderStyle = (buttonTypeCls: string, borderColor: string) => ({ // Border @@ -54,9 +54,7 @@ const genGroupStyle: GenerateStyle<ButtonToken> = (token) => { position: 'relative', zIndex: 1, - [`&:hover, - &:focus, - &:active`]: { + '&:hover, &:focus, &:active': { zIndex: 2, }, diff --git a/components/button/style/index.ts b/components/button/style/index.ts index 9b38143ae7b8..26dd4c7d40e2 100644 --- a/components/button/style/index.ts +++ b/components/button/style/index.ts @@ -13,12 +13,14 @@ export type { ComponentToken }; // ============================== Shared ============================== const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSSObject => { const { componentCls, iconCls, fontWeight } = token; - return { [componentCls]: { outline: 'none', position: 'relative', - display: 'inline-block', + display: 'inline-flex', + gap: token.marginXS, + alignItems: 'center', + justifyContent: 'center', fontWeight, whiteSpace: 'nowrap', textAlign: 'center', @@ -40,18 +42,7 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS }, [`${componentCls}-icon`]: { - lineHeight: 0, - }, - - // Leave a space between icon and text. - [`> ${iconCls} + span, > span + ${iconCls}`]: { - marginInlineStart: token.marginXS, - }, - - [`&:not(${componentCls}-icon-only) > ${componentCls}-icon`]: { - [`&${componentCls}-loading-icon, &:not(:last-child)`]: { - marginInlineEnd: token.marginXS, - }, + lineHeight: 1, }, '> a': { @@ -71,9 +62,9 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS letterSpacing: '0.34em', }, - // make `btn-icon-only` not too narrow - [`&-icon-only${componentCls}-compact-item`]: { - flex: 'none', + // iconPosition="end" + '&-icon-end': { + flexDirection: 'row-reverse', }, }, }; @@ -368,7 +359,7 @@ const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token) => ({ }, { color: token.colorErrorHover, - background: token.colorErrorBg, + background: token.colorErrorBgActive, }, ), }, @@ -395,7 +386,7 @@ const genTypeButtonStyle: GenerateStyle<ButtonToken> = (token) => { }; // =============================== Size =============================== -const genButtonStyle = (token: ButtonToken, prefixCls: string = ''): CSSInterpolation => { +const genButtonStyle = (token: ButtonToken, prefixCls = ''): CSSInterpolation => { const { componentCls, controlHeight, @@ -411,7 +402,7 @@ const genButtonStyle = (token: ButtonToken, prefixCls: string = ''): CSSInterpol return [ { - [`${prefixCls}`]: { + [prefixCls]: { fontSize, lineHeight, height: controlHeight, @@ -420,11 +411,17 @@ const genButtonStyle = (token: ButtonToken, prefixCls: string = ''): CSSInterpol [`&${iconOnlyCls}`]: { width: controlHeight, - paddingInlineStart: 0, - paddingInlineEnd: 0, + paddingInline: 0, + + // make `btn-icon-only` not too narrow + [`&${componentCls}-compact-item`]: { + flex: 'none', + }, + [`&${componentCls}-round`]: { width: 'auto', }, + [iconCls]: { fontSize: token.buttonIconOnlyFontSize, }, diff --git a/components/button/style/token.ts b/components/button/style/token.ts index c9e975e567cb..0e69484605e5 100644 --- a/components/button/style/token.ts +++ b/components/button/style/token.ts @@ -1,7 +1,7 @@ import type { CSSProperties } from 'react'; -import type { FullToken, GetDefaultToken } from '../../theme/internal'; + +import type { FullToken, GetDefaultToken, GenStyleFn } from '../../theme/internal'; import { getLineHeight, mergeToken } from '../../theme/internal'; -import type { GenStyleFn } from '../../theme/util/genComponentStyleHook'; /** Component only token. Which will handle additional calculation of alias token */ export interface ComponentToken { @@ -116,20 +116,20 @@ export interface ComponentToken { */ paddingInlineSM: CSSProperties['paddingInline']; /** - * @desc 按钮横向内间距 - * @descEN Horizontal padding of button + * @desc 按钮纵向内间距 + * @descEN Vertical padding of button */ - paddingBlock: CSSProperties['paddingInline']; + paddingBlock: CSSProperties['paddingBlock']; /** - * @desc 大号按钮横向内间距 - * @descEN Horizontal padding of large button + * @desc 大号按钮纵向内间距 + * @descEN Vertical padding of large button */ - paddingBlockLG: CSSProperties['paddingInline']; + paddingBlockLG: CSSProperties['paddingBlock']; /** - * @desc 小号按钮横向内间距 - * @descEN Horizontal padding of small button + * @desc 小号按钮纵向内间距 + * @descEN Vertical padding of small button */ - paddingBlockSM: CSSProperties['paddingInline']; + paddingBlockSM: CSSProperties['paddingBlock']; /** * @desc 只有图标的按钮图标尺寸 * @descEN Icon size of button which only contains icon @@ -193,8 +193,20 @@ export interface ComponentToken { } export interface ButtonToken extends FullToken<'Button'> { + /** + * @desc 按钮横向内边距 + * @descEN Horizontal padding of button + */ buttonPaddingHorizontal: CSSProperties['paddingInline']; + /** + * @desc 按钮纵向内边距 + * @descEN Vertical padding of button + */ buttonPaddingVertical: CSSProperties['paddingBlock']; + /** + * @desc 只有图标的按钮图标尺寸 + * @descEN Icon size of button which only contains icon + */ buttonIconOnlyFontSize: number; } diff --git a/components/calendar/Header.tsx b/components/calendar/Header.tsx index eeef2e8d5aa8..ddac2d1f9e38 100644 --- a/components/calendar/Header.tsx +++ b/components/calendar/Header.tsx @@ -1,7 +1,8 @@ -import type { GenerateConfig } from 'rc-picker/lib/generate'; -import type { Locale } from 'rc-picker/lib/interface'; import * as React from 'react'; import { useContext, useMemo } from 'react'; +import type { GenerateConfig } from 'rc-picker/lib/generate'; +import type { Locale } from 'rc-picker/lib/interface'; + import { FormItemInputContext } from '../form/context'; import { Button, Group } from '../radio'; import Select from '../select'; diff --git a/components/calendar/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/calendar/__tests__/__snapshots__/demo-extend.test.ts.snap index 16ab4c4b1b08..cf244317e55f 100644 --- a/components/calendar/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/calendar/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -7963,11 +7963,7 @@ exports[`renders components/calendar/demo/customize-header.tsx extend context co </div> `; -exports[`renders components/calendar/demo/customize-header.tsx extend context correctly 2`] = ` -[ - "Warning: [antd: Select] \`dropdownMatchSelectWidth\` is deprecated. Please use \`popupMatchSelectWidth\` instead.", -] -`; +exports[`renders components/calendar/demo/customize-header.tsx extend context correctly 2`] = `[]`; exports[`renders components/calendar/demo/notice-calendar.tsx extend context correctly 1`] = ` <div diff --git a/components/calendar/__tests__/demo.test.ts b/components/calendar/__tests__/demo.test.ts index a29e22786fd0..f3b88d0f4452 100644 --- a/components/calendar/__tests__/demo.test.ts +++ b/components/calendar/__tests__/demo.test.ts @@ -1,4 +1,5 @@ import dayjs from 'dayjs'; + import demoTest from '../../../tests/shared/demoTest'; demoTest('calendar', { diff --git a/components/calendar/__tests__/index.test.tsx b/components/calendar/__tests__/index.test.tsx index a5d3eb59be97..c4dc8a6eff5a 100644 --- a/components/calendar/__tests__/index.test.tsx +++ b/components/calendar/__tests__/index.test.tsx @@ -4,7 +4,7 @@ import 'dayjs/locale/zh-cn'; import React from 'react'; import MockDate from 'mockdate'; -import { type PickerPanelProps } from 'rc-picker'; +import type { PickerPanelProps } from 'rc-picker'; import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs'; import type { Locale } from 'rc-picker/lib/interface'; import { resetWarned } from 'rc-util/lib/warning'; @@ -16,7 +16,9 @@ import { fireEvent, render } from '../../../tests/utils'; import Group from '../../radio/group'; import Button from '../../radio/radioButton'; import Select from '../../select'; -import Header, { type CalendarHeaderProps } from '../Header'; +import Header from '../Header'; +import type { CalendarHeaderProps } from '../Header'; +import ConfigProvider from '../../config-provider'; const ref: { calendarProps?: PickerPanelProps; @@ -207,6 +209,23 @@ describe('Calendar', () => { MockDate.reset(); }); + it('Calendar locale support should override ConfigProvider locale', () => { + MockDate.set(Dayjs('2018-10-19').valueOf()); + // eslint-disable-next-line global-require + const zhCN = require('../locale/zh_CN').default; + // eslint-disable-next-line global-require + const enUs = require('../../locale/en_US').default; + const wrapper = render( + <ConfigProvider locale={enUs}> + <Calendar locale={zhCN} /> + </ConfigProvider>, + ); + expect(wrapper.container.querySelector('.ant-picker-content thead')?.textContent).toBe( + '一二三四五六日', + ); + MockDate.reset(); + }); + describe('onPanelChange', () => { it('trigger when click last month of date', () => { const onPanelChange = jest.fn(); @@ -334,7 +353,7 @@ describe('Calendar', () => { const onTypeChange = jest.fn(); const value = Dayjs('2018-12-03'); const wrapper = render( - <Header + <Header<Dayjs.Dayjs> prefixCls="ant-picker-calendar" generateConfig={dayjsGenerateConfig} onModeChange={onTypeChange} @@ -370,7 +389,7 @@ describe('Calendar', () => { return ( <Select size="small" - dropdownMatchSelectWidth={false} + popupMatchSelectWidth={false} className="my-year-select" onChange={onYearChange} value={String(year)} @@ -414,7 +433,7 @@ describe('Calendar', () => { return ( <Select size="small" - dropdownMatchSelectWidth={false} + popupMatchSelectWidth={false} className="my-month-select" onChange={onMonthChange} value={String(month)} diff --git a/components/calendar/demo/basic.tsx b/components/calendar/demo/basic.tsx index 0255c4beceb6..d79ed14736b2 100644 --- a/components/calendar/demo/basic.tsx +++ b/components/calendar/demo/basic.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import type { Dayjs } from 'dayjs'; import { Calendar } from 'antd'; import type { CalendarProps } from 'antd'; +import type { Dayjs } from 'dayjs'; const App: React.FC = () => { const onPanelChange = (value: Dayjs, mode: CalendarProps<Dayjs>['mode']) => { diff --git a/components/calendar/demo/card.tsx b/components/calendar/demo/card.tsx index aa08fc1690b6..b346625b3e4f 100644 --- a/components/calendar/demo/card.tsx +++ b/components/calendar/demo/card.tsx @@ -1,7 +1,7 @@ -import type { Dayjs } from 'dayjs'; import React from 'react'; import { Calendar, theme } from 'antd'; import type { CalendarProps } from 'antd'; +import type { Dayjs } from 'dayjs'; const onPanelChange = (value: Dayjs, mode: CalendarProps<Dayjs>['mode']) => { console.log(value.format('YYYY-MM-DD'), mode); diff --git a/components/calendar/demo/component-token.tsx b/components/calendar/demo/component-token.tsx index 90c3223d2fcb..5a27f76a9812 100644 --- a/components/calendar/demo/component-token.tsx +++ b/components/calendar/demo/component-token.tsx @@ -1,7 +1,7 @@ -import type { Dayjs } from 'dayjs'; import React from 'react'; import { Calendar, ConfigProvider } from 'antd'; import type { CalendarProps } from 'antd'; +import type { Dayjs } from 'dayjs'; /** Test usage. Do not use in your production. */ export default () => { diff --git a/components/calendar/demo/customize-header.tsx b/components/calendar/demo/customize-header.tsx index 4b969e0f33c1..da9bb28f21fd 100644 --- a/components/calendar/demo/customize-header.tsx +++ b/components/calendar/demo/customize-header.tsx @@ -1,10 +1,12 @@ import React from 'react'; import dayjs from 'dayjs'; + import 'dayjs/locale/zh-cn'; + +import { Calendar, Col, Radio, Row, Select, theme, Typography } from 'antd'; +import type { CalendarProps } from 'antd'; import type { Dayjs } from 'dayjs'; import dayLocaleData from 'dayjs/plugin/localeData'; -import { Calendar, Col, Radio, Row, Select, Typography, theme } from 'antd'; -import type { CalendarProps } from 'antd'; dayjs.extend(dayLocaleData); @@ -73,7 +75,7 @@ const App: React.FC = () => { <Col> <Select size="small" - dropdownMatchSelectWidth={false} + popupMatchSelectWidth={false} className="my-year-select" value={year} onChange={(newYear) => { @@ -87,7 +89,7 @@ const App: React.FC = () => { <Col> <Select size="small" - dropdownMatchSelectWidth={false} + popupMatchSelectWidth={false} value={month} onChange={(newMonth) => { const now = value.clone().month(newMonth); diff --git a/components/calendar/demo/lunar.tsx b/components/calendar/demo/lunar.tsx index 95035843a2dd..a3d45c29b20e 100644 --- a/components/calendar/demo/lunar.tsx +++ b/components/calendar/demo/lunar.tsx @@ -1,11 +1,11 @@ -import dayjs from 'dayjs'; -import type { Dayjs } from 'dayjs'; import React from 'react'; -import { Lunar, HolidayUtil } from 'lunar-typescript'; -import { createStyles } from 'antd-style'; -import classNames from 'classnames'; import { Calendar, Col, Radio, Row, Select } from 'antd'; import type { CalendarProps } from 'antd'; +import { createStyles } from 'antd-style'; +import classNames from 'classnames'; +import dayjs from 'dayjs'; +import type { Dayjs } from 'dayjs'; +import { HolidayUtil, Lunar } from 'lunar-typescript'; const useStyle = createStyles(({ token, css, cx }) => { const lunar = css` @@ -24,15 +24,15 @@ const useStyle = createStyles(({ token, css, cx }) => { &:before { content: ''; position: absolute; - left: 0; - right: 0; + inset-inline-start: 0; + inset-inline-end: 0; top: 0; bottom: 0; margin: auto; max-width: 40px; max-height: 40px; background: transparent; - transition: background 300ms; + transition: background-color 300ms; border-radius: ${token.borderRadiusOuter}px; border: 1px solid transparent; box-sizing: border-box; @@ -82,6 +82,12 @@ const useStyle = createStyles(({ token, css, cx }) => { opacity: 0.8; } `, + weekend: css` + color: ${token.colorError}; + &.gray { + opacity: 0.4; + } + `, }; }); @@ -89,9 +95,11 @@ const App: React.FC = () => { const { styles } = useStyle({ test: true }); const [selectDate, setSelectDate] = React.useState<Dayjs>(dayjs()); + const [panelDateDate, setPanelDate] = React.useState<Dayjs>(dayjs()); const onPanelChange = (value: Dayjs, mode: CalendarProps<Dayjs>['mode']) => { console.log(value.format('YYYY-MM-DD'), mode); + setPanelDate(value); }; const onDateChange: CalendarProps<Dayjs>['onSelect'] = (value, selectInfo) => { @@ -104,6 +112,7 @@ const App: React.FC = () => { const d = Lunar.fromDate(date.toDate()); const lunar = d.getDayInChinese(); const solarTerm = d.getJieQi(); + const isWeekend = date.day() === 6 || date.day() === 0; const h = HolidayUtil.getHoliday(date.get('year'), date.get('month') + 1, date.get('date')); const displayHoliday = h?.getTarget() === h?.getDay() ? h?.getName() : undefined; if (info.type === 'date') { @@ -115,7 +124,14 @@ const App: React.FC = () => { }), children: ( <div className={styles.text}> - {date.get('date')} + <span + className={classNames({ + [styles.weekend]: isWeekend, + gray: !panelDateDate.isSame(date, 'month'), + })} + > + {date.get('date')} + </span> {info.type === 'date' && ( <div className={styles.lunar}>{displayHoliday || solarTerm || lunar}</div> )} @@ -193,7 +209,7 @@ const App: React.FC = () => { <Col> <Select size="small" - dropdownMatchSelectWidth={false} + popupMatchSelectWidth={false} className="my-year-select" value={year} options={options} @@ -206,7 +222,7 @@ const App: React.FC = () => { <Col> <Select size="small" - dropdownMatchSelectWidth={false} + popupMatchSelectWidth={false} value={month} options={monthOptions} onChange={(newMonth) => { diff --git a/components/calendar/demo/notice-calendar.tsx b/components/calendar/demo/notice-calendar.tsx index fbf6aa24bddb..d661a126bfc1 100644 --- a/components/calendar/demo/notice-calendar.tsx +++ b/components/calendar/demo/notice-calendar.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import type { Dayjs } from 'dayjs'; import type { BadgeProps, CalendarProps } from 'antd'; import { Badge, Calendar } from 'antd'; +import type { Dayjs } from 'dayjs'; const getListData = (value: Dayjs) => { - let listData; + let listData: { type: string; content: string }[] = []; // Specify the type of listData switch (value.date()) { case 8: listData = [ diff --git a/components/calendar/demo/select.tsx b/components/calendar/demo/select.tsx index 117632be2542..25d90bd5d619 100644 --- a/components/calendar/demo/select.tsx +++ b/components/calendar/demo/select.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; +import { Alert, Calendar } from 'antd'; import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; -import { Alert, Calendar } from 'antd'; const App: React.FC = () => { const [value, setValue] = useState(() => dayjs('2017-01-25')); diff --git a/components/calendar/generateCalendar.tsx b/components/calendar/generateCalendar.tsx index 1202acf85402..cb47428d2027 100644 --- a/components/calendar/generateCalendar.tsx +++ b/components/calendar/generateCalendar.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import classNames from 'classnames'; +import type { BasePickerPanelProps as RcBasePickerPanelProps } from 'rc-picker'; import { PickerPanel as RCPickerPanel } from 'rc-picker'; import type { GenerateConfig } from 'rc-picker/lib/generate'; import type { CellRenderInfo } from 'rc-picker/lib/interface'; @@ -53,24 +54,23 @@ export interface CalendarProps<DateType> { onSelect?: (date: DateType, selectInfo: SelectInfo) => void; } -function generateCalendar<DateType extends AnyObject>(generateConfig: GenerateConfig<DateType>) { - function isSameYear(date1: DateType, date2: DateType) { - return date1 && date2 && generateConfig.getYear(date1) === generateConfig.getYear(date2); - } +const isSameYear = <T extends AnyObject>(date1: T, date2: T, config: GenerateConfig<T>) => { + const { getYear } = config; + return date1 && date2 && getYear(date1) === getYear(date2); +}; - function isSameMonth(date1: DateType, date2: DateType) { - return ( - isSameYear(date1, date2) && generateConfig.getMonth(date1) === generateConfig.getMonth(date2) - ); - } +const isSameMonth = <T extends AnyObject>(date1: T, date2: T, config: GenerateConfig<T>) => { + const { getMonth } = config; + return isSameYear(date1, date2, config) && getMonth(date1) === getMonth(date2); +}; - function isSameDate(date1: DateType, date2: DateType) { - return ( - isSameMonth(date1, date2) && generateConfig.getDate(date1) === generateConfig.getDate(date2) - ); - } +const isSameDate = <T extends AnyObject>(date1: T, date2: T, config: GenerateConfig<T>) => { + const { getDate } = config; + return isSameMonth(date1, date2, config) && getDate(date1) === getDate(date2); +}; - const Calendar = (props: CalendarProps<DateType>) => { +const generateCalendar = <DateType extends AnyObject>(generateConfig: GenerateConfig<DateType>) => { + const Calendar: React.FC<Readonly<CalendarProps<DateType>>> = (props) => { const { prefixCls: customizePrefixCls, className, @@ -148,11 +148,11 @@ function generateCalendar<DateType extends AnyObject>(generateConfig: GenerateCo const triggerChange = (date: DateType) => { setMergedValue(date); - if (!isSameDate(date, mergedValue)) { + if (!isSameDate(date, mergedValue, generateConfig)) { // Trigger when month panel switch month if ( - (panelMode === 'date' && !isSameMonth(date, mergedValue)) || - (panelMode === 'month' && !isSameYear(date, mergedValue)) + (panelMode === 'date' && !isSameMonth(date, mergedValue, generateConfig)) || + (panelMode === 'month' && !isSameYear(date, mergedValue, generateConfig)) ) { triggerPanelChange(date, mergedMode); } @@ -172,20 +172,6 @@ function generateCalendar<DateType extends AnyObject>(generateConfig: GenerateCo onSelect?.(date, { source }); }; - // ====================== Locale ====================== - const getDefaultLocale = () => { - const { locale } = props; - const result = { - ...enUS, - ...locale, - }; - result.lang = { - ...result.lang, - ...(locale || {}).lang, - }; - return result; - }; - // ====================== Render ====================== const dateRender = React.useCallback( (date: DateType, info: CellRenderInfo<DateType>): React.ReactNode => { @@ -199,14 +185,14 @@ function generateCalendar<DateType extends AnyObject>(generateConfig: GenerateCo return ( <div className={classNames(`${prefixCls}-cell-inner`, `${calendarPrefixCls}-date`, { - [`${calendarPrefixCls}-date-today`]: isSameDate(today, date), + [`${calendarPrefixCls}-date-today`]: isSameDate(today, date, generateConfig), })} > <div className={`${calendarPrefixCls}-date-value`}> {String(generateConfig.getDate(date)).padStart(2, '0')} </div> <div className={`${calendarPrefixCls}-date-content`}> - {cellRender ? cellRender(date, info) : dateCellRender && dateCellRender(date)} + {cellRender ? cellRender(date, info) : dateCellRender?.(date)} </div> </div> ); @@ -229,14 +215,14 @@ function generateCalendar<DateType extends AnyObject>(generateConfig: GenerateCo return ( <div className={classNames(`${prefixCls}-cell-inner`, `${calendarPrefixCls}-date`, { - [`${calendarPrefixCls}-date-today`]: isSameMonth(today, date), + [`${calendarPrefixCls}-date-today`]: isSameMonth(today, date, generateConfig), })} > <div className={`${calendarPrefixCls}-date-value`}> {months[generateConfig.getMonth(date)]} </div> <div className={`${calendarPrefixCls}-date-content`}> - {cellRender ? cellRender(date, info) : monthCellRender && monthCellRender(date)} + {cellRender ? cellRender(date, info) : monthCellRender?.(date)} </div> </div> ); @@ -244,9 +230,11 @@ function generateCalendar<DateType extends AnyObject>(generateConfig: GenerateCo [monthFullCellRender, monthCellRender, cellRender, fullCellRender], ); - const [contextLocale] = useLocale('Calendar', getDefaultLocale); + const [contextLocale] = useLocale('Calendar', enUS); - const mergedCellRender = (current: DateType, info: CellRenderInfo<DateType>) => { + const locale = { ...contextLocale, ...props.locale! }; + + const mergedCellRender: RcBasePickerPanelProps['cellRender'] = (current, info) => { if (info.type === 'date') { return dateRender(current, info); } @@ -254,7 +242,7 @@ function generateCalendar<DateType extends AnyObject>(generateConfig: GenerateCo if (info.type === 'month') { return monthRender(current, { ...info, - locale: contextLocale?.lang, + locale: locale?.lang, }); } }; @@ -292,7 +280,7 @@ function generateCalendar<DateType extends AnyObject>(generateConfig: GenerateCo generateConfig={generateConfig} mode={mergedMode} fullscreen={fullscreen} - locale={contextLocale?.lang} + locale={locale?.lang} validRange={validRange} onChange={onInternalSelect} onModeChange={triggerModeChange} @@ -301,7 +289,7 @@ function generateCalendar<DateType extends AnyObject>(generateConfig: GenerateCo <RCPickerPanel value={mergedValue} prefixCls={prefixCls} - locale={contextLocale?.lang} + locale={locale?.lang} generateConfig={generateConfig} cellRender={mergedCellRender} onSelect={(nextDate) => { @@ -321,6 +309,6 @@ function generateCalendar<DateType extends AnyObject>(generateConfig: GenerateCo } return Calendar; -} +}; export default generateCalendar; diff --git a/components/calendar/index.en-US.md b/components/calendar/index.en-US.md index 4480bc7fffb7..a5e88b28094f 100644 --- a/components/calendar/index.en-US.md +++ b/components/calendar/index.en-US.md @@ -2,12 +2,11 @@ category: Components group: Data Display title: Calendar +description: A container that displays data in calendar form. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*nF6_To7pDSAAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAAAAAAAAAAAADrJ8AQ/original --- -Container for displaying data in calendar form. - ## When To Use When data is in the form of dates, such as schedules, timetables, prices calendar, lunar calendar. This component also supports Year/Month switch. @@ -46,7 +45,9 @@ Common props ref:[Common props](/docs/react/common-props) | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | | dateCellRender | Customize the display of the date cell, the returned content will be appended to the cell | function(date: Dayjs): ReactNode | - | | +| cellRender | Customize cell content | function(current: dayjs, today: dayjs, info: { originNode: React.ReactElement,today: DateType, range?: 'start' \| 'end', type: PanelMode, locale?: Locale, subType?: 'hour' \| 'minute' \| 'second' \| 'meridiem' }) => React.ReactNode | - | 5.4.0 | | dateFullCellRender | Customize the display of the date cell, the returned content will override the cell | function(date: Dayjs): ReactNode | - | | +| fullCellRender | Customize cell content | function(current: dayjs, today: dayjs, info: { originNode: React.ReactElement,today: DateType, range?: 'start' \| 'end', type: PanelMode, locale?: Locale, subType?: 'hour' \| 'minute' \| 'second' \| 'meridiem' }) => React.ReactNode | - | 5.4.0 | | defaultValue | The date selected by default | [dayjs](https://day.js.org/) | - | | | disabledDate | Function that specifies the dates that cannot be selected, `currentDate` is same dayjs object as `value` prop which you shouldn't mutate it](https://github.com/ant-design/ant-design/issues/30987) | (currentDate: Dayjs) => boolean | - | | | fullscreen | Whether to display in full-screen | boolean | true | | diff --git a/components/calendar/index.ts b/components/calendar/index.tsx similarity index 99% rename from components/calendar/index.ts rename to components/calendar/index.tsx index 9933d970d337..d89d8cec86d4 100644 --- a/components/calendar/index.ts +++ b/components/calendar/index.tsx @@ -1,5 +1,6 @@ import type { Dayjs } from 'dayjs'; import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs'; + import type { CalendarProps } from './generateCalendar'; import generateCalendar from './generateCalendar'; diff --git a/components/calendar/index.zh-CN.md b/components/calendar/index.zh-CN.md index dbda79cb72e9..d48f3ec1a9a7 100644 --- a/components/calendar/index.zh-CN.md +++ b/components/calendar/index.zh-CN.md @@ -1,14 +1,13 @@ --- category: Components group: 数据展示 -subtitle: 日历 title: Calendar +subtitle: 日历 +description: 按照日历形式展示数据的容器。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*nF6_To7pDSAAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAAAAAAAAAAAADrJ8AQ/original --- -按照日历形式展示数据的容器。 - ## 何时使用 当数据是日期或按照日期划分时,例如日程、课表、价格日历等,农历等。目前支持年/月切换。 @@ -57,9 +56,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA | locale | 国际化配置 | object | [(默认配置)](https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json) | | | mode | 初始模式 | `month` \| `year` | `month` | | | monthCellRender | 自定义渲染月单元格,返回内容会被追加到单元格,>= 5.4.0 请用 `cellRender` | function(date: Dayjs): ReactNode | - | < 5.4.0 | -| cellRender | 自定义单元格的内容 | function(current: dayjs, today: dayjs, info: { originNode: React.ReactElement,today: DateType, range?: 'start' \| 'end', type: PanelMode, locale?: Locale, subType?: 'hour' \| 'minute' \| 'second' \| 'meridiem' }) => React.ReactNode | - | 5.4.0 | | monthFullCellRender | 自定义渲染月单元格,返回内容覆盖单元格,>= 5.4.0 请用 `fullCellRender` | function(date: Dayjs): ReactNode | - | < 5.4.0 | -| fullCellRender | 自定义单元格的内容 | function(current: dayjs, today: dayjs, info: { originNode: React.ReactElement,today: DateType, range?: 'start' \| 'end', type: PanelMode, locale?: Locale, subType?: 'hour' \| 'minute' \| 'second' \| 'meridiem' }) => React.ReactNode | - | 5.4.0 | | validRange | 设置可以显示的日期 | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | - | | | value | 展示日期 | [dayjs](https://day.js.org/) | - | | | onChange | 日期变化回调 | function(date: Dayjs) | - | | diff --git a/components/calendar/locale/uz_UZ.ts b/components/calendar/locale/uz_UZ.ts new file mode 100644 index 000000000000..8a026a01cec0 --- /dev/null +++ b/components/calendar/locale/uz_UZ.ts @@ -0,0 +1,3 @@ +import uzUZ from '../../date-picker/locale/uz_UZ'; + +export default uzUZ; diff --git a/components/calendar/style/index.ts b/components/calendar/style/index.ts index e4d126a49c25..fdeec4937956 100644 --- a/components/calendar/style/index.ts +++ b/components/calendar/style/index.ts @@ -16,17 +16,17 @@ export interface ComponentToken { * @desc 年选择器宽度 * @descEN Width of year select */ - yearControlWidth: number; + yearControlWidth: number | string; /** * @desc 月选择器宽度 * @descEN Width of month select */ - monthControlWidth: number; + monthControlWidth: number | string; /** * @desc 迷你日历内容高度 * @descEN Height of mini calendar content */ - miniContentHeight: number; + miniContentHeight: number | string; /** * @desc 完整日历背景色 * @descEN Background color of full calendar @@ -45,10 +45,26 @@ export interface ComponentToken { } interface CalendarToken extends FullToken<'Calendar'>, PickerPanelToken, PanelComponentToken { + /** + * @desc 日历类名 + * @descEN Calendar class name + */ calendarCls: string; - dateValueHeight: number; - weekHeight: number; - dateContentHeight: number; + /** + * @desc 日期值高度 + * @descEN Date value height + */ + dateValueHeight: number | string; + /** + * @desc 周高度 + * @descEN Week height + */ + weekHeight: number | string; + /** + * @desc 日期内容高度 + * @descEN Date content height + */ + dateContentHeight: number | string; } export const genCalendarStyles = (token: CalendarToken): CSSObject => { @@ -107,7 +123,7 @@ export const genCalendarStyles = (token: CalendarToken): CSSObject => { th: { height: 'auto', padding: 0, - lineHeight: `${unit(token.weekHeight)}`, + lineHeight: unit(token.weekHeight), }, }, [`${componentCls}-cell::before`]: { @@ -129,7 +145,7 @@ export const genCalendarStyles = (token: CalendarToken): CSSObject => { height: 'auto', paddingInlineEnd: token.paddingSM, paddingBottom: token.paddingXXS, - lineHeight: `${unit(token.weekHeight)}`, + lineHeight: unit(token.weekHeight), }, }, }, @@ -170,7 +186,7 @@ export const genCalendarStyles = (token: CalendarToken): CSSObject => { borderRadius: 0, transition: `background ${token.motionDurationSlow}`, '&-value': { - lineHeight: `${unit(token.dateValueHeight)}`, + lineHeight: unit(token.dateValueHeight), transition: `color ${token.motionDurationSlow}`, }, '&-content': { @@ -191,7 +207,7 @@ export const genCalendarStyles = (token: CalendarToken): CSSObject => { }, }, [`@media only screen and (max-width: ${unit(token.screenXS)}) `]: { - [`${calendarCls}`]: { + [calendarCls]: { [`${calendarCls}-header`]: { display: 'block', [`${calendarCls}-year-select`]: { diff --git a/components/card/Card.tsx b/components/card/Card.tsx index a4edc5566375..73a1f27ff4ee 100644 --- a/components/card/Card.tsx +++ b/components/card/Card.tsx @@ -140,15 +140,15 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => { const moduleClass = (moduleName: CardClassNamesModule) => classNames(card?.classNames?.[moduleName], customClassNames?.[moduleName]); - const moduleStyle = (moduleName: CardStylesModule) => ({ + const moduleStyle = (moduleName: CardStylesModule): React.CSSProperties => ({ ...card?.styles?.[moduleName], ...customStyles?.[moduleName], }); const isContainGrid = React.useMemo<boolean>(() => { let containGrid = false; - React.Children.forEach(children, (element: JSX.Element) => { - if (element && element.type && element.type === Grid) { + React.Children.forEach(children as React.ReactElement, (element: JSX.Element) => { + if (element?.type === Grid) { containGrid = true; } }); @@ -229,14 +229,13 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => { ); const actionClasses = classNames(`${prefixCls}-actions`, moduleClass('actions')); - const actionDom = - actions && actions.length ? ( - <ActionNode - actionClasses={actionClasses} - actionStyle={moduleStyle('actions')} - actions={actions} - /> - ) : null; + const actionDom = actions?.length ? ( + <ActionNode + actionClasses={actionClasses} + actionStyle={moduleStyle('actions')} + actions={actions} + /> + ) : null; const divProps = omit(others, ['onTabChange']); @@ -248,7 +247,7 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => { [`${prefixCls}-bordered`]: bordered, [`${prefixCls}-hoverable`]: hoverable, [`${prefixCls}-contain-grid`]: isContainGrid, - [`${prefixCls}-contain-tabs`]: tabList && tabList.length, + [`${prefixCls}-contain-tabs`]: tabList?.length, [`${prefixCls}-${mergedSize}`]: mergedSize, [`${prefixCls}-type-${type}`]: !!type, [`${prefixCls}-rtl`]: direction === 'rtl', diff --git a/components/card/Grid.tsx b/components/card/Grid.tsx index 5b4f87ee8eda..b4e0b32632f5 100644 --- a/components/card/Grid.tsx +++ b/components/card/Grid.tsx @@ -1,5 +1,6 @@ -import classNames from 'classnames'; import * as React from 'react'; +import classNames from 'classnames'; + import { ConfigContext } from '../config-provider'; import type { ConfigConsumerProps } from '../config-provider'; diff --git a/components/card/Meta.tsx b/components/card/Meta.tsx index 55107619ca50..adbf4a4763a0 100644 --- a/components/card/Meta.tsx +++ b/components/card/Meta.tsx @@ -1,5 +1,6 @@ -import classNames from 'classnames'; import * as React from 'react'; +import classNames from 'classnames'; + import type { ConfigConsumerProps } from '../config-provider'; import { ConfigContext } from '../config-provider'; diff --git a/components/card/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/card/__tests__/__snapshots__/demo-extend.test.ts.snap index 1fd4f89b29d3..b8a7b4b5a61e 100644 --- a/components/card/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/card/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -703,7 +703,9 @@ exports[`renders components/card/demo/inner.tsx extend context correctly 1`] = ` exports[`renders components/card/demo/inner.tsx extend context correctly 2`] = `[]`; exports[`renders components/card/demo/loading.tsx extend context correctly 1`] = ` -Array [ +<div + class="ant-flex ant-flex-align-start ant-flex-gap-middle ant-flex-vertical" +> <button aria-checked="false" class="ant-switch" @@ -723,10 +725,10 @@ Array [ class="ant-switch-inner-unchecked" /> </span> - </button>, + </button> <div class="ant-card ant-card-loading ant-card-bordered" - style="width: 300px; margin-top: 16px;" + style="min-width: 300px;" > <div class="ant-card-body" @@ -750,36 +752,108 @@ Array [ </div> </div> </div> - </div>, + <ul + class="ant-card-actions" + > + <li + style="width: 33.333333333333336%;" + > + <span> + <span + aria-label="edit" + class="anticon anticon-edit" + role="img" + > + <svg + aria-hidden="true" + data-icon="edit" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z" + /> + </svg> + </span> + </span> + </li> + <li + style="width: 33.333333333333336%;" + > + <span> + <span + aria-label="setting" + class="anticon anticon-setting" + role="img" + > + <svg + aria-hidden="true" + data-icon="setting" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z" + /> + </svg> + </span> + </span> + </li> + <li + style="width: 33.333333333333336%;" + > + <span> + <span + aria-label="ellipsis" + class="anticon anticon-ellipsis" + role="img" + > + <svg + aria-hidden="true" + data-icon="ellipsis" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z" + /> + </svg> + </span> + </span> + </li> + </ul> + </div> <div - class="ant-card ant-card-bordered" - style="width: 300px; margin-top: 16px;" + class="ant-card ant-card-loading ant-card-bordered" + style="min-width: 300px;" > <div class="ant-card-body" > <div - class="ant-skeleton ant-skeleton-with-avatar ant-skeleton-active" + class="ant-skeleton ant-skeleton-active" > - <div - class="ant-skeleton-header" - > - <span - class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle" - /> - </div> <div class="ant-skeleton-content" > - <h3 - class="ant-skeleton-title" - style="width: 50%;" - /> <ul class="ant-skeleton-paragraph" > <li /> <li /> + <li /> + <li + style="width: 61%;" + /> </ul> </div> </div> @@ -792,13 +866,13 @@ Array [ > <span> <span - aria-label="setting" - class="anticon anticon-setting" + aria-label="edit" + class="anticon anticon-edit" role="img" > <svg aria-hidden="true" - data-icon="setting" + data-icon="edit" fill="currentColor" focusable="false" height="1em" @@ -806,7 +880,7 @@ Array [ width="1em" > <path - d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z" + d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z" /> </svg> </span> @@ -817,13 +891,13 @@ Array [ > <span> <span - aria-label="edit" - class="anticon anticon-edit" + aria-label="setting" + class="anticon anticon-setting" role="img" > <svg aria-hidden="true" - data-icon="edit" + data-icon="setting" fill="currentColor" focusable="false" height="1em" @@ -831,7 +905,7 @@ Array [ width="1em" > <path - d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z" + d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z" /> </svg> </span> @@ -863,8 +937,8 @@ Array [ </span> </li> </ul> - </div>, -] + </div> +</div> `; exports[`renders components/card/demo/loading.tsx extend context correctly 2`] = `[]`; diff --git a/components/card/__tests__/__snapshots__/demo.test.ts.snap b/components/card/__tests__/__snapshots__/demo.test.ts.snap index 6af0c384b323..ac2919c40ee2 100644 --- a/components/card/__tests__/__snapshots__/demo.test.ts.snap +++ b/components/card/__tests__/__snapshots__/demo.test.ts.snap @@ -666,7 +666,9 @@ exports[`renders components/card/demo/inner.tsx correctly 1`] = ` `; exports[`renders components/card/demo/loading.tsx correctly 1`] = ` -Array [ +<div + class="ant-flex ant-flex-align-start ant-flex-gap-middle ant-flex-vertical" +> <button aria-checked="false" class="ant-switch" @@ -686,10 +688,10 @@ Array [ class="ant-switch-inner-unchecked" /> </span> - </button>, + </button> <div class="ant-card ant-card-loading ant-card-bordered" - style="width:300px;margin-top:16px" + style="min-width:300px" > <div class="ant-card-body" @@ -713,36 +715,108 @@ Array [ </div> </div> </div> - </div>, + <ul + class="ant-card-actions" + > + <li + style="width:33.333333333333336%" + > + <span> + <span + aria-label="edit" + class="anticon anticon-edit" + role="img" + > + <svg + aria-hidden="true" + data-icon="edit" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z" + /> + </svg> + </span> + </span> + </li> + <li + style="width:33.333333333333336%" + > + <span> + <span + aria-label="setting" + class="anticon anticon-setting" + role="img" + > + <svg + aria-hidden="true" + data-icon="setting" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z" + /> + </svg> + </span> + </span> + </li> + <li + style="width:33.333333333333336%" + > + <span> + <span + aria-label="ellipsis" + class="anticon anticon-ellipsis" + role="img" + > + <svg + aria-hidden="true" + data-icon="ellipsis" + fill="currentColor" + focusable="false" + height="1em" + viewBox="64 64 896 896" + width="1em" + > + <path + d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z" + /> + </svg> + </span> + </span> + </li> + </ul> + </div> <div - class="ant-card ant-card-bordered" - style="width:300px;margin-top:16px" + class="ant-card ant-card-loading ant-card-bordered" + style="min-width:300px" > <div class="ant-card-body" > <div - class="ant-skeleton ant-skeleton-with-avatar ant-skeleton-active" + class="ant-skeleton ant-skeleton-active" > - <div - class="ant-skeleton-header" - > - <span - class="ant-skeleton-avatar ant-skeleton-avatar-lg ant-skeleton-avatar-circle" - /> - </div> <div class="ant-skeleton-content" > - <h3 - class="ant-skeleton-title" - style="width:50%" - /> <ul class="ant-skeleton-paragraph" > <li /> <li /> + <li /> + <li + style="width:61%" + /> </ul> </div> </div> @@ -755,13 +829,13 @@ Array [ > <span> <span - aria-label="setting" - class="anticon anticon-setting" + aria-label="edit" + class="anticon anticon-edit" role="img" > <svg aria-hidden="true" - data-icon="setting" + data-icon="edit" fill="currentColor" focusable="false" height="1em" @@ -769,7 +843,7 @@ Array [ width="1em" > <path - d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z" + d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z" /> </svg> </span> @@ -780,13 +854,13 @@ Array [ > <span> <span - aria-label="edit" - class="anticon anticon-edit" + aria-label="setting" + class="anticon anticon-setting" role="img" > <svg aria-hidden="true" - data-icon="edit" + data-icon="setting" fill="currentColor" focusable="false" height="1em" @@ -794,7 +868,7 @@ Array [ width="1em" > <path - d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z" + d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z" /> </svg> </span> @@ -826,8 +900,8 @@ Array [ </span> </li> </ul> - </div>, -] + </div> +</div> `; exports[`renders components/card/demo/meta.tsx correctly 1`] = ` diff --git a/components/card/__tests__/index.test.tsx b/components/card/__tests__/index.test.tsx index 79a94fe5b3e7..71fdfdafde39 100644 --- a/components/card/__tests__/index.test.tsx +++ b/components/card/__tests__/index.test.tsx @@ -1,6 +1,8 @@ import '@testing-library/jest-dom'; -import userEvent from '@testing-library/user-event'; + import React from 'react'; +import userEvent from '@testing-library/user-event'; + import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; import { render, screen } from '../../../tests/utils'; diff --git a/components/card/__tests__/type.test.tsx b/components/card/__tests__/type.test.tsx index 19eae9739fe7..ac499feab979 100644 --- a/components/card/__tests__/type.test.tsx +++ b/components/card/__tests__/type.test.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; + import Card from '../index'; describe('Card.typescript', () => { diff --git a/components/card/demo/_semantic.tsx b/components/card/demo/_semantic.tsx new file mode 100644 index 000000000000..4f23af2e4e0d --- /dev/null +++ b/components/card/demo/_semantic.tsx @@ -0,0 +1,80 @@ +import React from 'react'; + +import SemanticPreview from '../../../.dumi/components/SemanticPreview'; +import useLocale from '../../../.dumi/hooks/useLocale'; + +import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons'; +import { Avatar, Card } from 'antd'; + +const { Meta } = Card; + +const locales = { + cn: { + header: '设置卡片头部区域', + body: '设置卡片内容区域', + extra: '设置卡片右上角的操作区域', + title: '设置卡片标题', + actions: '设置卡片底部操作组', + cover: '设置标题封面', + }, + en: { + header: 'set `header` of card', + body: 'set `body` of card', + extra: 'set `extra` of card', + title: 'set `title` of card', + actions: 'set `actions` of card', + cover: 'set `cover` of card', + }, +}; + +const BlockCard: React.FC<React.PropsWithChildren> = (props) => { + const divRef = React.useRef<HTMLDivElement>(null); + + return ( + <div ref={divRef} style={{ position: 'absolute', inset: 0 }}> + <Card + {...props} + title="Card title" + extra="More" + style={{ width: 300 }} + cover={ + <img + alt="example" + src="https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png" + /> + } + actions={[ + <SettingOutlined key="setting" />, + <EditOutlined key="edit" />, + <EllipsisOutlined key="ellipsis" />, + ]} + /> + </div> + ); +}; + +const App: React.FC = () => { + const [locale] = useLocale(locales); + return ( + <SemanticPreview + semantics={[ + { name: 'header', desc: locale.header, version: '5.14.0' }, + { name: 'body', desc: locale.body, version: '5.14.0' }, + { name: 'extra', desc: locale.extra, version: '5.14.0' }, + { name: 'title', desc: locale.title, version: '5.14.0' }, + { name: 'actions', desc: locale.actions, version: '5.14.0' }, + { name: 'cover', desc: locale.cover, version: '5.14.0' }, + ]} + > + <BlockCard> + <Meta + avatar={<Avatar src="https://api.dicebear.com/7.x/miniavs/svg?seed=8" />} + title="Card Meta title" + description="This is the description" + /> + </BlockCard> + </SemanticPreview> + ); +}; + +export default App; diff --git a/components/card/demo/component-token.tsx b/components/card/demo/component-token.tsx index ad0f92414436..1c15e239f00a 100644 --- a/components/card/demo/component-token.tsx +++ b/components/card/demo/component-token.tsx @@ -1,5 +1,5 @@ -import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons'; import React from 'react'; +import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons'; import { Card, ConfigProvider } from 'antd'; export default () => ( diff --git a/components/card/demo/loading.tsx b/components/card/demo/loading.tsx index 731dd793646b..01c23cbacbf8 100644 --- a/components/card/demo/loading.tsx +++ b/components/card/demo/loading.tsx @@ -1,43 +1,43 @@ -import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons'; import React, { useState } from 'react'; -import { Avatar, Card, Skeleton, Switch } from 'antd'; +import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons'; +import { Avatar, Card, Flex, Switch } from 'antd'; -const { Meta } = Card; +const actions: React.ReactNode[] = [ + <EditOutlined key="edit" />, + <SettingOutlined key="setting" />, + <EllipsisOutlined key="ellipsis" />, +]; const App: React.FC = () => { - const [loading, setLoading] = useState(true); - - const onChange = (checked: boolean) => { - setLoading(!checked); - }; - + const [loading, setLoading] = useState<boolean>(true); return ( - <> - <Switch checked={!loading} onChange={onChange} /> - <Card style={{ width: 300, marginTop: 16 }} loading={loading}> - <Meta + <Flex gap="middle" align="start" vertical> + <Switch checked={!loading} onChange={(checked) => setLoading(!checked)} /> + <Card loading={loading} actions={actions} style={{ minWidth: 300 }}> + <Card.Meta avatar={<Avatar src="https://api.dicebear.com/7.x/miniavs/svg?seed=1" />} title="Card title" - description="This is the description" + description={ + <> + <p>This is the description</p> + <p>This is the description</p> + </> + } /> </Card> - <Card - style={{ width: 300, marginTop: 16 }} - actions={[ - <SettingOutlined key="setting" />, - <EditOutlined key="edit" />, - <EllipsisOutlined key="ellipsis" />, - ]} - > - <Skeleton loading={loading} avatar active> - <Meta - avatar={<Avatar src="https://api.dicebear.com/7.x/miniavs/svg?seed=2" />} - title="Card title" - description="This is the description" - /> - </Skeleton> + <Card loading={loading} actions={actions} style={{ minWidth: 300 }}> + <Card.Meta + avatar={<Avatar src="https://api.dicebear.com/7.x/miniavs/svg?seed=2" />} + title="Card title" + description={ + <> + <p>This is the description</p> + <p>This is the description</p> + </> + } + /> </Card> - </> + </Flex> ); }; diff --git a/components/card/index.en-US.md b/components/card/index.en-US.md index bf119fee300a..722d44c64ba9 100644 --- a/components/card/index.en-US.md +++ b/components/card/index.en-US.md @@ -2,12 +2,11 @@ category: Components group: Data Display title: Card +description: A container for displaying information. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*QXO1SKEdIzYAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5WDvQp_H7LUAAAAAAAAAAAAADrJ8AQ/original --- -Simple rectangular container. - ## When To Use A card can be used to display content related to a single subject. The content can consist of multiple elements of varying types and sizes. @@ -35,15 +34,13 @@ Common props ref:[Common props](/docs/react/common-props) <Card title="Card title">Card content</Card> ``` -### Card - | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | | actions | The action list, shows at the bottom of the Card | Array<ReactNode> | - | | | activeTabKey | Current TabPane's key | string | - | | | bordered | Toggles rendering of the border around the card | boolean | true | | | cover | Card cover | ReactNode | - | | -| defaultActiveTabKey | Initial active TabPane's key, if `activeTabKey` is not set | string | - | | +| defaultActiveTabKey | Initial active TabPane's key, if `activeTabKey` is not set | string | `The key of first tab` | | | extra | Content to render in the top-right corner of the card | ReactNode | - | | | hoverable | Lift up when hovering card | boolean | false | | | loading | Shows a loading indicator while the contents of the card are being fetched | boolean | false | | @@ -53,8 +50,8 @@ Common props ref:[Common props](/docs/react/common-props) | tabProps | [Tabs](/components/tabs/#tabs) | - | - | | | title | Card title | ReactNode | - | | | type | Card style type, can be set to `inner` or not set | string | - | | -| classNames | Config Card build-in module's className | Record<SemanticDOM, string> | - | 5.14.0 | -| styles | Config Card build-in module's style | Record<SemanticDOM, string> | - | 5.14.0 | +| classNames | Config Card build-in module's className | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.14.0 | +| styles | Config Card build-in module's style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.14.0 | | onTabChange | Callback when tab is switched | (key) => void | - | | ### Card.Grid @@ -75,16 +72,9 @@ Common props ref:[Common props](/docs/react/common-props) | style | The style object of container | CSSProperties | - | | | title | Title content | ReactNode | - | | -### `styles` 和 `classNames` attribute +## Semantic DOM -| 名称 | 说明 | 版本 | -| ------- | --------------------- | ------ | -| header | set `header` of card | 5.14.0 | -| body | set `body` of card | 5.14.0 | -| extra | set `extra` of card | 5.14.0 | -| title | set `title` of card | 5.14.0 | -| actions | set `actions` of card | 5.14.0 | -| cover | set `cover` of card | 5.14.0 | +<code src="./demo/_semantic.tsx" simplify="true"></code> ## Design Token diff --git a/components/card/index.ts b/components/card/index.tsx similarity index 100% rename from components/card/index.ts rename to components/card/index.tsx diff --git a/components/card/index.zh-CN.md b/components/card/index.zh-CN.md index e13cfe3556fe..60e9ad9c0e23 100644 --- a/components/card/index.zh-CN.md +++ b/components/card/index.zh-CN.md @@ -3,12 +3,11 @@ category: Components group: 数据展示 title: Card subtitle: 卡片 +description: 通用卡片容器。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*QXO1SKEdIzYAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5WDvQp_H7LUAAAAAAAAAAAAADrJ8AQ/original --- -通用卡片容器。 - ## 何时使用 最基础的卡片容器,可承载文字、列表、图片、段落,常用于后台概览页面。 @@ -36,26 +35,24 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5WDvQp_H7LUAAA <Card title="卡片标题">卡片内容</Card> ``` -### Card - | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | | actions | 卡片操作组,位置在卡片底部 | Array<ReactNode> | - | | | activeTabKey | 当前激活页签的 key | string | - | | | bordered | 是否有边框 | boolean | true | | | cover | 卡片封面 | ReactNode | - | | -| defaultActiveTabKey | 初始化选中页签的 key,如果没有设置 activeTabKey | string | `第一个页签` | | +| defaultActiveTabKey | 初始化选中页签的 key,如果没有设置 activeTabKey | string | `第一个页签的 key` | | | extra | 卡片右上角的操作区域 | ReactNode | - | | | hoverable | 鼠标移过时可浮起 | boolean | false | | | loading | 当卡片内容还在加载中时,可以用 loading 展示一个占位 | boolean | false | | | size | card 的尺寸 | `default` \| `small` | `default` | | | tabBarExtraContent | tab bar 上额外的元素 | ReactNode | - | | -| tabList | 页签标题列表 | [TabItemType](/components/tabs#tabitemtype)[] | - | | +| tabList | 页签标题列表 | [TabItemType](/components/tabs-cn#tabitemtype)[] | - | | | tabProps | [Tabs](/components/tabs-cn#tabs) | - | - | | | title | 卡片标题 | ReactNode | - | | | type | 卡片类型,可设置为 `inner` 或 不设置 | string | - | | -| classNames | 配置卡片内置模块的 className | Record<SemanticDOM, string> | - | 5.14.0 | -| styles | 配置卡片内置模块的 style | Record<SemanticDOM, string> | - | 5.14.0 | +| classNames | 配置卡片内置模块的 className | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.14.0 | +| styles | 配置卡片内置模块的 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.14.0 | | onTabChange | 页签切换的回调 | (key) => void | - | | ### Card.Grid @@ -76,16 +73,9 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*5WDvQp_H7LUAAA | style | 定义容器类名的样式 | CSSProperties | - | | | title | 标题内容 | ReactNode | - | | -### `styles` 和 `classNames` 属性 +## Semantic DOM -| 名称 | 说明 | 版本 | -| ------- | ------------------------ | ------ | -| header | 设置卡片头部区域 | 5.14.0 | -| body | 设置卡片内容区域 | 5.14.0 | -| extra | 设置卡片右上角的操作区域 | 5.14.0 | -| title | 设置卡片标题 | 5.14.0 | -| actions | 设置卡片底部操作组 | 5.14.0 | -| cover | 设置标题封面 | 5.14.0 | +<code src="./demo/_semantic.tsx" simplify="true"></code> ## 主题变量(Design Token) diff --git a/components/card/style/index.ts b/components/card/style/index.ts index 1bf3ddad2b03..3aabdb985dbf 100644 --- a/components/card/style/index.ts +++ b/components/card/style/index.ts @@ -1,4 +1,5 @@ -import { type CSSObject, unit } from '@ant-design/cssinjs'; +import { unit } from '@ant-design/cssinjs'; +import type { CSSObject } from '@ant-design/cssinjs'; import { clearFix, resetComponent, textEllipsis } from '../../style'; import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal'; @@ -14,22 +15,22 @@ export interface ComponentToken { * @desc 卡片头部文字大小 * @descEN Font size of card header */ - headerFontSize: number; + headerFontSize: number | string; /** * @desc 小号卡片头部文字大小 * @descEN Font size of small card header */ - headerFontSizeSM: number; + headerFontSizeSM: number | string; /** * @desc 卡片头部高度 * @descEN Height of card header */ - headerHeight: number; + headerHeight: number | string; /** * @desc 小号卡片头部高度 * @descEN Height of small card header */ - headerHeightSM: number; + headerHeightSM: number | string; /** * @desc 操作区背景色 * @descEN Background color of card actions @@ -53,10 +54,30 @@ export interface ComponentToken { } interface CardToken extends FullToken<'Card'> { + /** + * @desc 卡片阴影 + * @descEN Shadow of card + */ cardShadow: string; + /** + * @desc 卡片头部内边距 + * @descEN Padding of card header + */ cardHeadPadding: number; + /** + * @desc 小号卡片内边距 + * @descEN Padding of small card + */ cardPaddingSM: number; + /** + * @desc 卡片基础内边距 + * @descEN Padding of base card + */ cardPaddingBase: number; + /** + * @desc 卡片操作区图标大小 + * @descEN Size of card actions icon + */ cardActionsIconSize: number; } @@ -272,7 +293,6 @@ const genCardLoadingStyle: GenerateStyle<CardToken> = (token): CSSObject => { // ============================== Basic ============================== const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => { const { - antCls, componentCls, cardShadow, cardHeadPadding, @@ -306,7 +326,7 @@ const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => { [`${componentCls}-body`]: { padding: cardPaddingBase, - borderRadius: ` 0 0 ${unit(token.borderRadiusLG)} ${unit(token.borderRadiusLG)}`, + borderRadius: `0 0 ${unit(token.borderRadiusLG)} ${unit(token.borderRadiusLG)}`, ...clearFix(), }, @@ -316,9 +336,6 @@ const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => { '> *': { display: 'block', width: '100%', - }, - - [`img, img + ${antCls}-image-mask`]: { borderRadius: `${unit(token.borderRadiusLG)} ${unit(token.borderRadiusLG)} 0 0`, }, }, @@ -363,7 +380,7 @@ const genCardStyle: GenerateStyle<CardToken> = (token): CSSObject => { }, [`${componentCls}-contain-tabs`]: { - [`> ${componentCls}-head`]: { + [`> div${componentCls}-head`]: { minHeight: 0, [`${componentCls}-head-title, ${componentCls}-extra`]: { paddingTop: cardHeadPadding, diff --git a/components/carousel/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/carousel/__tests__/__snapshots__/demo-extend.test.ts.snap index b17098232fbd..7986e7292350 100644 --- a/components/carousel/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/carousel/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -1,5 +1,304 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`renders components/carousel/demo/arrows.tsx extend context correctly 1`] = ` +Array [ + <div + class="ant-carousel" + > + <div + class="slick-slider slick-initialized" + dir="ltr" + > + <button + aria-label="prev" + class="slick-arrow slick-prev slick-disabled" + data-role="none" + style="display: block;" + type="button" + /> + <div + class="slick-list" + > + <div + class="slick-track" + style="opacity: 1; transform: translate3d(0px, 0px, 0px);" + > + <div + aria-hidden="false" + class="slick-slide slick-active slick-current" + data-index="0" + style="outline: none; width: 0px;" + tabindex="-1" + > + <div> + <div + style="width: 100%; display: inline-block;" + tabindex="-1" + > + <h3 + style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);" + > + 1 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="1" + style="outline: none; width: 0px;" + tabindex="-1" + > + <div> + <div + style="width: 100%; display: inline-block;" + tabindex="-1" + > + <h3 + style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);" + > + 2 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="2" + style="outline: none; width: 0px;" + tabindex="-1" + > + <div> + <div + style="width: 100%; display: inline-block;" + tabindex="-1" + > + <h3 + style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);" + > + 3 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="3" + style="outline: none; width: 0px;" + tabindex="-1" + > + <div> + <div + style="width: 100%; display: inline-block;" + tabindex="-1" + > + <h3 + style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);" + > + 4 + </h3> + </div> + </div> + </div> + </div> + </div> + <button + aria-label="next" + class="slick-arrow slick-next" + data-role="none" + style="display: block;" + type="button" + /> + <ul + class="slick-dots slick-dots-bottom" + style="display: block;" + > + <li + class="slick-active" + > + <button> + 1 + </button> + </li> + <li + class="" + > + <button> + 2 + </button> + </li> + <li + class="" + > + <button> + 3 + </button> + </li> + <li + class="" + > + <button> + 4 + </button> + </li> + </ul> + </div> + </div>, + <br />, + <div + class="ant-carousel ant-carousel-vertical" + > + <div + class="slick-slider slick-vertical slick-initialized" + dir="ltr" + > + <button + aria-label="prev" + class="slick-arrow slick-prev slick-disabled" + data-role="none" + style="display: block;" + type="button" + /> + <div + class="slick-list" + style="height: 0px;" + > + <div + class="slick-track" + style="opacity: 1; transform: translate3d(0px, 0px, 0px);" + > + <div + aria-hidden="false" + class="slick-slide slick-active slick-current" + data-index="0" + style="outline: none; width: 0px;" + tabindex="-1" + > + <div> + <div + style="width: 100%; display: inline-block;" + tabindex="-1" + > + <h3 + style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);" + > + 1 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="1" + style="outline: none; width: 0px;" + tabindex="-1" + > + <div> + <div + style="width: 100%; display: inline-block;" + tabindex="-1" + > + <h3 + style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);" + > + 2 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="2" + style="outline: none; width: 0px;" + tabindex="-1" + > + <div> + <div + style="width: 100%; display: inline-block;" + tabindex="-1" + > + <h3 + style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);" + > + 3 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="3" + style="outline: none; width: 0px;" + tabindex="-1" + > + <div> + <div + style="width: 100%; display: inline-block;" + tabindex="-1" + > + <h3 + style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);" + > + 4 + </h3> + </div> + </div> + </div> + </div> + </div> + <button + aria-label="next" + class="slick-arrow slick-next" + data-role="none" + style="display: block;" + type="button" + /> + <ul + class="slick-dots slick-dots-left" + style="display: block;" + > + <li + class="slick-active" + > + <button> + 1 + </button> + </li> + <li + class="" + > + <button> + 2 + </button> + </li> + <li + class="" + > + <button> + 3 + </button> + </li> + <li + class="" + > + <button> + 4 + </button> + </li> + </ul> + </div> + </div>, +] +`; + +exports[`renders components/carousel/demo/arrows.tsx extend context correctly 2`] = `[]`; + exports[`renders components/carousel/demo/autoplay.tsx extend context correctly 1`] = ` <div class="ant-carousel" @@ -727,7 +1026,7 @@ exports[`renders components/carousel/demo/fade.tsx extend context correctly 1`] aria-hidden="false" class="slick-slide slick-active slick-current" data-index="0" - style="outline: none; width: 0px; position: relative; left: 0px; opacity: 1; transition: opacity 500ms ease, visibility 500ms ease;" + style="outline: none; width: 0px; position: relative; left: 0px; opacity: 1; z-index: 999; transition: opacity 500ms ease, visibility 500ms ease;" tabindex="-1" > <div> @@ -747,7 +1046,7 @@ exports[`renders components/carousel/demo/fade.tsx extend context correctly 1`] aria-hidden="true" class="slick-slide" data-index="1" - style="outline: none; width: 0px; position: relative; left: 0px; opacity: 0; transition: opacity 500ms ease, visibility 500ms ease;" + style="outline: none; width: 0px; position: relative; left: 0px; opacity: 0; z-index: 998; transition: opacity 500ms ease, visibility 500ms ease;" tabindex="-1" > <div> @@ -767,7 +1066,7 @@ exports[`renders components/carousel/demo/fade.tsx extend context correctly 1`] aria-hidden="true" class="slick-slide" data-index="2" - style="outline: none; width: 0px; position: relative; left: 0px; opacity: 0; transition: opacity 500ms ease, visibility 500ms ease;" + style="outline: none; width: 0px; position: relative; left: 0px; opacity: 0; z-index: 998; transition: opacity 500ms ease, visibility 500ms ease;" tabindex="-1" > <div> @@ -787,7 +1086,7 @@ exports[`renders components/carousel/demo/fade.tsx extend context correctly 1`] aria-hidden="true" class="slick-slide" data-index="3" - style="outline: none; width: 0px; position: relative; left: 0px; opacity: 0; transition: opacity 500ms ease, visibility 500ms ease;" + style="outline: none; width: 0px; position: relative; left: 0px; opacity: 0; z-index: 998; transition: opacity 500ms ease, visibility 500ms ease;" tabindex="-1" > <div> diff --git a/components/carousel/__tests__/__snapshots__/demo.test.ts.snap b/components/carousel/__tests__/__snapshots__/demo.test.ts.snap index 05b37f5b99df..50751a7206a3 100644 --- a/components/carousel/__tests__/__snapshots__/demo.test.ts.snap +++ b/components/carousel/__tests__/__snapshots__/demo.test.ts.snap @@ -1,5 +1,301 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`renders components/carousel/demo/arrows.tsx correctly 1`] = ` +Array [ + <div + class="ant-carousel" + > + <div + class="slick-slider slick-initialized" + dir="ltr" + > + <button + aria-label="prev" + class="slick-arrow slick-prev slick-disabled" + data-role="none" + style="display:block" + type="button" + /> + <div + class="slick-list" + > + <div + class="slick-track" + style="width:400%;left:0%" + > + <div + aria-hidden="false" + class="slick-slide slick-active slick-current" + data-index="0" + style="outline:none;width:25%" + tabindex="-1" + > + <div> + <div + style="width:100%;display:inline-block" + tabindex="-1" + > + <h3 + style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79" + > + 1 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="1" + style="outline:none;width:25%" + tabindex="-1" + > + <div> + <div + style="width:100%;display:inline-block" + tabindex="-1" + > + <h3 + style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79" + > + 2 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="2" + style="outline:none;width:25%" + tabindex="-1" + > + <div> + <div + style="width:100%;display:inline-block" + tabindex="-1" + > + <h3 + style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79" + > + 3 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="3" + style="outline:none;width:25%" + tabindex="-1" + > + <div> + <div + style="width:100%;display:inline-block" + tabindex="-1" + > + <h3 + style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79" + > + 4 + </h3> + </div> + </div> + </div> + </div> + </div> + <button + aria-label="next" + class="slick-arrow slick-next" + data-role="none" + style="display:block" + type="button" + /> + <ul + class="slick-dots slick-dots-bottom" + style="display:block" + > + <li + class="slick-active" + > + <button> + 1 + </button> + </li> + <li + class="" + > + <button> + 2 + </button> + </li> + <li + class="" + > + <button> + 3 + </button> + </li> + <li + class="" + > + <button> + 4 + </button> + </li> + </ul> + </div> + </div>, + <br />, + <div + class="ant-carousel ant-carousel-vertical" + > + <div + class="slick-slider slick-vertical slick-initialized" + dir="ltr" + > + <button + aria-label="prev" + class="slick-arrow slick-prev slick-disabled" + data-role="none" + style="display:block" + type="button" + /> + <div + class="slick-list" + > + <div + class="slick-track" + style="width:400%;left:0%" + > + <div + aria-hidden="false" + class="slick-slide slick-active slick-current" + data-index="0" + style="outline:none;width:25%" + tabindex="-1" + > + <div> + <div + style="width:100%;display:inline-block" + tabindex="-1" + > + <h3 + style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79" + > + 1 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="1" + style="outline:none;width:25%" + tabindex="-1" + > + <div> + <div + style="width:100%;display:inline-block" + tabindex="-1" + > + <h3 + style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79" + > + 2 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="2" + style="outline:none;width:25%" + tabindex="-1" + > + <div> + <div + style="width:100%;display:inline-block" + tabindex="-1" + > + <h3 + style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79" + > + 3 + </h3> + </div> + </div> + </div> + <div + aria-hidden="true" + class="slick-slide" + data-index="3" + style="outline:none;width:25%" + tabindex="-1" + > + <div> + <div + style="width:100%;display:inline-block" + tabindex="-1" + > + <h3 + style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79" + > + 4 + </h3> + </div> + </div> + </div> + </div> + </div> + <button + aria-label="next" + class="slick-arrow slick-next" + data-role="none" + style="display:block" + type="button" + /> + <ul + class="slick-dots slick-dots-left" + style="display:block" + > + <li + class="slick-active" + > + <button> + 1 + </button> + </li> + <li + class="" + > + <button> + 2 + </button> + </li> + <li + class="" + > + <button> + 3 + </button> + </li> + <li + class="" + > + <button> + 4 + </button> + </li> + </ul> + </div> + </div>, +] +`; + exports[`renders components/carousel/demo/autoplay.tsx correctly 1`] = ` <div class="ant-carousel" @@ -721,7 +1017,7 @@ exports[`renders components/carousel/demo/fade.tsx correctly 1`] = ` aria-hidden="false" class="slick-slide slick-active slick-current" data-index="0" - style="outline:none;width:11.11111111111111%;position:relative;left:0;opacity:1;transition:opacity 500ms ease, visibility 500ms ease" + style="outline:none;width:11.11111111111111%;position:relative;left:0;opacity:1;z-index:999;transition:opacity 500ms ease, visibility 500ms ease" tabindex="-1" > <div> @@ -741,7 +1037,7 @@ exports[`renders components/carousel/demo/fade.tsx correctly 1`] = ` aria-hidden="true" class="slick-slide" data-index="1" - style="outline:none;width:11.11111111111111%;position:relative;left:-11px;opacity:0;transition:opacity 500ms ease, visibility 500ms ease" + style="outline:none;width:11.11111111111111%;position:relative;left:-11px;opacity:0;z-index:998;transition:opacity 500ms ease, visibility 500ms ease" tabindex="-1" > <div> @@ -761,7 +1057,7 @@ exports[`renders components/carousel/demo/fade.tsx correctly 1`] = ` aria-hidden="true" class="slick-slide" data-index="2" - style="outline:none;width:11.11111111111111%;position:relative;left:-22px;opacity:0;transition:opacity 500ms ease, visibility 500ms ease" + style="outline:none;width:11.11111111111111%;position:relative;left:-22px;opacity:0;z-index:998;transition:opacity 500ms ease, visibility 500ms ease" tabindex="-1" > <div> @@ -781,7 +1077,7 @@ exports[`renders components/carousel/demo/fade.tsx correctly 1`] = ` aria-hidden="true" class="slick-slide" data-index="3" - style="outline:none;width:11.11111111111111%;position:relative;left:-33px;opacity:0;transition:opacity 500ms ease, visibility 500ms ease" + style="outline:none;width:11.11111111111111%;position:relative;left:-33px;opacity:0;z-index:998;transition:opacity 500ms ease, visibility 500ms ease" tabindex="-1" > <div> diff --git a/components/carousel/__tests__/__snapshots__/index.test.tsx.snap b/components/carousel/__tests__/__snapshots__/index.test.tsx.snap index cf26889a0a25..f4960cdc7f99 100644 --- a/components/carousel/__tests__/__snapshots__/index.test.tsx.snap +++ b/components/carousel/__tests__/__snapshots__/index.test.tsx.snap @@ -6,6 +6,7 @@ exports[`Carousel rtl render component should be rendered correctly in RTL direc > <div class="slick-slider slick-initialized" + dir="ltr" > <div class="slick-list" @@ -25,6 +26,7 @@ exports[`Carousel should works for dotPosition bottom 1`] = ` > <div class="slick-slider slick-initialized" + dir="ltr" > <div class="slick-list" @@ -49,6 +51,18 @@ exports[`Carousel should works for dotPosition bottom 1`] = ` </div> </div> </div> + <ul + class="slick-dots slick-dots-bottom" + style="display: block;" + > + <li + class="slick-active" + > + <button> + 1 + </button> + </li> + </ul> </div> </div> `; @@ -59,9 +73,11 @@ exports[`Carousel should works for dotPosition left 1`] = ` > <div class="slick-slider slick-vertical slick-initialized" + dir="ltr" > <div class="slick-list" + style="height: 0px;" > <div class="slick-track" @@ -83,6 +99,18 @@ exports[`Carousel should works for dotPosition left 1`] = ` </div> </div> </div> + <ul + class="slick-dots slick-dots-left" + style="display: block;" + > + <li + class="slick-active" + > + <button> + 1 + </button> + </li> + </ul> </div> </div> `; @@ -93,9 +121,11 @@ exports[`Carousel should works for dotPosition right 1`] = ` > <div class="slick-slider slick-vertical slick-initialized" + dir="ltr" > <div class="slick-list" + style="height: 0px;" > <div class="slick-track" @@ -117,6 +147,18 @@ exports[`Carousel should works for dotPosition right 1`] = ` </div> </div> </div> + <ul + class="slick-dots slick-dots-right" + style="display: block;" + > + <li + class="slick-active" + > + <button> + 1 + </button> + </li> + </ul> </div> </div> `; @@ -127,6 +169,7 @@ exports[`Carousel should works for dotPosition top 1`] = ` > <div class="slick-slider slick-initialized" + dir="ltr" > <div class="slick-list" @@ -151,6 +194,18 @@ exports[`Carousel should works for dotPosition top 1`] = ` </div> </div> </div> + <ul + class="slick-dots slick-dots-top" + style="display: block;" + > + <li + class="slick-active" + > + <button> + 1 + </button> + </li> + </ul> </div> </div> `; diff --git a/components/carousel/__tests__/index.test.tsx b/components/carousel/__tests__/index.test.tsx index 9334ce7f3321..247736f5463b 100644 --- a/components/carousel/__tests__/index.test.tsx +++ b/components/carousel/__tests__/index.test.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import type { CarouselRef } from '..'; import Carousel from '..'; import mountTest from '../../../tests/shared/mountTest'; @@ -177,4 +178,18 @@ describe('Carousel', () => { await waitFakeTimer(); expect(ref.current?.innerSlider.state.currentSlide).toBe(1); }); + + it('no dom recognize warning', async () => { + const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + render( + <Carousel arrows> + <div>1</div> + <div>2</div> + <div>3</div> + </Carousel>, + ); + await waitFakeTimer(); + expect(errSpy).not.toHaveBeenCalled(); + errSpy.mockRestore(); + }); }); diff --git a/components/carousel/demo/arrows.md b/components/carousel/demo/arrows.md new file mode 100644 index 000000000000..03bf70d3948b --- /dev/null +++ b/components/carousel/demo/arrows.md @@ -0,0 +1,7 @@ +## zh-CN + +显示切换箭头。 + +## en-US + +Show the arrows for switching. diff --git a/components/carousel/demo/arrows.tsx b/components/carousel/demo/arrows.tsx new file mode 100644 index 000000000000..a0fdaf58d783 --- /dev/null +++ b/components/carousel/demo/arrows.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Carousel } from 'antd'; + +const contentStyle: React.CSSProperties = { + margin: 0, + height: '160px', + color: '#fff', + lineHeight: '160px', + textAlign: 'center', + background: '#364d79', +}; + +const App: React.FC = () => ( + <> + <Carousel arrows infinite={false}> + <div> + <h3 style={contentStyle}>1</h3> + </div> + <div> + <h3 style={contentStyle}>2</h3> + </div> + <div> + <h3 style={contentStyle}>3</h3> + </div> + <div> + <h3 style={contentStyle}>4</h3> + </div> + </Carousel> + <br /> + <Carousel arrows dotPosition="left" infinite={false}> + <div> + <h3 style={contentStyle}>1</h3> + </div> + <div> + <h3 style={contentStyle}>2</h3> + </div> + <div> + <h3 style={contentStyle}>3</h3> + </div> + <div> + <h3 style={contentStyle}>4</h3> + </div> + </Carousel> + </> +); + +export default App; diff --git a/components/carousel/index.en-US.md b/components/carousel/index.en-US.md index 484a1d0db581..9a4d74f478b7 100644 --- a/components/carousel/index.en-US.md +++ b/components/carousel/index.en-US.md @@ -2,14 +2,13 @@ category: Components group: Data Display title: Carousel +description: A set of carousel areas. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*bPMSSqbaTMkAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*a-58QpYnqOsAAAAAAAAAAAAADrJ8AQ/original demo: cols: 2 --- -A carousel component. Scales with its container. - ## When To Use - When there is a group of content on the same level. @@ -23,6 +22,7 @@ A carousel component. Scales with its container. <code src="./demo/position.tsx">Position</code> <code src="./demo/autoplay.tsx">Scroll automatically</code> <code src="./demo/fade.tsx">Fade in</code> +<code src="./demo/arrows.tsx" version="5.17.0">Arrows for switching</code> <code src="./demo/component-token.tsx" debug>Component Token</code> ## API @@ -31,10 +31,13 @@ Common props ref:[Common props](/docs/react/common-props) | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | +| arrows | Whether to show switch arrows | boolean | false | 5.17.0 | | autoplay | Whether to scroll automatically | boolean | false | | | autoplaySpeed | Delay between each auto scroll (in milliseconds) | number | 3000 | | +| adaptiveHeight | Adjust the slide's height automatically | boolean | false | | | dotPosition | The position of the dots, which can be one of `top` `bottom` `left` `right` | string | `bottom` | | -| dots | Whether to show the dots at the bottom of the gallery, `object` for `dotsClass` and any others | boolean \| { className?: string } | true | | +| dots | Whether to show the dots at the bottom of the gallery, `object` for `dotsClass` | boolean \| { className?: string } | true | | +| draggable | Enable scrollable via dragging on desktop | boolean | false | | | fade | Whether to use fade transition | boolean | false | | | infinite | Infinitely wrap around contents | boolean | true | | | speed | Animation speed in milliseconds | number | 500 | | @@ -44,6 +47,8 @@ Common props ref:[Common props](/docs/react/common-props) | beforeChange | Callback function called before the current index changes | (current: number, next: number) => void | - | | | waitForAnimate | Whether to wait for the animation when switching | boolean | false | | +Find more APIs in react-slick [documentation](https://react-slick.neostack.com/docs/api). + ## Methods | Name | Description | @@ -52,8 +57,6 @@ Common props ref:[Common props](/docs/react/common-props) | next() | Change current slide to next slide | | prev() | Change current slide to previous slide | -Find more APIs in react-slick [documentation](https://react-slick.neostack.com/docs/api). - ## Design Token <ComponentTokenTable component="Carousel"></ComponentTokenTable> diff --git a/components/carousel/index.tsx b/components/carousel/index.tsx index c963d6614778..f74891ebadf7 100644 --- a/components/carousel/index.tsx +++ b/components/carousel/index.tsx @@ -31,10 +31,23 @@ export interface CarouselRef { innerSlider: any; } +const dotsClass = 'slick-dots'; + +interface ArrowType extends React.ButtonHTMLAttributes<HTMLButtonElement> { + currentSlide?: number; + slideCount?: number; +} + +const ArrowButton: React.FC<ArrowType> = ({ currentSlide, slideCount, ...rest }) => ( + <button type="button" {...rest} /> +); + const Carousel = React.forwardRef<CarouselRef, CarouselProps>((props, ref) => { const { dots = true, arrows = false, + prevArrow = <ArrowButton aria-label="prev" />, + nextArrow = <ArrowButton aria-label="next" />, draggable = false, waitForAnimate = false, dotPosition = 'bottom', @@ -64,7 +77,7 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>((props, ref) => { [slickRef.current], ); - const prevCount = React.useRef(React.Children.count(props.children)); + const prevCount = React.useRef<number>(React.Children.count(props.children)); React.useEffect(() => { if (prevCount.current !== React.Children.count(props.children)) { @@ -85,7 +98,6 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>((props, ref) => { } const prefixCls = getPrefixCls('carousel', newProps.prefixCls); - const dotsClass = 'slick-dots'; const enableDots = !!dots; const dsClass = classNames( @@ -115,6 +127,8 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>((props, ref) => { dots={enableDots} dotsClass={dsClass} arrows={arrows} + prevArrow={prevArrow} + nextArrow={nextArrow} draggable={draggable} verticalSwiping={vertical} waitForAnimate={waitForAnimate} diff --git a/components/carousel/index.zh-CN.md b/components/carousel/index.zh-CN.md index 20958ef57b44..aed5efb46ec2 100644 --- a/components/carousel/index.zh-CN.md +++ b/components/carousel/index.zh-CN.md @@ -3,14 +3,13 @@ category: Components group: 数据展示 title: Carousel subtitle: 走马灯 +description: 一组轮播的区域。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*bPMSSqbaTMkAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*a-58QpYnqOsAAAAAAAAAAAAADrJ8AQ/original demo: cols: 2 --- -旋转木马,一组轮播的区域。 - ## 何时使用 - 当有一组平级的内容。 @@ -24,6 +23,7 @@ demo: <code src="./demo/position.tsx">位置</code> <code src="./demo/autoplay.tsx">自动切换</code> <code src="./demo/fade.tsx">渐显</code> +<code src="./demo/arrows.tsx" version="5.17.0">切换箭头</code> <code src="./demo/component-token.tsx" debug>组件 Token</code> ## API @@ -32,10 +32,13 @@ demo: | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | +| arrows | 是否显示箭头 | boolean | false | 5.17.0 | | autoplay | 是否自动切换 | boolean | false | | | autoplaySpeed | 自动切换的间隔(毫秒) | number | 3000 | | +| adaptiveHeight | 高度自适应 | boolean | false | | | dotPosition | 面板指示点位置,可选 `top` `bottom` `left` `right` | string | `bottom` | | -| dots | 是否显示面板指示点,如果为 `object` 则同时可以指定 `dotsClass` 或者 | boolean \| { className?: string } | true | | +| dots | 是否显示面板指示点,如果为 `object` 则可以指定 `dotsClass` | boolean \| { className?: string } | true | | +| draggable | 是否启用拖拽切换 | boolean | false | | | fade | 使用渐变切换动效 | boolean | false | | | infinite | 是否无限循环切换(实现方式是复制两份 children 元素,如果子元素有副作用则可能会引发 bug) | boolean | true | | | speed | 切换动效的时间(毫秒) | number | 500 | | @@ -45,6 +48,8 @@ demo: | beforeChange | 切换面板的回调 | (current: number, next: number) => void | - | | | waitForAnimate | 是否等待切换动画 | boolean | false | | +更多 API 可参考:<https://react-slick.neostack.com/docs/api> + ## 方法 | 名称 | 描述 | @@ -53,8 +58,6 @@ demo: | next() | 切换到下一面板 | | prev() | 切换到上一面板 | -更多 API 可参考:<https://react-slick.neostack.com/docs/api> - ## 主题变量(Design Token) <ComponentTokenTable component="Carousel"></ComponentTokenTable> diff --git a/components/carousel/style/index.ts b/components/carousel/style/index.ts index 8bb5087e30ec..f3d92ee8c0ed 100644 --- a/components/carousel/style/index.ts +++ b/components/carousel/style/index.ts @@ -2,39 +2,52 @@ import { unit } from '@ant-design/cssinjs'; import { resetComponent } from '../../style'; import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal'; -import { genStyleHooks, mergeToken } from '../../theme/internal'; +import { genStyleHooks } from '../../theme/internal'; export interface ComponentToken { /** * @desc 指示点宽度 * @descEN Width of indicator */ - dotWidth: number; + dotWidth: number | string; /** * @desc 指示点高度 * @descEN Height of indicator */ - dotHeight: number; + dotHeight: number | string; + /** + * @desc 指示点之间的间距 + * @descEN gap between indicator + */ + dotGap: number; + /** + * @desc 指示点距离边缘的距离 + * @descEN dot offset to Carousel edge + */ + dotOffset: number; /** @deprecated Use `dotActiveWidth` instead. */ dotWidthActive: number; /** * @desc 激活态指示点宽度 * @descEN Width of active indicator */ - dotActiveWidth: number; + dotActiveWidth: number | string; + /** + * @desc 切换箭头大小 + * @descEN Size of arrows + */ + arrowSize: number; + /** + * @desc 切换箭头边距 + * @descEN arrows offset to Carousel edge + */ + arrowOffset: number; } -interface CarouselToken extends FullToken<'Carousel'> { - carouselArrowSize: string | number; - carouselDotOffset: string | number; - carouselDotInline: string | number; -} +interface CarouselToken extends FullToken<'Carousel'> {} const genCarouselStyle: GenerateStyle<CarouselToken> = (token) => { - const { componentCls, antCls, carouselArrowSize, carouselDotOffset, marginXXS } = token; - const arrowOffset = token.calc(carouselArrowSize).mul(-1.25).equal(); - - const carouselDotMargin = marginXXS; + const { componentCls, antCls } = token; return { [componentCls]: { @@ -133,142 +146,176 @@ const genCarouselStyle: GenerateStyle<CarouselToken> = (token) => { display: 'block', height: 'auto', }, + }, + }; +}; - '.slick-arrow.slick-hidden': { - display: 'none', - }, +const genArrowsStyle: GenerateStyle<CarouselToken> = (token) => { + const { componentCls, motionDurationSlow, arrowSize, arrowOffset } = token; + const arrowLength = token.calc(arrowSize).div(Math.SQRT2).equal(); - // Arrows - '.slick-prev, .slick-next': { - position: 'absolute', - top: '50%', - display: 'block', - width: carouselArrowSize, - height: carouselArrowSize, - marginTop: token.calc(carouselArrowSize).mul(-1).div(2).equal(), - padding: 0, - color: 'transparent', - fontSize: 0, - lineHeight: 0, - background: 'transparent', - border: 0, - outline: 'none', - cursor: 'pointer', - - '&:hover, &:focus': { - color: 'transparent', + return [ + { + [componentCls]: { + // Arrows + '.slick-prev, .slick-next': { + position: 'absolute', + top: '50%', + width: arrowSize, + height: arrowSize, + transform: 'translateY(-50%)', + color: '#fff', + opacity: 0.4, background: 'transparent', + padding: 0, + lineHeight: 0, + border: 0, outline: 'none', + cursor: 'pointer', + zIndex: 1, + transition: `opacity ${motionDurationSlow}`, - '&::before': { + '&:hover, &:focus': { opacity: 1, }, - }, - - '&.slick-disabled::before': { - opacity: 0.25, - }, - }, - '.slick-prev': { - insetInlineStart: arrowOffset, + '&.slick-disabled': { + pointerEvents: 'none', + opacity: 0, + }, - '&::before': { - content: '"←"', + '&::after': { + boxSizing: 'border-box', + position: 'absolute', + top: token.calc(arrowSize).sub(arrowLength).div(2).equal(), + insetInlineStart: token.calc(arrowSize).sub(arrowLength).div(2).equal(), + display: 'inline-block', + width: arrowLength, + height: arrowLength, + border: `0 solid currentcolor`, + borderInlineWidth: '2px 0', + borderBlockWidth: '2px 0', + borderRadius: 1, + content: '""', + }, }, - }, - '.slick-next': { - insetInlineEnd: arrowOffset, + '.slick-prev': { + insetInlineStart: arrowOffset, - '&::before': { - content: '"→"', + '&::after': { + transform: 'rotate(-45deg)', + }, }, - }, - // Dots - '.slick-dots': { - position: 'absolute', - insetInlineEnd: 0, - bottom: 0, - insetInlineStart: 0, - zIndex: 15, - display: 'flex !important', - justifyContent: 'center', - paddingInlineStart: 0, - margin: 0, - listStyle: 'none', + '.slick-next': { + insetInlineEnd: arrowOffset, - '&-bottom': { - bottom: carouselDotOffset, + '&::after': { + transform: 'rotate(135deg)', + }, }, + }, + }, + ]; +}; - '&-top': { - top: carouselDotOffset, - bottom: 'auto', - }, +const genDotsStyle: GenerateStyle<CarouselToken> = (token) => { + const { + componentCls, + dotOffset, + dotWidth, + dotHeight, + dotGap, + colorBgContainer, + motionDurationSlow, + } = token; + return [ + { + [componentCls]: { + '.slick-dots': { + position: 'absolute', + insetInlineEnd: 0, + bottom: 0, + insetInlineStart: 0, + zIndex: 15, + display: 'flex !important', + justifyContent: 'center', + paddingInlineStart: 0, + margin: 0, + listStyle: 'none', + + '&-bottom': { + bottom: dotOffset, + }, - li: { - position: 'relative', - display: 'inline-block', - flex: '0 1 auto', - boxSizing: 'content-box', - width: token.dotWidth, - height: token.dotHeight, - marginInline: carouselDotMargin, - padding: 0, - textAlign: 'center', - textIndent: -999, - verticalAlign: 'top', - transition: `all ${token.motionDurationSlow}`, + '&-top': { + top: dotOffset, + bottom: 'auto', + }, - button: { + li: { position: 'relative', - display: 'block', - width: '100%', - height: token.dotHeight, + display: 'inline-block', + flex: '0 1 auto', + boxSizing: 'content-box', + width: dotWidth, + height: dotHeight, + marginInline: dotGap, padding: 0, - color: 'transparent', - fontSize: 0, - background: token.colorBgContainer, - border: 0, - borderRadius: token.dotHeight, - outline: 'none', - cursor: 'pointer', - opacity: 0.3, - transition: `all ${token.motionDurationSlow}`, - - '&: hover, &:focus': { - opacity: 0.75, - }, - - '&::after': { - position: 'absolute', - inset: token.calc(carouselDotMargin).mul(-1).equal(), - content: '""', + textAlign: 'center', + textIndent: -999, + verticalAlign: 'top', + transition: `all ${motionDurationSlow}`, + + button: { + position: 'relative', + display: 'block', + width: '100%', + height: dotHeight, + padding: 0, + color: 'transparent', + fontSize: 0, + background: colorBgContainer, + border: 0, + borderRadius: dotHeight, + outline: 'none', + cursor: 'pointer', + opacity: 0.2, + transition: `all ${motionDurationSlow}`, + + '&: hover, &:focus': { + opacity: 0.75, + }, + + '&::after': { + position: 'absolute', + inset: token.calc(dotGap).mul(-1).equal(), + content: '""', + }, }, - }, - '&.slick-active': { - width: token.dotActiveWidth, + '&.slick-active': { + width: token.dotActiveWidth, - '& button': { - background: token.colorBgContainer, - opacity: 1, - }, + '& button': { + background: colorBgContainer, + opacity: 1, + }, - '&: hover, &:focus': { - opacity: 1, + '&: hover, &:focus': { + opacity: 1, + }, }, }, }, }, }, - }; + ]; }; const genCarouselVerticalStyle: GenerateStyle<CarouselToken> = (token) => { - const { componentCls, carouselDotOffset, marginXXS } = token; + const { componentCls, dotOffset, arrowOffset, marginXXS } = token; const reverseSizeOfDot = { width: token.dotHeight, @@ -277,6 +324,26 @@ const genCarouselVerticalStyle: GenerateStyle<CarouselToken> = (token) => { return { [`${componentCls}-vertical`]: { + '.slick-prev, .slick-next': { + insetInlineStart: '50%', + marginBlockStart: 'unset', + transform: 'translateX(-50%)', + }, + '.slick-prev': { + insetBlockStart: arrowOffset, + insetInlineStart: '50%', + + '&::after': { + transform: 'rotate(45deg)', + }, + }, + '.slick-next': { + insetBlockStart: 'auto', + insetBlockEnd: arrowOffset, + '&::after': { + transform: 'rotate(-135deg)', + }, + }, '.slick-dots': { top: '50%', bottom: 'auto', @@ -288,11 +355,11 @@ const genCarouselVerticalStyle: GenerateStyle<CarouselToken> = (token) => { '&-left': { insetInlineEnd: 'auto', - insetInlineStart: carouselDotOffset, + insetInlineStart: dotOffset, }, '&-right': { - insetInlineEnd: carouselDotOffset, + insetInlineEnd: dotOffset, insetInlineStart: 'auto', }, @@ -343,12 +410,16 @@ const genCarouselRtlStyle: GenerateStyle<CarouselToken> = (token) => { ]; }; -export const prepareComponentToken: GetDefaultToken<'Carousel'> = () => { +export const prepareComponentToken: GetDefaultToken<'Carousel'> = (token) => { const dotActiveWidth = 24; return { + arrowSize: 16, + arrowOffset: token.marginXS, dotWidth: 16, dotHeight: 3, + dotGap: token.marginXXS, + dotOffset: 12, dotWidthActive: dotActiveWidth, dotActiveWidth, }; @@ -357,19 +428,13 @@ export const prepareComponentToken: GetDefaultToken<'Carousel'> = () => { // ============================== Export ============================== export default genStyleHooks( 'Carousel', - (token) => { - const { controlHeightLG, controlHeightSM } = token; - const carouselToken = mergeToken<CarouselToken>(token, { - carouselArrowSize: token.calc(controlHeightLG).div(2).equal(), - carouselDotOffset: token.calc(controlHeightSM).div(2).equal(), - }); - - return [ - genCarouselStyle(carouselToken), - genCarouselVerticalStyle(carouselToken), - genCarouselRtlStyle(carouselToken), - ]; - }, + (token) => [ + genCarouselStyle(token), + genArrowsStyle(token), + genDotsStyle(token), + genCarouselVerticalStyle(token), + genCarouselRtlStyle(token), + ], prepareComponentToken, { deprecatedTokens: [['dotWidthActive', 'dotActiveWidth']], diff --git a/components/cascader/Panel.tsx b/components/cascader/Panel.tsx index 7c88e085a55b..5993c62df778 100644 --- a/components/cascader/Panel.tsx +++ b/components/cascader/Panel.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import classNames from 'classnames'; +import type { CascaderProps as RcCascaderProps } from 'rc-cascader'; import { Panel } from 'rc-cascader'; import type { PickType } from 'rc-cascader/lib/Panel'; -import type { CascaderProps } from '.'; +import type { CascaderProps, DefaultOptionType } from '.'; import DefaultRenderEmpty from '../config-provider/defaultRenderEmpty'; import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; import useBase from './hooks/useBase'; @@ -14,9 +15,23 @@ import usePanelStyle from './style/panel'; export type PanelPickType = Exclude<PickType, 'checkable'> | 'multiple' | 'rootClassName'; -export type CascaderPanelProps = Pick<CascaderProps, PanelPickType>; +export type CascaderPanelProps< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, + Multiple extends boolean = boolean, +> = Pick<CascaderProps<OptionType, ValueField, Multiple>, PanelPickType>; -const CascaderPanel: React.FC<CascaderPanelProps> = (props) => { +export type CascaderPanelAutoProps< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, +> = + | (CascaderPanelProps<OptionType, ValueField> & { multiple?: false }) + | (CascaderPanelProps<OptionType, ValueField, true> & { multiple: true }); + +function CascaderPanel< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, +>(props: CascaderPanelAutoProps<OptionType, ValueField>) { const { prefixCls: customizePrefixCls, className, @@ -53,7 +68,7 @@ const CascaderPanel: React.FC<CascaderPanelProps> = (props) => { return wrapCSSVar( <Panel - {...props} + {...(props as Pick<RcCascaderProps, PickType>)} checkable={checkable} prefixCls={cascaderPrefixCls} className={classNames(className, hashId, rootClassName, cssVarCls, rootCls)} @@ -63,6 +78,6 @@ const CascaderPanel: React.FC<CascaderPanelProps> = (props) => { loadingIcon={loadingIcon} />, ); -}; +} export default CascaderPanel; diff --git a/components/cascader/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/cascader/__tests__/__snapshots__/demo-extend.test.ts.snap index 3a9637162101..e687cd198fa4 100644 --- a/components/cascader/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/cascader/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -498,10 +498,10 @@ exports[`renders components/cascader/demo/custom-render.tsx extend context corre class="ant-select-selection-item" > <span> - Zhejiang / + Zhejiang / </span> <span> - Hangzhou / + Hangzhou / </span> <span> West Lake ( @@ -660,7 +660,7 @@ exports[`renders components/cascader/demo/custom-render.tsx extend context corre exports[`renders components/cascader/demo/custom-trigger.tsx extend context correctly 1`] = ` <span> - Unselect + Unselect  <a> Change city </a> @@ -1922,6 +1922,9 @@ exports[`renders components/cascader/demo/panel.tsx extend context correctly 1`] width="64" xmlns="http://www.w3.org/2000/svg" > + <title> + Simple Empty + + + Simple Empty + + + Simple Empty + Zhejiang - / + /
Hangzhou - / + / West Lake @@ -903,6 +903,9 @@ exports[`renders components/cascader/demo/panel.tsx correctly 1`] = ` width="64" xmlns="http://www.w3.org/2000/svg" > + + Simple Empty + + + Simple Empty + + + Simple Empty + ( +function filter( inputValue: string, path: OptionType[], ): boolean { - return path.some((option) => option.label.toLowerCase().includes(inputValue.toLowerCase())); + return path.some((option) => + option.label?.toString().toLowerCase().includes(inputValue.toLowerCase()), + ); } describe('Cascader', () => { @@ -182,7 +185,7 @@ describe('Cascader', () => { ], }, ]; - function customFilter( + function customFilter( inputValue: string, path: OptionType[], ): boolean { @@ -373,7 +376,7 @@ describe('Cascader', () => { { value: 'hangzhou', label: 'Hangzhou', - children: null, + children: null as any, }, ], }, @@ -515,7 +518,7 @@ describe('Cascader', () => { it('onChange works correctly when the label of fieldNames is the same as value', () => { const onChange = jest.fn(); - const sameNames = { label: 'label', value: 'label' }; + const sameNames = { label: 'label', value: 'label' } as const; const { container } = render( , ); diff --git a/components/cascader/__tests__/type.test.tsx b/components/cascader/__tests__/type.test.tsx index e0c9010e6578..7fdd3a1fa68c 100644 --- a/components/cascader/__tests__/type.test.tsx +++ b/components/cascader/__tests__/type.test.tsx @@ -1,7 +1,9 @@ import * as React from 'react'; -import type { BaseOptionType } from '..'; + +import type { BaseOptionType, CascaderAutoProps, CascaderProps } from '..'; import Cascader from '..'; import { render } from '../../../tests/utils'; +import type { CascaderPanelAutoProps, CascaderPanelProps } from '../Panel'; describe('Cascader.typescript', () => { it('options value', () => { @@ -79,16 +81,44 @@ describe('Cascader.typescript', () => { }); it('single onChange', () => { - const { container } = render( - values} />, - ); + const { container } = render( values} />); expect(container).toBeTruthy(); }); it('multiple onChange', () => { - const { container } = render( - values} />, - ); + const { container } = render( values} />); + expect(container).toBeTruthy(); + }); + + it('cascader props', () => { + // Incorrect usage, onChange value type is `value[]` + const cascaderProps: { props?: CascaderProps }[] = [{ props: { multiple: true } }]; + expect(cascaderProps).toBeTruthy(); + + const { container } = render( value} />); expect(container).toBeTruthy(); }); + + it('cascader panel props', () => { + // Incorrect usage, onChange value type is `value[]` + const cascaderPanelProps: { props?: CascaderPanelProps }[] = [{ props: { multiple: true } }]; + expect(cascaderPanelProps).toBeTruthy(); + + const { container } = render( value} />); + expect(container).toBeTruthy(); + }); + + it('props', () => { + const list: { props?: CascaderAutoProps }[] = [ + { props: { multiple: true, onChange: (value) => value } }, + { props: { multiple: false, onChange: (value) => value } }, + ]; + expect(list).toBeTruthy(); + + const list2: { props?: CascaderPanelAutoProps }[] = [ + { props: { multiple: true, onChange: (value) => value } }, + { props: { multiple: false, onChange: (value) => value } }, + ]; + expect(list2).toBeTruthy(); + }); }); diff --git a/components/cascader/demo/basic.tsx b/components/cascader/demo/basic.tsx index 5c51fdec2458..cf8d68a998af 100644 --- a/components/cascader/demo/basic.tsx +++ b/components/cascader/demo/basic.tsx @@ -1,8 +1,9 @@ import React from 'react'; +import type { CascaderProps } from 'antd'; import { Cascader } from 'antd'; interface Option { - value: string | number; + value: string; label: string; children?: Option[]; } @@ -42,7 +43,7 @@ const options: Option[] = [ }, ]; -const onChange = (value: (string | number)[]) => { +const onChange: CascaderProps ); - return [mergedExpandIcon, loadingIcon]; -} + return React.useMemo>( + () => [mergedExpandIcon, loadingIcon] as const, + [mergedExpandIcon], + ); +}; + +export default useColumnIcons; diff --git a/components/cascader/index.en-US.md b/components/cascader/index.en-US.md index 786c15813a63..b881ce5b9b95 100644 --- a/components/cascader/index.en-US.md +++ b/components/cascader/index.en-US.md @@ -2,18 +2,17 @@ category: Components group: Data Entry title: Cascader +description: Cascade selection box. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ngTnQZNOcK0AAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*Nt8xR7afyr0AAAAAAAAAAAAADrJ8AQ/original demo: cols: 2 --- -Cascade selection box. - ## When To Use - When you need to select from a set of associated data set. Such as province/city/district, company level, things classification. -- When selecting from a large data set, with multi-stage classification separated for easy selection. +- When selecting from a large data set, with multi-stage classifications separated for easy selection. - Chooses cascade items in one float layer for better user experience. ## Examples @@ -52,7 +51,7 @@ Common props ref:[Common props](/docs/react/common-props) | allowClear | Show clear button | boolean \| { clearIcon?: ReactNode } | true | 5.8.0: Support object type | | autoClearSearchValue | Whether the current search will be cleared on selecting an item. Only applies when `multiple` is `true` | boolean | true | 5.9.0 | | autoFocus | If get focus when component mounted | boolean | false | | -| changeOnSelect | (Work on single select) Change value on each selection if set to true, see above demo for details | boolean | false | | +| changeOnSelect | Change value on each selection if set to true, see above demo for details | boolean | false | | | className | The additional css class | string | - | | | defaultValue | Initial selected value | string\[] \| number\[] | \[] | | | disabled | Whether disabled select | boolean | false | | @@ -71,7 +70,7 @@ Common props ref:[Common props](/docs/react/common-props) | notFoundContent | Specify content to show when no result matches | string | `Not Found` | | | open | Set visible of cascader popup | boolean | - | 4.17.0 | | options | The data options of cascade | [Option](#option)\[] | - | | -| placeholder | The input placeholder | string | `Please select` | | +| placeholder | The input placeholder | string | - | | | placement | Use preset popup align config from builtinPlacements | `bottomLeft` `bottomRight` `topLeft` `topRight` | `bottomLeft` | 4.17.0 | | showSearch | Whether show search input in single mode | boolean \| [Object](#showsearch) | false | | | size | The input size | `large` \| `middle` \| `small` | - | | @@ -89,6 +88,7 @@ Common props ref:[Common props](/docs/react/common-props) | onSearch | The callback function triggered when input changed | (search: string) => void | - | 4.17.0 | | dropdownMenuColumnStyle | The style of the drop-down menu column | CSSProperties | - | | | loadingIcon | The appearance of lazy loading (now is useless) | ReactNode | - | | +| optionRender | Customize the rendering dropdown options | (option: Option) => React.ReactNode | - | 5.16.0 | ### showSearch diff --git a/components/cascader/index.tsx b/components/cascader/index.tsx index 670cff17942f..c7078aa89e46 100644 --- a/components/cascader/index.tsx +++ b/components/cascader/index.tsx @@ -4,8 +4,7 @@ import type { BaseOptionType, DefaultOptionType, FieldNames, - MultipleCascaderProps as RcMultipleCascaderProps, - SingleCascaderProps as RcSingleCascaderProps, + CascaderProps as RcCascaderProps, ShowSearchType, } from 'rc-cascader'; import RcCascader from 'rc-cascader'; @@ -20,13 +19,13 @@ import type { InputStatus } from '../_util/statusUtils'; import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils'; import { devUseWarning } from '../_util/warning'; import { ConfigContext } from '../config-provider'; +import type { Variant } from '../config-provider'; import DefaultRenderEmpty from '../config-provider/defaultRenderEmpty'; import DisabledContext from '../config-provider/DisabledContext'; import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; import useSize from '../config-provider/hooks/useSize'; import type { SizeType } from '../config-provider/SizeContext'; import { FormItemInputContext } from '../form/context'; -import type { Variant } from '../form/hooks/useVariants'; import useVariant from '../form/hooks/useVariants'; import mergedBuiltinPlacements from '../select/mergedBuiltinPlacements'; import useSelectStyle from '../select/style'; @@ -56,7 +55,10 @@ function highlightKeyword(str: string, lowerKeyword: string, prefixCls?: string) const cells = str .toLowerCase() .split(lowerKeyword) - .reduce((list, cur, index) => (index === 0 ? [cur] : [...list, lowerKeyword, cur]), []); + .reduce( + (list, cur, index) => (index === 0 ? [cur] : [...list, lowerKeyword, cur]), + [], + ); const fillCells: React.ReactNode[] = []; let start = 0; @@ -102,51 +104,43 @@ const defaultSearchRender: ShowSearchType['render'] = (inputValue, path, prefixC return optionList; }; -type SingleCascaderProps = Omit< - RcSingleCascaderProps, - 'checkable' | 'options' -> & { - multiple?: false; -}; -type MultipleCascaderProps = Omit< - RcMultipleCascaderProps, - 'checkable' | 'options' -> & { - multiple: true; -}; - -type UnionCascaderProps = - | SingleCascaderProps - | MultipleCascaderProps; - -export type CascaderProps = - UnionCascaderProps & { - multiple?: boolean; - size?: SizeType; - /** - * @deprecated `showArrow` is deprecated which will be removed in next major version. It will be a - * default behavior, you can hide it by setting `suffixIcon` to null. - */ - showArrow?: boolean; - disabled?: boolean; - /** @deprecated Use `variant` instead. */ - bordered?: boolean; - placement?: SelectCommonPlacement; - suffixIcon?: React.ReactNode; - options?: DataNodeType[]; - status?: InputStatus; - autoClearSearchValue?: boolean; - - rootClassName?: string; - popupClassName?: string; - /** @deprecated Please use `popupClassName` instead */ - dropdownClassName?: string; - /** - * @since 5.13.0 - * @default "outlined" - */ - variant?: Variant; - }; +export interface CascaderProps< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, + Multiple extends boolean = boolean, +> extends Omit, 'checkable'> { + multiple?: Multiple; + size?: SizeType; + /** + * @deprecated `showArrow` is deprecated which will be removed in next major version. It will be a + * default behavior, you can hide it by setting `suffixIcon` to null. + */ + showArrow?: boolean; + disabled?: boolean; + /** @deprecated Use `variant` instead. */ + bordered?: boolean; + placement?: SelectCommonPlacement; + suffixIcon?: React.ReactNode; + options?: OptionType[]; + status?: InputStatus; + autoClearSearchValue?: boolean; + + rootClassName?: string; + popupClassName?: string; + /** @deprecated Please use `popupClassName` instead */ + dropdownClassName?: string; + /** + * @since 5.13.0 + * @default "outlined" + */ + variant?: Variant; +} +export type CascaderAutoProps< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, +> = + | (CascaderProps & { multiple?: false }) + | (CascaderProps & { multiple: true }); export interface CascaderRef { focus: () => void; @@ -230,7 +224,7 @@ const Cascader = React.forwardRef>((props, ref) const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction); - const [variant, enableVariantCls] = useVariant(customVariant, bordered); + const [variant, enableVariantCls] = useVariant('cascader', customVariant, bordered); // =================== No Found ==================== const mergedNotFoundContent = notFoundContent || renderEmpty?.('Cascader') || ( @@ -357,8 +351,12 @@ const Cascader = React.forwardRef>((props, ref) ); return wrapCascaderCSSVar(wrapSelectCSSVar(renderNode)); -}) as unknown as (( - props: React.PropsWithChildren> & React.RefAttributes, +}) as unknown as (< + OptionType extends DefaultOptionType = DefaultOptionType, + ValueField extends keyof OptionType = keyof OptionType, +>( + props: React.PropsWithChildren> & + React.RefAttributes, ) => React.ReactElement) & { displayName: string; SHOW_PARENT: typeof SHOW_PARENT; diff --git a/components/cascader/index.zh-CN.md b/components/cascader/index.zh-CN.md index 6bc7d57199d9..37e85d452ce4 100644 --- a/components/cascader/index.zh-CN.md +++ b/components/cascader/index.zh-CN.md @@ -3,14 +3,13 @@ category: Components group: 数据录入 title: Cascader subtitle: 级联选择 +description: 级联选择框。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ngTnQZNOcK0AAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*Nt8xR7afyr0AAAAAAAAAAAAADrJ8AQ/original demo: cols: 2 --- -级联选择框。 - ## 何时使用 - 需要从一组相关联的数据集合进行选择,例如省市区,公司层级,事物分类等。 @@ -53,7 +52,7 @@ demo: | allowClear | 支持清除 | boolean \| { clearIcon?: ReactNode } | true | 5.8.0: 支持对象形式 | | autoClearSearchValue | 是否在选中项后清空搜索框,只在 `multiple` 为 `true` 时有效 | boolean | true | 5.9.0 | | autoFocus | 自动获取焦点 | boolean | false | | -| changeOnSelect | (单选时生效)当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 | boolean | false | | +| changeOnSelect | 单选时生效(multiple 下始终都可以选择),点选每级菜单选项值都会发生变化。 | boolean | false | | | className | 自定义类名 | string | - | | | defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | | | disabled | 禁用 | boolean | false | | @@ -72,7 +71,7 @@ demo: | notFoundContent | 当下拉列表为空时显示的内容 | string | `Not Found` | | | open | 控制浮层显隐 | boolean | - | 4.17.0 | | options | 可选项数据源 | [Option](#option)\[] | - | | -| placeholder | 输入框占位文本 | string | `请选择` | | +| placeholder | 输入框占位文本 | string | - | | | placement | 浮层预设位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | `bottomLeft` | 4.17.0 | | showSearch | 在选择框中显示搜索框 | boolean \| [Object](#showsearch) | false | | | size | 输入框大小 | `large` \| `middle` \| `small` | - | | @@ -89,6 +88,7 @@ demo: | searchValue | 设置搜索的值,需要与 `showSearch` 配合使用 | string | - | 4.17.0 | | onSearch | 监听搜索,返回输入的值 | (search: string) => void | - | 4.17.0 | | dropdownMenuColumnStyle | 下拉菜单列的样式 | CSSProperties | - | | +| optionRender | 自定义渲染下拉选项 | (option: Option) => React.ReactNode | - | 5.16.0 | ### showSearch diff --git a/components/cascader/style/columns.ts b/components/cascader/style/columns.ts index 760b08fa5a06..adc24abafc26 100644 --- a/components/cascader/style/columns.ts +++ b/components/cascader/style/columns.ts @@ -1,4 +1,5 @@ -import { unit, type CSSInterpolation } from '@ant-design/cssinjs'; +import { unit } from '@ant-design/cssinjs'; +import type { CSSInterpolation } from '@ant-design/cssinjs'; import type { CascaderToken } from '.'; import { getStyle as getCheckboxStyle } from '../../checkbox/style'; @@ -90,7 +91,7 @@ const getColumnsStyle: GenerateStyle = (token: CascaderToken): CS }, [`&-active:not(${cascaderMenuItemCls}-disabled)`]: { - [`&, &:hover`]: { + '&, &:hover': { fontWeight: token.optionSelectedFontWeight, backgroundColor: token.optionSelectedBg, }, diff --git a/components/cascader/style/index.ts b/components/cascader/style/index.ts index 7b03acd1a1d4..663d3961321f 100644 --- a/components/cascader/style/index.ts +++ b/components/cascader/style/index.ts @@ -1,8 +1,7 @@ import type { CSSProperties } from 'react'; import { genCompactItemStyle } from '../../style/compact-item'; -import type { GlobalToken } from '../../theme'; -import type { FullToken, GenerateStyle } from '../../theme/internal'; +import type { FullToken, GenerateStyle, GlobalToken } from '../../theme/internal'; import { genStyleHooks } from '../../theme/internal'; import getColumnsStyle from './columns'; @@ -11,17 +10,17 @@ export interface ComponentToken { * @desc 选择器宽度 * @descEN Width of Cascader */ - controlWidth: number; + controlWidth: number | string; /** * @desc 选项宽度 * @descEN Width of item */ - controlItemWidth: number; + controlItemWidth: number | string; /** * @desc 下拉菜单高度 * @descEN Height of dropdown */ - dropdownHeight: number; + dropdownHeight: number | string; /** * @desc 选项选中时背景色 * @descEN Background color of selected item diff --git a/components/cascader/style/panel.ts b/components/cascader/style/panel.ts index 61b3075228f2..9becab97aa0b 100644 --- a/components/cascader/style/panel.ts +++ b/components/cascader/style/panel.ts @@ -1,7 +1,10 @@ -import { unit, type CSSObject } from '@ant-design/cssinjs'; +import { unit } from '@ant-design/cssinjs'; +import type { CSSObject } from '@ant-design/cssinjs'; -import { prepareComponentToken, type CascaderToken } from '.'; -import { genComponentStyleHook, type GenerateStyle } from '../../theme/internal'; +import { prepareComponentToken } from '.'; +import type { CascaderToken } from '.'; +import { genComponentStyleHook } from '../../theme/internal'; +import type { GenerateStyle } from '../../theme/internal'; import getColumnsStyle from './columns'; // ============================== Panel =============================== diff --git a/components/checkbox/Checkbox.tsx b/components/checkbox/Checkbox.tsx index bab2c60b955a..a5371e6bee47 100644 --- a/components/checkbox/Checkbox.tsx +++ b/components/checkbox/Checkbox.tsx @@ -138,9 +138,7 @@ const InternalCheckbox: React.ForwardRefRenderFunction + {/* @ts-ignore */} { +export interface CheckboxOptionType { label: React.ReactNode; value: T; style?: React.CSSProperties; @@ -22,7 +21,7 @@ export interface CheckboxOptionType { +export interface AbstractCheckboxGroupProps { prefixCls?: string; className?: string; rootClassName?: string; @@ -31,8 +30,7 @@ export interface AbstractCheckboxGroupProps - extends AbstractCheckboxGroupProps { +export interface CheckboxGroupProps extends AbstractCheckboxGroupProps { name?: string; defaultValue?: T[]; value?: T[]; @@ -40,8 +38,10 @@ export interface CheckboxGroupProps( + ( props: CheckboxGroupProps, ref: React.ForwardedRef, ) => { @@ -69,7 +69,7 @@ const CheckboxGroup = React.forwardRef( const memoOptions = React.useMemo[]>( () => - options.map>((option: CheckboxOptionType) => { + options.map>((option: any) => { if (typeof option === 'string' || typeof option === 'number') { return { label: option, value: option }; } @@ -82,11 +82,11 @@ const CheckboxGroup = React.forwardRef( setRegisteredValues((prevValues) => prevValues.filter((v) => v !== val)); }; - const registerValue = (val: T) => { + const registerValue: CheckboxGroupContext['registerValue'] = (val) => { setRegisteredValues((prevValues) => [...prevValues, val]); }; - const toggleOption = (option: CheckboxOptionType) => { + const toggleOption: CheckboxGroupContext['toggleOption'] = (option) => { const optionIndex = value.indexOf(option.value); const newValue = [...value]; if (optionIndex === -1) { @@ -136,8 +136,7 @@ const CheckboxGroup = React.forwardRef( )) : children; - // eslint-disable-next-line react/jsx-no-constructed-context-values - const context = { + const context: CheckboxGroupContext = { toggleOption, value, disabled: restProps.disabled, @@ -168,6 +167,6 @@ const CheckboxGroup = React.forwardRef( export type { CheckboxGroupContext } from './GroupContext'; export { GroupContext }; -export default CheckboxGroup as ( +export default CheckboxGroup as ( props: CheckboxGroupProps & React.RefAttributes, ) => React.ReactElement; diff --git a/components/checkbox/GroupContext.ts b/components/checkbox/GroupContext.ts index b356f4b9efe4..4cf5d6f25c38 100644 --- a/components/checkbox/GroupContext.ts +++ b/components/checkbox/GroupContext.ts @@ -1,9 +1,10 @@ import React from 'react'; -import type { CheckboxOptionType, CheckboxValueType } from './Group'; -export interface CheckboxGroupContext { +import type { CheckboxOptionType } from './Group'; + +export interface CheckboxGroupContext { name?: string; - toggleOption?: (option: CheckboxOptionType) => void; + toggleOption?: (option: CheckboxOptionType) => void; value?: any; disabled?: boolean; registerValue: (val: T) => void; diff --git a/components/checkbox/__tests__/checkbox.test.tsx b/components/checkbox/__tests__/checkbox.test.tsx index 8cd3d6dcde58..c4c2b8142ba3 100644 --- a/components/checkbox/__tests__/checkbox.test.tsx +++ b/components/checkbox/__tests__/checkbox.test.tsx @@ -1,10 +1,11 @@ import React from 'react'; + import Checkbox from '..'; +import { resetWarned } from '../../_util/warning'; import focusTest from '../../../tests/shared/focusTest'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; import { fireEvent, render } from '../../../tests/utils'; -import { resetWarned } from '../../_util/warning'; describe('Checkbox', () => { focusTest(Checkbox, { refFocus: true }); diff --git a/components/checkbox/__tests__/demo.test.tsx b/components/checkbox/__tests__/demo.test.tsx index c506cb1542c8..b68fd9492c7c 100644 --- a/components/checkbox/__tests__/demo.test.tsx +++ b/components/checkbox/__tests__/demo.test.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; + import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest'; demoTest('checkbox'); diff --git a/components/checkbox/__tests__/group.test.tsx b/components/checkbox/__tests__/group.test.tsx index 1178df577624..b44c5022d822 100644 --- a/components/checkbox/__tests__/group.test.tsx +++ b/components/checkbox/__tests__/group.test.tsx @@ -6,7 +6,6 @@ import { fireEvent, render } from '../../../tests/utils'; import Collapse from '../../collapse'; import Input from '../../input'; import Table from '../../table'; -import type { CheckboxValueType } from '../Group'; import type { CheckboxGroupProps } from '../index'; import Checkbox from '../index'; @@ -89,7 +88,7 @@ describe('CheckboxGroup', () => { const renderCheckbox = (props: CheckboxGroupProps) => ; const { container, rerender } = render(renderCheckbox({ options })); expect(container.querySelectorAll('.ant-checkbox-checked').length).toBe(0); - rerender(renderCheckbox({ options, value: 'Apple' as unknown as CheckboxValueType[] })); + rerender(renderCheckbox({ options, value: 'Apple' as any })); expect(container.querySelectorAll('.ant-checkbox-checked').length).toBe(1); }); diff --git a/components/checkbox/__tests__/type.test.tsx b/components/checkbox/__tests__/type.test.tsx index b519588776b9..d69b71dba25c 100644 --- a/components/checkbox/__tests__/type.test.tsx +++ b/components/checkbox/__tests__/type.test.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; + import Checkbox from '..'; import type { CheckboxRef } from '..'; import Input from '../../input'; @@ -26,4 +27,11 @@ describe('Checkbox.typescript', () => { ); expect(group).toBeTruthy(); }); + it('Checkbox.Group defaultValue', () => { + const defaultValue: React.Key[] = ['1']; + const group = ( + + ); + expect(group).toBeTruthy(); + }); }); diff --git a/components/checkbox/demo/check-all.tsx b/components/checkbox/demo/check-all.tsx index ac092bb3dae1..e4f2be9d59e4 100644 --- a/components/checkbox/demo/check-all.tsx +++ b/components/checkbox/demo/check-all.tsx @@ -1,8 +1,6 @@ import React, { useState } from 'react'; import { Checkbox, Divider } from 'antd'; -import type { CheckboxProps, GetProp } from 'antd'; - -type CheckboxValueType = GetProp[number]; +import type { CheckboxProps } from 'antd'; const CheckboxGroup = Checkbox.Group; @@ -10,12 +8,12 @@ const plainOptions = ['Apple', 'Pear', 'Orange']; const defaultCheckedList = ['Apple', 'Orange']; const App: React.FC = () => { - const [checkedList, setCheckedList] = useState(defaultCheckedList); + const [checkedList, setCheckedList] = useState(defaultCheckedList); const checkAll = plainOptions.length === checkedList.length; const indeterminate = checkedList.length > 0 && checkedList.length < plainOptions.length; - const onChange = (list: CheckboxValueType[]) => { + const onChange = (list: string[]) => { setCheckedList(list); }; diff --git a/components/checkbox/index.en-US.md b/components/checkbox/index.en-US.md index 867068673a34..09e4b6a3c302 100644 --- a/components/checkbox/index.en-US.md +++ b/components/checkbox/index.en-US.md @@ -2,14 +2,13 @@ category: Components group: Data Entry title: Checkbox +description: Collect user's choices. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*DzgiRbW3khIAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*G3MjTYXL6AIAAAAAAAAAAAAADrJ8AQ/original demo: cols: 2 --- -Checkbox component. - ## When To Use - Used for selecting multiple values from several options. @@ -52,7 +51,7 @@ Common props ref:[Common props](/docs/react/common-props) | name | The `name` property of all `input[type="checkbox"]` children | string | - | | | options | Specifies options | string\[] \| number\[] \| Option\[] | \[] | | | value | Used for setting the currently selected value | (string \| number \| boolean)\[] | \[] | | -| onChange | The callback function that is triggered when the state changes | (checkedValue: CheckboxValueType[]) => void | - | | +| onChange | The callback function that is triggered when the state changes | (checkedValue: T[]) => void | - | | ##### Option @@ -68,10 +67,11 @@ interface Option { #### Checkbox -| Name | Description | Version | -| ------- | ------------ | ------- | -| blur() | Remove focus | | -| focus() | Get focus | | +| Name | Description | Version | +| ------------- | ------------------------------------ | ------- | +| blur() | Remove focus | | +| focus() | Get focus | | +| nativeElement | Returns the DOM node of the Checkbox | 5.17.3 | ## Design Token diff --git a/components/checkbox/index.ts b/components/checkbox/index.tsx similarity index 74% rename from components/checkbox/index.ts rename to components/checkbox/index.tsx index 53b0c0493a40..eab6718140c5 100644 --- a/components/checkbox/index.ts +++ b/components/checkbox/index.tsx @@ -1,6 +1,5 @@ -import type * as React from 'react'; import type { CheckboxRef } from 'rc-checkbox'; -import type { CheckboxProps } from './Checkbox'; + import InternalCheckbox from './Checkbox'; import Group from './Group'; @@ -8,9 +7,7 @@ export type { CheckboxChangeEvent, CheckboxProps } from './Checkbox'; export type { CheckboxGroupProps, CheckboxOptionType } from './Group'; export type { CheckboxRef }; -type CompoundedComponent = React.ForwardRefExoticComponent< - CheckboxProps & React.RefAttributes -> & { +type CompoundedComponent = typeof InternalCheckbox & { Group: typeof Group; /** @internal */ __ANT_CHECKBOX: boolean; diff --git a/components/checkbox/index.zh-CN.md b/components/checkbox/index.zh-CN.md index 6f3caf508fe7..e82a4e335edb 100644 --- a/components/checkbox/index.zh-CN.md +++ b/components/checkbox/index.zh-CN.md @@ -1,16 +1,15 @@ --- category: Components -subtitle: 多选框 group: 数据录入 title: Checkbox +subtitle: 多选框 +description: 收集用户的多项选择。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*DzgiRbW3khIAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*G3MjTYXL6AIAAAAAAAAAAAAADrJ8AQ/original demo: cols: 2 --- -多选框。 - ## 何时使用 - 在一组可选项中进行多项选择时; @@ -53,7 +52,7 @@ demo: | name | CheckboxGroup 下所有 `input[type="checkbox"]` 的 `name` 属性 | string | - | | | options | 指定可选项 | string\[] \| number\[] \| Option\[] | \[] | | | value | 指定选中的选项 | (string \| number \| boolean)\[] | \[] | | -| onChange | 变化时的回调函数 | (checkedValue: CheckboxValueType[]) => void | - | | +| onChange | 变化时的回调函数 | (checkedValue: T[]) => void | - | | ##### Option @@ -69,10 +68,11 @@ interface Option { #### Checkbox -| 名称 | 描述 | 版本 | -| ------- | -------- | ---- | -| blur() | 移除焦点 | | -| focus() | 获取焦点 | | +| 名称 | 描述 | 版本 | +| ------------- | ------------------------- | ------ | +| blur() | 移除焦点 | | +| focus() | 获取焦点 | | +| nativeElement | 返回 Checkbox 的 DOM 节点 | 5.17.3 | ## 主题变量(Design Token) diff --git a/components/checkbox/style/index.ts b/components/checkbox/style/index.ts index ae4a2dfc7c67..aedb8aa340b1 100644 --- a/components/checkbox/style/index.ts +++ b/components/checkbox/style/index.ts @@ -4,10 +4,23 @@ import { genFocusOutline, resetComponent } from '../../style'; import type { FullToken, GenerateStyle } from '../../theme/internal'; import { genStyleHooks, mergeToken } from '../../theme/internal'; +// biome-ignore lint/suspicious/noEmptyInterface: ComponentToken need to be empty by default export interface ComponentToken {} +/** + * @desc Checkbox 组件的 Token + * @descEN Token for Checkbox component + */ interface CheckboxToken extends FullToken<'Checkbox'> { + /** + * @desc Checkbox 类名 + * @descEN Checkbox class name + */ checkboxCls: string; + /** + * @desc Checkbox 尺寸 + * @descEN Size of Checkbox + */ checkboxSize: number; } @@ -188,8 +201,8 @@ export const genCheckboxStyle: GenerateStyle = (token) => { '&-indeterminate': { // Wrapper > Checkbox > inner [`${checkboxCls}-inner`]: { - backgroundColor: token.colorBgContainer, - borderColor: token.colorBorder, + backgroundColor: `${token.colorBgContainer} !important`, + borderColor: `${token.colorBorder} !important`, '&:after': { top: '50%', @@ -203,6 +216,12 @@ export const genCheckboxStyle: GenerateStyle = (token) => { content: '""', }, }, + + // https://github.com/ant-design/ant-design/issues/50074 + [`&:hover ${checkboxCls}-inner`]: { + backgroundColor: `${token.colorBgContainer} !important`, + borderColor: `${token.colorPrimary} !important`, + }, }, }, }, diff --git a/components/col/index.ts b/components/col/index.ts deleted file mode 100644 index 413ae4d2be25..000000000000 --- a/components/col/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Col, type ColProps, type ColSize } from '../grid'; - -export type { ColProps, ColSize }; - -export default Col; diff --git a/components/col/index.tsx b/components/col/index.tsx new file mode 100644 index 000000000000..92e1f5e8fd18 --- /dev/null +++ b/components/col/index.tsx @@ -0,0 +1,6 @@ +import { Col } from '../grid'; +import type { ColProps, ColSize } from '../grid'; + +export type { ColProps, ColSize }; + +export default Col; diff --git a/components/collapse/Collapse.tsx b/components/collapse/Collapse.tsx index 985c785ddaef..ae24f7480299 100644 --- a/components/collapse/Collapse.tsx +++ b/components/collapse/Collapse.tsx @@ -105,7 +105,10 @@ const Collapse = React.forwardRef((props, ref) => typeof mergedExpandIcon === 'function' ? ( mergedExpandIcon(panelProps) ) : ( - + ); return cloneElement(icon, () => ({ className: classNames((icon as React.ReactElement)?.props?.className, `${prefixCls}-arrow`), @@ -155,6 +158,7 @@ const Collapse = React.forwardRef((props, ref) => ); return wrapCSSVar( + // @ts-ignore @@ -59,7 +59,7 @@ exports[`renders components/collapse/demo/accordion.tsx extend context correctly class="ant-collapse-expand-icon" > @@ -99,7 +99,7 @@ exports[`renders components/collapse/demo/accordion.tsx extend context correctly class="ant-collapse-expand-icon" > @@ -148,7 +148,7 @@ exports[`renders components/collapse/demo/basic.tsx extend context correctly 1`] class="ant-collapse-expand-icon" > @@ -202,7 +202,7 @@ exports[`renders components/collapse/demo/basic.tsx extend context correctly 1`] class="ant-collapse-expand-icon" > @@ -242,7 +242,7 @@ exports[`renders components/collapse/demo/basic.tsx extend context correctly 1`] class="ant-collapse-expand-icon" > @@ -291,7 +291,7 @@ exports[`renders components/collapse/demo/borderless.tsx extend context correctl class="ant-collapse-expand-icon" > @@ -324,7 +324,7 @@ exports[`renders components/collapse/demo/borderless.tsx extend context correctl class="ant-collapse-content-box" >

A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a welcome guest in many households across the world.

@@ -345,7 +345,7 @@ exports[`renders components/collapse/demo/borderless.tsx extend context correctl class="ant-collapse-expand-icon" > @@ -385,7 +385,7 @@ exports[`renders components/collapse/demo/borderless.tsx extend context correctl class="ant-collapse-expand-icon" > @@ -438,7 +438,7 @@ exports[`renders components/collapse/demo/collapsible.tsx extend context correct class="ant-collapse-expand-icon" > @@ -498,7 +498,7 @@ exports[`renders components/collapse/demo/collapsible.tsx extend context correct class="ant-collapse-expand-icon" > @@ -560,7 +560,7 @@ exports[`renders components/collapse/demo/collapsible.tsx extend context correct class="ant-collapse-expand-icon" > @@ -611,7 +611,7 @@ exports[`renders components/collapse/demo/component-token.tsx extend context cor class="ant-collapse-expand-icon" > @@ -651,7 +651,7 @@ exports[`renders components/collapse/demo/component-token.tsx extend context cor class="ant-collapse-expand-icon" > @@ -691,7 +691,7 @@ exports[`renders components/collapse/demo/component-token.tsx extend context cor class="ant-collapse-expand-icon" > @@ -888,7 +888,7 @@ Array [ class="ant-collapse-expand-icon" > @@ -966,7 +966,7 @@ Array [ class="ant-collapse-expand-icon" > @@ -1030,7 +1030,7 @@ Array [ class="ant-collapse-expand-icon" > @@ -1083,7 +1083,7 @@ Array [
,
, - Expand Icon Position: + Expand Icon Position: ,
@@ -1303,7 +1303,7 @@ exports[`renders components/collapse/demo/ghost.tsx extend context correctly 1`] class="ant-collapse-expand-icon" > @@ -1343,7 +1343,7 @@ exports[`renders components/collapse/demo/ghost.tsx extend context correctly 1`] class="ant-collapse-expand-icon" > @@ -1392,7 +1392,7 @@ exports[`renders components/collapse/demo/mix.tsx extend context correctly 1`] = class="ant-collapse-expand-icon" > @@ -1432,7 +1432,7 @@ exports[`renders components/collapse/demo/mix.tsx extend context correctly 1`] = class="ant-collapse-expand-icon" > @@ -1472,7 +1472,7 @@ exports[`renders components/collapse/demo/mix.tsx extend context correctly 1`] = class="ant-collapse-expand-icon" > @@ -1521,7 +1521,7 @@ exports[`renders components/collapse/demo/noarrow.tsx extend context correctly 1 class="ant-collapse-expand-icon" > @@ -1612,7 +1612,7 @@ Array [ class="ant-collapse-expand-icon" > @@ -1666,7 +1666,7 @@ Array [ class="ant-collapse-expand-icon" > @@ -1720,7 +1720,7 @@ Array [ class="ant-collapse-expand-icon" > diff --git a/components/collapse/__tests__/__snapshots__/demo.test.ts.snap b/components/collapse/__tests__/__snapshots__/demo.test.ts.snap index 76bc68bfc327..c1264bfec1f7 100644 --- a/components/collapse/__tests__/__snapshots__/demo.test.ts.snap +++ b/components/collapse/__tests__/__snapshots__/demo.test.ts.snap @@ -19,7 +19,7 @@ exports[`renders components/collapse/demo/accordion.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -59,7 +59,7 @@ exports[`renders components/collapse/demo/accordion.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -99,7 +99,7 @@ exports[`renders components/collapse/demo/accordion.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -146,7 +146,7 @@ exports[`renders components/collapse/demo/basic.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -200,7 +200,7 @@ exports[`renders components/collapse/demo/basic.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -240,7 +240,7 @@ exports[`renders components/collapse/demo/basic.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -287,7 +287,7 @@ exports[`renders components/collapse/demo/borderless.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -320,7 +320,7 @@ exports[`renders components/collapse/demo/borderless.tsx correctly 1`] = ` class="ant-collapse-content-box" >

A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a welcome guest in many households across the world.

@@ -341,7 +341,7 @@ exports[`renders components/collapse/demo/borderless.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -381,7 +381,7 @@ exports[`renders components/collapse/demo/borderless.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -432,7 +432,7 @@ exports[`renders components/collapse/demo/collapsible.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -492,7 +492,7 @@ exports[`renders components/collapse/demo/collapsible.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -554,7 +554,7 @@ exports[`renders components/collapse/demo/collapsible.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -603,7 +603,7 @@ exports[`renders components/collapse/demo/component-token.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -643,7 +643,7 @@ exports[`renders components/collapse/demo/component-token.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -683,7 +683,7 @@ exports[`renders components/collapse/demo/component-token.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -876,7 +876,7 @@ Array [ class="ant-collapse-expand-icon" > @@ -954,7 +954,7 @@ Array [ class="ant-collapse-expand-icon" > @@ -1018,7 +1018,7 @@ Array [ class="ant-collapse-expand-icon" > @@ -1071,7 +1071,7 @@ Array [
,
, - Expand Icon Position: + Expand Icon Position: ,
@@ -1208,7 +1208,7 @@ exports[`renders components/collapse/demo/ghost.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -1248,7 +1248,7 @@ exports[`renders components/collapse/demo/ghost.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -1295,7 +1295,7 @@ exports[`renders components/collapse/demo/mix.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -1335,7 +1335,7 @@ exports[`renders components/collapse/demo/mix.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -1375,7 +1375,7 @@ exports[`renders components/collapse/demo/mix.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -1422,7 +1422,7 @@ exports[`renders components/collapse/demo/noarrow.tsx correctly 1`] = ` class="ant-collapse-expand-icon" > @@ -1511,7 +1511,7 @@ Array [ class="ant-collapse-expand-icon" > @@ -1565,7 +1565,7 @@ Array [ class="ant-collapse-expand-icon" > @@ -1619,7 +1619,7 @@ Array [ class="ant-collapse-expand-icon" > diff --git a/components/collapse/__tests__/__snapshots__/index.test.tsx.snap b/components/collapse/__tests__/__snapshots__/index.test.tsx.snap index a52b278f8d41..332e3e7ec446 100644 --- a/components/collapse/__tests__/__snapshots__/index.test.tsx.snap +++ b/components/collapse/__tests__/__snapshots__/index.test.tsx.snap @@ -18,7 +18,7 @@ exports[`Collapse Collapse.Panel usage 1`] = ` class="ant-collapse-expand-icon" > @@ -58,7 +58,7 @@ exports[`Collapse Collapse.Panel usage 1`] = ` class="ant-collapse-expand-icon" > @@ -105,7 +105,7 @@ exports[`Collapse could override default openMotion 1`] = ` class="ant-collapse-expand-icon" > @@ -162,7 +162,7 @@ exports[`Collapse should render extra node of panel 1`] = ` class="ant-collapse-expand-icon" > @@ -211,7 +211,7 @@ exports[`Collapse should render extra node of panel 1`] = ` class="ant-collapse-expand-icon" > diff --git a/components/collapse/__tests__/index.test.tsx b/components/collapse/__tests__/index.test.tsx index a7e23f68e618..30bb3b959e81 100644 --- a/components/collapse/__tests__/index.test.tsx +++ b/components/collapse/__tests__/index.test.tsx @@ -1,9 +1,7 @@ import React from 'react'; -import { act } from 'react-dom/test-utils'; - -import { fireEvent, render, waitFakeTimer } from '../../../tests/utils'; import { resetWarned } from '../../_util/warning'; +import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils'; describe('Collapse', () => { // eslint-disable-next-line global-require @@ -158,9 +156,9 @@ describe('Collapse', () => { jest.useFakeTimers(); const spiedRAF = jest .spyOn(window, 'requestAnimationFrame') - .mockImplementation((cb) => setTimeout(cb, 16.66)); + .mockImplementation((cb) => setTimeout(cb, 1000 / 60)); - let setActiveKeyOuter: React.Dispatch>; + let setActiveKeyOuter: React.Dispatch>; const Test: React.FC = () => { const [activeKey, setActiveKey] = React.useState(); setActiveKeyOuter = setActiveKey; @@ -253,4 +251,28 @@ describe('Collapse', () => { ); expect(container.firstChild).toMatchSnapshot(); }); + + it('Check expandIcon aria-label value', () => { + const { container, rerender } = render( + + + , + ); + + expect(container.querySelector('.ant-collapse-arrow')).toHaveAttribute( + 'aria-label', + 'expanded', + ); + + rerender( + + + , + ); + + expect(container.querySelector('.ant-collapse-arrow')).toHaveAttribute( + 'aria-label', + 'collapsed', + ); + }); }); diff --git a/components/collapse/demo/accordion.md b/components/collapse/demo/accordion.md index c47032a45f1c..f1ff14896ac0 100644 --- a/components/collapse/demo/accordion.md +++ b/components/collapse/demo/accordion.md @@ -1,6 +1,6 @@ ## zh-CN -手风琴,每次只打开一个 tab。 +手风琴模式,始终只有一个面板处在激活状态。 ## en-US diff --git a/components/collapse/demo/borderless.tsx b/components/collapse/demo/borderless.tsx index c2f25fc5d1bf..de4cc543f49a 100644 --- a/components/collapse/demo/borderless.tsx +++ b/components/collapse/demo/borderless.tsx @@ -3,7 +3,7 @@ import type { CollapseProps } from 'antd'; import { Collapse } from 'antd'; const text = ( -

+

A dog is a type of domesticated animal. Known for its loyalty and faithfulness, it can be found as a welcome guest in many households across the world.

diff --git a/components/collapse/demo/collapsible.md b/components/collapse/demo/collapsible.md index 6c5ebe3c406b..66f095e4147f 100644 --- a/components/collapse/demo/collapsible.md +++ b/components/collapse/demo/collapsible.md @@ -7,7 +7,7 @@ Specify the trigger area of collapsible by `collapsible`. diff --git a/components/collapse/demo/component-token.tsx b/components/collapse/demo/component-token.tsx index 8243c56037be..f16bbd037f6c 100644 --- a/components/collapse/demo/component-token.tsx +++ b/components/collapse/demo/component-token.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { Collapse, ConfigProvider } from 'antd'; - /** Test usage. Do not use in your production. */ import type { CollapseProps } from 'antd'; diff --git a/components/collapse/demo/custom.tsx b/components/collapse/demo/custom.tsx index d0fad5737eb0..f4c6b7d3a993 100644 --- a/components/collapse/demo/custom.tsx +++ b/components/collapse/demo/custom.tsx @@ -1,6 +1,6 @@ -import { CaretRightOutlined } from '@ant-design/icons'; import type { CSSProperties } from 'react'; import React from 'react'; +import { CaretRightOutlined } from '@ant-design/icons'; import type { CollapseProps } from 'antd'; import { Collapse, theme } from 'antd'; diff --git a/components/collapse/demo/extra.tsx b/components/collapse/demo/extra.tsx index dc2eaca17b33..2e6df64c1dca 100644 --- a/components/collapse/demo/extra.tsx +++ b/components/collapse/demo/extra.tsx @@ -1,5 +1,5 @@ -import { SettingOutlined } from '@ant-design/icons'; import React, { useState } from 'react'; +import { SettingOutlined } from '@ant-design/icons'; import type { CollapseProps } from 'antd'; import { Collapse, Select } from 'antd'; diff --git a/components/collapse/demo/size.md b/components/collapse/demo/size.md index a458ac577cab..4f6a03ae3e71 100644 --- a/components/collapse/demo/size.md +++ b/components/collapse/demo/size.md @@ -2,7 +2,7 @@ 折叠面板有大、中、小三种尺寸。 -通过设置 `size` 为 `large` `small` 分别把折叠面板设为大、小尺寸。若不设置 `size`,则尺寸为中。 +通过设置 `size` 为 `large` `small` 分别把折叠面板设为大、小尺寸。若不设置 `size`,则尺寸默认为中。 ## en-US diff --git a/components/collapse/index.en-US.md b/components/collapse/index.en-US.md index b0ccf9040435..9fa8614a0eec 100644 --- a/components/collapse/index.en-US.md +++ b/components/collapse/index.en-US.md @@ -2,12 +2,11 @@ category: Components group: Data Display title: Collapse +description: A content area which can be collapsed and expanded. cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*B7HKR5OBe8gAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAAAAAAAAAAAADrJ8AQ/original --- -A content area which can be collapsed and expanded. - ## When To Use - Can be used to group or hide complex regions to keep the page clean. @@ -80,7 +79,7 @@ Common props ref:[Common props](/docs/react/common-props) | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | | accordion | If true, Collapse renders as Accordion | boolean | false | | -| activeKey | Key of the active panel | string\[] \| string
number\[] \| number | No default value. In `accordion` mode, it's the key of the first panel | | +| activeKey | Key of the active panel | string\[] \| string
number\[] \| number | No default value. In [accordion mode](#collapse-demo-accordion), it's the key of the first panel | | | bordered | Toggles rendering of the border around the collapse block | boolean | true | | | collapsible | Specify whether the panels of children be collapsible or the trigger area of collapsible | `header` \| `icon` \| `disabled` | - | 4.9.0 | | defaultActiveKey | Key of the initial active panel | string\[] \| string
number\[] \| number | - | | diff --git a/components/collapse/index.ts b/components/collapse/index.tsx similarity index 100% rename from components/collapse/index.ts rename to components/collapse/index.tsx diff --git a/components/collapse/index.zh-CN.md b/components/collapse/index.zh-CN.md index 69772d814498..5b9733e8086a 100644 --- a/components/collapse/index.zh-CN.md +++ b/components/collapse/index.zh-CN.md @@ -3,12 +3,11 @@ category: Components group: 数据展示 title: Collapse subtitle: 折叠面板 +description: 可以折叠/展开的内容区域。 cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*B7HKR5OBe8gAAAAAAAAAAAAADrJ8AQ/original coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*sir-TK0HkWcAAAAAAAAAAAAADrJ8AQ/original --- -可以折叠/展开的内容区域。 - ## 何时使用 - 对复杂区域进行分组和隐藏,保持页面的整洁。 @@ -81,7 +80,7 @@ const items: CollapseProps['items'] = [ | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | | accordion | 手风琴模式 | boolean | false | | -| activeKey | 当前激活 tab 面板的 key | string\[] \| string
number\[] \| number | 默认无,accordion 模式下默认第一个元素 | | +| activeKey | 当前激活 tab 面板的 key | string\[] \| string
number\[] \| number | [手风琴模式](#collapse-demo-accordion)下默认第一个元素 | | | bordered | 带边框风格的折叠面板 | boolean | true | | | collapsible | 所有子面板是否可折叠或指定可折叠触发区域 | `header` \| `icon` \| `disabled` | - | 4.9.0 | | defaultActiveKey | 初始化选中面板的 key | string\[] \| string
number\[] \| number | - | | diff --git a/components/collapse/style/index.ts b/components/collapse/style/index.ts index 5bc45830331c..050a48432e28 100644 --- a/components/collapse/style/index.ts +++ b/components/collapse/style/index.ts @@ -20,7 +20,7 @@ export interface ComponentToken { */ headerBg: string; /** - * @desc 折叠面板内容内部编辑 + * @desc 折叠面板内容内边距 * @descEN Padding of content */ contentPadding: CSSProperties['padding']; @@ -32,8 +32,20 @@ export interface ComponentToken { } type CollapseToken = FullToken<'Collapse'> & { + /** + * @desc 小号折叠面板头部内边距 + * @descEN Padding of small header + */ collapseHeaderPaddingSM: string; + /** + * @desc 大号折叠面板头部内边距 + * @descEN Padding of large header + */ collapseHeaderPaddingLG: string; + /** + * @desc 折叠面板边框圆角 + * @descEN Border radius of collapse panel + */ collapsePanelBorderRadius: number; }; @@ -75,16 +87,15 @@ export const genBaseStyle: GenerateStyle = (token) => { ...resetComponent(token), backgroundColor: headerBg, border: borderBase, - borderBottom: 0, borderRadius: collapsePanelBorderRadius, - [`&-rtl`]: { + '&-rtl': { direction: 'rtl', }, [`& > ${componentCls}-item`]: { borderBottom: borderBase, - [`&:last-child`]: { + '&:last-child': { [` &, & > ${componentCls}-header`]: { @@ -124,7 +135,9 @@ export const genBaseStyle: GenerateStyle = (token) => { [`${componentCls}-arrow`]: { ...resetIcon(), fontSize: fontSizeIcon, - + // when `transform: rotate()` is applied to icon's root element + transition: `transform ${motionDurationSlow}`, + // when `transform: rotate()` is applied to icon's child element svg: { transition: `transform ${motionDurationSlow}`, }, @@ -154,12 +167,12 @@ export const genBaseStyle: GenerateStyle = (token) => { padding: contentPadding, }, - [`&-hidden`]: { + '&-hidden': { display: 'none', }, }, - [`&-small`]: { + '&-small': { [`> ${componentCls}-item`]: { [`> ${componentCls}-header`]: { padding: collapseHeaderPaddingSM, @@ -176,7 +189,7 @@ export const genBaseStyle: GenerateStyle = (token) => { }, }, - [`&-large`]: { + '&-large': { [`> ${componentCls}-item`]: { fontSize: fontSizeLG, lineHeight: lineHeightLG, @@ -197,6 +210,8 @@ export const genBaseStyle: GenerateStyle = (token) => { }, [`${componentCls}-item:last-child`]: { + borderBottom: 0, + [`> ${componentCls}-content`]: { borderRadius: `0 0 ${unit(collapsePanelBorderRadius)} ${unit(collapsePanelBorderRadius)}`, }, @@ -231,7 +246,7 @@ export const genBaseStyle: GenerateStyle = (token) => { const genArrowStyle: GenerateStyle = (token) => { const { componentCls } = token; - const fixedSelector = `> ${componentCls}-item > ${componentCls}-header ${componentCls}-arrow svg`; + const fixedSelector = `> ${componentCls}-item > ${componentCls}-header ${componentCls}-arrow`; return { [`${componentCls}-rtl`]: { diff --git a/components/color-picker/ColorPicker.tsx b/components/color-picker/ColorPicker.tsx index c3feb9704274..d82a10a0e26c 100644 --- a/components/color-picker/ColorPicker.tsx +++ b/components/color-picker/ColorPicker.tsx @@ -1,12 +1,8 @@ -import type { CSSProperties, FC } from 'react'; -import React, { useContext, useMemo, useRef, useState } from 'react'; -import type { - HsbaColorType, - ColorPickerProps as RcColorPickerProps, -} from '@rc-component/color-picker'; +import React, { useContext, useMemo } from 'react'; import classNames from 'classnames'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; +import ContextIsolator from '../_util/ContextIsolator'; import genPurePanel from '../_util/PurePanel'; import { getStatusClassNames } from '../_util/statusUtils'; import { devUseWarning } from '../_util/warning'; @@ -15,57 +11,18 @@ import { ConfigContext } from '../config-provider/context'; import DisabledContext from '../config-provider/DisabledContext'; import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; import useSize from '../config-provider/hooks/useSize'; -import type { SizeType } from '../config-provider/SizeContext'; -import { FormItemInputContext, NoFormStyle } from '../form/context'; +import { FormItemInputContext } from '../form/context'; import type { PopoverProps } from '../popover'; import Popover from '../popover'; -import type { Color } from './color'; +import { useCompactItemContext } from '../space/Compact'; +import { AggregationColor } from './color'; +import type { ColorPickerPanelProps } from './ColorPickerPanel'; import ColorPickerPanel from './ColorPickerPanel'; import ColorTrigger from './components/ColorTrigger'; -import useColorState from './hooks/useColorState'; -import type { - ColorFormat, - ColorPickerBaseProps, - ColorValueType, - PresetsItem, - TriggerPlacement, - TriggerType, -} from './interface'; +import useModeColor from './hooks/useModeColor'; +import type { ColorPickerProps, ModeType, TriggerPlacement } from './interface'; import useStyle from './style'; -import { genAlphaColor, generateColor, getAlphaColor } from './util'; - -export type ColorPickerProps = Omit< - RcColorPickerProps, - 'onChange' | 'value' | 'defaultValue' | 'panelRender' | 'disabledAlpha' | 'onChangeComplete' -> & { - value?: ColorValueType; - defaultValue?: ColorValueType; - children?: React.ReactNode; - open?: boolean; - disabled?: boolean; - placement?: TriggerPlacement; - trigger?: TriggerType; - format?: keyof typeof ColorFormat; - defaultFormat?: keyof typeof ColorFormat; - allowClear?: boolean; - presets?: PresetsItem[]; - arrow?: boolean | { pointAtCenter: boolean }; - panelRender?: ( - panel: React.ReactNode, - extra: { components: { Picker: FC; Presets: FC } }, - ) => React.ReactNode; - showText?: boolean | ((color: Color) => React.ReactNode); - size?: SizeType; - styles?: { popup?: CSSProperties; popupOverlayInner?: CSSProperties }; - rootClassName?: string; - disabledAlpha?: boolean; - [key: `data-${string}`]: string; - onOpenChange?: (open: boolean) => void; - onFormatChange?: (format: ColorFormat) => void; - onChange?: (value: Color, hex: string) => void; - onClear?: () => void; - onChangeComplete?: (value: Color) => void; -} & Pick; +import { genAlphaColor, generateColor, getColorAlpha } from './util'; type CompoundedComponent = React.FC & { _InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel; @@ -73,6 +30,7 @@ type CompoundedComponent = React.FC & { const ColorPicker: CompoundedComponent = (props) => { const { + mode, value, defaultValue, format, @@ -109,10 +67,6 @@ const ColorPicker: CompoundedComponent = (props) => { const contextDisabled = useContext(DisabledContext); const mergedDisabled = disabled ?? contextDisabled; - const [colorValue, setColorValue] = useColorState('', { - value, - defaultValue, - }); const [popupOpen, setPopupOpen] = useMergedState(false, { value: open, postState: (openData) => !mergedDisabled && openData, @@ -124,35 +78,118 @@ const ColorPicker: CompoundedComponent = (props) => { onChange: onFormatChange, }); - const [colorCleared, setColorCleared] = useState(!value && !defaultValue); - const prefixCls = getPrefixCls('color-picker', customizePrefixCls); - const isAlphaColor = useMemo(() => getAlphaColor(colorValue) < 100, [colorValue]); + // ================== Value & Mode ================= + const [mergedColor, setColor, modeState, setModeState, modeOptions] = useModeColor( + defaultValue, + value, + mode, + ); + + const isAlphaColor = useMemo(() => getColorAlpha(mergedColor) < 100, [mergedColor]); + + // ==================== Change ===================== + // To enhance user experience, we cache the gradient color when switch from gradient to single + // If user not modify single color, we will use the cached gradient color. + const [cachedGradientColor, setCachedGradientColor] = React.useState( + null, + ); + + const onInternalChangeComplete: ColorPickerProps['onChangeComplete'] = (color) => { + if (onChangeComplete) { + let changeColor = generateColor(color); + + // ignore alpha color + if (disabledAlpha && isAlphaColor) { + changeColor = genAlphaColor(color); + } + onChangeComplete(changeColor); + } + }; + + const onInternalChange: ColorPickerPanelProps['onChange'] = (data, pickColor) => { + let color: AggregationColor = generateColor(data as AggregationColor); + + // ignore alpha color + if (disabledAlpha && isAlphaColor) { + color = genAlphaColor(color); + } + + setColor(color); + setCachedGradientColor(null); + + // Trigger change event + if (onChange) { + onChange(color, color.toCssString()); + } + + // Only for drag-and-drop color picking + if (!pickColor) { + onInternalChangeComplete(color); + } + }; + + // =================== Gradient ==================== + const [activeIndex, setActiveIndex] = React.useState(0); + const [gradientDragging, setGradientDragging] = React.useState(false); + + // Mode change should also trigger color change + const onInternalModeChange = (newMode: ModeType) => { + setModeState(newMode); + + if (newMode === 'single' && mergedColor.isGradient()) { + setActiveIndex(0); + onInternalChange(new AggregationColor(mergedColor.getColors()[0].color)); - // ===================== Form Status ===================== + // Should after `onInternalChange` since it will clear the cached color + setCachedGradientColor(mergedColor); + } else if (newMode === 'gradient' && !mergedColor.isGradient()) { + const baseColor = isAlphaColor ? genAlphaColor(mergedColor) : mergedColor; + + onInternalChange( + new AggregationColor( + cachedGradientColor || [ + { + percent: 0, + color: baseColor, + }, + { + percent: 100, + color: baseColor, + }, + ], + ), + ); + } + }; + + // ================== Form Status ================== const { status: contextStatus } = React.useContext(FormItemInputContext); + // ==================== Compact ==================== + const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction); + // ===================== Style ===================== - const mergedSize = useSize(customizeSize); + const mergedSize = useSize((ctx) => customizeSize ?? compactSize ?? ctx); + const rootCls = useCSSVarCls(prefixCls); const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls); const rtlCls = { [`${prefixCls}-rtl`]: direction }; - const mergeRootCls = classNames(rootClassName, cssVarCls, rootCls, rtlCls); - const mergeCls = classNames( + const mergedRootCls = classNames(rootClassName, cssVarCls, rootCls, rtlCls); + const mergedCls = classNames( getStatusClassNames(prefixCls, contextStatus), { [`${prefixCls}-sm`]: mergedSize === 'small', [`${prefixCls}-lg`]: mergedSize === 'large', }, + compactItemClassnames, colorPicker?.className, - mergeRootCls, + mergedRootCls, className, hashId, ); - const mergePopupCls = classNames(prefixCls, mergeRootCls); - - const popupAllowCloseRef = useRef(true); + const mergedPopupCls = classNames(prefixCls, mergedRootCls); // ===================== Warning ====================== if (process.env.NODE_ENV !== 'production') { @@ -165,47 +202,6 @@ const ColorPicker: CompoundedComponent = (props) => { ); } - const handleChange = (data: Color, type?: HsbaColorType, pickColor?: boolean) => { - let color: Color = generateColor(data); - const isNull = value === null || (!value && defaultValue === null); - if (colorCleared || isNull) { - setColorCleared(false); - // ignore alpha slider - if (getAlphaColor(colorValue) === 0 && type !== 'alpha') { - color = genAlphaColor(color); - } - } - // ignore alpha color - if (disabledAlpha && isAlphaColor) { - color = genAlphaColor(color); - } - - // Only for drag-and-drop color picking - if (pickColor) { - popupAllowCloseRef.current = false; - } else { - onChangeComplete?.(color); - } - - setColorValue(color); - onChange?.(color, color.toHexString()); - }; - - const handleClear = () => { - setColorCleared(true); - onClear?.(); - }; - - const handleChangeComplete: ColorPickerProps['onChangeComplete'] = (color) => { - popupAllowCloseRef.current = true; - let changeColor = generateColor(color); - // ignore alpha color - if (disabledAlpha && isAlphaColor) { - changeColor = genAlphaColor(color); - } - onChangeComplete?.(changeColor); - }; - const popoverProps: PopoverProps = { open: popupOpen, trigger, @@ -217,20 +213,6 @@ const ColorPicker: CompoundedComponent = (props) => { destroyTooltipOnHide, }; - const colorBaseProps: ColorPickerBaseProps = { - prefixCls, - color: colorValue, - allowClear, - colorCleared, - disabled: mergedDisabled, - disabledAlpha, - presets, - panelRender, - format: formatValue, - onFormatChange: setFormatValue, - onChangeComplete: handleChangeComplete, - }; - const mergedStyle: React.CSSProperties = { ...colorPicker?.style, ...style }; // ============================ zIndex ============================ @@ -240,35 +222,50 @@ const ColorPicker: CompoundedComponent = (props) => { style={styles?.popup} overlayInnerStyle={styles?.popupOverlayInner} onOpenChange={(visible) => { - if (popupAllowCloseRef.current && !mergedDisabled) { + if (!visible || !mergedDisabled) { setPopupOpen(visible); } }} content={ - + - + } - overlayClassName={mergePopupCls} + overlayClassName={mergedPopupCls} {...popoverProps} > {children || ( )} , diff --git a/components/color-picker/ColorPickerPanel.tsx b/components/color-picker/ColorPickerPanel.tsx index fb837198c63b..03af006f1e6f 100644 --- a/components/color-picker/ColorPickerPanel.tsx +++ b/components/color-picker/ColorPickerPanel.tsx @@ -1,39 +1,91 @@ -import type { HsbaColorType } from '@rc-component/color-picker'; import type { FC } from 'react'; import React from 'react'; + import Divider from '../divider'; -import type { Color } from './color'; import PanelPicker from './components/PanelPicker'; import PanelPresets from './components/PanelPresets'; -import { PanelPickerProvider, PanelPresetsProvider } from './context'; -import type { ColorPickerBaseProps } from './interface'; +import { PanelPickerContext, PanelPresetsContext } from './context'; +import type { PanelPickerContextProps, PanelPresetsContextProps } from './context'; +import type { ColorPickerProps } from './interface'; -interface ColorPickerPanelProps extends ColorPickerBaseProps { - onChange?: (value?: Color, type?: HsbaColorType, pickColor?: boolean) => void; +export interface ColorPickerPanelProps + extends PanelPickerContextProps, + Omit { onClear?: () => void; + panelRender?: ColorPickerProps['panelRender']; } const ColorPickerPanel: FC = (props) => { - const { prefixCls, presets, panelRender, color, onChange, onClear, ...injectProps } = props; - const colorPickerPanelPrefixCls = `${prefixCls}-inner`; - - // ==== Inject props === - const panelPickerProps = { + const { prefixCls, - value: color, + presets, + panelRender, + value, onChange, onClear, - ...injectProps, - }; + allowClear, + disabledAlpha, + mode, + onModeChange, + modeOptions, + onChangeComplete, + activeIndex, + onActive, + format, + onFormatChange, + gradientDragging, + onGradientDragging, + } = props; + const colorPickerPanelPrefixCls = `${prefixCls}-inner`; + + // ===================== Context ====================== + const panelContext: PanelPickerContextProps = React.useMemo( + () => ({ + prefixCls, + value, + onChange, + onClear, + allowClear, + disabledAlpha, + mode, + onModeChange, + modeOptions, + onChangeComplete, + activeIndex, + onActive, + format, + onFormatChange, + gradientDragging, + onGradientDragging, + }), + [ + prefixCls, + value, + onChange, + onClear, + allowClear, + disabledAlpha, + mode, + onModeChange, + modeOptions, + onChangeComplete, + activeIndex, + onActive, + format, + onFormatChange, + gradientDragging, + onGradientDragging, + ], + ); - const panelPresetsProps = React.useMemo( + const presetContext: PanelPresetsContextProps = React.useMemo( () => ({ prefixCls, - value: color, + value, presets, onChange, }), - [prefixCls, color, presets, onChange], + [prefixCls, value, presets, onChange], ); // ====================== Render ====================== @@ -46,8 +98,8 @@ const ColorPickerPanel: FC = (props) => { ); return ( - - + +
{typeof panelRender === 'function' ? panelRender(innerPanel, { @@ -58,8 +110,8 @@ const ColorPickerPanel: FC = (props) => { }) : innerPanel}
-
-
+ + ); }; diff --git a/components/color-picker/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/color-picker/__tests__/__snapshots__/demo-extend.test.ts.snap index 8823f8d26eee..1397dcd7af2f 100644 --- a/components/color-picker/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/color-picker/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -39,8 +39,12 @@ Array [ class="ant-color-picker-inner-content" >
+ class="ant-color-picker-operation" + > +
+
@@ -52,7 +56,7 @@ Array [ style="position: relative;" >
-
-
-
-
-
+ class="ant-slider-rail ant-color-picker-slider-rail" + /> +
+
-
-
-
-
-
+ class="ant-slider-rail ant-color-picker-slider-rail" + /> +
+
-
-
-
-
-
+ class="ant-slider-rail ant-color-picker-slider-rail" + /> +
+
-
-
-
-
-
+ class="ant-slider-rail ant-color-picker-slider-rail" + /> +
+
+exports[`renders components/color-picker/demo/controlled.tsx extend context correctly 1`] = ` +Array [
@@ -792,7 +794,7 @@ exports[`renders components/color-picker/demo/change-completed.tsx extend contex style="background: rgb(22, 119, 255);" />
-
+
,
-
-
-
-
-
+ class="ant-slider-rail ant-color-picker-slider-rail" + /> +
+
-
-
-
-
-
+ class="ant-slider-rail ant-color-picker-slider-rail" + /> +
+
-
-
+
, +] `; -exports[`renders components/color-picker/demo/change-completed.tsx extend context correctly 2`] = `[]`; +exports[`renders components/color-picker/demo/controlled.tsx extend context correctly 2`] = `[]`; -exports[`renders components/color-picker/demo/controlled.tsx extend context correctly 1`] = ` +exports[`renders components/color-picker/demo/disabled.tsx extend context correctly 1`] = ` Array [
+
+ #1677FF +
,
-
-
-
-
-
+ class="ant-slider-rail ant-color-picker-slider-rail" + /> +
+
-
-
-
-
-
+ class="ant-slider-rail ant-color-picker-slider-rail" + /> +
+
-
- #1677FF -
,
-
-
-
-
-
-
-
+ class="ant-slider-rail ant-color-picker-slider-rail" + />
-
-
-
-
-
+ class="ant-slider-step" + /> +
-
-
- - - - - - - - - - -
-
- -
-
@@ -1943,439 +1848,149 @@ Array [ ] `; -exports[`renders components/color-picker/demo/disabled.tsx extend context correctly 2`] = `[]`; +exports[`renders components/color-picker/demo/disabled-alpha.tsx extend context correctly 2`] = `[]`; -exports[`renders components/color-picker/demo/disabled-alpha.tsx extend context correctly 1`] = ` -Array [ -
-
-
-
-
, +exports[`renders components/color-picker/demo/format.tsx extend context correctly 1`] = ` +
-