From 3714918f2b658c7b1f15b13069010c45983fa781 Mon Sep 17 00:00:00 2001 From: fi3ework Date: Wed, 14 Jan 2026 14:18:31 +0800 Subject: [PATCH] fix(nav-icon): remove white border in dark mode popover --- .github/workflows/chromatic.yml | 51 ++++++++++ AGENTS.md | 172 ++++++++++++++++++++++++++++++++ package.json | 5 +- pnpm-lock.yaml | 119 ++++++++++++++++++++++ src/nav-icon/index.module.scss | 10 ++ stories/NavIcon.stories.tsx | 36 +++++++ stories/index.scss | 1 + 7 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/chromatic.yml create mode 100644 AGENTS.md diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 0000000..361b4cf --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,51 @@ +# Chromatic Visual Regression Testing +# https://www.chromatic.com/docs/github-actions/ + +name: Chromatic + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + chromatic: + name: Run Chromatic + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Install Pnpm + run: npm install -g corepack@latest --force && corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 24.11.1 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + + - name: Build Storybook + run: pnpm run build-storybook + + - name: Run Chromatic + uses: chromaui/action@latest + with: + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + # Use the pre-built Storybook + storybookBuildDir: storybook-static + # Auto accept changes on main branch (for squash/rebase merge) + autoAcceptChanges: main + # Don't fail CI when there are visual changes - require review in Chromatic UI + exitZeroOnChanges: true + # Skip builds for dependabot/renovate branches + skip: '@(renovate/**|dependabot/**)' diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d750240 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,172 @@ +# AGENTS.md + +Shared UI component library (`@rstack-dev/doc-ui`) for Rstack websites (rspack.rs, rsbuild.rs, rspress.rs). + +## Tech Stack + +- React 18 + TypeScript 5.9 (strict mode) +- Build: Rslib (based on Rsbuild) +- Styling: SCSS Modules (`.module.scss`) +- Animation: Framer Motion, Lottie +- UI: Ant Design 6 +- Dev: Storybook 10 + +## Commands + +```bash +# Development +pnpm dev # Start Storybook (port 6006) + +# Build +pnpm build # Build with Rslib +pnpm build:watch # Watch mode + +# Lint (prefer file-scoped) +pnpm lint # Check all with Biome +pnpm lint:fix # Auto-fix + +# Single file commands +npx tsc --noEmit 'path/to/file.tsx' +npx prettier --write 'path/to/file.tsx' +npx biome check --write 'path/to/file.tsx' +``` + +No test framework - visual testing via Storybook. + +## Project Structure + +``` +src/ +├── announcement/ # Announcement banner +├── antd/ # Ant Design wrappers +├── background-image/ # Background component +├── benchmark/ # Benchmark display +├── built-with-rspack/ # Showcase component +├── fully-featured/ # Feature list +├── hero/ # Hero section +├── nav-icon/ # Nav icon with popover +├── section-style/ # Section utilities +├── tool-stack/ # Tool stack display +├── why-rspack/ # Feature cards +├── env.d.ts # Type declarations +└── shared.tsx # Shared utilities + +stories/ # Storybook stories +``` + +## Code Style + +### Formatting (Prettier - `.prettierrc`) + +- Single quotes, trailing commas (`all`), no parens for single arrow params + +### Linting (Biome - `biome.json`) + +- `noExplicitAny`: off, `noArrayIndexKey`: off +- File naming: `camelCase`, `PascalCase`, or export name + +### TypeScript + +- Strict mode, use `type` imports: `import type { FC } from 'react'` +- Path alias: `@/*` → `./src/*` + +### Imports Order + +1. External packages (react, antd, framer-motion) +2. Internal components (relative) +3. Styles (`.module.scss`) +4. Assets (JSON, images) + +```typescript +import type { FC } from 'react'; +import { useState } from 'react'; +import { useInView } from 'react-intersection-observer'; +import { ProgressBar } from './ProgressBar'; +import styles from './index.module.scss'; +``` + +## Component Patterns + +### Naming + +- Components: `PascalCase` (`Hero`, `NavIcon`) +- Hooks: `useCamelCase` (`useMouseMove`) +- Props: `ComponentNameProps` (`HeroProps`) +- Styles: `index.module.scss` per component + +### Structure + +```typescript +import type { FC } from 'react'; +import styles from './index.module.scss'; + +export type MyComponentProps = { + title: string; + active?: boolean; +}; + +export const MyComponent: FC = ({ + title, + active = false, +}) => { + return
{title}
; +}; +``` + +### Styling + +- SCSS Modules only (`.module.scss`) +- Compose: `${styles.btn} ${styles.btnPrimary}` +- Root class: `styles.root` + +## Do / Don't + +### Do + +- Functional components with hooks +- SCSS Modules for styling +- Export types with components +- Semantic HTML (`section`, `button`) +- Default values for optional props +- `memo()` for expensive components + +### Don't + +- Inline styles (except dynamic values) +- CSS-in-JS (emotion, styled-components) +- Hard-coded colors +- Class components +- Heavy deps without consideration + +## Storybook + +```typescript +// stories/Hero.stories.tsx +import { Hero } from '@rstack-dev/doc-ui/hero'; +import './index.scss'; + +export const HeroStory = () => {}} />; + +export default { title: 'Hero' }; +``` + +## Git Hooks + +Pre-commit via `simple-git-hooks` + `nano-staged`: + +- Biome lint on JS/TS +- Prettier format on all files + +## Adding Components + +1. Create `src/component-name/index.tsx` +2. Add `src/component-name/index.module.scss` +3. Add entry in `rslib.config.ts` +4. Add export in `package.json` exports +5. Create `stories/ComponentName.stories.tsx` + +## External Dependencies + +Externalized (consumers provide): `react`, `react-dom`, `framer-motion` + +Peer deps: `antd`, `lottie-web`, `react-intersection-observer` diff --git a/package.json b/package.json index 4759302..3cf1cbe 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,8 @@ "build:watch": "rslib build -w", "lint": "biome check", "lint:fix": "biome check --write --unsafe", - "bump": "npx bumpp" + "bump": "npx bumpp", + "chromatic": "chromatic" }, "dependencies": { "framer-motion": "^12.23.25" @@ -90,10 +91,12 @@ "@rstack-dev/doc-ui": "workspace:*", "@storybook/addon-themes": "^10.1.5", "@storybook/react": "^10.1.5", + "@storybook/test": "9.0.0-alpha.2", "@types/node": "~24.10.2", "@types/react": "^18.3.27", "@types/react-dom": "^18.3.7", "antd": "^6.1.0", + "chromatic": "^13.3.5", "execa": "9.6.1", "fs-extra": "11.3.2", "lottie-web": "5.13.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c5a4c0..5e7017b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: '@storybook/react': specifier: ^10.1.5 version: 10.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.1.10(@testing-library/dom@10.4.0)(prettier@3.7.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3) + '@storybook/test': + specifier: 9.0.0-alpha.2 + version: 9.0.0-alpha.2(storybook@10.1.10(@testing-library/dom@10.4.0)(prettier@3.7.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@types/node': specifier: ~24.10.2 version: 24.10.2 @@ -45,6 +48,9 @@ importers: antd: specifier: ^6.1.0 version: 6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + chromatic: + specifier: ^13.3.5 + version: 13.3.5 execa: specifier: 9.6.1 version: 9.6.1 @@ -1105,6 +1111,11 @@ packages: typescript: optional: true + '@storybook/test@9.0.0-alpha.2': + resolution: {integrity: sha512-K2NPgyY8FoyRurijB6LKZmRUwb7fSZ2GbA8Hg1l18x2fSno467eRCm2IH/mmj5UpHTOQoaMIUXjRTY3XyNPdCQ==} + peerDependencies: + storybook: ^9.0.0-alpha.2 + '@swc/helpers@0.5.17': resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} @@ -1112,10 +1123,20 @@ packages: resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} + '@testing-library/jest-dom@6.5.0': + resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/jest-dom@6.9.1': resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/user-event@14.5.2': + resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@testing-library/user-event@14.6.1': resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} engines: {node: '>=12', npm: '>=6'} @@ -1187,6 +1208,9 @@ packages: '@types/resolve@1.20.6': resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + '@vitest/expect@2.0.5': + resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -1201,12 +1225,21 @@ packages: vite: optional: true + '@vitest/pretty-format@2.0.5': + resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} + '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/spy@2.0.5': + resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/utils@2.0.5': + resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} @@ -1404,6 +1437,10 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1420,6 +1457,18 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chromatic@13.3.5: + resolution: {integrity: sha512-MzPhxpl838qJUo0A55osCF2ifwPbjcIPeElr1d4SHcjnHoIcg7l1syJDrAYK/a+PcCBrOGi06jPNpQAln5hWgw==} + hasBin: true + peerDependencies: + '@chromatic-com/cypress': ^0.*.* || ^1.0.0 + '@chromatic-com/playwright': ^0.*.* || ^1.0.0 + peerDependenciesMeta: + '@chromatic-com/cypress': + optional: true + '@chromatic-com/playwright': + optional: true + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -1913,6 +1962,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2585,10 +2637,18 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + tinyspy@4.0.4: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} @@ -3733,6 +3793,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@storybook/test@9.0.0-alpha.2(storybook@10.1.10(@testing-library/dom@10.4.0)(prettier@3.7.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': + dependencies: + '@storybook/global': 5.0.0 + '@testing-library/dom': 10.4.0 + '@testing-library/jest-dom': 6.5.0 + '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) + '@vitest/expect': 2.0.5 + '@vitest/spy': 2.0.5 + storybook: 10.1.10(@testing-library/dom@10.4.0)(prettier@3.7.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@swc/helpers@0.5.17': dependencies: tslib: 2.8.1 @@ -3748,6 +3818,16 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 + '@testing-library/jest-dom@6.5.0': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + redent: 3.0.0 + '@testing-library/jest-dom@6.9.1': dependencies: '@adobe/css-tools': 4.4.4 @@ -3757,6 +3837,10 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 + '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': + dependencies: + '@testing-library/dom': 10.4.0 + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': dependencies: '@testing-library/dom': 10.4.0 @@ -3837,6 +3921,13 @@ snapshots: '@types/resolve@1.20.6': {} + '@vitest/expect@2.0.5': + dependencies: + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.3.3 + tinyrainbow: 1.2.0 + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.3 @@ -3851,14 +3942,29 @@ snapshots: estree-walker: 3.0.3 magic-string: 0.30.21 + '@vitest/pretty-format@2.0.5': + dependencies: + tinyrainbow: 1.2.0 + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 + '@vitest/spy@2.0.5': + dependencies: + tinyspy: 3.0.2 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 + '@vitest/utils@2.0.5': + dependencies: + '@vitest/pretty-format': 2.0.5 + estree-walker: 3.0.3 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -4133,6 +4239,11 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -4157,6 +4268,8 @@ snapshots: readdirp: 4.1.2 optional: true + chromatic@13.3.5: {} + chrome-trace-event@1.0.4: {} cjs-module-lexer@2.1.1: {} @@ -4608,6 +4721,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash@4.17.21: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -5247,8 +5362,12 @@ snapshots: tiny-invariant@1.3.3: {} + tinyrainbow@1.2.0: {} + tinyrainbow@2.0.0: {} + tinyspy@3.0.2: {} + tinyspy@4.0.4: {} to-regex-range@5.0.1: diff --git a/src/nav-icon/index.module.scss b/src/nav-icon/index.module.scss index 66f1a1c..bef3615 100644 --- a/src/nav-icon/index.module.scss +++ b/src/nav-icon/index.module.scss @@ -20,6 +20,12 @@ .popover { z-index: 999; + :global(.ant-popover-container) { + background-color: transparent; + box-shadow: none; + padding: 0; + } + :global(.ant-popover-content) { box-sizing: border-box; width: 552px; @@ -27,6 +33,10 @@ border-radius: 24px; background-color: var(--rp-c-bg); overflow: hidden; + box-shadow: + 0 6px 16px 0 rgba(0, 0, 0, 0.08), + 0 3px 6px -4px rgba(0, 0, 0, 0.12), + 0 9px 28px 8px rgba(0, 0, 0, 0.05); font-family: 'Open Sans', -apple-system, diff --git a/stories/NavIcon.stories.tsx b/stories/NavIcon.stories.tsx index e765407..a33a87d 100644 --- a/stories/NavIcon.stories.tsx +++ b/stories/NavIcon.stories.tsx @@ -1,4 +1,5 @@ import { NavIcon } from '@rstack-dev/doc-ui/nav-icon'; +import { userEvent, within } from '@storybook/test'; import './index.scss'; export const NavIconStory = () => ( @@ -11,6 +12,41 @@ export const NavIconStory = () => ( ); +export const NavIconStoryExpanded = () => ( +
+ +
+); + +const playExpanded = async ({ + canvasElement, +}: { + canvasElement: HTMLElement; +}) => { + const canvas = within(canvasElement); + const icon = canvas.getByTitle('close'); + await userEvent.click(icon); +}; + +NavIconStoryExpanded.play = playExpanded; +NavIconStoryExpanded.parameters = { + // Wait for remote resources to load before Chromatic snapshot + chromatic: { delay: 5000 }, +}; + +export const NavIconStoryExpandedDark = () => ( +
+ +
+); + +NavIconStoryExpandedDark.parameters = { + themes: { themeOverride: 'dark' }, + chromatic: { delay: 5000 }, +}; + +NavIconStoryExpandedDark.play = playExpanded; + export default { title: 'NavIcon', }; diff --git a/stories/index.scss b/stories/index.scss index bea00c0..72a58b6 100644 --- a/stories/index.scss +++ b/stories/index.scss @@ -63,4 +63,5 @@ body { font-family: var(--rp-font-family-base); -webkit-font-smoothing: antialiased; + background-color: var(--rp-c-bg); }