From fd9fc53f9309d90f1d7c2be10123889d09171bd3 Mon Sep 17 00:00:00 2001 From: seungwon Lee Date: Thu, 8 Oct 2026 18:41:44 +0900 Subject: [PATCH 01/15] chore: set up Storybook with per-PR previews on GitHub Pages Add Storybook 10 (react-vite) for ui-common's components, modeled on backend.ai-webui's packages/backend.ai-ui setup: - .storybook/: stories render inside and Astryx's InternationalizationProvider, with toolbar switches for the theme (Lablup / Astryx neutral), the locale (Astryx + ui-common catalogs) and dark mode. Storybook uses its own minimal Vite config instead of the library build's. - Seed stories for StatCard and PageHeader, and an Introduction page. - Stories are excluded from dts emission, so dist is unchanged. Add the PR Preview workflow: build Storybook, deploy it to gh-pages under pr//storybook/, and post or update a "PR Analysis Report" comment that links each changed component's stories and the bundle-size delta against the base. A cleanup workflow removes previews of closed PRs and compacts gh-pages history weekly. GitHub Pages itself still has to be enabled for the gh-pages branch. Resolves #61 Co-Authored-By: Claude Opus 5.5 --- .github/scripts/pr-preview/analyze-pr.mjs | 190 +++ .../pr-preview/compact-gh-pages-history.sh | 71 + .../pr-preview/generate-pr-comment.mjs | 233 +++ .github/scripts/pr-preview/measure-dist.mjs | 72 + .github/scripts/pr-preview/pr-preview.test.ts | 252 +++ .github/scripts/pr-preview/write-index.mjs | 73 + .github/workflows/pr-preview-cleanup.yml | 160 ++ .github/workflows/pr-preview.yml | 340 ++++ .gitignore | 2 + .prettierignore | 1 + .storybook/DocsContainer.tsx | 35 + .storybook/decorators.tsx | 102 ++ .storybook/localeConfig.ts | 28 + .storybook/main.ts | 29 + .storybook/manager.ts | 10 + .storybook/preview-head.html | 12 + .storybook/preview.tsx | 53 + .storybook/storybook.css | 13 + .storybook/storybookTheme.ts | 55 + .storybook/vite.config.ts | 24 + eslint.config.js | 9 + package.json | 7 + pnpm-lock.yaml | 1416 ++++++++++++++++- pnpm-workspace.yaml | 3 + src/Introduction.mdx | 30 + .../PageHeader/PageHeader.stories.tsx | 35 + src/components/StatCard/StatCard.stories.tsx | 68 + tsconfig.json | 2 +- vite.config.ts | 7 +- 29 files changed, 3316 insertions(+), 16 deletions(-) create mode 100644 .github/scripts/pr-preview/analyze-pr.mjs create mode 100755 .github/scripts/pr-preview/compact-gh-pages-history.sh create mode 100644 .github/scripts/pr-preview/generate-pr-comment.mjs create mode 100644 .github/scripts/pr-preview/measure-dist.mjs create mode 100644 .github/scripts/pr-preview/pr-preview.test.ts create mode 100644 .github/scripts/pr-preview/write-index.mjs create mode 100644 .github/workflows/pr-preview-cleanup.yml create mode 100644 .github/workflows/pr-preview.yml create mode 100644 .storybook/DocsContainer.tsx create mode 100644 .storybook/decorators.tsx create mode 100644 .storybook/localeConfig.ts create mode 100644 .storybook/main.ts create mode 100644 .storybook/manager.ts create mode 100644 .storybook/preview-head.html create mode 100644 .storybook/preview.tsx create mode 100644 .storybook/storybook.css create mode 100644 .storybook/storybookTheme.ts create mode 100644 .storybook/vite.config.ts create mode 100644 src/Introduction.mdx create mode 100644 src/components/PageHeader/PageHeader.stories.tsx create mode 100644 src/components/StatCard/StatCard.stories.tsx diff --git a/.github/scripts/pr-preview/analyze-pr.mjs b/.github/scripts/pr-preview/analyze-pr.mjs new file mode 100644 index 0000000..b492fce --- /dev/null +++ b/.github/scripts/pr-preview/analyze-pr.mjs @@ -0,0 +1,190 @@ +#!/usr/bin/env node +// Work out which components a PR changed, and map each one onto the story +// entries that Storybook actually built for it. The mapping is read out of the +// built `index.json` rather than guessed, so a component whose stories live +// under a different title still resolves. +// +// A component here is a directory: `src/components//` for ui-common's +// own components, `src/forks//` for its copies of Astryx components. Any +// file inside it (source, stylesheet, messages, stories) counts as a change to +// that component; tests alone do not, since they change nothing to look at. +// +// Usage: +// node analyze-pr.mjs --base origin/main --head HEAD \ +// --storybook-index --output analysis.json + +import { execFileSync } from "node:child_process"; +import { existsSync, readFileSync, writeFileSync } from "node:fs"; + +const COMPONENT_DIR_RE = /^src\/(components|forks)\/([^/]+)\//; + +const getArg = (name) => { + const i = process.argv.indexOf(`--${name}`); + return i === -1 ? undefined : process.argv[i + 1]; +}; + +const base = getArg("base") ?? "origin/main"; +const head = getArg("head") ?? "HEAD"; +const indexPath = getArg("storybook-index"); +const output = getArg("output") ?? "analysis.json"; + +const git = (args) => + execFileSync("git", args, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }); + +// A three-dot diff reports only what the PR branch changed, ignoring commits +// that landed on base meanwhile — that is the list we want. It needs a merge +// base, which a shallow clone may not have once base has advanced past the +// fetch window; deepen and retry before degrading. The two-dot fallback needs +// no merge base but compares two trees, so it can both over-report base churn +// and under-report a change that also exists on base. Callers get `diffMode` so +// the report can say when the list is approximate. +function changedFiles() { + // `--name-status` so a newly ADDED component can be told apart from a + // modified one; the report only flags missing stories for the former. + const parse = (raw) => + raw + .split("\n") + .filter(Boolean) + .map((line) => { + const [status, ...rest] = line.split("\t"); + // Renames are `R100\told\tnew` — the new path is what we care about. + return { status: status[0], file: rest[rest.length - 1] }; + }); + + const threeDot = () => parse(git(["diff", "--name-status", `${base}...${head}`])); + + try { + return { files: threeDot(), diffMode: "three-dot" }; + } catch { + /* fall through to the deepen retry */ + } + + try { + const bareBase = base.replace(/^origin\//, ""); + git([ + "fetch", + "--deepen=200", + "origin", + `+refs/heads/${bareBase}:refs/remotes/origin/${bareBase}`, + ]); + return { files: threeDot(), diffMode: "three-dot" }; + } catch { + /* fall through to the two-dot fallback */ + } + + try { + return { + files: parse(git(["diff", "--name-status", `${base}..${head}`])), + diffMode: "two-dot", + }; + } catch (error) { + // A failed diff is not the same as "nothing changed" — publishing an empty + // analysis here would look legitimate and silently drop the whole report. + console.error(`::error::Could not diff ${base}..${head}: ${error.message}`); + process.exit(1); + } +} + +const isStory = (file) => /\.stories\.(ts|tsx)$/.test(file); +const isTest = (file) => /\.test\.(ts|tsx)$/.test(file); + +function loadStoryEntries() { + if (!indexPath || !existsSync(indexPath)) return []; + const parsed = JSON.parse(readFileSync(indexPath, "utf8")); + return Object.values(parsed.entries ?? {}); +} + +/** + * The stories built for one component directory, one link per story title + * (a directory such as `Skeleton/` can hold several components, each with its + * own stories file). The autodocs page comes first — it shows every story of + * the component on one page, which is what a reviewer wants first. + */ +function linksFor(dir, storyEntries) { + const byTitle = new Map(); + for (const entry of storyEntries) { + if (!entry.importPath?.startsWith(`./${dir}`)) continue; + const list = byTitle.get(entry.title) ?? []; + list.push(entry); + byTitle.set(entry.title, list); + } + return [...byTitle.entries()] + .map(([title, entries]) => { + const preferred = entries.find((e) => e.type === "docs") ?? entries[0]; + return { + title, + storyCount: entries.filter((e) => e.type === "story").length, + deepLink: `?path=/${preferred.type}/${preferred.id}`, + }; + }) + .sort((a, b) => a.title.localeCompare(b.title)); +} + +const { files, diffMode } = changedFiles(); +const storyEntries = loadStoryEntries(); + +// One row per changed component directory. +const rows = new Map(); +const accounted = new Set(); + +for (const { status, file } of files) { + const match = COMPONENT_DIR_RE.exec(file); + if (!match || isTest(file)) continue; + const [dir, kind, name] = match; + accounted.add(file); + + let row = rows.get(dir); + if (!row) { + const links = linksFor(dir, storyEntries); + row = { + name, + kind, + dir, + links, + hasStories: links.length > 0, + storyCount: links.reduce((n, l) => n + l.storyCount, 0), + sourceChanged: false, + storiesChanged: false, + isNew: false, + }; + rows.set(dir, row); + } + + if (isStory(file)) { + row.storiesChanged = true; + } else { + row.sourceChanged = true; + // A directory whose barrel was added in this PR is a new component. + if (status === "A" && file === `${dir}index.ts`) row.isNew = true; + } +} + +const components = [...rows.values()].sort((a, b) => a.name.localeCompare(b.name)); + +const analysis = { + diffMode, + base, + head, + changedFileCount: files.length, + // A hook, theme or i18n change still deploys a preview; the report says so + // rather than pretending nothing happened. + otherSourceFiles: files + .map(({ file }) => file) + .filter((f) => f.startsWith("src/") && !accounted.has(f) && !isTest(f)), + components, + summary: { + componentsChanged: components.filter((c) => c.sourceChanged).length, + // Only NEW components are counted as a gap. Most existing components + // have no story yet, and nagging about them on every unrelated edit trains + // reviewers to ignore the report. + newWithoutStories: components.filter((c) => c.isNew && !c.hasStories).length, + totalStories: storyEntries.filter((e) => e.type === "story").length, + }, +}; + +writeFileSync(output, `${JSON.stringify(analysis, null, 2)}\n`); +console.log( + `Analyzed ${files.length} changed files (${diffMode}); ` + + `${analysis.summary.componentsChanged} component(s) changed, ` + + `${analysis.summary.newWithoutStories} new one(s) without stories.`, +); diff --git a/.github/scripts/pr-preview/compact-gh-pages-history.sh b/.github/scripts/pr-preview/compact-gh-pages-history.sh new file mode 100755 index 0000000..63712eb --- /dev/null +++ b/.github/scripts/pr-preview/compact-gh-pages-history.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Squash gh-pages down to a single orphan commit. +# +# Every preview deploy and every cleanup adds a commit whose tree carries a full +# Storybook build per live PR. The history of those trees is worthless — only +# the current tree is ever served — but it is what makes the branch grow without +# bound. Rewriting to one orphan commit lets git drop every unreferenced blob on +# the next gc. +# +# Force-pushing a branch is normally off-limits here. gh-pages is the exception: +# it holds no reviewed history, nobody branches from it, and the tree is +# reproducible from CI. The rewrite keeps the tree byte-identical — it only +# discards ancestry. +set -euo pipefail + +BRANCH="${BRANCH:-gh-pages}" +WORK="${WORK:-${RUNNER_TEMP:-/tmp}/gh-pages-compact}" +# Below this, the rewrite costs more (a full force-push of the tree) than the +# history it reclaims. +MIN_COMMITS="${MIN_COMMITS:-5}" +# REPO_URL is overridable so the logic can be exercised against a local bare +# repository; CI leaves it unset and gets the token URL. +if [ -z "${REPO_URL:-}" ]; then + REPO="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}" + TOKEN="${GITHUB_TOKEN:?GITHUB_TOKEN is required}" + REPO_URL="https://x-access-token:${TOKEN}@github.com/${REPO}.git" +fi + +rm -rf "$WORK" +git clone --branch "$BRANCH" "$REPO_URL" "$WORK" \ + || { echo "No ${BRANCH} branch yet — nothing to compact."; exit 0; } + +cd "$WORK" +git config user.name "github-actions[bot]" +git config user.email "github-actions[bot]@users.noreply.github.com" + +# The tip we cloned. Everything below is asserted against exactly this commit, +# and the push is leased to it, so a deploy or cleanup that lands while we work +# makes the push fail instead of being erased by it. +CLONED_TIP="$(git rev-parse HEAD)" + +COMMITS="$(git rev-list --count HEAD)" +echo "${BRANCH} has ${COMMITS} commit(s) at ${CLONED_TIP}." +if [ "$COMMITS" -lt "$MIN_COMMITS" ]; then + echo "Fewer than ${MIN_COMMITS} — leaving history alone." + exit 0 +fi + +BEFORE="$(git count-objects -vH | awk '/size-pack/ {print $2 $3}')" + +git checkout --orphan compacted +git add -A +git commit -m "Compact ${BRANCH} history ($(date -u +%Y-%m-%d), was ${COMMITS} commits)" + +# The tree must be identical to the commit we cloned; a mismatch means the +# orphan commit lost content and must not be pushed. +if [ "$(git rev-parse compacted^{tree})" != "$(git rev-parse "${CLONED_TIP}^{tree}")" ]; then + echo "::error::Compacted tree differs from ${CLONED_TIP} — refusing to force-push." + exit 1 +fi + +# --force-with-lease pinned to the cloned tip, NOT a bare --force. The deploy and +# cleanup jobs run in different concurrency groups, so one of them can land a new +# preview between our clone and this push; a bare force would delete it, and the +# tree check above would not notice because it only ever saw the stale tip. +# Failing here is correct — the next weekly run compacts the newer history. +if ! git push --force-with-lease="${BRANCH}:${CLONED_TIP}" origin "compacted:${BRANCH}"; then + echo "::warning::${BRANCH} moved since the clone (a deploy or cleanup landed) — skipping compaction this run." + exit 0 +fi +echo "Compacted ${COMMITS} commits into 1 (pack size before rewrite: ${BEFORE})." diff --git a/.github/scripts/pr-preview/generate-pr-comment.mjs b/.github/scripts/pr-preview/generate-pr-comment.mjs new file mode 100644 index 0000000..8f2343e --- /dev/null +++ b/.github/scripts/pr-preview/generate-pr-comment.mjs @@ -0,0 +1,233 @@ +#!/usr/bin/env node +// Render the "PR Analysis Report" comment body from the artifacts CI produced. +// Writes markdown to stdout; the calling workflow posts or updates it. +// +// Usage: +// node generate-pr-comment.mjs --meta pr-meta.json --analysis analysis.json \ +// [--bundle-head bundle-head.json] [--bundle-base bundle-base.json] + +import { existsSync, readFileSync } from "node:fs"; + +const getArg = (name) => { + const i = process.argv.indexOf(`--${name}`); + return i === -1 ? undefined : process.argv[i + 1]; +}; + +const readJson = (path) => + path && existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : null; + +const analysis = readJson(getArg("analysis")); +const meta = readJson(getArg("meta")); +const bundleHead = readJson(getArg("bundle-head")); +const bundleBase = readJson(getArg("bundle-base")); + +if (!meta) { + console.error("Missing pr-meta.json"); + process.exit(1); +} +if (!analysis) { + console.error("Missing analysis.json — nothing to report"); + process.exit(1); +} + +const kb = (bytes) => `${(bytes / 1024).toFixed(1)} kB`; +const signed = (bytes) => + `${bytes > 0 ? "+" : bytes < 0 ? "−" : "±"}${kb(Math.abs(bytes))}`; + +const link = (text, href) => + `${text}`; + +// Story titles come from the PR's own tree, so they are escaped before they +// reach the Markdown. +const escapeText = (value) => + String(value ?? "") + .replace(/\s+/g, " ") + .replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]) + .replace(/([\\`*_[\]|~])/g, "\\$1") + .trim() + .slice(0, 120); + +const out = []; +out.push("## PR Analysis Report"); +out.push(""); + +// ── Storybook preview ──────────────────────────────────────────────────────── +out.push("### 📚 Storybook Preview"); +out.push(""); +if (meta.storybookUrl) { + out.push(`**${link("View Storybook for this PR", meta.storybookUrl)}**`); + out.push(""); + out.push("_GitHub Pages may take up to a minute to hydrate after the deploy._"); +} else { + out.push("_No preview was deployed for this run._"); +} +out.push(""); + +// ── Changed components ─────────────────────────────────────────────────────── +out.push("### 🧩 Changed Components"); +out.push(""); +// The table is the actionable part, so it holds only the components a reviewer +// can actually open. Everything else goes below it, out of the way — a wall of +// "no story, nothing to open" rows is what makes a report like this get ignored. +const previewable = analysis.components.filter( + (c) => c.links.length > 0 && meta.storybookUrl, +); +const unpreviewable = analysis.components.filter( + (c) => c.links.length === 0 || !meta.storybookUrl, +); +const MAX_ROWS = 30; + +// `components/Drawer` would read the same as `forks/Drawer`; name the kind only +// when a PR touches both. +const nameCounts = new Map(); +for (const c of analysis.components) { + nameCounts.set(c.name, (nameCounts.get(c.name) ?? 0) + 1); +} +const label = (c) => (nameCounts.get(c.name) === 1 ? c.name : `${c.kind}/${c.name}`); + +if (analysis.components.length === 0) { + out.push("_No files changed under `src/components/` or `src/forks/`._"); +} else if (previewable.length === 0) { + out.push( + `_${analysis.components.length} component(s) changed, none of which has a ` + + "`*.stories.tsx` — nothing to open in the preview._", + ); +} else { + out.push("| Component | Changed | Stories | Open |"); + out.push("|---|---|---|---|"); + for (const c of previewable.slice(0, MAX_ROWS)) { + const changed = [ + c.isNew ? "new" : c.sourceChanged && "source", + c.storiesChanged && "stories", + ] + .filter(Boolean) + .join(" + "); + // One link per stories file in the directory, named by its story title + // when there is more than one. + const open = c.links + .map((l) => + link( + c.links.length === 1 ? "Storybook ↗" : `${escapeText(l.title)} ↗`, + `${meta.storybookUrl}${l.deepLink}`, + ), + ) + .join("
"); + out.push(`| \`${label(c)}\` | ${changed} | ${c.storyCount} | ${open} |`); + } + if (previewable.length > MAX_ROWS) { + out.push(`| _…and ${previewable.length - MAX_ROWS} more_ | | | |`); + } +} +out.push(""); + +if (analysis.summary.newWithoutStories > 0) { + const names = analysis.components + .filter((c) => c.isNew && !c.hasStories) + .map((c) => `\`${label(c)}\``) + .join(", "); + out.push( + `> ⚠️ New component(s) without a \`*.stories.tsx\`: ${names}. They cannot appear ` + + "in the preview, and a story is the cheapest way to make a new component " + + "reviewable.", + ); + out.push(""); +} + +if (unpreviewable.length > 0) { + out.push( + `
${unpreviewable.length} changed component(s) with no story ` + + "to open", + ); + out.push(""); + for (const c of unpreviewable) out.push(`- \`${label(c)}\` — \`${c.dir}\``); + out.push(""); + out.push("
"); + out.push(""); +} + +if (analysis.otherSourceFiles.length > 0) { + const shown = analysis.otherSourceFiles.slice(0, 10); + out.push( + `
${analysis.otherSourceFiles.length} other changed file(s) in ` + + "src/", + ); + out.push(""); + for (const f of shown) out.push(`- \`${f}\``); + if (analysis.otherSourceFiles.length > shown.length) { + out.push(`- …and ${analysis.otherSourceFiles.length - shown.length} more`); + } + out.push(""); + out.push("
"); + out.push(""); +} + +// ── Bundle size ────────────────────────────────────────────────────────────── +out.push("### 📦 Bundle Size"); +out.push(""); +if (!bundleHead) { + out.push("_Bundle was not measured for this run._"); +} else { + const totalHead = bundleHead.entries.reduce((n, e) => n + e.gzipBytes, 0); + if (bundleBase) { + const baseByFile = new Map(bundleBase.entries.map((e) => [e.file, e])); + const headFiles = new Set(bundleHead.entries.map((e) => e.file)); + // Only what moved. With every module its own file, an unchanged group is + // byte-identical and its row says nothing. + const changed = [ + ...bundleHead.entries + .filter((e) => baseByFile.get(e.file)?.hash !== e.hash) + .map((e) => ({ file: e.file, head: e, prev: baseByFile.get(e.file) })), + ...bundleBase.entries + .filter((e) => !headFiles.has(e.file)) + .map((e) => ({ file: e.file, head: null, prev: e })), + ]; + const totalBase = bundleBase.entries.reduce((n, e) => n + e.gzipBytes, 0); + if (changed.length === 0) { + out.push("_No change to the built JS or CSS._"); + } else { + out.push("| Group | Gzip | Δ Gzip vs base |"); + out.push("|---|---|---|"); + for (const { file, head, prev } of changed.slice(0, MAX_ROWS)) { + const delta = !prev + ? "🆕 new" + : !head + ? "🗑️ removed" + : signed(head.gzipBytes - prev.gzipBytes); + out.push(`| \`${file}\` | ${head ? kb(head.gzipBytes) : "—"} | ${delta} |`); + } + if (changed.length > MAX_ROWS) { + out.push(`| _…and ${changed.length - MAX_ROWS} more_ | | |`); + } + } + out.push(""); + out.push( + `**Total gzip (JS + CSS):** ${kb(totalHead)} (${signed(totalHead - totalBase)} vs \`${analysis.base}\`)`, + ); + } else { + out.push(`**Total gzip (JS + CSS):** ${kb(totalHead)}`); + out.push(""); + out.push( + "_No base measurement — the merge-base build was skipped " + + "(a build input outside `src/` changed, or the base build failed)._", + ); + } +} +out.push(""); + +if (analysis.diffMode === "two-dot") { + out.push( + "> ℹ️ The changed-file list came from a two-dot diff (no merge base was " + + "reachable in the shallow clone), so it may over- or under-report.", + ); + out.push(""); +} + +out.push("---"); +out.push(""); +out.push( + `Generated by the PR Preview workflow for \`${meta.shortHash}\` · ${ + meta.storybookUrl ? `${link("Storybook", meta.storybookUrl)} · ` : "" + }${link("CI run", meta.runUrl)}`, +); + +console.log(out.join("\n")); diff --git a/.github/scripts/pr-preview/measure-dist.mjs b/.github/scripts/pr-preview/measure-dist.mjs new file mode 100644 index 0000000..d01dac4 --- /dev/null +++ b/.github/scripts/pr-preview/measure-dist.mjs @@ -0,0 +1,72 @@ +#!/usr/bin/env node +// Measure the built package: raw and gzip bytes of the JS and CSS in `dist`, +// grouped by what a consumer imports. The build preserves modules, so one file +// is not one entry; a component's group is its whole directory +// (`components/StatCard`, `forks/Drawer`), and everything else is grouped by +// its top-level directory. Run once for the PR head and once for the merge +// base; the PR comment renders the delta between the two reports. +// +// Usage: node measure-dist.mjs --dist --output + +import { createHash } from "node:crypto"; +import { readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join, relative, sep } from "node:path"; +import { gzipSync } from "node:zlib"; + +const getArg = (name) => { + const i = process.argv.indexOf(`--${name}`); + return i === -1 ? undefined : process.argv[i + 1]; +}; + +const distDir = getArg("dist"); +const output = getArg("output"); +if (!distDir || !output) { + console.error("usage: measure-dist.mjs --dist --output "); + process.exit(1); +} + +function* walk(dir) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const path = join(dir, entry.name); + if (entry.isDirectory()) yield* walk(path); + else yield path; + } +} + +/** `components/StatCard/StatCard.js` -> `components/StatCard`; `index.js` -> `index.js`. */ +const groupOf = (file) => { + const parts = file.split("/"); + if ((parts[0] === "components" || parts[0] === "forks") && parts.length > 2) { + return `${parts[0]}/${parts[1]}`; + } + return parts.length > 1 ? parts[0] : file; +}; + +// Only what ships to a browser. Source maps and declarations do not, and the +// locale catalogs are data the consumer loads one at a time. +const groups = new Map(); +for (const path of walk(distDir)) { + const file = relative(distDir, path).split(sep).join("/"); + if (!/\.(js|css)$/.test(file) || /(^|\/)(locales|ui-common-locales)\//.test(file)) { + continue; + } + const buf = readFileSync(path); + const group = groupOf(file); + const current = groups.get(group) ?? { + file: group, + bytes: 0, + gzipBytes: 0, + hash: createHash("sha256"), + }; + current.bytes += buf.length; + current.gzipBytes += gzipSync(buf, { level: 9 }).length; + current.hash.update(file).update(buf); + groups.set(group, current); +} + +const entries = [...groups.values()] + .map((g) => ({ ...g, hash: g.hash.digest("hex").slice(0, 12) })) + .sort((a, b) => b.bytes - a.bytes); + +writeFileSync(output, `${JSON.stringify({ entries }, null, 2)}\n`); +console.log(`Measured ${entries.length} dist group(s) -> ${output}`); diff --git a/.github/scripts/pr-preview/pr-preview.test.ts b/.github/scripts/pr-preview/pr-preview.test.ts new file mode 100644 index 0000000..79031b8 --- /dev/null +++ b/.github/scripts/pr-preview/pr-preview.test.ts @@ -0,0 +1,252 @@ +import { execFileSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const here = dirname(fileURLToPath(import.meta.url)); +const ANALYZE = join(here, "analyze-pr.mjs"); +const COMMENT = join(here, "generate-pr-comment.mjs"); + +const META = { + prNumber: "61", + shortHash: "a1b2c3d", + storybookUrl: "https://lablup.github.io/ui-common/pr/61/storybook/", + runUrl: "https://github.com/lablup/ui-common/actions/runs/1", +}; + +function tempDir() { + return mkdtempSync(join(tmpdir(), "pr-preview-")); +} + +function writeJson(dir: string, name: string, value: unknown) { + const path = join(dir, name); + writeFileSync(path, JSON.stringify(value)); + return path; +} + +function renderComment(args: { + analysis: unknown; + bundleHead?: unknown; + bundleBase?: unknown; +}) { + const dir = tempDir(); + const argv = [ + COMMENT, + "--meta", + writeJson(dir, "meta.json", META), + "--analysis", + writeJson(dir, "analysis.json", args.analysis), + ]; + if (args.bundleHead) + argv.push("--bundle-head", writeJson(dir, "head.json", args.bundleHead)); + if (args.bundleBase) + argv.push("--bundle-base", writeJson(dir, "base.json", args.bundleBase)); + return execFileSync("node", argv, { encoding: "utf8" }); +} + +describe("analyze-pr.mjs", () => { + function repoWithPr() { + const repo = tempDir(); + const git = (...args: string[]) => + execFileSync("git", args, { cwd: repo, encoding: "utf8" }); + const write = (file: string, contents = "x") => { + mkdirSync(dirname(join(repo, file)), { recursive: true }); + writeFileSync(join(repo, file), contents); + }; + + git("init", "-q", "-b", "main"); + git("config", "user.email", "test@example.com"); + git("config", "user.name", "test"); + write("src/components/StatCard/index.ts"); + write("src/components/StatCard/StatCard.tsx"); + write("src/components/Modal/Modal.tsx"); + write("src/hooks/index.ts"); + git("add", "-A"); + git("commit", "-q", "-m", "base"); + git("checkout", "-q", "-b", "pr"); + + write("src/components/StatCard/StatCard.css", "changed"); + write("src/components/StatCard/StatCard.stories.tsx", "new"); + write("src/components/Modal/Modal.test.tsx", "test only"); + write("src/components/Fresh/index.ts"); + write("src/components/Fresh/Fresh.tsx"); + write("src/hooks/index.ts", "changed"); + git("add", "-A"); + git("commit", "-q", "-m", "pr"); + return repo; + } + + it("groups changes by component directory and links the built stories", () => { + const repo = repoWithPr(); + const index = writeJson(repo, "index.json", { + entries: { + "components-statcard--documentation": { + id: "components-statcard--documentation", + title: "Components/StatCard", + type: "docs", + importPath: "./src/components/StatCard/StatCard.stories.tsx", + }, + "components-statcard--default": { + id: "components-statcard--default", + title: "Components/StatCard", + type: "story", + importPath: "./src/components/StatCard/StatCard.stories.tsx", + }, + }, + }); + const output = join(repo, "analysis.json"); + execFileSync( + "node", + [ + ANALYZE, + "--base", + "main", + "--head", + "pr", + "--storybook-index", + index, + "--output", + output, + ], + { cwd: repo }, + ); + const analysis = JSON.parse(readFileSync(output, "utf8")); + + expect(analysis.diffMode).toBe("three-dot"); + // A test-only change is not a component change. + expect(analysis.components.map((c: { name: string }) => c.name)).toEqual([ + "Fresh", + "StatCard", + ]); + + const statCard = analysis.components.find( + (c: { name: string }) => c.name === "StatCard", + ); + expect(statCard).toMatchObject({ + kind: "components", + sourceChanged: true, + storiesChanged: true, + isNew: false, + storyCount: 1, + links: [ + { + title: "Components/StatCard", + deepLink: "?path=/docs/components-statcard--documentation", + }, + ], + }); + + const fresh = analysis.components.find((c: { name: string }) => c.name === "Fresh"); + expect(fresh).toMatchObject({ isNew: true, hasStories: false }); + expect(analysis.summary.newWithoutStories).toBe(1); + expect(analysis.otherSourceFiles).toEqual(["src/hooks/index.ts"]); + }); +}); + +describe("generate-pr-comment.mjs", () => { + const ANALYSIS = { + base: "origin/main", + diffMode: "three-dot", + components: [ + { + name: "Skeleton", + kind: "components", + dir: "src/components/Skeleton/", + links: [ + { + title: "Components/SkeletonCard", + storyCount: 2, + deepLink: "?path=/docs/a", + }, + { + title: "Components/SkeletonText", + storyCount: 1, + deepLink: "?path=/docs/b", + }, + ], + hasStories: true, + storyCount: 3, + sourceChanged: true, + storiesChanged: false, + isNew: false, + }, + { + name: "Fresh", + kind: "components", + dir: "src/components/Fresh/", + links: [], + hasStories: false, + storyCount: 0, + sourceChanged: true, + storiesChanged: false, + isNew: true, + }, + ], + otherSourceFiles: ["src/index.ts"], + summary: { componentsChanged: 2, newWithoutStories: 1, totalStories: 3 }, + }; + + it("links the preview and every changed component's stories", () => { + const body = renderComment({ analysis: ANALYSIS }); + + expect(body).toContain("## PR Analysis Report"); + expect(body).toContain(`href="${META.storybookUrl}"`); + expect(body).toContain(`href="${META.storybookUrl}?path=/docs/a"`); + expect(body).toContain(`href="${META.storybookUrl}?path=/docs/b"`); + // Several stories files in one directory are told apart by title. + expect(body).toContain("Components/SkeletonCard ↗"); + expect(body).toContain("| `Skeleton` | source | 3 |"); + expect(body).toContain("New component(s) without a `*.stories.tsx`: `Fresh`"); + expect(body).toContain("1 changed component(s) with no story"); + expect(body).toContain("`src/index.ts`"); + expect(body).toContain("_Bundle was not measured for this run._"); + }); + + it("reports only the bundle groups that changed", () => { + const body = renderComment({ + analysis: ANALYSIS, + bundleHead: { + entries: [ + { file: "components/StatCard", bytes: 4096, gzipBytes: 2048, hash: "b" }, + { file: "components/Modal", bytes: 4096, gzipBytes: 1024, hash: "same" }, + { file: "components/Fresh", bytes: 1024, gzipBytes: 512, hash: "n" }, + ], + }, + bundleBase: { + entries: [ + { file: "components/StatCard", bytes: 2048, gzipBytes: 1024, hash: "a" }, + { file: "components/Modal", bytes: 4096, gzipBytes: 1024, hash: "same" }, + { file: "components/Gone", bytes: 1024, gzipBytes: 512, hash: "g" }, + ], + }, + }); + + expect(body).toContain("| `components/StatCard` | 2.0 kB | +1.0 kB |"); + expect(body).toContain("| `components/Fresh` | 0.5 kB | 🆕 new |"); + expect(body).toContain("| `components/Gone` | — | 🗑️ removed |"); + expect(body).not.toContain("`components/Modal`"); + expect(body).toContain( + "**Total gzip (JS + CSS):** 3.5 kB (+1.0 kB vs `origin/main`)", + ); + }); + + it("escapes story titles from the PR's tree", () => { + const body = renderComment({ + analysis: { + ...ANALYSIS, + components: [ + { + ...ANALYSIS.components[0], + links: [ + { title: "", storyCount: 1, deepLink: "?path=/docs/a" }, + { title: "b", storyCount: 1, deepLink: "?path=/docs/b" }, + ], + }, + ], + }, + }); + expect(body).not.toContain("/` directories are +// currently on disk. Both the deploy and the cleanup job call this after they +// change the tree, so the index never claims a preview that is gone. +// +// Usage: node write-index.mjs --root + +import { existsSync, readdirSync, statSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +const getArg = (name) => { + const i = process.argv.indexOf(`--${name}`); + return i === -1 ? undefined : process.argv[i + 1]; +}; + +const root = getArg("root"); +if (!root) { + console.error("usage: write-index.mjs --root "); + process.exit(1); +} + +const prRoot = join(root, "pr"); +const isDir = (path) => existsSync(path) && statSync(path).isDirectory(); + +const previews = isDir(prRoot) + ? readdirSync(prRoot) + .filter((name) => /^\d+$/.test(name)) + .filter((name) => isDir(join(prRoot, name, "storybook"))) + .map(Number) + .sort((a, b) => b - a) + : []; + +const items = previews + .map( + (n) => + `
  • PR #${n} — Storybook ` + + `source ↗
  • `, + ) + .join("\n"); + +const html = ` + + + + + + ui-common — PR previews + + + +

    ui-common — PR previews

    +

    + One Storybook build per open pull request that changed it. Previews are + removed when their PR is merged or closed. +

    +${previews.length > 0 ? `
      \n${items}\n
    ` : "

    No live previews right now.

    "} + + +`; + +writeFileSync(join(root, "index.html"), html); +console.log(`Wrote index.html listing ${previews.length} preview(s).`); diff --git a/.github/workflows/pr-preview-cleanup.yml b/.github/workflows/pr-preview-cleanup.yml new file mode 100644 index 0000000..17a3471 --- /dev/null +++ b/.github/workflows/pr-preview-cleanup.yml @@ -0,0 +1,160 @@ +# Keep gh-pages bounded. +# +# Two independent chores, both cheap and both necessary because GitHub Pages has +# a hard 1 GB published-site ceiling: +# +# cleanup — delete `pr//` for every PR that is no longer open. This is the +# only thing keeping merged PRs' previews from staying forever. +# compact — squash gh-pages history to a single orphan commit once a week, so +# the branch's *history* of those trees stops growing the repository. +# +# The cleanup reconciles against the live open-PR list on every run, so it needs +# no `closed` event to be correct — firing on any preview build (plus the daily +# cron) is enough to garbage-collect. +# +# SAFETY: never checks out or runs PR-controlled code. It only edits gh-pages. + +name: PR Preview Cleanup + +on: + schedule: + - cron: "0 6 * * *" # daily — reclaim closed PRs' previews + - cron: "17 9 * * 1" # Mondays — compact gh-pages history + workflow_run: + workflows: ["PR Preview"] + types: [completed] + workflow_dispatch: + inputs: + dry_run: + description: "List what would be deleted without deleting it" + type: boolean + default: false + compact: + description: "Also squash gh-pages history" + type: boolean + default: false + +permissions: {} + +concurrency: + # One cleanup at a time. A burst of preview completions collapses into a + # single pending run, which is fine: every run reconciles against the live + # open-PR list. The deploy has no group of its own; races against it are + # handled by the push retry instead. + group: pr-preview-cleanup + cancel-in-progress: false + +jobs: + cleanup: + # Skip on the weekly compaction cron — that fires the `compact` job instead. + if: >- + github.event_name != 'schedule' || github.event.schedule == '0 6 * * *' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + steps: + - name: Check out the maintenance scripts + uses: actions/checkout@v7 + with: + sparse-checkout: .github/scripts/pr-preview + + - name: Remove previews for closed PRs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DRY_RUN: ${{ github.event.inputs.dry_run }} + run: | + set -euo pipefail + REPO_URL="https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" + WORK_DIR="$PWD" + PAGES_DIR="${RUNNER_TEMP}/gh-pages" + MAX_ATTEMPTS=5 + + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + # One list for the whole run: an open PR that closes mid-run keeps its + # preview until tomorrow, which is the harmless direction to err in. + OPEN_PRS="$(gh pr list --repo "${{ github.repository }}" --state open --limit 500 --json number --jq '.[].number' | sort -u)" + echo "Open PRs: $(echo "$OPEN_PRS" | wc -w)" + + for attempt in $(seq 1 $MAX_ATTEMPTS); do + echo "::group::Attempt ${attempt} of ${MAX_ATTEMPTS}" + cd "$WORK_DIR" + rm -rf "$PAGES_DIR" + + if ! git clone --depth=1 --single-branch --branch gh-pages "$REPO_URL" "$PAGES_DIR" 2>/dev/null; then + echo "No gh-pages branch — nothing to clean." + echo "::endgroup::" + exit 0 + fi + + REMOVED=0 + if [ -d "${PAGES_DIR}/pr" ]; then + for dir in "${PAGES_DIR}"/pr/*/; do + [ -d "$dir" ] || continue + n="$(basename "$dir")" + case "$n" in ''|*[!0-9]*) echo "Skipping non-numeric entry: $n"; continue ;; esac + if echo "$OPEN_PRS" | grep -qx "$n"; then continue; fi + SIZE="$(du -sh "$dir" | cut -f1)" + if [ "${DRY_RUN:-false}" = "true" ]; then + echo "DRY RUN — would delete pr/${n} (${SIZE})" + else + echo "Deleting pr/${n} (${SIZE}) — PR is not open." + rm -rf "$dir" + REMOVED=$((REMOVED + 1)) + fi + done + fi + + if [ "${DRY_RUN:-false}" = "true" ]; then + echo "Dry run complete; nothing was pushed." + echo "::endgroup::" + exit 0 + fi + + node .github/scripts/pr-preview/write-index.mjs --root "$PAGES_DIR" + + cd "$PAGES_DIR" + git add -A + git commit -m "Clean up ${REMOVED} stale PR preview(s)" \ + || { echo "Nothing to clean up."; echo "::endgroup::"; exit 0; } + + if git push origin gh-pages; then + echo "Removed ${REMOVED} preview(s) on attempt ${attempt}." + echo "::endgroup::" + exit 0 + fi + + echo "Push failed (likely a concurrent deploy); retrying in $((attempt * 2))s." + echo "::endgroup::" + sleep $((attempt * 2)) + done + + echo "::error::Failed to push the cleanup after ${MAX_ATTEMPTS} attempts." + exit 1 + + compact: + # Sequenced after `cleanup`, never beside it. On a manual dispatch with + # compact=true the cleanup condition is also true, and two jobs cloning the + # same tip and pushing would race — the compaction would rewrite history + # that the cleanup had just changed. `always()` keeps this running on the + # weekly cron, where `cleanup` is skipped rather than run. + needs: [cleanup] + if: >- + always() && + (needs.cleanup.result == 'success' || needs.cleanup.result == 'skipped') && + (github.event.schedule == '17 9 * * 1' || github.event.inputs.compact == 'true') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v7 + with: + sparse-checkout: .github/scripts/pr-preview + + - name: Compact gh-pages history + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bash .github/scripts/pr-preview/compact-gh-pages-history.sh diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml new file mode 100644 index 0000000..d9ec3ff --- /dev/null +++ b/.github/workflows/pr-preview.yml @@ -0,0 +1,340 @@ +# Build the per-PR Storybook preview, publish it to gh-pages, and post the PR +# Analysis Report: the preview link, a deep link for each changed component's +# stories, and the bundle-size delta. Modeled on backend.ai-webui's PR Preview. +# +# The event-level `paths` filter is a cheap first cut; the `changes` job +# re-asks the question against the PR's own diff and skips the build when the +# answer is no. +# +# WHY THE DEPLOY LIVES HERE. A separate `workflow_run` deploy would run main's +# code with main's token, but a push by Copilot coding agent holds every run it +# causes for approval, and the `workflow_run` run that follows is held again — +# invisibly, because such runs are attached to main and never listed on the PR. +# Approval is per run, so the only way one approval can cover the deploy is for +# the deploy to be part of the approved run. +# +# SECURITY, the trade-off that buys it. `publish` and `report` are the only jobs +# with a write token, and they run from the PR's own copy of this file and of +# `.github/scripts/pr-preview`. Approving a Copilot run is therefore a review of +# the whole diff, not just a click to start the build. Keep both jobs free of +# anything that executes PR-authored source: they download the artifacts the +# build job produced and copy them as static files. Do not add a build step, a +# `pnpm install`, or any `run:` that invokes package scripts to either. +# +# Fork PRs get a read-only token no matter what this file declares, so +# `publish` and `report` skip them and a fork PR gets no preview. +# +# Needs GitHub Pages enabled for the `gh-pages` branch (Settings → Pages). +# Until then the deploy still pushes the branch and the report still posts; the +# links resolve once Pages is switched on. + +name: PR Preview + +on: + pull_request: + paths: + - src/** + - .storybook/** + - package.json + - pnpm-lock.yaml + - .github/workflows/pr-preview.yml + - .github/scripts/pr-preview/** + +permissions: + contents: read + +concurrency: + # Keyed by PR number, not branch name: branch names are unique only within a + # repository, so two fork PRs both using e.g. `patch-1` would share a group + # and cancel each other's runs. + group: pr-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + changes: + runs-on: ubuntu-latest + outputs: + storybook: ${{ steps.detect.outputs.storybook }} + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 50 + + - name: Fetch base branch + run: git fetch --depth=50 origin "+refs/heads/${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }}" + + - name: Decide whether this PR needs a preview + id: detect + env: + BASE: origin/${{ github.base_ref }} + run: | + set -euo pipefail + # A merge base is what makes the three-dot diff report only what this + # branch changed; deepen once before falling back to a two-dot diff. + if ! git merge-base "$BASE" HEAD >/dev/null 2>&1; then + git fetch --deepen=200 origin "+refs/heads/${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }}" || true + fi + DIFF="$(git diff --name-only "${BASE}...HEAD" 2>/dev/null || git diff --name-only "${BASE}..HEAD")" + + if grep -qE '^(src/|\.storybook/|package\.json$|pnpm-lock\.yaml$|\.github/workflows/pr-preview\.yml$|\.github/scripts/pr-preview/)' <<<"$DIFF"; then + echo "storybook=true" >> "$GITHUB_OUTPUT" + else + echo "storybook=false" >> "$GITHUB_OUTPUT" + fi + + build: + needs: changes + if: needs.changes.outputs.storybook == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + # Enough history for a three-dot diff against the base in the common + # case; analyze-pr.mjs deepens on its own when the base has moved past + # this window. + fetch-depth: 50 + + - name: Fetch base branch + run: git fetch --depth=50 origin "+refs/heads/${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }}" + + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v7 + with: + node-version: 26 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Build Storybook + run: pnpm run build-storybook + + - name: Compute the artifact suffix + id: urls + run: echo "short_hash=$(echo "${{ github.event.pull_request.head.sha }}" | cut -c1-7)" >> "$GITHUB_OUTPUT" + + - name: Analyze changed components + run: | + node .github/scripts/pr-preview/analyze-pr.mjs \ + --base "origin/${{ github.base_ref }}" \ + --head HEAD \ + --storybook-index storybook-static/index.json \ + --output analysis.json + + - name: Build and measure the package bundle (head) + run: | + pnpm run build + node .github/scripts/pr-preview/measure-dist.mjs \ + --dist dist \ + --output bundle-head.json + + # Rebuild the package from the base's sources to get a comparable number. + # Only `src/` is swapped, so node_modules stays put — which is also why + # the delta is abandoned outright when the PR touches anything else that + # feeds the build. Swapping only `src/` under a changed vite config or a + # changed dependency set would build the "base" with the HEAD's bundling, + # and report a delta (often a reassuring zero) that describes no real + # comparison. Runs LAST, and best-effort: a failure costs the delta + # column and nothing else. + - name: Build and measure the package bundle (base) + id: bundle-base + continue-on-error: true + run: | + BUILD_INPUTS=( + pnpm-lock.yaml + pnpm-workspace.yaml + package.json + vite.config.ts + tsconfig.json + scripts/layer-order.mjs + ) + if ! git diff --quiet "origin/${{ github.base_ref }}...HEAD" -- "${BUILD_INPUTS[@]}"; then + echo "A build input outside src/ changed — skipping the base build (not comparable)." + git diff --name-only "origin/${{ github.base_ref }}...HEAD" -- "${BUILD_INPUTS[@]}" + exit 0 + fi + rm -rf src dist + git checkout "origin/${{ github.base_ref }}" -- src + pnpm run build + node .github/scripts/pr-preview/measure-dist.mjs \ + --dist dist \ + --output bundle-base.json + + - name: Upload Storybook artifact + uses: actions/upload-artifact@v7 + with: + name: pr-preview-storybook-${{ steps.urls.outputs.short_hash }} + path: storybook-static/ + if-no-files-found: error + retention-days: 7 + + - name: Upload analysis artifact + uses: actions/upload-artifact@v7 + with: + name: pr-preview-analysis + path: | + analysis.json + bundle-head.json + bundle-base.json + if-no-files-found: error + retention-days: 7 + + # ── the write half ───────────────────────────────────────────────────────── + # `publish` and `report` hold the only write tokens in this workflow, one + # scope each, and they are independent so a gh-pages failure still leaves a + # report and vice versa. Read the SECURITY note in the header before adding a + # step to either: neither may execute PR-authored source. + + publish: + needs: build + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + contents: write + # No shared concurrency group here: GitHub keeps one pending job per group + # and cancels the older one, so a burst of PRs would lose previews. The + # push retry below is what serializes gh-pages. + steps: + - name: Check out the maintenance scripts + uses: actions/checkout@v7 + with: + sparse-checkout: .github/scripts/pr-preview + + - name: Compute the artifact suffix + id: urls + run: echo "short_hash=$(echo "${{ github.event.pull_request.head.sha }}" | cut -c1-7)" >> "$GITHUB_OUTPUT" + + - name: Download Storybook artifact + uses: actions/download-artifact@v8 + with: + name: pr-preview-storybook-${{ steps.urls.outputs.short_hash }} + path: storybook-dist + + - name: Deploy to gh-pages + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + # A manual push with retry-on-conflict rather than a Pages action, so + # a concurrent cleanup or sibling preview just retries instead of + # failing the run. + set -euo pipefail + REPO_URL="https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" + WORK_DIR="$PWD" + PAGES_DIR="${RUNNER_TEMP}/gh-pages" + MAX_ATTEMPTS=8 + + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + for attempt in $(seq 1 $MAX_ATTEMPTS); do + echo "::group::Attempt ${attempt} of ${MAX_ATTEMPTS}" + cd "$WORK_DIR" + rm -rf "$PAGES_DIR" + + if git clone --depth=1 --single-branch --branch gh-pages "$REPO_URL" "$PAGES_DIR" 2>/dev/null; then + : + else + # First ever deploy: start the branch with no history to inherit. + echo "No gh-pages branch — creating it." + git init "$PAGES_DIR" + git -C "$PAGES_DIR" checkout -b gh-pages + git -C "$PAGES_DIR" remote add origin "$REPO_URL" + fi + + rm -rf "${PAGES_DIR}/pr/${PR_NUMBER}" + mkdir -p "${PAGES_DIR}/pr/${PR_NUMBER}/storybook" + cp -r storybook-dist/. "${PAGES_DIR}/pr/${PR_NUMBER}/storybook/" + # Tell Pages not to run the content through Jekyll, which would drop + # Storybook's `_`-prefixed asset directories. + touch "${PAGES_DIR}/.nojekyll" + node .github/scripts/pr-preview/write-index.mjs --root "$PAGES_DIR" + + cd "$PAGES_DIR" + git add -A + git commit -m "Deploy PR #${PR_NUMBER} preview" \ + || { echo "Nothing to commit."; echo "::endgroup::"; exit 0; } + + if git push origin gh-pages; then + echo "Deployed on attempt ${attempt}." + echo "::endgroup::" + exit 0 + fi + + delay=$((attempt + RANDOM % 5)) + echo "Push failed (likely a concurrent update); retrying in ${delay}s." + echo "::endgroup::" + sleep "$delay" + done + + echo "::error::Failed to deploy the PR preview after ${MAX_ATTEMPTS} attempts." + exit 1 + + report: + needs: build + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + # No setup-node — the report scripts use nothing beyond `node:fs` / + # `node:child_process`, so the runner image's Node is enough. + - name: Check out the report scripts + uses: actions/checkout@v7 + with: + sparse-checkout: .github/scripts/pr-preview + + - name: Download analysis artifact + uses: actions/download-artifact@v8 + with: + name: pr-preview-analysis + path: pr-analysis + + - name: Post or update the PR Analysis Report + uses: actions/github-script@v9 + with: + script: | + const fs = require('node:fs'); + const { execFileSync } = require('node:child_process'); + + const { owner, repo } = context.repo; + const prNumber = context.payload.pull_request.number; + const headSha = context.payload.pull_request.head.sha; + + // Everything the report needs that is not a measurement is derived + // here, from the event payload, not from an artifact. + fs.writeFileSync('pr-meta.json', JSON.stringify({ + prNumber: String(prNumber), + shortHash: headSha.slice(0, 7), + storybookUrl: `https://${owner}.github.io/${repo}/pr/${prNumber}/storybook/`, + runUrl: `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`, + })); + + const args = [ + '.github/scripts/pr-preview/generate-pr-comment.mjs', + '--meta', 'pr-meta.json', + '--analysis', 'pr-analysis/analysis.json', + ]; + if (fs.existsSync('pr-analysis/bundle-head.json')) { + args.push('--bundle-head', 'pr-analysis/bundle-head.json'); + } + if (fs.existsSync('pr-analysis/bundle-base.json')) { + args.push('--bundle-base', 'pr-analysis/bundle-base.json'); + } + + const body = execFileSync('node', args, { encoding: 'utf8' }); + + const comments = await github.paginate(github.rest.issues.listComments, { + owner, repo, issue_number: prNumber, per_page: 100, + }); + const existing = comments.find( + (c) => c.user.type === 'Bot' && c.body.includes('PR Analysis Report'), + ); + + if (existing) { + await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); + core.info(`Updated the PR Analysis Report on #${prNumber}.`); + } else { + await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body }); + core.info(`Posted the PR Analysis Report on #${prNumber}.`); + } diff --git a/.gitignore b/.gitignore index 1e30d3e..c901c8d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ coverage/ fixture/node_modules/ fixture/dist/ fixture/pnpm-lock.yaml +storybook-static/ +*storybook.log diff --git a/.prettierignore b/.prettierignore index 85289f4..86d05b8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,4 @@ LICENSE src/theme/*/built/ packages/cli/test/upgrade/fixtures/ .claude/ +storybook-static/ diff --git a/.storybook/DocsContainer.tsx b/.storybook/DocsContainer.tsx new file mode 100644 index 0000000..83cdf4c --- /dev/null +++ b/.storybook/DocsContainer.tsx @@ -0,0 +1,35 @@ +import { useEffect, useState, type PropsWithChildren } from "react"; +import { + DocsContainer as BaseContainer, + type DocsContainerProps, +} from "@storybook/addon-docs/blocks"; +import { DARK_MODE_EVENT_NAME } from "@vueless/storybook-dark-mode"; +import { addons } from "storybook/preview-api"; + +import { storybookDarkTheme, storybookLightTheme } from "./storybookTheme"; + +const channel = addons.getChannel(); + +/** Docs pages follow the dark-mode toggle, like the stories do. */ +export function DocsContainer({ + context, + children, +}: PropsWithChildren) { + const [isDarkMode, setIsDarkMode] = useState(false); + + useEffect(() => { + channel.on(DARK_MODE_EVENT_NAME, setIsDarkMode); + return () => { + channel.off(DARK_MODE_EVENT_NAME, setIsDarkMode); + }; + }, []); + + return ( + + {children} + + ); +} diff --git a/.storybook/decorators.tsx b/.storybook/decorators.tsx new file mode 100644 index 0000000..532d4d6 --- /dev/null +++ b/.storybook/decorators.tsx @@ -0,0 +1,102 @@ +import { useEffect, useMemo, useState, type ReactNode } from "react"; +import type { Decorator } from "@storybook/react-vite"; +import { useDarkMode } from "@vueless/storybook-dark-mode"; + +import { InternationalizationProvider } from "../src/astryx/i18n"; +import type { Catalog } from "../src/astryx/i18n"; +import { Theme } from "../src/astryx/theme"; +import { neutralTheme } from "../src/astryx/theme/neutral/built"; +import { mergeMessages, uiCommonMessages } from "../src/i18n"; +import { lablupTheme } from "../src/theme/lablup/built/lablup"; + +export type ThemeStyle = "lablup" | "neutral"; + +/** + * The "Theme" toolbar: the Lablup brand, or Astryx's neutral baseline as the + * "does this component assume the brand?" check. Both are the pre-built + * themes a consumer mounts, paired with their `theme.css` in storybook.css. + */ +const themes = { lablup: lablupTheme, neutral: neutralTheme }; + +// Astryx's own catalogs, loaded when a locale is first picked. ui-common's +// catalog (`uiCommonMessages`) is small and always present. +const astryxCatalogs = import.meta.glob( + "../node_modules/@astryxdesign/core/locales/*.json", + { import: "default" }, +); + +function useAstryxCatalog(locale: string): Record { + const [loaded, setLoaded] = useState>({}); + + useEffect(() => { + const load = + astryxCatalogs[`../node_modules/@astryxdesign/core/locales/${locale}.json`]; + if (!load || loaded[locale]) return; + let cancelled = false; + void load().then((catalog) => { + if (!cancelled) setLoaded((current) => ({ ...current, [locale]: catalog })); + }); + return () => { + cancelled = true; + }; + }, [locale, loaded]); + + return loaded; +} + +function StoryProviders({ + locale, + themeStyle, + isDarkMode, + children, +}: { + locale: string; + themeStyle: ThemeStyle; + isDarkMode: boolean; + children: ReactNode; +}) { + const astryx = useAstryxCatalog(locale); + const messages = useMemo(() => mergeMessages(astryx, uiCommonMessages), [astryx]); + + return ( + + + {children} + + + ); +} + +/** Body background and text follow the mounted theme, as in an app. */ +function useBodyColors() { + useEffect(() => { + document.body.style.backgroundColor = "var(--color-background-body)"; + document.body.style.color = "var(--color-text-primary)"; + document.body.style.fontFamily = "var(--font-family-body)"; + }, []); +} + +function WithGlobalProviders({ + Story, + context, +}: { + Story: Parameters[0]; + context: Parameters[1]; +}) { + const isDarkMode = useDarkMode(); + useBodyColors(); + const { locale, themeStyle } = context.globals as { + locale: string; + themeStyle: ThemeStyle; + }; + + return ( + + + + ); +} + +export const withGlobalProviders: Decorator = (Story, context) => ( + +); diff --git a/.storybook/localeConfig.ts b/.storybook/localeConfig.ts new file mode 100644 index 0000000..83df983 --- /dev/null +++ b/.storybook/localeConfig.ts @@ -0,0 +1,28 @@ +/** + * The locale toolbar's list: English plus every locale ui-common ships a + * catalog for (`src/i18n/locales`). Values are Astryx locale names, the same + * ones a consumer passes to `InternationalizationProvider`. + */ +export const localeItems = [ + { value: "en", title: "English" }, + { value: "ko-KR", title: "한국어" }, + { value: "ja-JP", title: "日本語" }, + { value: "zh-CN", title: "简体中文" }, + { value: "zh-TW", title: "繁體中文" }, + { value: "de-DE", title: "Deutsch" }, + { value: "fr-FR", title: "Français" }, + { value: "es-ES", title: "Español" }, + { value: "pt-PT", title: "Português" }, + { value: "pt-BR", title: "Português (Brasil)" }, + { value: "it-IT", title: "Italiano" }, + { value: "ru-RU", title: "Русский" }, + { value: "pl-PL", title: "Polski" }, + { value: "el-GR", title: "Ελληνικά" }, + { value: "fi-FI", title: "Suomi" }, + { value: "tr-TR", title: "Türkçe" }, + { value: "th-TH", title: "ไทย" }, + { value: "vi-VN", title: "Tiếng Việt" }, + { value: "id-ID", title: "Indonesia" }, + { value: "ms-MY", title: "Melayu" }, + { value: "mn-MN", title: "Монгол" }, +]; diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000..afa7a0f --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,29 @@ +import type { StorybookConfig } from "@storybook/react-vite"; + +const config: StorybookConfig = { + stories: [ + { + titlePrefix: "Docs Overview", + directory: "../src", + files: "Introduction.mdx", + }, + "../src/**/*.stories.@(ts|tsx)", + ], + addons: ["@storybook/addon-docs", "@vueless/storybook-dark-mode"], + framework: { + name: "@storybook/react-vite", + options: { + // The repository's own vite.config.ts is the library build (lib mode, + // dts emission, the dist copies), none of which belongs in Storybook. + builder: { viteConfigPath: ".storybook/vite.config.ts" }, + }, + }, + docs: { + defaultName: "Documentation", + }, + typescript: { + reactDocgen: "react-docgen-typescript", + }, +}; + +export default config; diff --git a/.storybook/manager.ts b/.storybook/manager.ts new file mode 100644 index 0000000..706bc23 --- /dev/null +++ b/.storybook/manager.ts @@ -0,0 +1,10 @@ +import { addons } from "storybook/manager-api"; + +import { storybookLightTheme } from "./storybookTheme"; + +addons.setConfig({ + theme: storybookLightTheme, + sidebar: { + showRoots: true, + }, +}); diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 0000000..3f7037f --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,12 @@ + + + + + diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 0000000..cf736a2 --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,53 @@ +import type { Preview } from "@storybook/react-vite"; + +import "./storybook.css"; +import { DocsContainer } from "./DocsContainer"; +import { withGlobalProviders } from "./decorators"; +import { localeItems } from "./localeConfig"; +import { storybookDarkTheme, storybookLightTheme } from "./storybookTheme"; + +const preview: Preview = { + tags: ["autodocs"], + decorators: [withGlobalProviders], + parameters: { + darkMode: { + dark: storybookDarkTheme, + light: storybookLightTheme, + current: "light", + stylePreview: true, + }, + docs: { + container: DocsContainer, + }, + layout: "padded", + }, + globalTypes: { + locale: { + name: "Locale", + description: "Locale passed to Astryx's InternationalizationProvider", + toolbar: { + icon: "globe", + items: localeItems, + dynamicTitle: true, + }, + }, + themeStyle: { + name: "Theme", + description: "The Lablup brand theme vs. Astryx's neutral baseline", + toolbar: { + icon: "paintbrush", + items: [ + { value: "lablup", title: "Lablup" }, + { value: "neutral", title: "Astryx (neutral)" }, + ], + dynamicTitle: true, + }, + }, + }, + initialGlobals: { + locale: "en", + themeStyle: "lablup", + }, +}; + +export default preview; diff --git a/.storybook/storybook.css b/.storybook/storybook.css new file mode 100644 index 0000000..e59e913 --- /dev/null +++ b/.storybook/storybook.css @@ -0,0 +1,13 @@ +/* The stylesheets a consumer loads (README, "Set up"), in the same order. + Storybook never loads an app entry stylesheet, so this file is its only + one. The `@layer` statement comes first, as in every sheet ui-common ships; + keep it identical to scripts/layer-order.mjs. No unlayered global rules + here: they would outrank every layer. */ +@layer reset, theme, base, astryx-base, astryx-theme, ui-common, components, utilities; + +@import "../src/astryx/reset.css"; +@import "../src/astryx/astryx.css"; +@import "../src/astryx/theme/neutral/theme.css"; +@import "../src/theme/lablup/built/theme.css"; +@import "../src/ui-common.css"; +@import "../src/astryx/lab/lab.css"; diff --git a/.storybook/storybookTheme.ts b/.storybook/storybookTheme.ts new file mode 100644 index 0000000..01f7147 --- /dev/null +++ b/.storybook/storybookTheme.ts @@ -0,0 +1,55 @@ +import { create } from "storybook/theming/create"; + +// Storybook's own UI (sidebar, toolbar, docs pages), in the Lablup accent. +// The stories themselves are themed by the decorator, not by this file. +const common = { + brandTitle: "Lablup UI Common", + brandUrl: "https://github.com/lablup/ui-common", + brandTarget: "_blank" as const, + fontBase: "'Ubuntu Sans', 'Pretendard Variable', 'Helvetica Neue', Arial, sans-serif", + fontCode: "'Roboto Mono', 'SF Mono', 'Fira Code', Consolas, monospace", + appBorderRadius: 4, + inputBorderRadius: 4, +}; + +export const storybookLightTheme = create({ + base: "light", + ...common, + colorPrimary: "#FF7A00", + colorSecondary: "#DC6B03", + appBg: "#f5f5f5", + appContentBg: "#ffffff", + appPreviewBg: "#ffffff", + appBorderColor: "#e0e0e0", + textColor: "#141414", + textInverseColor: "#ffffff", + textMutedColor: "#666666", + barBg: "#ffffff", + barTextColor: "#666666", + barSelectedColor: "#FF7A00", + barHoverColor: "#FF7A00", + inputBg: "#ffffff", + inputBorder: "#d9d9d9", + inputTextColor: "#141414", +}); + +export const storybookDarkTheme = create({ + base: "dark", + ...common, + colorPrimary: "#FF9D00", + colorSecondary: "#DC6B03", + appBg: "#111217", + appContentBg: "#181b1f", + appPreviewBg: "#181b1f", + appBorderColor: "#2c2f36", + textColor: "#e0e0e0", + textInverseColor: "#111217", + textMutedColor: "#9b9b9b", + barBg: "#1f2229", + barTextColor: "#9b9b9b", + barSelectedColor: "#FF9D00", + barHoverColor: "#FF9D00", + inputBg: "#1f2229", + inputBorder: "#3d424d", + inputTextColor: "#e0e0e0", +}); diff --git a/.storybook/vite.config.ts b/.storybook/vite.config.ts new file mode 100644 index 0000000..d69e7d2 --- /dev/null +++ b/.storybook/vite.config.ts @@ -0,0 +1,24 @@ +import { readFileSync } from "node:fs"; + +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +const pkg = JSON.parse( + readFileSync(new URL("../package.json", import.meta.url), "utf8"), +) as { + version: string; + dependencies: Record; +}; + +/** + * Storybook's Vite config. Only what the components need at runtime: React, + * and the build constants the duplicate-copy warning reads (src/instance.ts), + * defined the same way as the library build. + */ +export default defineConfig({ + plugins: [react()], + define: { + __UI_COMMON_VERSION__: JSON.stringify(pkg.version), + __ASTRYX_CORE_VERSION__: JSON.stringify(pkg.dependencies["@astryxdesign/core"]), + }, +}); diff --git a/eslint.config.js b/eslint.config.js index d32c0a5..5f96aca 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,6 +1,7 @@ import js from "@eslint/js"; import globals from "globals"; import reactHooks from "eslint-plugin-react-hooks"; +import storybook from "eslint-plugin-storybook"; import tseslint from "typescript-eslint"; export default tseslint.config( @@ -10,6 +11,7 @@ export default tseslint.config( "coverage", "fixture/node_modules", "fixture/dist", + "storybook-static", // `astryx theme build` output, committed as generated. "src/theme/*/built/**", // Consumer code the upgrade codemods run on, and their expected output. @@ -70,6 +72,12 @@ export default tseslint.config( files: ["src/components/Form/**"], rules: { "@typescript-eslint/no-explicit-any": "off" }, }, + ...storybook.configs["flat/recommended"], + { + // Storybook's own config runs in Node (main.ts, vite.config.ts). + files: [".storybook/main.ts", ".storybook/vite.config.ts"], + languageOptions: { globals: globals.node }, + }, { files: ["**/*.test.{ts,tsx}", "src/test/**"], languageOptions: { globals: { ...globals.browser, ...globals.node } }, @@ -77,6 +85,7 @@ export default tseslint.config( { files: [ "scripts/**/*.mjs", + ".github/scripts/**/*.mjs", "packages/cli/{bin,cli,codemods,scripts}/**/*.mjs", "*.config.{js,ts}", ], diff --git a/package.json b/package.json index 4743fb9..bbd37af 100644 --- a/package.json +++ b/package.json @@ -592,6 +592,8 @@ "format:check": "prettier --check .", "test": "vitest run", "test:watch": "vitest", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", "gen:exports": "node scripts/gen-exports.mjs", "sync:forks": "node scripts/sync-forks.mjs", "theme:build": "astryx theme build src/theme/lablup/lablupTheme.ts -o src/theme/lablup/built/theme.css --icons-specifier @astryxdesign/theme-neutral", @@ -626,6 +628,8 @@ "@astryxdesign/cli": "0.6.5", "@astryxdesign/lab": "0.6.5-canary.8701623", "@eslint/js": "^9.39.0", + "@storybook/addon-docs": "^10.6.1", + "@storybook/react-vite": "^10.6.1", "@stylexjs/stylex": "0.19.0", "@testing-library/jest-dom": "6.9.1", "@testing-library/react": "^16.3.2", @@ -634,14 +638,17 @@ "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", "@vitejs/plugin-react": "^6.0.2", + "@vueless/storybook-dark-mode": "^10.0.8", "eslint": "^9.39.0", "eslint-plugin-react-hooks": "6.0.0-rc.1", + "eslint-plugin-storybook": "^10.6.1", "globals": "^16.5.0", "jsdom": "^29.1.1", "prettier": "^3.8.4", "react": "^19.2.7", "react-dom": "^19.2.7", "sass": "^1.105.0", + "storybook": "^10.6.1", "tinyglobby": "^0.2.15", "typescript": "^5.9.3", "typescript-eslint": "^8.47.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cddf95a..71caa75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,12 @@ importers: '@eslint/js': specifier: ^9.39.0 version: 9.39.5 + '@storybook/addon-docs': + specifier: ^10.6.1 + version: 10.6.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8)) + '@storybook/react-vite': + specifier: ^10.6.1 + version: 10.6.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8))(supports-color@8.1.1)(typescript@5.9.3)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0)) '@stylexjs/stylex': specifier: 0.19.0 version: 0.19.0 @@ -65,13 +71,19 @@ importers: version: 19.2.4(@types/react@19.2.18) '@vitejs/plugin-react': specifier: ^6.0.2 - version: 6.0.5(vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0)) + version: 6.0.5(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0)) + '@vueless/storybook-dark-mode': + specifier: ^10.0.8 + version: 10.0.8(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8)) eslint: specifier: ^9.39.0 version: 9.39.5(jiti@2.7.0)(supports-color@8.1.1) eslint-plugin-react-hooks: specifier: 6.0.0-rc.1 version: 6.0.0-rc.1(eslint@9.39.5(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1) + eslint-plugin-storybook: + specifier: ^10.6.1 + version: 10.6.1(eslint@9.39.5(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3) globals: specifier: ^16.5.0 version: 16.5.0 @@ -90,6 +102,9 @@ importers: sass: specifier: ^1.105.0 version: 1.105.0 + storybook: + specifier: ^10.6.1 + version: 10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8) tinyglobby: specifier: ^0.2.15 version: 0.2.17 @@ -101,13 +116,13 @@ importers: version: 8.66.0(eslint@9.39.5(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3) vite: specifier: ^8.0.16 - version: 8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0) + version: 8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0) vite-plugin-dts: specifier: ^4.5.4 - version: 4.5.4(@types/node@22.20.1)(supports-color@8.1.1)(typescript@5.9.3)(vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0)) + version: 4.5.4(@types/node@22.20.1)(supports-color@8.1.1)(typescript@5.9.3)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0)) vitest: specifier: ^4.1.8 - version: 4.1.10(@types/node@22.20.1)(jsdom@29.1.1)(vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0)) + version: 4.1.10(@types/node@22.20.1)(jsdom@29.1.1)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0)) packages/cli: dependencies: @@ -437,6 +452,180 @@ packages: peerDependencies: react: '>=16.8.0' + '@emnapi/core@1.11.0': + resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} + + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + + '@emnapi/runtime@1.11.0': + resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} + + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.10.1': resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -513,6 +702,15 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0': + resolution: {integrity: sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ==} + peerDependencies: + typescript: '>= 4.3.x' + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -526,9 +724,18 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mdx-js/react@3.1.1': + resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + '@microsoft/api-extractor-model@7.33.10': resolution: {integrity: sha512-uPUK17xGxeQ3av6TN7awp+dTTSTvx8fZC0XFL1gyt7hFapYuxND3XLXH8vkmI7UjfW92oogzFAFqHSifmJROaw==} @@ -542,9 +749,249 @@ packages: '@microsoft/tsdoc@0.16.0': resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} + '@napi-rs/wasm-runtime@1.2.5': + resolution: {integrity: sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 + + '@oxc-parser/binding-android-arm-eabi@0.127.0': + resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.127.0': + resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.127.0': + resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.127.0': + resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.127.0': + resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.142.0': resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==} + '@oxc-resolver/binding-android-arm-eabi@11.21.2': + resolution: {integrity: sha512-xQoCRv+gKax9KTdwdaQNnAFOai8neay7g3jExDIORzhbrejwGSJaZNTdOJHR5ziLg2joMxOCMOFMo4zuxza2uQ==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.21.2': + resolution: {integrity: sha512-HF5oiE2L05yInPYCFD/4uxSrEZW4SuIfn99Y6L1xnJnzl066JR+MJs2rIdstw8A2MPlAKH+13dpFPNycjqzvGg==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.21.2': + resolution: {integrity: sha512-UX4u49CVCAD8QZNELaW8eMGgMAGwFWYEPbvNsh+3r/gs4NX3KfpiACMVwRQT0EuH3uat9hM5Zl+Ppm9pJD8tgg==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.21.2': + resolution: {integrity: sha512-J9xPx7YBkrRmJ+xl561ztnMWEc1aOyjEIxBiGX1dVb3u7bGSnfObfcZk+Pd+uM0HZAPNsQ1xvD8j52A/uOSqNQ==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.21.2': + resolution: {integrity: sha512-fRlt7OvSaQkWj6+EDTVxawVxOlqJB2QSnBfkeCyK4RTvsGctbw3BiH2Tb7DzMs7bikc4BRBpvWP5zF9K8b54Zg==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.2': + resolution: {integrity: sha512-gK+vPUcPQITkGwBKpZGrcDHSlU6eDGl7AQacxS2CEKAZIBHWkOVFeJwLZ4tYnA1acJqRM5lt7yYwPCVqGHIJ7A==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.2': + resolution: {integrity: sha512-L/Rgas7SrOKy/z7IH+HxSFRqVO4PuLDKLEGKvnhoCBBq3UJ0YzGBou3qMzaAGgkJwsIvX2pBF+7ojzfUhLiZxQ==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.21.2': + resolution: {integrity: sha512-CUEYvlX1Fk7E9kUMzuswru1J9HLxMwnpDeQGjQuI4ZH+iNCoa2X9T+pvyzrbsgl7WnIeTFTlNlHsRfVdf5g9/g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.21.2': + resolution: {integrity: sha512-ViN1ZibQyxwC67GpoP33oo+S9UyUnkog13vzQb9+v9bCNvrVzJuk0MRdacjtv/9xfRMVF/eqHFyl8YOeykI10Q==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.2': + resolution: {integrity: sha512-CU1sCqWnhGqYD5I1HedHk5pujrn7ssDkNB/AEQd/pd3D/EojVSgJUlpbafwIbyhia3PgIfkvdFpRPWSALzVumA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.2': + resolution: {integrity: sha512-jWVyZtIHca4Gb96x7dag+y69vlei7ffjrsveLkmf2ZhqEAz6ZSBnY1GWvgZUaZlwZP62A3xD092BW97Q5VGc+g==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.21.2': + resolution: {integrity: sha512-LF29obFqNgBUgDX7rmUK7M4D0JQG5LxhYzn3xXmECcHU9aQAdWG7NiY052qybtesEdwHQXKNTWYQ7mTsybNvWg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.21.2': + resolution: {integrity: sha512-+NYcm+cCHBbtdQQ3A4phQTSuVRYnNHz7wrl9XRAPEovcdoqi0mb1K5ZOl+jN54ZD+q1zz3V0vltbFJmzecJKmw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.21.2': + resolution: {integrity: sha512-UQqZDdG2r2HhAOsZEgufkIWHPQ886IUyuJQkoZByvzhW8j51R4UNzGBJFkTiTnLhQnggwJRdJgFWK4uY6ZVIMw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.21.2': + resolution: {integrity: sha512-3Q9PMRjalWkT6NZ4jfujuqTCFwoWErg3y3BnOgb544B8IMw4PktiwWOigMfOHNRLMghZeJ7hpfpZf4CP7rV7Og==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.21.2': + resolution: {integrity: sha512-Eljeq3ndtyKhM+Es8LITi4Zl2htzuRZcrPMF3kMCsrILztvU6AjZ3FuEhHHKobPUB9rMpzLpJP5bDqXI7r+iog==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.21.2': + resolution: {integrity: sha512-HGDbNsIywqc4LxU38+CTJNnB/6BF7rheWOJ259b4eE0aEnelYblC8x+1tEd63bp31fYne63RQz9Jb4yVnC7Yig==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.21.2': + resolution: {integrity: sha512-iWx25CBEgH49iE9q5coEGI/jb1jl5kkCY9z6U5Og67xCkQ/WFMDc2J5U78+AE91SUxM2NqSLhJC8/PLfWnImww==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.21.2': + resolution: {integrity: sha512-VPoCAhKvCQTlG7vxqaBXcmuvbh77BfnGXj8g0pbvVXpm1F/R8rDVwqIWcEbMzrI1JvJlm8v7T9uMVQb6UctMRg==} + cpu: [x64] + os: [win32] + '@parcel/watcher-android-arm64@2.6.0': resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} engines: {node: '>= 10.0.0'} @@ -762,6 +1209,72 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@storybook/addon-docs@10.6.1': + resolution: {integrity: sha512-31PXR+MCWfI5YHKKah6tWnbV1FJWA9qbTHxtvpszPLd8FBekXtPOnmyYOpFF9CfciBF9bkeJFdFanJvUCjWbjA==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.6.1 + peerDependenciesMeta: + '@types/react': + optional: true + + '@storybook/builder-vite@10.6.1': + resolution: {integrity: sha512-kUMRRRS4MpJdxOLhrlQX3cpMYi2vVB3tIIRXyqWmAjVM6xT7OZ1SCQEMFPP+S3ToRqaYWZQb8aBqrJucbbYufA==} + peerDependencies: + storybook: ^10.6.1 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@storybook/global@5.0.0': + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + + '@storybook/icons@2.1.0': + resolution: {integrity: sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@storybook/react-dom-shim@10.6.1': + resolution: {integrity: sha512-OkPFycR6kVE+9ZsgQWRqnUfZm/sLY6dxsNAn3lsvV7DSAQWH9QnHLkqP9E/N/Hg6cKPDtGzDZ8vPNDz2FuaDuQ==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.6.1 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@storybook/react-vite@10.6.1': + resolution: {integrity: sha512-oyUZxuW2lIJN8BRbYyMyZbfjeYqtF6HwmNfqa1SAF525zRnMewYp66wm/5d8FAgwqejOf6N2+wR3hX866lSwMA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.6.1 + typescript: '>= 4.9.x' + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@storybook/react@10.6.1': + resolution: {integrity: sha512-t0QIzO0MVcpD52UEMZKSv35nO/+8vUnLGoFC1E3BIVjHEIdr6IFFM7ty2E7FWsaZGsah3XrFINKT3dPd2Atz+Q==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.6.1 + typescript: '>= 4.9.x' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + typescript: + optional: true + '@stylexjs/stylex@0.19.0': resolution: {integrity: sha512-CnUFp7YMaDLDeemsWOfJgoC/gKM5P/yBNMcpJaE6ChJmXr7s0DJwSeGTTlHJcqqwN9OW1qGtmARWLFhGZN1pTA==} @@ -794,24 +1307,45 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' + '@tybys/wasm-util@0.10.4': + resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} + '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mdx@2.0.14': + resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} + '@types/node@22.20.1': resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} @@ -823,6 +1357,9 @@ packages: '@types/react@19.2.18': resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + '@typescript-eslint/eslint-plugin@8.66.0': resolution: {integrity: sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -895,6 +1432,9 @@ packages: babel-plugin-react-compiler: optional: true + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.10': resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} @@ -909,6 +1449,9 @@ packages: vite: optional: true + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.10': resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} @@ -918,9 +1461,15 @@ packages: '@vitest/snapshot@4.1.10': resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.1.10': resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.1.10': resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} @@ -958,6 +1507,15 @@ packages: '@vue/shared@3.5.40': resolution: {integrity: sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==} + '@vueless/storybook-dark-mode@10.0.8': + resolution: {integrity: sha512-nhueFjEbyNGBzye4H+STC+pRIKthttiuPJRfcqCrDwqKnLS9AkVT3Ra+F5bvOeWbvZt3xc6xNJBNV5OrjRIoZQ==} + engines: {node: '>=20'} + peerDependencies: + storybook: ^10.0.0 + + '@webcontainer/env@1.1.1': + resolution: {integrity: sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1062,6 +1620,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + bundle-name@4.1.1: + resolution: {integrity: sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==} + engines: {node: '>=18'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1069,6 +1631,10 @@ packages: caniuse-lite@1.0.30001806: resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -1077,6 +1643,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -1191,9 +1761,25 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.1: + resolution: {integrity: sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1206,6 +1792,10 @@ packages: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -1215,6 +1805,10 @@ packages: electron-to-chromium@1.5.400: resolution: {integrity: sha512-96EWDNjM59SYflgeV5Ylsf4EMiq1a25YjCnJH7cxn/AF2H3pILRweaUnoLax0yKHWdpOzY6JKEu45e8irqZIHA==} + empathic@2.1.0: + resolution: {integrity: sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==} + engines: {node: '>=14'} + entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} @@ -1230,6 +1824,11 @@ packages: es-module-lexer@2.3.1: resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1244,6 +1843,11 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-storybook@10.6.1: + resolution: {integrity: sha512-XBmXuQcI0wzxMYc0rWKIiydvUxgS+GzLesO1YK1adnj97LhMqqsnlalbM+nXfQo1JoH2F2CI12sAH9/dAC99Dw==} + peerDependencies: + eslint: '>=8' + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1381,6 +1985,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1455,6 +2063,11 @@ packages: resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1463,6 +2076,11 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-plain-object@2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} @@ -1470,6 +2088,10 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1532,6 +2154,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} @@ -1635,6 +2260,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -1642,6 +2270,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} @@ -1661,6 +2292,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} + make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -1687,6 +2321,13 @@ packages: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} @@ -1722,10 +2363,21 @@ packages: resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + oxc-parser@0.127.0: + resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.21.2: + resolution: {integrity: sha512-w5tLwYN3Zo24w5EeWJjJWZOwhYqTtC8PS2B1tIt7BZUuqTIcU07sQValbDw+rq7+AuAGzOHklgK+ifsy4lpXfw==} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -1771,9 +2423,17 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1827,6 +2487,15 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + react-docgen-typescript@2.4.0: + resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} + peerDependencies: + typescript: '>= 4.3.x' + + react-docgen@8.0.4: + resolution: {integrity: sha512-gIYjtVTU4NCWZnb/LXVSWfClZXnjzX3+xxi69Tkfu4YsmYuqiOk8giLkKt/WwxWqh+U66CTh8Kqbha1uPv8/qQ==} + engines: {node: ^20.9.0 || >=22} + react-dom@19.2.8: resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} peerDependencies: @@ -1869,6 +2538,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + sass@1.105.0: resolution: {integrity: sha512-4EjSuRTyG8M4yRXXY1PT9OUV5zz23xBxJkbIAprohKHqf6HOU4MCpE3U3vAC+iO1gxobBjESNbcj8rYAQZWnyA==} engines: {node: '>=20.19.0'} @@ -1938,14 +2611,37 @@ packages: std-env@4.2.0: resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + storybook@10.6.1: + resolution: {integrity: sha512-NeEmukMWPA5OqHizUkQtxCIhGt7MUZLPY4FFgpC0LNiipjrac/oKpH0TxI+9Zc+vI5+IoN+NiskOmBP7gtYfmA==} + hasBin: true + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + prettier: ^2 || ^3 + vite-plus: ^0.1.15 || ^0.2.0 + peerDependenciesMeta: + '@types/react': + optional: true + prettier: + optional: true + vite-plus: + optional: true + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1982,10 +2678,18 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyrainbow@3.1.1: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} + tinyspy@4.0.6: + resolution: {integrity: sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==} + engines: {node: '>=14.0.0'} + tldts-core@7.4.10: resolution: {integrity: sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==} @@ -2011,6 +2715,14 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2044,6 +2756,10 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -2053,6 +2769,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.7.0: + resolution: {integrity: sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -2160,6 +2881,9 @@ packages: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-mimetype@5.0.0: resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} engines: {node: '>=20'} @@ -2186,6 +2910,22 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ws@8.22.0: + resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -2587,6 +3327,116 @@ snapshots: react: 19.2.8 tslib: 2.8.1 + '@emnapi/core@1.11.0': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@8.1.1))': dependencies: eslint: 9.39.5(jiti@2.7.0)(supports-color@8.1.1) @@ -2659,6 +3509,14 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@5.9.3)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0))': + dependencies: + glob: 13.0.6 + react-docgen-typescript: 2.4.0(typescript@5.9.3) + vite: 8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0) + optionalDependencies: + typescript: 5.9.3 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2673,11 +3531,19 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mdx-js/react@3.1.1(@types/react@19.2.18)(react@19.2.8)': + dependencies: + '@types/mdx': 2.0.14 + '@types/react': 19.2.18 + react: 19.2.8 + '@microsoft/api-extractor-model@7.33.10(@types/node@22.20.1)': dependencies: '@microsoft/tsdoc': 0.16.0 @@ -2713,8 +3579,149 @@ snapshots: '@microsoft/tsdoc@0.16.0': {} + '@napi-rs/wasm-runtime@1.2.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@tybys/wasm-util': 0.10.4 + optional: true + + '@napi-rs/wasm-runtime@1.2.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.4 + optional: true + + '@oxc-parser/binding-android-arm-eabi@0.127.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.127.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.2.5(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + optional: true + + '@oxc-project/types@0.127.0': {} + '@oxc-project/types@0.142.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.21.2': + optional: true + + '@oxc-resolver/binding-android-arm64@11.21.2': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.21.2': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.21.2': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.21.2': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.21.2': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.21.2': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.21.2': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@napi-rs/wasm-runtime': 1.2.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.21.2': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.21.2': + optional: true + '@parcel/watcher-android-arm64@2.6.0': optional: true @@ -2863,6 +3870,81 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@storybook/addon-docs@10.6.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8))': + dependencies: + '@mdx-js/react': 3.1.1(@types/react@19.2.18)(react@19.2.8) + '@storybook/icons': 2.1.0(react@19.2.8) + '@storybook/react-dom-shim': 10.6.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8)) + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + storybook: 10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8) + ts-dedent: 2.3.0 + unplugin: 2.3.11 + optionalDependencies: + '@types/react': 19.2.18 + transitivePeerDependencies: + - '@types/react-dom' + + '@storybook/builder-vite@10.6.1(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8))(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0))': + dependencies: + storybook: 10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8) + ts-dedent: 2.3.0 + vite: 8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0) + + '@storybook/global@5.0.0': {} + + '@storybook/icons@2.1.0(react@19.2.8)': + dependencies: + react: 19.2.8 + + '@storybook/react-dom-shim@10.6.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8))': + dependencies: + react: 19.2.8 + react-dom: 19.2.8(react@19.2.8) + storybook: 10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) + + '@storybook/react-vite@10.6.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8))(supports-color@8.1.1)(typescript@5.9.3)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0))': + dependencies: + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@5.9.3)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0)) + '@rollup/pluginutils': 5.4.0 + '@storybook/builder-vite': 10.6.1(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8))(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0)) + '@storybook/react': 10.6.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8))(supports-color@8.1.1)(typescript@5.9.3) + empathic: 2.1.0 + magic-string: 1.4.3 + react: 19.2.8 + react-docgen: 8.0.4(supports-color@8.1.1) + react-dom: 19.2.8(react@19.2.8) + resolve: 1.22.12 + storybook: 10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8) + tsconfig-paths: 4.2.0 + vite: 8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - rollup + - supports-color + + '@storybook/react@10.6.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8))(supports-color@8.1.1)(typescript@5.9.3)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/react-dom-shim': 10.6.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8)) + react: 19.2.8 + react-docgen: 8.0.4(supports-color@8.1.1) + react-docgen-typescript: 2.4.0(typescript@5.9.3) + react-dom: 19.2.8(react@19.2.8) + storybook: 10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8) + optionalDependencies: + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@stylexjs/stylex@0.19.0': dependencies: css-mediaquery: 0.1.2 @@ -2903,10 +3985,36 @@ snapshots: dependencies: '@testing-library/dom': 10.4.1 + '@tybys/wasm-util@0.10.4': + dependencies: + tslib: 2.8.1 + optional: true + '@types/argparse@1.0.38': {} '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.8 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -2914,10 +4022,14 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/doctrine@0.0.9': {} + '@types/estree@1.0.9': {} '@types/json-schema@7.0.15': {} + '@types/mdx@2.0.14': {} + '@types/node@22.20.1': dependencies: undici-types: 6.21.0 @@ -2930,6 +4042,8 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/resolve@1.20.6': {} + '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -3021,10 +4135,18 @@ snapshots: '@typescript-eslint/types': 8.66.0 eslint-visitor-keys: 5.0.1 - '@vitejs/plugin-react@6.0.5(vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0))': + '@vitejs/plugin-react@6.0.5(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0) + vite: 8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0) + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 '@vitest/expect@4.1.10': dependencies: @@ -3035,13 +4157,17 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.10(vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0))': + '@vitest/mocker@4.1.10(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0) + vite: 8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 '@vitest/pretty-format@4.1.10': dependencies: @@ -3059,8 +4185,18 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.6 + '@vitest/spy@4.1.10': {} + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@vitest/utils@4.1.10': dependencies: '@vitest/pretty-format': 4.1.10 @@ -3114,6 +4250,14 @@ snapshots: '@vue/shared@3.5.40': {} + '@vueless/storybook-dark-mode@10.0.8(storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8))': + dependencies: + '@storybook/global': 5.0.0 + lodash-es: 4.18.1 + storybook: 10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8) + + '@webcontainer/env@1.1.1': {} + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: acorn: 8.18.0 @@ -3210,10 +4354,22 @@ snapshots: buffer-from@1.1.2: {} + bundle-name@4.1.1: + dependencies: + run-applescript: 7.1.0 + callsites@3.1.0: {} caniuse-lite@1.0.30001806: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chai@6.2.2: {} chalk@4.1.2: @@ -3221,6 +4377,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + check-error@2.1.3: {} + chokidar@5.0.0: dependencies: readdirp: 5.1.1 @@ -3321,20 +4479,37 @@ snapshots: decimal.js@10.6.0: {} + deep-eql@5.0.2: {} + deep-is@0.1.4: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.1: + dependencies: + bundle-name: 4.1.1 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: {} diff@8.0.4: {} + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} electron-to-chromium@1.5.400: {} + empathic@2.1.0: {} + entities@7.0.1: {} entities@8.0.0: {} @@ -3343,6 +4518,35 @@ snapshots: es-module-lexer@2.3.1: {} + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -3359,6 +4563,15 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-storybook@10.6.1(eslint@9.39.5(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3): + dependencies: + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/utils': 8.66.0(eslint@9.39.5(jiti@2.7.0)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.3) + eslint: 9.39.5(jiti@2.7.0)(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + - typescript + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -3502,6 +4715,12 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@13.0.6: + dependencies: + minimatch: 10.2.6 + minipass: 7.1.3 + path-scurry: 2.0.2 + globals@14.0.0: {} globals@16.5.0: {} @@ -3560,18 +4779,28 @@ snapshots: dependencies: hasown: 2.0.4 + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-plain-object@2.0.4: dependencies: isobject: 3.0.1 is-potential-custom-element-name@1.0.1: {} + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isexe@2.0.0: {} isobject@3.0.1: {} @@ -3647,6 +4876,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + jsonfile@6.2.1: dependencies: universalify: 2.0.1 @@ -3730,12 +4961,16 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.18.1: {} + lodash.merge@4.6.2: {} loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lru-cache@11.5.2: {} lru-cache@5.1.1: @@ -3752,6 +4987,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@1.4.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -3777,6 +5016,10 @@ snapshots: dependencies: brace-expansion: 2.1.4 + minimist@1.2.8: {} + + minipass@7.1.3: {} + mlly@1.8.2: dependencies: acorn: 8.18.0 @@ -3803,6 +5046,13 @@ snapshots: obug@2.1.4: {} + open@10.2.0: + dependencies: + default-browser: 5.5.1 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3812,6 +5062,53 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + oxc-parser@0.127.0: + dependencies: + '@oxc-project/types': 0.127.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.127.0 + '@oxc-parser/binding-android-arm64': 0.127.0 + '@oxc-parser/binding-darwin-arm64': 0.127.0 + '@oxc-parser/binding-darwin-x64': 0.127.0 + '@oxc-parser/binding-freebsd-x64': 0.127.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.127.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.127.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.127.0 + '@oxc-parser/binding-linux-arm64-musl': 0.127.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.127.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-musl': 0.127.0 + '@oxc-parser/binding-openharmony-arm64': 0.127.0 + '@oxc-parser/binding-wasm32-wasi': 0.127.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.127.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 + '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + + oxc-resolver@11.21.2: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.21.2 + '@oxc-resolver/binding-android-arm64': 11.21.2 + '@oxc-resolver/binding-darwin-arm64': 11.21.2 + '@oxc-resolver/binding-darwin-x64': 11.21.2 + '@oxc-resolver/binding-freebsd-x64': 11.21.2 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.21.2 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.21.2 + '@oxc-resolver/binding-linux-arm64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-arm64-musl': 11.21.2 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-riscv64-musl': 11.21.2 + '@oxc-resolver/binding-linux-s390x-gnu': 11.21.2 + '@oxc-resolver/binding-linux-x64-gnu': 11.21.2 + '@oxc-resolver/binding-linux-x64-musl': 11.21.2 + '@oxc-resolver/binding-openharmony-arm64': 11.21.2 + '@oxc-resolver/binding-wasm32-wasi': 11.21.2 + '@oxc-resolver/binding-win32-arm64-msvc': 11.21.2 + '@oxc-resolver/binding-win32-x64-msvc': 11.21.2 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -3848,8 +5145,15 @@ snapshots: path-parse@1.0.7: {} + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + pathe@2.0.3: {} + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@4.0.5: {} @@ -3899,6 +5203,25 @@ snapshots: quansync@0.2.11: {} + react-docgen-typescript@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + react-docgen@8.0.4(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + '@types/doctrine': 0.0.9 + '@types/resolve': 1.20.6 + doctrine: 3.0.0 + resolve: 1.22.12 + strip-indent: 4.1.1 + transitivePeerDependencies: + - supports-color + react-dom@19.2.8(react@19.2.8): dependencies: react: 19.2.8 @@ -3954,6 +5277,8 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.2 '@rolldown/binding-win32-x64-msvc': 1.2.2 + run-applescript@7.1.0: {} + sass@1.105.0: dependencies: chokidar: 5.0.0 @@ -4005,12 +5330,43 @@ snapshots: std-env@4.2.0: {} + storybook@10.6.1(@types/react@19.2.18)(prettier@3.9.6)(react@19.2.8): + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.1.0(react@19.2.8) + '@testing-library/dom': 10.4.1 + '@testing-library/jest-dom': 6.9.1 + '@testing-library/user-event': 14.6.3(@testing-library/dom@10.4.1) + '@vitest/expect': 3.2.4 + '@vitest/spy': 3.2.4 + '@webcontainer/env': 1.1.1 + esbuild: 0.28.2 + jsonc-parser: 3.3.1 + open: 10.2.0 + oxc-parser: 0.127.0 + oxc-resolver: 11.21.2 + recast: 0.23.21 + semver: 7.8.5 + use-sync-external-store: 1.7.0(react@19.2.8) + ws: 8.22.0 + optionalDependencies: + '@types/react': 19.2.18 + prettier: 3.9.6 + transitivePeerDependencies: + - bufferutil + - react + - utf-8-validate + string-argv@0.3.2: {} + strip-bom@3.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 + strip-indent@4.1.1: {} + strip-json-comments@3.1.1: {} styleq@0.2.1: {} @@ -4038,8 +5394,12 @@ snapshots: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 + tinyrainbow@2.0.0: {} + tinyrainbow@3.1.1: {} + tinyspy@4.0.6: {} + tldts-core@7.4.10: {} tldts@7.4.10: @@ -4060,6 +5420,14 @@ snapshots: dependencies: typescript: 5.9.3 + ts-dedent@2.3.0: {} + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.8.1: {} type-check@0.4.0: @@ -4087,6 +5455,13 @@ snapshots: universalify@2.0.1: {} + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.18.0 + picomatch: 4.0.5 + webpack-virtual-modules: 0.6.2 + update-browserslist-db@1.2.3(browserslist@4.28.7): dependencies: browserslist: 4.28.7 @@ -4097,9 +5472,13 @@ snapshots: dependencies: punycode: 2.3.1 + use-sync-external-store@1.7.0(react@19.2.8): + dependencies: + react: 19.2.8 + util-deprecate@1.0.2: {} - vite-plugin-dts@4.5.4(@types/node@22.20.1)(supports-color@8.1.1)(typescript@5.9.3)(vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0)): + vite-plugin-dts@4.5.4(@types/node@22.20.1)(supports-color@8.1.1)(typescript@5.9.3)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0)): dependencies: '@microsoft/api-extractor': 7.58.12(@types/node@22.20.1) '@rollup/pluginutils': 5.4.0 @@ -4112,13 +5491,13 @@ snapshots: magic-string: 0.30.21 typescript: 5.9.3 optionalDependencies: - vite: 8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0) + vite: 8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0): + vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -4127,14 +5506,15 @@ snapshots: tinyglobby: 0.2.17 optionalDependencies: '@types/node': 22.20.1 + esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.7.0 sass: 1.105.0 - vitest@4.1.10(@types/node@22.20.1)(jsdom@29.1.1)(vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0)): + vitest@4.1.10(@types/node@22.20.1)(jsdom@29.1.1)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0)) + '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -4151,7 +5531,7 @@ snapshots: tinyexec: 1.3.0 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.2.0(@types/node@22.20.1)(jiti@2.7.0)(sass@1.105.0) + vite: 8.2.0(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.105.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.20.1 @@ -4167,6 +5547,8 @@ snapshots: webidl-conversions@8.0.1: {} + webpack-virtual-modules@0.6.2: {} + whatwg-mimetype@5.0.0: {} whatwg-url@16.0.1: @@ -4193,6 +5575,12 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 + ws@8.22.0: {} + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9949de3..3ebb9ad 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -29,3 +29,6 @@ allowBuilds: "@astryxdesign/core": false # sass's optional native file watcher; the tests compile, they never watch. "@parcel/watcher": false + # Storybook's bundler. Its postinstall only checks that the platform binary + # from its optional dependency is in place; the binary works without it. + esbuild: false diff --git a/src/Introduction.mdx b/src/Introduction.mdx new file mode 100644 index 0000000..48a83ea --- /dev/null +++ b/src/Introduction.mdx @@ -0,0 +1,30 @@ +import { Meta } from "@storybook/addon-docs/blocks"; + + + +# @lablup/ui-common + +Lablup's UI layer on top of [Astryx](https://github.com/facebook/astryx): the +Astryx surface re-exported 1:1, the Lablup brand theme, and the components +Lablup products share. + +The stories here cover ui-common's own components. Astryx's components are +documented by Astryx. + +## Toolbar + +- **Theme**: the Lablup brand theme, or Astryx's neutral baseline. A component + that only looks right under Lablup is assuming the brand. +- **Locale**: the locale handed to Astryx's `InternationalizationProvider`, + with Astryx's and ui-common's catalogs. Built-in strings follow it. +- **Dark mode**: the moon icon. It switches the `mode` of ``. + +## Writing a story + +Put `.stories.tsx` beside the component, in +`src/components//`. Every story is rendered inside `` and +`InternationalizationProvider` by the global decorator, so a story needs no +providers of its own. + +See the [README](https://github.com/lablup/ui-common#readme) for installing +and setting up the package in an app. diff --git a/src/components/PageHeader/PageHeader.stories.tsx b/src/components/PageHeader/PageHeader.stories.tsx new file mode 100644 index 0000000..87de216 --- /dev/null +++ b/src/components/PageHeader/PageHeader.stories.tsx @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { fn } from "storybook/test"; + +import { Button } from "../../astryx/Button"; +import { PageHeader } from "./PageHeader"; + +const meta = { + title: "Components/PageHeader", + component: PageHeader, + args: { + title: "Sessions", + description: "Compute sessions running in this project.", + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithActions: Story = { + args: { + actions: