From 09dec1cdce143a5ca8a999150b3bf9687080ee45 Mon Sep 17 00:00:00 2001 From: aeei <18022843+aeei@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:26:13 +0900 Subject: [PATCH 01/30] docs: define shadcn docs theme design --- ...26-07-14-shadcn-docusaurus-theme-design.md | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-14-shadcn-docusaurus-theme-design.md diff --git a/docs/superpowers/specs/2026-07-14-shadcn-docusaurus-theme-design.md b/docs/superpowers/specs/2026-07-14-shadcn-docusaurus-theme-design.md new file mode 100644 index 000000000..60749f099 --- /dev/null +++ b/docs/superpowers/specs/2026-07-14-shadcn-docusaurus-theme-design.md @@ -0,0 +1,130 @@ +# Shadcn Docusaurus Theme 설계 + +## 목표 + +다른 Docusaurus 사용자가 설치해 일반 문서와 `docusaurus-openapi-docs` 기반 API 문서에 함께 적용할 수 있는 reusable theme package를 만든다. 정보 구조는 Nextra, navigation/search interaction은 Fumadocs를 참고하되 코드는 독자 구현한다. + +## 범위 + +- 기존 `docusaurus-theme-openapi-docs`의 OpenAPI rendering 동작 유지 +- 일반 docs와 OpenAPI docs를 하나의 visual system으로 통합 +- 얇은 sticky header, compact left sidebar, 넓은 본문, right TOC/API panel +- command search, breadcrumbs, docs/API version selector +- responsive sidebar drawer, light/dark mode +- demo를 package reference 및 local preview site로 사용 +- Tailwind는 package build-time에만 사용하고 배포물에는 완성된 CSS를 포함 + +범위 밖: + +- DeskPie OAS 및 OAuth 구현 +- generated MDX 수동 수정 +- 검색 index/provider 구현 +- Nextra, Fumadocs, Redocusaurus dependency + +## Package 모델 + +현재 monorepo의 `packages/docusaurus-theme-openapi-docs`를 일반 docs까지 담당하는 full theme로 확장한다. 구현 중 package source 위치와 OpenAPI public component contract를 유지해 기존 test와 demo를 재사용한다. 배포 이름은 `docusaurus-theme-shadcn-docs`를 사용한다. + +소비자는 package를 설치하고 Docusaurus `themes`에 등록한다. 소비자 project에는 Tailwind 또는 Sass 설정을 요구하지 않는다. Theme package가 component와 compiled CSS를 제공하며 Docusaurus classic preset의 content/routing을 재사용한다. + +## Token contract + +Theme은 token consumer이며 값을 소유하지 않는다. Package CSS가 참조할 수 있는 semantic token: + +- `--background`, `--foreground` +- `--card`, `--card-foreground` +- `--popover`, `--popover-foreground` +- `--primary`, `--primary-foreground` +- `--secondary`, `--secondary-foreground` +- `--muted`, `--muted-foreground` +- `--accent`, `--accent-foreground` +- `--destructive` +- `--border`, `--input`, `--ring` +- `--chart-1` … `--chart-5` +- `--radius` +- `--sidebar-*` + +금지: + +- `--docs-*`, `--api-*`, `--openapi-*`, `--nextra-*`, `--fumadocs-*` +- hardcoded color 및 raw Tailwind palette +- component-local theme token 및 별도 design token 체계 +- 별도 spacing token + +Infima variable은 adapter alias로만 정의한다. 모든 primary action과 active state는 `--primary`를 사용한다. Spacing/layout은 Tailwind standard scale을 사용한다. + +Token value는 소비 site가 제공한다. Demo는 shadcn Nova preset b0, Neutral light/dark 값을 reference stylesheet로 제공하지만 package runtime stylesheet에는 값을 넣지 않는다. + +## Layout + +### Desktop + +- Header: sticky, compact, logo/navigation/version/search/color mode 배치 +- Left sidebar: compact tree navigation, active row는 primary token +- Standard docs: centered wide content + sticky right TOC +- OpenAPI operation: content + sticky right request/code panel +- Breadcrumbs는 본문 제목 위 +- Quiet 1px borders, shadow 최소 + +### Tablet/mobile + +- Left sidebar는 header trigger로 여는 drawer +- TOC는 본문 상단 collapsible section +- OpenAPI right panel은 본문 아래로 이동 +- Search는 full-screen command dialog +- Drawer/dialog는 focus management, Escape close, focus-visible ring 제공 + +## Component 경계 + +- `Navbar`: shell과 responsive actions +- `SearchBar`: Docusaurus search provider UI를 호출하는 command trigger; indexing은 소유하지 않음 +- `DocRoot/Layout`: header/sidebar/content/right rail grid +- `DocSidebar`: navigation tree, collapse, active state +- `DocBreadcrumbs`, `DocVersionDropdown`, `DocPaginator`: classic data contract 재사용 +- `DocItem/TOC`: desktop rail 및 mobile collapse +- `ApiItem/Layout`: standard docs shell 안에서 OpenAPI right panel 선택 +- OpenAPI explorer/schema/input/tab components: 기존 behavior 유지, semantic styling만 교체 + +각 override는 Docusaurus theme alias를 유지해 site-level swizzle이 가능해야 한다. + +## Search + +Theme은 command-style trigger와 modal visual/keyboard behavior를 제공한다. 실제 검색 결과는 Docusaurus에 등록된 search provider가 공급한다. Provider가 없으면 search action을 렌더링하지 않아 dead control을 만들지 않는다. + +## Styling/build + +- Tailwind CSS v4를 dev/build dependency로만 사용 +- Theme source에서 사용한 utility를 static CSS로 compile +- Package `getClientModules()`가 compiled CSS를 로드 +- 소비 site에 Tailwind content scan/config 요구 없음 +- Lucide icons는 package dependency로 제공 +- Inter는 CSS font stack을 기본으로 참조하고 network font를 강제 fetch하지 않음 + +## 접근성 + +- 모든 action에 accessible name +- keyboard navigation과 visible `--ring` focus state +- active/current page는 color 외 `aria-current`와 font/background 변화로 구분 +- mobile drawer와 command dialog focus trap 및 Escape recovery +- light/dark 양쪽 WCAG AA 대비 검증 +- reduced-motion 존중 + +## 검증 + +- unit/component tests: navigation/search/drawer state 및 accessibility semantics +- generated OpenAPI fixtures: existing schema/request/response behavior regression +- E2E: docs navigation, command search, version selection, responsive drawer, color mode +- visual regression: docs 및 OpenAPI 대표 화면의 light/dark/desktop/mobile +- token audit: forbidden custom property, hex/rgb/hsl/raw palette 검사 +- package smoke test: `npm pack` 산출물을 clean Docusaurus 3.10.x fixture에 설치 후 build +- demo production build 및 lint/test 통과 + +## 완료 기준 + +- 다른 사용자가 package 설치와 Docusaurus theme 등록만으로 사용 가능 +- 소비자 Tailwind/Sass config 불필요 +- 일반 docs와 generated OpenAPI docs가 동일 theme shell 사용 +- token contract 위반 0 +- desktop/tablet/mobile 및 light/dark 동작 +- build, test, E2E, package smoke test, CI 통과 +- remote 제공 후 commit/push/PR/CI까지 확인 From 791a181c74be8e57013c8980be84c4a3715ec57c Mon Sep 17 00:00:00 2001 From: aeei <18022843+aeei@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:27:59 +0900 Subject: [PATCH 02/30] docs: add shadcn theme implementation plan --- .../2026-07-14-shadcn-docusaurus-theme.md | 321 ++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-14-shadcn-docusaurus-theme.md diff --git a/docs/superpowers/plans/2026-07-14-shadcn-docusaurus-theme.md b/docs/superpowers/plans/2026-07-14-shadcn-docusaurus-theme.md new file mode 100644 index 000000000..eef2d5edf --- /dev/null +++ b/docs/superpowers/plans/2026-07-14-shadcn-docusaurus-theme.md @@ -0,0 +1,321 @@ +# Shadcn Docusaurus Theme Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 다른 Docusaurus 사용자가 Tailwind 설정 없이 설치할 수 있는 Nextra형 shadcn docs/OpenAPI theme를 만든다. + +**Architecture:** Docusaurus classic preset의 content/routing contract와 기존 OpenAPI React behavior는 유지한다. Theme package가 component override와 compiled CSS를 제공하고, 소비 site만 shadcn semantic token 값을 정의한다. + +**Tech Stack:** Docusaurus 3.10.x, React 19, TypeScript, Sass/CSS, Tailwind CSS v4 build tooling, Jest, Cypress/Playwright + +## Global Constraints + +- Nextra/Fumadocs/Redocusaurus dependency를 추가하지 않는다. +- Theme package에서 `--docs-*`, `--api-*`, `--openapi-*`, hardcoded color, raw Tailwind palette를 사용하지 않는다. +- Primary action 및 active state는 `--primary`를 사용한다. +- 소비 project에 Tailwind/Sass config를 요구하지 않는다. +- Generated OpenAPI MDX를 수정하지 않는다. +- DeskPie OAS/OAuth 구현을 포함하지 않는다. + +--- + +### Task 1: Local preview와 semantic token baseline + +**Files:** +- Create: `demo/src/css/tokens.css` +- Modify: `demo/docusaurus.config.ts` +- Modify: `demo/src/css/custom.css` + +**Interfaces:** +- Consumes: shadcn Nova Neutral semantic token contract +- Produces: `:root`/`[data-theme="dark"]` token values와 Infima adapter aliases + +- [ ] **Step 1: dependency 설치와 기존 build 확인** + +Run: + +```bash +yarn install --frozen-lockfile +yarn build-packages +``` + +Expected: package build exit 0. + +- [ ] **Step 2: demo token stylesheet 작성** + +`tokens.css`에 light/dark `--background`, `--foreground`, card/popover/primary/secondary/muted/accent/destructive/border/input/ring/chart/radius/sidebar token을 정의한다. 같은 파일에서 Infima 값을 다음처럼 alias한다. + +```css +:root { + --ifm-color-primary: var(--primary); + --ifm-background-color: var(--background); + --ifm-font-color-base: var(--foreground); + --ifm-navbar-background-color: var(--background); + --ifm-toc-border-color: var(--border); + --ifm-menu-color-active: var(--primary); + --ifm-global-radius: var(--radius); +} +``` + +- [ ] **Step 3: token stylesheet를 structural CSS보다 먼저 로드** + +```ts +theme: { + customCss: ["./src/css/tokens.css", "./src/css/custom.css"], +}, +``` + +- [ ] **Step 4: demo-specific hardcoded theme/palette 규칙 제거** + +Run: + +```bash +rg -n '#[0-9a-fA-F]{3,8}|rgba?\(|hsla?\(' demo/src/css/custom.css +``` + +Expected: color literal 0건. + +- [ ] **Step 5: local hot-reload server 실행** + +Run: + +```bash +yarn watch:demo +``` + +Expected: `http://localhost:3000` 응답 200. + +- [ ] **Step 6: commit** + +```bash +git add demo/src/css/tokens.css demo/src/css/custom.css demo/docusaurus.config.ts +git commit -m "feat: add neutral semantic theme baseline" +``` + +### Task 2: Reusable package shell styling + +**Files:** +- Create: `packages/docusaurus-theme-openapi-docs/src/theme/base.scss` +- Modify: `packages/docusaurus-theme-openapi-docs/src/theme/styles.scss` +- Modify: `packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/Layout/styles.module.css` + +**Interfaces:** +- Consumes: Task 1 semantic tokens +- Produces: sticky header, compact sidebar, wide content, right rail, typography, focus style + +- [ ] **Step 1: token contract test 추가** + +Package stylesheet를 검사해 forbidden variable과 color literal을 거부하는 Jest test를 추가한다. 허용 literal은 data URI와 demo token owner file뿐이다. + +- [ ] **Step 2: test 실패 확인** + +```bash +yarn test --runInBand token-contract +``` + +Expected: 기존 `--openapi-*`와 color literal 때문에 FAIL. + +- [ ] **Step 3: package base shell CSS 구현** + +`base.scss`는 token 값 정의 없이 semantic token만 소비한다. + +```scss +html, +body { + background: var(--background); + color: var(--foreground); +} + +.navbar { + position: sticky; + top: 0; + height: 3.5rem; + border-bottom: 1px solid var(--border); + background: var(--background); + box-shadow: none; +} + +.theme-doc-sidebar-container { + width: 16rem; + border-right: 1px solid var(--sidebar-border); + background: var(--sidebar); + color: var(--sidebar-foreground); +} + +.menu__link--active { + background: var(--sidebar-primary); + color: var(--sidebar-primary-foreground); +} + +:focus-visible { + outline: 2px solid var(--ring); + outline-offset: 2px; +} +``` + +- [ ] **Step 4: package stylesheet entry에 base import** + +```scss +@use "./base"; +``` + +- [ ] **Step 5: focused test/build** + +```bash +yarn test --runInBand token-contract +yarn workspace docusaurus-theme-openapi-docs build +``` + +Expected: PASS 및 build exit 0. + +- [ ] **Step 6: commit** + +```bash +git add packages/docusaurus-theme-openapi-docs/src/theme +git commit -m "feat: style reusable docs shell" +``` + +### Task 3: Docs navigation과 responsive UX + +**Files:** +- Create/modify Docusaurus theme overrides under `packages/docusaurus-theme-openapi-docs/src/theme/Navbar/` +- Create/modify overrides under `packages/docusaurus-theme-openapi-docs/src/theme/DocSidebar/` +- Create/modify overrides under `packages/docusaurus-theme-openapi-docs/src/theme/DocItem/TOC/` +- Modify: `demo/docusaurus.config.ts` + +**Interfaces:** +- Consumes: Docusaurus classic theme aliases and metadata +- Produces: command search trigger, mobile drawer, breadcrumbs/version/TOC styling + +- [ ] **Step 1: component tests 작성** + +검증 항목: search accessible name/shortcut, drawer Escape close/focus recovery, current page `aria-current`, dark-mode button accessible name. + +- [ ] **Step 2: tests 실패 확인** + +```bash +yarn test --runInBand Navbar DocSidebar +``` + +Expected: override가 없어 FAIL. + +- [ ] **Step 3: 최소 override 구현** + +Classic component contract를 wrapper로 재사용하고 DOM 전체 복사를 피한다. Search provider 미설치 시 dead trigger를 렌더링하지 않는다. + +- [ ] **Step 4: responsive CSS 구현** + +Desktop은 3-column, mobile은 sidebar drawer/inline TOC/API panel로 매핑한다. Breakpoint는 기존 Docusaurus `996px` 경계를 유지한다. + +- [ ] **Step 5: tests/build** + +```bash +yarn test --runInBand Navbar DocSidebar +yarn build-demo +``` + +Expected: PASS 및 production build exit 0. + +- [ ] **Step 6: commit** + +```bash +git add packages/docusaurus-theme-openapi-docs/src/theme demo/docusaurus.config.ts +git commit -m "feat: add compact docs navigation" +``` + +### Task 4: OpenAPI visual integration + +**Files:** +- Modify: `packages/docusaurus-theme-openapi-docs/src/theme/styles.scss` +- Modify: `packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/**/*.scss` +- Modify: `packages/docusaurus-theme-openapi-docs/src/theme/{ApiTabs,CodeSamples,Example,MimeTabs,OperationTabs,ParamsItem,SchemaItem,SchemaTabs}/**/*.scss` + +**Interfaces:** +- Consumes: existing OpenAPI component props/state and semantic tokens +- Produces: token-compliant forms, tabs, schemas, request/code/response panels + +- [ ] **Step 1: existing OpenAPI behavior tests 실행** + +```bash +yarn test --runInBand +``` + +Expected: baseline PASS. + +- [ ] **Step 2: forbidden OpenAPI token 제거** + +`--openapi-*` alias를 만들지 않고 각 declaration이 allowed semantic token을 직접 참조하도록 변경한다. DELETE/error만 `--destructive`; method badge 기본은 muted/foreground를 사용한다. + +- [ ] **Step 3: right panel과 controls 스타일링** + +Input은 `--input`, focus는 `--ring`, selected tab/action은 `--primary`, panel은 `--card`, border는 `--border`, radius는 `--radius`를 사용한다. + +- [ ] **Step 4: regression 검증** + +```bash +yarn test --runInBand +yarn build-demo +``` + +Expected: PASS 및 build exit 0. + +- [ ] **Step 5: commit** + +```bash +git add packages/docusaurus-theme-openapi-docs/src/theme +git commit -m "feat: apply semantic styles to OpenAPI docs" +``` + +### Task 5: Package consumer smoke test와 release validation + +**Files:** +- Modify: `packages/docusaurus-theme-openapi-docs/package.json` +- Create: `scripts/check-theme-tokens.mjs` +- Modify: root `package.json` +- Modify: `README.md` + +**Interfaces:** +- Consumes: completed theme package +- Produces: installable tarball and documented consumer config + +- [ ] **Step 1: package identity/build output 정리** + +Package name을 `docusaurus-theme-shadcn-docs`로 변경하고 compiled CSS 및 theme components가 tarball에 포함되도록 확인한다. + +- [ ] **Step 2: token audit script 연결** + +```json +{ + "scripts": { + "check:theme-tokens": "node scripts/check-theme-tokens.mjs" + } +} +``` + +- [ ] **Step 3: consumer install 문서 작성** + +README에 install, Docusaurus `themes` 등록, required token stylesheet, OpenAPI plugin 조합, local example을 포함한다. + +- [ ] **Step 4: full validation** + +```bash +yarn check:theme-tokens +yarn format +yarn lint +yarn test --runInBand +yarn build +npm pack --dry-run ./packages/docusaurus-theme-openapi-docs +``` + +Expected: 모두 exit 0; tarball에 JS/types/CSS 포함. + +- [ ] **Step 5: clean consumer smoke test** + +생성 tarball을 별도 clean Docusaurus 3.10.x fixture에 설치하고 일반 docs와 generated OpenAPI docs production build를 실행한다. + +- [ ] **Step 6: commit** + +```bash +git add packages/docusaurus-theme-openapi-docs/package.json scripts/check-theme-tokens.mjs package.json README.md +git commit -m "chore: prepare reusable theme package" +``` From 68e77d234d1734892e6310ace7394d443086cb82 Mon Sep 17 00:00:00 2001 From: aeei <18022843+aeei@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:41:26 +0900 Subject: [PATCH 03/30] feat: add neutral docs theme baseline --- demo/docusaurus.config.ts | 19 +- demo/src/css/tokens.css | 105 +++++ .../src/theme/base.scss | 371 ++++++++++++++++++ .../src/theme/base.test.ts | 13 + .../src/theme/styles.scss | 3 + 5 files changed, 493 insertions(+), 18 deletions(-) create mode 100644 demo/src/css/tokens.css create mode 100644 packages/docusaurus-theme-openapi-docs/src/theme/base.scss create mode 100644 packages/docusaurus-theme-openapi-docs/src/theme/base.test.ts diff --git a/demo/docusaurus.config.ts b/demo/docusaurus.config.ts index 15baf699a..d68c16f11 100644 --- a/demo/docusaurus.config.ts +++ b/demo/docusaurus.config.ts @@ -41,7 +41,7 @@ const config: Config = { }, blog: false, theme: { - customCss: ["./src/css/custom.css"], + customCss: ["./src/css/custom.css", "./src/css/tokens.css"], }, gtag: { trackingID: "GTM-THVM29S", @@ -93,7 +93,6 @@ const config: Config = { }, ], }, - { type: "custom-PalettePicker", position: "right" }, { href: "https://medium.com/palo-alto-networks-developer-blog", position: "right", @@ -387,22 +386,6 @@ const config: Config = { } satisfies Plugin.PluginOptions, }, ], - // FOUC prevention: restore saved palette before React hydrates - function paletteScript() { - return { - name: "palette-fouc-script", - injectHtmlTags() { - return { - headTags: [ - { - tagName: "script", - innerHTML: `try{var p=localStorage.getItem('openapi-demo-palette');if(p){var l=document.createElement('link');l.id='openapi-palette-link';l.rel='stylesheet';l.href='/themes/'+p+'.css';document.head.appendChild(l);}}catch(e){}`, - }, - ], - }; - }, - }; - }, ], themes: ["docusaurus-theme-openapi-docs"], stylesheets: [ diff --git a/demo/src/css/tokens.css b/demo/src/css/tokens.css new file mode 100644 index 000000000..92f345c96 --- /dev/null +++ b/demo/src/css/tokens.css @@ -0,0 +1,105 @@ +/* Reference token owner: shadcn Nova / Neutral. The reusable theme only consumes these. */ +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); + + /* Infima compatibility aliases; no independent values. */ + --ifm-color-primary: var(--primary); + --ifm-color-primary-dark: var(--primary); + --ifm-color-primary-darker: var(--primary); + --ifm-color-primary-darkest: var(--primary); + --ifm-color-primary-light: var(--primary); + --ifm-color-primary-lighter: var(--primary); + --ifm-color-primary-lightest: var(--primary); + --ifm-color-success: var(--chart-2); + --ifm-color-info: var(--chart-3); + --ifm-color-warning: var(--chart-4); + --ifm-color-danger: var(--destructive); + --ifm-color-emphasis-100: var(--muted); + --ifm-color-emphasis-200: var(--border); + --ifm-color-emphasis-300: var(--border); + --ifm-color-emphasis-400: var(--muted-foreground); + --ifm-color-emphasis-500: var(--muted-foreground); + --ifm-color-emphasis-600: var(--muted-foreground); + --ifm-color-emphasis-700: var(--foreground); + --ifm-color-emphasis-800: var(--foreground); + --ifm-color-emphasis-900: var(--foreground); + --ifm-color-content: var(--foreground); + --ifm-color-content-secondary: var(--muted-foreground); + --ifm-heading-color: var(--foreground); + --ifm-background-color: var(--background); + --ifm-background-surface-color: var(--card); + --ifm-navbar-background-color: var(--background); + --ifm-footer-background-color: var(--background); + --ifm-font-color-base: var(--foreground); + --ifm-toc-border-color: var(--border); + --ifm-menu-color-active: var(--primary); + --ifm-code-background: var(--muted); + --ifm-pre-background: var(--muted); + --ifm-global-radius: var(--radius); + --ifm-pre-border-radius: var(--radius); +} + +[data-theme="dark"] { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.922 0 0); + --sidebar-primary-foreground: oklch(0.205 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/base.scss b/packages/docusaurus-theme-openapi-docs/src/theme/base.scss new file mode 100644 index 000000000..593f22eda --- /dev/null +++ b/packages/docusaurus-theme-openapi-docs/src/theme/base.scss @@ -0,0 +1,371 @@ +/* Reusable docs shell. Values come from the consuming site's semantic tokens. */ + +:root { + --ifm-color-primary: var(--primary); + --ifm-background-color: var(--background); + --ifm-background-surface-color: var(--card); + --ifm-font-color-base: var(--foreground); + --ifm-heading-color: var(--foreground); + --ifm-navbar-background-color: var(--background); + --ifm-footer-background-color: var(--background); + --ifm-toc-border-color: var(--border); + --ifm-menu-color: var(--muted-foreground); + --ifm-menu-color-active: var(--primary); + --ifm-code-background: var(--muted); + --ifm-global-border-width: 1px; + --ifm-global-radius: var(--radius); + --ifm-navbar-height: 3.5rem; + --doc-sidebar-width: 16rem; + --ifm-font-family-base: Inter, ui-sans-serif, system-ui, sans-serif; + --ifm-heading-font-family: Inter, ui-sans-serif, system-ui, sans-serif; + --ifm-font-family-monospace: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; +} + +html, +body { + background: var(--background); + color: var(--foreground); +} + +body { + font-feature-settings: + "rlig" 1, + "calt" 1; + text-rendering: optimizeLegibility; +} + +* { + border-color: var(--border); +} + +::selection { + background: var(--primary); + color: var(--primary-foreground); +} + +:where(a, button, input, select, textarea, [tabindex]):focus-visible { + outline: 2px solid var(--ring); + outline-offset: 2px; +} + +/* Header */ +.navbar { + border-bottom: 1px solid var(--border); + background: color-mix(in oklab, var(--background) 92%, transparent); + box-shadow: none; + backdrop-filter: blur(12px); +} + +.navbar__inner { + width: 100%; + max-width: 96rem; + margin-inline: auto; +} + +.navbar__brand { + gap: 0.5rem; + color: var(--foreground); + font-weight: 600; +} + +.navbar__logo { + height: 1.75rem; +} + +.navbar__item, +.navbar__link { + color: var(--muted-foreground); + font-size: 0.875rem; + font-weight: 500; +} + +.navbar__link:hover, +.navbar__link--active { + color: var(--primary); +} + +.navbar__search-input { + width: 15rem; + height: 2.25rem; + border: 1px solid var(--input); + border-radius: calc(var(--radius) - 0.125rem); + background-color: var(--muted); + color: var(--foreground); + box-shadow: none; + font-size: 0.8125rem; +} + +.navbar__search-input:focus { + border-color: var(--ring); + background-color: var(--background); +} + +/* Sidebar */ +.theme-doc-sidebar-container { + width: 16rem !important; + border-right: 1px solid var(--sidebar-border) !important; + background: var(--sidebar) !important; + color: var(--sidebar-foreground); +} + +.theme-doc-sidebar-menu { + padding: 1rem 0.75rem 2rem; +} + +.docs-wrapper .menu { + padding: 0; + font-size: 0.875rem; + font-weight: 450; +} + +.docs-wrapper .menu__list .menu__list { + margin-top: 0.25rem; + padding-left: 0.75rem; + border-left: 1px solid var(--sidebar-border); +} + +.docs-wrapper .menu__list-item { + margin-top: 0.125rem; +} + +.docs-wrapper .menu__link, +.docs-wrapper .menu__caret { + min-height: 2rem; + border-radius: calc(var(--radius) - 0.25rem); + color: var(--sidebar-foreground); + line-height: 1.25rem; +} + +.docs-wrapper .menu__link { + padding: 0.375rem 0.625rem; +} + +.docs-wrapper .menu__link:hover, +.docs-wrapper .menu__list-item-collapsible:hover { + background: var(--sidebar-accent); + color: var(--sidebar-accent-foreground); +} + +.docs-wrapper .menu__link--active:not(.menu__link--sublist) { + background: var(--sidebar-primary); + color: var(--sidebar-primary-foreground); + font-weight: 500; +} + +.docs-wrapper .menu__list-item-collapsible { + border-radius: calc(var(--radius) - 0.25rem); +} + +.docs-wrapper + .theme-doc-sidebar-item-category-level-1 + > .menu__list-item-collapsible + > .menu__link { + color: var(--muted-foreground); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.025em; + text-transform: none; +} + +/* Content */ +.main-wrapper { + background: var(--background); +} + +.theme-doc-markdown { + color: var(--foreground); + font-size: 0.9375rem; + line-height: 1.75; +} + +.theme-doc-markdown > header, +.theme-doc-markdown > h1:first-child { + margin-bottom: 1.5rem; +} + +.theme-doc-markdown h1 { + font-size: 2.25rem; + font-weight: 650; + letter-spacing: -0.035em; + line-height: 1.15; +} + +.theme-doc-markdown h2 { + margin-top: 3rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--border); + font-size: 1.5rem; + font-weight: 600; + letter-spacing: -0.025em; +} + +.theme-doc-markdown h3 { + margin-top: 2rem; + font-size: 1.125rem; + font-weight: 600; + letter-spacing: -0.015em; +} + +.theme-doc-markdown p, +.theme-doc-markdown li { + color: color-mix(in oklab, var(--foreground) 88%, var(--muted-foreground)); +} + +.theme-doc-markdown a { + color: var(--primary); + font-weight: 500; + text-underline-offset: 0.2em; +} + +.theme-doc-markdown code:not(pre code) { + border: 1px solid var(--border); + border-radius: calc(var(--radius) - 0.25rem); + background: var(--muted); + color: var(--foreground); + font-size: 0.8125em; + font-weight: 500; +} + +.theme-doc-markdown pre, +.theme-code-block { + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: none; +} + +.theme-doc-markdown table { + display: table; + width: 100%; + border: 1px solid var(--border); + border-radius: var(--radius); + font-size: 0.875rem; +} + +.theme-doc-markdown th { + background: var(--muted); + color: var(--foreground); + font-weight: 500; +} + +.theme-doc-markdown tr, +.theme-doc-markdown td, +.theme-doc-markdown th { + border-color: var(--border); +} + +.alert { + border: 1px solid var(--border); + border-left: 3px solid var(--primary); + border-radius: var(--radius); + background: var(--card); + color: var(--card-foreground); + box-shadow: none; +} + +.breadcrumbs { + margin-bottom: 1.5rem; + font-size: 0.8125rem; +} + +.breadcrumbs__link { + border-radius: calc(var(--radius) - 0.25rem); + color: var(--muted-foreground); +} + +.breadcrumbs__link:hover { + background: var(--accent); + color: var(--accent-foreground); +} + +.pagination-nav__link { + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--card); + box-shadow: none; +} + +.pagination-nav__link:hover { + border-color: var(--primary); +} + +.pagination-nav__label { + color: var(--foreground); +} + +.pagination-nav__sublabel { + color: var(--muted-foreground); +} + +/* Right rail */ +.table-of-contents { + border-left: 1px solid var(--border); + font-size: 0.8125rem; +} + +.table-of-contents__link { + color: var(--muted-foreground); +} + +.table-of-contents__link:hover, +.table-of-contents__link--active { + color: var(--primary); +} + +/* OpenAPI surfaces */ +.openapi-left-panel__container { + border-right: 1px solid var(--border) !important; +} + +.openapi-right-panel__container { + background: var(--background); +} + +.theme-api-markdown :where(input, select, textarea) { + border-color: var(--input); + border-radius: calc(var(--radius) - 0.125rem); + background: var(--background); + color: var(--foreground); + box-shadow: none; +} + +.theme-api-markdown :where(button, [role="tab"]) { + border-radius: calc(var(--radius) - 0.125rem); +} + +.theme-api-markdown :where(button[type="submit"], .button--primary) { + border-color: var(--primary); + background: var(--primary); + color: var(--primary-foreground); +} + +@media (min-width: 997px) { + .theme-doc-markdown:not(.theme-api-markdown) { + max-width: 52rem; + } +} + +@media (max-width: 996px) { + :root { + --ifm-navbar-height: 3.25rem; + } + + .navbar__inner { + padding-inline: 0.25rem; + } + + .theme-doc-markdown h1 { + font-size: 2rem; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/base.test.ts b/packages/docusaurus-theme-openapi-docs/src/theme/base.test.ts new file mode 100644 index 000000000..c9e4fe217 --- /dev/null +++ b/packages/docusaurus-theme-openapi-docs/src/theme/base.test.ts @@ -0,0 +1,13 @@ +import fs from "fs"; +import path from "path"; + +const stylesheetPath = path.join(__dirname, "base.scss"); + +it("uses only the public semantic color token contract", () => { + const stylesheet = fs.readFileSync(stylesheetPath, "utf8"); + const forbiddenToken = /--(?:docs|api|openapi|nextra|fumadocs)-/; + const hardcodedColor = /#[\da-f]{3,8}\b|\b(?:rgb|hsl)a?\(/i; + + expect(stylesheet).not.toMatch(forbiddenToken); + expect(stylesheet).not.toMatch(hardcodedColor); +}); diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss b/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss index 103ca7c4e..383c335fd 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss +++ b/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss @@ -4,6 +4,9 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * ========================================================================== */ +/* Shared docs shell */ +@use "./base"; + /* API Demo Panel */ @use "./ApiExplorer/CodeTabs/CodeTabs"; @use "./ApiExplorer/FloatingButton/FloatingButton"; From 1d82e4000722a0aab02a9266c4e9de5adc88280b Mon Sep 17 00:00:00 2001 From: aeei <18022843+aeei@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:52:47 +0900 Subject: [PATCH 04/30] feat: adopt Lucide icons in OpenAPI controls --- demo/src/components/PalettePicker/index.tsx | 244 ------------------ .../PalettePicker/styles.module.css | 203 --------------- demo/src/theme/NavbarItem/index.tsx | 14 - .../package.json | 1 + .../ApiCodeBlock/CopyButton/index.tsx | 15 +- .../ApiCodeBlock/ExitButton/index.tsx | 8 +- .../ApiCodeBlock/ExpandButton/index.tsx | 15 +- .../ApiCodeBlock/WordWrapButton/index.tsx | 11 +- .../src/theme/SchemaExpansion/index.tsx | 17 +- .../src/theme/icon-contract.test.ts | 19 ++ yarn.lock | 6 + 11 files changed, 39 insertions(+), 514 deletions(-) delete mode 100644 demo/src/components/PalettePicker/index.tsx delete mode 100644 demo/src/components/PalettePicker/styles.module.css create mode 100644 packages/docusaurus-theme-openapi-docs/src/theme/icon-contract.test.ts diff --git a/demo/src/components/PalettePicker/index.tsx b/demo/src/components/PalettePicker/index.tsx deleted file mode 100644 index ee31f2361..000000000 --- a/demo/src/components/PalettePicker/index.tsx +++ /dev/null @@ -1,244 +0,0 @@ -/* ============================================================================ - * PalettePicker — runtime color palette switcher for the navbar. - * - * Desktop: pill button in the top bar that opens a dropdown. - * Mobile: section rendered inside the hamburger drawer (mobile={true}). - * - * Dynamically injects /themes/.css into and persists - * the choice in localStorage under 'openapi-demo-palette'. - * ========================================================================== */ - -import React, { useEffect, useRef, useState } from "react"; - -import styles from "./styles.module.css"; - -const THEMES = [ - { id: "evergreen", label: "Evergreen", color: "#2e8555" }, - { id: "panw", label: "PANW", color: "#004c9d" }, - { id: "violet", label: "Violet", color: "#7c3aed" }, - { id: "midnight", label: "Midnight", color: "#0284c7" }, - { id: "indigo", label: "Indigo", color: "#6366f1" }, - { id: "nord", label: "Nord", color: "#5e81ac" }, - { id: "cyber", label: "Cyber", color: "#059669" }, -] as const; - -type ThemeId = (typeof THEMES)[number]["id"]; - -const STORAGE_KEY = "openapi-demo-palette"; -const DEFAULT_PALETTE: ThemeId = "evergreen"; - -function applyPalette(id: ThemeId): void { - let link = document.getElementById( - "openapi-palette-link" - ) as HTMLLinkElement | null; - if (!link) { - link = document.createElement("link"); - link.id = "openapi-palette-link"; - link.rel = "stylesheet"; - document.head.appendChild(link); - } - link.href = `/themes/${id}.css`; - localStorage.setItem(STORAGE_KEY, id); -} - -function MobileSection({ - active, - onSelect, - current, -}: { - active: ThemeId; - onSelect: (id: ThemeId) => void; - current: (typeof THEMES)[number]; -}) { - const [expanded, setExpanded] = useState(false); - - return ( -
- - - {expanded && ( -
- {THEMES.map((theme) => ( - - ))} -
- )} -
- ); -} - -export default function PalettePicker({ - mobile, -}: { - mobile?: boolean; -}): JSX.Element | null { - const [open, setOpen] = useState(false); - const [active, setActive] = useState(DEFAULT_PALETTE); - const containerRef = useRef(null); - - useEffect(() => { - const saved = - (localStorage.getItem(STORAGE_KEY) as ThemeId) ?? DEFAULT_PALETTE; - setActive(saved); - if (!document.getElementById("openapi-palette-link")) { - applyPalette(saved); - } - }, []); - - // Close dropdown on outside pointer-down (desktop only) - useEffect(() => { - if (mobile) return; - function onPointerDown(e: PointerEvent) { - if ( - containerRef.current && - !containerRef.current.contains(e.target as Node) - ) { - setOpen(false); - } - } - document.addEventListener("pointerdown", onPointerDown); - return () => document.removeEventListener("pointerdown", onPointerDown); - }, [mobile]); - - useEffect(() => { - if (mobile) return; - function onKeyDown(e: KeyboardEvent) { - if (e.key === "Escape") setOpen(false); - } - document.addEventListener("keydown", onKeyDown); - return () => document.removeEventListener("keydown", onKeyDown); - }, [mobile]); - - function select(id: ThemeId) { - setActive(id); - applyPalette(id); - setOpen(false); - } - - const current = THEMES.find((t) => t.id === active) ?? THEMES[0]; - - /* ---- Mobile drawer ---------------------------------------------------- */ - if (mobile) { - return ( - - ); - } - - /* ---- Desktop dropdown ------------------------------------------------- */ - return ( -
- - - {open && ( -
- {THEMES.map((theme) => ( - - ))} -
- )} -
- ); -} diff --git a/demo/src/components/PalettePicker/styles.module.css b/demo/src/components/PalettePicker/styles.module.css deleted file mode 100644 index 3d14dfcf8..000000000 --- a/demo/src/components/PalettePicker/styles.module.css +++ /dev/null @@ -1,203 +0,0 @@ -/* ============================================================ - * Desktop — pill trigger + dropdown - * ============================================================ */ - -.root { - position: relative; - display: flex; - align-items: center; -} - -.trigger { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px 4px 8px; - border: 1px solid var(--ifm-color-emphasis-300); - border-radius: 6px; - background: transparent; - color: var(--ifm-color-content); - cursor: pointer; - font-size: 13px; - font-weight: 500; - font-family: inherit; - height: 32px; - line-height: 1; - transition: - background 150ms, - border-color 150ms; - white-space: nowrap; -} - -.trigger:hover { - background: var(--ifm-color-emphasis-100); - border-color: var(--ifm-color-emphasis-400); -} - -.swatch { - width: 10px; - height: 10px; - border-radius: 50%; - flex-shrink: 0; - display: inline-block; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15); -} - -.triggerLabel { - /* inherits from .trigger */ -} - -.chevron { - margin-left: 2px; - flex-shrink: 0; - color: var(--ifm-color-emphasis-600); - transition: transform 150ms ease; -} - -.chevronOpen { - transform: rotate(180deg); -} - -.dropdown { - position: absolute; - top: calc(100% + 8px); - right: 0; - z-index: 500; - min-width: 160px; - background: var(--ifm-background-color); - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: 8px; - box-shadow: - 0 4px 16px rgba(0, 0, 0, 0.12), - 0 1px 4px rgba(0, 0, 0, 0.08); - padding: 4px; - display: flex; - flex-direction: column; -} - -.option { - display: flex; - align-items: center; - gap: 8px; - padding: 7px 10px; - border: none; - border-radius: 6px; - background: transparent; - color: var(--ifm-color-content); - cursor: pointer; - font-size: 13px; - font-family: inherit; - font-weight: 400; - text-align: left; - width: 100%; - transition: background 100ms; -} - -.option:hover { - background: var(--ifm-color-emphasis-100); -} - -.optionActive { - font-weight: 600; -} - -.check { - margin-left: auto; - flex-shrink: 0; - color: var(--ifm-color-primary); -} - -/* Hide pill button in mobile top bar — accessible via the drawer instead */ -@media (max-width: 996px) { - .root { - display: none; - } -} - -/* ============================================================ - * Mobile drawer — flat grid of labeled swatches - * ============================================================ */ - -.mobileRoot { - border-top: 1px solid var(--ifm-color-emphasis-200); - margin-top: 4px; -} - -.mobileToggle { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 12px 16px; - border: none; - background: transparent; - color: var(--ifm-color-content); - cursor: pointer; - font-size: 14px; - font-family: inherit; - font-weight: 500; - text-align: left; -} - -.mobileToggle:hover { - background: var(--ifm-color-emphasis-100); -} - -.mobileToggleLeft { - display: flex; - align-items: center; - gap: 8px; -} - -.mobileChevron { - color: var(--ifm-color-emphasis-600); - transition: transform 200ms ease; - flex-shrink: 0; - transform: rotate(-90deg); -} - -.mobileChevronOpen { - transform: rotate(0deg); -} - -.mobileGrid { - padding: 4px 12px 12px; - display: grid; - grid-template-columns: 1fr 1fr; - gap: 6px; -} - -.mobileTile { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 10px; - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: 8px; - background: transparent; - color: var(--ifm-color-content); - cursor: pointer; - font-size: 13px; - font-family: inherit; - font-weight: 400; - text-align: left; - transition: background 100ms; -} - -.mobileTile:hover { - background: var(--ifm-color-emphasis-100); -} - -.mobileTileActive { - border-color: var(--ifm-color-primary); - font-weight: 600; - background: var(--ifm-color-emphasis-100); -} - -.mobileSwatch { - width: 12px; - height: 12px; - border-radius: 50%; - flex-shrink: 0; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15); -} diff --git a/demo/src/theme/NavbarItem/index.tsx b/demo/src/theme/NavbarItem/index.tsx index c4e34ad5b..a2c6e6896 100644 --- a/demo/src/theme/NavbarItem/index.tsx +++ b/demo/src/theme/NavbarItem/index.tsx @@ -1,23 +1,9 @@ -/* ============================================================================ - * NavbarItem swizzle — intercepts custom-PalettePicker before - * ComponentTypes lookup so no registry change is needed. - * ========================================================================== */ - import React from "react"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any import OriginalNavbarItem from "@theme-original/NavbarItem"; -import PalettePicker from "@site/src/components/PalettePicker"; - type OriginalProps = React.ComponentProps; -// eslint-disable-next-line @typescript-eslint/no-explicit-any export default function NavbarItem(props: OriginalProps): JSX.Element { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((props as any).type === "custom-PalettePicker") { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return ; - } return ; } diff --git a/packages/docusaurus-theme-openapi-docs/package.json b/packages/docusaurus-theme-openapi-docs/package.json index b31514bfc..f2a0ff5eb 100644 --- a/packages/docusaurus-theme-openapi-docs/package.json +++ b/packages/docusaurus-theme-openapi-docs/package.json @@ -52,6 +52,7 @@ "crypto-js": "^4.2.0", "file-saver": "^2.0.5", "lodash": "^4.17.21", + "lucide-react": "^0.468.0", "pako": "^2.1.0", "path-browserify": "^1.0.1", "postman-code-generators": "^2.0.0", diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/CopyButton/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/CopyButton/index.tsx index 87e5517a8..02bdcfb67 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/CopyButton/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/CopyButton/index.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useState, useRef, useEffect } from "react"; import { translate } from "@docusaurus/Translate"; import clsx from "clsx"; +import { Check, Copy } from "lucide-react"; interface CopyButtonProps { code: string; @@ -77,18 +78,8 @@ export default function CopyButton({ className="openapi-explorer__code-block-copy-btn-icons" aria-hidden="true" > - - - - - - + + ); diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/ExitButton/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/ExitButton/index.tsx index 8c4a69fa8..936fec50c 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/ExitButton/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/ExitButton/index.tsx @@ -9,6 +9,7 @@ import React from "react"; import { translate } from "@docusaurus/Translate"; import clsx from "clsx"; +import { X } from "lucide-react"; export interface Props { readonly className: string; @@ -43,12 +44,7 @@ export default function ExitButton({ className="openapi-explorer__code-block-exit-btn-icons" aria-hidden="true" > - - - + ); diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/ExpandButton/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/ExpandButton/index.tsx index 7177931ac..04f6e79fe 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/ExpandButton/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ApiCodeBlock/ExpandButton/index.tsx @@ -14,6 +14,7 @@ import CopyButton from "@theme/ApiExplorer/ApiCodeBlock/CopyButton"; import ExitButton from "@theme/ApiExplorer/ApiCodeBlock/ExitButton"; import Line from "@theme/ApiExplorer/ApiCodeBlock/Line"; import clsx from "clsx"; +import { Check, Maximize2 } from "lucide-react"; import { Highlight, Language } from "prism-react-renderer"; import Modal from "react-modal"; @@ -77,18 +78,8 @@ export default function ExpandButton({ className="openapi-explorer__code-block-expand-btn-icons" aria-hidden="true" > - - - - - - + + - + /> ); } diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/SchemaExpansion/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/SchemaExpansion/index.tsx index d008bb07a..2f9ee0652 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/SchemaExpansion/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/SchemaExpansion/index.tsx @@ -17,6 +17,7 @@ import React, { import { translate } from "@docusaurus/Translate"; import clsx from "clsx"; +import { ChevronsUpDown } from "lucide-react"; import { useSchemaExpansion } from "./context"; @@ -32,21 +33,7 @@ export { const ALL_VALUE = Number.POSITIVE_INFINITY; const ExpandIcon: React.FC = () => ( - +