From 5d12c89c4f899a5baf17ec9cbaae47c21e9d3c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=97=D0=B8=D0=BC=D0=B8=D0=BD?= Date: Sun, 10 May 2026 21:47:24 +0000 Subject: [PATCH] feat(admin-ui): shadcn primitives + @/ui barrel (Stage 2.1) --- ordinis-admin-ui/package.json | 17 +- ordinis-admin-ui/pnpm-lock.yaml | 1035 +++++++++++++++++ ordinis-admin-ui/public/favicon.svg | 19 +- ordinis-admin-ui/src/auth/TokenSync.tsx | 19 +- .../src/components/layout/ThemeSwitch.tsx | 57 + ordinis-admin-ui/src/i18n.ts | 10 + ordinis-admin-ui/src/lib/utils.ts | 12 + ordinis-admin-ui/src/main.tsx | 21 +- ordinis-admin-ui/src/routes/__root.tsx | 13 + ordinis-admin-ui/src/stores/ThemeProvider.tsx | 101 ++ ordinis-admin-ui/src/styles.css | 189 ++- ordinis-admin-ui/src/ui/components/alert.tsx | 39 + ordinis-admin-ui/src/ui/components/badge.tsx | 27 + ordinis-admin-ui/src/ui/components/button.tsx | 86 ++ ordinis-admin-ui/src/ui/components/cap.tsx | 22 + ordinis-admin-ui/src/ui/components/card.tsx | 59 + .../src/ui/components/checkbox.tsx | 36 + ordinis-admin-ui/src/ui/components/dialog.tsx | 138 +++ .../src/ui/components/dropdown-menu.tsx | 162 +++ .../src/ui/components/empty-state.tsx | 36 + .../src/ui/components/form-actions.tsx | 22 + .../src/ui/components/icon-button.tsx | 54 + ordinis-admin-ui/src/ui/components/input.tsx | 30 + ordinis-admin-ui/src/ui/components/label.tsx | 20 + .../src/ui/components/loading-block.tsx | 27 + .../src/ui/components/page-header.tsx | 33 + .../src/ui/components/popover.tsx | 30 + ordinis-admin-ui/src/ui/components/scope.tsx | 81 ++ .../src/ui/components/search-input.tsx | 44 + .../src/ui/components/separator.tsx | 22 + ordinis-admin-ui/src/ui/components/sheet.tsx | 133 +++ .../src/ui/components/skeleton.tsx | 16 + ordinis-admin-ui/src/ui/components/switch.tsx | 29 + ordinis-admin-ui/src/ui/components/tabs.tsx | 62 + .../src/ui/components/tooltip.tsx | 28 + ordinis-admin-ui/src/ui/index.ts | 125 ++ 36 files changed, 2826 insertions(+), 28 deletions(-) create mode 100644 ordinis-admin-ui/src/components/layout/ThemeSwitch.tsx create mode 100644 ordinis-admin-ui/src/lib/utils.ts create mode 100644 ordinis-admin-ui/src/stores/ThemeProvider.tsx create mode 100644 ordinis-admin-ui/src/ui/components/alert.tsx create mode 100644 ordinis-admin-ui/src/ui/components/badge.tsx create mode 100644 ordinis-admin-ui/src/ui/components/button.tsx create mode 100644 ordinis-admin-ui/src/ui/components/cap.tsx create mode 100644 ordinis-admin-ui/src/ui/components/card.tsx create mode 100644 ordinis-admin-ui/src/ui/components/checkbox.tsx create mode 100644 ordinis-admin-ui/src/ui/components/dialog.tsx create mode 100644 ordinis-admin-ui/src/ui/components/dropdown-menu.tsx create mode 100644 ordinis-admin-ui/src/ui/components/empty-state.tsx create mode 100644 ordinis-admin-ui/src/ui/components/form-actions.tsx create mode 100644 ordinis-admin-ui/src/ui/components/icon-button.tsx create mode 100644 ordinis-admin-ui/src/ui/components/input.tsx create mode 100644 ordinis-admin-ui/src/ui/components/label.tsx create mode 100644 ordinis-admin-ui/src/ui/components/loading-block.tsx create mode 100644 ordinis-admin-ui/src/ui/components/page-header.tsx create mode 100644 ordinis-admin-ui/src/ui/components/popover.tsx create mode 100644 ordinis-admin-ui/src/ui/components/scope.tsx create mode 100644 ordinis-admin-ui/src/ui/components/search-input.tsx create mode 100644 ordinis-admin-ui/src/ui/components/separator.tsx create mode 100644 ordinis-admin-ui/src/ui/components/sheet.tsx create mode 100644 ordinis-admin-ui/src/ui/components/skeleton.tsx create mode 100644 ordinis-admin-ui/src/ui/components/switch.tsx create mode 100644 ordinis-admin-ui/src/ui/components/tabs.tsx create mode 100644 ordinis-admin-ui/src/ui/components/tooltip.tsx create mode 100644 ordinis-admin-ui/src/ui/index.ts diff --git a/ordinis-admin-ui/package.json b/ordinis-admin-ui/package.json index e9ad732..7348405 100644 --- a/ordinis-admin-ui/package.json +++ b/ordinis-admin-ui/package.json @@ -14,21 +14,36 @@ "@hookform/resolvers": "^3.9.1", "@nstart/ui": "^0.1.3", "@phosphor-icons/react": "^2.1.10", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.62.0", "@tanstack/react-router": "^1.86.0", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "axios": "^1.7.9", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "i18next": "^26.0.3", "i18next-browser-languagedetector": "^8.0.2", "leaflet": "^1.9.4", + "lucide-react": "^1.14.0", "oidc-client-ts": "^3.5.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-hook-form": "^7.54.2", "react-i18next": "^17.0.2", "react-leaflet": "^5.0.0", - "react-oidc-context": "^3.3.1" + "react-oidc-context": "^3.3.1", + "tailwind-merge": "^3.6.0", + "tw-animate-css": "^1.4.0" }, "devDependencies": { "@tailwindcss/vite": "^4.0.0-beta.7", diff --git a/ordinis-admin-ui/pnpm-lock.yaml b/ordinis-admin-ui/pnpm-lock.yaml index 7217219..c61935d 100644 --- a/ordinis-admin-ui/pnpm-lock.yaml +++ b/ordinis-admin-ui/pnpm-lock.yaml @@ -17,6 +17,36 @@ importers: '@phosphor-icons/react': specifier: ^2.1.10 version: 2.1.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-checkbox': + specifier: ^1.3.3 + version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-label': + specifier: ^2.1.8 + version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-separator': + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-switch': + specifier: ^1.2.6 + version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-tabs': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tanstack/react-query': specifier: ^5.62.0 version: 5.100.9(react@19.2.5) @@ -32,6 +62,12 @@ importers: axios: specifier: ^1.7.9 version: 1.16.0 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 i18next: specifier: ^26.0.3 version: 26.0.8(typescript@5.9.3) @@ -41,6 +77,9 @@ importers: leaflet: specifier: ^1.9.4 version: 1.9.4 + lucide-react: + specifier: ^1.14.0 + version: 1.14.0(react@19.2.5) oidc-client-ts: specifier: ^3.5.0 version: 3.5.0 @@ -62,6 +101,12 @@ importers: react-oidc-context: specifier: ^3.3.1 version: 3.3.1(oidc-client-ts@3.5.0)(react@19.2.5) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 devDependencies: '@tailwindcss/vite': specifier: ^4.0.0-beta.7 @@ -568,6 +613,21 @@ packages: '@noble/hashes': optional: true + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@hookform/resolvers@3.10.0': resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==} peerDependencies: @@ -605,6 +665,420 @@ packages: react: '>= 16.8' react-dom: '>= 16.8' + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + 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 + + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + 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 + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + 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 + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + 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 + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + 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 + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + 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 + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + 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 + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.8': + resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} + 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 + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + 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 + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + 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 + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + 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 + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + 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 + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + 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 + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + 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 + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + 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 + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + 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 + + '@radix-ui/react-separator@1.1.8': + resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} + 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 + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + 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 + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + 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 + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + 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 + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + 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 + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@react-leaflet/core@3.0.0': resolution: {integrity: sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==} peerDependencies: @@ -1042,6 +1516,10 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -1106,6 +1584,13 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1158,6 +1643,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -1274,6 +1762,10 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -1478,6 +1970,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@1.14.0: + resolution: {integrity: sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -1616,6 +2113,36 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + 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 + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + 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 + react@19.2.5: resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} engines: {node: '>=0.10.0'} @@ -1678,6 +2205,9 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + tailwindcss@4.2.4: resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} @@ -1729,6 +2259,9 @@ packages: tslib@2.3.0: resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1748,6 +2281,26 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + 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 + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + 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 + use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -2223,6 +2776,23 @@ snapshots: '@exodus/bytes@1.15.0': {} + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@floating-ui/utils@0.2.11': {} + '@hookform/resolvers@3.10.0(react-hook-form@7.75.0(react@19.2.5))': dependencies: react-hook-form: 7.75.0(react@19.2.5) @@ -2262,6 +2832,407 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + aria-hidden: 1.2.6 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) + aria-hidden: 1.2.6 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + aria-hidden: 1.2.6 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/rect': 1.1.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/rect@1.1.1': {} + '@react-leaflet/core@3.0.0(leaflet@1.9.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: leaflet: 1.9.4 @@ -2655,6 +3626,10 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.2 + aria-hidden@1.2.6: + dependencies: + tslib: 2.3.0 + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -2733,6 +3708,12 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -2771,6 +3752,8 @@ snapshots: detect-libc@2.1.2: {} + detect-node-es@1.1.0: {} + diff@8.0.4: {} dom-accessibility-api@0.5.16: {} @@ -2919,6 +3902,8 @@ snapshots: hasown: 2.0.3 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -3085,6 +4070,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@1.14.0(react@19.2.5): + dependencies: + react: 19.2.5 + lz-string@1.5.0: {} magic-string@0.30.21: @@ -3185,6 +4174,33 @@ snapshots: react-refresh@0.17.0: {} + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.5): + dependencies: + react: 19.2.5 + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.5) + tslib: 2.3.0 + optionalDependencies: + '@types/react': 19.2.14 + + react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.5): + dependencies: + react: 19.2.5 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.5) + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.5) + tslib: 2.3.0 + use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.5) + use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + + react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.5): + dependencies: + get-nonce: 1.0.1 + react: 19.2.5 + tslib: 2.3.0 + optionalDependencies: + '@types/react': 19.2.14 + react@19.2.5: {} readdirp@3.6.0: @@ -3257,6 +4273,8 @@ snapshots: symbol-tree@3.2.4: {} + tailwind-merge@3.6.0: {} + tailwindcss@4.2.4: {} tapable@2.3.3: {} @@ -3296,6 +4314,8 @@ snapshots: tslib@2.3.0: {} + tw-animate-css@1.4.0: {} + typescript@5.9.3: {} undici@7.25.0: {} @@ -3312,6 +4332,21 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.5): + dependencies: + react: 19.2.5 + tslib: 2.3.0 + optionalDependencies: + '@types/react': 19.2.14 + + use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.5): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.5 + tslib: 2.3.0 + optionalDependencies: + '@types/react': 19.2.14 + use-sync-external-store@1.6.0(react@19.2.5): dependencies: react: 19.2.5 diff --git a/ordinis-admin-ui/public/favicon.svg b/ordinis-admin-ui/public/favicon.svg index 93f34f9..2493ef6 100644 --- a/ordinis-admin-ui/public/favicon.svg +++ b/ordinis-admin-ui/public/favicon.svg @@ -1,10 +1,11 @@ - - - - - - - - - + + + + + + + diff --git a/ordinis-admin-ui/src/auth/TokenSync.tsx b/ordinis-admin-ui/src/auth/TokenSync.tsx index 0863936..f836685 100644 --- a/ordinis-admin-ui/src/auth/TokenSync.tsx +++ b/ordinis-admin-ui/src/auth/TokenSync.tsx @@ -25,14 +25,29 @@ export function TokenSync() { const redirecting = useRef(false) // 1) Token sync — single update path к accessToken holder в client.ts. + // ВАЖНО: пропускаем токен ТОЛЬКО когда user реально authenticated AND token + // не expired. Раньше передавали raw {@code auth.user?.access_token} — это + // включало случаи когда oidc-client-ts вернул user object с уже expired + // token'ом (silent renew failed но user object всё ещё в state). axios + // отправлял Bearer expired_token → backend (с permissive=false для guest + // anyway re-validates JWT через oauth2ResourceServer) → 401 → catalog UI + // показывал «Не удалось загрузить данные» вместо PUBLIC dicts. + // + // user.expired property из oidc-client-ts: true если access_token expiry + // прошла. Дополнительно проверяем isAuthenticated — react-oidc-context + // обновляет это в реальном времени. useEffect(() => { - setAccessToken(auth.user?.access_token ?? null) + const liveToken = + auth.isAuthenticated && auth.user && !auth.user.expired + ? auth.user.access_token + : null + setAccessToken(liveToken ?? null) // Cleanup legacy localStorage key (was used до fix #12 + #2/3/13 cascade). // Идемпотентный no-op если ключа нет. if (typeof window !== 'undefined') { window.localStorage.removeItem(LEGACY_TOKEN_STORAGE_KEY) } - }, [auth.user?.access_token]) + }, [auth.isAuthenticated, auth.user, auth.user?.access_token, auth.user?.expired]) // 2) 401 handler — registered once на app mount, делегирует silent renew / // redirect на Keycloak. Guard'ы: diff --git a/ordinis-admin-ui/src/components/layout/ThemeSwitch.tsx b/ordinis-admin-ui/src/components/layout/ThemeSwitch.tsx new file mode 100644 index 0000000..65158cf --- /dev/null +++ b/ordinis-admin-ui/src/components/layout/ThemeSwitch.tsx @@ -0,0 +1,57 @@ +import { useTranslation } from 'react-i18next' +import { Monitor, Moon, Sun } from 'lucide-react' +import { useTheme, type ThemePreference } from '@/stores/ThemeProvider' +import { cn } from '@/lib/utils' + +/** + * Tri-state theme switch: Light / Dark / System. + * + *

Segmented control с тремя иконками. Активный — `--color-accent` + * background + `--color-on-accent` foreground. Click меняет `preference` + * в {@link ThemeProvider}, который пишет в localStorage и переключает + * `data-theme` на html. + * + *

System icon показывает «follow OS» — auto-update при OS theme change. + */ +export function ThemeSwitch() { + const { t } = useTranslation() + const { preference, setPreference } = useTheme() + + const items: { id: ThemePreference; label: string; icon: typeof Sun }[] = [ + { id: 'light', label: t('theme.light'), icon: Sun }, + { id: 'dark', label: t('theme.dark'), icon: Moon }, + { id: 'system', label: t('theme.system'), icon: Monitor }, + ] + + return ( +

+ {items.map((item) => { + const Icon = item.icon + const active = preference === item.id + return ( + + ) + })} +
+ ) +} diff --git a/ordinis-admin-ui/src/i18n.ts b/ordinis-admin-ui/src/i18n.ts index 439be6a..edc48c8 100644 --- a/ordinis-admin-ui/src/i18n.ts +++ b/ordinis-admin-ui/src/i18n.ts @@ -13,6 +13,11 @@ i18n 'ru-RU': { translation: { 'app.title': 'Ordinis', + 'theme.label': 'Тема', + 'theme.light': 'Светлая', + 'theme.dark': 'Тёмная', + 'theme.system': 'Системная', + 'nav.docs': 'Руководство', 'updateBanner.message': 'Доступна новая версия НСИ ({{version}}). Обновите страницу, чтобы получить последние исправления.', 'updateBanner.reload': 'Обновить', 'updateBanner.docs': 'Что нового', @@ -513,6 +518,11 @@ i18n 'en-US': { translation: { 'app.title': 'Ordinis MDM', + 'theme.label': 'Theme', + 'theme.light': 'Light', + 'theme.dark': 'Dark', + 'theme.system': 'System', + 'nav.docs': 'Docs', 'updateBanner.message': 'A new NSI version ({{version}}) is available. Reload to get the latest fixes.', 'updateBanner.reload': 'Reload', 'updateBanner.docs': "What's new", diff --git a/ordinis-admin-ui/src/lib/utils.ts b/ordinis-admin-ui/src/lib/utils.ts new file mode 100644 index 0000000..c0ef9f0 --- /dev/null +++ b/ordinis-admin-ui/src/lib/utils.ts @@ -0,0 +1,12 @@ +import { type ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +/** + * Tailwind class merger — combines clsx (conditional classes) + tailwind-merge + * (resolves conflicts: e.g. `px-2 px-4` → `px-4`). + * + * Standard shadcn/ui utility, used by every primitive variant. + */ +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/ordinis-admin-ui/src/main.tsx b/ordinis-admin-ui/src/main.tsx index 7b22139..287bab3 100644 --- a/ordinis-admin-ui/src/main.tsx +++ b/ordinis-admin-ui/src/main.tsx @@ -8,6 +8,7 @@ import { routeTree } from './routeTree.gen' import { oidcAuthConfig } from '@/auth/oidcConfig' import { TokenSync } from '@/auth/TokenSync' import { RequireAuth } from '@/auth/RequireAuth' +import { ThemeProvider } from '@/stores/ThemeProvider' import './i18n' import './styles.css' @@ -29,15 +30,17 @@ createRoot(document.getElementById('root')!).render( - - - - - - - - - + + + + + + + + + + + , ) diff --git a/ordinis-admin-ui/src/routes/__root.tsx b/ordinis-admin-ui/src/routes/__root.tsx index 77bcc6f..31ddb23 100644 --- a/ordinis-admin-ui/src/routes/__root.tsx +++ b/ordinis-admin-ui/src/routes/__root.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next' import { LanguageSwitch } from '@nstart/ui' import { AuthBadge } from '@/auth/AuthBadge' import { useCanMutate } from '@/auth/useCanMutate' +import { ThemeSwitch } from '@/components/layout/ThemeSwitch' import { UpdateBanner } from '@/components/version/UpdateBanner' import { VersionBadge } from '@/components/version/VersionBadge' @@ -46,6 +47,17 @@ function RootLayout() { > {t('nav.search')} + {/* Документация — внешняя страница (Docusaurus → ordinis-docs + deployment монтируется на /docs ingress'ом). target=_blank + чтобы не терять контекст приложения. */} + + {t('nav.docs')} + {canMutate && ( <>
+ Persist'ится в {@code localStorage[STORAGE_KEY]}. На mount читает saved + * preference или fallback'ает к 'system'. Меняет {@code data-theme} attr на + * {@code } → CSS custom properties в styles.css переключают палитру. + * + *

{@code system} mode — если user ничего не выбрал, следим за prefers-color-scheme + * media query. matchMedia listener обновляет attr реально-time при OS theme change. + */ + +const STORAGE_KEY = 'ord-theme' + +export type ThemePreference = 'light' | 'dark' | 'system' +export type ResolvedTheme = 'light' | 'dark' + +type ThemeContextValue = { + preference: ThemePreference + resolved: ResolvedTheme + setPreference: (next: ThemePreference) => void +} + +const ThemeContext = createContext(null) + +function readSystemPreference(): ResolvedTheme { + if (typeof window === 'undefined') return 'light' + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' +} + +function readSavedPreference(): ThemePreference { + if (typeof window === 'undefined') return 'system' + const raw = window.localStorage.getItem(STORAGE_KEY) + if (raw === 'light' || raw === 'dark' || raw === 'system') return raw + return 'system' +} + +function applyToDom(theme: ResolvedTheme) { + if (typeof document === 'undefined') return + document.documentElement.setAttribute('data-theme', theme) + // shadcn convention — добавляем .dark class тоже на случай third-party + // компонентов которые ждут именно его. Custom variant в styles.css всё равно + // их связывает, но эта мера держит совместимость на корню. + if (theme === 'dark') { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } + // чтобы system browser UI (scrollbars, autofill) + // был в гармонии с темой. + let meta = document.querySelector('meta[name="color-scheme"]') + if (!meta) { + meta = document.createElement('meta') + meta.name = 'color-scheme' + document.head.appendChild(meta) + } + meta.content = theme +} + +export function ThemeProvider({ children }: { children: ReactNode }) { + const [preference, setPreferenceState] = useState(() => readSavedPreference()) + const [systemResolved, setSystemResolved] = useState(() => readSystemPreference()) + + // Слушаем OS theme change при mode='system'. + useEffect(() => { + if (typeof window === 'undefined') return + const mq = window.matchMedia('(prefers-color-scheme: dark)') + const handler = () => setSystemResolved(mq.matches ? 'dark' : 'light') + mq.addEventListener('change', handler) + return () => mq.removeEventListener('change', handler) + }, []) + + const resolved: ResolvedTheme = preference === 'system' ? systemResolved : preference + + // Применяем к DOM каждый раз когда resolved меняется. + useEffect(() => { + applyToDom(resolved) + }, [resolved]) + + const setPreference = (next: ThemePreference) => { + setPreferenceState(next) + if (typeof window !== 'undefined') { + window.localStorage.setItem(STORAGE_KEY, next) + } + } + + return ( + + {children} + + ) +} + +export function useTheme() { + const ctx = useContext(ThemeContext) + if (!ctx) { + throw new Error('useTheme must be used inside ') + } + return ctx +} diff --git a/ordinis-admin-ui/src/styles.css b/ordinis-admin-ui/src/styles.css index 26685ea..9b4c9d9 100644 --- a/ordinis-admin-ui/src/styles.css +++ b/ordinis-admin-ui/src/styles.css @@ -1,12 +1,187 @@ -@import "tailwindcss"; -@import "@nstart/ui/styles/fonts.css"; -@import "@nstart/ui/styles/theme.css"; +@import 'tailwindcss'; +@import 'tw-animate-css'; +@import '@nstart/ui/styles/fonts.css'; +@import '@nstart/ui/styles/theme.css'; /* Без @source Tailwind v4 не сгенерирует CSS для классов внутри dist/@nstart/ui */ -@source "../node_modules/@nstart/ui/dist"; +@source '../node_modules/@nstart/ui/dist'; -html, -body, -#root { +/* Google Fonts — Inter / JetBrains Mono / Tektur (handoff spec). + * @nstart fonts.css дублируется по части семейств — overlap безвреден, + * на финальной фазе миграции (когда удалим @nstart/ui) останется только это. */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Tektur:wght@500;600;700&display=swap'); + +/* === Dark mode trigger === + * shadcn использует .dark class по умолчанию. Handoff просит [data-theme="dark"] + * на . Custom variant связывает оба пути — компоненты с `dark:` modifier + * срабатывают при любом из условий. */ +@custom-variant dark (&:where([data-theme='dark'], [data-theme='dark'] *)); + +/* === DESIGN TOKENS — Light theme (Earth, default :root) === */ +:root { + --color-ink: #1a1714; + --color-ink-2: #52483d; + --color-mute: #8c8276; + --color-bg: #fbf8ee; + --color-surface: #ffffff; + --color-surface-2: #f4eedc; + --color-line: #e3dccb; + --color-line-2: #efe9d8; + --color-navy: #3a2818; + --color-accent: #b05a2e; + --color-accent-bg: rgb(176 90 46 / 11%); + --color-on-accent: #ffffff; + --color-green: #4f7038; + --color-green-bg: #e4ead0; + --color-warn: #a8762a; + --color-warn-bg: #f6e8bc; + --color-pink: #a64636; + --color-pink-bg: #f4d9cd; + + /* shadcn semantic tokens — re-mapped onto наши значения */ + --background: var(--color-bg); + --foreground: var(--color-ink); + --card: var(--color-surface); + --card-foreground: var(--color-ink); + --popover: var(--color-surface); + --popover-foreground: var(--color-ink); + --primary: var(--color-navy); + --primary-foreground: var(--color-on-accent); + --secondary: var(--color-surface-2); + --secondary-foreground: var(--color-ink); + --muted: var(--color-surface-2); + --muted-foreground: var(--color-mute); + --accent: var(--color-accent); + --accent-foreground: var(--color-on-accent); + --destructive: var(--color-pink); + --destructive-foreground: var(--color-on-accent); + --border: var(--color-line); + --input: var(--color-line); + --ring: var(--color-accent); +} + +[data-theme='dark'] { + --color-ink: #f0ece4; + --color-ink-2: #c8c1b3; + --color-mute: #8a8474; + --color-bg: #1c1a17; + --color-surface: #262421; + --color-surface-2: #2e2b27; + --color-line: #3a3733; + --color-line-2: #2d2a27; + --color-navy: #d97757; + --color-accent: #d97757; + --color-accent-bg: rgb(217 119 87 / 16%); + --color-on-accent: #1c1a17; + --color-green: #6fa97d; + --color-green-bg: rgb(111 169 125 / 18%); + --color-warn: #d4a653; + --color-warn-bg: rgb(212 166 83 / 18%); + --color-pink: #d18272; + --color-pink-bg: rgb(209 130 114 / 20%); + + --background: var(--color-bg); + --foreground: var(--color-ink); + --card: var(--color-surface); + --card-foreground: var(--color-ink); + --popover: var(--color-surface); + --popover-foreground: var(--color-ink); + --primary: var(--color-navy); + --primary-foreground: var(--color-on-accent); + --secondary: var(--color-surface-2); + --secondary-foreground: var(--color-ink); + --muted: var(--color-surface-2); + --muted-foreground: var(--color-mute); + --accent: var(--color-accent); + --accent-foreground: var(--color-on-accent); + --destructive: var(--color-pink); + --destructive-foreground: var(--color-on-accent); + --border: var(--color-line); + --input: var(--color-line); + --ring: var(--color-accent); +} + +/* Tailwind v4 @theme inline — экспонирует CSS vars как utility classes + * (`bg-bg`, `text-ink`, `border-line`, `bg-accent` и т.д.). */ +@theme inline { + --color-bg: var(--color-bg); + --color-surface: var(--color-surface); + --color-surface-2: var(--color-surface-2); + --color-ink: var(--color-ink); + --color-ink-2: var(--color-ink-2); + --color-mute: var(--color-mute); + --color-line: var(--color-line); + --color-line-2: var(--color-line-2); + --color-navy: var(--color-navy); + --color-accent: var(--color-accent); + --color-accent-bg: var(--color-accent-bg); + --color-on-accent: var(--color-on-accent); + --color-green: var(--color-green); + --color-green-bg: var(--color-green-bg); + --color-warn: var(--color-warn); + --color-warn-bg: var(--color-warn-bg); + --color-pink: var(--color-pink); + --color-pink-bg: var(--color-pink-bg); + + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + --font-mono: 'JetBrains Mono', 'SF Mono', monospace; + --font-display: 'Tektur', 'Inter', sans-serif; + + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; +} + +html, body, #root { height: 100%; } + +body { + background: var(--color-bg); + color: var(--color-ink); + font-family: var(--font-sans); + font-feature-settings: 'ss01', 'cv11', 'cv02', 'cv03', 'cv04'; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +/* Tektur uppercase caption helper — handoff .cap class */ +.cap { + font-family: var(--font-display); + font-size: 10.5px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.10em; + color: var(--color-mute); +} + +/* === Legacy fallback: старые компоненты используют nstart token names + * (--color-carbon, --color-ultramarain etc). Маппим их на новые pending + * полная миграция. === */ +:root, +[data-theme='dark'] { + --color-carbon: var(--color-ink); + --color-ultramarain: var(--color-accent); + --color-orbit: var(--color-warn); + --color-regolith: var(--color-line); + --color-asteroid: var(--color-mute); +} diff --git a/ordinis-admin-ui/src/ui/components/alert.tsx b/ordinis-admin-ui/src/ui/components/alert.tsx new file mode 100644 index 0000000..fdae404 --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/alert.tsx @@ -0,0 +1,39 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' +import { cn } from '@/lib/utils' + +/** + * Alert / inline message — статусная карточка с border-left accent. + * shadcn-style API: children может быть [Title, Description] или произвольный. + */ +const alertVariants = cva( + 'relative w-full rounded-md border-l-4 px-4 py-3 text-sm [&>svg]:text-ink-2 [&>svg]:size-4 [&>svg]:absolute [&>svg]:left-3 [&>svg]:top-3 [&>svg+*]:pl-6', + { + variants: { + variant: { + info: 'border-l-accent bg-accent-bg text-ink', + success: 'border-l-green bg-green-bg text-ink', + warning: 'border-l-warn bg-warn-bg text-ink', + danger: 'border-l-pink bg-pink-bg text-ink', + neutral: 'border-l-line bg-surface-2 text-ink', + }, + }, + defaultVariants: { variant: 'info' }, + }, +) + +export type AlertProps = React.HTMLAttributes & + VariantProps & { + title?: React.ReactNode + } + +export const Alert = React.forwardRef( + function Alert({ className, variant, title, children, ...props }, ref) { + return ( +

+ {title &&
{title}
} +
{children}
+
+ ) + }, +) diff --git a/ordinis-admin-ui/src/ui/components/badge.tsx b/ordinis-admin-ui/src/ui/components/badge.tsx new file mode 100644 index 0000000..86e7f3f --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/badge.tsx @@ -0,0 +1,27 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-2xs font-medium font-mono uppercase tracking-wide whitespace-nowrap', + { + variants: { + variant: { + default: 'bg-surface-2 text-ink-2 border border-line', + accent: 'bg-accent-bg text-accent', + success: 'bg-green-bg text-green', + warning: 'bg-warn-bg text-warn', + danger: 'bg-pink-bg text-pink', + outline: 'border border-line text-ink-2', + }, + }, + defaultVariants: { variant: 'default' }, + }, +) + +export type BadgeProps = React.HTMLAttributes & + VariantProps + +export function Badge({ className, variant, ...props }: BadgeProps) { + return +} diff --git a/ordinis-admin-ui/src/ui/components/button.tsx b/ordinis-admin-ui/src/ui/components/button.tsx new file mode 100644 index 0000000..fe693af --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/button.tsx @@ -0,0 +1,86 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' +import { cn } from '@/lib/utils' + +/** + * Button — shadcn-стиль, адаптирован под наш token palette. + * + * Variants per design handoff: + * - primary — navy bg + on-accent text (deep coffee → cream). + * - secondary — surface bg + ink text + line border (default outlined). + * - ghost — transparent + hover:surface-2. + * - danger — pink bg + on-accent text (destructive actions). + * - link — accent text + underline-offset, no bg. + * + * Sizes: sm (28px), md (36px default), lg (40px), icon-sm (24px square), + * icon (32px square). + * + * Use {@code asChild} (Radix Slot pattern) для рендера кастомного root + * элемента — например {@code }. + */ +const buttonVariants = cva( + [ + 'inline-flex items-center justify-center gap-2 whitespace-nowrap', + 'rounded-md text-sm font-medium transition-colors', + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg', + 'disabled:pointer-events-none disabled:opacity-50', + '[&_svg]:pointer-events-none [&_svg]:shrink-0', + ].join(' '), + { + variants: { + variant: { + primary: + 'bg-navy text-on-accent hover:opacity-90 active:opacity-95', + secondary: + 'border border-line bg-surface text-ink hover:bg-surface-2', + ghost: + 'text-ink hover:bg-surface-2', + danger: + 'bg-pink text-on-accent hover:opacity-90 active:opacity-95', + link: + 'text-accent underline-offset-4 hover:underline', + }, + size: { + sm: 'h-7 px-2.5 text-xs', + md: 'h-9 px-3.5', + lg: 'h-10 px-4', + 'icon-sm': 'h-6 w-6', + icon: 'h-8 w-8', + }, + }, + defaultVariants: { + variant: 'primary', + size: 'md', + }, + }, +) + +export type ButtonProps = React.ButtonHTMLAttributes & + VariantProps & { + asChild?: boolean + leftIcon?: React.ReactNode + rightIcon?: React.ReactNode + } + +export const Button = React.forwardRef( + function Button( + { className, variant, size, asChild, leftIcon, rightIcon, children, ...props }, + ref, + ) { + const Comp = asChild ? Slot : 'button' + return ( + + {leftIcon} + {children} + {rightIcon} + + ) + }, +) + +export { buttonVariants } diff --git a/ordinis-admin-ui/src/ui/components/cap.tsx b/ordinis-admin-ui/src/ui/components/cap.tsx new file mode 100644 index 0000000..147b348 --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/cap.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' +import { cn } from '@/lib/utils' + +/** + * Cap — Tektur uppercase caption (handoff design system primitive). + * 10.5px / 600 / letter-spacing 0.10em / mute color. + * + * Use для section labels, table headers (cap variant), toolbar group titles. + */ +export type CapProps = React.HTMLAttributes + +export function Cap({ className, ...props }: CapProps) { + return ( + + ) +} diff --git a/ordinis-admin-ui/src/ui/components/card.tsx b/ordinis-admin-ui/src/ui/components/card.tsx new file mode 100644 index 0000000..7c26b08 --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/card.tsx @@ -0,0 +1,59 @@ +import * as React from 'react' +import { cn } from '@/lib/utils' + +/** + * Card / Panel — surface block с radius-card, border-line. Замена для + * {@code @nstart/ui} Panel. Используем сложно через {@link CardHeader}, + * {@link CardContent}, {@link CardFooter}. + */ +export const Card = React.forwardRef>( + function Card({ className, ...props }, ref) { + return ( +
+ ) + }, +) + +export const CardHeader = React.forwardRef>( + function CardHeader({ className, ...props }, ref) { + return
+ }, +) + +export const CardTitle = React.forwardRef>( + function CardTitle({ className, ...props }, ref) { + return ( +

+ ) + }, +) + +export const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(function CardDescription({ className, ...props }, ref) { + return

+}) + +export const CardContent = React.forwardRef>( + function CardContent({ className, ...props }, ref) { + return

+ }, +) + +export const CardFooter = React.forwardRef>( + function CardFooter({ className, ...props }, ref) { + return
+ }, +) diff --git a/ordinis-admin-ui/src/ui/components/checkbox.tsx b/ordinis-admin-ui/src/ui/components/checkbox.tsx new file mode 100644 index 0000000..0d44ce5 --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/checkbox.tsx @@ -0,0 +1,36 @@ +import * as React from 'react' +import * as CheckboxPrimitive from '@radix-ui/react-checkbox' +import { Check, Minus } from 'lucide-react' +import { cn } from '@/lib/utils' + +/** + * Checkbox — Radix primitive с indeterminate support через {@code checked='indeterminate'}. + * Решает старый bug @nstart/ui где indeterminate не sync'ался через ref. + */ +export const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function Checkbox({ className, ...props }, ref) { + return ( + + + {props.checked === 'indeterminate' ? ( + + ) : ( + + )} + + + ) +}) diff --git a/ordinis-admin-ui/src/ui/components/dialog.tsx b/ordinis-admin-ui/src/ui/components/dialog.tsx new file mode 100644 index 0000000..2a15782 --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/dialog.tsx @@ -0,0 +1,138 @@ +import * as React from 'react' +import * as DialogPrimitive from '@radix-ui/react-dialog' +import { X } from 'lucide-react' +import { cn } from '@/lib/utils' + +/** + * Radix Dialog — modal в shadcn-стиле. Заменяет {@code @nstart/ui} Modal. + * + * Использование: + *
+ * 
+ *   
+ *     
+ *       Создать запись
+ *       Описание формы
+ *     
+ *     
+ * + * + * + * + * + *
+ *
+ * + * Default size — 560px (handoff Create-dict). Variants: sm 440px, md 560px, + * lg 720px, xl 880px, full (mobile fullscreen). + */ +export const Dialog = DialogPrimitive.Root +export const DialogTrigger = DialogPrimitive.Trigger +export const DialogPortal = DialogPrimitive.Portal +export const DialogClose = DialogPrimitive.Close + +export const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function DialogOverlay({ className, ...props }, ref) { + return ( + + ) +}) + +const SIZE_CLASSES: Record = { + sm: 'max-w-[440px]', + md: 'max-w-[560px]', + lg: 'max-w-[720px]', + xl: 'max-w-[880px]', + full: 'max-w-full max-h-full sm:max-w-[920px]', +} + +export type DialogContentProps = React.ComponentPropsWithoutRef & { + size?: keyof typeof SIZE_CLASSES + hideClose?: boolean +} + +export const DialogContent = React.forwardRef< + React.ElementRef, + DialogContentProps +>(function DialogContent({ className, children, size = 'md', hideClose, ...props }, ref) { + return ( + + + + {children} + {!hideClose && ( + + + + )} + + + ) +}) + +export function DialogHeader({ className, ...props }: React.HTMLAttributes) { + return
+} + +export function DialogFooter({ className, ...props }: React.HTMLAttributes) { + return ( +
+ ) +} + +export const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function DialogTitle({ className, ...props }, ref) { + return ( + + ) +}) + +export const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function DialogDescription({ className, ...props }, ref) { + return ( + + ) +}) diff --git a/ordinis-admin-ui/src/ui/components/dropdown-menu.tsx b/ordinis-admin-ui/src/ui/components/dropdown-menu.tsx new file mode 100644 index 0000000..19b207b --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/dropdown-menu.tsx @@ -0,0 +1,162 @@ +import * as React from 'react' +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' +import { Check, ChevronRight, Circle } from 'lucide-react' +import { cn } from '@/lib/utils' + +export const DropdownMenu = DropdownMenuPrimitive.Root +export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger +export const DropdownMenuGroup = DropdownMenuPrimitive.Group +export const DropdownMenuPortal = DropdownMenuPrimitive.Portal +export const DropdownMenuSub = DropdownMenuPrimitive.Sub +export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +export const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(function DropdownMenuSubTrigger({ className, inset, children, ...props }, ref) { + return ( + + {children} + + + ) +}) + +export const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function DropdownMenuSubContent({ className, ...props }, ref) { + return ( + + ) +}) + +export const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function DropdownMenuContent({ className, sideOffset = 4, ...props }, ref) { + return ( + + + + ) +}) + +export const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { inset?: boolean } +>(function DropdownMenuItem({ className, inset, ...props }, ref) { + return ( + + ) +}) + +export const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function DropdownMenuCheckboxItem({ className, children, checked, ...props }, ref) { + return ( + + + + + + + {children} + + ) +}) + +export const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function DropdownMenuRadioItem({ className, children, ...props }, ref) { + return ( + + + + + + + {children} + + ) +}) + +export const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { inset?: boolean } +>(function DropdownMenuLabel({ className, inset, ...props }, ref) { + return ( + + ) +}) + +export const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(function DropdownMenuSeparator({ className, ...props }, ref) { + return ( + + ) +}) diff --git a/ordinis-admin-ui/src/ui/components/empty-state.tsx b/ordinis-admin-ui/src/ui/components/empty-state.tsx new file mode 100644 index 0000000..5cf4697 --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/empty-state.tsx @@ -0,0 +1,36 @@ +import * as React from 'react' +import { cn } from '@/lib/utils' + +export type EmptyStateProps = React.HTMLAttributes & { + icon?: React.ReactNode + title: React.ReactNode + description?: React.ReactNode + action?: React.ReactNode +} + +export function EmptyState({ + className, + icon, + title, + description, + action, + ...props +}: EmptyStateProps) { + return ( +
+ {icon &&
{icon}
} +
+
{title}
+ {description &&
{description}
} +
+ {action &&
{action}
} +
+ ) +} diff --git a/ordinis-admin-ui/src/ui/components/form-actions.tsx b/ordinis-admin-ui/src/ui/components/form-actions.tsx new file mode 100644 index 0000000..90de412 --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/form-actions.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' +import { cn } from '@/lib/utils' + +/** + * FormActions — фиксированная row кнопок [Cancel | Submit] в footer форм. + * Reverse на mobile — primary action на bottom (proper UX). + */ +export type FormActionsProps = React.HTMLAttributes + +export function FormActions({ className, children, ...props }: FormActionsProps) { + return ( +
+ {children} +
+ ) +} diff --git a/ordinis-admin-ui/src/ui/components/icon-button.tsx b/ordinis-admin-ui/src/ui/components/icon-button.tsx new file mode 100644 index 0000000..3d9451f --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/icon-button.tsx @@ -0,0 +1,54 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' +import { cn } from '@/lib/utils' + +/** + * IconButton — square button с иконкой. Заменяет {@code @nstart/ui} IconButton. + * Variants: default (ghost), accent (primary fill), danger (destructive). + */ +const iconButtonVariants = cva( + [ + 'inline-flex items-center justify-center rounded-md transition-colors', + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1', + 'disabled:pointer-events-none disabled:opacity-50', + ].join(' '), + { + variants: { + variant: { + default: 'text-ink-2 hover:text-ink hover:bg-surface-2', + accent: 'bg-accent text-on-accent hover:opacity-90', + danger: 'text-pink hover:bg-pink-bg', + }, + size: { + sm: 'h-6 w-6 [&_svg]:size-3.5', + md: 'h-8 w-8 [&_svg]:size-4', + lg: 'h-10 w-10 [&_svg]:size-5', + }, + }, + defaultVariants: { variant: 'default', size: 'md' }, + }, +) + +export type IconButtonProps = React.ButtonHTMLAttributes & + VariantProps & { + /** Required для accessibility — иконки без текста должны иметь label. */ + label: string + icon: React.ReactNode + } + +export const IconButton = React.forwardRef( + function IconButton({ className, variant, size, label, icon, ...props }, ref) { + return ( + + ) + }, +) diff --git a/ordinis-admin-ui/src/ui/components/input.tsx b/ordinis-admin-ui/src/ui/components/input.tsx new file mode 100644 index 0000000..bcb7d43 --- /dev/null +++ b/ordinis-admin-ui/src/ui/components/input.tsx @@ -0,0 +1,30 @@ +import * as React from 'react' +import { cn } from '@/lib/utils' + +/** + * Text input — single-line. Использует наши tokens, focus-ring через accent. + * + * Wrap'итесь в {@code