diff --git a/PLAN.md b/PLAN.md index ed371b4..9740e6b 100644 --- a/PLAN.md +++ b/PLAN.md @@ -29,9 +29,10 @@ Tasks and subtasks for building the bread-recipes app (SolidJS + Python REST + O - [x] **4.2** Generate or synchronise typed API usage from the OpenAPI spec (client/types) so API calls stay strictly typed. - [x] **4.3** App shell: router, layout, and global styles (clean, minimalist, bread-appropriate palette, responsive). - [x] **4.4** Home page: fetch and list bread recipes with overview + thumbnail; navigate to detail on click. -- [ ] **4.5** Component library: evaluate options for SolidJS (e.g. **shadcn-solid** with Tailwind and Kobalte primitives vs smaller headless stacks); record the decision; add the chosen tooling and migrate or adopt components on at least one real screen so the pattern is established. +- [x] **4.5** Component library: evaluate options for SolidJS (e.g. **shadcn-solid** with Tailwind vs smaller stacks); record the decision; add the chosen tooling and migrate or adopt components on at least one real screen so the pattern is established. - [x] **4.6** Recipe page: full recipe content and larger image; deep-linkable route (e.g. by id). -- [ ] **4.7** MSW for tests; knip configured; Vitest coverage at 100% with a CI gate. +- [ ] **4.7** **shadcn-solid adoption (full):** migrate remaining UI (app shell, home, recipe cards and detail sections, and any shared layout) to registry components and Tailwind utilities where it replaces bespoke CSS; align tokens with **`COMPONENT_LIBRARY.md`**; no orphaned hand-rolled controls that duplicate registry patterns. Update **`COMPONENT_LIBRARY.md`** when scope is complete. +- [ ] **4.8** MSW for tests; knip configured; Vitest coverage at 100% with a CI gate. ## 5. Contract testing (Pact) diff --git a/apps/web/COMPONENT_LIBRARY.md b/apps/web/COMPONENT_LIBRARY.md new file mode 100644 index 0000000..636cbb5 --- /dev/null +++ b/apps/web/COMPONENT_LIBRARY.md @@ -0,0 +1,33 @@ +# Component library (PLAN §4.5) + +## Decision: [shadcn-solid](https://shadcn-solid.com/) + +We standardise on **shadcn-solid** — the SolidJS port of **[shadcn/ui](https://ui.shadcn.com/)**: copy-in components under **`src/components/ui/`**, **Tailwind CSS**, **class-variance-authority**, **`cn()`** (`clsx` + `tailwind-merge`), and the **registry CLI** to add or refresh blocks. + +Implementation details (Kobalte, CVA, etc.) are whatever the **published registry** uses for each component; we do **not** pick a separate primitive stack as the product decision — **shadcn-solid** is the choice. + +## Options considered (summary) + +| Option | Notes | +| ------ | ----- | +| **shadcn-solid** | Same mental model as shadcn/ui: own the source, consistent variants, CLI adds components. | +| **Ad hoc Tailwind only** | Fast for one-offs; diverges from a shared design system. | +| **A full component npm package** | Version churn; less control than vendored registry files. | + +## Tooling in this repo + +- **Tailwind CSS v4** via **`@tailwindcss/vite`** (`vite.config.ts`). +- **`tailwindcss-animate`** (`@plugin` in `src/index.css`). +- **Path alias `@/`** for imports (aligned with [shadcn-solid manual install](https://shadcn-solid.com/docs/installation/manual)). +- **`src/lib/utils.ts`** — **`cn()`** helper as in the docs. + +## Adding or updating UI + +1. From **`apps/web`**, run **`pnpm dlx shadcn-solid@latest init`** once if **`components.json`** is missing (needs registry access). +2. Add components: **`pnpm dlx shadcn-solid@latest add `** (e.g. **`button`**, **`card`**). Use **`--overwrite`** when refreshing an existing file. +3. Prefer **`@/`** imports; use **`./`** for same-folder modules where Biome allows (see project rules). + +## Adoption so far + +- **`src/components/ui/button.tsx`** — **shadcn-solid-style** `Button` + **`buttonVariants`** (see [Button](https://shadcn-solid.com/docs/components/button)). For router links, the docs recommend **`buttonVariants` on ``** — used on **recipe detail** for “← All recipes” (`RecipePage`). +- Existing bread **CSS variables** in **`src/index.css`** remain the source of theme tokens; utility classes reference them where the default shadcn HSL tokens are not wired yet. diff --git a/apps/web/README.md b/apps/web/README.md index 83b4da6..3f14a7a 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -35,7 +35,11 @@ Configuration: **`biome.json`** (Biome **2.4.x**), **`vite.config.ts`** (include ## App shell (PLAN §4.3) -Routing uses [**`@solidjs/router`**](https://github.com/solidjs/solid-router): **`Router`** with a shared **`AppShell`** layout (header, main outlet, footer). Routes include **`/`** (home) and **`/recipes/:id`** (detail placeholder until §4.5). Global styles live in **`src/index.css`** (warm bread surfaces, cool complementary accents); layout CSS in **`src/layout/AppShell.css`**. +Routing uses [**`@solidjs/router`**](https://github.com/solidjs/solid-router): **`Router`** with a shared **`AppShell`** layout (header, main outlet, footer). Routes include **`/`** (home) and **`/recipes/:id`** (detail). Global styles live in **`src/index.css`** (warm bread surfaces, cool complementary accents); layout CSS in **`src/layout/AppShell.css`**. + +## Component library (PLAN §4.5) + +**[shadcn-solid](https://shadcn-solid.com/)** — registry components under **`src/components/ui/`**, Tailwind v4, and **`cn()`**. Rationale and workflow: **[`COMPONENT_LIBRARY.md`](./COMPONENT_LIBRARY.md)**. ## OpenAPI client diff --git a/apps/web/biome.json b/apps/web/biome.json index 0db319f..e8974b0 100644 --- a/apps/web/biome.json +++ b/apps/web/biome.json @@ -52,6 +52,11 @@ } } ], + "css": { + "parser": { + "tailwindDirectives": true + } + }, "javascript": { "formatter": { "quoteStyle": "single", diff --git a/apps/web/package.json b/apps/web/package.json index 401043c..89a1c1d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,15 +15,22 @@ "openapi:validate": "pnpm --filter @solid-pact/openapi run lint" }, "dependencies": { + "@kobalte/core": "0.13.11", "@solidjs/router": "0.16.1", - "solid-js": "1.9.12" + "class-variance-authority": "0.7.1", + "clsx": "2.1.1", + "solid-js": "1.9.12", + "tailwind-merge": "3.5.0" }, "devDependencies": { "@biomejs/biome": "2.4.10", "@hey-api/openapi-ts": "0.95.0", "@solidjs/testing-library": "0.8.10", + "@tailwindcss/vite": "4.2.2", "@types/node": "24.12.0", "jsdom": "29.0.1", + "tailwindcss": "4.2.2", + "tailwindcss-animate": "1.0.7", "typescript": "5.9.3", "vite": "8.0.3", "vite-plugin-solid": "2.11.11", diff --git a/apps/web/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx new file mode 100644 index 0000000..1a089ff --- /dev/null +++ b/apps/web/src/components/ui/button.tsx @@ -0,0 +1,62 @@ +/** + * shadcn-solid Button — this file *is* the shadcn component (vendored registry source, not an npm import). + * There is no `from "shadcn-solid"` package for UI: the CLI copies components into `components/ui/`. + * + * The upstream registry implementation wraps Kobalte’s primitive — so `@kobalte/core` appears here by design. + * @see https://shadcn-solid.com/docs/components/button + * + * Refresh from registry: `pnpm dlx shadcn-solid@latest add button --overwrite` (after `init`). + */ +import { Button as KobalteButton } from '@kobalte/core/button'; +import { cva, type VariantProps } from 'class-variance-authority'; +import type { ComponentProps, JSX } from 'solid-js'; +import { splitProps } from 'solid-js'; +import { cn } from '@/lib/utils'; + +// biome-ignore lint/nursery/useExplicitType: explicit CVA output type is verbose and harms inference +export const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg)] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + variants: { + variant: { + default: + 'bg-[var(--accent)] text-white shadow hover:bg-[var(--accent-hover)]', + destructive: + 'bg-red-600 text-white shadow-sm hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800', + outline: + 'border border-[var(--border)] bg-transparent shadow-sm hover:bg-[var(--accent-bg)] hover:text-[var(--text-h)]', + secondary: + 'bg-[var(--border)] text-[var(--text-h)] shadow-sm hover:bg-[var(--text-muted)]/25', + ghost: 'hover:bg-[var(--accent-bg)] hover:text-[var(--text-h)]', + link: 'text-[var(--accent)] underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9', + link: 'h-auto p-0 text-[0.95rem]', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +); + +export type ButtonProps = ComponentProps & + VariantProps; + +export const Button = (props: ButtonProps): JSX.Element => { + const [local, rest] = splitProps(props, ['class', 'variant', 'size']); + return ( + + ); +}; diff --git a/apps/web/src/index.css b/apps/web/src/index.css index b288049..3f87cba 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1,3 +1,6 @@ +@import "tailwindcss"; +@plugin "tailwindcss-animate"; + :root { /* * Surfaces: warm flour / crust / wheat (bread). diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts new file mode 100644 index 0000000..72ea646 --- /dev/null +++ b/apps/web/src/lib/utils.ts @@ -0,0 +1,5 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +/** Merge Tailwind classes; later wins on conflicts. */ +export const cn = (...inputs: ClassValue[]): string => twMerge(clsx(inputs)); diff --git a/apps/web/src/pages/Page.css b/apps/web/src/pages/Page.css index 95a1b2e..21faebc 100644 --- a/apps/web/src/pages/Page.css +++ b/apps/web/src/pages/Page.css @@ -162,17 +162,6 @@ margin: 0 0 1rem; } -.recipe-detail-back-link { - font-size: 0.95rem; - color: var(--accent); - text-decoration: none; -} - -.recipe-detail-back-link:hover { - color: var(--accent-hover); - text-decoration: underline; -} - .recipe-detail-header { margin-bottom: 1rem; } diff --git a/apps/web/src/pages/RecipePage.tsx b/apps/web/src/pages/RecipePage.tsx index 4f33c48..4211ec7 100644 --- a/apps/web/src/pages/RecipePage.tsx +++ b/apps/web/src/pages/RecipePage.tsx @@ -4,6 +4,7 @@ import { createResource, Show } from 'solid-js'; import type { RecipeDetail } from '@/api'; import { getRecipeById } from '@/api'; import { RecipeDetailBody } from '@/components/recipe-detail/RecipeDetailBody'; +import { buttonVariants } from '@/components/ui/button'; import './Page.css'; export const RecipePage = (): JSX.Element => { @@ -25,7 +26,7 @@ export const RecipePage = (): JSX.Element => { return (

- + ← All recipes

diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index ed700a7..a0d36d0 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,6 +1,7 @@ /// import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import tailwindcss from '@tailwindcss/vite'; import { defineConfig } from 'vite'; import solid from 'vite-plugin-solid'; @@ -12,7 +13,7 @@ export default defineConfig({ '@': path.resolve(__dirname, 'src'), }, }, - plugins: [solid()], + plugins: [tailwindcss(), solid()], test: { environment: 'jsdom', globals: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a19585d..f78da9d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,12 +16,24 @@ importers: apps/web: dependencies: + '@kobalte/core': + specifier: 0.13.11 + version: 0.13.11(solid-js@1.9.12) '@solidjs/router': specifier: 0.16.1 version: 0.16.1(solid-js@1.9.12) + class-variance-authority: + specifier: 0.7.1 + version: 0.7.1 + clsx: + specifier: 2.1.1 + version: 2.1.1 solid-js: specifier: 1.9.12 version: 1.9.12 + tailwind-merge: + specifier: 3.5.0 + version: 3.5.0 devDependencies: '@biomejs/biome': specifier: 2.4.10 @@ -32,12 +44,21 @@ importers: '@solidjs/testing-library': specifier: 0.8.10 version: 0.8.10(@solidjs/router@0.16.1(solid-js@1.9.12))(solid-js@1.9.12) + '@tailwindcss/vite': + specifier: 4.2.2 + version: 4.2.2(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.0)(jiti@2.6.1)(yaml@2.8.3)) '@types/node': specifier: 24.12.0 version: 24.12.0 jsdom: specifier: 29.0.1 version: 29.0.1 + tailwindcss: + specifier: 4.2.2 + version: 4.2.2 + tailwindcss-animate: + specifier: 1.0.7 + version: 1.0.7(tailwindcss@4.2.2) typescript: specifier: 5.9.3 version: 5.9.3 @@ -221,6 +242,11 @@ packages: resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true + '@corvu/utils@0.4.2': + resolution: {integrity: sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA==} + peerDependencies: + solid-js: ^1.8 + '@csstools/color-helpers@6.0.2': resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} engines: {node: '>=20.19.0'} @@ -290,6 +316,15 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@hey-api/codegen-core@0.7.4': resolution: {integrity: sha512-DGd9yeSQzflOWO3Y5mt1GRXkXH9O/yIMgbxPjwLI3jwu/3nAjoXXD26lEeFb6tclYlg0JAqTIs5d930G/qxHeA==} engines: {node: '>=20.19.0'} @@ -315,6 +350,12 @@ packages: '@hey-api/types@0.1.4': resolution: {integrity: sha512-thWfawrDIP7wSI9ioT13I5soaaqB5vAPIiZmgD8PbeEVKNrkonc0N/Sjj97ezl7oQgusZmaNphGdMKipPO6IBg==} + '@internationalized/date@3.12.0': + resolution: {integrity: sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==} + + '@internationalized/number@3.6.5': + resolution: {integrity: sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -334,6 +375,16 @@ packages: '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@kobalte/core@0.13.11': + resolution: {integrity: sha512-hK7TYpdib/XDb/r/4XDBFaO9O+3ZHz4ZWryV4/3BfES+tSQVgg2IJupDnztKXB0BqbSRy/aWlHKw1SPtNPYCFQ==} + peerDependencies: + solid-js: ^1.8.15 + + '@kobalte/utils@0.9.1': + resolution: {integrity: sha512-eeU60A3kprIiBDAfv9gUJX1tXGLuZiKMajUfSQURAF2pk4ZoMYiqIzmrMBvzcxP39xnYttgTyQEVLwiTZnrV4w==} + peerDependencies: + solid-js: ^1.8.8 + '@napi-rs/wasm-runtime@1.1.2': resolution: {integrity: sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==} peerDependencies: @@ -564,6 +615,61 @@ packages: '@rolldown/pluginutils@1.0.0-rc.12': resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + '@solid-primitives/event-listener@2.4.5': + resolution: {integrity: sha512-nwRV558mIabl4yVAhZKY8cb6G+O1F0M6Z75ttTu5hk+SxdOnKSGj+eetDIu7Oax1P138ZdUU01qnBPR8rnxaEA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/keyed@1.5.3': + resolution: {integrity: sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/map@0.4.13': + resolution: {integrity: sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/media@2.3.5': + resolution: {integrity: sha512-LX9fB5WDaK87FMDtUB1qokBOfT2et9Uobv/zZaKLH9caFSz4+P70MBKEIBHcZQy+9MV5M2XvGYLTbLskjkzMjA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/props@3.2.3': + resolution: {integrity: sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/refs@1.1.3': + resolution: {integrity: sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/resize-observer@2.1.5': + resolution: {integrity: sha512-AiyTknKcNBaKHbcSMuxtSNM8FjIuiSuFyFghdD0TcCMU9hKi9EmsC5pjfjDwxE+5EueB1a+T/34PLRI5vbBbKw==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/rootless@1.5.3': + resolution: {integrity: sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/static-store@0.1.3': + resolution: {integrity: sha512-uxez7SXnr5GiRnzqO2IEDjOJRIXaG+0LZLBizmUA1FwSi+hrpuMzVBwyk70m4prcl8X6FDDXUl9O8hSq8wHbBQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/trigger@1.2.3': + resolution: {integrity: sha512-Za2JebEiDyfamjmDwRaESYqBBYOlgYGzB8kHYH0QrkXyLf2qNADlKdGN+z3vWSLCTDcKxChS43Kssjuc0OZhng==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/utils@6.4.0': + resolution: {integrity: sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A==} + peerDependencies: + solid-js: ^1.6.12 + '@solidjs/router@0.16.1': resolution: {integrity: sha512-IhyjedgC6LRpw/8CPGGI89FrV+r0xTHzOl2c4CRyzYQ1bLepJxbVI1LLKvsavMWY5TRBRacV7hAeOhuTXkjiqg==} peerDependencies: @@ -582,6 +688,99 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/helpers@0.5.21': + resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} + + '@tailwindcss/node@4.2.2': + resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} + + '@tailwindcss/oxide-android-arm64@4.2.2': + resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.2': + resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.2': + resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.2.2': + resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -786,6 +985,9 @@ packages: citty@0.2.2: resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} @@ -936,6 +1138,10 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + engines: {node: '>=10.13.0'} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -1068,6 +1274,9 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + handlebars@4.7.9: resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} engines: {node: '>=0.4.7'} @@ -1680,6 +1889,16 @@ packages: solid-js@1.9.12: resolution: {integrity: sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==} + solid-presence@0.1.8: + resolution: {integrity: sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA==} + peerDependencies: + solid-js: ^1.8 + + solid-prevent-scroll@0.1.10: + resolution: {integrity: sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw==} + peerDependencies: + solid-js: ^1.8 + solid-refresh@0.6.3: resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} peerDependencies: @@ -1740,6 +1959,21 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@4.2.2: + resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} + + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} + engines: {node: '>=6'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2211,6 +2445,11 @@ snapshots: dependencies: css-tree: 3.2.1 + '@corvu/utils@0.4.2(solid-js@1.9.12)': + dependencies: + '@floating-ui/dom': 1.7.6 + solid-js: 1.9.12 + '@csstools/color-helpers@6.0.2': {} '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': @@ -2265,6 +2504,17 @@ snapshots: '@exodus/schemasafe@1.3.0': {} + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/utils@0.2.11': {} + '@hey-api/codegen-core@0.7.4': dependencies: '@hey-api/types': 0.1.4 @@ -2314,6 +2564,14 @@ snapshots: '@hey-api/types@0.1.4': {} + '@internationalized/date@3.12.0': + dependencies: + '@swc/helpers': 0.5.21 + + '@internationalized/number@3.6.5': + dependencies: + '@swc/helpers': 0.5.21 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2335,6 +2593,29 @@ snapshots: '@jsdevtools/ono@7.1.3': {} + '@kobalte/core@0.13.11(solid-js@1.9.12)': + dependencies: + '@floating-ui/dom': 1.7.6 + '@internationalized/date': 3.12.0 + '@internationalized/number': 3.6.5 + '@kobalte/utils': 0.9.1(solid-js@1.9.12) + '@solid-primitives/props': 3.2.3(solid-js@1.9.12) + '@solid-primitives/resize-observer': 2.1.5(solid-js@1.9.12) + solid-js: 1.9.12 + solid-presence: 0.1.8(solid-js@1.9.12) + solid-prevent-scroll: 0.1.10(solid-js@1.9.12) + + '@kobalte/utils@0.9.1(solid-js@1.9.12)': + dependencies: + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) + '@solid-primitives/keyed': 1.5.3(solid-js@1.9.12) + '@solid-primitives/map': 0.4.13(solid-js@1.9.12) + '@solid-primitives/media': 2.3.5(solid-js@1.9.12) + '@solid-primitives/props': 3.2.3(solid-js@1.9.12) + '@solid-primitives/refs': 1.1.3(solid-js@1.9.12) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: '@emnapi/core': 1.9.2 @@ -2564,6 +2845,65 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.12': {} + '@solid-primitives/event-listener@2.4.5(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/keyed@1.5.3(solid-js@1.9.12)': + dependencies: + solid-js: 1.9.12 + + '@solid-primitives/map@0.4.13(solid-js@1.9.12)': + dependencies: + '@solid-primitives/trigger': 1.2.3(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/media@2.3.5(solid-js@1.9.12)': + dependencies: + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) + '@solid-primitives/rootless': 1.5.3(solid-js@1.9.12) + '@solid-primitives/static-store': 0.1.3(solid-js@1.9.12) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/props@3.2.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/refs@1.1.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/resize-observer@2.1.5(solid-js@1.9.12)': + dependencies: + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) + '@solid-primitives/rootless': 1.5.3(solid-js@1.9.12) + '@solid-primitives/static-store': 0.1.3(solid-js@1.9.12) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/rootless@1.5.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/static-store@0.1.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/trigger@1.2.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + + '@solid-primitives/utils@6.4.0(solid-js@1.9.12)': + dependencies: + solid-js: 1.9.12 + '@solidjs/router@0.16.1(solid-js@1.9.12)': dependencies: solid-js: 1.9.12 @@ -2577,6 +2917,78 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@swc/helpers@0.5.21': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.2.2': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.20.1 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.2 + + '@tailwindcss/oxide-android-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide@4.2.2': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-x64': 4.2.2 + '@tailwindcss/oxide-freebsd-x64': 4.2.2 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-x64-musl': 4.2.2 + '@tailwindcss/oxide-wasm32-wasi': 4.2.2 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 + + '@tailwindcss/vite@4.2.2(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.0)(jiti@2.6.1)(yaml@2.8.3))': + dependencies: + '@tailwindcss/node': 4.2.2 + '@tailwindcss/oxide': 4.2.2 + tailwindcss: 4.2.2 + vite: 8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.0)(jiti@2.6.1)(yaml@2.8.3) + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.0 @@ -2819,6 +3231,10 @@ snapshots: citty@0.2.2: {} + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + classnames@2.5.1: {} cliui@7.0.4: @@ -2941,6 +3357,11 @@ snapshots: emoji-regex@8.0.0: {} + enhanced-resolve@5.20.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.2 + entities@6.0.1: {} es-define-property@1.0.1: {} @@ -3069,6 +3490,8 @@ snapshots: gopd@1.2.0: {} + graceful-fs@4.2.11: {} + handlebars@4.7.9: dependencies: minimist: 1.2.8 @@ -3673,6 +4096,16 @@ snapshots: seroval: 1.5.1 seroval-plugins: 1.5.1(seroval@1.5.1) + solid-presence@0.1.8(solid-js@1.9.12): + dependencies: + '@corvu/utils': 0.4.2(solid-js@1.9.12) + solid-js: 1.9.12 + + solid-prevent-scroll@0.1.10(solid-js@1.9.12): + dependencies: + '@corvu/utils': 0.4.2(solid-js@1.9.12) + solid-js: 1.9.12 + solid-refresh@0.6.3(solid-js@1.9.12): dependencies: '@babel/generator': 7.29.1 @@ -3748,6 +4181,16 @@ snapshots: symbol-tree@3.2.4: {} + tailwind-merge@3.5.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@4.2.2): + dependencies: + tailwindcss: 4.2.2 + + tailwindcss@4.2.2: {} + + tapable@2.3.2: {} + tinybench@2.9.0: {} tinyexec@1.0.4: {}