diff --git a/.codecov.yml b/.codecov.yml index 696dbf31af92..f972a0d9d300 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -3,4 +3,4 @@ coverage: project: default: # Fail the status if coverage drops by >= 0.1% - threshold: 0.1 + threshold: 1% diff --git a/.dumi/theme/builtins/Previewer.tsx b/.dumi/theme/builtins/Previewer.tsx deleted file mode 100644 index 5afa590a8f5e..000000000000 --- a/.dumi/theme/builtins/Previewer.tsx +++ /dev/null @@ -1,40 +0,0 @@ -// @ts-ignore -import PreView, { IPreviewerProps } from 'dumi-theme-default/src/builtins/Previewer'; -import LazyLoad from 'react-lazyload'; -export default ({ - children, - ...rest -}: IPreviewerProps & { - height: string; - iframe: string; -}) => { - if (process.env.NODE_ENV === 'production') { - return ( - -
- {children} -
-
- ); - } - - return ( - - -
- {children} -
-
-
- ); -}; diff --git a/.dumi/theme/builtins/Previewer/index.tsx b/.dumi/theme/builtins/Previewer/index.tsx new file mode 100644 index 000000000000..d0fd811a4b32 --- /dev/null +++ b/.dumi/theme/builtins/Previewer/index.tsx @@ -0,0 +1,12 @@ +//@ts-ignore +import Previewer from 'dumi/theme-original/builtins/Previewer'; + +import DemoProvider from '../../components/DemoProvider'; + +const Page: React.FC = (props) => ( + + + +); + +export default Page; diff --git a/.dumi/theme/builtins/SourceCode/index.tsx b/.dumi/theme/builtins/SourceCode/index.tsx new file mode 100644 index 000000000000..93e4b2e8ba11 --- /dev/null +++ b/.dumi/theme/builtins/SourceCode/index.tsx @@ -0,0 +1,80 @@ +import { CheckOutlined, CopyOutlined } from '@ant-design/icons'; +import { Button, ConfigProvider, Tooltip } from 'antd'; +import { createStyles } from 'antd-style'; +import copy from 'copy-to-clipboard'; +import { FC } from 'react'; +import Highlighter, { HighlighterProps } from '../../components/Highlighter'; +import { useCopied } from '../../hooks/useCopied'; + +const useStyles = createStyles(({ token, css, cx }) => { + const prefixCls = 'source-code'; + const buttonHoverCls = `${prefixCls}-hover-btn`; + + return { + container: cx( + prefixCls, + css` + position: relative; + + pre { + background: ${token.colorFillTertiary} !important; + border-radius: 8px; + padding: 12px !important; + } + + &:hover { + .${buttonHoverCls} { + opacity: 1; + } + } + `, + ), + button: cx( + buttonHoverCls, + css` + opacity: 0; + position: absolute; + right: 8px; + top: 8px; + `, + ), + }; +}); + +const SourceCode: FC = ({ children, language }) => { + const { copied, setCopied } = useCopied(); + const { styles, theme } = useStyles(); + + return ( +
+ + 复制成功 + + ) : ( + '复制' + ) + } + > + +
+ ); +}; + +export default SourceCode; diff --git a/.dumi/theme/components/ApiHeader/NpmFilled.tsx b/.dumi/theme/components/ApiHeader/NpmFilled.tsx new file mode 100644 index 000000000000..a2f16a1687a1 --- /dev/null +++ b/.dumi/theme/components/ApiHeader/NpmFilled.tsx @@ -0,0 +1,15 @@ +export default () => ( + + + +); diff --git a/.dumi/theme/components/ApiHeader/index.tsx b/.dumi/theme/components/ApiHeader/index.tsx new file mode 100644 index 000000000000..f6f017468e5f --- /dev/null +++ b/.dumi/theme/components/ApiHeader/index.tsx @@ -0,0 +1,80 @@ +import { EditOutlined, GithubFilled } from '@ant-design/icons'; +import { Typography } from 'antd'; +import { styled } from 'antd-style'; +import { FC, memo } from 'react'; +import { Flexbox } from 'react-layout-kit'; + +import Code from '../CodeSnippet'; +import NpmFilled from './NpmFilled'; + +import { useStyles } from './style'; + +const Label = styled(Typography.Text)` + width: 100px; +`; + +interface ApiTitleProps { + title: string; + description?: string; +} +const REPO_BASE = `https://github.com/arvinxx/antd-style`; + +export const ApiHeader: FC = memo(({ title, description }) => { + const { styles, theme } = useStyles(); + + const items = [ + { + label: '引入方法', + import: true, + children: `import { ${title} } from "antd-style";`, + }, + { + label: '源码', + icon: , + children: '查看源码', + url: `${REPO_BASE}/tree/master/src/${title}`, + }, + { + label: '文档', + icon: , + children: '编辑文档', + url: `${REPO_BASE}/tree/master/docs/api/${title}`, + }, + { + label: '产物', + icon: , + children: 'antd-style', + url: 'https://www.npmjs.com/package/antd-style?activeTab=explore', + }, + ]; + + return ( + + {title} + {description && ( +
+ + {description} + +
+ )} + + {items.map((item) => ( + + + {item.import ? ( + {item.children} + ) : ( + + + <>{item.icon} + <>{item.children} + + + )} + + ))} + +
+ ); +}); diff --git a/.dumi/theme/components/ApiHeader/style.ts b/.dumi/theme/components/ApiHeader/style.ts new file mode 100644 index 000000000000..c969f05ae464 --- /dev/null +++ b/.dumi/theme/components/ApiHeader/style.ts @@ -0,0 +1,14 @@ +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(({ css, token, stylish }) => ({ + title: css` + font-family: monospace; + `, + desc: css` + font-size: ${token.fontSizeLG}px; + line-height: ${token.lineHeightLG}px; + `, + text: css` + ${stylish.clickableText} + `, +})); diff --git a/.dumi/theme/components/Burger/index.tsx b/.dumi/theme/components/Burger/index.tsx new file mode 100644 index 000000000000..7c6fe9ad8605 --- /dev/null +++ b/.dumi/theme/components/Burger/index.tsx @@ -0,0 +1,77 @@ +import { Link } from '@@/exports'; +import { Drawer, Menu } from 'antd'; +import isEqual from 'fast-deep-equal'; +import { uniq } from 'lodash'; +import { useState } from 'react'; +import { Center } from 'react-layout-kit'; +import { activePathSel, useSiteStore } from '../../store/useSiteStore'; +import { useStyles } from './style'; + +const Burger = () => { + const [opened, setOpened] = useState(false); + const { styles, cx } = useStyles(); + + const nav = useSiteStore((s) => s.navData, isEqual); + const sidebar = useSiteStore((s) => s.sidebar, isEqual); + const activePath = useSiteStore(activePathSel); + const pathname = useSiteStore((s) => s.location.pathname); + + return ( +
{ + setOpened(!opened); + }} + > +
+ + + ({ + label: {item.title}, + key: item.activePath!, + children: + item.activePath === activePath && + sidebar?.map((group) => { + return ( + !group.link && { + label: group.title, + + type: 'group', + children: group.children.map((item) => ({ + label: ( + { + setOpened(false); + }} + > + {item.title} + + ), + key: `s-${item.link}`, + })), + } + ); + }), + }))} + > + +
+ ); +}; + +export default Burger; diff --git a/.dumi/theme/components/Burger/style.ts b/.dumi/theme/components/Burger/style.ts new file mode 100644 index 000000000000..e81ae32d8c14 --- /dev/null +++ b/.dumi/theme/components/Burger/style.ts @@ -0,0 +1,102 @@ +import { createStyles } from 'antd-style'; +import chroma from 'chroma-js'; + +export const useStyles = createStyles(({ token, prefixCls, cx, css, stylish }) => { + const offset = 6; + + return { + icon: cx( + 'site-burger-icon', + css` + position: relative; + + &, + &::before, + &::after { + display: inline-block; + height: 2px; + background: ${token.colorTextSecondary}; + + width: 16px; + + transition: all 150ms ease; + } + + &::before, + &::after { + position: absolute; + left: 0; + + content: ''; + } + + &::before { + top: ${offset}px; + } + + &::after { + top: -${offset}px; + } + `, + ), + active: css` + &::before, + &::after { + background: ${token.colorText}; + } + & { + background: transparent; + } + + &::before { + top: 0; + transform: rotate(-135deg); + } + + &::after { + top: 0; + transform: rotate(135deg); + } + `, + container: css` + width: ${token.controlHeight}px; + height: ${token.controlHeight}px; + border-radius: ${token.borderRadius}px; + cursor: pointer; + `, + + drawerRoot: css` + top: ${token.headerHeight + 1}px; + + :focus-visible { + outline: none; + } + + .${prefixCls}-drawer { + &-mask { + background: transparent; + backdrop-filter: blur(7px); + background: ${chroma(token.colorBgBase).alpha(0.5).hex()}; + } + + &-content-wrapper { + box-shadow: none; + } + } + `, + drawer: css` + &.${prefixCls}-drawer-content { + background: transparent; + } + `, + + menu: css` + background: transparent; + border-inline-end: transparent !important; + + .${prefixCls}-menu-sub.${prefixCls}-menu-inline { + background: ${chroma(token.colorBgLayout).alpha(0.8).hex()} !important; + } + `, + }; +}); diff --git a/.dumi/theme/components/CodeSnippet/index.tsx b/.dumi/theme/components/CodeSnippet/index.tsx new file mode 100644 index 000000000000..ce53b7d893f3 --- /dev/null +++ b/.dumi/theme/components/CodeSnippet/index.tsx @@ -0,0 +1,41 @@ +import { CheckOutlined } from '@ant-design/icons'; +import { Tooltip } from 'antd'; +import copy from 'copy-to-clipboard'; +import { FC } from 'react'; + +import Highlighter from '../Highlighter'; + +import { useCopied } from '../../hooks/useCopied'; +import { useStyles } from './style'; + +const CodeSnippet: FC<{ children: string }> = ({ children }) => { + const { styles, theme } = useStyles(); + const { copied, setCopied } = useCopied(); + + return ( + + 复制成功 + + ) : ( + '复制' + ) + } + > +
{ + copy(children); + setCopied(); + }} + > + {children} +
+
+ ); +}; + +export default CodeSnippet; diff --git a/.dumi/theme/components/CodeSnippet/style.ts b/.dumi/theme/components/CodeSnippet/style.ts new file mode 100644 index 000000000000..434bafba8f5d --- /dev/null +++ b/.dumi/theme/components/CodeSnippet/style.ts @@ -0,0 +1,20 @@ +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles( + ({ css, token }) => + css` + cursor: pointer; + &:hover { + background: ${token.colorFillSecondary}; + border-radius: 4px; + } + pre { + background: none !important; + padding: 0 !important; + margin: 0; + } + code[class*='language-'] { + background: none; + } + `, +); diff --git a/.dumi/theme/components/DemoProvider/index.tsx b/.dumi/theme/components/DemoProvider/index.tsx new file mode 100644 index 000000000000..78c42e876d25 --- /dev/null +++ b/.dumi/theme/components/DemoProvider/index.tsx @@ -0,0 +1,8 @@ +import { StyleProvider } from 'antd-style'; + +const Page: React.FC<{ + children: React.ReactNode; +}> = ({ children }) => { + return {children}; +}; +export default Page; diff --git a/.dumi/theme/components/GithubButton/index.tsx b/.dumi/theme/components/GithubButton/index.tsx new file mode 100644 index 000000000000..474efec4065d --- /dev/null +++ b/.dumi/theme/components/GithubButton/index.tsx @@ -0,0 +1,19 @@ +import { GithubFilled } from '@ant-design/icons'; +import { Button, Tooltip } from 'antd'; +import { useSiteStore } from '../../store/useSiteStore'; + +const GithubButton = () => { + const repoUrl = useSiteStore((s) => s.siteData.themeConfig?.repoUrl); + + return ( + repoUrl && ( + + + + ); + }, +); + +export default SelectItem; diff --git a/.dumi/theme/components/NativeSelect/SelectItem/style.ts b/.dumi/theme/components/NativeSelect/SelectItem/style.ts new file mode 100644 index 000000000000..a88a36b6cc4a --- /dev/null +++ b/.dumi/theme/components/NativeSelect/SelectItem/style.ts @@ -0,0 +1,45 @@ +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(({ css, cx, token }, prefixCls) => ({ + item: cx( + `${prefixCls}-item`, + + css` + display: block; + all: unset; + width: 100%; + padding: 12px 10px; + border-radius: 5px; + box-sizing: inherit; + user-select: none; + line-height: 1; + scroll-margin: 50px; + + font-weight: normal; + font-family: ${token.fontFamily}; + color: ${token.colorText}; + background: transparent; + &:hover { + background: ${token.colorFillTertiary}; + } + `, + ), + selected: cx( + `${prefixCls}-item-selected`, + css` + color: ${token.colorPrimaryText}; + background: ${token.colorPrimaryBg}; + font-weight: bold; + &:hover { + color: ${token.colorPrimaryTextHover}; + background: ${token.colorPrimaryBgHover}; + } + `, + ), + active: cx( + `${prefixCls}-item-active`, + css` + background: ${token.colorFillTertiary}; + `, + ), +})); diff --git a/.dumi/theme/components/NativeSelect/index.tsx b/.dumi/theme/components/NativeSelect/index.tsx new file mode 100644 index 000000000000..07aedfa6bf24 --- /dev/null +++ b/.dumi/theme/components/NativeSelect/index.tsx @@ -0,0 +1,276 @@ +import { + autoUpdate, + flip, + FloatingFocusManager, + FloatingOverlay, + FloatingPortal, + inner, + offset, + shift, + SideObject, + size, + useClick, + useDismiss, + useFloating, + useInnerOffset, + useInteractions, + useListNavigation, + useRole, + useTypeahead, +} from '@floating-ui/react'; +import { FC, ReactNode, useEffect, useRef, useState } from 'react'; +import { flushSync } from 'react-dom'; +import useControlledState from 'use-merge-value'; + +import SelectItem from './SelectItem'; +import { useStyles } from './style'; + +interface OptionType { + label: ReactNode; + icon?: ReactNode; + value: string | number | null; +} +export interface NativeSelectProps { + value?: number; + options?: OptionType[]; + prefixCls?: string; + onChange?: (index: number) => void; + renderValue?: (index: number) => ReactNode; + renderItem: (item: OptionType, index: number) => ReactNode; +} + +const NativeSelect: FC = ({ + options = [], + value, + prefixCls, + onChange, + renderValue, + renderItem, +}) => { + const cls = prefixCls ?? 'native-select'; + const [selectedIndex, setSelectedIndex] = useControlledState(0, { value, onChange }); + + const { styles } = useStyles(cls); + const listRef = useRef>([]); + const listContentRef = useRef>([]); + const overflowRef = useRef(null); + const allowSelectRef = useRef(false); + const allowMouseUpRef = useRef(true); + const selectTimeoutRef = useRef(); + const scrollRef = useRef(null); + + const [open, setOpen] = useState(false); + const [activeIndex, setActiveIndex] = useState(null); + const [fallback, setFallback] = useState(false); + const [innerOffset, setInnerOffset] = useState(0); + const [touch, setTouch] = useState(false); + const [scrollTop, setScrollTop] = useState(0); + const [blockSelection, setBlockSelection] = useState(false); + + if (!open) { + if (innerOffset !== 0) setInnerOffset(0); + if (fallback) setFallback(false); + if (blockSelection) setBlockSelection(false); + } + + const { x, y, strategy, refs, context, isPositioned } = useFloating({ + placement: 'bottom-start', + open, + onOpenChange: setOpen, + whileElementsMounted: autoUpdate, + middleware: fallback + ? [ + offset(5), + touch ? shift({ crossAxis: true, padding: 10 }) : flip({ padding: 10 }), + size({ + apply({ availableHeight }) { + Object.assign(scrollRef.current?.style ?? {}, { + maxHeight: `${availableHeight}px`, + }); + }, + padding: 10, + }), + ] + : [ + inner({ + listRef, + overflowRef, + scrollRef, + index: selectedIndex, + offset: innerOffset, + onFallbackChange: setFallback, + padding: 10, + minItemsVisible: touch ? 8 : 4, + referenceOverflowThreshold: 20, + }), + offset({ crossAxis: -4 }), + ], + }); + + const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([ + useClick(context, { event: 'mousedown' }), + useDismiss(context), + useRole(context, { role: 'listbox' }), + useInnerOffset(context, { + enabled: !fallback, + onChange: setInnerOffset, + overflowRef, + scrollRef, + }), + useListNavigation(context, { + listRef, + activeIndex, + selectedIndex, + onNavigate: setActiveIndex, + }), + useTypeahead(context, { + listRef: listContentRef, + activeIndex, + onMatch: open ? setActiveIndex : setSelectedIndex, + }), + ]); + + useEffect(() => { + if (open) { + selectTimeoutRef.current = setTimeout(() => { + allowSelectRef.current = true; + }, 300); + + return () => { + clearTimeout(selectTimeoutRef.current); + }; + } else { + allowSelectRef.current = false; + allowMouseUpRef.current = true; + } + return () => []; + }, [open]); + + const handleArrowScroll = (amount: number) => { + if (fallback) { + if (scrollRef.current) { + scrollRef.current.scrollTop -= amount; + flushSync(() => setScrollTop(scrollRef.current?.scrollTop ?? 0)); + } + } else { + flushSync(() => setInnerOffset((value) => value - amount)); + } + }; + + const handleArrowHide = () => { + if (touch) { + clearTimeout(selectTimeoutRef.current); + setBlockSelection(true); + selectTimeoutRef.current = setTimeout(() => { + setBlockSelection(false); + }, 400); + } + }; + + const { label } = options[selectedIndex] || {}; + + return ( + <> + + + + {open && ( + + +
+
+ {options.map((item, i) => { + return ( + { + listRef.current[i] = node; + listContentRef.current[i] = item.label as string; + }} + {...getItemProps({ + onTouchStart() { + allowSelectRef.current = true; + allowMouseUpRef.current = false; + }, + onKeyDown() { + allowSelectRef.current = true; + }, + onClick() { + if (allowSelectRef.current) { + setSelectedIndex(i); + setOpen(false); + } + }, + onMouseUp() { + if (!allowMouseUpRef.current) { + return; + } + + if (allowSelectRef.current) { + setSelectedIndex(i); + setOpen(false); + } + + // On touch devices, prevent the element from + // immediately closing `onClick` by deferring it + clearTimeout(selectTimeoutRef.current); + selectTimeoutRef.current = setTimeout(() => { + allowSelectRef.current = true; + }); + }, + })} + /> + ); + })} +
+
+
+
+ )} +
+ + ); +}; + +export default NativeSelect; diff --git a/.dumi/theme/components/NativeSelect/style.ts b/.dumi/theme/components/NativeSelect/style.ts new file mode 100644 index 000000000000..893ef0d57e22 --- /dev/null +++ b/.dumi/theme/components/NativeSelect/style.ts @@ -0,0 +1,49 @@ +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(({ css, stylish, cx, token }, prefixCls: string) => ({ + container: cx( + prefixCls, + css` + background: ${token.colorBgElevated}; + font-size: ${token.fontSize}; + border: 1px solid ${token.colorBorder}; + box-shadow: ${token.boxShadowSecondary}; + border-radius: 8px; + box-sizing: border-box; + overflow-y: auto; + overscroll-behavior: contain; + scrollbar-width: none; + padding: 5px; + outline: 0; + user-select: none; + width: 160px; + + &::-webkit-scrollbar { + display: none; + } + `, + ), + button: cx( + `${prefixCls}-button`, + css` + all: unset; + font-size: ${token.fontSize}px; + padding: 8px; + line-height: 0; + background: ${token.colorBgContainer}; + color: ${token.colorTextSecondary}; + border-radius: ${token.borderRadius}px; + cursor: default; + user-select: none; + border: 1px solid ${token.colorBorder}; + -webkit-tap-highlight-color: transparent; + + ${stylish.buttonDefaultHover} + + &:focus-visible { + border-color: ${token.colorPrimary}; + box-shadow: 0 0 0 2px ${token.colorPrimaryBg}; + } + `, + ), +})); diff --git a/.dumi/theme/components/SiteProvider/index.tsx b/.dumi/theme/components/SiteProvider/index.tsx new file mode 100644 index 000000000000..0d04820d13ee --- /dev/null +++ b/.dumi/theme/components/SiteProvider/index.tsx @@ -0,0 +1,24 @@ +import { App } from 'antd'; +import { StyleProvider, ThemeProvider } from 'antd-style'; +import { PropsWithChildren } from 'react'; + +import { useThemeStore } from '../../store/useThemeStore'; +import { getAntdTheme, getCustomStylish, getCustomToken } from '../../styles'; + +export default ({ children }: PropsWithChildren) => { + const themeMode = useThemeStore((s) => s.themeMode); + + return ( + + + {children} + + + ); +}; diff --git a/.dumi/theme/components/StoreUpdater/index.tsx b/.dumi/theme/components/StoreUpdater/index.tsx new file mode 100644 index 000000000000..d38229a10616 --- /dev/null +++ b/.dumi/theme/components/StoreUpdater/index.tsx @@ -0,0 +1,42 @@ +import { useLocation } from '@@/exports'; +import { useNavData, useRouteMeta, useSidebarData, useSiteData } from 'dumi'; +import isEqual from 'fast-deep-equal'; +import { memo, useEffect } from 'react'; +import { useSiteStore } from '../../store/useSiteStore'; + +export const StoreUpdater = memo(() => { + const siteData = useSiteData(); + const sidebar = useSidebarData(); + const routeMeta = useRouteMeta(); + const navData = useNavData(); + const location = useLocation(); + + useEffect(() => { + const { setLoading, ...data } = siteData; + const { + siteData: { setLoading: _, ...prevData }, + } = useSiteStore.getState(); + + if (isEqual(data, prevData)) return; + + useSiteStore.setState({ siteData }); + }, [siteData]); + + useEffect(() => { + useSiteStore.setState({ sidebar }); + }, [sidebar]); + + useEffect(() => { + useSiteStore.setState({ routeMeta }); + }, [routeMeta]); + + useEffect(() => { + useSiteStore.setState({ navData }); + }, [navData]); + + useEffect(() => { + useSiteStore.setState({ location }); + }, [location]); + + return null; +}); diff --git a/.dumi/theme/components/ThemeSwitch/index.tsx b/.dumi/theme/components/ThemeSwitch/index.tsx new file mode 100644 index 000000000000..5f64901e9ba4 --- /dev/null +++ b/.dumi/theme/components/ThemeSwitch/index.tsx @@ -0,0 +1,62 @@ +import { styled, ThemeMode } from 'antd-style'; +import { memo, ReactNode, type FC } from 'react'; +import { Flexbox } from 'react-layout-kit'; + +import { useThemeStore } from '../../store/useThemeStore'; +import NativeSelect from '../NativeSelect'; + +const IconDark = () => ( + + + +); + +const IconLight = () => ( + + + +); + +const IconAuto = () => ( + + + +); + +const IconWrapper = styled.span` + width: 12px; +`; + +const Option = ({ icon, label }: { icon: ReactNode; label: ReactNode }) => ( + + {icon} + {label} + +); + +const options = [ + { label: '跟随系统', icon: , value: 'auto' }, + { label: '亮色模式', icon: , value: 'light' }, + { label: '暗色模式', icon: , value: 'dark' }, +]; + +const ThemeSwitch: FC = () => { + const themeMode = useThemeStore((s) => s.themeMode); + + return ( + + o.value === themeMode)} + onChange={(index) => { + const mode = options[index].value as unknown as ThemeMode; + useThemeStore.setState({ themeMode: mode }); + }} + renderValue={(index) => options[index].icon} + renderItem={(item) => + ); +}; + +export default memo(ThemeSwitch); diff --git a/.dumi/theme/hooks/useCopied.ts b/.dumi/theme/hooks/useCopied.ts new file mode 100644 index 000000000000..360e45f49255 --- /dev/null +++ b/.dumi/theme/hooks/useCopied.ts @@ -0,0 +1,21 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; + +export const useCopied = () => { + const [copied, setCopy] = useState(false); + + useEffect(() => { + if (!copied) return; + + const timer = setTimeout(() => { + setCopy(false); + }, 2000); + + return () => { + clearTimeout(timer); + }; + }, [copied]); + + const setCopied = useCallback(() => setCopy(true), []); + + return useMemo(() => ({ copied, setCopied }), [copied]); +}; diff --git a/.dumi/theme/layout.less b/.dumi/theme/layout.less deleted file mode 100644 index a52093e6c96d..000000000000 --- a/.dumi/theme/layout.less +++ /dev/null @@ -1,79 +0,0 @@ -.__dumi-default-menu .__dumi-default-menu-list > li > a { - padding-left: 24px !important; - - ~ ul { - margin-left: 24px !important; - } -} - -.markdown table { - max-width: 100vw !important; -} - -div > .markdown:first-child > :first-child { - margin-top: 0 !important; -} - -html, -.__dumi-default-layout-toc, -.__dumi-default-menu-inner { - &::-webkit-scrollbar { - width: 2px; - height: 6px; - } - &::-webkit-scrollbar-thumb { - background-color: rgba(50, 50, 50, 0.3); - border-radius: 1em; - } - &::-webkit-scrollbar-track { - background-color: rgba(50, 50, 50, 0.1); - border-radius: 1em; - } -} - -.__dumi-default-locale-select { - margin-right: 16px; - margin-left: 16px !important; -} - -html { - &::-webkit-scrollbar { - width: 2px; - height: 6px; - } -} -.__dumi-default-layout-hero { - background-color: #1890ff !important; - background-image: url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/YIJ8Tbp7Oc4AAAAAAAAAAAAAFl94AQBr'); - background-repeat: no-repeat; - background-position: 90% center; - background-size: contain; - transition: 0.3s all; - - &:hover { - background-position-y: -20px; - } - - h1 { - color: #fff !important; - } - - div > p { - color: #fff; - } -} - -.procomponents_dark_theme_view { - height: 64px; - background-color: #fff; -} - -@media screen and (max-width: 680px) { - .procomponents_dark_theme_view { - height: 50px; - } -} - -.__dumi-default-layout[data-site-mode='true'][data-show-slugs='true'] { - max-width: 1500px; -} diff --git a/.dumi/theme/layout.tsx b/.dumi/theme/layout.tsx deleted file mode 100644 index 70f97355e328..000000000000 --- a/.dumi/theme/layout.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import dumiContext from '@umijs/preset-dumi/lib/theme/context'; -import { ConfigProvider, Switch } from 'antd'; -import zhCN from 'antd/es/locale/zh_CN'; -import Layout from 'dumi-theme-default/src/layout'; -import { useContext, useEffect, useMemo } from 'react'; -import { Helmet, HelmetProvider } from 'react-helmet-async'; -import { IRouteComponentProps, isBrowser } from 'umi'; -import './layout.less'; -import { useDarkreader } from './useDarkreader'; - -const DarkButton = () => { - const colorScheme = useMemo(() => { - if (!isBrowser()) { - return 'light'; - } - if ((window as any).HeadlessChrome) { - return 'light'; - } - - return matchMedia?.('(prefers-color-scheme: dark)').matches && 'dark'; - }, []); - - const defaultDarken = useMemo(() => { - if (!isBrowser()) { - return 'light'; - } - if ((window as any).HeadlessChrome) { - return 'light'; - } - return localStorage.getItem('procomponents_dark_theme') || colorScheme; - }, []); - - const setColor = (isDarken: boolean) => { - try { - const theme = document.getElementsByTagName('meta')['theme-color']; - theme.setAttribute('content', isDarken ? '#242525' : '#1890ff'); - } catch (error) {} - }; - - const [isDark, { toggle }] = useDarkreader(defaultDarken === 'dark'); - - useEffect(() => { - setColor(isDark); - }, [isDark]); - - if (!isBrowser()) { - return null; - } - if ((window as any).HeadlessChrome) { - return null; - } - return ( - { - toggle(); - if (!check) { - localStorage.setItem('procomponents_dark_theme', 'light'); - } else { - localStorage.setItem('procomponents_dark_theme', 'dark'); - } - }} - /> - ); -}; - -function loadJS(url, callback) { - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.onload = function () { - callback?.(); - }; - script.src = url; - - document.getElementsByTagName('head')[0].appendChild(script); -} - -const LayoutPage = ({ children, ...props }: IRouteComponentProps) => { - const context = useContext(dumiContext); - useEffect(() => { - if (!isBrowser()) { - return; - } - - if ((window as any).HeadlessChrome) { - return; - } - - loadJS('https://www.googletagmanager.com/gtag/js?id=G-RMBLDHGL1N', function () { - // @ts-ignore - window.dataLayer = window.dataLayer || []; - function gtag() { - // @ts-ignore - dataLayer.push(arguments); - } - // @ts-ignore - gtag('js', new Date()); - // @ts-ignore - gtag('config', 'G-RMBLDHGL1N'); - }); - - (function (h, o, t, j, a, r) { - // @ts-ignore - h.hj = - // @ts-ignore - h.hj || - function () { - // @ts-ignore - (h.hj.q = h.hj.q || []).push(arguments); - }; - // @ts-ignore - h._hjSettings = { hjid: 2036108, hjsv: 6 }; - a = o.getElementsByTagName('head')[0]; - r = o.createElement('script'); - r.async = 1; - // @ts-ignore - r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv; - a.appendChild(r); - })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv='); - }, []); - - const title = useMemo(() => { - if (context.meta.title?.includes('-')) { - return `${context.meta.title}`; - } - if (!context.meta.title) { - return 'ProComponents - 模板组件'; - } - return `${context.meta.title} - ProComponents`; - }, [context]); - - return ( - - - -
- - {title} - -
{children}
-
- {isBrowser() ? : null} -
-
-
-
-
- ); -}; - -export default LayoutPage; diff --git a/.dumi/theme/layouts/DemoLayout/index.tsx b/.dumi/theme/layouts/DemoLayout/index.tsx new file mode 100644 index 000000000000..2fc1ca968966 --- /dev/null +++ b/.dumi/theme/layouts/DemoLayout/index.tsx @@ -0,0 +1,7 @@ +import { useOutlet } from 'dumi'; +import DemoProvider from '../../components/DemoProvider'; + +export default () => { + const outlet = useOutlet(); + return {outlet}; +}; diff --git a/.dumi/theme/layouts/DocLayout/index.tsx b/.dumi/theme/layouts/DocLayout/index.tsx new file mode 100644 index 000000000000..2642730d137a --- /dev/null +++ b/.dumi/theme/layouts/DocLayout/index.tsx @@ -0,0 +1,64 @@ +import animateScrollTo from 'animated-scroll-to'; +import { Helmet, useIntl, useLocation } from 'dumi'; +import isEqual from 'fast-deep-equal'; +import { memo, StrictMode, useEffect, type FC } from 'react'; + +import SiteProvider from '../../components/SiteProvider'; +import { StoreUpdater } from '../../components/StoreUpdater'; + +import Docs from '../../pages/Docs'; +import Home from '../../pages/Home'; + +import { isHeroPageSel, useSiteStore } from '../../store/useSiteStore'; +import { GlobalStyle } from './styles'; + +const DocLayout: FC = memo(() => { + const intl = useIntl(); + const { hash } = useLocation(); + const fm = useSiteStore((s) => s.routeMeta.frontmatter, isEqual); + const isHomePage = useSiteStore(isHeroPageSel); + const loading = useSiteStore((s) => s.siteData.loading); + + // handle hash change or visit page hash after async chunk loaded + useEffect(() => { + const id = hash.replace('#', ''); + + if (id) { + setTimeout(() => { + const elm = document.getElementById(decodeURIComponent(id)); + + if (elm) { + // animated-scroll-to instead of native scroll + animateScrollTo(elm.offsetTop - 80, { + maxDuration: 300, + }); + } + }, 1); + } + }, [loading, hash]); + + return ( + <> + + + {fm.title && } + {fm.description && } + {fm.description && } + {fm.keywords && } + {fm.keywords && } + + + {isHomePage ? : } + + ); +}); + +export default () => ( + + + + + + + +); diff --git a/.dumi/theme/layouts/DocLayout/styles.ts b/.dumi/theme/layouts/DocLayout/styles.ts new file mode 100644 index 000000000000..6834b07732ea --- /dev/null +++ b/.dumi/theme/layouts/DocLayout/styles.ts @@ -0,0 +1,26 @@ +import { createGlobalStyle } from 'antd-style'; + +export const GlobalStyle = createGlobalStyle` + body { + margin: 0; + padding: 0; + background-color: ${(p) => p.theme.colorBgLayout}; + } + + @font-face { + font-family: AliPuHui; + font-weight: normal; + src: url('//at.alicdn.com/t/webfont_exesdog9toj.woff2') format('woff2'), + url('//at.alicdn.com/t/webfont_exesdog9toj.woff') format('woff'), + url('//at.alicdn.com/t/webfont_exesdog9toj.ttf') format('truetype'); + font-display: swap; + } + + @font-face { + font-family: AliPuHui; + font-weight: bold; + src: url('https://at.alicdn.com/wf/webfont/exMpJIukiCms/Gsw2PSKrftc1yNWMNlXgw.woff2') format('woff2'), + url('https://at.alicdn.com/wf/webfont/exMpJIukiCms/vtu73by4O2gEBcvBuLgeu.woff') format('woff'); + font-display: swap; + } +`; diff --git a/.dumi/theme/layouts/demo.tsx b/.dumi/theme/layouts/demo.tsx deleted file mode 100644 index 145cdd94ba0c..000000000000 --- a/.dumi/theme/layouts/demo.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { isBrowser } from 'umi'; - -export default ({ children, location, ...rest }) => { - if (!isBrowser()) { - return children; - } - if (location.pathname.startsWith('/~demos/layout')) { - return children; - } - if (location.pathname.startsWith('/~demos/pagecontainer')) { - return children; - } - if (location.pathname.startsWith('/~demos/form-layout')) { - return children; - } - - return ( -
-
- {children} -
-
- ); -}; diff --git a/.dumi/theme/pages/Docs/index.tsx b/.dumi/theme/pages/Docs/index.tsx new file mode 100644 index 000000000000..57a45a76eda4 --- /dev/null +++ b/.dumi/theme/pages/Docs/index.tsx @@ -0,0 +1,86 @@ +import { Helmet, useLocation, useOutlet } from 'dumi'; +import isEqual from 'fast-deep-equal'; +import { memo, useEffect, type FC } from 'react'; +import { Center, Flexbox } from 'react-layout-kit'; + +//@ts-ignore +import Content from 'dumi/theme/slots/Content'; +//@ts-ignore +import Footer from 'dumi/theme/slots/Footer'; +//@ts-ignore +import Header from 'dumi/theme/slots/Header'; +//@ts-ignore +import Sidebar from 'dumi/theme/slots/Sidebar'; +//@ts-ignore +import Toc from 'dumi/theme/slots/Toc'; + +import { ApiHeader } from '../../components/ApiHeader'; + +import { useResponsive } from 'antd-style'; +import { isApiPageSel, useSiteStore } from '../../store/useSiteStore'; +import { useStyles } from './styles'; + +const Docs: FC = memo(() => { + const outlet = useOutlet(); + const { mobile } = useResponsive(); + const fm = useSiteStore((s) => s.routeMeta.frontmatter, isEqual); + const isApiPage = useSiteStore(isApiPageSel); + + const { styles, theme } = useStyles(); + + const location = useLocation(); + + useEffect(() => { + requestAnimationFrame(() => { + window.scrollTo(0, 0); + }); + }, [location.pathname]); + + return ( +
+
+ + {mobile ? null : } + {isApiPage ? ( + +
+ + + + + +
+
+ ) : null} + +
+ + + {outlet} + + +
+
+
+
+ ); +}); + +export default Docs; diff --git a/.dumi/theme/pages/Docs/styles.ts b/.dumi/theme/pages/Docs/styles.ts new file mode 100644 index 000000000000..d1c97f4086ee --- /dev/null +++ b/.dumi/theme/pages/Docs/styles.ts @@ -0,0 +1,65 @@ +import { createStyles } from 'antd-style'; + +export const useStyles = createStyles(({ css, responsive, token }) => ({ + layout: css` + background-color: ${token.colorBgLayout}; + background-image: linear-gradient( + 180deg, + ${token.colorBgContainer} 0%, + rgba(255, 255, 255, 0) 10% + ); + display: grid; + grid-template-columns: ${token.sidebarWidth}px 1fr ${token.tocWidth}px; + grid-template-rows: ${token.headerHeight}px auto 1fr 80px; + grid-template-areas: + 'head head head' + 'sidebar title .' + 'sidebar main toc' + 'sidebar footer footer'; + min-height: 100vh; + + ${responsive.mobile} { + display: flex; + flex-direction: column; + } + `, + + toc: css` + position: sticky; + top: 100px; + width: ${token.tocWidth}px; + margin-inline-end: 24px; + max-height: 80vh; + overflow: auto; + margin-top: 48px; + + ${responsive.mobile} { + z-index: 300; + top: ${token.headerHeight + 1}px; + margin-top: 0; + width: 100%; + } + + overscroll-behavior: contain; + -webkit-overflow-scrolling: touch; + + > h4 { + margin: 0 0 8px; + color: ${token.colorTextDescription}; + font-size: 12px; + line-height: 1; + } + `, + + content: css` + max-width: ${token.contentMaxWidth}px; + width: 100%; + margin: 0 24px; + + + ${responsive.mobile} { + margin: 0; + } + } + `, +})); diff --git a/.dumi/theme/pages/Home/index.tsx b/.dumi/theme/pages/Home/index.tsx new file mode 100644 index 000000000000..d7a47510878f --- /dev/null +++ b/.dumi/theme/pages/Home/index.tsx @@ -0,0 +1,25 @@ +import { Helmet } from 'dumi'; +import { memo, type FC } from 'react'; + +//@ts-ignore +import Features from 'dumi/theme/slots/Features'; +//@ts-ignore +import Footer from 'dumi/theme/slots/Footer'; +//@ts-ignore +import Header from 'dumi/theme/slots/Header'; +//@ts-ignore +import Hero from 'dumi/theme/slots/Hero'; +import { Flexbox } from 'react-layout-kit'; + +const Home: FC = memo(() => ( + <> + +
+ + +