diff --git a/.gitignore b/.gitignore index 6e0851f..13b85bf 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,6 @@ packages/propel/.ds-shim/ .ds-sync/ ds-bundle/ packages/propel/.ds-storybook/ + +# Icon generation scratch dir (swapped into src/icons on success) +packages/propel/.icons.tmp/ diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 5e33a88..50aff82 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,5 +1,11 @@ { - "ignorePatterns": ["**/dist/**", "**/.claude/**", "**/.agents/**"], + "ignorePatterns": [ + "**/dist/**", + "**/.claude/**", + "**/.agents/**", + "**/src/icons/**", + "**/.icons.tmp/**" + ], "sortTailwindcss": { "functions": ["cva", "cx"], "stylesheet": "packages/propel/tailwind.css" diff --git a/.oxlintrc.json b/.oxlintrc.json index b3c10ed..988cdf1 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -8,7 +8,9 @@ "**/.storybook/**", "**/storybook-static/**", "**/vitest.config.ts", - "**/tsdown.config.ts" + "**/tsdown.config.ts", + "**/src/icons/**", + "**/.icons.tmp/**" ], "rules": { "propel/prefer-tailwind-v4-shorthand": "error", diff --git a/docs/propel-icons/design.md b/docs/propel-icons/design.md new file mode 100644 index 0000000..703313e --- /dev/null +++ b/docs/propel-icons/design.md @@ -0,0 +1,113 @@ +# Propel icons — design + +Ship Plane's icon set (741 SVGs from the Figma [Foundations / Iconography](https://www.figma.com/design/pr5DXHRES2d5QM1qMlr0io/Foundations?node-id=1612-6650) section) as tree-shakeable React components under a new `@makeplane/propel/icons` subpath. + +## Decisions + +| Question | Decision | +| --- | --- | +| Where do icons live? | Inside `packages/propel`, published as the `@makeplane/propel/icons` subpath | +| Import API | Named per-icon exports from one entry: `import { StickyNoteOutline } from "@makeplane/propel/icons"` | +| Codegen model | SVGs committed as source of truth; generator output (`.tsx`) also committed; CI drift check | +| Color | Monochrome icons → `currentColor`; multi-color brand marks keep their palette | +| Default size | `1em` (tracks font-size, overridable via `className`/`width`/`height`) | + +### Why committed output instead of build-time generation + +- `tsdown.config.ts` builds its entry map by scanning real folders on disk; typecheck, Storybook, Vitest, and the editor TS server all need the component files to exist. Build-time generation would put a "generate first" step in front of every workflow. +- Committed output makes icon changes reviewable in PRs: a Figma re-sync shows exactly which components changed. +- Staleness is prevented by a `check:icons` task that re-runs the generator and fails on any drift between the SVGs and the committed output — the guarantee of build-time generation without its DX cost. + +Build-time generation wins when output is huge, per-environment, or constantly churning; none of that applies to an icon set that changes only when design ships new icons. + +## File layout + +``` +packages/propel/ + icons/ # SVG source of truth (moved from repo root), one folder per Figma category + actions/add-outline.svg + layouts/board-filled.svg + … + scripts/generate-icons.mjs # the generator + src/icons/ # generated output, committed — never hand-edited + index.ts # barrel: export * from every icon module + actions/add-outline.tsx + … +``` + +- Categories (from the Figma section headers): `workspace-features`, `sub-brands`, `project-features`, `layouts`, `formatting-and-editors`, `properties`, `actions`, `charts`, `arrows`, `miscellaneous`, `other-products`, `placeholder`. +- Categories exist as folders for browsing and diffs only; they are **not** part of the import path. Consumers only ever import from `@makeplane/propel/icons`. +- Wiring: one static `"./icons"` entry in the package.json `exports` map, one named `icons/index` entry in `tsdown.config.ts`. With `unbundle: true`, every icon module stays its own output chunk, so per-icon tree-shaking works through the barrel. + +## Generated component template + +```tsx +// Generated by scripts/generate-icons.mjs — do not edit. +import type { SVGProps } from "react"; + +export function AddOutline(props: SVGProps) { + return ( + + + + ); +} +``` + +- Plain function component with an `SVGProps` spread — React 19, so `ref` is a normal prop; no `forwardRef`. +- `aria-hidden` by default (icons are decorative); consumers override via the spread when an icon is meaningful. +- Component names are PascalCased from the SVG filename (`h1-outline` → `H1Outline`, `plane-ai-2` → `PlaneAi2`). + +## Generator (`pnpm gen:icons`) + +`packages/propel/scripts/generate-icons.mjs`, run with the repo's script runner. Pipeline per SVG: + +1. **SVGO** cleanup: strip `width`/`height` (keep `viewBox`), strip `id`s and editor metadata, merge/minify paths. +2. **Color pass**: count distinct paint values (`fill`/`stroke`). Exactly one → replace with `currentColor` (monochrome icon). More than one → leave untouched (brand marks in `sub-brands` / `other-products` such as Instagram, MS Word). +3. **SVGR** SVG → TSX with the template above, TypeScript output. +4. **Barrel**: regenerate `src/icons/index.ts` with `export * from ".//"` lines, sorted for stable diffs. + +Error handling: + +- **Cross-category duplicates**: the Figma section repeats 14 filenames across two categories. Investigation showed 12 of the 14 are **genuinely different glyphs that coincidentally share a name** (e.g. `delete-filled` is a trash can in `actions` but an octagon-X in `arrows`); only the `toggle-off-*` pair is the same glyph (normalized in source to the `properties` copy). Policy: when two source SVGs share a filename, the generator compares post-SVGO content. Byte-identical → keep the first by category order, skip the rest. Different content → emit **both**; the occurrence in the later category gets its category appended to the component name (`arrows/delete-filled.svg` → `DeleteFilledArrows`) so the flat named-export API stays collision-free while filenames keep tracking Figma. A final uniqueness check over all export names fails the build if any collision survives. Expected output: 739 components (741 SVGs − 2 deduped toggle-off icons). +- Any SVG that fails to parse fails the whole run with its path. + +## Scripts and CI + +Following the repo's `check:*` / `fix:*` convention (turbo tasks + per-package scripts): + +- `gen:icons` — run the generator, write into `src/icons/`. +- `check:icons` — run the generator into a temp dir, diff against `src/icons/`, exit non-zero on any difference. Added to the `check` umbrella and `turbo.json`. + +Re-sync flow when design ships new icons: export SVGs into `packages/propel/icons//` → `pnpm gen:icons` → commit SVGs + generated output together. + +## Cleanup before first generation + +- The Figma MCP export wraps each icon in section-background junk; a cleaning script strips it. 14 of the 741 SVGs came through with a different structure ("no wrapper group") and were saved raw — these get fixed before the first generation: `group-filled`, `group-outline`, `unsyn-outline`, `unsyn-filled`, `anchor-link-filled`, `anchor-link-outline` (miscellaneous), `intake-outline`, `intake-filled` (project-features), `ms-word`, `ms-excel`, `ms-powerpoint`, `instagram`, `jira`, `codex` (other-products). +- All 741 manifest entries are verified on disk. + +## Docs + +`packages/propel/AGENTS.md` gets a short icons section: `src/icons/` is generated — never hand-edit; run `pnpm gen:icons` after changing SVGs; `lucide-react` remains the icon source inside `components` for now (migrating propel's own components to these icons is explicitly out of scope for this feature). + +## Testing + +One Vitest suite (`src/internal/icons.test.ts` — it cannot live in `src/icons/`, which is generated and drift-checked byte-for-byte): + +- The barrel exports the expected number of components (count derived from the SVG source tree, not hardcoded). +- Every export renders an `` element with a `viewBox`. +- Monochrome icons contain no hardcoded hex fills (everything is `currentColor`). + +## Out of scope + +- Migrating propel `components` off `lucide-react`. +- A Storybook gallery page for the icon set (nice-to-have follow-up). +- Automated Figma → repo sync (the manifest + download tooling from this effort lives in session scratchpad; a committed sync script can be a follow-up). diff --git a/docs/propel-icons/plan.md b/docs/propel-icons/plan.md new file mode 100644 index 0000000..9e9afe4 --- /dev/null +++ b/docs/propel-icons/plan.md @@ -0,0 +1,526 @@ +# Propel Icons 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:** Ship the 741 Figma icons as tree-shakeable React components under a new `@makeplane/propel/icons` subpath, with SVGs as committed source of truth, a committed generator output, and a CI drift check. + +**Architecture:** SVGs live in `packages/propel/icons//`. A Node script (`scripts/generate-icons.mjs`) runs SVGO cleanup, swaps monochrome fills to `currentColor`, converts to TSX via SVGR, and writes committed components into `src/icons/` plus a barrel `index.ts`. The barrel becomes one tsdown entry / one `exports` subpath; `unbundle: true` keeps per-icon tree-shaking. `check:icons` regenerates into a temp dir and diffs. + +**Tech Stack:** Node ESM script, `svgo` v4, `@svgr/core` + `@svgr/plugin-jsx` v8, tsdown, Vitest (node project), Turborepo. + +**Spec:** `docs/propel-icons/design.md` + +## Global Constraints + +- Package manager is pnpm; run package-local scripts as `pnpm --filter @makeplane/propel run ")] + # Unwrap ONE outer page-level if it wraps the entire body. + m = re.fullmatch(r"\s*]*>(.*)\s*", body, re.S) + if m: + body = m.group(1) + # Drop full-canvas background rects and junk paths (self-closing elements only). + body = re.sub(r']*width="16" height="16"[^>]*/>\s*', "", body) + body = re.sub(r"]*/>", lambda mm: "" if is_junk(mm.group(0)) else mm.group(0), body) + p.write_text(head + "\n" + body.strip() + "\n\n") + print("fixed", rel) +``` + +Run: `python3 /tmp/fix-raw.py` +Expected: prints `fixed ` for all 14. + +- [ ] **Step 4: Verify the whole tree is clean and complete** + +Run: `python3 /tmp/scan-junk.py && find packages/propel/icons -name '*.svg' | wc -l` +Expected: `clean`, then `741`. If any of the 14 still fails the scan, its structure differs — open it, keep only the svg root + the icon's own geometry, delete the rest by hand, re-run the scan. + +- [ ] **Step 5: Spot-check the 14 fixed icons render correctly** + +Open a few in a browser (e.g. `open packages/propel/icons/other-products/jira.svg packages/propel/icons/miscellaneous/group-filled.svg packages/propel/icons/project-features/intake-outline.svg`) and confirm each shows a plausible icon, not a blank or a giant rectangle. Brand marks (jira, instagram, ms-*) should be multi-color. + +- [ ] **Step 6: Commit** + +```bash +git add -A packages/propel/icons icons +git commit -m "feat: add icon SVG source of truth from Figma" +``` + +--- + +### Task 2: The generator script + +**Files:** +- Create: `packages/propel/scripts/generate-icons.mjs` +- Create (generated): `packages/propel/src/icons/**/*.tsx`, `packages/propel/src/icons/index.ts` +- Modify: `packages/propel/package.json` (devDeps + `gen:icons` / `check:icons` scripts) + +**Interfaces:** +- Consumes: `packages/propel/icons//.svg` from Task 1. +- Produces: `src/icons//.tsx`, each exporting `export function (props: SVGProps)` (later-category shared-name icons carry a category suffix in the export name only, not the filename); `src/icons/index.ts` barrel with `export * from ".//";` lines. Task 3 wires `src/icons/index.ts` as the tsdown entry; Task 4's test imports `./index` and counts `.tsx` files. + +- [ ] **Step 1: Add devDependencies** + +```bash +pnpm --filter @makeplane/propel add -D svgo@^4.0.0 @svgr/core@^8.1.0 @svgr/plugin-jsx@^8.1.0 +``` + +- [ ] **Step 2: Write the generator** + +Create `packages/propel/scripts/generate-icons.mjs`: + +```js +// Generates src/icons/ from icons/ (SVG source of truth). See docs/propel-icons/design.md. +// Usage: node scripts/generate-icons.mjs [--check] +import { transform } from "@svgr/core"; +import { createHash } from "node:crypto"; +import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { optimize } from "svgo"; + +const pkgRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +const srcDir = path.join(pkgRoot, "icons"); +const outTarget = path.join(pkgRoot, "src", "icons"); +const HEADER = "// Generated by scripts/generate-icons.mjs — do not edit.\n"; + +// Figma section order — duplicate filenames keep the first occurrence in this order. +const CATEGORY_ORDER = [ + "placeholder", "workspace-features", "sub-brands", "project-features", "layouts", + "formatting-and-editors", "properties", "actions", "charts", "arrows", + "miscellaneous", "other-products", +]; + +// svgo v4: preset-default keeps viewBox by default (removeViewBox is opt-in now). +const svgoConfig = { plugins: ["preset-default", "removeDimensions"] }; + +function componentName(base, categorySuffix = "") { + const pascal = (base + (categorySuffix ? `-${categorySuffix}` : "")) + .split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(""); + return /^\d/.test(pascal) ? `Icon${pascal}` : pascal; +} + +// Swap paints to currentColor when the icon uses exactly one concrete color. +// Clip-rect fills inside / never render, so they are ignored +// when counting paints (else every clipPath-bearing monochrome icon reads as two-tone). +function monochromify(svg) { + const visible = svg.replace(/[\s\S]*?<\/defs>/g, "").replace(//g, ""); + const paints = new Set(); + for (const m of visible.matchAll(/(?:fill|stroke)="([^"]+)"/g)) { + const v = m[1]; + if (v !== "none" && v !== "currentColor" && !v.startsWith("url(")) paints.add(v); + } + if (paints.size !== 1) return svg; // multi-color brand mark (or already colorless): untouched + const only = [...paints][0]; + return svg.replaceAll(`"${only}"`, '"currentColor"'); +} + +const template = (variables, { tpl }) => tpl` +${variables.imports}; + +export function ${variables.componentName}(${variables.props}) { + return (${variables.jsx}); +} +`; + +async function generate(outDir) { + const categories = readdirSync(srcDir, { withFileTypes: true }) + .filter((d) => d.isDirectory()).map((d) => d.name); + for (const c of categories) { + if (!CATEGORY_ORDER.includes(c)) throw new Error(`Unknown category dir icons/${c} — add it to CATEGORY_ORDER`); + } + const seen = new Map(); // filename -> { category, hash } + const usedNames = new Set(); + const exportLines = []; + for (const category of CATEGORY_ORDER.filter((c) => categories.includes(c))) { + for (const file of readdirSync(path.join(srcDir, category)).filter((f) => f.endsWith(".svg")).sort()) { + const base = file.replace(/\.svg$/, ""); + const raw = readFileSync(path.join(srcDir, category, file), "utf8"); + const optimized = monochromify(optimize(raw, svgoConfig).data); + const hash = createHash("sha256").update(optimized).digest("hex"); + const prior = seen.get(file); + let suffix = ""; + if (prior) { + if (prior.hash === hash) continue; // identical duplicate — first category wins + // Different glyphs coincidentally sharing a Figma name: emit both, the + // later category disambiguates its export name (DeleteFilled vs DeleteFilledArrows). + suffix = category; + } else { + seen.set(file, { category, hash }); + } + const name = componentName(base, suffix); + if (usedNames.has(name)) throw new Error(`Export name collision: ${name} (icons/${category}/${file})`); + usedNames.add(name); + const tsx = await transform( + optimized, + { + plugins: ["@svgr/plugin-jsx"], + typescript: true, + jsxRuntime: "automatic", + expandProps: "end", + svgProps: { width: "1em", height: "1em", "aria-hidden": "true" }, + template, + }, + { componentName: name }, + ); + mkdirSync(path.join(outDir, category), { recursive: true }); + writeFileSync(path.join(outDir, category, `${base}.tsx`), HEADER + tsx); + exportLines.push(`export * from "./${category}/${base}";`); + } + } + exportLines.sort(); + writeFileSync(path.join(outDir, "index.ts"), HEADER + exportLines.join("\n") + "\n"); + return exportLines.length; +} + +function listFiles(dir, prefix = "") { + const out = []; + for (const d of readdirSync(dir, { withFileTypes: true })) { + const rel = path.join(prefix, d.name); + if (d.isDirectory()) out.push(...listFiles(path.join(dir, d.name), rel)); + else out.push(rel); + } + return out.sort(); +} + +const check = process.argv.includes("--check"); +if (check) { + const tmp = mkdtempSync(path.join(tmpdir(), "propel-icons-")); + try { + await generate(tmp); + const want = listFiles(tmp); + const have = listFiles(outTarget); + const drift = []; + for (const f of want) { + if (!have.includes(f)) drift.push(`missing: src/icons/${f}`); + else if (readFileSync(path.join(tmp, f), "utf8") !== readFileSync(path.join(outTarget, f), "utf8")) { + drift.push(`differs: src/icons/${f}`); + } + } + for (const f of have) if (!want.includes(f)) drift.push(`stale: src/icons/${f}`); + if (drift.length > 0) { + console.error(`check:icons FAILED — src/icons is out of sync with icons/. Run \`pnpm gen:icons\` and commit.\n${drift.join("\n")}`); + process.exit(1); + } + console.log(`check:icons OK (${want.length} files)`); + } finally { + rmSync(tmp, { recursive: true, force: true }); + } +} else { + rmSync(outTarget, { recursive: true, force: true }); + const count = await generate(outTarget); + console.log(`generated ${count} icon components into src/icons/`); +} +``` + +- [ ] **Step 3: Add package scripts** + +In `packages/propel/package.json` `"scripts"`, add (alphabetical position, matching the existing style): + +```json +"check:icons": "node scripts/generate-icons.mjs --check", +"gen:icons": "node scripts/generate-icons.mjs", +``` + +- [ ] **Step 4: Run the generator** + +First, normalize the one same-glyph-with-drift duplicate: overwrite `packages/propel/icons/miscellaneous/toggle-off-outline.svg` with the content of `packages/propel/icons/properties/toggle-off-outline.svg` (same glyph, minor Figma geometry drift; `properties` is first in category order). + +Run: `pnpm --filter @makeplane/propel run gen:icons` +Expected: `generated 739 icon components into src/icons/` (741 SVGs − 2 deduped toggle-off icons; the 12 distinct-glyph shared-name pairs each emit two components, the later category's export carrying its category suffix, e.g. `DeleteFilled` in `actions/` and `DeleteFilledArrows` in `arrows/`). If it throws "Export name collision", a suffixed name still collided — report it rather than working around it. + +- [ ] **Step 5: Verify the drift check passes and catches drift** + +```bash +pnpm --filter @makeplane/propel run check:icons +# expect: check:icons OK (740 files) (739 components + index.ts) +echo "// tamper" >> packages/propel/src/icons/index.ts +pnpm --filter @makeplane/propel run check:icons +# expect: exit 1, "differs: src/icons/index.ts" +git checkout -- packages/propel/src/icons/index.ts +``` + +- [ ] **Step 6: Inspect two outputs by eye** + +Read `packages/propel/src/icons/actions/add-outline.tsx` (or any actions icon): must be the template shape — header comment, `import type { SVGProps } from "react";`, `export function AddOutline(props: SVGProps)`, `
+ {categories.map((category) => { + const icons = byCategory.get(category) ?? []; + return ( +
+

+ {category.replaceAll("-", " ")} + · {icons.length} +

+
+ {icons.map(([name, Icon]) => ( +
+ +
+ {name} +
+
+ ))} +
+
+ ); + })} +
+ ), +}; diff --git a/packages/propel/tsdown.config.ts b/packages/propel/tsdown.config.ts index db89a60..6b04fa7 100644 --- a/packages/propel/tsdown.config.ts +++ b/packages/propel/tsdown.config.ts @@ -26,6 +26,9 @@ function subpathEntries(): Record { if (file) entry[`${group}/${dirent.name}/index`] = file; } } + // The icons tree is one subpath with a single barrel entry (not folder-per-icon); + // `unbundle` keeps every icon its own chunk for tree-shaking. + if (existsSync("src/icons/index.ts")) entry["icons/index"] = "src/icons/index.ts"; return entry; } @@ -51,9 +54,11 @@ export default defineConfig({ // maximum tree-shaking. unbundle: true, clean: true, - // Mark every emitted chunk as a client component so the library works inside - // React Server Component boundaries (e.g. Next.js app router). - banner: '"use client";', + // Mark emitted chunks as client components so the library works inside React Server Component + // boundaries (e.g. Next.js app router). Icons are exempt: they are pure static SVG with no + // hooks, state, or events, so a `"use client"` there would open a client boundary in a server + // component and ship the glyph's JS to the browser instead of rendering it on the server. + banner: ({ fileName }) => (fileName.startsWith("icons/") ? undefined : '"use client";'), // Ship the design tokens as standalone CSS (not bundled through JS). copy: [{ from: "src/styles/*.css", to: "dist/styles" }], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae7a375..483db0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,6 +21,12 @@ catalogs: '@oxlint/plugins': specifier: 1.72.0 version: 1.72.0 + '@svgr/core': + specifier: ^8.1.0 + version: 8.1.0 + '@svgr/plugin-jsx': + specifier: ^8.1.0 + version: 8.1.0 '@tailwindcss/typography': specifier: ^0.5.20 version: 0.5.20 @@ -57,6 +63,9 @@ catalogs: publint: specifier: ^0.3.21 version: 0.3.21 + svgo: + specifier: ^4.0.2 + version: 4.0.2 tailwindcss: specifier: ^4.3.2 version: 4.3.2 @@ -210,6 +219,12 @@ importers: '@storybook/react-vite': specifier: ^10.4.6 version: 10.5.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.5.0(@types/react@19.2.17)(prettier@3.9.5)(react@19.2.7))(typescript@6.0.3)(vite@8.1.4(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) + '@svgr/core': + specifier: 'catalog:' + version: 8.1.0(typescript@6.0.3) + '@svgr/plugin-jsx': + specifier: 'catalog:' + version: 8.1.0(@svgr/core@8.1.0(typescript@6.0.3)) '@tailwindcss/vite': specifier: 'catalog:' version: 4.3.2(vite@8.1.4(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) @@ -249,6 +264,9 @@ importers: storybook-addon-pseudo-states: specifier: ^10.4.6 version: 10.5.0(storybook@10.5.0(@types/react@19.2.17)(prettier@3.9.5)(react@19.2.7)) + svgo: + specifier: 'catalog:' + version: 4.0.2 tailwindcss: specifier: 'catalog:' version: 4.3.2 @@ -2290,6 +2308,74 @@ packages: typescript: optional: true + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + '@tailwindcss/node@4.3.2': resolution: {integrity: sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==} @@ -2939,6 +3025,14 @@ packages: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + caniuse-lite@1.0.30001805: resolution: {integrity: sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==} @@ -3033,6 +3127,15 @@ packages: resolution: {integrity: sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==} engines: {node: '>=22'} + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3156,6 +3259,9 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dset@3.1.4: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} @@ -3198,6 +3304,9 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} @@ -3455,6 +3564,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + import-without-cache@0.4.0: resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} engines: {node: ^22.18.0 || >=24.0.0} @@ -3475,6 +3588,9 @@ packages: is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-core-module@2.16.2: resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} @@ -3570,6 +3686,9 @@ packages: engines: {node: '>=6'} hasBin: true + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -3665,6 +3784,9 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -3678,6 +3800,9 @@ packages: loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} @@ -3932,6 +4057,9 @@ packages: nlcst-to-string@4.0.0: resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} @@ -4047,9 +4175,17 @@ packages: package-manager-detector@1.7.0: resolution: {integrity: sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + parse-latin@7.0.0: resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} @@ -4289,6 +4425,10 @@ packages: reselect@5.2.0: resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -4431,6 +4571,9 @@ packages: resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==} engines: {node: '>= 18'} + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4523,6 +4666,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svg-parser@2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + svgo@4.0.2: resolution: {integrity: sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==} engines: {node: '>=16'} @@ -6832,6 +6978,76 @@ snapshots: transitivePeerDependencies: - supports-color + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@svgr/babel-preset@8.1.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.7) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.7) + + '@svgr/core@8.1.0(typescript@6.0.3)': + dependencies: + '@babel/core': 7.29.7 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@6.0.3) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.29.7 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@6.0.3))': + dependencies: + '@babel/core': 7.29.7 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7) + '@svgr/core': 8.1.0(typescript@6.0.3) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + '@tailwindcss/node@4.3.2': dependencies: '@jridgewell/remapping': 2.3.5 @@ -7525,6 +7741,10 @@ snapshots: cac@7.0.0: {} + callsites@3.1.0: {} + + camelcase@6.3.0: {} + caniuse-lite@1.0.30001805: {} ccount@2.0.1: {} @@ -7597,6 +7817,15 @@ snapshots: cookie@2.0.1: {} + cosmiconfig@8.3.6(typescript@6.0.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 6.0.3 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -7706,6 +7935,11 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + dset@3.1.4: {} dts-resolver@3.0.0(oxc-resolver@11.24.2): @@ -7737,6 +7971,10 @@ snapshots: entities@6.0.1: {} + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + error-stack-parser-es@1.0.5: {} es-errors@1.3.0: {} @@ -8107,6 +8345,11 @@ snapshots: ignore@5.3.2: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + import-without-cache@0.4.0: {} indent-string@4.0.0: {} @@ -8122,6 +8365,8 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 + is-arrayish@0.2.1: {} + is-core-module@2.16.2: dependencies: hasown: 2.0.4 @@ -8191,6 +8436,8 @@ snapshots: jsesc@3.1.0: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@1.0.0: {} json5@2.2.3: {} @@ -8254,6 +8501,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lines-and-columns@1.2.4: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -8264,6 +8513,10 @@ snapshots: loupe@3.2.1: {} + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + lru-cache@11.5.2: {} lru-cache@5.1.1: @@ -8788,6 +9041,11 @@ snapshots: dependencies: '@types/nlcst': 2.0.3 + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + node-fetch-native@1.6.7: {} node-mock-http@1.0.4: {} @@ -8963,6 +9221,10 @@ snapshots: package-manager-detector@1.7.0: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -8973,6 +9235,13 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + parse-latin@7.0.0: dependencies: '@types/nlcst': 2.0.3 @@ -9248,6 +9517,8 @@ snapshots: reselect@5.2.0: {} + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -9485,6 +9756,11 @@ snapshots: smol-toml@1.7.0: {} + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + source-map-js@1.2.1: {} source-map@0.6.1: {} @@ -9576,6 +9852,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svg-parser@2.0.4: {} + svgo@4.0.2: dependencies: commander: 11.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fc72a29..1ccb7cf 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -37,6 +37,9 @@ catalog: tsdown: ^0.22.9 publint: ^0.3.21 "@arethetypeswrong/core": ^0.18.5 + svgo: ^4.0.2 + '@svgr/core': ^8.1.0 + '@svgr/plugin-jsx': ^8.1.0 overrides: # Keep a single Vite instance across storybook + vitest + the tailwind plugin (duplicate diff --git a/turbo.json b/turbo.json index 5d774c2..3edbc7e 100644 --- a/turbo.json +++ b/turbo.json @@ -11,12 +11,16 @@ "outputs": ["storybook-static/**"] }, "check": { - "dependsOn": ["check:format", "check:lint", "check:types"] + "dependsOn": ["check:format", "check:lint", "check:types", "check:icons"] }, "check:format": { "inputs": ["$TURBO_DEFAULT$"], "outputs": [] }, + "check:icons": { + "inputs": ["icons/**", "scripts/generate-icons.mjs", "src/icons/**"], + "outputs": [] + }, "check:lint": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", "!**/*.md"],