diff --git a/.dumi/global.d.ts b/.dumi/global.d.ts new file mode 100644 index 00000000..14017d4e --- /dev/null +++ b/.dumi/global.d.ts @@ -0,0 +1,24 @@ +declare module '*.svg' { + import * as React from 'react'; + + export const ReactComponent: React.FunctionComponent< + React.SVGProps & { title?: string } + >; + const content: string; + export default content; +} + +declare module '*.png' { + const content: string; + export default content; +} + +declare module '*.css' { + const content: Record; + export default content; +} + +declare module '*.less' { + const content: Record; + export default content; +} diff --git a/.dumi/pages/index/index.tsx b/.dumi/pages/index/index.tsx index 33b230ba..f6379579 100644 --- a/.dumi/pages/index/index.tsx +++ b/.dumi/pages/index/index.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/display-name */ -import React from 'react'; +import * as React from 'react'; import { Button } from '@bifrostui/react'; -import { usePrefersColor, useNavigate, useIntl, useLocale } from 'dumi'; +import { usePrefersColor, useNavigate, useIntl } from 'dumi'; import { ToTopOutlinedIcon } from '@bifrostui/icons'; import Tpp from './user-icon/tpp'; import Dm from './user-icon/dm'; @@ -13,6 +13,7 @@ export default () => { const { locale } = useIntl(); const current = locale === 'zh-CN' ? 'zhCN' : 'enUS'; const navigate = useNavigate(); + return (
@@ -80,15 +81,15 @@ export default () => {
- {locales[current].subTitle[0].title} + {locales[current].subtitle[0].title}
- {locales[current].subTitle[0].title} + {locales[current].subtitle[0].title}
- {locales[current].subTitle[0].desc} + {locales[current].subtitle[0].desc}
{
- {locales[current].subTitle[1].title} + {locales[current].subtitle[1].title}
- {locales[current].subTitle[1].title} + {locales[current].subtitle[1].title}
- {locales[current].subTitle[1].desc} + {locales[current].subtitle[1].desc}
{
-
{locales[current].subTitle[2].title}
+
{locales[current].subtitle[2].title}
diff --git a/.dumi/pages/index/locales.ts b/.dumi/pages/index/locales.ts index 0abcd3b6..a4f6c43e 100644 --- a/.dumi/pages/index/locales.ts +++ b/.dumi/pages/index/locales.ts @@ -1,6 +1,6 @@ export default { zhCN: { - description: '一款简洁灵活的跨端组件库', + description: '一款简洁优雅的跨端组件库', start: '开始体验', features: [ { @@ -9,14 +9,14 @@ export default { }, { title: '高定制', - desc: '一致优雅的API帮助您快速创造新主题', + desc: '一致优雅的API帮助您快速创建新主题', }, { title: '高可用', - desc: '完善的配套,丰富的组件,开箱即用', + desc: '配套完善,组件丰富,开箱即用', }, ], - subTitle: [ + subtitle: [ { title: '设计语言', desc: '基于阿里影业的众多业务实践沉淀的设计语言和设计系统', @@ -49,7 +49,7 @@ export default { desc: 'Complete supporting facilities, rich components, ready to use', }, ], - subTitle: [ + subtitle: [ { title: 'Design language', desc: 'Design language and design system based on the numerous business practices of Alibaba Pictures', diff --git a/.dumi/pages/index/user-icon/dm.tsx b/.dumi/pages/index/user-icon/dm.tsx index 7bcbcfd7..6f5a8a2f 100644 --- a/.dumi/pages/index/user-icon/dm.tsx +++ b/.dumi/pages/index/user-icon/dm.tsx @@ -1,4 +1,6 @@ -export default ({ color = '#eee' }) => { +import * as React from 'react'; + +export default function UserIcon({ color = '#eee' }) { return ( { ); -}; +} diff --git a/.dumi/pages/index/user-icon/tpp.tsx b/.dumi/pages/index/user-icon/tpp.tsx index 007fab5b..eedeaa4c 100644 --- a/.dumi/pages/index/user-icon/tpp.tsx +++ b/.dumi/pages/index/user-icon/tpp.tsx @@ -1,3 +1,5 @@ +import * as React from 'react'; + export default ({ color = '#eee' }) => { return ( { ); diff --git a/.dumi/theme/builtins/Badge/index.tsx b/.dumi/theme/builtins/Badge/index.tsx index 1ac1617e..b32e03d1 100644 --- a/.dumi/theme/builtins/Badge/index.tsx +++ b/.dumi/theme/builtins/Badge/index.tsx @@ -4,6 +4,9 @@ import './index.less'; const Badge: FC<{ children: ReactNode; type: 'info' | 'warning' | 'error' | 'success'; -}> = (props) => ; +}> = (props) => { + const { key, ...restProps } = props as any; + return ; +}; -export default Badge; +export default Badge; \ No newline at end of file diff --git a/.dumi/theme/builtins/Previewer/index.less b/.dumi/theme/builtins/Previewer/index.less index ceaae3af..ff391c9d 100644 --- a/.dumi/theme/builtins/Previewer/index.less +++ b/.dumi/theme/builtins/Previewer/index.less @@ -1,8 +1,8 @@ @import (reference) '../../styles/variables.less'; .@{prefix}-previewer { + max-width: 100%; margin: 24px 0 32px; - // border: 1px solid @c-border-light; border-radius: 16px; background-color: inherit; @@ -22,12 +22,16 @@ } } + &-action-btn { + margin-bottom: 0; + } + &-actions { position: absolute; right: 0; bottom: 0; display: flex; - height: 32px; + padding: var(--bui-spacing-md, 9px); align-items: center; justify-content: flex-end; @@ -62,15 +66,17 @@ &-demo { position: relative; - border-radius: 8px; - padding: 40px 24px 82px; + border-radius: var(--bui-shape-radius-card, 9px); + padding: 12px 12px 42px; border: 1px solid @c-border; @{dark-selector} & { border-color: @c-border-dark; } @media @mobile { - padding: 40px 8px 82px; + margin: 24px -12px 0; + border-radius: 0; + border-width: 1px 0; } > iframe { @@ -91,10 +97,6 @@ display: block; height: 24px; background-color: @c-border-light; - - @{dark-selector} & { - // background-color: @c-border-less-dark; - } } &::after { @@ -124,16 +126,6 @@ } } - &-meta { - // margin-top: 24px; - - // border-top: 1px solid @c-border-light; - - // @{dark-selector} & { - // border-top-color: @c-border-less-dark; - // } - } - &-desc { position: relative; diff --git a/.dumi/theme/builtins/Previewer/index.tsx b/.dumi/theme/builtins/Previewer/index.tsx index 16c8beb5..cf5fb4e2 100644 --- a/.dumi/theme/builtins/Previewer/index.tsx +++ b/.dumi/theme/builtins/Previewer/index.tsx @@ -1,7 +1,13 @@ -import classnames from 'classnames'; -import { IPreviewerProps, useLocation, useIntl, openCodeSandbox, - openStackBlitz, getSketchJSON } from 'dumi'; - import { ReactComponent as IconCheck } from '@ant-design/icons-svg/inline-svg/outlined/check.svg'; +import clsx from 'clsx'; +import { + IPreviewerProps, + useLocation, + useIntl, + openCodeSandbox, + openStackBlitz, + getSketchJSON, +} from 'dumi'; +import { ReactComponent as IconCheck } from '@ant-design/icons-svg/inline-svg/outlined/check.svg'; import { ReactComponent as IconCodeSandbox } from '@ant-design/icons-svg/inline-svg/outlined/code-sandbox.svg'; import { ReactComponent as IconSketch } from '@ant-design/icons-svg/inline-svg/outlined/sketch.svg'; import { ReactComponent as IconStackBlitz } from '@ant-design/icons-svg/inline-svg/outlined/thunderbolt.svg'; @@ -47,7 +53,7 @@ const Previewer: FC = (props) => { return (
= (props) => { className="dumi-default-previewer-action-btn qr-icon" type="button" > - - - + + + } {!props.disabledActions?.includes('CSB') && ( )} {!props.disabledActions?.includes('STACKBLITZ') && ( @@ -149,10 +155,14 @@ const Previewer: FC = (props) => { {intl.formatMessage({ id: 'previewer.actions.sketch.group' })}
)} = (props) => { {tokens.map((line, i) => (
= (props) => { {i + 1} )}
- {line.map((token, key) => ( - // getTokenProps 返回值包含 key - // eslint-disable-next-line react/jsx-key - - ))} + {line.map((token, key) => { + const tokenProps = getTokenProps({ token, key }); + const { key: tokenKey, ...restTokenProps } = tokenProps; + return ( + + ); + })}
))} @@ -94,4 +92,4 @@ const SourceCode: FC = (props) => { ); }; -export default SourceCode; +export default SourceCode; \ No newline at end of file diff --git a/.dumi/theme/builtins/Tree/index.tsx b/.dumi/theme/builtins/Tree/index.tsx index 4c1017db..fbda9fae 100644 --- a/.dumi/theme/builtins/Tree/index.tsx +++ b/.dumi/theme/builtins/Tree/index.tsx @@ -19,7 +19,7 @@ import './index.less'; function getTreeFromList(nodes: ReactNode, prefix = '') { const data: TreeProps['treeData'] = []; - ([] as ReactNode[]).concat(nodes).forEach((node, i) => { + ([] as ReactNode[]).concat(nodes).forEach((node: any, i) => { const key = `${prefix ? `${prefix}-` : ''}${i}`; switch (node?.type) { @@ -37,7 +37,7 @@ function getTreeFromList(nodes: ReactNode, prefix = '') { data.push({ title: ([] as ReactNode[]) .concat(node.props.children) - .filter((child) => child.type !== 'ul'), + .filter((child: any) => child.type !== 'ul'), key, children: liLeafs, isLeaf: !liLeafs.length, @@ -85,16 +85,16 @@ const getIcon = (props: TreeNodeProps) => { const renderSwitcherIcon = (props: TreeNodeProps) => { const { isLeaf, expanded } = props; if (isLeaf) { - return ; + return ; } return expanded ? ( - + ) : ( - + @@ -133,7 +133,8 @@ const initCollapseMotion: CSSMotionProps = { }; export default (props: ComponentProps<'div'>) => { - const data = useListToTree(props.children); + const { children, title } = props; + const data = useListToTree(children); const treeRef = createRef(); @@ -155,7 +156,7 @@ export default (props: ComponentProps<'div'>) => { icon={getIcon} ref={treeRef} itemHeight={20} - showLine={true} + showLine selectable={false} virtual={false} motion={{ @@ -163,7 +164,7 @@ export default (props: ComponentProps<'div'>) => { motionAppear: false, }} onClick={onClick} - treeData={[{ key: '0', title: props.title || '', children: data }]} + treeData={[{ key: '0', title: title || '', children: data }]} defaultExpandAll switcherIcon={renderSwitcherIcon} /> diff --git a/.dumi/theme/layouts/DocLayout/index.tsx b/.dumi/theme/layouts/DocLayout/index.tsx index b9dc7bb6..cb7e1770 100644 --- a/.dumi/theme/layouts/DocLayout/index.tsx +++ b/.dumi/theme/layouts/DocLayout/index.tsx @@ -20,7 +20,7 @@ import React, { useEffect, useState, type FC } from 'react'; import En from '@bifrostui/react/locales/en-US'; import CN from '@bifrostui/react/locales/zh-CN'; import { ThemeProvider } from '@bifrostui/react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import './index.less'; const DocLayout: FC = () => { @@ -28,14 +28,12 @@ const DocLayout: FC = () => { const outlet = useOutlet(); const sidebar = useSidebarData(); const { hash, pathname } = useLocation(); - const { loading, hostname, themeConfig } = useSiteData(); - const [activateSidebar, updateActivateSidebar] = useState(false); + const { loading, hostname } = useSiteData(); + const [activateSidebar, setActivateSidebar] = useState(false); const { frontmatter: fm } = useRouteMeta(); const [color] = usePrefersColor(); fm.toc = 'content'; - // const curColor = themeConfig.switch ? 'dark' : 'light'; - const showSidebar = fm.sidebar !== false && sidebar?.length > 0; const hideToc = fm.title === 'bifrostui' && fm.filename === 'docs/index.md'; @@ -58,10 +56,11 @@ const DocLayout: FC = () => { }, [loading, hash]); return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
updateActivateSidebar(false)} + onClick={() => setActivateSidebar(false)} > { {fm.keywords && ( )} - {fm.keywords && - fm.keywords.map((keyword) => ( - - ))} + {fm.keywords?.map((keyword) => ( + + ))} {hostname && } @@ -95,7 +93,7 @@ const DocLayout: FC = () => { className="dumi-default-sidebar-btn" onClick={(ev) => { ev.stopPropagation(); - updateActivateSidebar((v) => !v); + setActivateSidebar((v) => !v); }} > @@ -104,7 +102,7 @@ const DocLayout: FC = () => {
)}
diff --git a/.dumi/theme/slots/Header/index.tsx b/.dumi/theme/slots/Header/index.tsx index 7d8c387e..c527ebe5 100644 --- a/.dumi/theme/slots/Header/index.tsx +++ b/.dumi/theme/slots/Header/index.tsx @@ -1,4 +1,3 @@ -import type { SocialTypes } from '@/client/theme-api/types'; import { ReactComponent as IconClose } from '@ant-design/icons-svg/inline-svg/outlined/close.svg'; import { ReactComponent as IconMenu } from '@ant-design/icons-svg/inline-svg/outlined/menu.svg'; import { useRouteMeta, useSiteData } from 'dumi'; @@ -10,7 +9,7 @@ import VersionSelect from 'dumi/theme/slots/VersionSelect'; import Navbar from 'dumi/theme/slots/Navbar'; import RtlSwitch from 'dumi/theme/slots/RtlSwitch'; import SearchBar from 'dumi/theme/slots/SearchBar'; -import React, { useMemo, useState, type FC } from 'react'; +import React, { useState, type FC } from 'react'; import './index.less'; const Header: FC = () => { @@ -18,19 +17,6 @@ const Header: FC = () => { const [showMenu, setShowMenu] = useState(false); const { themeConfig } = useSiteData(); - const socialIcons = useMemo( - () => - themeConfig.socialLinks - ? Object.keys(themeConfig.socialLinks) - .slice(0, 5) - .map((key) => ({ - icon: key as SocialTypes, - link: themeConfig.socialLinks[key as SocialTypes], - })) - : [], - [themeConfig.socialLinks], - ); - return (
{
- {/*
*/} {themeConfig.prefersColor.switch && } - {/* {socialIcons.map((item) => ( - - ))} */} {/*
*/}
diff --git a/.dumi/theme/slots/LangSwitch/index.less b/.dumi/theme/slots/LangSwitch/index.less index 50e79b8a..6c549720 100644 --- a/.dumi/theme/slots/LangSwitch/index.less +++ b/.dumi/theme/slots/LangSwitch/index.less @@ -7,6 +7,7 @@ text-decoration: none; transition: all 0.3s; cursor: pointer; + white-space: nowrap; @{dark-selector} & { color: @c-text-secondary-dark; diff --git a/.dumi/theme/slots/Navbar/index.less b/.dumi/theme/slots/Navbar/index.less index 4bffef91..080be4b4 100644 --- a/.dumi/theme/slots/Navbar/index.less +++ b/.dumi/theme/slots/Navbar/index.less @@ -38,6 +38,8 @@ color: @c-text-secondary; text-decoration: none; transition: all 0.3s; + white-space: nowrap; + margin-bottom: 2px; @{dark-selector} & { color: @c-text-secondary-dark; @@ -103,7 +105,7 @@ pointer-events: none; > svg { - width: 12px; + width: 9px; transition-delay: 0.1s; } } @@ -117,28 +119,41 @@ > .@{prefix}-navbar-dropdown { position: absolute; top: 100%; - left: -18px; - min-width: calc(100% + 16px); + left: 50%; + transform: translateX(-50%); + min-width: 140px; list-style: none; - padding: 0; - margin: 6px 0 0; + padding: 6px; + margin: 8px 0 0; background-color: #fff; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); - border-radius: 6px; - transition: all 0.2s ease-in-out; + box-shadow: + 0 8px 24px rgba(0, 0, 0, 0.08), + 0 2px 8px rgba(0, 0, 0, 0.04); + border-radius: 10px; + border: 1px solid rgba(0, 0, 0, 0.04); + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); @{dark-selector} & { background-color: lighten(@c-site-bg-dark, 6%); + border-color: @c-border-dark; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); } > li { > a { - display: block; - padding: 0 18px; + display: flex; + align-items: center; + padding: 8px 16px; color: @c-text-secondary; - font-size: 15px; - line-height: 1.6; + font-size: 14px; + line-height: 22px; text-align: left; + text-decoration: none; + border-radius: 6px; + transition: + background-color 0.2s, + color 0.2s; + white-space: nowrap; @media @mobile { display: inline; @@ -146,23 +161,33 @@ &:hover { color: @c-primary; + background-color: @c-primary-light; + + @{dark-selector} & { + color: @c-primary-dark; + background-color: @c-light-bg-dark; + } } - } - &:first-child > a { - padding-top: 8px; - } + &.active { + color: @c-primary; + font-weight: 500; - &:last-child > a { - padding-bottom: 8px; + @{dark-selector} & { + color: @c-primary-dark; + } + } } } @media @mobile { position: static; + transform: none; background: transparent; box-shadow: none; + border: 0; min-width: 0; + padding: 0; @{dark-selector} & { background: transparent; @@ -177,7 +202,7 @@ &:not(:hover) > .@{prefix}-navbar-dropdown { visibility: hidden; opacity: 0; - transform: translateY(-6px) scale(0.98); + transform: translateX(-50%) translateY(-6px) scale(0.96); transition-delay: 0.1s; @media @mobile { diff --git a/.dumi/theme/slots/Navbar/index.tsx b/.dumi/theme/slots/Navbar/index.tsx index 3945162e..4cc946e2 100644 --- a/.dumi/theme/slots/Navbar/index.tsx +++ b/.dumi/theme/slots/Navbar/index.tsx @@ -5,9 +5,10 @@ import NavbarExtra from 'dumi/theme/slots/NavbarExtra'; import React, { useState, type FC } from 'react'; import './index.less'; -const NavbarItem: FC<{ data: ReturnType[0] }> = ({ - data, -}) => { +const NavbarItem: FC<{ + data: ReturnType[0]; + isActive?: boolean; +}> = ({ data, isActive }) => { const { pathname } = useLocation(); const [isCollapsed, setIsCollapsed] = useState(() => { return data.children?.some((item) => { @@ -34,15 +35,18 @@ const NavbarItem: FC<{ data: ReturnType[0] }> = ({ className="dumi-default-navbar-dropdown" data-collapsed={isCollapsed || undefined} > - + ); // user custom nav has no activePath, so fallback to link const activePath = data.activePath || data.link; - const extraProps = - activePath && pathname.startsWith(activePath) - ? { className: 'active' } - : {}; + // When inside a dropdown, use the pre-computed best-match flag to avoid + // multiple items being active when their paths share a common prefix. + const active = + isActive !== undefined + ? isActive + : !!(activePath && pathname.startsWith(activePath)); + const extraProps = active ? { className: 'active' } : {}; return data.link ? ( <> @@ -69,22 +73,40 @@ const NavbarItem: FC<{ data: ReturnType[0] }> = ({ ); }; -const NavbarContent: FC<{ data: ReturnType }> = ({ - data, -}) => { +const NavbarContent: FC<{ + data: ReturnType; + isDropdown?: boolean; +}> = ({ data, isDropdown = false }) => { + const { pathname } = useLocation(); + + // Within a dropdown, find the single best match (longest activePath prefix) + // so that only one item is ever marked active at a time. + const bestMatchPath = isDropdown + ? data.reduce((best, item) => { + const ap = item.activePath || item.link; + if (!ap || !pathname.startsWith(ap)) return best; + if (!best || ap.length > best.length) return ap; + return best; + }, null) + : null; + return ( <> - {data.map((item) => ( -
  • - {item.link && /^(\w+:)\/\/|^(mailto|tel):/.test(item.link) ? ( - - {item.title} - - ) : ( - - )} -
  • - ))} + {data.map((item) => { + const ap = item.activePath || item.link; + const isActive = isDropdown ? ap === bestMatchPath : undefined; + return ( +
  • + {item.link && /^(\w+:)\/\/|^(mailto|tel):/.test(item.link) ? ( + + {item.title} + + ) : ( + + )} +
  • + ); + })} ); }; diff --git a/.dumi/theme/slots/PreviewerActions/index.tsx b/.dumi/theme/slots/PreviewerActions/index.tsx index 2d3f785c..b6466320 100644 --- a/.dumi/theme/slots/PreviewerActions/index.tsx +++ b/.dumi/theme/slots/PreviewerActions/index.tsx @@ -1,175 +1,26 @@ -import { ReactComponent as IconCheck } from '@ant-design/icons-svg/inline-svg/outlined/check.svg'; -import { ReactComponent as IconCodeSandbox } from '@ant-design/icons-svg/inline-svg/outlined/code-sandbox.svg'; -import { ReactComponent as IconSketch } from '@ant-design/icons-svg/inline-svg/outlined/sketch.svg'; -import { ReactComponent as IconStackBlitz } from '@ant-design/icons-svg/inline-svg/outlined/thunderbolt.svg'; -import copy from 'copy-to-clipboard'; -import { - getSketchJSON, - openCodeSandbox, - openStackBlitz, - useIntl, - type IPreviewerProps, -} from 'dumi'; -import SourceCode from 'dumi/theme/builtins/SourceCode'; -import PreviewerActionsExtra from 'dumi/theme/slots/PreviewerActionsExtra'; +import { type IPreviewerProps } from 'dumi'; import Tabs from 'rc-tabs'; -import React, { useRef, useState, type FC, type ReactNode } from 'react'; +import React, { useState, type FC } from 'react'; +import SourceCode from '../../builtins/SourceCode'; import './index.less'; + export interface IPreviewerActionsProps extends IPreviewerProps { - /** - * disabled actions - */ - disabledActions?: ('CSB' | 'STACKBLITZ' | 'EXTERNAL' | 'HTML2SKETCH')[]; - extra?: ReactNode; - forceShowCode?: boolean; - showCode?: boolean; - demoContainer: HTMLDivElement | HTMLIFrameElement; + asset: any; + showCode: boolean; } -const IconCode: FC = () => ( - - - -); - -const IconCodeExpand: FC = () => ( - - - -); - -const IconExternalLink: FC = () => ( - - - - -); - const PreviewerActions: FC = (props) => { - const intl = useIntl(); - const files = Object.entries(props.asset.dependencies).filter( - ([, { type }]) => type === 'FILE', + const { asset, showCode } = props; + const files = Object.entries(asset.dependencies).filter( + ([, { type }]: [string, { type: string }]) => type === 'FILE', ); const [activeKey, setActiveKey] = useState(0); - const [showCode, setShowCode] = useState( - props.forceShowCode || props.defaultShowCode, - ); - const copyTimer = useRef(); - const [isCopied, setIsCopied] = useState(false); const isSingleFile = files.length === 1; const lang = (files[activeKey][0].match(/\.([^.]+)$/)?.[1] || 'text') as any; return ( - <> - {/*
    - {!props.disabledActions?.includes('CSB') && ( - - )} - {!props.disabledActions?.includes('STACKBLITZ') && ( - - )} - {!props.disabledActions?.includes('HTML2SKETCH') && getSketchJSON && ( - - {isCopied ? : } - - - )} - - {!props.disabledActions?.includes('EXTERNAL') && ( - - - - )} - {props.extra} - - {!props.forceShowCode && ( - - )} -
    */} - {props.showCode && ( + + {showCode && ( <>
    {!isSingleFile && ( @@ -187,11 +38,13 @@ const PreviewerActions: FC = (props) => { )}
    - {files[activeKey][1].value} + + {(files[activeKey][1] as any).value} +
    )} - +
    ); }; diff --git a/.dumi/theme/slots/SocialIcon/index.tsx b/.dumi/theme/slots/SocialIcon/index.tsx index b2720955..1b174aae 100644 --- a/.dumi/theme/slots/SocialIcon/index.tsx +++ b/.dumi/theme/slots/SocialIcon/index.tsx @@ -5,6 +5,7 @@ import { ReactComponent as IconGitlab } from '@ant-design/icons-svg/inline-svg/o import { ReactComponent as IconLinkedin } from '@ant-design/icons-svg/inline-svg/outlined/linkedin.svg'; import { ReactComponent as IconTwitter } from '@ant-design/icons-svg/inline-svg/outlined/twitter.svg'; import { ReactComponent as IconWeiBo } from '@ant-design/icons-svg/inline-svg/outlined/weibo.svg'; +import { ReactComponent as IconX } from '@ant-design/icons-svg/inline-svg/outlined/x.svg'; import { ReactComponent as IconYuque } from '@ant-design/icons-svg/inline-svg/outlined/yuque.svg'; import { ReactComponent as IconZhihu } from '@ant-design/icons-svg/inline-svg/outlined/zhihu.svg'; import React, { FunctionComponent, useMemo, type FC } from 'react'; @@ -30,6 +31,7 @@ const presetIconMap: Record = { zhihu: IconZhihu, yuque: IconYuque, linkedin: IconLinkedin, + x: IconX, }; const SocialIcon: FC = (props: SocialIconProps) => { diff --git a/.dumi/theme/slots/VersionSelect/index.less b/.dumi/theme/slots/VersionSelect/index.less index 53720af3..120194c3 100644 --- a/.dumi/theme/slots/VersionSelect/index.less +++ b/.dumi/theme/slots/VersionSelect/index.less @@ -1,65 +1,121 @@ @import (reference) '../../styles/variables.less'; +@keyframes version-shimmer { + 0% { + background-position: 0% center; + } + + 100% { + background-position: 200% center; + } +} + +.version-label { + background: linear-gradient( + 135deg, + @c-primary 0%, + #7c3aed 40%, + #c026d3 60%, + @c-primary 100% + ); + background-size: 200% auto; + background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: version-shimmer 3s linear infinite; + font-weight: 500; +} + .version-select { padding: 10px; position: relative; display: flex; + align-items: center; cursor: pointer; &:hover { .versions-container { visibility: visible; + opacity: 1; + transform: translateY(0) scale(1); + transition-delay: 0s; + } + + .version-select-arrow { + transform: rotate(-135deg) translateY(2px); } } &-arrow { margin-left: 4px; - width: 0; - height: 0; - border: 6px solid transparent; - border-top-color: @c-text-grey; - transform: translateY(10px); + width: 6px; + height: 6px; + border-right: 1.5px solid @c-text-note; + border-bottom: 1.5px solid @c-text-note; + transform: rotate(45deg) translateY(-2px); + transition: transform 0.3s; + + @{dark-selector} & { + border-right-color: @c-text-note-dark; + border-bottom-color: @c-text-note-dark; + } } .versions-container { display: flex; flex-direction: column; visibility: hidden; + opacity: 0; + transform: translateY(-6px) scale(0.96); + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + transition-delay: 0.1s; position: absolute; - top: 40px; + top: calc(100% + 4px); left: 0; - width: 140px; - padding: 10px 6px; - border-radius: 6px; - background-color: @c-site-bg; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4); + min-width: 120px; + padding: 6px; + border-radius: 10px; + border: 1px solid rgba(0, 0, 0, 0.04); + background-color: #fff; + box-shadow: + 0 8px 24px rgba(0, 0, 0, 0.08), + 0 2px 8px rgba(0, 0, 0, 0.04); z-index: 1200; + @{dark-selector} & { - background-color: @c-site-bg-dark; + background-color: lighten(@c-site-bg-dark, 6%); + border-color: @c-border-dark; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); } .version-item { - color: @c-text; - padding: 6px; - border-radius: 4px; + display: flex; + align-items: center; + padding: 8px 16px; + border-radius: 6px; margin-bottom: 2px; text-decoration: none; - @{dark-selector} & { - color: @c-text-dark; + transition: + background-color 0.2s, + color 0.2s; + white-space: nowrap; + + &:last-child { + margin-bottom: 0; } &:hover { - background-color: @c-bg-grey; + background-color: @c-primary-light; + @{dark-selector} & { background-color: @c-light-bg-dark; } } &-active { - color: @c-error; - background-color: @c-bg-grey; + background-color: @c-primary-light; + @{dark-selector} & { - color: @c-error; background-color: @c-light-bg-dark; } } diff --git a/.dumi/theme/slots/VersionSelect/index.tsx b/.dumi/theme/slots/VersionSelect/index.tsx index 93989d6e..0d258c9b 100644 --- a/.dumi/theme/slots/VersionSelect/index.tsx +++ b/.dumi/theme/slots/VersionSelect/index.tsx @@ -7,12 +7,17 @@ const VersionSelect: FC = () => { const otherVersions = process.env.NODE_ENV === 'development' ? [] - : [{ label: 'beta', publicPath: '/beta/', rootPath: '/beta' }]; + : [ + { label: 'alpha', publicPath: '/alpha/', rootPath: '/alpha' }, + { label: 'beta', publicPath: '/beta/', rootPath: '/beta' }, + ]; const versions = [latestVersion, ...otherVersions]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const [version, setVersion] = useState({}); useEffect(() => { + // eslint-disable-next-line no-restricted-globals const pathname = location.pathname || latestVersion.rootPath; const isLatest = (pathname.length === 1 && pathname === latestVersion.rootPath) || @@ -33,8 +38,8 @@ const VersionSelect: FC = () => { if (!version?.rootPath) return null; return ( -
    {}}> - {version.label} +
    + {version.label}
    {versions.map((v) => ( @@ -46,7 +51,7 @@ const VersionSelect: FC = () => { }} href={v.publicPath} > - {v.label} + {v.label} ))}
    diff --git a/.dumi/tsconfig.json b/.dumi/tsconfig.json index a32dd4f2..e4b15cbf 100644 --- a/.dumi/tsconfig.json +++ b/.dumi/tsconfig.json @@ -1,4 +1,23 @@ { "extends": "../tsconfig.json", - "include": ["**/*"] + "compilerOptions": { + "paths": { + "@@/*": ["./tmp/*"], + "dumi": ["./tmp/dumi/exports.ts"], + "dumi/theme/slots/*": ["./theme/slots/*"], + "@/*": ["../node_modules/dumi/dist/*"], + "@bifrostui/react": ["../packages/bui-core/src/index.ts"], + "@bifrostui/react/*": ["../packages/bui-core/src/*"], + "@bifrostui/icons": ["../packages/bui-icons/src/index.ts"], + "@bifrostui/icons-pioneer": [ + "../packages/bui-icons-pioneer/src/index.ts" + ], + "@bifrostui/types": ["../packages/bui-types/src/index.ts"], + "@bifrostui/utils": ["../packages/bui-utils/src/index.ts"] + }, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true + }, + "include": ["**/*", "../.dumi/tmp/**/*", "../packages/**/*"], + "exclude": ["node_modules", "tmp/node_modules"] } diff --git a/.dumi/typings.d.ts b/.dumi/typings.d.ts new file mode 100644 index 00000000..14017d4e --- /dev/null +++ b/.dumi/typings.d.ts @@ -0,0 +1,24 @@ +declare module '*.svg' { + import * as React from 'react'; + + export const ReactComponent: React.FunctionComponent< + React.SVGProps & { title?: string } + >; + const content: string; + export default content; +} + +declare module '*.png' { + const content: string; + export default content; +} + +declare module '*.css' { + const content: Record; + export default content; +} + +declare module '*.less' { + const content: Record; + export default content; +} diff --git a/.dumirc.ts b/.dumirc.ts index 60328804..cbef5398 100644 --- a/.dumirc.ts +++ b/.dumirc.ts @@ -1,5 +1,11 @@ import { defineConfig } from 'dumi'; +// Register a custom plural rule so that dumi's atomDirs route generation +// maps type 'icon-pioneer' → URL prefix 'icons-pioneer' (matching nav links). +// eslint-disable-next-line @typescript-eslint/no-var-requires +const pluralize = require('pluralize'); +pluralize.addIrregularRule('icon-pioneer', 'icons-pioneer'); + console.log('process.env.PUBLIC_PATH', process.env.PUBLIC_PATH); export default defineConfig({ @@ -13,14 +19,28 @@ export default defineConfig({ { title: '指南', link: '/guide/introduce', activePath: '/guide' }, { title: '设计语言', link: '/design/colors', activePath: '/design' }, { title: '组件', link: '/cores/button', activePath: '/cores' }, - { title: '图标', link: '/icons' }, + { + title: '图标', + activePath: '/icons', + children: [ + { title: '默认图标', link: '/icons' }, + { title: '先锋图标', link: '/icons-pioneer' }, + ], + }, { title: 'GitHub', link: 'https://github.com/alibaba/bifrostui' }, ], 'en-US': [ { title: 'Guide', link: '/guide/introduce-en', activePath: '/guide' }, { title: 'Design', link: '/design/colors-en', activePath: '/design' }, { title: 'Components', link: '/cores/button-en', activePath: '/cores' }, - { title: 'Icons', link: '/icons-en' }, + { + title: 'Icons', + activePath: '/icons', + children: [ + { title: 'Default Icons', link: '/icons-en' }, + { title: 'Pioneer Icons', link: '/icons-pioneer-en' }, + ], + }, { title: 'GitHub', link: 'https://github.com/alibaba/bifrostui' }, ], }, @@ -30,6 +50,7 @@ export default defineConfig({ atomDirs: [ { type: 'core', dir: 'packages/bui-core/src' }, { type: 'icon', dir: 'packages/bui-icons/src' }, + { type: 'icon-pioneer', dir: 'packages/bui-icons-pioneer/src' }, ], entryFile: './packages/bui/src/index.ts', }, @@ -37,6 +58,7 @@ export default defineConfig({ alias: { '@bifrostui/react': '/packages/bui-core/src', '@bifrostui/icons': '/packages/bui-icons/src', + '@bifrostui/icons-pioneer': '/packages/bui-icons-pioneer/src', '@bifrostui/utils': '/packages/bui-utils/src', }, locales: [ diff --git a/.eslintignore b/.eslintignore index 1a8d0b2b..2659aceb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,8 +1,8 @@ websites scripts coverage -node_modules -dist +**/canvas-demos/** +**/node_modules/** **/es/** **/dist/** .idea @@ -10,3 +10,4 @@ dist .vscode .github pnpm-workspace.yaml +!/.dumi diff --git a/.eslintrc.js b/.eslintrc.js index ef8d571f..18416c04 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,5 @@ module.exports = { + root: true, env: { browser: true, es2021: true, @@ -10,7 +11,9 @@ module.exports = { extensions: ['.js', '.jsx', '.ts', '.tsx'], moduleDirectory: ['node_modules', 'packages/', 'test/'], }, - typescript: {}, + typescript: { + project: ['tsconfig.json', '.dumi/tsconfig.json'], + }, }, }, extends: [ @@ -21,10 +24,27 @@ module.exports = { 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'plugin:markdown/recommended', + 'plugin:jsx-a11y/recommended', ], overrides: [ { - files: ['**/*.test.{ts,tsx}', '**/demo/*'], + files: ['**/*.ts', '**/*.tsx'], + rules: { + 'no-undef': 'off', // TypeScript handles this better + }, + }, + { + files: ['**/*.test.{ts,tsx}', '**/demo/*', '**/docs/**/*.{ts,tsx}'], + globals: { + vi: 'readonly', + describe: 'readonly', + it: 'readonly', + expect: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + beforeAll: 'readonly', + afterAll: 'readonly', + }, rules: { 'import/no-extraneous-dependencies': 0, }, @@ -35,19 +55,29 @@ module.exports = { '@typescript-eslint/ban-types': 0, }, }, - // .md文档使用markdown处理器 { files: ['**/*.md'], processor: 'markdown/markdown', }, // 每个markdown文档中的代码块都有个虚拟文件名,针对[```tsx,```ts,```jsx,```js]代码块配置校验规则 { - files: ['**/*.md/*.tsx', '**/*.md/*.ts', '**/*.md/*.jsx', '**/*.md/*.js'], + files: [ + '**/*.md/*.tsx', + '**/*.md/*.ts', + '**/*.md/*.jsx', + '**/*.md/*.js', + '**/*.test.{ts,tsx}', + '**/__tests__/**/*.{ts,tsx}', + ], rules: { 'import/newline-after-import': 0, 'react/react-in-jsx-scope': 0, 'react/display-name': 0, 'no-console': 0, + 'import/no-extraneous-dependencies': 0, + 'jsx-a11y/label-has-associated-control': 0, + '@typescript-eslint/no-unused-vars': 0, + '@typescript-eslint/no-explicit-any': 0, }, }, ], @@ -63,6 +93,7 @@ module.exports = { '@typescript-eslint', 'prettier', 'markdown', + 'jsx-a11y', ], rules: { 'import/extensions': 0, @@ -71,8 +102,8 @@ module.exports = { 'warn', { functions: false, classes: true, variables: true }, ], - "no-shadow": "off", - "@typescript-eslint/no-shadow": "error", + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': 'error', 'no-redeclare': 'error', 'no-multi-assign': 'error', 'no-dupe-class-members': 'error', @@ -104,12 +135,20 @@ module.exports = { { devDependencies: [ 'scripts/**', + 'tests/**', '**/*.test.js', + '**/*.test.ts', + '**/*.test.tsx', '**/__tests__/*', '*.config.js', + '*.config.mjs', '**/*.config.js', + '**/*.config.mjs', '**/*.md', '**/builder.js', + '.dumi/**', + '.dumi/**/*.ts', + '.dumi/**/*.tsx', ], }, ], @@ -119,14 +158,19 @@ module.exports = { 'no-debugger': 'warn', 'valid-typeof': ['warn', { requireStringLiterals: true }], 'import/no-cycle': ['warn', { maxDepth: Infinity }], + 'import/prefer-default-export': 'off', 'no-extra-boolean-cast': 'warn', - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": [2, { vars: 'all', args: 'none' }], + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [2, { vars: 'all', args: 'none' }], 'react/jsx-no-undef': 'error', 'react/jsx-uses-vars': 'error', 'react/jsx-uses-react': 'error', 'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }], 'react/no-this-in-sfc': 'error', + 'react/prop-types': 'off', + 'react/display-name': 'off', + 'react/destructuring-assignment': 'off', + 'react/require-default-props': 'off', 'react/require-render-return': 'warn', 'react/no-children-prop': 'warn', 'react/jsx-filename-extension': [ @@ -134,16 +178,33 @@ module.exports = { { extensions: ['.jsx', '.js', '.tsx', '.ts', '.vue'] }, ], 'react-hooks/rules-of-hooks': 'error', - 'no-restricted-exports': 0, - // ********** 'react/function-component-definition': 0, - 'jsx-a11y/click-events-have-key-events': 0, - 'jsx-a11y/no-static-element-interactions': 0, - 'jsx-a11y/label-has-associated-control': 0, - 'jsx-a11y/no-noninteractive-element-interactions': 0, 'react/jsx-props-no-spreading': 0, 'react/no-array-index-key': 0, + 'no-restricted-exports': 0, '@typescript-eslint/ban-ts-comment': 0, '@typescript-eslint/no-var-requires': 0, + // ===== 可访问性 (a11y) 规则 ===== + // 1. 键盘交互规则 + 'jsx-a11y/click-events-have-key-events': 'off', // 确保可点击元素有对应的键盘事件处理 + 'jsx-a11y/no-static-element-interactions': 'off', // 禁止在静态元素上添加事件处理器 + 'jsx-a11y/no-noninteractive-element-interactions': 'warn', // 禁止在非交互元素上添加事件处理器 + 'jsx-a11y/no-autofocus': 'off', // 禁止使用自动聚焦,除非必要 + + // 2. 表单和标签规则 + 'jsx-a11y/label-has-associated-control': 'warn', // 确保每个 label 标签都有对应的表单控件 + 'jsx-a11y/anchor-is-valid': 'warn', // 确保锚点标签有有效的 href 属性 + + // 3. ARIA 角色和属性规则 + 'jsx-a11y/role-has-required-aria-props': 'warn', // 确保具有 ARIA 角色的元素包含该角色所需的所有属性 + 'jsx-a11y/role-supports-aria-props': 'warn', // 确保 ARIA 属性与元素的 ARIA 角色兼容 + 'jsx-a11y/aria-role': 'warn', // 确保 ARIA 角色有效 + 'jsx-a11y/aria-props': 'warn', // 确保 ARIA 属性有效 + 'jsx-a11y/aria-proptypes': 'warn', // 确保 ARIA 属性值有效 + 'jsx-a11y/aria-unsupported-elements': 'warn', // 确保 ARIA 属性不被用于不支持它的元素 + + // 4. 图片可访问性规则 + 'jsx-a11y/alt-text': 'warn', // 确保图片有替代文本 + 'jsx-a11y/img-redundant-alt': 'warn', // 避免图片的 alt 文本与图片内容重复 }, }; diff --git a/.github/workflows/create-release-draft-and-publish.yml b/.github/workflows/create-release-draft-and-publish.yml index 3ba0c76c..a2d1d504 100644 --- a/.github/workflows/create-release-draft-and-publish.yml +++ b/.github/workflows/create-release-draft-and-publish.yml @@ -104,14 +104,20 @@ jobs: - name: Determine release type id: release-type run: | - if [[ "${{ env.COMMIT_TAG }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$ ]]; then + if [[ "${{ env.COMMIT_TAG }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]]; then echo "RELEASE=pre-release" >> $GITHUB_ENV + echo "TAGTYPE=alpha" >> $GITHUB_ENV + echo "......pre-release......" + elif [[ "${{ env.COMMIT_TAG }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$ ]]; then + echo "RELEASE=pre-release" >> $GITHUB_ENV + echo "TAGTYPE=beta" >> $GITHUB_ENV echo "......pre-release......" elif [[ "${{ env.COMMIT_TAG }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "RELEASE=latest" >> $GITHUB_ENV + echo "TAGTYPE=latest" >> $GITHUB_ENV echo "......latest......" else - echo "Error: Invalid tag, only support like vX.X.X-beta.X or vX.X.X, plase check your tag." + echo "Error: Invalid tag, only support like vX.X.X-alpha.X, vX.X.X-beta.X or vX.X.X, plase check your tag." exit 1 fi @@ -139,9 +145,11 @@ jobs: # ----------------------发布到npm--------------------- - name: Publish to NPM run: | - if [ '${{ env.RELEASE }}' == 'pre-release' ] ; then + if [ '${{ env.TAGTYPE }}' == 'alpha' ] ; then + pnpm -r publish --tag alpha --publish-branch=${{ github.ref_name }} + elif [ '${{ env.TAGTYPE }}' == 'beta' ] ; then pnpm -r publish --tag beta --publish-branch=${{ github.ref_name }} - elif [ '${{ env.RELEASE }}' == 'latest' ] ; then + elif [ '${{ env.TAGTYPE }}' == 'latest' ] ; then pnpm -r publish --publish-branch=${{ github.ref_name }} fi env: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 501da611..fb38b5b4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -37,6 +37,20 @@ jobs: echo "Current TAG: ${TAG}" echo "Major TAG: ${MAJOR_TAG}" + - name: Build Docs for Alpha + if: contains(env.TAG, 'alpha') == true + run: pnpm docs:build:alpha alpha + + - name: Deploy Alpha to GitHub Pages + if: contains(env.TAG, 'alpha') == true + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: gh-pages + folder: docs-dist/alpha + target-folder: alpha + force: false + clean: true + - name: Build Docs for Beta if: contains(env.TAG, 'beta') == true run: pnpm docs:build:beta beta @@ -52,11 +66,11 @@ jobs: clean: true - name: Build Docs for Current - if: contains(env.TAG, 'beta') == false + if: contains(env.TAG, 'alpha') == false && contains(env.TAG, 'beta') == false run: pnpm docs:build v1 - name: Deploy Latest to GitHub Pages - if: contains(env.TAG, 'beta') == false + if: contains(env.TAG, 'alpha') == false && contains(env.TAG, 'beta') == false uses: JamesIves/github-pages-deploy-action@v4 with: branch: gh-pages @@ -65,4 +79,5 @@ jobs: clean: true # 若存在老版本,需要在此添加老版本目录 clean-exclude: | + alpha/** beta/** diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a1784840..93456c8f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,5 +25,5 @@ jobs: - name: Install dependencies run: pnpm install - - name: Run Tests with Coverage + - name: Run Tests run: pnpm ci:test diff --git a/.gitignore b/.gitignore index 8cb0197b..26d95656 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ node_modules # production dist es +umd +umd-main +logs /dist /docs-dist .history @@ -22,7 +25,6 @@ yarn.lock coverage/ *.history .node -.vscode .dumi/tmp @@ -32,12 +34,24 @@ coverage/ backstop_data/bitmaps_test backstop_data/html_report .ci/ +.nx /.ci /.dumi/tmp -/.vscode +/inspect.json +/inspect.html project.private.config.json .tgz .env +CLAUDE.md +.cursor +.qoder +.claude + +# Test temp folders +**/tempDemos* +tests/.temp-snapshots/ +tests/.temp-demos/ +tests/snapshot.*.tsx diff --git a/.prettierignore b/.prettierignore index 8506c98a..f9426fbf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,4 @@ **/*.svg package.json node_modules +!/.dumi diff --git a/.stylelintignore b/.stylelintignore index 4411e50a..cbc25ad0 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,4 +1,5 @@ *.js +*.mjs *.tsx *.ts *.json diff --git a/.stylelintrc.js b/.stylelintrc.js index 6a498024..46ffcf89 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -20,6 +20,7 @@ module.exports = { 'selector-class-pattern': null, 'selector-id-pattern': null, 'selector-not-notation': null, + 'comment-empty-line-before': null, 'function-no-unknown': null, "font-family-no-missing-generic-family-keyword": null, "declaration-block-no-redundant-longhand-properties": null, diff --git a/.syncpackrc.cjs b/.syncpackrc.cjs new file mode 100644 index 00000000..737d8d7f --- /dev/null +++ b/.syncpackrc.cjs @@ -0,0 +1,23 @@ +/** @type {import('syncpack').RcFile} */ +module.exports = { + versionGroups: [ + { + // Peer deps intentionally support React 17, 18, and 19 for consumers. + // devDependencies pin to ^19.0.0 for local development only. + label: 'React ecosystem peer deps support multiple major versions', + packages: ['**'], + dependencies: ['react', 'react-dom', '@types/react'], + dependencyTypes: ['peer'], + isIgnored: true, + }, + { + // scheduler peer dep uses >=0.27.0 to be permissive for consumers; + // devDependencies pin to the exact version used by React 19. + label: 'scheduler peer dep accepts a wider range than the dev pin', + packages: ['**'], + dependencies: ['scheduler'], + dependencyTypes: ['peer'], + isIgnored: true, + }, + ], +}; diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..9e9f8321 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "dbaeumer.vscode-eslint", + "stylelint.vscode-stylelint", + "esbenp.prettier-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..cc425753 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "prettier.configPath": ".prettierrc" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a04f773f..f241c5ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,14 +22,14 @@ $ pnpm install ### 跨端开发 -一般先在`/bifrostui`目录执行 `pnpm start:h5` 运行 H5,进行 H5 功能开发,待 H5 开发接近尾声时,需要在`/bifrostui`目录下执行选择以下小程序相关命令,进行[微信|抖音|支付宝]小程序端的开发工作,请确保你的功能至少适配 H5、微信、抖音、支付宝 4 个渠道(除非有合理的理由无法保证各端功能一致)。 +一般先在`/bifrostui`目录执行 `pnpm start` 运行 H5,进行 H5 功能开发,待 H5 开发接近尾声时,需要在`/bifrostui`目录下执行选择以下小程序相关命令,进行[微信|抖音|支付宝]小程序端的开发工作,请确保你的功能至少适配 H5、微信、抖音、支付宝 4 个渠道(除非有合理的理由无法保证各端功能一致)。 以下是本地开发命令: - 调试 H5 ```bash -$ pnpm start:h5 +$ pnpm start ``` - 调试微信小程序 diff --git a/backstop_data/engine_scripts/puppet/interceptImages.js b/backstop_data/engine_scripts/puppet/interceptImages.js index 2b02be91..f60e4d72 100644 --- a/backstop_data/engine_scripts/puppet/interceptImages.js +++ b/backstop_data/engine_scripts/puppet/interceptImages.js @@ -12,8 +12,8 @@ * */ -const fs = require('fs'); -const path = require('path'); +const fs = require('node:fs'); +const path = require('node:path'); const IMAGE_URL_RE = /\.gif|\.jpg|\.png/i; const IMAGE_STUB_URL = path.resolve(__dirname, '../imageStub.jpg'); @@ -26,7 +26,7 @@ module.exports = async function (page, scenario) { await request.respond({ body: IMAGE_DATA_BUFFER, headers: HEADERS_STUB, - status: 200 + status: 200, }); } else { request.continue(); diff --git a/docs/components/design/Colors/index.tsx b/docs/components/design/Colors/index.tsx index 59616415..cd38ed1f 100644 --- a/docs/components/design/Colors/index.tsx +++ b/docs/components/design/Colors/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable */ -import React from 'react'; +import * as React from 'react'; import './index.less'; export default () => { @@ -11,7 +11,7 @@ export default () => { { name: '亲和绿', color: '#00d68f', lightColor: '#dcf9f0' }, { name: '神秘紫', color: '#8b52ff', lightColor: '#f2ebff' }, { name: '宁静蓝', color: '#148aff', lightColor: '#e1f0ff' }, - { name: '中性灰', color: '#8896b1', lightColor: '#f3f5f8' }, + { name: '中性灰', color: '#8896b1', lightColor: '#f7f9fc' }, // { name: '榜单金', color: '#d7932c', lightColor: '#fbefe1' }, { name: '会员柚', color: '#ff866e', lightColor: '' }, // { name: '会员文字', color: '#582331', lightColor: '#874953' }, @@ -21,16 +21,16 @@ export default () => { // 文字色 const textColors = [ { name: '特级', color: '#000000', desc: '主标题' }, - { name: '一级', color: '#2e333e', desc: '主标题' }, - { name: '次级', color: '#5f6672', desc: '副标题' }, - { name: '辅助', color: '#959aa5', desc: '弱文案' }, - { name: '失效', color: '#ced1d6', desc: '失效' }, + { name: '一级', color: '#030b1a', desc: '主标题' }, + { name: '次级', color: '#444b5b', desc: '副标题' }, + { name: '辅助', color: '#8b93a5', desc: '弱文案' }, + { name: '失效', color: '#bfc4cf', desc: '失效' }, ]; // 背景色 const backgroundColors = [ { name: '分割', color: '#e9ebef', desc: '分割线' }, - { name: '背景', color: '#f5f6f8', desc: '页面背景' }, + { name: '背景', color: '#f7f9fc', desc: '页面背景' }, { name: '灰色反色', color: '#ffffff', desc: '卡片背景' }, ]; diff --git a/docs/components/design/Dark/index.tsx b/docs/components/design/Dark/index.tsx index 65e3ab39..e896e607 100644 --- a/docs/components/design/Dark/index.tsx +++ b/docs/components/design/Dark/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable */ -import React from 'react'; +import * as React from 'react'; import './index.less'; export default () => { diff --git a/docs/components/design/Font/index.tsx b/docs/components/design/Font/index.tsx index 2ae430af..b5b28db0 100644 --- a/docs/components/design/Font/index.tsx +++ b/docs/components/design/Font/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable */ -import React from 'react'; +import * as React from 'react'; import './index.less'; export default () => { diff --git a/docs/components/design/Space/index.tsx b/docs/components/design/Space/index.tsx index 4678cc26..b5b4a555 100644 --- a/docs/components/design/Space/index.tsx +++ b/docs/components/design/Space/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable */ -import React from 'react'; +import * as React from 'react'; import './index.less'; export default () => { diff --git a/docs/components/design/Space/radius.tsx b/docs/components/design/Space/radius.tsx index f43b8c1b..b3403117 100644 --- a/docs/components/design/Space/radius.tsx +++ b/docs/components/design/Space/radius.tsx @@ -1,5 +1,5 @@ /* eslint-disable */ -import React from 'react'; +import * as React from 'react'; import './index.less'; export default () => { diff --git a/docs/components/theme-designer/CopyThemeModal.less b/docs/components/theme-designer/CopyThemeModal.less new file mode 100644 index 00000000..7875f58c --- /dev/null +++ b/docs/components/theme-designer/CopyThemeModal.less @@ -0,0 +1,46 @@ +.theme-modal { + display: flex; + align-items: center; + justify-content: center; + + .theme-modal-content { + display: flex; + flex-direction: column; + width: 700px; + height: 480px; + padding: 16px; + background: var(--bui-color-bg-view); + border-radius: 6px; + overflow-y: auto; + } + + .theme-modal-title { + text-align: center; + font-size: var(--bui-title-size-2); + font-weight: var(--bui-font-weight-medium); + margin-bottom: var(--bui-spacing-md); + } + + .theme-modal-desc { + font-size: var(--bui-text-size-2); + } + + .theme-modal-textarea { + flex: 1; + margin: var(--bui-spacing-lg) 0; + + textarea { + height: 100%; + } + } + + .theme-modal-footer { + display: flex; + align-items: center; + justify-content: flex-end; + + .btn-cancel { + margin-right: var(--bui-spacing-lg); + } + } +} diff --git a/docs/components/theme-designer/CopyThemeModal.tsx b/docs/components/theme-designer/CopyThemeModal.tsx new file mode 100644 index 00000000..abdd700a --- /dev/null +++ b/docs/components/theme-designer/CopyThemeModal.tsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from 'react'; +import { TextArea, Modal, Button, Toast } from '@bifrostui/react'; +import './CopyThemeModal.less'; + +interface CopyThemeModalProps { + open: boolean; + themeVars: string; + onClose: () => void; +} + +const CopyThemeModal = (props: CopyThemeModalProps) => { + const { open, themeVars, onClose, ...others } = props; + const [value, setValue] = useState(themeVars); + + useEffect(() => { + setValue(themeVars); + }, [themeVars]); + + const onChange = (_e, data) => { + setValue(data.value); + }; + + const copy = () => { + navigator?.clipboard + ?.writeText(value) + .then(() => { + Toast.success('复制成功'); + }) + .catch((err) => { + Toast.fail('复制失败'); + console.error(err); + }); + }; + + return ( + +
    +
    自定义主题
    +
    + 以下自定义主题可复制到业务代码的app.less文件中,并在项目入口引用该主题变量文件,实现自定义主题定制。 +
    +