From e0a4653653aaed6993e7dc5c332715c975cac437 Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Fri, 17 Apr 2026 23:51:02 +0300 Subject: [PATCH 001/104] fix(design-system): ancrer palette canvas/ink-2 sur html+body --- src/index.css | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/index.css b/src/index.css index d4b5078..558ede0 100644 --- a/src/index.css +++ b/src/index.css @@ -1 +1,105 @@ @import 'tailwindcss'; + +/* Dark mode : .dark class sur — toggle React (ThemeProvider, étape 2) */ +@variant dark (&:where(.dark, .dark *)); + +@theme { + /* ─── Typographie ───────────────────────────────────────────── */ + --font-sans: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", + system-ui, sans-serif; + + /* ─── Fonds ─────────────────────────────────────────────────── */ + /* bg-canvas = fond de page (jamais pur blanc) */ + /* bg-surface = cards — ressortent sur le canvas */ + --color-canvas: #EEF2F8; + --color-canvas-2: #E6EBF4; + --color-surface: #FFFFFF; + --color-surface-hover: #F8FAFD; + + /* ─── Hairlines ──────────────────────────────────────────────── */ + --color-line: #DDE3ED; + --color-line-strong: #C7D0E0; + + /* ─── Encres ─────────────────────────────────────────────────── */ + --color-ink-1: #0F172A; + --color-ink-2: #1E293B; + --color-ink-3: #475569; + --color-ink-4: #64748B; + --color-ink-5: #94A3B8; + + /* ─── Brand Expria ───────────────────────────────────────────── */ + --color-expria: #1B4FD8; + --color-expria-hover: #1741B8; + --color-expria-50: #EEF3FF; + --color-expria-100: #DCE6FF; + --color-expria-200: #B8CDFF; + --color-deep: #0B1F5C; + --color-deep-2: #142B6E; + + /* ─── Sémantiques ────────────────────────────────────────────── */ + --color-success: #0E9F6E; + --color-success-bg: #E6F6F0; + --color-warning: #C77A00; + --color-warning-bg: #FEF3E2; + --color-danger: #C53030; + --color-danger-bg: #FDECEC; + + /* ─── Rayons (override des defaults Tailwind) ────────────────── */ + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --radius-xl: 18px; + --radius-full: 999px; + + /* ─── Ombres (light mode) ────────────────────────────────────── */ + --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04); + --shadow-md: 0 4px 12px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.04); + --shadow-lg: 0 12px 28px rgba(15, 23, 42, 0.08), 0 2px 6px rgba(15, 23, 42, 0.04); +} + +/* ─── Dark mode — override des tokens couleur et ombres ──────────── */ +.dark { + /* Fonds */ + --color-canvas: #0D1220; + --color-canvas-2: #121A2D; + --color-surface: #182238; + --color-surface-hover: #1E2A42; + + /* Hairlines */ + --color-line: #27324B; + --color-line-strong: #364363; + + /* Encres */ + --color-ink-1: #F1F4FA; + --color-ink-2: #DDE3EF; + --color-ink-3: #A8B2C7; + --color-ink-4: #7A8499; + --color-ink-5: #525C73; + + /* Brand — remonté en luminance pour rester lisible sur fond sombre */ + --color-expria: #5B7FFF; + --color-expria-hover: #6F8EFF; + --color-expria-50: rgba(91, 127, 255, 0.12); + --color-deep: #060B1A; + + /* Sémantiques */ + --color-success: #3DD68C; + --color-success-bg: rgba(61, 214, 140, 0.12); + --color-warning: #F5B849; + --color-warning-bg: rgba(245, 184, 73, 0.12); + --color-danger: #F06B6B; + --color-danger-bg: rgba(240, 107, 107, 0.12); + + /* Ombres — jouer sur les surfaces, pas les ombres claires */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5); +} + +/* ─── Rendu sub-pixel global (non couvert par Tailwind) ──────────── */ +body { + background-color: var(--color-canvas); + color: var(--color-ink-2); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} From a1d98bd255f6488e329d186dea1ddc7310c85be3 Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Fri, 17 Apr 2026 23:55:41 +0300 Subject: [PATCH 002/104] =?UTF-8?q?feat(design-system):=20ThemeProvider=20?= =?UTF-8?q?+=20useTheme=20=E2=80=94=20toggle=20dark/light=20(Sprint=200.5?= =?UTF-8?q?=20=C3=A9tape=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/providers.tsx | 35 ++++++++++++++++++++++++++++++----- src/shared/hooks/useTheme.ts | 8 ++++++++ src/shared/lib/theme.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 src/shared/hooks/useTheme.ts create mode 100644 src/shared/lib/theme.ts diff --git a/src/app/providers.tsx b/src/app/providers.tsx index 1a6feff..915515d 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -1,14 +1,39 @@ +import { useState, useEffect } from 'react' import { QueryClientProvider } from '@tanstack/react-query' import { BrowserRouter } from 'react-router-dom' import { queryClient } from '@/shared/lib/query-client' +import { + ThemeContext, + type Theme, + getInitialTheme, + applyTheme, + persistTheme, +} from '@/shared/lib/theme' import { AppRouter } from './router' +function ThemeProvider({ children }: { children: React.ReactNode }) { + const [theme, setThemeState] = useState(getInitialTheme) + + useEffect(() => { + applyTheme(theme) + persistTheme(theme) + }, [theme]) + + function setTheme(t: Theme) { + setThemeState(t) + } + + return {children} +} + export function Providers() { return ( - - - - - + + + + + + + ) } diff --git a/src/shared/hooks/useTheme.ts b/src/shared/hooks/useTheme.ts new file mode 100644 index 0000000..45a8206 --- /dev/null +++ b/src/shared/hooks/useTheme.ts @@ -0,0 +1,8 @@ +import { useContext } from 'react' +import { ThemeContext } from '@/shared/lib/theme' + +export function useTheme() { + const ctx = useContext(ThemeContext) + if (!ctx) throw new Error('useTheme must be used within ThemeProvider') + return ctx +} diff --git a/src/shared/lib/theme.ts b/src/shared/lib/theme.ts new file mode 100644 index 0000000..aedd752 --- /dev/null +++ b/src/shared/lib/theme.ts @@ -0,0 +1,26 @@ +import { createContext } from 'react' + +export type Theme = 'light' | 'dark' + +export interface ThemeContextValue { + theme: Theme + setTheme: (t: Theme) => void +} + +export const ThemeContext = createContext(null) + +const STORAGE_KEY = 'expria-theme' + +export function getInitialTheme(): Theme { + const stored = localStorage.getItem(STORAGE_KEY) + if (stored === 'light' || stored === 'dark') return stored + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' +} + +export function applyTheme(theme: Theme): void { + document.documentElement.classList.toggle('dark', theme === 'dark') +} + +export function persistTheme(theme: Theme): void { + localStorage.setItem(STORAGE_KEY, theme) +} From 4c4d46d637c2fc332b041ebdbb2fe4324641754c Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 18 Apr 2026 00:03:44 +0300 Subject: [PATCH 003/104] =?UTF-8?q?feat(design-system):=20scaffold=20shadc?= =?UTF-8?q?n/ui=20=E2=80=94=20components.json=20+=20cn()=20(Sprint=200.5?= =?UTF-8?q?=20=C3=A9tape=203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.json | 21 +++++++++++++++++++++ src/shared/lib/utils.ts | 6 ++++++ 2 files changed, 27 insertions(+) create mode 100644 components.json create mode 100644 src/shared/lib/utils.ts diff --git a/components.json b/components.json new file mode 100644 index 0000000..18e6574 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/shared/components", + "utils": "@/shared/lib/utils", + "ui": "@/shared/components/ui", + "lib": "@/shared/lib", + "hooks": "@/shared/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/src/shared/lib/utils.ts b/src/shared/lib/utils.ts new file mode 100644 index 0000000..d32b0fe --- /dev/null +++ b/src/shared/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} From dfe9fee56aa850eb69c4d05873b0e3c0a8ca0441 Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 18 Apr 2026 00:56:13 +0300 Subject: [PATCH 004/104] =?UTF-8?q?feat(design-system):=20Button=20+=20Bad?= =?UTF-8?q?ge=20shadcn=20remapp=C3=A9s=20tokens=20Direction=20H=20(Sprint?= =?UTF-8?q?=200.5=20=C3=A9tape=204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.json | 10 ++--- src/shared/components/ui/badge.tsx | 48 ++++++++++++++++++++++ src/shared/components/ui/button.tsx | 64 +++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 src/shared/components/ui/badge.tsx create mode 100644 src/shared/components/ui/button.tsx diff --git a/components.json b/components.json index 18e6574..10d45fb 100644 --- a/components.json +++ b/components.json @@ -11,11 +11,11 @@ "prefix": "" }, "aliases": { - "components": "@/shared/components", - "utils": "@/shared/lib/utils", - "ui": "@/shared/components/ui", - "lib": "@/shared/lib", - "hooks": "@/shared/hooks" + "components": "src/shared/components", + "utils": "src/shared/lib/utils", + "ui": "src/shared/components/ui", + "lib": "src/shared/lib", + "hooks": "src/shared/hooks" }, "iconLibrary": "lucide" } diff --git a/src/shared/components/ui/badge.tsx b/src/shared/components/ui/badge.tsx new file mode 100644 index 0000000..8c12c58 --- /dev/null +++ b/src/shared/components/ui/badge.tsx @@ -0,0 +1,48 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/shared/lib/utils" + +const badgeVariants = cva( + "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-expria focus-visible:ring-[3px] focus-visible:ring-expria/30 aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40 [&>svg]:pointer-events-none [&>svg]:size-3", + { + variants: { + variant: { + default: "bg-expria text-white [a&]:hover:bg-expria/90", + secondary: + "bg-canvas-2 text-ink-1 [a&]:hover:bg-canvas-2/90", + destructive: + "bg-danger text-white focus-visible:ring-danger/20 dark:bg-danger/60 dark:focus-visible:ring-danger/40 [a&]:hover:bg-danger/90", + outline: + "border-line text-ink-2 [a&]:hover:bg-canvas-2 [a&]:hover:text-ink-1", + ghost: "[a&]:hover:bg-canvas-2 [a&]:hover:text-ink-1", + link: "text-expria underline-offset-4 [a&]:hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant = "default", + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/src/shared/components/ui/button.tsx b/src/shared/components/ui/button.tsx new file mode 100644 index 0000000..a6f59f9 --- /dev/null +++ b/src/shared/components/ui/button.tsx @@ -0,0 +1,64 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/shared/lib/utils" + +const buttonVariants = cva( + "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-expria focus-visible:ring-[3px] focus-visible:ring-expria/30 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-expria text-white hover:bg-expria/90", + destructive: + "bg-danger text-white hover:bg-danger/90 focus-visible:ring-danger/20 dark:bg-danger/60 dark:focus-visible:ring-danger/40", + outline: + "border bg-surface shadow-xs hover:bg-canvas-2 hover:text-ink-1 dark:border-line dark:bg-surface/30 dark:hover:bg-surface/50", + secondary: + "bg-canvas-2 text-ink-1 hover:bg-canvas-2/80", + ghost: + "hover:bg-canvas-2 hover:text-ink-1 dark:hover:bg-canvas-2/50", + link: "text-expria underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant = "default", + size = "default", + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot.Root : "button" + + return ( + + ) +} + +export { Button, buttonVariants } From 323b48c7cb45279dc1958bf4574ec8e11c023076 Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 18 Apr 2026 01:04:52 +0300 Subject: [PATCH 005/104] =?UTF-8?q?feat(design-system):=20composants=20sha?= =?UTF-8?q?dcn=20input/label/separator/avatar/progress=20=E2=80=94=20token?= =?UTF-8?q?s=20Direction=20H=20(Sprint=200.5=20=C3=A9tape=205)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 1768 +++++++++++++++++++++++- package.json | 1 + src/shared/components/ui/avatar.tsx | 107 ++ src/shared/components/ui/input.tsx | 21 + src/shared/components/ui/label.tsx | 22 + src/shared/components/ui/progress.tsx | 29 + src/shared/components/ui/separator.tsx | 26 + 7 files changed, 1971 insertions(+), 3 deletions(-) create mode 100644 src/shared/components/ui/avatar.tsx create mode 100644 src/shared/components/ui/input.tsx create mode 100644 src/shared/components/ui/label.tsx create mode 100644 src/shared/components/ui/progress.tsx create mode 100644 src/shared/components/ui/separator.tsx diff --git a/package-lock.json b/package-lock.json index abb1ef1..ecfc6b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.460.0", + "radix-ui": "^1.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", "react-router-dom": "^7.14.1", @@ -638,6 +639,44 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -769,6 +808,1504 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", + "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", + "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", + "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", + "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", + "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", + "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-toggle-group": "1.1.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-rc.15", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", @@ -1578,7 +3115,7 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1588,7 +3125,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -2152,6 +3689,18 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -2463,7 +4012,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/data-urls": { @@ -2542,6 +4091,12 @@ "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -3062,6 +4617,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -4223,6 +5787,83 @@ "node": ">=6" } }, + "node_modules/radix-ui": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", + "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-accessible-icon": "1.1.7", + "@radix-ui/react-accordion": "1.2.12", + "@radix-ui/react-alert-dialog": "1.1.15", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-aspect-ratio": "1.1.7", + "@radix-ui/react-avatar": "1.1.10", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-context-menu": "2.2.16", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-form": "0.1.8", + "@radix-ui/react-hover-card": "1.1.15", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-menubar": "1.1.16", + "@radix-ui/react-navigation-menu": "1.2.14", + "@radix-ui/react-one-time-password-field": "0.1.8", + "@radix-ui/react-password-toggle-field": "0.1.3", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-progress": "1.1.7", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-scroll-area": "1.2.10", + "@radix-ui/react-select": "2.2.6", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-slider": "1.3.6", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tabs": "1.1.13", + "@radix-ui/react-toast": "1.2.15", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-toggle-group": "1.1.11", + "@radix-ui/react-toolbar": "1.1.11", + "@radix-ui/react-tooltip": "1.2.8", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-escape-keydown": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/react": { "version": "19.2.5", "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", @@ -4252,6 +5893,53 @@ "license": "MIT", "peer": true }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "7.14.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.1.tgz", @@ -4290,6 +5978,28 @@ "react-dom": ">=18" } }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -4742,6 +6452,58 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "8.0.8", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", diff --git a/package.json b/package.json index 4e61d3f..b03c4aa 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.460.0", + "radix-ui": "^1.4.3", "react": "^19.2.4", "react-dom": "^19.2.4", "react-router-dom": "^7.14.1", diff --git a/src/shared/components/ui/avatar.tsx b/src/shared/components/ui/avatar.tsx new file mode 100644 index 0000000..099636e --- /dev/null +++ b/src/shared/components/ui/avatar.tsx @@ -0,0 +1,107 @@ +import * as React from "react" +import { Avatar as AvatarPrimitive } from "radix-ui" + +import { cn } from "@/shared/lib/utils" + +function Avatar({ + className, + size = "default", + ...props +}: React.ComponentProps & { + size?: "default" | "sm" | "lg" +}) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) { + return ( + svg]:hidden", + "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", + "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", + className + )} + {...props} + /> + ) +} + +function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AvatarGroupCount({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", + className + )} + {...props} + /> + ) +} + +export { + Avatar, + AvatarImage, + AvatarFallback, + AvatarBadge, + AvatarGroup, + AvatarGroupCount, +} diff --git a/src/shared/components/ui/input.tsx b/src/shared/components/ui/input.tsx new file mode 100644 index 0000000..536184c --- /dev/null +++ b/src/shared/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" + +import { cn } from "@/shared/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/src/shared/components/ui/label.tsx b/src/shared/components/ui/label.tsx new file mode 100644 index 0000000..f61345a --- /dev/null +++ b/src/shared/components/ui/label.tsx @@ -0,0 +1,22 @@ +import * as React from "react" +import { Label as LabelPrimitive } from "radix-ui" + +import { cn } from "@/shared/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Label } diff --git a/src/shared/components/ui/progress.tsx b/src/shared/components/ui/progress.tsx new file mode 100644 index 0000000..5e2c5cc --- /dev/null +++ b/src/shared/components/ui/progress.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import { Progress as ProgressPrimitive } from "radix-ui" + +import { cn } from "@/shared/lib/utils" + +function Progress({ + className, + value, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { Progress } diff --git a/src/shared/components/ui/separator.tsx b/src/shared/components/ui/separator.tsx new file mode 100644 index 0000000..ef26830 --- /dev/null +++ b/src/shared/components/ui/separator.tsx @@ -0,0 +1,26 @@ +import * as React from "react" +import { Separator as SeparatorPrimitive } from "radix-ui" + +import { cn } from "@/shared/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Separator } From 9a4e22b5336f0b0776f5ec0b36cd8228ac280950 Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 18 Apr 2026 01:15:02 +0300 Subject: [PATCH 006/104] =?UTF-8?q?feat(design-system):=20composant=20shad?= =?UTF-8?q?cn=20dialog=20=E2=80=94=20tokens=20Direction=20H=20(Sprint=200.?= =?UTF-8?q?5=20=C3=A9tape=206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/components/ui/dialog.tsx | 156 ++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/shared/components/ui/dialog.tsx diff --git a/src/shared/components/ui/dialog.tsx b/src/shared/components/ui/dialog.tsx new file mode 100644 index 0000000..bd1efc6 --- /dev/null +++ b/src/shared/components/ui/dialog.tsx @@ -0,0 +1,156 @@ +import * as React from "react" +import { XIcon } from "lucide-react" +import { Dialog as DialogPrimitive } from "radix-ui" + +import { cn } from "@/shared/lib/utils" +import { Button } from "@/shared/components/ui/button" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<"div"> & { + showCloseButton?: boolean +}) { + return ( +
+ {children} + {showCloseButton && ( + + + + )} +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} From 7dfd0df6b3914202ffc6f6950de998ac44ee7108 Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 18 Apr 2026 01:25:54 +0300 Subject: [PATCH 007/104] =?UTF-8?q?feat(design-system):=20page=20/design-s?= =?UTF-8?q?ystem=20dev-only=20=E2=80=94=20palette=20live=20+=20composants?= =?UTF-8?q?=20(Sprint=200.5=20=C3=A9tape=207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/router.tsx | 15 ++ .../design-system/DesignSystemPage.tsx | 248 ++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 src/features/design-system/DesignSystemPage.tsx diff --git a/src/app/router.tsx b/src/app/router.tsx index dfe0538..9f09a3e 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -1,9 +1,24 @@ +import React, { Suspense } from 'react' import { Routes, Route } from 'react-router-dom' +const DesignSystemPage = import.meta.env.DEV + ? React.lazy(() => import('@/features/design-system/DesignSystemPage')) + : () => null + export function AppRouter() { return ( } /> + {import.meta.env.DEV && ( + Loading…
}> + + + } + /> + )} ) } diff --git a/src/features/design-system/DesignSystemPage.tsx b/src/features/design-system/DesignSystemPage.tsx new file mode 100644 index 0000000..27edc6f --- /dev/null +++ b/src/features/design-system/DesignSystemPage.tsx @@ -0,0 +1,248 @@ +import React, { useState } from 'react' +import { useTheme } from '@/shared/hooks/useTheme' +import { Button } from '@/shared/components/ui/button' +import { Badge } from '@/shared/components/ui/badge' +import { Input } from '@/shared/components/ui/input' +import { Label } from '@/shared/components/ui/label' +import { Separator } from '@/shared/components/ui/separator' +import { Progress } from '@/shared/components/ui/progress' +import { + Avatar, + AvatarFallback, + AvatarImage, + AvatarGroup, + AvatarGroupCount, +} from '@/shared/components/ui/avatar' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/shared/components/ui/dialog' + +// ─── palette data ──────────────────────────────────────────────────────────── + +const PALETTE: { token: string; var: string; light: string; dark: string }[] = [ + { token: 'canvas', var: '--color-canvas', light: '#EEF2F8', dark: '#0D1220' }, + { token: 'canvas-2', var: '--color-canvas-2', light: '#E6EBF4', dark: '#121A2D' }, + { token: 'surface', var: '--color-surface', light: '#FFFFFF', dark: '#182238' }, + { token: 'surface-hover',var: '--color-surface-hover',light: '#F8FAFD', dark: '#1E2A42' }, + { token: 'line', var: '--color-line', light: '#DDE3ED', dark: '#27324B' }, + { token: 'line-strong', var: '--color-line-strong', light: '#C7D0E0', dark: '#364363' }, + { token: 'ink-1', var: '--color-ink-1', light: '#0F172A', dark: '#F1F4FA' }, + { token: 'ink-2', var: '--color-ink-2', light: '#1E293B', dark: '#DDE3EF' }, + { token: 'ink-3', var: '--color-ink-3', light: '#475569', dark: '#A8B2C7' }, + { token: 'ink-4', var: '--color-ink-4', light: '#64748B', dark: '#7A8499' }, + { token: 'ink-5', var: '--color-ink-5', light: '#94A3B8', dark: '#525C73' }, + { token: 'expria', var: '--color-expria', light: '#1B4FD8', dark: '#5B7FFF' }, + { token: 'expria-hover', var: '--color-expria-hover', light: '#1741B8', dark: '#6F8EFF' }, + { token: 'expria-50', var: '--color-expria-50', light: '#EEF3FF', dark: 'rgba(91,127,255,.12)' }, + { token: 'expria-100', var: '--color-expria-100', light: '#DCE6FF', dark: '—' }, + { token: 'expria-200', var: '--color-expria-200', light: '#B8CDFF', dark: '—' }, + { token: 'deep', var: '--color-deep', light: '#0B1F5C', dark: '#060B1A' }, + { token: 'success', var: '--color-success', light: '#0E9F6E', dark: '#3DD68C' }, + { token: 'success-bg', var: '--color-success-bg', light: '#E6F6F0', dark: 'rgba(61,214,140,.12)' }, + { token: 'warning', var: '--color-warning', light: '#C77A00', dark: '#F5B849' }, + { token: 'warning-bg', var: '--color-warning-bg', light: '#FEF3E2', dark: 'rgba(245,184,73,.12)' }, + { token: 'danger', var: '--color-danger', light: '#C53030', dark: '#F06B6B' }, + { token: 'danger-bg', var: '--color-danger-bg', light: '#FDECEC', dark: 'rgba(240,107,107,.12)' }, +] + +// ─── section wrapper ───────────────────────────────────────────────────────── + +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+

{title}

+ + {children} +
+ ) +} + +// ─── main page ─────────────────────────────────────────────────────────────── + +export default function DesignSystemPage() { + const { theme, setTheme } = useTheme() + const [dialogOpen, setDialogOpen] = useState(false) + + return ( +
+ + {/* ── header ── */} +
+
+

Design System

+

Expria — Direction H palette · Sprint 0.5

+
+ +
+ + {/* ── palette ── */} +
+
+ {PALETTE.map(({ token, var: cssVar, light, dark }) => ( +
+
+
+

{token}

+

+ ☀ {light} +

+

+ ☾ {dark} +

+
+
+ ))} +
+
+ + {/* ── typography ── */} +
+
+

Display / 36px Bold

+

Heading 1 / 24px Semibold

+

Heading 2 / 20px Semibold

+

Heading 3 / 18px Medium

+

Body / 16px Regular — Plus Jakarta Sans

+

Small / 14px Regular — secondary copy

+

Caption / 12px Regular — labels, metadata

+

Mono / 12px — token names, code

+
+
+ + {/* ── buttons ── */} +
+
+
+ + + + + + +
+
+ + + + +
+
+ + +
+
+
+ + {/* ── badges ── */} +
+
+ Default + Secondary + Outline + Destructive +
+
+ + {/* ── inputs / forms ── */} +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +

Content below separator

+
+
+ + {/* ── avatar ── */} +
+
+
+ + + HK + + sm +
+
+ + + HK + + default +
+
+ + + HK + + lg +
+
+ + {['AB', 'CD', 'EF'].map(initials => ( + + {initials} + + ))} + +5 + + group +
+
+
+ + {/* ── dialog ── */} +
+
+ + + + + + + Example dialog + + This dialog uses Direction H tokens — bg-surface, border-line, text-ink-4. + Toggle the theme to see it adapt. + + + + + + + +
+
+ +
+ ) +} From ee6d679950fdbf65b0ce5f9bcc44a01266b1852e Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 18 Apr 2026 01:33:24 +0300 Subject: [PATCH 008/104] =?UTF-8?q?feat(shared):=20ThemeToggle=20+=20Logo?= =?UTF-8?q?=20+=20design=20system=20rules=20(Sprint=200.5=20=C3=A9tape=208?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/DEVELOPMENT_PRINCIPLES.md | 19 +++++++++++ docs/TECH_DEBT.md | 49 +++++++++++++++++---------- src/shared/components/Logo.tsx | 46 +++++++++++++++++++++++++ src/shared/components/ThemeToggle.tsx | 24 +++++++++++++ 4 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 src/shared/components/Logo.tsx create mode 100644 src/shared/components/ThemeToggle.tsx diff --git a/docs/DEVELOPMENT_PRINCIPLES.md b/docs/DEVELOPMENT_PRINCIPLES.md index 9d70607..4257a81 100644 --- a/docs/DEVELOPMENT_PRINCIPLES.md +++ b/docs/DEVELOPMENT_PRINCIPLES.md @@ -143,6 +143,24 @@ Voir `SECURITY.md` pour le détail. Claude Code ne crée jamais de worktree Git. Toutes les modifications se font directement dans le dossier du projet principal. +### Règle L — Tokens du design system, jamais de valeurs brutes +Toutes les couleurs dans le JSX passent exclusivement par les tokens Direction H : +- Utilitaires Tailwind : `bg-canvas`, `text-ink-2`, `border-line`, `bg-expria`, `text-danger`, etc. +- Jamais de classes couleur Tailwind par défaut : `bg-slate-100`, `text-gray-500`, `blue-600`… +- Jamais de valeurs inline brutes : `#1B4FD8`, `oklch(…)`, `rgb(…)` dans les className ou style +- Pour les inline styles dynamiques uniquement : `style={{ background: 'var(--color-expria)' }}` +- Tout nouveau token est ajouté exclusivement dans `@theme {}` (et `.dark {}`) de `src/index.css` + +```tsx +// ❌ JAMAIS +
+
+ +// ✅ TOUJOURS +
+
{/* inline style dynamique uniquement */} +``` + --- ## 3. Structure du code — conventions @@ -461,3 +479,4 @@ Avant chaque session Claude Code, vérifier : | Version | Date | Changements | |---|---|---| | 1.0 | 2026-04-17 | Création, adaptée de la version backend | +| 1.1 | 2026-04-18 | Ajout Règle L — tokens du design system (Sprint 0.5) | diff --git a/docs/TECH_DEBT.md b/docs/TECH_DEBT.md index 315c96f..c7beafa 100644 --- a/docs/TECH_DEBT.md +++ b/docs/TECH_DEBT.md @@ -123,30 +123,41 @@ Vient du pattern `c.json(result, result.status)` où `result` contient déjà `s --- -### FTD-11 — `@theme` Tailwind 4 non défini -**Priorité :** 🟢 Mineur -**Statut :** Ouvert — à faire dans session design system -**Estimation de session :** 1 jour (palette + typo + itérations design) -**Description :** `src/index.css` a été nettoyé à l'étape 11 du Sprint 0 et réduit à la seule ligne `@import 'tailwindcss';`. L'ADR 006 (§Configuration Tailwind 4) décrit le bloc `@theme { ... }` comme le mécanisme officiel de configuration Tailwind 4 (CSS-first) : +### FTD-14 — Anti-FOUC thème : script inline manquant dans `` +**Priorité :** 🟡 Important +**Statut :** Ouvert — à faire avant déploiement production +**Estimation de session :** 30 min +**Description :** Le `ThemeProvider` applique la classe `.dark` sur `` après l'hydratation React (`useEffect`). Entre le premier paint du navigateur et l'exécution de React, la page s'affiche brièvement en mode clair même si l'utilisateur a choisi le mode sombre — c'est le FOUC (Flash Of Unstyled Content). -```css -@theme { - --color-primary: #1B4FD8; - --font-sans: 'Plus Jakarta Sans', system-ui, sans-serif; -} +**Fix :** ajouter un script inline bloquant dans le `` de `index.html` qui lit `localStorage.getItem('expria-theme')` (et `prefers-color-scheme` en fallback) et applique `.dark` sur `document.documentElement` avant le premier paint. Ce script doit être minifié et inliné (non-async, non-defer) pour garantir l'exécution avant le CSS. + +```html + ``` -La palette brand Expria et la typographie ne sont pas encore décidées, donc `@theme` est volontairement absent pour ne pas poser de valeurs placeholder qu'il faudrait repasser plus tard. +**Impact actuel :** visible uniquement pour les utilisateurs en mode sombre — bref flash de fond clair au chargement. Acceptable en dev, indésirable en production. -**Impact actuel :** les composants utilisent les couleurs Tailwind par défaut (`bg-slate-*`, `text-gray-*`). Visuellement cohérent mais pas brand. +**Condition de résolution :** avant la première mise en production (Sprint 1 ou avant). -**À faire au Sprint 1 (design system) :** -- Définir la palette brand Expria (primary, secondary, neutrals, danger, success) -- Choisir + installer la typo (Plus Jakarta Sans ou autre) via `` dans `index.html` ou `@import` dans `index.css` -- Ajouter le bloc `@theme` dans `src/index.css` -- Mettre à jour ADR 006 avec les valeurs retenues +--- -**Condition de résolution :** Sprint 1 — session dédiée au design system. +### FTD-15 — Option `'system'` manquante dans ThemeProvider +**Priorité :** 🟢 Mineur +**Statut :** Reporté — après MVP +**Estimation de session :** 2h +**Description :** Le `ThemeProvider` est bi-state (`'light' | 'dark'`). L'option `'system'` (qui suit `prefers-color-scheme` en temps réel via `MediaQueryList.addEventListener`) a été volontairement différée (décision Sprint 0.5). + +**À faire :** +- Étendre le type `Theme` à `'light' | 'dark' | 'system'` +- Dans `ThemeProvider`, si `theme === 'system'` : écouter `matchMedia('(prefers-color-scheme: dark)')` et appliquer/retirer `.dark` dynamiquement +- `ThemeToggle` : cycle light → dark → system (ou un sélecteur 3 états) +- Mettre à jour `getInitialTheme()` pour retourner `'system'` si aucune préférence stockée + +**Condition de résolution :** après MVP — confort utilisateur, pas bloquant. --- @@ -244,6 +255,7 @@ La palette brand Expria et la typographie ne sont pas encore décidées, donc `@ | ID | Description | Résolu le | Comment | |---|---|---|---| +| FTD-11 | `@theme` Tailwind 4 non défini — palette et typographie absentes | 2026-04-18 | Résolu au Sprint 0.5 (design system). Palette Direction H complète (canvas/surface/ink/expria/deep/semantic) + typo Plus Jakarta Sans définis dans `src/index.css` via `@theme {}` et `.dark {}`. shadcn/ui remappé sur ces tokens. Règle L ajoutée dans `DEVELOPMENT_PRINCIPLES.md` pour garantir l'usage exclusif des tokens. | | FTD-13 | Incompatibilité Vitest 3 / Vite 8 (conflit de types `Plugin` entre le Vite 8 top-level avec Rolldown et le Vite 7 pinné de Vitest 3.2.4 ; `npm run build` cassé) | 2026-04-17 | Résolu par upgrade Vitest `3.2.4 → 4.1.4` (et `@vitest/coverage-v8` idem) à l'étape 12-bis du Sprint 0. Vitest 4.x supporte nativement Vite 8 Rolldown. Correctif complémentaire : script `typecheck` passé de `tsc --noEmit -p tsconfig.app.json` à `tsc -b --noEmit` pour couvrir aussi `tsconfig.node.json` (d'où `vite.config.ts`) et éviter qu'un bug similaire échappe à la CI. | --- @@ -255,3 +267,4 @@ La palette brand Expria et la typographie ne sont pas encore décidées, donc `@ | 1.0 | 2026-04-17 | Création initiale avec 9 FTD identifiées depuis l'audit backend et les décisions d'architecture | | 1.1 | 2026-04-17 | Ajout FTD-10 (Semgrep CI), FTD-11 (`@theme` Tailwind 4), FTD-12 (tests `api-client`) suite à l'étape 11 du Sprint 0 | | 1.2 | 2026-04-17 | Ajout FTD-13 résolu (incompatibilité Vitest 3 / Vite 8) suite à l'étape 12-bis du Sprint 0 | +| 1.3 | 2026-04-18 | FTD-11 résolu (design system Sprint 0.5) ; ajout FTD-14 (anti-FOUC), FTD-15 (option 'system' thème) | diff --git a/src/shared/components/Logo.tsx b/src/shared/components/Logo.tsx new file mode 100644 index 0000000..f1573d5 --- /dev/null +++ b/src/shared/components/Logo.tsx @@ -0,0 +1,46 @@ +import { cn } from '@/shared/lib/utils' + +type LogoSize = 'sm' | 'md' +type LogoVariant = 'icon' | 'full' + +interface LogoProps { + size?: LogoSize + variant?: LogoVariant + className?: string +} + +const markStyles: Record = { + sm: 'size-6 text-[11px]', + md: 'size-8 text-[13px]', +} + +const wordmarkStyles: Record = { + sm: 'text-sm', + md: 'text-base', +} + +export function Logo({ size = 'md', variant = 'full', className }: LogoProps) { + return ( +
+ + {variant === 'full' && ( + Expria + )} +
+ ) +} diff --git a/src/shared/components/ThemeToggle.tsx b/src/shared/components/ThemeToggle.tsx new file mode 100644 index 0000000..5189302 --- /dev/null +++ b/src/shared/components/ThemeToggle.tsx @@ -0,0 +1,24 @@ +import { Moon, Sun } from 'lucide-react' +import { useTheme } from '@/shared/hooks/useTheme' +import { Button } from '@/shared/components/ui/button' + +interface ThemeToggleProps { + className?: string +} + +export function ThemeToggle({ className }: ThemeToggleProps) { + const { theme, setTheme } = useTheme() + const isDark = theme === 'dark' + + return ( + + ) +} From b246f89903387347d4f434dfd99c9165df6a27ee Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 18 Apr 2026 01:36:16 +0300 Subject: [PATCH 009/104] fix(lint): eslint-disable react-refresh sur exports cva (badge, button) --- src/shared/components/ui/badge.tsx | 36 ++++++++++---------- src/shared/components/ui/button.tsx | 51 ++++++++++++++--------------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/src/shared/components/ui/badge.tsx b/src/shared/components/ui/badge.tsx index 8c12c58..364622e 100644 --- a/src/shared/components/ui/badge.tsx +++ b/src/shared/components/ui/badge.tsx @@ -1,39 +1,36 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" -import { Slot } from "radix-ui" +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' +import { Slot } from 'radix-ui' -import { cn } from "@/shared/lib/utils" +import { cn } from '@/shared/lib/utils' const badgeVariants = cva( - "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-expria focus-visible:ring-[3px] focus-visible:ring-expria/30 aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40 [&>svg]:pointer-events-none [&>svg]:size-3", + 'inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-expria focus-visible:ring-[3px] focus-visible:ring-expria/30 aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40 [&>svg]:pointer-events-none [&>svg]:size-3', { variants: { variant: { - default: "bg-expria text-white [a&]:hover:bg-expria/90", - secondary: - "bg-canvas-2 text-ink-1 [a&]:hover:bg-canvas-2/90", + default: 'bg-expria text-white [a&]:hover:bg-expria/90', + secondary: 'bg-canvas-2 text-ink-1 [a&]:hover:bg-canvas-2/90', destructive: - "bg-danger text-white focus-visible:ring-danger/20 dark:bg-danger/60 dark:focus-visible:ring-danger/40 [a&]:hover:bg-danger/90", - outline: - "border-line text-ink-2 [a&]:hover:bg-canvas-2 [a&]:hover:text-ink-1", - ghost: "[a&]:hover:bg-canvas-2 [a&]:hover:text-ink-1", - link: "text-expria underline-offset-4 [a&]:hover:underline", + 'bg-danger text-white focus-visible:ring-danger/20 dark:bg-danger/60 dark:focus-visible:ring-danger/40 [a&]:hover:bg-danger/90', + outline: 'border-line text-ink-2 [a&]:hover:bg-canvas-2 [a&]:hover:text-ink-1', + ghost: '[a&]:hover:bg-canvas-2 [a&]:hover:text-ink-1', + link: 'text-expria underline-offset-4 [a&]:hover:underline', }, }, defaultVariants: { - variant: "default", + variant: 'default', }, - } + }, ) function Badge({ className, - variant = "default", + variant = 'default', asChild = false, ...props -}: React.ComponentProps<"span"> & - VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot.Root : "span" +}: React.ComponentProps<'span'> & VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : 'span' return ( svg]:px-3", + default: 'h-9 px-4 py-2 has-[>svg]:px-3', xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3", - sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3", - "icon-sm": "size-8", - "icon-lg": "size-10", + sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5', + lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', + icon: 'size-9', + 'icon-xs': "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3", + 'icon-sm': 'size-8', + 'icon-lg': 'size-10', }, }, defaultVariants: { - variant: "default", - size: "default", + variant: 'default', + size: 'default', }, - } + }, ) function Button({ className, - variant = "default", - size = "default", + variant = 'default', + size = 'default', asChild = false, ...props -}: React.ComponentProps<"button"> & +}: React.ComponentProps<'button'> & VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot.Root : "button" + const Comp = asChild ? Slot.Root : 'button' return ( Date: Sat, 18 Apr 2026 02:00:12 +0300 Subject: [PATCH 010/104] =?UTF-8?q?feat(entities/user):=20PlanStatus=20+?= =?UTF-8?q?=20getPlanStatus=20+=20hook=20usePlan=20(Sprint=201=20=C3=A9tap?= =?UTF-8?q?e=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fondations data plan utilisateur pour le dashboard conditionnel : - entities/user/types.ts : interface PlanStatus (plan, permissions, simulations_used/remaining, plan_expires_at) - entities/user/api.ts : getPlanStatus() via apiFetch('/plans/status') - features/dashboard/hooks/usePlan.ts : useQuery + PLAN_QUERY_KEY + staleTime 5 min Co-Authored-By: Claude Opus 4.7 (1M context) --- src/entities/user/api.ts | 22 +++++++++++++++++ src/entities/user/types.ts | 32 +++++++++++++++++++++++++ src/features/dashboard/hooks/usePlan.ts | 24 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/entities/user/api.ts create mode 100644 src/entities/user/types.ts create mode 100644 src/features/dashboard/hooks/usePlan.ts diff --git a/src/entities/user/api.ts b/src/entities/user/api.ts new file mode 100644 index 0000000..3866873 --- /dev/null +++ b/src/entities/user/api.ts @@ -0,0 +1,22 @@ +/** + * Appels API du domaine `user`. + * + * Toutes les requêtes passent par `apiFetch` (Règle J) qui gère auth, retry, + * timeout et erreurs typées. Les consommateurs consomment ces fonctions via + * TanStack Query (cf. `features/dashboard/hooks/usePlan`). + */ + +import { apiFetch } from '@/shared/lib/api-client' +import type { PlanStatus } from './types' + +/** + * Récupère le statut courant du plan de l'utilisateur connecté. + * + * Endpoint : `GET /plans/status` + * Auth : JWT Bearer requis (ajouté automatiquement par `apiFetch`). + * + * @throws ApiError — notamment `AUTH_REQUIRED` si le JWT est absent/expiré. + */ +export function getPlanStatus(): Promise { + return apiFetch('/plans/status') +} diff --git a/src/entities/user/types.ts b/src/entities/user/types.ts new file mode 100644 index 0000000..e86ae34 --- /dev/null +++ b/src/entities/user/types.ts @@ -0,0 +1,32 @@ +/** + * Types publics du domaine `user`. + * + * Porte d'entrée unique pour les consommateurs frontend : toute UI ou hook + * qui manipule un plan ou une permission importe depuis ce fichier (ou `./lib` + * pour les fonctions), jamais directement depuis `./access` (cf. ADR 005). + */ + +import type { Feature, Plan } from './access' + +/** + * Réponse du backend pour `GET /plans/status`. + * + * Format confirmé par l'audit backend 2026-04-17 (cf. ARCHITECTURE.md §5). + * + * - `permissions` : dictionnaire booléen par feature. Les consommateurs + * doivent passer par `hasAccess(plan, feature)` plutôt que lire ce champ + * directement (Règle D / ADR 005). Il est exposé ici uniquement parce que + * le backend le renvoie et qu'il peut servir à du debug côté DevTools. + * - `simulations_remaining` : `null` si le plan est illimité (standard/premium), + * sinon nombre de simulations restantes sur le quota à vie (Free : 5). + * - `plan_expires_at` : ISO 8601 pour un plan payant actif, `null` pour Free. + */ +export interface PlanStatus { + plan: Plan + permissions: Record + simulations_used: number + simulations_remaining: number | null + plan_expires_at: string | null +} + +export type { Feature, Plan } diff --git a/src/features/dashboard/hooks/usePlan.ts b/src/features/dashboard/hooks/usePlan.ts new file mode 100644 index 0000000..4bd5cee --- /dev/null +++ b/src/features/dashboard/hooks/usePlan.ts @@ -0,0 +1,24 @@ +/** + * Hook TanStack Query sur le statut du plan utilisateur. + * + * Source unique de vérité côté frontend pour `plan`, `permissions`, et + * compteurs de simulations. Consommé par `DashboardPage`, les gardes de + * permission dans les pages simulations/t2-live, et le router. + * + * `staleTime: 5 min` — le plan change peu (upgrade Stripe, expiration). Les + * flux d'upgrade appellent `queryClient.invalidateQueries(['plan'])` pour + * forcer un refetch immédiat après webhook. + */ + +import { useQuery } from '@tanstack/react-query' +import { getPlanStatus } from '@/entities/user/api' + +export const PLAN_QUERY_KEY = ['plan'] as const + +export function usePlan() { + return useQuery({ + queryKey: PLAN_QUERY_KEY, + queryFn: getPlanStatus, + staleTime: 5 * 60 * 1000, + }) +} From 38777796aa626e1f90c78df6563ca88203ec21ab Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 18 Apr 2026 02:09:46 +0300 Subject: [PATCH 011/104] =?UTF-8?q?feat(auth):=20useAuth=20+=20ProtectedRo?= =?UTF-8?q?ute=20+=20signUp=20dans=20auth-client=20(Sprint=201=20=C3=A9tap?= =?UTF-8?q?e=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/skills/frontend-design/SKILL.md | 42 + .gitignore | 3 + design-reference/direction-h-dark.html | 927 ++++++++++++++++++ .../direction-h-juste-milieu.html | 921 +++++++++++++++++ docs/DESIGN_SYSTEM.md | 343 +++++++ docs/adr/006-stack-versions-2026.md | 104 +- index.html | 6 + .../auth/components/ProtectedRoute.tsx | 42 + src/features/auth/hooks/useAuth.ts | 54 + .../design-system/DesignSystemPage.tsx | 74 +- src/index.css | 96 +- src/shared/components/Logo.tsx | 4 +- src/shared/components/ui/avatar.tsx | 61 +- src/shared/components/ui/dialog.tsx | 54 +- src/shared/components/ui/input.tsx | 14 +- src/shared/components/ui/label.tsx | 15 +- src/shared/components/ui/progress.tsx | 11 +- src/shared/components/ui/separator.tsx | 12 +- src/shared/lib/auth-client.ts | 28 +- 19 files changed, 2620 insertions(+), 191 deletions(-) create mode 100644 .cursor/skills/frontend-design/SKILL.md create mode 100644 design-reference/direction-h-dark.html create mode 100644 design-reference/direction-h-juste-milieu.html create mode 100644 docs/DESIGN_SYSTEM.md create mode 100644 src/features/auth/components/ProtectedRoute.tsx create mode 100644 src/features/auth/hooks/useAuth.ts diff --git a/.cursor/skills/frontend-design/SKILL.md b/.cursor/skills/frontend-design/SKILL.md new file mode 100644 index 0000000..600b6db --- /dev/null +++ b/.cursor/skills/frontend-design/SKILL.md @@ -0,0 +1,42 @@ +--- +name: frontend-design +description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics. +license: Complete terms in LICENSE.txt +--- + +This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. + +The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints. + +## Design Thinking + +Before coding, understand the context and commit to a BOLD aesthetic direction: +- **Purpose**: What problem does this interface solve? Who uses it? +- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. +- **Constraints**: Technical requirements (framework, performance, accessibility). +- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember? + +**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. + +Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: +- Production-grade and functional +- Visually striking and memorable +- Cohesive with a clear aesthetic point-of-view +- Meticulously refined in every detail + +## Frontend Aesthetics Guidelines + +Focus on: +- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. +- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. +- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. +- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density. +- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays. + +NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character. + +Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations. + +**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well. + +Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4591520..1862d22 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ dist-ssr # Claude Code local config .claude/ + +# Exploration DA temporaire — supprimer une fois la direction choisie +design-exploration/ diff --git a/design-reference/direction-h-dark.html b/design-reference/direction-h-dark.html new file mode 100644 index 0000000..4a52f4a --- /dev/null +++ b/design-reference/direction-h-dark.html @@ -0,0 +1,927 @@ + + + + + +Expria — Tableau de bord · Direction H : Mode sombre + + + + + +
+
+
+
Direction H — Mode sombre
+
Inversion réfléchie de la version claire. Fond bleu-nuit désaturé, cards qui ressortent, bleu Expria remonté en luminance pour rester lisible.
+
+
Mode sombre
+
+
+ +
+ + + + + +
+ + + + +
+ +
+
Simulations restantes
+
3 / 5
+
+
Plan Découverte — renouvellement à chaque upgrade
+
+ +
+
Niveau estimé
+
NCLC 8
+
+
Moyenne des 3 dernières simulations · Objectif NCLC 9
+
+ +
+
Plan actuel
+
Découverte
+
Débloquez des simulations illimitées, le Mode Examen et le T2 Live avec Standard ou Premium.
+ + Passer à Standard + + +
+ +
+ + +
+ + +
+
Simulations récentes
+
Vos 3 dernières corrections
+ +
+ +
+
+ +
+
+
Expression écrite — Tâche 2
+
Aujourd'hui · 09:42
+
+
NCLC 9
+
16/20
+
+ +
+
+ +
+
+
Expression orale — Tâche 1
+
Il y a 2 jours
+
+
NCLC 8
+
14/20
+
+ +
+
+ +
+
+
Expression écrite — Tâche 3
+
Il y a 5 jours
+
+
NCLC 9
+
15/20
+
+ +
+
+ + +
+
Prochaine étape recommandée
+
Travaillez la tâche 2 à l'oral
+
+ Votre dernier score à l'EO T2 (14/20) est en dessous de votre moyenne. Une simulation de 10 minutes suffit pour consolider. +
+ +
+
+
Durée
+
10 min
+
+
+
Difficulté
+
Modérée
+
+
+ + +
+ +
+ + +
+
Palette de la direction H — mode sombre
+
+
+
+
#0D1220
+
Fond principal
+
+
+
+
#182238
+
Cards surface
+
+
+
+
#5B7FFF
+
Bleu Expria — remonté
+
+
+
+
#27324B
+
Hairlines
+
+
+
+
#F1F4FA
+
Titres
+
+
+
+
#A8B2C7
+
Corps secondaire
+
+
+
+
#3DD68C
+
Succès
+
+
+
+
#F5B849
+
Attention
+
+
+
+ +
+ +
+ + + diff --git a/design-reference/direction-h-juste-milieu.html b/design-reference/direction-h-juste-milieu.html new file mode 100644 index 0000000..87a062d --- /dev/null +++ b/design-reference/direction-h-juste-milieu.html @@ -0,0 +1,921 @@ + + + + + +Expria — Tableau de bord · Direction H : Juste milieu + + + + + +
+
+
+
Direction H — Juste milieu
+
Entre Boréal (trop blanc) et Cadence (trop sombre). Fond gris-bleuté, cards blanches en relief, bleu Expria pivot, accents bleu-nuit.
+
+
Version recommandée
+
+
+ +
+ + + + + +
+ + + + +
+ +
+
Simulations restantes
+
3 / 5
+
+
Plan Découverte — renouvellement à chaque upgrade
+
+ +
+
Niveau estimé
+
NCLC 8
+
+
Moyenne des 3 dernières simulations · Objectif NCLC 9
+
+ +
+
Plan actuel
+
Découverte
+
Débloquez des simulations illimitées, le Mode Examen et le T2 Live avec Standard ou Premium.
+ + Passer à Standard + + +
+ +
+ + +
+ + +
+
Simulations récentes
+
Vos 3 dernières corrections
+ +
+ +
+
+ +
+
+
Expression écrite — Tâche 2
+
Aujourd'hui · 09:42
+
+
NCLC 9
+
16/20
+
+ +
+
+ +
+
+
Expression orale — Tâche 1
+
Il y a 2 jours
+
+
NCLC 8
+
14/20
+
+ +
+
+ +
+
+
Expression écrite — Tâche 3
+
Il y a 5 jours
+
+
NCLC 9
+
15/20
+
+ +
+
+ + +
+
Prochaine étape recommandée
+
Travaillez la tâche 2 à l'oral
+
+ Votre dernier score à l'EO T2 (14/20) est en dessous de votre moyenne. Une simulation de 10 minutes suffit pour consolider. +
+ +
+
+
Durée
+
10 min
+
+
+
Difficulté
+
Modérée
+
+
+ + +
+ +
+ + +
+
Palette de la direction H
+
+
+
+
#EEF2F8
+
Fond principal
+
+
+
+
#FFFFFF
+
Cards (blanc franc)
+
+
+
+
#1B4FD8
+
Bleu Expria — pivot
+
+
+
+
#0B1F5C
+
Bleu nuit — premium
+
+
+
+
#0F172A
+
Titres
+
+
+
+
#475569
+
Corps
+
+
+
+
#0E9F6E
+
Succès
+
+
+
+
#C77A00
+
Attention
+
+
+
+ +
+ +
+ + + diff --git a/docs/DESIGN_SYSTEM.md b/docs/DESIGN_SYSTEM.md new file mode 100644 index 0000000..3a16386 --- /dev/null +++ b/docs/DESIGN_SYSTEM.md @@ -0,0 +1,343 @@ +# DESIGN_SYSTEM.md — Expria Frontend + +> **Document de référence — Version 1.0 — Sprint 1** +> Source de vérité unique pour l'identité visuelle, les tokens de design et les primitives UI. +> Toute décision de DA doit être consignée ici avant d'être implémentée. + +--- + +## 1. Direction artistique — verrouillée + +**Nom :** Boréal +**Positionnement :** institutionnel chaleureux, premium sans flashy, sérieux sans austère. +**Référence mentale :** Stripe Dashboard, Linear, Notion Desktop — mais réchauffé d'un cran. + +### Parti pris fondateurs + +| Principe | Décision | +|---|---| +| Mode canonique Sprint 1 | **Clair uniquement** (light chaud) | +| Mode sombre | Prévu Sprint 2+ (tokens écrits dual-theme-ready dès J1) | +| Fond principal | `#F4F2EC` (off-white calibré, ni froid ni saturé) | +| Surfaces élevées | Blanc pur `#FFFFFF` pour contraste subtil avec le fond | +| Bleu de marque | `#1B4FD8` **sacro-saint** en mode clair — aucune variation | +| Bleu mode sombre | `#7C9BFF` **prévu** pour Sprint 2+ (pattern Apple system colors) | +| Accent chaleureux | Aucun en Sprint 1 — le bleu porte toute l'intentionnalité | +| Angles | Rayons généreux mais retenus : 8 / 12 / 16 px | +| Ombres | Minimales. 1 ombre-card unique, très subtile. Hairlines 1px privilégiées. | +| Animations | 150–200 ms, `ease-out`, respect de `prefers-reduced-motion` | +| Icônes | SVG inline dans `shared/ui/icons/` — aucune dépendance externe | +| Typographie | Plus Jakarta Sans (via `font-family`, fallback système) | + +### Ce qu'on refuse explicitement + +- Gradients criards (le seul acceptable : aucun). +- Glassmorphism ou `backdrop-filter` généralisé — réservé à la bottom nav mobile si besoin. +- Emojis dans les éléments interactifs ou les labels fonctionnels. +- Ombres lourdes, "drop shadows" style Material Design 2. +- Plus de 2 niveaux d'élévation visuelle (fond → card → modal). +- Toute police de display fantaisiste, serif décorative ou condensée. +- Les motifs SaaS génériques : illustrations 3D, dégradés violet-rose, glass blobs. + +--- + +## 2. Tokens — `src/index.css` + +Remplacer intégralement le contenu actuel (`@import 'tailwindcss';`) par le bloc ci-dessous. Tailwind 4 lit automatiquement les tokens déclarés dans `@theme`. + +```css +@import 'tailwindcss'; + +@theme { + /* ----- Brand ------------------------------------------------------- */ + --color-brand: #1B4FD8; + --color-brand-hover: #1744B8; + --color-brand-active: #13379C; + --color-brand-soft: #E7EDFC; + --color-brand-ink: #FFFFFF; + + /* ----- Surfaces (light — Sprint 1) --------------------------------- */ + --color-bg: #F4F2EC; + --color-surface: #FBFAF6; + --color-surface-raised: #FFFFFF; + --color-surface-sunken: #EEECE4; + + /* ----- Ink (texte) ------------------------------------------------- */ + --color-ink-primary: #0F1220; + --color-ink-secondary: #4A4F5E; + --color-ink-tertiary: #8A8F9E; + --color-ink-inverse: #FBFAF6; + + /* ----- Borders & dividers ------------------------------------------ */ + --color-border: #E3E0D6; + --color-border-strong: #C9C5B7; + --color-border-focus: #1B4FD8; + + /* ----- Feedback ---------------------------------------------------- */ + --color-success: #1F7A4C; + --color-success-soft: #E3F2EA; + --color-warning: #B8741A; + --color-warning-soft: #F7EEDF; + --color-danger: #B8322D; + --color-danger-soft: #F7E1DF; + + /* ----- Typographie ------------------------------------------------- */ + --font-sans: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", sans-serif; + --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace; + + /* Échelle : mobile-first, les tailles desktop se gèrent via utilities Tailwind */ + --text-xs: 11px; + --text-sm: 13px; + --text-base: 14px; + --text-md: 15px; + --text-lg: 17px; + --text-xl: 20px; + --text-2xl: 24px; + --text-3xl: 32px; + --text-display: 40px; + + /* ----- Rayons ------------------------------------------------------ */ + --radius-xs: 6px; + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 20px; + --radius-pill: 999px; + + /* ----- Ombres ------------------------------------------------------ */ + --shadow-card: 0 1px 2px rgba(15, 18, 32, 0.04), 0 1px 8px rgba(15, 18, 32, 0.03); + --shadow-raised: 0 4px 16px rgba(15, 18, 32, 0.06), 0 1px 2px rgba(15, 18, 32, 0.04); + --shadow-focus: 0 0 0 3px rgba(27, 79, 216, 0.18); +} + +/* --------------------------------------------------------------------- */ +/* Globals — reset minimal, fond chaud par défaut */ +/* --------------------------------------------------------------------- */ + +html, body { + background: var(--color-bg); + color: var(--color-ink-primary); + font-family: var(--font-sans); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-variant-numeric: tabular-nums; +} + +*:focus-visible { + outline: none; + box-shadow: var(--shadow-focus); + border-radius: var(--radius-xs); +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0ms !important; + transition-duration: 0ms !important; + } +} + +/* --------------------------------------------------------------------- */ +/* TODO Sprint 2+ — Dark theme (Cadence recalibré) */ +/* --------------------------------------------------------------------- */ +/* +@theme { + --color-bg-dark: #0F1320; + --color-surface-dark: #171B2B; + --color-surface-raised-dark: #1E2338; + --color-ink-primary-dark: #E6E4DB; + --color-ink-secondary-dark: #9CA0AC; + --color-brand-dark: #7C9BFF; + --color-brand-hover-dark: #9AB3FF; + --color-border-dark: #2A3048; +} +*/ +``` + +### Règles d'usage des tokens + +1. **Aucune valeur hexadécimale en dur** dans les composants. Toute couleur passe par un token. +2. **Nommage sémantique obligatoire.** On écrit `bg-surface`, pas `bg-gray-50`. +3. Si un cas d'usage exige une teinte hors charte, **le documenter ici avant de l'ajouter**. Pas de token orphelin. +4. Les tokens marqués `*-dark` ne sont **pas utilisés en Sprint 1**. Leur présence en commentaire est intentionnelle pour faciliter la reprise Sprint 2+. + +--- + +## 3. Typographie + +| Usage | Taille | Poids | Tracking | Ligne | Token | +|---|---|---|---|---|---| +| Display (NCLC hero) | 40px | 700 | -0.02em | 1.0 | `text-display` | +| H1 page | 32px | 700 | -0.02em | 1.1 | `text-3xl` | +| H2 section | 24px | 700 | -0.015em | 1.2 | `text-2xl` | +| H3 card title | 20px | 700 | -0.01em | 1.3 | `text-xl` | +| Lead / intro | 17px | 500 | -0.005em | 1.5 | `text-lg` | +| Body | 14px | 400 | 0 | 1.6 | `text-base` | +| Body renforcé | 15px | 500 | 0 | 1.55 | `text-md` | +| Small / meta | 13px | 400 | 0 | 1.5 | `text-sm` | +| Eyebrow / label | 11px | 600 | 0.1em (uppercase) | 1.4 | `text-xs` | + +**Règles :** +- Tout nombre métier (score 16/20, NCLC 7,5, compteur 3/5) est rendu en `font-variant-numeric: tabular-nums`. Hérité par le body, mais à re-spécifier explicitement sur les tables et listes. +- `Plus Jakarta Sans` est déclarée en `font-family` avec fallback système. **Aucune webfont chargée** tant qu'on n'a pas validé la stratégie self-hosting (décision reportée). +- Les chiffres français utilisent la **virgule** comme séparateur décimal (`7,5`, jamais `7.5`). + +--- + +## 4. Primitives UI — Sprint 1 + +À créer dans `src/shared/ui/` en FSD, une primitive par dossier (`button/`, `card/`, etc.) avec `index.ts` pour l'export. + +### Inventaire minimal + +| Composant | Variants | Usage dashboard | +|---|---|---| +| `Button` | `primary` / `secondary` / `ghost` / `upgrade` | CTA "Nouvelle simulation", "Passer au plan Standard", actions tertiaires | +| `Card` | `default` / `raised` / `interactive` | Cadre métriques, item simulation, recommandation | +| `MetricCard` | `default` / `hero` (pour le NCLC) | Bloc NCLC, compteur simulations, dernier score | +| `ProgressBar` | `default` | Progression vers NCLC 9 | +| `Badge` | `plan` / `nclc` / `neutral` | Plan actuel dans header, niveau NCLC par simulation | +| `Sidebar` | — | Nav desktop (≥ 1024px) | +| `BottomNav` | — | Nav mobile (< 1024px), 4 items max | +| `PageHeader` | — | Greeting + plan pill | +| `SectionHeader` | — | Titre de section + action optionnelle | + +### Règles d'implémentation + +- Chaque primitive **accepte `className`** en plus de ses props typées, pour overrides ponctuels. +- Chaque primitive **expose ses props via un type exporté** (`ButtonProps`, `CardProps`, etc.). +- Aucune primitive ne contient de logique métier ou d'appel API. Elles reçoivent tout par props. +- Les icônes sont passées par une prop `icon` acceptant un `ReactNode`, jamais par nom de string. + +--- + +## 5. Layout dashboard — spécification + +Les primitives ci-dessus s'assemblent dans `src/features/dashboard/` et `src/pages/dashboard/`. + +### Structure sémantique + +``` + + (≥ 1024px) +
+ (greeting + plan) +
+ (NCLC estimé + progression) + (simulations restantes) + (dernier score) +
+
+ (< 1024px) + +``` + +### Breakpoints + +| Breakpoint | Comportement | +|---|---| +| `< 1024px` | Mono-colonne, `BottomNav` fixe en bas, padding horizontal 20px | +| `≥ 1024px` | Sidebar 240px + contenu centré 860px max, padding horizontal 32px | +| `≥ 1440px` | Contenu centré 920px max (pas d'élargissement excessif) | + +### Densité verticale + +- Padding vertical section : 24px mobile, 32px desktop. +- Gap inter-cards : 12px mobile, 16px desktop. +- Marge sous `PageHeader` : 20px mobile, 28px desktop. + +--- + +## 6. Données mock — Sprint 1 + +Avant branchement API, fournir les données via `src/shared/api/mock/dashboard.ts`. Données crédibles, françaises, alignées sur l'audience réelle. + +```typescript +export const mockDashboard = { + user: { + firstName: 'Yacine', + plan: 'decouverte' as const, + planLabel: 'Plan Découverte', + }, + metrics: { + nclcEstimated: 7.5, + nclcTarget: 9, + simulationsUsed: 2, + simulationsQuota: 5, + lastScore: { value: 16, max: 20, type: 'ecrit' as const }, + }, + recentSimulations: [ + { id: 's-001', type: 'ecrit', relativeDate: 'il y a 2 jours', score: 16, max: 20, nclc: 8 }, + { id: 's-002', type: 'oral', relativeDate: 'il y a 5 jours', score: 14, max: 20, nclc: 7 }, + { id: 's-003', type: 'ecrit', relativeDate: 'il y a 1 semaine', score: 15, max: 20, nclc: 7 }, + ], + nextStep: { + title: 'Cible une simulation orale cette semaine', + body: 'Ton écrit est solide (NCLC 8). L\'oral reste à consolider pour sécuriser ton NCLC 9.', + action: { label: 'Démarrer Expression Orale', to: '/simulation/orale' }, + }, +} as const; +``` + +**Règles contenu :** +- Aucun "Lorem ipsum", aucune date absolue — relatif uniquement (`il y a X jours`). +- Les prénoms mocks reflètent l'audience : Yacine, Aminata, Kenza, Bilal, Fatou, Kévin (Canada). +- Les scores suivent une progression crédible (pas de 20/20 ni de 5/20). + +--- + +## 7. Accessibilité — plancher Sprint 1 + +- Contraste minimum **WCAG AA** sur tous les couples texte/fond (vérifié pour la palette ci-dessus). +- Tous les éléments interactifs ont un `:focus-visible` avec `--shadow-focus` (halo bleu 3px). +- Les icônes purement décoratives portent `aria-hidden="true"`. +- Les icônes fonctionnelles (sans label visible) portent `aria-label`. +- Les landmarks sémantiques sont utilisés : `
`, `
@@ -208,7 +215,7 @@ export default function DesignSystemPage() {
- {['AB', 'CD', 'EF'].map(initials => ( + {['AB', 'CD', 'EF'].map((initials) => ( {initials} @@ -231,8 +238,8 @@ export default function DesignSystemPage() { Example dialog - This dialog uses Direction H tokens — bg-surface, border-line, text-ink-4. - Toggle the theme to see it adapt. + This dialog uses Direction H tokens — bg-surface, border-line, text-ink-4. Toggle + the theme to see it adapt. @@ -242,7 +249,6 @@ export default function DesignSystemPage() {
-
) } diff --git a/src/index.css b/src/index.css index 558ede0..17e6743 100644 --- a/src/index.css +++ b/src/index.css @@ -5,50 +5,50 @@ @theme { /* ─── Typographie ───────────────────────────────────────────── */ - --font-sans: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", - system-ui, sans-serif; + --font-sans: + 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; /* ─── Fonds ─────────────────────────────────────────────────── */ /* bg-canvas = fond de page (jamais pur blanc) */ /* bg-surface = cards — ressortent sur le canvas */ - --color-canvas: #EEF2F8; - --color-canvas-2: #E6EBF4; - --color-surface: #FFFFFF; - --color-surface-hover: #F8FAFD; + --color-canvas: #eef2f8; + --color-canvas-2: #e6ebf4; + --color-surface: #ffffff; + --color-surface-hover: #f8fafd; /* ─── Hairlines ──────────────────────────────────────────────── */ - --color-line: #DDE3ED; - --color-line-strong: #C7D0E0; + --color-line: #dde3ed; + --color-line-strong: #c7d0e0; /* ─── Encres ─────────────────────────────────────────────────── */ - --color-ink-1: #0F172A; - --color-ink-2: #1E293B; + --color-ink-1: #0f172a; + --color-ink-2: #1e293b; --color-ink-3: #475569; - --color-ink-4: #64748B; - --color-ink-5: #94A3B8; + --color-ink-4: #64748b; + --color-ink-5: #94a3b8; /* ─── Brand Expria ───────────────────────────────────────────── */ - --color-expria: #1B4FD8; - --color-expria-hover: #1741B8; - --color-expria-50: #EEF3FF; - --color-expria-100: #DCE6FF; - --color-expria-200: #B8CDFF; - --color-deep: #0B1F5C; - --color-deep-2: #142B6E; + --color-expria: #1b4fd8; + --color-expria-hover: #1741b8; + --color-expria-50: #eef3ff; + --color-expria-100: #dce6ff; + --color-expria-200: #b8cdff; + --color-deep: #0b1f5c; + --color-deep-2: #142b6e; /* ─── Sémantiques ────────────────────────────────────────────── */ - --color-success: #0E9F6E; - --color-success-bg: #E6F6F0; - --color-warning: #C77A00; - --color-warning-bg: #FEF3E2; - --color-danger: #C53030; - --color-danger-bg: #FDECEC; + --color-success: #0e9f6e; + --color-success-bg: #e6f6f0; + --color-warning: #c77a00; + --color-warning-bg: #fef3e2; + --color-danger: #c53030; + --color-danger-bg: #fdecec; /* ─── Rayons (override des defaults Tailwind) ────────────────── */ - --radius-sm: 6px; - --radius-md: 10px; - --radius-lg: 14px; - --radius-xl: 18px; + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --radius-xl: 18px; --radius-full: 999px; /* ─── Ombres (light mode) ────────────────────────────────────── */ @@ -60,35 +60,35 @@ /* ─── Dark mode — override des tokens couleur et ombres ──────────── */ .dark { /* Fonds */ - --color-canvas: #0D1220; - --color-canvas-2: #121A2D; - --color-surface: #182238; - --color-surface-hover: #1E2A42; + --color-canvas: #0d1220; + --color-canvas-2: #121a2d; + --color-surface: #182238; + --color-surface-hover: #1e2a42; /* Hairlines */ - --color-line: #27324B; - --color-line-strong: #364363; + --color-line: #27324b; + --color-line-strong: #364363; /* Encres */ - --color-ink-1: #F1F4FA; - --color-ink-2: #DDE3EF; - --color-ink-3: #A8B2C7; - --color-ink-4: #7A8499; - --color-ink-5: #525C73; + --color-ink-1: #f1f4fa; + --color-ink-2: #dde3ef; + --color-ink-3: #a8b2c7; + --color-ink-4: #7a8499; + --color-ink-5: #525c73; /* Brand — remonté en luminance pour rester lisible sur fond sombre */ - --color-expria: #5B7FFF; - --color-expria-hover: #6F8EFF; - --color-expria-50: rgba(91, 127, 255, 0.12); - --color-deep: #060B1A; + --color-expria: #5b7fff; + --color-expria-hover: #6f8eff; + --color-expria-50: rgba(91, 127, 255, 0.12); + --color-deep: #060b1a; /* Sémantiques */ - --color-success: #3DD68C; + --color-success: #3dd68c; --color-success-bg: rgba(61, 214, 140, 0.12); - --color-warning: #F5B849; + --color-warning: #f5b849; --color-warning-bg: rgba(245, 184, 73, 0.12); - --color-danger: #F06B6B; - --color-danger-bg: rgba(240, 107, 107, 0.12); + --color-danger: #f06b6b; + --color-danger-bg: rgba(240, 107, 107, 0.12); /* Ombres — jouer sur les surfaces, pas les ombres claires */ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); diff --git a/src/shared/components/Logo.tsx b/src/shared/components/Logo.tsx index f1573d5..dd31c3e 100644 --- a/src/shared/components/Logo.tsx +++ b/src/shared/components/Logo.tsx @@ -24,7 +24,7 @@ export function Logo({ size = 'md', variant = 'full', className }: LogoProps) {