chore: added dashboard components
d7cdfcd3
25 file(s) · +2590 −0
| 7 | 7 | "@evolu/common": "^6.0.1-preview.19", |
|
| 8 | 8 | "@evolu/react": "^9.0.1-preview.4", |
|
| 9 | 9 | "@evolu/react-web": "^1.0.1-preview.3", |
|
| 10 | + | "@radix-ui/react-avatar": "^1.1.10", |
|
| 11 | + | "@radix-ui/react-collapsible": "^1.1.12", |
|
| 12 | + | "@radix-ui/react-dialog": "^1.1.15", |
|
| 13 | + | "@radix-ui/react-dropdown-menu": "^2.1.16", |
|
| 14 | + | "@radix-ui/react-label": "^2.1.7", |
|
| 15 | + | "@radix-ui/react-popover": "^1.1.15", |
|
| 16 | + | "@radix-ui/react-separator": "^1.1.7", |
|
| 10 | 17 | "@radix-ui/react-slot": "^1.2.3", |
|
| 18 | + | "@radix-ui/react-switch": "^1.2.6", |
|
| 19 | + | "@radix-ui/react-tooltip": "^1.2.8", |
|
| 11 | 20 | "@tailwindcss/vite": "^4.1.16", |
|
| 12 | 21 | "class-variance-authority": "^0.7.1", |
|
| 13 | 22 | "clsx": "^2.1.1", |
|
| 108 | 117 | ||
| 109 | 118 | "@evolu/web": ["@evolu/web@1.0.1-preview.5", "", { "dependencies": { "@sqlite.org/sqlite-wasm": "3.50.4-build1" }, "peerDependencies": { "@evolu/common": "^6.0.1-preview.18" } }, "sha512-3MHTpw7Dm7xgKjp1s9lymVNA+FbobhcsBabs3vohUv26sqF5bl4/tZeVrdORYL+mT9K3TPiGolzi19nZF2N+hQ=="], |
|
| 110 | 119 | ||
| 120 | + | "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], |
|
| 121 | + | ||
| 122 | + | "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], |
|
| 123 | + | ||
| 124 | + | "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.6", "", { "dependencies": { "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw=="], |
|
| 125 | + | ||
| 126 | + | "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], |
|
| 127 | + | ||
| 111 | 128 | "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], |
|
| 112 | 129 | ||
| 113 | 130 | "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], |
|
| 153 | 170 | "@oxc-project/runtime": ["@oxc-project/runtime@0.92.0", "", {}, "sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw=="], |
|
| 154 | 171 | ||
| 155 | 172 | "@oxc-project/types": ["@oxc-project/types@0.93.0", "", {}, "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg=="], |
|
| 173 | + | ||
| 174 | + | "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], |
|
| 175 | + | ||
| 176 | + | "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], |
|
| 177 | + | ||
| 178 | + | "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="], |
|
| 179 | + | ||
| 180 | + | "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], |
|
| 181 | + | ||
| 182 | + | "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], |
|
| 156 | 183 | ||
| 157 | 184 | "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], |
|
| 158 | 185 | ||
| 186 | + | "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], |
|
| 187 | + | ||
| 188 | + | "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], |
|
| 189 | + | ||
| 190 | + | "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], |
|
| 191 | + | ||
| 192 | + | "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], |
|
| 193 | + | ||
| 194 | + | "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], |
|
| 195 | + | ||
| 196 | + | "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], |
|
| 197 | + | ||
| 198 | + | "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], |
|
| 199 | + | ||
| 200 | + | "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "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" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], |
|
| 201 | + | ||
| 202 | + | "@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="], |
|
| 203 | + | ||
| 204 | + | "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], |
|
| 205 | + | ||
| 206 | + | "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], |
|
| 207 | + | ||
| 208 | + | "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], |
|
| 209 | + | ||
| 210 | + | "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], |
|
| 211 | + | ||
| 212 | + | "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], |
|
| 213 | + | ||
| 214 | + | "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], |
|
| 215 | + | ||
| 216 | + | "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], |
|
| 217 | + | ||
| 218 | + | "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], |
|
| 219 | + | ||
| 159 | 220 | "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "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" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], |
|
| 160 | 221 | ||
| 222 | + | "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="], |
|
| 223 | + | ||
| 224 | + | "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], |
|
| 225 | + | ||
| 226 | + | "@radix-ui/react-use-callback-ref": ["@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" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], |
|
| 227 | + | ||
| 228 | + | "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "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" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], |
|
| 229 | + | ||
| 230 | + | "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "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" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], |
|
| 231 | + | ||
| 232 | + | "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "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" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], |
|
| 233 | + | ||
| 234 | + | "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], |
|
| 235 | + | ||
| 236 | + | "@radix-ui/react-use-layout-effect": ["@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" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], |
|
| 237 | + | ||
| 238 | + | "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], |
|
| 239 | + | ||
| 240 | + | "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], |
|
| 241 | + | ||
| 242 | + | "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "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" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], |
|
| 243 | + | ||
| 244 | + | "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], |
|
| 245 | + | ||
| 246 | + | "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], |
|
| 247 | + | ||
| 161 | 248 | "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.41", "", { "os": "android", "cpu": "arm64" }, "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ=="], |
|
| 162 | 249 | ||
| 163 | 250 | "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw=="], |
|
| 278 | 365 | ||
| 279 | 366 | "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], |
|
| 280 | 367 | ||
| 368 | + | "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], |
|
| 369 | + | ||
| 281 | 370 | "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], |
|
| 282 | 371 | ||
| 283 | 372 | "baseline-browser-mapping": ["baseline-browser-mapping@2.8.20", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ=="], |
|
| 315 | 404 | "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], |
|
| 316 | 405 | ||
| 317 | 406 | "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], |
|
| 407 | + | ||
| 408 | + | "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], |
|
| 318 | 409 | ||
| 319 | 410 | "electron-to-chromium": ["electron-to-chromium@1.5.240", "", {}, "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ=="], |
|
| 320 | 411 | ||
| 370 | 461 | ||
| 371 | 462 | "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], |
|
| 372 | 463 | ||
| 464 | + | "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], |
|
| 465 | + | ||
| 373 | 466 | "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], |
|
| 374 | 467 | ||
| 375 | 468 | "globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="], |
|
| 502 | 595 | ||
| 503 | 596 | "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], |
|
| 504 | 597 | ||
| 598 | + | "react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "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" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], |
|
| 599 | + | ||
| 600 | + | "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], |
|
| 601 | + | ||
| 602 | + | "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], |
|
| 603 | + | ||
| 505 | 604 | "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], |
|
| 506 | 605 | ||
| 507 | 606 | "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], |
|
| 551 | 650 | "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], |
|
| 552 | 651 | ||
| 553 | 652 | "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], |
|
| 653 | + | ||
| 654 | + | "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], |
|
| 655 | + | ||
| 656 | + | "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], |
|
| 657 | + | ||
| 658 | + | "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], |
|
| 554 | 659 | ||
| 555 | 660 | "vite": ["rolldown-vite@7.1.14", "", { "dependencies": { "@oxc-project/runtime": "0.92.0", "fdir": "^6.5.0", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.41", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw=="], |
|
| 556 | 661 | ||
| 13 | 13 | "@evolu/common": "^6.0.1-preview.19", |
|
| 14 | 14 | "@evolu/react": "^9.0.1-preview.4", |
|
| 15 | 15 | "@evolu/react-web": "^1.0.1-preview.3", |
|
| 16 | + | "@radix-ui/react-avatar": "^1.1.10", |
|
| 17 | + | "@radix-ui/react-collapsible": "^1.1.12", |
|
| 18 | + | "@radix-ui/react-dialog": "^1.1.15", |
|
| 19 | + | "@radix-ui/react-dropdown-menu": "^2.1.16", |
|
| 20 | + | "@radix-ui/react-label": "^2.1.7", |
|
| 21 | + | "@radix-ui/react-popover": "^1.1.15", |
|
| 22 | + | "@radix-ui/react-separator": "^1.1.7", |
|
| 16 | 23 | "@radix-ui/react-slot": "^1.2.3", |
|
| 24 | + | "@radix-ui/react-switch": "^1.2.6", |
|
| 25 | + | "@radix-ui/react-tooltip": "^1.2.8", |
|
| 17 | 26 | "@tailwindcss/vite": "^4.1.16", |
|
| 18 | 27 | "class-variance-authority": "^0.7.1", |
|
| 19 | 28 | "clsx": "^2.1.1", |
| 1 | + | "use client"; |
|
| 2 | + | ||
| 3 | + | import * as React from "react"; |
|
| 4 | + | import { |
|
| 5 | + | ArchiveX, |
|
| 6 | + | Circle, |
|
| 7 | + | Command, |
|
| 8 | + | File, |
|
| 9 | + | Inbox, |
|
| 10 | + | Send, |
|
| 11 | + | Star, |
|
| 12 | + | Trash2, |
|
| 13 | + | } from "lucide-react"; |
|
| 14 | + | ||
| 15 | + | import { NavUser } from "@/components/nav-user"; |
|
| 16 | + | import { Label } from "@/components/ui/label"; |
|
| 17 | + | import { |
|
| 18 | + | Sidebar, |
|
| 19 | + | SidebarContent, |
|
| 20 | + | SidebarFooter, |
|
| 21 | + | SidebarGroup, |
|
| 22 | + | SidebarGroupContent, |
|
| 23 | + | SidebarHeader, |
|
| 24 | + | SidebarInput, |
|
| 25 | + | SidebarMenu, |
|
| 26 | + | SidebarMenuButton, |
|
| 27 | + | SidebarMenuItem, |
|
| 28 | + | useSidebar, |
|
| 29 | + | } from "@/components/ui/sidebar"; |
|
| 30 | + | import { Switch } from "@/components/ui/switch"; |
|
| 31 | + | ||
| 32 | + | // This is sample data |
|
| 33 | + | const data = { |
|
| 34 | + | user: { |
|
| 35 | + | name: "shadcn", |
|
| 36 | + | email: "m@example.com", |
|
| 37 | + | avatar: "/avatars/shadcn.jpg", |
|
| 38 | + | }, |
|
| 39 | + | navMain: [ |
|
| 40 | + | { |
|
| 41 | + | title: "Today", |
|
| 42 | + | url: "#", |
|
| 43 | + | icon: Inbox, |
|
| 44 | + | isActive: true, |
|
| 45 | + | }, |
|
| 46 | + | { |
|
| 47 | + | title: "Unread", |
|
| 48 | + | url: "#", |
|
| 49 | + | icon: Circle, |
|
| 50 | + | isActive: false, |
|
| 51 | + | }, |
|
| 52 | + | { |
|
| 53 | + | title: "Starred", |
|
| 54 | + | url: "#", |
|
| 55 | + | icon: Star, |
|
| 56 | + | isActive: false, |
|
| 57 | + | }, |
|
| 58 | + | ], |
|
| 59 | + | mails: [ |
|
| 60 | + | { |
|
| 61 | + | name: "William Smith", |
|
| 62 | + | email: "williamsmith@example.com", |
|
| 63 | + | subject: "Meeting Tomorrow", |
|
| 64 | + | date: "09:34 AM", |
|
| 65 | + | teaser: |
|
| 66 | + | "Hi team, just a reminder about our meeting tomorrow at 10 AM.\nPlease come prepared with your project updates.", |
|
| 67 | + | }, |
|
| 68 | + | { |
|
| 69 | + | name: "Alice Smith", |
|
| 70 | + | email: "alicesmith@example.com", |
|
| 71 | + | subject: "Re: Project Update", |
|
| 72 | + | date: "Yesterday", |
|
| 73 | + | teaser: |
|
| 74 | + | "Thanks for the update. The progress looks great so far.\nLet's schedule a call to discuss the next steps.", |
|
| 75 | + | }, |
|
| 76 | + | { |
|
| 77 | + | name: "Bob Johnson", |
|
| 78 | + | email: "bobjohnson@example.com", |
|
| 79 | + | subject: "Weekend Plans", |
|
| 80 | + | date: "2 days ago", |
|
| 81 | + | teaser: |
|
| 82 | + | "Hey everyone! I'm thinking of organizing a team outing this weekend.\nWould you be interested in a hiking trip or a beach day?", |
|
| 83 | + | }, |
|
| 84 | + | { |
|
| 85 | + | name: "Emily Davis", |
|
| 86 | + | email: "emilydavis@example.com", |
|
| 87 | + | subject: "Re: Question about Budget", |
|
| 88 | + | date: "2 days ago", |
|
| 89 | + | teaser: |
|
| 90 | + | "I've reviewed the budget numbers you sent over.\nCan we set up a quick call to discuss some potential adjustments?", |
|
| 91 | + | }, |
|
| 92 | + | { |
|
| 93 | + | name: "Michael Wilson", |
|
| 94 | + | email: "michaelwilson@example.com", |
|
| 95 | + | subject: "Important Announcement", |
|
| 96 | + | date: "1 week ago", |
|
| 97 | + | teaser: |
|
| 98 | + | "Please join us for an all-hands meeting this Friday at 3 PM.\nWe have some exciting news to share about the company's future.", |
|
| 99 | + | }, |
|
| 100 | + | { |
|
| 101 | + | name: "Sarah Brown", |
|
| 102 | + | email: "sarahbrown@example.com", |
|
| 103 | + | subject: "Re: Feedback on Proposal", |
|
| 104 | + | date: "1 week ago", |
|
| 105 | + | teaser: |
|
| 106 | + | "Thank you for sending over the proposal. I've reviewed it and have some thoughts.\nCould we schedule a meeting to discuss my feedback in detail?", |
|
| 107 | + | }, |
|
| 108 | + | { |
|
| 109 | + | name: "David Lee", |
|
| 110 | + | email: "davidlee@example.com", |
|
| 111 | + | subject: "New Project Idea", |
|
| 112 | + | date: "1 week ago", |
|
| 113 | + | teaser: |
|
| 114 | + | "I've been brainstorming and came up with an interesting project concept.\nDo you have time this week to discuss its potential impact and feasibility?", |
|
| 115 | + | }, |
|
| 116 | + | { |
|
| 117 | + | name: "Olivia Wilson", |
|
| 118 | + | email: "oliviawilson@example.com", |
|
| 119 | + | subject: "Vacation Plans", |
|
| 120 | + | date: "1 week ago", |
|
| 121 | + | teaser: |
|
| 122 | + | "Just a heads up that I'll be taking a two-week vacation next month.\nI'll make sure all my projects are up to date before I leave.", |
|
| 123 | + | }, |
|
| 124 | + | { |
|
| 125 | + | name: "James Martin", |
|
| 126 | + | email: "jamesmartin@example.com", |
|
| 127 | + | subject: "Re: Conference Registration", |
|
| 128 | + | date: "1 week ago", |
|
| 129 | + | teaser: |
|
| 130 | + | "I've completed the registration for the upcoming tech conference.\nLet me know if you need any additional information from my end.", |
|
| 131 | + | }, |
|
| 132 | + | { |
|
| 133 | + | name: "Sophia White", |
|
| 134 | + | email: "sophiawhite@example.com", |
|
| 135 | + | subject: "Team Dinner", |
|
| 136 | + | date: "1 week ago", |
|
| 137 | + | teaser: |
|
| 138 | + | "To celebrate our recent project success, I'd like to organize a team dinner.\nAre you available next Friday evening? Please let me know your preferences.", |
|
| 139 | + | }, |
|
| 140 | + | ], |
|
| 141 | + | }; |
|
| 142 | + | ||
| 143 | + | export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { |
|
| 144 | + | // Note: I'm using state to show active item. |
|
| 145 | + | // IRL you should use the url/router. |
|
| 146 | + | const [activeItem, setActiveItem] = React.useState(data.navMain[0]); |
|
| 147 | + | const [mails, setMails] = React.useState(data.mails); |
|
| 148 | + | const { setOpen } = useSidebar(); |
|
| 149 | + | ||
| 150 | + | return ( |
|
| 151 | + | <Sidebar |
|
| 152 | + | collapsible="icon" |
|
| 153 | + | className="overflow-hidden *:data-[sidebar=sidebar]:flex-row" |
|
| 154 | + | {...props} |
|
| 155 | + | > |
|
| 156 | + | {/* This is the first sidebar */} |
|
| 157 | + | {/* We disable collapsible and adjust width to icon. */} |
|
| 158 | + | {/* This will make the sidebar appear as icons. */} |
|
| 159 | + | <Sidebar |
|
| 160 | + | collapsible="none" |
|
| 161 | + | className="w-[calc(var(--sidebar-width-icon)+1px)]! border-r" |
|
| 162 | + | > |
|
| 163 | + | <SidebarHeader> |
|
| 164 | + | <SidebarMenu> |
|
| 165 | + | <SidebarMenuItem> |
|
| 166 | + | <SidebarMenuButton size="lg" asChild className="md:h-8 md:p-0"> |
|
| 167 | + | <a href="#"> |
|
| 168 | + | <div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg"> |
|
| 169 | + | <Command className="size-4" /> |
|
| 170 | + | </div> |
|
| 171 | + | <div className="grid flex-1 text-left text-sm leading-tight"> |
|
| 172 | + | <span className="truncate font-medium">Acme Inc</span> |
|
| 173 | + | <span className="truncate text-xs">Enterprise</span> |
|
| 174 | + | </div> |
|
| 175 | + | </a> |
|
| 176 | + | </SidebarMenuButton> |
|
| 177 | + | </SidebarMenuItem> |
|
| 178 | + | </SidebarMenu> |
|
| 179 | + | </SidebarHeader> |
|
| 180 | + | <SidebarContent> |
|
| 181 | + | <SidebarGroup> |
|
| 182 | + | <SidebarGroupContent className="px-1.5 md:px-0"> |
|
| 183 | + | <SidebarMenu> |
|
| 184 | + | {data.navMain.map((item) => ( |
|
| 185 | + | <SidebarMenuItem key={item.title}> |
|
| 186 | + | <SidebarMenuButton |
|
| 187 | + | tooltip={{ |
|
| 188 | + | children: item.title, |
|
| 189 | + | hidden: false, |
|
| 190 | + | }} |
|
| 191 | + | onClick={() => { |
|
| 192 | + | setActiveItem(item); |
|
| 193 | + | const mail = data.mails.sort(() => Math.random() - 0.5); |
|
| 194 | + | setMails( |
|
| 195 | + | mail.slice( |
|
| 196 | + | 0, |
|
| 197 | + | Math.max(5, Math.floor(Math.random() * 10) + 1), |
|
| 198 | + | ), |
|
| 199 | + | ); |
|
| 200 | + | setOpen(true); |
|
| 201 | + | }} |
|
| 202 | + | isActive={activeItem?.title === item.title} |
|
| 203 | + | className="px-2.5 md:px-2" |
|
| 204 | + | > |
|
| 205 | + | <item.icon /> |
|
| 206 | + | <span>{item.title}</span> |
|
| 207 | + | </SidebarMenuButton> |
|
| 208 | + | </SidebarMenuItem> |
|
| 209 | + | ))} |
|
| 210 | + | </SidebarMenu> |
|
| 211 | + | </SidebarGroupContent> |
|
| 212 | + | </SidebarGroup> |
|
| 213 | + | </SidebarContent> |
|
| 214 | + | <SidebarFooter> |
|
| 215 | + | <NavUser user={data.user} /> |
|
| 216 | + | </SidebarFooter> |
|
| 217 | + | </Sidebar> |
|
| 218 | + | ||
| 219 | + | {/* This is the second sidebar */} |
|
| 220 | + | {/* We disable collapsible and let it fill remaining space */} |
|
| 221 | + | <Sidebar collapsible="none" className="hidden flex-1 md:flex"> |
|
| 222 | + | <SidebarHeader className="gap-3.5 border-b p-4"> |
|
| 223 | + | <div className="flex w-full items-center justify-between"> |
|
| 224 | + | <div className="text-foreground text-base font-medium"> |
|
| 225 | + | {activeItem?.title} |
|
| 226 | + | </div> |
|
| 227 | + | <Label className="flex items-center gap-2 text-sm"> |
|
| 228 | + | <span>Unreads</span> |
|
| 229 | + | <Switch className="shadow-none" /> |
|
| 230 | + | </Label> |
|
| 231 | + | </div> |
|
| 232 | + | <SidebarInput placeholder="Type to search..." /> |
|
| 233 | + | </SidebarHeader> |
|
| 234 | + | <SidebarContent> |
|
| 235 | + | <SidebarGroup className="px-0"> |
|
| 236 | + | <SidebarGroupContent> |
|
| 237 | + | {mails.map((mail) => ( |
|
| 238 | + | <a |
|
| 239 | + | href="#" |
|
| 240 | + | key={mail.email} |
|
| 241 | + | className="hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex flex-col items-start gap-2 border-b p-4 text-sm leading-tight whitespace-nowrap last:border-b-0" |
|
| 242 | + | > |
|
| 243 | + | <div className="flex w-full items-center gap-2"> |
|
| 244 | + | <span>{mail.name}</span>{" "} |
|
| 245 | + | <span className="ml-auto text-xs">{mail.date}</span> |
|
| 246 | + | </div> |
|
| 247 | + | <span className="font-medium">{mail.subject}</span> |
|
| 248 | + | <span className="line-clamp-2 w-[260px] text-xs whitespace-break-spaces"> |
|
| 249 | + | {mail.teaser} |
|
| 250 | + | </span> |
|
| 251 | + | </a> |
|
| 252 | + | ))} |
|
| 253 | + | </SidebarGroupContent> |
|
| 254 | + | </SidebarGroup> |
|
| 255 | + | </SidebarContent> |
|
| 256 | + | </Sidebar> |
|
| 257 | + | </Sidebar> |
|
| 258 | + | ); |
|
| 259 | + | } |
| 1 | + | import { useEvolu } from "../main"; |
|
| 2 | + | import { AppSidebar } from "@/components/app-sidebar"; |
|
| 3 | + | import { |
|
| 4 | + | Breadcrumb, |
|
| 5 | + | BreadcrumbItem, |
|
| 6 | + | BreadcrumbLink, |
|
| 7 | + | BreadcrumbList, |
|
| 8 | + | BreadcrumbPage, |
|
| 9 | + | BreadcrumbSeparator, |
|
| 10 | + | } from "@/components/ui/breadcrumb"; |
|
| 11 | + | import { Separator } from "@/components/ui/separator"; |
|
| 12 | + | import { |
|
| 13 | + | SidebarInset, |
|
| 14 | + | SidebarProvider, |
|
| 15 | + | SidebarTrigger, |
|
| 16 | + | } from "@/components/ui/sidebar"; |
|
| 17 | + | ||
| 18 | + | function Dashboard() { |
|
| 19 | + | const { insert, update } = useEvolu(); |
|
| 20 | + | ||
| 21 | + | return ( |
|
| 22 | + | <main className="min-h-screen w-full items-center justify-center flex-col flex gap-2"> |
|
| 23 | + | <SidebarProvider |
|
| 24 | + | style={ |
|
| 25 | + | { |
|
| 26 | + | "--sidebar-width": "350px", |
|
| 27 | + | } as React.CSSProperties |
|
| 28 | + | } |
|
| 29 | + | > |
|
| 30 | + | <AppSidebar /> |
|
| 31 | + | <SidebarInset> |
|
| 32 | + | <header className="bg-background sticky top-0 flex shrink-0 items-center gap-2 border-b p-4"> |
|
| 33 | + | <SidebarTrigger className="-ml-1" /> |
|
| 34 | + | <Separator |
|
| 35 | + | orientation="vertical" |
|
| 36 | + | className="mr-2 data-[orientation=vertical]:h-4" |
|
| 37 | + | /> |
|
| 38 | + | <Breadcrumb> |
|
| 39 | + | <BreadcrumbList> |
|
| 40 | + | <BreadcrumbItem className="hidden md:block"> |
|
| 41 | + | <BreadcrumbLink href="#">All Inboxes</BreadcrumbLink> |
|
| 42 | + | </BreadcrumbItem> |
|
| 43 | + | <BreadcrumbSeparator className="hidden md:block" /> |
|
| 44 | + | <BreadcrumbItem> |
|
| 45 | + | <BreadcrumbPage>Inbox</BreadcrumbPage> |
|
| 46 | + | </BreadcrumbItem> |
|
| 47 | + | </BreadcrumbList> |
|
| 48 | + | </Breadcrumb> |
|
| 49 | + | </header> |
|
| 50 | + | <div className="flex flex-1 flex-col gap-4 p-4"> |
|
| 51 | + | {Array.from({ length: 24 }).map((_, index) => ( |
|
| 52 | + | <div |
|
| 53 | + | key={index} |
|
| 54 | + | className="bg-muted/50 aspect-video h-12 w-full rounded-lg" |
|
| 55 | + | /> |
|
| 56 | + | ))} |
|
| 57 | + | </div> |
|
| 58 | + | </SidebarInset> |
|
| 59 | + | </SidebarProvider> |
|
| 60 | + | </main> |
|
| 61 | + | ); |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | export default Dashboard; |
| 1 | + | import * as React from "react" |
|
| 2 | + | import { |
|
| 3 | + | ArrowDown, |
|
| 4 | + | ArrowUp, |
|
| 5 | + | Bell, |
|
| 6 | + | Copy, |
|
| 7 | + | CornerUpLeft, |
|
| 8 | + | CornerUpRight, |
|
| 9 | + | FileText, |
|
| 10 | + | GalleryVerticalEnd, |
|
| 11 | + | LineChart, |
|
| 12 | + | Link, |
|
| 13 | + | MoreHorizontal, |
|
| 14 | + | Settings2, |
|
| 15 | + | Star, |
|
| 16 | + | Trash, |
|
| 17 | + | Trash2, |
|
| 18 | + | } from "lucide-react" |
|
| 19 | + | ||
| 20 | + | import { Button } from "@/components/ui/button" |
|
| 21 | + | import { |
|
| 22 | + | Popover, |
|
| 23 | + | PopoverContent, |
|
| 24 | + | PopoverTrigger, |
|
| 25 | + | } from "@/components/ui/popover" |
|
| 26 | + | import { |
|
| 27 | + | Sidebar, |
|
| 28 | + | SidebarContent, |
|
| 29 | + | SidebarGroup, |
|
| 30 | + | SidebarGroupContent, |
|
| 31 | + | SidebarMenu, |
|
| 32 | + | SidebarMenuButton, |
|
| 33 | + | SidebarMenuItem, |
|
| 34 | + | } from "@/components/ui/sidebar" |
|
| 35 | + | ||
| 36 | + | const data = [ |
|
| 37 | + | [ |
|
| 38 | + | { |
|
| 39 | + | label: "Customize Page", |
|
| 40 | + | icon: Settings2, |
|
| 41 | + | }, |
|
| 42 | + | { |
|
| 43 | + | label: "Turn into wiki", |
|
| 44 | + | icon: FileText, |
|
| 45 | + | }, |
|
| 46 | + | ], |
|
| 47 | + | [ |
|
| 48 | + | { |
|
| 49 | + | label: "Copy Link", |
|
| 50 | + | icon: Link, |
|
| 51 | + | }, |
|
| 52 | + | { |
|
| 53 | + | label: "Duplicate", |
|
| 54 | + | icon: Copy, |
|
| 55 | + | }, |
|
| 56 | + | { |
|
| 57 | + | label: "Move to", |
|
| 58 | + | icon: CornerUpRight, |
|
| 59 | + | }, |
|
| 60 | + | { |
|
| 61 | + | label: "Move to Trash", |
|
| 62 | + | icon: Trash2, |
|
| 63 | + | }, |
|
| 64 | + | ], |
|
| 65 | + | [ |
|
| 66 | + | { |
|
| 67 | + | label: "Undo", |
|
| 68 | + | icon: CornerUpLeft, |
|
| 69 | + | }, |
|
| 70 | + | { |
|
| 71 | + | label: "View analytics", |
|
| 72 | + | icon: LineChart, |
|
| 73 | + | }, |
|
| 74 | + | { |
|
| 75 | + | label: "Version History", |
|
| 76 | + | icon: GalleryVerticalEnd, |
|
| 77 | + | }, |
|
| 78 | + | { |
|
| 79 | + | label: "Show delete pages", |
|
| 80 | + | icon: Trash, |
|
| 81 | + | }, |
|
| 82 | + | { |
|
| 83 | + | label: "Notifications", |
|
| 84 | + | icon: Bell, |
|
| 85 | + | }, |
|
| 86 | + | ], |
|
| 87 | + | [ |
|
| 88 | + | { |
|
| 89 | + | label: "Import", |
|
| 90 | + | icon: ArrowUp, |
|
| 91 | + | }, |
|
| 92 | + | { |
|
| 93 | + | label: "Export", |
|
| 94 | + | icon: ArrowDown, |
|
| 95 | + | }, |
|
| 96 | + | ], |
|
| 97 | + | ] |
|
| 98 | + | ||
| 99 | + | export function NavActions() { |
|
| 100 | + | const [isOpen, setIsOpen] = React.useState(false) |
|
| 101 | + | ||
| 102 | + | React.useEffect(() => { |
|
| 103 | + | setIsOpen(true) |
|
| 104 | + | }, []) |
|
| 105 | + | ||
| 106 | + | return ( |
|
| 107 | + | <div className="flex items-center gap-2 text-sm"> |
|
| 108 | + | <div className="text-muted-foreground hidden font-medium md:inline-block"> |
|
| 109 | + | Edit Oct 08 |
|
| 110 | + | </div> |
|
| 111 | + | <Button variant="ghost" size="icon" className="h-7 w-7"> |
|
| 112 | + | <Star /> |
|
| 113 | + | </Button> |
|
| 114 | + | <Popover open={isOpen} onOpenChange={setIsOpen}> |
|
| 115 | + | <PopoverTrigger asChild> |
|
| 116 | + | <Button |
|
| 117 | + | variant="ghost" |
|
| 118 | + | size="icon" |
|
| 119 | + | className="data-[state=open]:bg-accent h-7 w-7" |
|
| 120 | + | > |
|
| 121 | + | <MoreHorizontal /> |
|
| 122 | + | </Button> |
|
| 123 | + | </PopoverTrigger> |
|
| 124 | + | <PopoverContent |
|
| 125 | + | className="w-56 overflow-hidden rounded-lg p-0" |
|
| 126 | + | align="end" |
|
| 127 | + | > |
|
| 128 | + | <Sidebar collapsible="none" className="bg-transparent"> |
|
| 129 | + | <SidebarContent> |
|
| 130 | + | {data.map((group, index) => ( |
|
| 131 | + | <SidebarGroup key={index} className="border-b last:border-none"> |
|
| 132 | + | <SidebarGroupContent className="gap-0"> |
|
| 133 | + | <SidebarMenu> |
|
| 134 | + | {group.map((item, index) => ( |
|
| 135 | + | <SidebarMenuItem key={index}> |
|
| 136 | + | <SidebarMenuButton> |
|
| 137 | + | <item.icon /> <span>{item.label}</span> |
|
| 138 | + | </SidebarMenuButton> |
|
| 139 | + | </SidebarMenuItem> |
|
| 140 | + | ))} |
|
| 141 | + | </SidebarMenu> |
|
| 142 | + | </SidebarGroupContent> |
|
| 143 | + | </SidebarGroup> |
|
| 144 | + | ))} |
|
| 145 | + | </SidebarContent> |
|
| 146 | + | </Sidebar> |
|
| 147 | + | </PopoverContent> |
|
| 148 | + | </Popover> |
|
| 149 | + | </div> |
|
| 150 | + | ) |
|
| 151 | + | } |
| 1 | + | "use client" |
|
| 2 | + | ||
| 3 | + | import { |
|
| 4 | + | ArrowUpRight, |
|
| 5 | + | Link, |
|
| 6 | + | MoreHorizontal, |
|
| 7 | + | StarOff, |
|
| 8 | + | Trash2, |
|
| 9 | + | } from "lucide-react" |
|
| 10 | + | ||
| 11 | + | import { |
|
| 12 | + | DropdownMenu, |
|
| 13 | + | DropdownMenuContent, |
|
| 14 | + | DropdownMenuItem, |
|
| 15 | + | DropdownMenuSeparator, |
|
| 16 | + | DropdownMenuTrigger, |
|
| 17 | + | } from "@/components/ui/dropdown-menu" |
|
| 18 | + | import { |
|
| 19 | + | SidebarGroup, |
|
| 20 | + | SidebarGroupLabel, |
|
| 21 | + | SidebarMenu, |
|
| 22 | + | SidebarMenuAction, |
|
| 23 | + | SidebarMenuButton, |
|
| 24 | + | SidebarMenuItem, |
|
| 25 | + | useSidebar, |
|
| 26 | + | } from "@/components/ui/sidebar" |
|
| 27 | + | ||
| 28 | + | export function NavFavorites({ |
|
| 29 | + | favorites, |
|
| 30 | + | }: { |
|
| 31 | + | favorites: { |
|
| 32 | + | name: string |
|
| 33 | + | url: string |
|
| 34 | + | emoji: string |
|
| 35 | + | }[] |
|
| 36 | + | }) { |
|
| 37 | + | const { isMobile } = useSidebar() |
|
| 38 | + | ||
| 39 | + | return ( |
|
| 40 | + | <SidebarGroup className="group-data-[collapsible=icon]:hidden"> |
|
| 41 | + | <SidebarGroupLabel>Favorites</SidebarGroupLabel> |
|
| 42 | + | <SidebarMenu> |
|
| 43 | + | {favorites.map((item) => ( |
|
| 44 | + | <SidebarMenuItem key={item.name}> |
|
| 45 | + | <SidebarMenuButton asChild> |
|
| 46 | + | <a href={item.url} title={item.name}> |
|
| 47 | + | <span>{item.emoji}</span> |
|
| 48 | + | <span>{item.name}</span> |
|
| 49 | + | </a> |
|
| 50 | + | </SidebarMenuButton> |
|
| 51 | + | <DropdownMenu> |
|
| 52 | + | <DropdownMenuTrigger asChild> |
|
| 53 | + | <SidebarMenuAction showOnHover> |
|
| 54 | + | <MoreHorizontal /> |
|
| 55 | + | <span className="sr-only">More</span> |
|
| 56 | + | </SidebarMenuAction> |
|
| 57 | + | </DropdownMenuTrigger> |
|
| 58 | + | <DropdownMenuContent |
|
| 59 | + | className="w-56 rounded-lg" |
|
| 60 | + | side={isMobile ? "bottom" : "right"} |
|
| 61 | + | align={isMobile ? "end" : "start"} |
|
| 62 | + | > |
|
| 63 | + | <DropdownMenuItem> |
|
| 64 | + | <StarOff className="text-muted-foreground" /> |
|
| 65 | + | <span>Remove from Favorites</span> |
|
| 66 | + | </DropdownMenuItem> |
|
| 67 | + | <DropdownMenuSeparator /> |
|
| 68 | + | <DropdownMenuItem> |
|
| 69 | + | <Link className="text-muted-foreground" /> |
|
| 70 | + | <span>Copy Link</span> |
|
| 71 | + | </DropdownMenuItem> |
|
| 72 | + | <DropdownMenuItem> |
|
| 73 | + | <ArrowUpRight className="text-muted-foreground" /> |
|
| 74 | + | <span>Open in New Tab</span> |
|
| 75 | + | </DropdownMenuItem> |
|
| 76 | + | <DropdownMenuSeparator /> |
|
| 77 | + | <DropdownMenuItem> |
|
| 78 | + | <Trash2 className="text-muted-foreground" /> |
|
| 79 | + | <span>Delete</span> |
|
| 80 | + | </DropdownMenuItem> |
|
| 81 | + | </DropdownMenuContent> |
|
| 82 | + | </DropdownMenu> |
|
| 83 | + | </SidebarMenuItem> |
|
| 84 | + | ))} |
|
| 85 | + | <SidebarMenuItem> |
|
| 86 | + | <SidebarMenuButton className="text-sidebar-foreground/70"> |
|
| 87 | + | <MoreHorizontal /> |
|
| 88 | + | <span>More</span> |
|
| 89 | + | </SidebarMenuButton> |
|
| 90 | + | </SidebarMenuItem> |
|
| 91 | + | </SidebarMenu> |
|
| 92 | + | </SidebarGroup> |
|
| 93 | + | ) |
|
| 94 | + | } |
| 1 | + | import { type LucideIcon } from "lucide-react" |
|
| 2 | + | ||
| 3 | + | import { |
|
| 4 | + | SidebarMenu, |
|
| 5 | + | SidebarMenuButton, |
|
| 6 | + | SidebarMenuItem, |
|
| 7 | + | } from "@/components/ui/sidebar" |
|
| 8 | + | ||
| 9 | + | export function NavMain({ |
|
| 10 | + | items, |
|
| 11 | + | }: { |
|
| 12 | + | items: { |
|
| 13 | + | title: string |
|
| 14 | + | url: string |
|
| 15 | + | icon: LucideIcon |
|
| 16 | + | isActive?: boolean |
|
| 17 | + | }[] |
|
| 18 | + | }) { |
|
| 19 | + | return ( |
|
| 20 | + | <SidebarMenu> |
|
| 21 | + | {items.map((item) => ( |
|
| 22 | + | <SidebarMenuItem key={item.title}> |
|
| 23 | + | <SidebarMenuButton asChild isActive={item.isActive}> |
|
| 24 | + | <a href={item.url}> |
|
| 25 | + | <item.icon /> |
|
| 26 | + | <span>{item.title}</span> |
|
| 27 | + | </a> |
|
| 28 | + | </SidebarMenuButton> |
|
| 29 | + | </SidebarMenuItem> |
|
| 30 | + | ))} |
|
| 31 | + | </SidebarMenu> |
|
| 32 | + | ) |
|
| 33 | + | } |
| 1 | + | import React from "react" |
|
| 2 | + | import { type LucideIcon } from "lucide-react" |
|
| 3 | + | ||
| 4 | + | import { |
|
| 5 | + | SidebarGroup, |
|
| 6 | + | SidebarGroupContent, |
|
| 7 | + | SidebarMenu, |
|
| 8 | + | SidebarMenuBadge, |
|
| 9 | + | SidebarMenuButton, |
|
| 10 | + | SidebarMenuItem, |
|
| 11 | + | } from "@/components/ui/sidebar" |
|
| 12 | + | ||
| 13 | + | export function NavSecondary({ |
|
| 14 | + | items, |
|
| 15 | + | ...props |
|
| 16 | + | }: { |
|
| 17 | + | items: { |
|
| 18 | + | title: string |
|
| 19 | + | url: string |
|
| 20 | + | icon: LucideIcon |
|
| 21 | + | badge?: React.ReactNode |
|
| 22 | + | }[] |
|
| 23 | + | } & React.ComponentPropsWithoutRef<typeof SidebarGroup>) { |
|
| 24 | + | return ( |
|
| 25 | + | <SidebarGroup {...props}> |
|
| 26 | + | <SidebarGroupContent> |
|
| 27 | + | <SidebarMenu> |
|
| 28 | + | {items.map((item) => ( |
|
| 29 | + | <SidebarMenuItem key={item.title}> |
|
| 30 | + | <SidebarMenuButton asChild> |
|
| 31 | + | <a href={item.url}> |
|
| 32 | + | <item.icon /> |
|
| 33 | + | <span>{item.title}</span> |
|
| 34 | + | </a> |
|
| 35 | + | </SidebarMenuButton> |
|
| 36 | + | {item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>} |
|
| 37 | + | </SidebarMenuItem> |
|
| 38 | + | ))} |
|
| 39 | + | </SidebarMenu> |
|
| 40 | + | </SidebarGroupContent> |
|
| 41 | + | </SidebarGroup> |
|
| 42 | + | ) |
|
| 43 | + | } |
| 1 | + | import { |
|
| 2 | + | BadgeCheck, |
|
| 3 | + | Bell, |
|
| 4 | + | ChevronsUpDown, |
|
| 5 | + | CreditCard, |
|
| 6 | + | LogOut, |
|
| 7 | + | Sparkles, |
|
| 8 | + | } from "lucide-react" |
|
| 9 | + | ||
| 10 | + | import { |
|
| 11 | + | Avatar, |
|
| 12 | + | AvatarFallback, |
|
| 13 | + | AvatarImage, |
|
| 14 | + | } from "@/components/ui/avatar" |
|
| 15 | + | import { |
|
| 16 | + | DropdownMenu, |
|
| 17 | + | DropdownMenuContent, |
|
| 18 | + | DropdownMenuGroup, |
|
| 19 | + | DropdownMenuItem, |
|
| 20 | + | DropdownMenuLabel, |
|
| 21 | + | DropdownMenuSeparator, |
|
| 22 | + | DropdownMenuTrigger, |
|
| 23 | + | } from "@/components/ui/dropdown-menu" |
|
| 24 | + | import { |
|
| 25 | + | SidebarMenu, |
|
| 26 | + | SidebarMenuButton, |
|
| 27 | + | SidebarMenuItem, |
|
| 28 | + | useSidebar, |
|
| 29 | + | } from "@/components/ui/sidebar" |
|
| 30 | + | ||
| 31 | + | export function NavUser({ |
|
| 32 | + | user, |
|
| 33 | + | }: { |
|
| 34 | + | user: { |
|
| 35 | + | name: string |
|
| 36 | + | email: string |
|
| 37 | + | avatar: string |
|
| 38 | + | } |
|
| 39 | + | }) { |
|
| 40 | + | const { isMobile } = useSidebar() |
|
| 41 | + | ||
| 42 | + | return ( |
|
| 43 | + | <SidebarMenu> |
|
| 44 | + | <SidebarMenuItem> |
|
| 45 | + | <DropdownMenu> |
|
| 46 | + | <DropdownMenuTrigger asChild> |
|
| 47 | + | <SidebarMenuButton |
|
| 48 | + | size="lg" |
|
| 49 | + | className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground md:h-8 md:p-0" |
|
| 50 | + | > |
|
| 51 | + | <Avatar className="h-8 w-8 rounded-lg"> |
|
| 52 | + | <AvatarImage src={user.avatar} alt={user.name} /> |
|
| 53 | + | <AvatarFallback className="rounded-lg">CN</AvatarFallback> |
|
| 54 | + | </Avatar> |
|
| 55 | + | <div className="grid flex-1 text-left text-sm leading-tight"> |
|
| 56 | + | <span className="truncate font-medium">{user.name}</span> |
|
| 57 | + | <span className="truncate text-xs">{user.email}</span> |
|
| 58 | + | </div> |
|
| 59 | + | <ChevronsUpDown className="ml-auto size-4" /> |
|
| 60 | + | </SidebarMenuButton> |
|
| 61 | + | </DropdownMenuTrigger> |
|
| 62 | + | <DropdownMenuContent |
|
| 63 | + | className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg" |
|
| 64 | + | side={isMobile ? "bottom" : "right"} |
|
| 65 | + | align="end" |
|
| 66 | + | sideOffset={4} |
|
| 67 | + | > |
|
| 68 | + | <DropdownMenuLabel className="p-0 font-normal"> |
|
| 69 | + | <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> |
|
| 70 | + | <Avatar className="h-8 w-8 rounded-lg"> |
|
| 71 | + | <AvatarImage src={user.avatar} alt={user.name} /> |
|
| 72 | + | <AvatarFallback className="rounded-lg">CN</AvatarFallback> |
|
| 73 | + | </Avatar> |
|
| 74 | + | <div className="grid flex-1 text-left text-sm leading-tight"> |
|
| 75 | + | <span className="truncate font-medium">{user.name}</span> |
|
| 76 | + | <span className="truncate text-xs">{user.email}</span> |
|
| 77 | + | </div> |
|
| 78 | + | </div> |
|
| 79 | + | </DropdownMenuLabel> |
|
| 80 | + | <DropdownMenuSeparator /> |
|
| 81 | + | <DropdownMenuGroup> |
|
| 82 | + | <DropdownMenuItem> |
|
| 83 | + | <Sparkles /> |
|
| 84 | + | Upgrade to Pro |
|
| 85 | + | </DropdownMenuItem> |
|
| 86 | + | </DropdownMenuGroup> |
|
| 87 | + | <DropdownMenuSeparator /> |
|
| 88 | + | <DropdownMenuGroup> |
|
| 89 | + | <DropdownMenuItem> |
|
| 90 | + | <BadgeCheck /> |
|
| 91 | + | Account |
|
| 92 | + | </DropdownMenuItem> |
|
| 93 | + | <DropdownMenuItem> |
|
| 94 | + | <CreditCard /> |
|
| 95 | + | Billing |
|
| 96 | + | </DropdownMenuItem> |
|
| 97 | + | <DropdownMenuItem> |
|
| 98 | + | <Bell /> |
|
| 99 | + | Notifications |
|
| 100 | + | </DropdownMenuItem> |
|
| 101 | + | </DropdownMenuGroup> |
|
| 102 | + | <DropdownMenuSeparator /> |
|
| 103 | + | <DropdownMenuItem> |
|
| 104 | + | <LogOut /> |
|
| 105 | + | Log out |
|
| 106 | + | </DropdownMenuItem> |
|
| 107 | + | </DropdownMenuContent> |
|
| 108 | + | </DropdownMenu> |
|
| 109 | + | </SidebarMenuItem> |
|
| 110 | + | </SidebarMenu> |
|
| 111 | + | ) |
|
| 112 | + | } |
| 1 | + | import { ChevronRight, MoreHorizontal, Plus } from "lucide-react" |
|
| 2 | + | ||
| 3 | + | import { |
|
| 4 | + | Collapsible, |
|
| 5 | + | CollapsibleContent, |
|
| 6 | + | CollapsibleTrigger, |
|
| 7 | + | } from "@/components/ui/collapsible" |
|
| 8 | + | import { |
|
| 9 | + | SidebarGroup, |
|
| 10 | + | SidebarGroupContent, |
|
| 11 | + | SidebarGroupLabel, |
|
| 12 | + | SidebarMenu, |
|
| 13 | + | SidebarMenuAction, |
|
| 14 | + | SidebarMenuButton, |
|
| 15 | + | SidebarMenuItem, |
|
| 16 | + | SidebarMenuSub, |
|
| 17 | + | SidebarMenuSubButton, |
|
| 18 | + | SidebarMenuSubItem, |
|
| 19 | + | } from "@/components/ui/sidebar" |
|
| 20 | + | ||
| 21 | + | export function NavWorkspaces({ |
|
| 22 | + | workspaces, |
|
| 23 | + | }: { |
|
| 24 | + | workspaces: { |
|
| 25 | + | name: string |
|
| 26 | + | emoji: React.ReactNode |
|
| 27 | + | pages: { |
|
| 28 | + | name: string |
|
| 29 | + | emoji: React.ReactNode |
|
| 30 | + | }[] |
|
| 31 | + | }[] |
|
| 32 | + | }) { |
|
| 33 | + | return ( |
|
| 34 | + | <SidebarGroup> |
|
| 35 | + | <SidebarGroupLabel>Workspaces</SidebarGroupLabel> |
|
| 36 | + | <SidebarGroupContent> |
|
| 37 | + | <SidebarMenu> |
|
| 38 | + | {workspaces.map((workspace) => ( |
|
| 39 | + | <Collapsible key={workspace.name}> |
|
| 40 | + | <SidebarMenuItem> |
|
| 41 | + | <SidebarMenuButton asChild> |
|
| 42 | + | <a href="#"> |
|
| 43 | + | <span>{workspace.emoji}</span> |
|
| 44 | + | <span>{workspace.name}</span> |
|
| 45 | + | </a> |
|
| 46 | + | </SidebarMenuButton> |
|
| 47 | + | <CollapsibleTrigger asChild> |
|
| 48 | + | <SidebarMenuAction |
|
| 49 | + | className="bg-sidebar-accent text-sidebar-accent-foreground left-2 data-[state=open]:rotate-90" |
|
| 50 | + | showOnHover |
|
| 51 | + | > |
|
| 52 | + | <ChevronRight /> |
|
| 53 | + | </SidebarMenuAction> |
|
| 54 | + | </CollapsibleTrigger> |
|
| 55 | + | <SidebarMenuAction showOnHover> |
|
| 56 | + | <Plus /> |
|
| 57 | + | </SidebarMenuAction> |
|
| 58 | + | <CollapsibleContent> |
|
| 59 | + | <SidebarMenuSub> |
|
| 60 | + | {workspace.pages.map((page) => ( |
|
| 61 | + | <SidebarMenuSubItem key={page.name}> |
|
| 62 | + | <SidebarMenuSubButton asChild> |
|
| 63 | + | <a href="#"> |
|
| 64 | + | <span>{page.emoji}</span> |
|
| 65 | + | <span>{page.name}</span> |
|
| 66 | + | </a> |
|
| 67 | + | </SidebarMenuSubButton> |
|
| 68 | + | </SidebarMenuSubItem> |
|
| 69 | + | ))} |
|
| 70 | + | </SidebarMenuSub> |
|
| 71 | + | </CollapsibleContent> |
|
| 72 | + | </SidebarMenuItem> |
|
| 73 | + | </Collapsible> |
|
| 74 | + | ))} |
|
| 75 | + | <SidebarMenuItem> |
|
| 76 | + | <SidebarMenuButton className="text-sidebar-foreground/70"> |
|
| 77 | + | <MoreHorizontal /> |
|
| 78 | + | <span>More</span> |
|
| 79 | + | </SidebarMenuButton> |
|
| 80 | + | </SidebarMenuItem> |
|
| 81 | + | </SidebarMenu> |
|
| 82 | + | </SidebarGroupContent> |
|
| 83 | + | </SidebarGroup> |
|
| 84 | + | ) |
|
| 85 | + | } |
| 1 | + | "use client" |
|
| 2 | + | ||
| 3 | + | import * as React from "react" |
|
| 4 | + | import { ChevronDown, Plus } from "lucide-react" |
|
| 5 | + | ||
| 6 | + | import { |
|
| 7 | + | DropdownMenu, |
|
| 8 | + | DropdownMenuContent, |
|
| 9 | + | DropdownMenuItem, |
|
| 10 | + | DropdownMenuLabel, |
|
| 11 | + | DropdownMenuSeparator, |
|
| 12 | + | DropdownMenuShortcut, |
|
| 13 | + | DropdownMenuTrigger, |
|
| 14 | + | } from "@/components/ui/dropdown-menu" |
|
| 15 | + | import { |
|
| 16 | + | SidebarMenu, |
|
| 17 | + | SidebarMenuButton, |
|
| 18 | + | SidebarMenuItem, |
|
| 19 | + | } from "@/components/ui/sidebar" |
|
| 20 | + | ||
| 21 | + | export function TeamSwitcher({ |
|
| 22 | + | teams, |
|
| 23 | + | }: { |
|
| 24 | + | teams: { |
|
| 25 | + | name: string |
|
| 26 | + | logo: React.ElementType |
|
| 27 | + | plan: string |
|
| 28 | + | }[] |
|
| 29 | + | }) { |
|
| 30 | + | const [activeTeam, setActiveTeam] = React.useState(teams[0]) |
|
| 31 | + | ||
| 32 | + | if (!activeTeam) { |
|
| 33 | + | return null |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | return ( |
|
| 37 | + | <SidebarMenu> |
|
| 38 | + | <SidebarMenuItem> |
|
| 39 | + | <DropdownMenu> |
|
| 40 | + | <DropdownMenuTrigger asChild> |
|
| 41 | + | <SidebarMenuButton className="w-fit px-1.5"> |
|
| 42 | + | <div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-5 items-center justify-center rounded-md"> |
|
| 43 | + | <activeTeam.logo className="size-3" /> |
|
| 44 | + | </div> |
|
| 45 | + | <span className="truncate font-medium">{activeTeam.name}</span> |
|
| 46 | + | <ChevronDown className="opacity-50" /> |
|
| 47 | + | </SidebarMenuButton> |
|
| 48 | + | </DropdownMenuTrigger> |
|
| 49 | + | <DropdownMenuContent |
|
| 50 | + | className="w-64 rounded-lg" |
|
| 51 | + | align="start" |
|
| 52 | + | side="bottom" |
|
| 53 | + | sideOffset={4} |
|
| 54 | + | > |
|
| 55 | + | <DropdownMenuLabel className="text-muted-foreground text-xs"> |
|
| 56 | + | Teams |
|
| 57 | + | </DropdownMenuLabel> |
|
| 58 | + | {teams.map((team, index) => ( |
|
| 59 | + | <DropdownMenuItem |
|
| 60 | + | key={team.name} |
|
| 61 | + | onClick={() => setActiveTeam(team)} |
|
| 62 | + | className="gap-2 p-2" |
|
| 63 | + | > |
|
| 64 | + | <div className="flex size-6 items-center justify-center rounded-xs border"> |
|
| 65 | + | <team.logo className="size-4 shrink-0" /> |
|
| 66 | + | </div> |
|
| 67 | + | {team.name} |
|
| 68 | + | <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut> |
|
| 69 | + | </DropdownMenuItem> |
|
| 70 | + | ))} |
|
| 71 | + | <DropdownMenuSeparator /> |
|
| 72 | + | <DropdownMenuItem className="gap-2 p-2"> |
|
| 73 | + | <div className="bg-background flex size-6 items-center justify-center rounded-md border"> |
|
| 74 | + | <Plus className="size-4" /> |
|
| 75 | + | </div> |
|
| 76 | + | <div className="text-muted-foreground font-medium">Add team</div> |
|
| 77 | + | </DropdownMenuItem> |
|
| 78 | + | </DropdownMenuContent> |
|
| 79 | + | </DropdownMenu> |
|
| 80 | + | </SidebarMenuItem> |
|
| 81 | + | </SidebarMenu> |
|
| 82 | + | ) |
|
| 83 | + | } |
| 1 | + | "use client" |
|
| 2 | + | ||
| 3 | + | import * as React from "react" |
|
| 4 | + | import * as AvatarPrimitive from "@radix-ui/react-avatar" |
|
| 5 | + | ||
| 6 | + | import { cn } from "@/lib/utils" |
|
| 7 | + | ||
| 8 | + | function Avatar({ |
|
| 9 | + | className, |
|
| 10 | + | ...props |
|
| 11 | + | }: React.ComponentProps<typeof AvatarPrimitive.Root>) { |
|
| 12 | + | return ( |
|
| 13 | + | <AvatarPrimitive.Root |
|
| 14 | + | data-slot="avatar" |
|
| 15 | + | className={cn( |
|
| 16 | + | "relative flex size-8 shrink-0 overflow-hidden rounded-full", |
|
| 17 | + | className |
|
| 18 | + | )} |
|
| 19 | + | {...props} |
|
| 20 | + | /> |
|
| 21 | + | ) |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | function AvatarImage({ |
|
| 25 | + | className, |
|
| 26 | + | ...props |
|
| 27 | + | }: React.ComponentProps<typeof AvatarPrimitive.Image>) { |
|
| 28 | + | return ( |
|
| 29 | + | <AvatarPrimitive.Image |
|
| 30 | + | data-slot="avatar-image" |
|
| 31 | + | className={cn("aspect-square size-full", className)} |
|
| 32 | + | {...props} |
|
| 33 | + | /> |
|
| 34 | + | ) |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | function AvatarFallback({ |
|
| 38 | + | className, |
|
| 39 | + | ...props |
|
| 40 | + | }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) { |
|
| 41 | + | return ( |
|
| 42 | + | <AvatarPrimitive.Fallback |
|
| 43 | + | data-slot="avatar-fallback" |
|
| 44 | + | className={cn( |
|
| 45 | + | "bg-muted flex size-full items-center justify-center rounded-full", |
|
| 46 | + | className |
|
| 47 | + | )} |
|
| 48 | + | {...props} |
|
| 49 | + | /> |
|
| 50 | + | ) |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | export { Avatar, AvatarImage, AvatarFallback } |
| 1 | + | import * as React from "react" |
|
| 2 | + | import { Slot } from "@radix-ui/react-slot" |
|
| 3 | + | import { ChevronRight, MoreHorizontal } from "lucide-react" |
|
| 4 | + | ||
| 5 | + | import { cn } from "@/lib/utils" |
|
| 6 | + | ||
| 7 | + | function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { |
|
| 8 | + | return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} /> |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) { |
|
| 12 | + | return ( |
|
| 13 | + | <ol |
|
| 14 | + | data-slot="breadcrumb-list" |
|
| 15 | + | className={cn( |
|
| 16 | + | "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5", |
|
| 17 | + | className |
|
| 18 | + | )} |
|
| 19 | + | {...props} |
|
| 20 | + | /> |
|
| 21 | + | ) |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) { |
|
| 25 | + | return ( |
|
| 26 | + | <li |
|
| 27 | + | data-slot="breadcrumb-item" |
|
| 28 | + | className={cn("inline-flex items-center gap-1.5", className)} |
|
| 29 | + | {...props} |
|
| 30 | + | /> |
|
| 31 | + | ) |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | function BreadcrumbLink({ |
|
| 35 | + | asChild, |
|
| 36 | + | className, |
|
| 37 | + | ...props |
|
| 38 | + | }: React.ComponentProps<"a"> & { |
|
| 39 | + | asChild?: boolean |
|
| 40 | + | }) { |
|
| 41 | + | const Comp = asChild ? Slot : "a" |
|
| 42 | + | ||
| 43 | + | return ( |
|
| 44 | + | <Comp |
|
| 45 | + | data-slot="breadcrumb-link" |
|
| 46 | + | className={cn("hover:text-foreground transition-colors", className)} |
|
| 47 | + | {...props} |
|
| 48 | + | /> |
|
| 49 | + | ) |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) { |
|
| 53 | + | return ( |
|
| 54 | + | <span |
|
| 55 | + | data-slot="breadcrumb-page" |
|
| 56 | + | role="link" |
|
| 57 | + | aria-disabled="true" |
|
| 58 | + | aria-current="page" |
|
| 59 | + | className={cn("text-foreground font-normal", className)} |
|
| 60 | + | {...props} |
|
| 61 | + | /> |
|
| 62 | + | ) |
|
| 63 | + | } |
|
| 64 | + | ||
| 65 | + | function BreadcrumbSeparator({ |
|
| 66 | + | children, |
|
| 67 | + | className, |
|
| 68 | + | ...props |
|
| 69 | + | }: React.ComponentProps<"li">) { |
|
| 70 | + | return ( |
|
| 71 | + | <li |
|
| 72 | + | data-slot="breadcrumb-separator" |
|
| 73 | + | role="presentation" |
|
| 74 | + | aria-hidden="true" |
|
| 75 | + | className={cn("[&>svg]:size-3.5", className)} |
|
| 76 | + | {...props} |
|
| 77 | + | > |
|
| 78 | + | {children ?? <ChevronRight />} |
|
| 79 | + | </li> |
|
| 80 | + | ) |
|
| 81 | + | } |
|
| 82 | + | ||
| 83 | + | function BreadcrumbEllipsis({ |
|
| 84 | + | className, |
|
| 85 | + | ...props |
|
| 86 | + | }: React.ComponentProps<"span">) { |
|
| 87 | + | return ( |
|
| 88 | + | <span |
|
| 89 | + | data-slot="breadcrumb-ellipsis" |
|
| 90 | + | role="presentation" |
|
| 91 | + | aria-hidden="true" |
|
| 92 | + | className={cn("flex size-9 items-center justify-center", className)} |
|
| 93 | + | {...props} |
|
| 94 | + | > |
|
| 95 | + | <MoreHorizontal className="size-4" /> |
|
| 96 | + | <span className="sr-only">More</span> |
|
| 97 | + | </span> |
|
| 98 | + | ) |
|
| 99 | + | } |
|
| 100 | + | ||
| 101 | + | export { |
|
| 102 | + | Breadcrumb, |
|
| 103 | + | BreadcrumbList, |
|
| 104 | + | BreadcrumbItem, |
|
| 105 | + | BreadcrumbLink, |
|
| 106 | + | BreadcrumbPage, |
|
| 107 | + | BreadcrumbSeparator, |
|
| 108 | + | BreadcrumbEllipsis, |
|
| 109 | + | } |
| 1 | + | "use client" |
|
| 2 | + | ||
| 3 | + | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" |
|
| 4 | + | ||
| 5 | + | function Collapsible({ |
|
| 6 | + | ...props |
|
| 7 | + | }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) { |
|
| 8 | + | return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} /> |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | function CollapsibleTrigger({ |
|
| 12 | + | ...props |
|
| 13 | + | }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) { |
|
| 14 | + | return ( |
|
| 15 | + | <CollapsiblePrimitive.CollapsibleTrigger |
|
| 16 | + | data-slot="collapsible-trigger" |
|
| 17 | + | {...props} |
|
| 18 | + | /> |
|
| 19 | + | ) |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | function CollapsibleContent({ |
|
| 23 | + | ...props |
|
| 24 | + | }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) { |
|
| 25 | + | return ( |
|
| 26 | + | <CollapsiblePrimitive.CollapsibleContent |
|
| 27 | + | data-slot="collapsible-content" |
|
| 28 | + | {...props} |
|
| 29 | + | /> |
|
| 30 | + | ) |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | export { Collapsible, CollapsibleTrigger, CollapsibleContent } |
| 1 | + | import * as React from "react" |
|
| 2 | + | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" |
|
| 3 | + | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" |
|
| 4 | + | ||
| 5 | + | import { cn } from "@/lib/utils" |
|
| 6 | + | ||
| 7 | + | function DropdownMenu({ |
|
| 8 | + | ...props |
|
| 9 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) { |
|
| 10 | + | return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} /> |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | function DropdownMenuPortal({ |
|
| 14 | + | ...props |
|
| 15 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) { |
|
| 16 | + | return ( |
|
| 17 | + | <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} /> |
|
| 18 | + | ) |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | function DropdownMenuTrigger({ |
|
| 22 | + | ...props |
|
| 23 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) { |
|
| 24 | + | return ( |
|
| 25 | + | <DropdownMenuPrimitive.Trigger |
|
| 26 | + | data-slot="dropdown-menu-trigger" |
|
| 27 | + | {...props} |
|
| 28 | + | /> |
|
| 29 | + | ) |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | function DropdownMenuContent({ |
|
| 33 | + | className, |
|
| 34 | + | sideOffset = 4, |
|
| 35 | + | ...props |
|
| 36 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) { |
|
| 37 | + | return ( |
|
| 38 | + | <DropdownMenuPrimitive.Portal> |
|
| 39 | + | <DropdownMenuPrimitive.Content |
|
| 40 | + | data-slot="dropdown-menu-content" |
|
| 41 | + | sideOffset={sideOffset} |
|
| 42 | + | className={cn( |
|
| 43 | + | "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md", |
|
| 44 | + | className |
|
| 45 | + | )} |
|
| 46 | + | {...props} |
|
| 47 | + | /> |
|
| 48 | + | </DropdownMenuPrimitive.Portal> |
|
| 49 | + | ) |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | function DropdownMenuGroup({ |
|
| 53 | + | ...props |
|
| 54 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) { |
|
| 55 | + | return ( |
|
| 56 | + | <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} /> |
|
| 57 | + | ) |
|
| 58 | + | } |
|
| 59 | + | ||
| 60 | + | function DropdownMenuItem({ |
|
| 61 | + | className, |
|
| 62 | + | inset, |
|
| 63 | + | variant = "default", |
|
| 64 | + | ...props |
|
| 65 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & { |
|
| 66 | + | inset?: boolean |
|
| 67 | + | variant?: "default" | "destructive" |
|
| 68 | + | }) { |
|
| 69 | + | return ( |
|
| 70 | + | <DropdownMenuPrimitive.Item |
|
| 71 | + | data-slot="dropdown-menu-item" |
|
| 72 | + | data-inset={inset} |
|
| 73 | + | data-variant={variant} |
|
| 74 | + | className={cn( |
|
| 75 | + | "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", |
|
| 76 | + | className |
|
| 77 | + | )} |
|
| 78 | + | {...props} |
|
| 79 | + | /> |
|
| 80 | + | ) |
|
| 81 | + | } |
|
| 82 | + | ||
| 83 | + | function DropdownMenuCheckboxItem({ |
|
| 84 | + | className, |
|
| 85 | + | children, |
|
| 86 | + | checked, |
|
| 87 | + | ...props |
|
| 88 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) { |
|
| 89 | + | return ( |
|
| 90 | + | <DropdownMenuPrimitive.CheckboxItem |
|
| 91 | + | data-slot="dropdown-menu-checkbox-item" |
|
| 92 | + | className={cn( |
|
| 93 | + | "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", |
|
| 94 | + | className |
|
| 95 | + | )} |
|
| 96 | + | checked={checked} |
|
| 97 | + | {...props} |
|
| 98 | + | > |
|
| 99 | + | <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> |
|
| 100 | + | <DropdownMenuPrimitive.ItemIndicator> |
|
| 101 | + | <CheckIcon className="size-4" /> |
|
| 102 | + | </DropdownMenuPrimitive.ItemIndicator> |
|
| 103 | + | </span> |
|
| 104 | + | {children} |
|
| 105 | + | </DropdownMenuPrimitive.CheckboxItem> |
|
| 106 | + | ) |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | function DropdownMenuRadioGroup({ |
|
| 110 | + | ...props |
|
| 111 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) { |
|
| 112 | + | return ( |
|
| 113 | + | <DropdownMenuPrimitive.RadioGroup |
|
| 114 | + | data-slot="dropdown-menu-radio-group" |
|
| 115 | + | {...props} |
|
| 116 | + | /> |
|
| 117 | + | ) |
|
| 118 | + | } |
|
| 119 | + | ||
| 120 | + | function DropdownMenuRadioItem({ |
|
| 121 | + | className, |
|
| 122 | + | children, |
|
| 123 | + | ...props |
|
| 124 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) { |
|
| 125 | + | return ( |
|
| 126 | + | <DropdownMenuPrimitive.RadioItem |
|
| 127 | + | data-slot="dropdown-menu-radio-item" |
|
| 128 | + | className={cn( |
|
| 129 | + | "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", |
|
| 130 | + | className |
|
| 131 | + | )} |
|
| 132 | + | {...props} |
|
| 133 | + | > |
|
| 134 | + | <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> |
|
| 135 | + | <DropdownMenuPrimitive.ItemIndicator> |
|
| 136 | + | <CircleIcon className="size-2 fill-current" /> |
|
| 137 | + | </DropdownMenuPrimitive.ItemIndicator> |
|
| 138 | + | </span> |
|
| 139 | + | {children} |
|
| 140 | + | </DropdownMenuPrimitive.RadioItem> |
|
| 141 | + | ) |
|
| 142 | + | } |
|
| 143 | + | ||
| 144 | + | function DropdownMenuLabel({ |
|
| 145 | + | className, |
|
| 146 | + | inset, |
|
| 147 | + | ...props |
|
| 148 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { |
|
| 149 | + | inset?: boolean |
|
| 150 | + | }) { |
|
| 151 | + | return ( |
|
| 152 | + | <DropdownMenuPrimitive.Label |
|
| 153 | + | data-slot="dropdown-menu-label" |
|
| 154 | + | data-inset={inset} |
|
| 155 | + | className={cn( |
|
| 156 | + | "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", |
|
| 157 | + | className |
|
| 158 | + | )} |
|
| 159 | + | {...props} |
|
| 160 | + | /> |
|
| 161 | + | ) |
|
| 162 | + | } |
|
| 163 | + | ||
| 164 | + | function DropdownMenuSeparator({ |
|
| 165 | + | className, |
|
| 166 | + | ...props |
|
| 167 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) { |
|
| 168 | + | return ( |
|
| 169 | + | <DropdownMenuPrimitive.Separator |
|
| 170 | + | data-slot="dropdown-menu-separator" |
|
| 171 | + | className={cn("bg-border -mx-1 my-1 h-px", className)} |
|
| 172 | + | {...props} |
|
| 173 | + | /> |
|
| 174 | + | ) |
|
| 175 | + | } |
|
| 176 | + | ||
| 177 | + | function DropdownMenuShortcut({ |
|
| 178 | + | className, |
|
| 179 | + | ...props |
|
| 180 | + | }: React.ComponentProps<"span">) { |
|
| 181 | + | return ( |
|
| 182 | + | <span |
|
| 183 | + | data-slot="dropdown-menu-shortcut" |
|
| 184 | + | className={cn( |
|
| 185 | + | "text-muted-foreground ml-auto text-xs tracking-widest", |
|
| 186 | + | className |
|
| 187 | + | )} |
|
| 188 | + | {...props} |
|
| 189 | + | /> |
|
| 190 | + | ) |
|
| 191 | + | } |
|
| 192 | + | ||
| 193 | + | function DropdownMenuSub({ |
|
| 194 | + | ...props |
|
| 195 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) { |
|
| 196 | + | return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} /> |
|
| 197 | + | } |
|
| 198 | + | ||
| 199 | + | function DropdownMenuSubTrigger({ |
|
| 200 | + | className, |
|
| 201 | + | inset, |
|
| 202 | + | children, |
|
| 203 | + | ...props |
|
| 204 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { |
|
| 205 | + | inset?: boolean |
|
| 206 | + | }) { |
|
| 207 | + | return ( |
|
| 208 | + | <DropdownMenuPrimitive.SubTrigger |
|
| 209 | + | data-slot="dropdown-menu-sub-trigger" |
|
| 210 | + | data-inset={inset} |
|
| 211 | + | className={cn( |
|
| 212 | + | "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", |
|
| 213 | + | className |
|
| 214 | + | )} |
|
| 215 | + | {...props} |
|
| 216 | + | > |
|
| 217 | + | {children} |
|
| 218 | + | <ChevronRightIcon className="ml-auto size-4" /> |
|
| 219 | + | </DropdownMenuPrimitive.SubTrigger> |
|
| 220 | + | ) |
|
| 221 | + | } |
|
| 222 | + | ||
| 223 | + | function DropdownMenuSubContent({ |
|
| 224 | + | className, |
|
| 225 | + | ...props |
|
| 226 | + | }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) { |
|
| 227 | + | return ( |
|
| 228 | + | <DropdownMenuPrimitive.SubContent |
|
| 229 | + | data-slot="dropdown-menu-sub-content" |
|
| 230 | + | className={cn( |
|
| 231 | + | "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg", |
|
| 232 | + | className |
|
| 233 | + | )} |
|
| 234 | + | {...props} |
|
| 235 | + | /> |
|
| 236 | + | ) |
|
| 237 | + | } |
|
| 238 | + | ||
| 239 | + | export { |
|
| 240 | + | DropdownMenu, |
|
| 241 | + | DropdownMenuPortal, |
|
| 242 | + | DropdownMenuTrigger, |
|
| 243 | + | DropdownMenuContent, |
|
| 244 | + | DropdownMenuGroup, |
|
| 245 | + | DropdownMenuLabel, |
|
| 246 | + | DropdownMenuItem, |
|
| 247 | + | DropdownMenuCheckboxItem, |
|
| 248 | + | DropdownMenuRadioGroup, |
|
| 249 | + | DropdownMenuRadioItem, |
|
| 250 | + | DropdownMenuSeparator, |
|
| 251 | + | DropdownMenuShortcut, |
|
| 252 | + | DropdownMenuSub, |
|
| 253 | + | DropdownMenuSubTrigger, |
|
| 254 | + | DropdownMenuSubContent, |
|
| 255 | + | } |
| 1 | + | import * as React from "react" |
|
| 2 | + | ||
| 3 | + | import { cn } from "@/lib/utils" |
|
| 4 | + | ||
| 5 | + | function Input({ className, type, ...props }: React.ComponentProps<"input">) { |
|
| 6 | + | return ( |
|
| 7 | + | <input |
|
| 8 | + | type={type} |
|
| 9 | + | data-slot="input" |
|
| 10 | + | className={cn( |
|
| 11 | + | "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", |
|
| 12 | + | "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", |
|
| 13 | + | "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", |
|
| 14 | + | className |
|
| 15 | + | )} |
|
| 16 | + | {...props} |
|
| 17 | + | /> |
|
| 18 | + | ) |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | export { Input } |
| 1 | + | "use client" |
|
| 2 | + | ||
| 3 | + | import * as React from "react" |
|
| 4 | + | import * as LabelPrimitive from "@radix-ui/react-label" |
|
| 5 | + | ||
| 6 | + | import { cn } from "@/lib/utils" |
|
| 7 | + | ||
| 8 | + | function Label({ |
|
| 9 | + | className, |
|
| 10 | + | ...props |
|
| 11 | + | }: React.ComponentProps<typeof LabelPrimitive.Root>) { |
|
| 12 | + | return ( |
|
| 13 | + | <LabelPrimitive.Root |
|
| 14 | + | data-slot="label" |
|
| 15 | + | className={cn( |
|
| 16 | + | "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", |
|
| 17 | + | className |
|
| 18 | + | )} |
|
| 19 | + | {...props} |
|
| 20 | + | /> |
|
| 21 | + | ) |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | export { Label } |
| 1 | + | "use client" |
|
| 2 | + | ||
| 3 | + | import * as React from "react" |
|
| 4 | + | import * as PopoverPrimitive from "@radix-ui/react-popover" |
|
| 5 | + | ||
| 6 | + | import { cn } from "@/lib/utils" |
|
| 7 | + | ||
| 8 | + | function Popover({ |
|
| 9 | + | ...props |
|
| 10 | + | }: React.ComponentProps<typeof PopoverPrimitive.Root>) { |
|
| 11 | + | return <PopoverPrimitive.Root data-slot="popover" {...props} /> |
|
| 12 | + | } |
|
| 13 | + | ||
| 14 | + | function PopoverTrigger({ |
|
| 15 | + | ...props |
|
| 16 | + | }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) { |
|
| 17 | + | return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} /> |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | function PopoverContent({ |
|
| 21 | + | className, |
|
| 22 | + | align = "center", |
|
| 23 | + | sideOffset = 4, |
|
| 24 | + | ...props |
|
| 25 | + | }: React.ComponentProps<typeof PopoverPrimitive.Content>) { |
|
| 26 | + | return ( |
|
| 27 | + | <PopoverPrimitive.Portal> |
|
| 28 | + | <PopoverPrimitive.Content |
|
| 29 | + | data-slot="popover-content" |
|
| 30 | + | align={align} |
|
| 31 | + | sideOffset={sideOffset} |
|
| 32 | + | className={cn( |
|
| 33 | + | "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden", |
|
| 34 | + | className |
|
| 35 | + | )} |
|
| 36 | + | {...props} |
|
| 37 | + | /> |
|
| 38 | + | </PopoverPrimitive.Portal> |
|
| 39 | + | ) |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | function PopoverAnchor({ |
|
| 43 | + | ...props |
|
| 44 | + | }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) { |
|
| 45 | + | return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} /> |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } |
| 1 | + | import * as React from "react" |
|
| 2 | + | import * as SeparatorPrimitive from "@radix-ui/react-separator" |
|
| 3 | + | ||
| 4 | + | import { cn } from "@/lib/utils" |
|
| 5 | + | ||
| 6 | + | function Separator({ |
|
| 7 | + | className, |
|
| 8 | + | orientation = "horizontal", |
|
| 9 | + | decorative = true, |
|
| 10 | + | ...props |
|
| 11 | + | }: React.ComponentProps<typeof SeparatorPrimitive.Root>) { |
|
| 12 | + | return ( |
|
| 13 | + | <SeparatorPrimitive.Root |
|
| 14 | + | data-slot="separator" |
|
| 15 | + | decorative={decorative} |
|
| 16 | + | orientation={orientation} |
|
| 17 | + | className={cn( |
|
| 18 | + | "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", |
|
| 19 | + | className |
|
| 20 | + | )} |
|
| 21 | + | {...props} |
|
| 22 | + | /> |
|
| 23 | + | ) |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | export { Separator } |
| 1 | + | "use client" |
|
| 2 | + | ||
| 3 | + | import * as React from "react" |
|
| 4 | + | import * as SheetPrimitive from "@radix-ui/react-dialog" |
|
| 5 | + | import { XIcon } from "lucide-react" |
|
| 6 | + | ||
| 7 | + | import { cn } from "@/lib/utils" |
|
| 8 | + | ||
| 9 | + | function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) { |
|
| 10 | + | return <SheetPrimitive.Root data-slot="sheet" {...props} /> |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | function SheetTrigger({ |
|
| 14 | + | ...props |
|
| 15 | + | }: React.ComponentProps<typeof SheetPrimitive.Trigger>) { |
|
| 16 | + | return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} /> |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | function SheetClose({ |
|
| 20 | + | ...props |
|
| 21 | + | }: React.ComponentProps<typeof SheetPrimitive.Close>) { |
|
| 22 | + | return <SheetPrimitive.Close data-slot="sheet-close" {...props} /> |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | function SheetPortal({ |
|
| 26 | + | ...props |
|
| 27 | + | }: React.ComponentProps<typeof SheetPrimitive.Portal>) { |
|
| 28 | + | return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} /> |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | function SheetOverlay({ |
|
| 32 | + | className, |
|
| 33 | + | ...props |
|
| 34 | + | }: React.ComponentProps<typeof SheetPrimitive.Overlay>) { |
|
| 35 | + | return ( |
|
| 36 | + | <SheetPrimitive.Overlay |
|
| 37 | + | data-slot="sheet-overlay" |
|
| 38 | + | className={cn( |
|
| 39 | + | "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", |
|
| 40 | + | className |
|
| 41 | + | )} |
|
| 42 | + | {...props} |
|
| 43 | + | /> |
|
| 44 | + | ) |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | function SheetContent({ |
|
| 48 | + | className, |
|
| 49 | + | children, |
|
| 50 | + | side = "right", |
|
| 51 | + | ...props |
|
| 52 | + | }: React.ComponentProps<typeof SheetPrimitive.Content> & { |
|
| 53 | + | side?: "top" | "right" | "bottom" | "left" |
|
| 54 | + | }) { |
|
| 55 | + | return ( |
|
| 56 | + | <SheetPortal> |
|
| 57 | + | <SheetOverlay /> |
|
| 58 | + | <SheetPrimitive.Content |
|
| 59 | + | data-slot="sheet-content" |
|
| 60 | + | className={cn( |
|
| 61 | + | "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500", |
|
| 62 | + | side === "right" && |
|
| 63 | + | "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm", |
|
| 64 | + | side === "left" && |
|
| 65 | + | "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm", |
|
| 66 | + | side === "top" && |
|
| 67 | + | "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b", |
|
| 68 | + | side === "bottom" && |
|
| 69 | + | "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t", |
|
| 70 | + | className |
|
| 71 | + | )} |
|
| 72 | + | {...props} |
|
| 73 | + | > |
|
| 74 | + | {children} |
|
| 75 | + | <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"> |
|
| 76 | + | <XIcon className="size-4" /> |
|
| 77 | + | <span className="sr-only">Close</span> |
|
| 78 | + | </SheetPrimitive.Close> |
|
| 79 | + | </SheetPrimitive.Content> |
|
| 80 | + | </SheetPortal> |
|
| 81 | + | ) |
|
| 82 | + | } |
|
| 83 | + | ||
| 84 | + | function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { |
|
| 85 | + | return ( |
|
| 86 | + | <div |
|
| 87 | + | data-slot="sheet-header" |
|
| 88 | + | className={cn("flex flex-col gap-1.5 p-4", className)} |
|
| 89 | + | {...props} |
|
| 90 | + | /> |
|
| 91 | + | ) |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { |
|
| 95 | + | return ( |
|
| 96 | + | <div |
|
| 97 | + | data-slot="sheet-footer" |
|
| 98 | + | className={cn("mt-auto flex flex-col gap-2 p-4", className)} |
|
| 99 | + | {...props} |
|
| 100 | + | /> |
|
| 101 | + | ) |
|
| 102 | + | } |
|
| 103 | + | ||
| 104 | + | function SheetTitle({ |
|
| 105 | + | className, |
|
| 106 | + | ...props |
|
| 107 | + | }: React.ComponentProps<typeof SheetPrimitive.Title>) { |
|
| 108 | + | return ( |
|
| 109 | + | <SheetPrimitive.Title |
|
| 110 | + | data-slot="sheet-title" |
|
| 111 | + | className={cn("text-foreground font-semibold", className)} |
|
| 112 | + | {...props} |
|
| 113 | + | /> |
|
| 114 | + | ) |
|
| 115 | + | } |
|
| 116 | + | ||
| 117 | + | function SheetDescription({ |
|
| 118 | + | className, |
|
| 119 | + | ...props |
|
| 120 | + | }: React.ComponentProps<typeof SheetPrimitive.Description>) { |
|
| 121 | + | return ( |
|
| 122 | + | <SheetPrimitive.Description |
|
| 123 | + | data-slot="sheet-description" |
|
| 124 | + | className={cn("text-muted-foreground text-sm", className)} |
|
| 125 | + | {...props} |
|
| 126 | + | /> |
|
| 127 | + | ) |
|
| 128 | + | } |
|
| 129 | + | ||
| 130 | + | export { |
|
| 131 | + | Sheet, |
|
| 132 | + | SheetTrigger, |
|
| 133 | + | SheetClose, |
|
| 134 | + | SheetContent, |
|
| 135 | + | SheetHeader, |
|
| 136 | + | SheetFooter, |
|
| 137 | + | SheetTitle, |
|
| 138 | + | SheetDescription, |
|
| 139 | + | } |
| 1 | + | import * as React from "react" |
|
| 2 | + | import { Slot } from "@radix-ui/react-slot" |
|
| 3 | + | import { cva, type VariantProps } from "class-variance-authority" |
|
| 4 | + | import { PanelLeftIcon } from "lucide-react" |
|
| 5 | + | ||
| 6 | + | import { useIsMobile } from "@/hooks/use-mobile" |
|
| 7 | + | import { cn } from "@/lib/utils" |
|
| 8 | + | import { Button } from "@/components/ui/button" |
|
| 9 | + | import { Input } from "@/components/ui/input" |
|
| 10 | + | import { Separator } from "@/components/ui/separator" |
|
| 11 | + | import { |
|
| 12 | + | Sheet, |
|
| 13 | + | SheetContent, |
|
| 14 | + | SheetDescription, |
|
| 15 | + | SheetHeader, |
|
| 16 | + | SheetTitle, |
|
| 17 | + | } from "@/components/ui/sheet" |
|
| 18 | + | import { Skeleton } from "@/components/ui/skeleton" |
|
| 19 | + | import { |
|
| 20 | + | Tooltip, |
|
| 21 | + | TooltipContent, |
|
| 22 | + | TooltipProvider, |
|
| 23 | + | TooltipTrigger, |
|
| 24 | + | } from "@/components/ui/tooltip" |
|
| 25 | + | ||
| 26 | + | const SIDEBAR_COOKIE_NAME = "sidebar_state" |
|
| 27 | + | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 |
|
| 28 | + | const SIDEBAR_WIDTH = "16rem" |
|
| 29 | + | const SIDEBAR_WIDTH_MOBILE = "18rem" |
|
| 30 | + | const SIDEBAR_WIDTH_ICON = "3rem" |
|
| 31 | + | const SIDEBAR_KEYBOARD_SHORTCUT = "b" |
|
| 32 | + | ||
| 33 | + | type SidebarContextProps = { |
|
| 34 | + | state: "expanded" | "collapsed" |
|
| 35 | + | open: boolean |
|
| 36 | + | setOpen: (open: boolean) => void |
|
| 37 | + | openMobile: boolean |
|
| 38 | + | setOpenMobile: (open: boolean) => void |
|
| 39 | + | isMobile: boolean |
|
| 40 | + | toggleSidebar: () => void |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | const SidebarContext = React.createContext<SidebarContextProps | null>(null) |
|
| 44 | + | ||
| 45 | + | function useSidebar() { |
|
| 46 | + | const context = React.useContext(SidebarContext) |
|
| 47 | + | if (!context) { |
|
| 48 | + | throw new Error("useSidebar must be used within a SidebarProvider.") |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | return context |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | function SidebarProvider({ |
|
| 55 | + | defaultOpen = true, |
|
| 56 | + | open: openProp, |
|
| 57 | + | onOpenChange: setOpenProp, |
|
| 58 | + | className, |
|
| 59 | + | style, |
|
| 60 | + | children, |
|
| 61 | + | ...props |
|
| 62 | + | }: React.ComponentProps<"div"> & { |
|
| 63 | + | defaultOpen?: boolean |
|
| 64 | + | open?: boolean |
|
| 65 | + | onOpenChange?: (open: boolean) => void |
|
| 66 | + | }) { |
|
| 67 | + | const isMobile = useIsMobile() |
|
| 68 | + | const [openMobile, setOpenMobile] = React.useState(false) |
|
| 69 | + | ||
| 70 | + | // This is the internal state of the sidebar. |
|
| 71 | + | // We use openProp and setOpenProp for control from outside the component. |
|
| 72 | + | const [_open, _setOpen] = React.useState(defaultOpen) |
|
| 73 | + | const open = openProp ?? _open |
|
| 74 | + | const setOpen = React.useCallback( |
|
| 75 | + | (value: boolean | ((value: boolean) => boolean)) => { |
|
| 76 | + | const openState = typeof value === "function" ? value(open) : value |
|
| 77 | + | if (setOpenProp) { |
|
| 78 | + | setOpenProp(openState) |
|
| 79 | + | } else { |
|
| 80 | + | _setOpen(openState) |
|
| 81 | + | } |
|
| 82 | + | ||
| 83 | + | // This sets the cookie to keep the sidebar state. |
|
| 84 | + | document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` |
|
| 85 | + | }, |
|
| 86 | + | [setOpenProp, open] |
|
| 87 | + | ) |
|
| 88 | + | ||
| 89 | + | // Helper to toggle the sidebar. |
|
| 90 | + | const toggleSidebar = React.useCallback(() => { |
|
| 91 | + | return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open) |
|
| 92 | + | }, [isMobile, setOpen, setOpenMobile]) |
|
| 93 | + | ||
| 94 | + | // Adds a keyboard shortcut to toggle the sidebar. |
|
| 95 | + | React.useEffect(() => { |
|
| 96 | + | const handleKeyDown = (event: KeyboardEvent) => { |
|
| 97 | + | if ( |
|
| 98 | + | event.key === SIDEBAR_KEYBOARD_SHORTCUT && |
|
| 99 | + | (event.metaKey || event.ctrlKey) |
|
| 100 | + | ) { |
|
| 101 | + | event.preventDefault() |
|
| 102 | + | toggleSidebar() |
|
| 103 | + | } |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | window.addEventListener("keydown", handleKeyDown) |
|
| 107 | + | return () => window.removeEventListener("keydown", handleKeyDown) |
|
| 108 | + | }, [toggleSidebar]) |
|
| 109 | + | ||
| 110 | + | // We add a state so that we can do data-state="expanded" or "collapsed". |
|
| 111 | + | // This makes it easier to style the sidebar with Tailwind classes. |
|
| 112 | + | const state = open ? "expanded" : "collapsed" |
|
| 113 | + | ||
| 114 | + | const contextValue = React.useMemo<SidebarContextProps>( |
|
| 115 | + | () => ({ |
|
| 116 | + | state, |
|
| 117 | + | open, |
|
| 118 | + | setOpen, |
|
| 119 | + | isMobile, |
|
| 120 | + | openMobile, |
|
| 121 | + | setOpenMobile, |
|
| 122 | + | toggleSidebar, |
|
| 123 | + | }), |
|
| 124 | + | [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] |
|
| 125 | + | ) |
|
| 126 | + | ||
| 127 | + | return ( |
|
| 128 | + | <SidebarContext.Provider value={contextValue}> |
|
| 129 | + | <TooltipProvider delayDuration={0}> |
|
| 130 | + | <div |
|
| 131 | + | data-slot="sidebar-wrapper" |
|
| 132 | + | style={ |
|
| 133 | + | { |
|
| 134 | + | "--sidebar-width": SIDEBAR_WIDTH, |
|
| 135 | + | "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, |
|
| 136 | + | ...style, |
|
| 137 | + | } as React.CSSProperties |
|
| 138 | + | } |
|
| 139 | + | className={cn( |
|
| 140 | + | "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", |
|
| 141 | + | className |
|
| 142 | + | )} |
|
| 143 | + | {...props} |
|
| 144 | + | > |
|
| 145 | + | {children} |
|
| 146 | + | </div> |
|
| 147 | + | </TooltipProvider> |
|
| 148 | + | </SidebarContext.Provider> |
|
| 149 | + | ) |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | function Sidebar({ |
|
| 153 | + | side = "left", |
|
| 154 | + | variant = "sidebar", |
|
| 155 | + | collapsible = "offcanvas", |
|
| 156 | + | className, |
|
| 157 | + | children, |
|
| 158 | + | ...props |
|
| 159 | + | }: React.ComponentProps<"div"> & { |
|
| 160 | + | side?: "left" | "right" |
|
| 161 | + | variant?: "sidebar" | "floating" | "inset" |
|
| 162 | + | collapsible?: "offcanvas" | "icon" | "none" |
|
| 163 | + | }) { |
|
| 164 | + | const { isMobile, state, openMobile, setOpenMobile } = useSidebar() |
|
| 165 | + | ||
| 166 | + | if (collapsible === "none") { |
|
| 167 | + | return ( |
|
| 168 | + | <div |
|
| 169 | + | data-slot="sidebar" |
|
| 170 | + | className={cn( |
|
| 171 | + | "bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", |
|
| 172 | + | className |
|
| 173 | + | )} |
|
| 174 | + | {...props} |
|
| 175 | + | > |
|
| 176 | + | {children} |
|
| 177 | + | </div> |
|
| 178 | + | ) |
|
| 179 | + | } |
|
| 180 | + | ||
| 181 | + | if (isMobile) { |
|
| 182 | + | return ( |
|
| 183 | + | <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}> |
|
| 184 | + | <SheetContent |
|
| 185 | + | data-sidebar="sidebar" |
|
| 186 | + | data-slot="sidebar" |
|
| 187 | + | data-mobile="true" |
|
| 188 | + | className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden" |
|
| 189 | + | style={ |
|
| 190 | + | { |
|
| 191 | + | "--sidebar-width": SIDEBAR_WIDTH_MOBILE, |
|
| 192 | + | } as React.CSSProperties |
|
| 193 | + | } |
|
| 194 | + | side={side} |
|
| 195 | + | > |
|
| 196 | + | <SheetHeader className="sr-only"> |
|
| 197 | + | <SheetTitle>Sidebar</SheetTitle> |
|
| 198 | + | <SheetDescription>Displays the mobile sidebar.</SheetDescription> |
|
| 199 | + | </SheetHeader> |
|
| 200 | + | <div className="flex h-full w-full flex-col">{children}</div> |
|
| 201 | + | </SheetContent> |
|
| 202 | + | </Sheet> |
|
| 203 | + | ) |
|
| 204 | + | } |
|
| 205 | + | ||
| 206 | + | return ( |
|
| 207 | + | <div |
|
| 208 | + | className="group peer text-sidebar-foreground hidden md:block" |
|
| 209 | + | data-state={state} |
|
| 210 | + | data-collapsible={state === "collapsed" ? collapsible : ""} |
|
| 211 | + | data-variant={variant} |
|
| 212 | + | data-side={side} |
|
| 213 | + | data-slot="sidebar" |
|
| 214 | + | > |
|
| 215 | + | {/* This is what handles the sidebar gap on desktop */} |
|
| 216 | + | <div |
|
| 217 | + | data-slot="sidebar-gap" |
|
| 218 | + | className={cn( |
|
| 219 | + | "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear", |
|
| 220 | + | "group-data-[collapsible=offcanvas]:w-0", |
|
| 221 | + | "group-data-[side=right]:rotate-180", |
|
| 222 | + | variant === "floating" || variant === "inset" |
|
| 223 | + | ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" |
|
| 224 | + | : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)" |
|
| 225 | + | )} |
|
| 226 | + | /> |
|
| 227 | + | <div |
|
| 228 | + | data-slot="sidebar-container" |
|
| 229 | + | className={cn( |
|
| 230 | + | "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex", |
|
| 231 | + | side === "left" |
|
| 232 | + | ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" |
|
| 233 | + | : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", |
|
| 234 | + | // Adjust the padding for floating and inset variants. |
|
| 235 | + | variant === "floating" || variant === "inset" |
|
| 236 | + | ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" |
|
| 237 | + | : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", |
|
| 238 | + | className |
|
| 239 | + | )} |
|
| 240 | + | {...props} |
|
| 241 | + | > |
|
| 242 | + | <div |
|
| 243 | + | data-sidebar="sidebar" |
|
| 244 | + | data-slot="sidebar-inner" |
|
| 245 | + | className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm" |
|
| 246 | + | > |
|
| 247 | + | {children} |
|
| 248 | + | </div> |
|
| 249 | + | </div> |
|
| 250 | + | </div> |
|
| 251 | + | ) |
|
| 252 | + | } |
|
| 253 | + | ||
| 254 | + | function SidebarTrigger({ |
|
| 255 | + | className, |
|
| 256 | + | onClick, |
|
| 257 | + | ...props |
|
| 258 | + | }: React.ComponentProps<typeof Button>) { |
|
| 259 | + | const { toggleSidebar } = useSidebar() |
|
| 260 | + | ||
| 261 | + | return ( |
|
| 262 | + | <Button |
|
| 263 | + | data-sidebar="trigger" |
|
| 264 | + | data-slot="sidebar-trigger" |
|
| 265 | + | variant="ghost" |
|
| 266 | + | size="icon" |
|
| 267 | + | className={cn("size-7", className)} |
|
| 268 | + | onClick={(event) => { |
|
| 269 | + | onClick?.(event) |
|
| 270 | + | toggleSidebar() |
|
| 271 | + | }} |
|
| 272 | + | {...props} |
|
| 273 | + | > |
|
| 274 | + | <PanelLeftIcon /> |
|
| 275 | + | <span className="sr-only">Toggle Sidebar</span> |
|
| 276 | + | </Button> |
|
| 277 | + | ) |
|
| 278 | + | } |
|
| 279 | + | ||
| 280 | + | function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { |
|
| 281 | + | const { toggleSidebar } = useSidebar() |
|
| 282 | + | ||
| 283 | + | return ( |
|
| 284 | + | <button |
|
| 285 | + | data-sidebar="rail" |
|
| 286 | + | data-slot="sidebar-rail" |
|
| 287 | + | aria-label="Toggle Sidebar" |
|
| 288 | + | tabIndex={-1} |
|
| 289 | + | onClick={toggleSidebar} |
|
| 290 | + | title="Toggle Sidebar" |
|
| 291 | + | className={cn( |
|
| 292 | + | "hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex", |
|
| 293 | + | "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize", |
|
| 294 | + | "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", |
|
| 295 | + | "hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full", |
|
| 296 | + | "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", |
|
| 297 | + | "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", |
|
| 298 | + | className |
|
| 299 | + | )} |
|
| 300 | + | {...props} |
|
| 301 | + | /> |
|
| 302 | + | ) |
|
| 303 | + | } |
|
| 304 | + | ||
| 305 | + | function SidebarInset({ className, ...props }: React.ComponentProps<"main">) { |
|
| 306 | + | return ( |
|
| 307 | + | <main |
|
| 308 | + | data-slot="sidebar-inset" |
|
| 309 | + | className={cn( |
|
| 310 | + | "bg-background relative flex w-full flex-1 flex-col", |
|
| 311 | + | "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2", |
|
| 312 | + | className |
|
| 313 | + | )} |
|
| 314 | + | {...props} |
|
| 315 | + | /> |
|
| 316 | + | ) |
|
| 317 | + | } |
|
| 318 | + | ||
| 319 | + | function SidebarInput({ |
|
| 320 | + | className, |
|
| 321 | + | ...props |
|
| 322 | + | }: React.ComponentProps<typeof Input>) { |
|
| 323 | + | return ( |
|
| 324 | + | <Input |
|
| 325 | + | data-slot="sidebar-input" |
|
| 326 | + | data-sidebar="input" |
|
| 327 | + | className={cn("bg-background h-8 w-full shadow-none", className)} |
|
| 328 | + | {...props} |
|
| 329 | + | /> |
|
| 330 | + | ) |
|
| 331 | + | } |
|
| 332 | + | ||
| 333 | + | function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) { |
|
| 334 | + | return ( |
|
| 335 | + | <div |
|
| 336 | + | data-slot="sidebar-header" |
|
| 337 | + | data-sidebar="header" |
|
| 338 | + | className={cn("flex flex-col gap-2 p-2", className)} |
|
| 339 | + | {...props} |
|
| 340 | + | /> |
|
| 341 | + | ) |
|
| 342 | + | } |
|
| 343 | + | ||
| 344 | + | function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) { |
|
| 345 | + | return ( |
|
| 346 | + | <div |
|
| 347 | + | data-slot="sidebar-footer" |
|
| 348 | + | data-sidebar="footer" |
|
| 349 | + | className={cn("flex flex-col gap-2 p-2", className)} |
|
| 350 | + | {...props} |
|
| 351 | + | /> |
|
| 352 | + | ) |
|
| 353 | + | } |
|
| 354 | + | ||
| 355 | + | function SidebarSeparator({ |
|
| 356 | + | className, |
|
| 357 | + | ...props |
|
| 358 | + | }: React.ComponentProps<typeof Separator>) { |
|
| 359 | + | return ( |
|
| 360 | + | <Separator |
|
| 361 | + | data-slot="sidebar-separator" |
|
| 362 | + | data-sidebar="separator" |
|
| 363 | + | className={cn("bg-sidebar-border mx-2 w-auto", className)} |
|
| 364 | + | {...props} |
|
| 365 | + | /> |
|
| 366 | + | ) |
|
| 367 | + | } |
|
| 368 | + | ||
| 369 | + | function SidebarContent({ className, ...props }: React.ComponentProps<"div">) { |
|
| 370 | + | return ( |
|
| 371 | + | <div |
|
| 372 | + | data-slot="sidebar-content" |
|
| 373 | + | data-sidebar="content" |
|
| 374 | + | className={cn( |
|
| 375 | + | "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", |
|
| 376 | + | className |
|
| 377 | + | )} |
|
| 378 | + | {...props} |
|
| 379 | + | /> |
|
| 380 | + | ) |
|
| 381 | + | } |
|
| 382 | + | ||
| 383 | + | function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) { |
|
| 384 | + | return ( |
|
| 385 | + | <div |
|
| 386 | + | data-slot="sidebar-group" |
|
| 387 | + | data-sidebar="group" |
|
| 388 | + | className={cn("relative flex w-full min-w-0 flex-col p-2", className)} |
|
| 389 | + | {...props} |
|
| 390 | + | /> |
|
| 391 | + | ) |
|
| 392 | + | } |
|
| 393 | + | ||
| 394 | + | function SidebarGroupLabel({ |
|
| 395 | + | className, |
|
| 396 | + | asChild = false, |
|
| 397 | + | ...props |
|
| 398 | + | }: React.ComponentProps<"div"> & { asChild?: boolean }) { |
|
| 399 | + | const Comp = asChild ? Slot : "div" |
|
| 400 | + | ||
| 401 | + | return ( |
|
| 402 | + | <Comp |
|
| 403 | + | data-slot="sidebar-group-label" |
|
| 404 | + | data-sidebar="group-label" |
|
| 405 | + | className={cn( |
|
| 406 | + | "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", |
|
| 407 | + | "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", |
|
| 408 | + | className |
|
| 409 | + | )} |
|
| 410 | + | {...props} |
|
| 411 | + | /> |
|
| 412 | + | ) |
|
| 413 | + | } |
|
| 414 | + | ||
| 415 | + | function SidebarGroupAction({ |
|
| 416 | + | className, |
|
| 417 | + | asChild = false, |
|
| 418 | + | ...props |
|
| 419 | + | }: React.ComponentProps<"button"> & { asChild?: boolean }) { |
|
| 420 | + | const Comp = asChild ? Slot : "button" |
|
| 421 | + | ||
| 422 | + | return ( |
|
| 423 | + | <Comp |
|
| 424 | + | data-slot="sidebar-group-action" |
|
| 425 | + | data-sidebar="group-action" |
|
| 426 | + | className={cn( |
|
| 427 | + | "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", |
|
| 428 | + | // Increases the hit area of the button on mobile. |
|
| 429 | + | "after:absolute after:-inset-2 md:after:hidden", |
|
| 430 | + | "group-data-[collapsible=icon]:hidden", |
|
| 431 | + | className |
|
| 432 | + | )} |
|
| 433 | + | {...props} |
|
| 434 | + | /> |
|
| 435 | + | ) |
|
| 436 | + | } |
|
| 437 | + | ||
| 438 | + | function SidebarGroupContent({ |
|
| 439 | + | className, |
|
| 440 | + | ...props |
|
| 441 | + | }: React.ComponentProps<"div">) { |
|
| 442 | + | return ( |
|
| 443 | + | <div |
|
| 444 | + | data-slot="sidebar-group-content" |
|
| 445 | + | data-sidebar="group-content" |
|
| 446 | + | className={cn("w-full text-sm", className)} |
|
| 447 | + | {...props} |
|
| 448 | + | /> |
|
| 449 | + | ) |
|
| 450 | + | } |
|
| 451 | + | ||
| 452 | + | function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) { |
|
| 453 | + | return ( |
|
| 454 | + | <ul |
|
| 455 | + | data-slot="sidebar-menu" |
|
| 456 | + | data-sidebar="menu" |
|
| 457 | + | className={cn("flex w-full min-w-0 flex-col gap-1", className)} |
|
| 458 | + | {...props} |
|
| 459 | + | /> |
|
| 460 | + | ) |
|
| 461 | + | } |
|
| 462 | + | ||
| 463 | + | function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) { |
|
| 464 | + | return ( |
|
| 465 | + | <li |
|
| 466 | + | data-slot="sidebar-menu-item" |
|
| 467 | + | data-sidebar="menu-item" |
|
| 468 | + | className={cn("group/menu-item relative", className)} |
|
| 469 | + | {...props} |
|
| 470 | + | /> |
|
| 471 | + | ) |
|
| 472 | + | } |
|
| 473 | + | ||
| 474 | + | const sidebarMenuButtonVariants = cva( |
|
| 475 | + | "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", |
|
| 476 | + | { |
|
| 477 | + | variants: { |
|
| 478 | + | variant: { |
|
| 479 | + | default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", |
|
| 480 | + | outline: |
|
| 481 | + | "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", |
|
| 482 | + | }, |
|
| 483 | + | size: { |
|
| 484 | + | default: "h-8 text-sm", |
|
| 485 | + | sm: "h-7 text-xs", |
|
| 486 | + | lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!", |
|
| 487 | + | }, |
|
| 488 | + | }, |
|
| 489 | + | defaultVariants: { |
|
| 490 | + | variant: "default", |
|
| 491 | + | size: "default", |
|
| 492 | + | }, |
|
| 493 | + | } |
|
| 494 | + | ) |
|
| 495 | + | ||
| 496 | + | function SidebarMenuButton({ |
|
| 497 | + | asChild = false, |
|
| 498 | + | isActive = false, |
|
| 499 | + | variant = "default", |
|
| 500 | + | size = "default", |
|
| 501 | + | tooltip, |
|
| 502 | + | className, |
|
| 503 | + | ...props |
|
| 504 | + | }: React.ComponentProps<"button"> & { |
|
| 505 | + | asChild?: boolean |
|
| 506 | + | isActive?: boolean |
|
| 507 | + | tooltip?: string | React.ComponentProps<typeof TooltipContent> |
|
| 508 | + | } & VariantProps<typeof sidebarMenuButtonVariants>) { |
|
| 509 | + | const Comp = asChild ? Slot : "button" |
|
| 510 | + | const { isMobile, state } = useSidebar() |
|
| 511 | + | ||
| 512 | + | const button = ( |
|
| 513 | + | <Comp |
|
| 514 | + | data-slot="sidebar-menu-button" |
|
| 515 | + | data-sidebar="menu-button" |
|
| 516 | + | data-size={size} |
|
| 517 | + | data-active={isActive} |
|
| 518 | + | className={cn(sidebarMenuButtonVariants({ variant, size }), className)} |
|
| 519 | + | {...props} |
|
| 520 | + | /> |
|
| 521 | + | ) |
|
| 522 | + | ||
| 523 | + | if (!tooltip) { |
|
| 524 | + | return button |
|
| 525 | + | } |
|
| 526 | + | ||
| 527 | + | if (typeof tooltip === "string") { |
|
| 528 | + | tooltip = { |
|
| 529 | + | children: tooltip, |
|
| 530 | + | } |
|
| 531 | + | } |
|
| 532 | + | ||
| 533 | + | return ( |
|
| 534 | + | <Tooltip> |
|
| 535 | + | <TooltipTrigger asChild>{button}</TooltipTrigger> |
|
| 536 | + | <TooltipContent |
|
| 537 | + | side="right" |
|
| 538 | + | align="center" |
|
| 539 | + | hidden={state !== "collapsed" || isMobile} |
|
| 540 | + | {...tooltip} |
|
| 541 | + | /> |
|
| 542 | + | </Tooltip> |
|
| 543 | + | ) |
|
| 544 | + | } |
|
| 545 | + | ||
| 546 | + | function SidebarMenuAction({ |
|
| 547 | + | className, |
|
| 548 | + | asChild = false, |
|
| 549 | + | showOnHover = false, |
|
| 550 | + | ...props |
|
| 551 | + | }: React.ComponentProps<"button"> & { |
|
| 552 | + | asChild?: boolean |
|
| 553 | + | showOnHover?: boolean |
|
| 554 | + | }) { |
|
| 555 | + | const Comp = asChild ? Slot : "button" |
|
| 556 | + | ||
| 557 | + | return ( |
|
| 558 | + | <Comp |
|
| 559 | + | data-slot="sidebar-menu-action" |
|
| 560 | + | data-sidebar="menu-action" |
|
| 561 | + | className={cn( |
|
| 562 | + | "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", |
|
| 563 | + | // Increases the hit area of the button on mobile. |
|
| 564 | + | "after:absolute after:-inset-2 md:after:hidden", |
|
| 565 | + | "peer-data-[size=sm]/menu-button:top-1", |
|
| 566 | + | "peer-data-[size=default]/menu-button:top-1.5", |
|
| 567 | + | "peer-data-[size=lg]/menu-button:top-2.5", |
|
| 568 | + | "group-data-[collapsible=icon]:hidden", |
|
| 569 | + | showOnHover && |
|
| 570 | + | "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0", |
|
| 571 | + | className |
|
| 572 | + | )} |
|
| 573 | + | {...props} |
|
| 574 | + | /> |
|
| 575 | + | ) |
|
| 576 | + | } |
|
| 577 | + | ||
| 578 | + | function SidebarMenuBadge({ |
|
| 579 | + | className, |
|
| 580 | + | ...props |
|
| 581 | + | }: React.ComponentProps<"div">) { |
|
| 582 | + | return ( |
|
| 583 | + | <div |
|
| 584 | + | data-slot="sidebar-menu-badge" |
|
| 585 | + | data-sidebar="menu-badge" |
|
| 586 | + | className={cn( |
|
| 587 | + | "text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none", |
|
| 588 | + | "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground", |
|
| 589 | + | "peer-data-[size=sm]/menu-button:top-1", |
|
| 590 | + | "peer-data-[size=default]/menu-button:top-1.5", |
|
| 591 | + | "peer-data-[size=lg]/menu-button:top-2.5", |
|
| 592 | + | "group-data-[collapsible=icon]:hidden", |
|
| 593 | + | className |
|
| 594 | + | )} |
|
| 595 | + | {...props} |
|
| 596 | + | /> |
|
| 597 | + | ) |
|
| 598 | + | } |
|
| 599 | + | ||
| 600 | + | function SidebarMenuSkeleton({ |
|
| 601 | + | className, |
|
| 602 | + | showIcon = false, |
|
| 603 | + | ...props |
|
| 604 | + | }: React.ComponentProps<"div"> & { |
|
| 605 | + | showIcon?: boolean |
|
| 606 | + | }) { |
|
| 607 | + | // Random width between 50 to 90%. |
|
| 608 | + | const width = React.useMemo(() => { |
|
| 609 | + | return `${Math.floor(Math.random() * 40) + 50}%` |
|
| 610 | + | }, []) |
|
| 611 | + | ||
| 612 | + | return ( |
|
| 613 | + | <div |
|
| 614 | + | data-slot="sidebar-menu-skeleton" |
|
| 615 | + | data-sidebar="menu-skeleton" |
|
| 616 | + | className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)} |
|
| 617 | + | {...props} |
|
| 618 | + | > |
|
| 619 | + | {showIcon && ( |
|
| 620 | + | <Skeleton |
|
| 621 | + | className="size-4 rounded-md" |
|
| 622 | + | data-sidebar="menu-skeleton-icon" |
|
| 623 | + | /> |
|
| 624 | + | )} |
|
| 625 | + | <Skeleton |
|
| 626 | + | className="h-4 max-w-(--skeleton-width) flex-1" |
|
| 627 | + | data-sidebar="menu-skeleton-text" |
|
| 628 | + | style={ |
|
| 629 | + | { |
|
| 630 | + | "--skeleton-width": width, |
|
| 631 | + | } as React.CSSProperties |
|
| 632 | + | } |
|
| 633 | + | /> |
|
| 634 | + | </div> |
|
| 635 | + | ) |
|
| 636 | + | } |
|
| 637 | + | ||
| 638 | + | function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) { |
|
| 639 | + | return ( |
|
| 640 | + | <ul |
|
| 641 | + | data-slot="sidebar-menu-sub" |
|
| 642 | + | data-sidebar="menu-sub" |
|
| 643 | + | className={cn( |
|
| 644 | + | "border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5", |
|
| 645 | + | "group-data-[collapsible=icon]:hidden", |
|
| 646 | + | className |
|
| 647 | + | )} |
|
| 648 | + | {...props} |
|
| 649 | + | /> |
|
| 650 | + | ) |
|
| 651 | + | } |
|
| 652 | + | ||
| 653 | + | function SidebarMenuSubItem({ |
|
| 654 | + | className, |
|
| 655 | + | ...props |
|
| 656 | + | }: React.ComponentProps<"li">) { |
|
| 657 | + | return ( |
|
| 658 | + | <li |
|
| 659 | + | data-slot="sidebar-menu-sub-item" |
|
| 660 | + | data-sidebar="menu-sub-item" |
|
| 661 | + | className={cn("group/menu-sub-item relative", className)} |
|
| 662 | + | {...props} |
|
| 663 | + | /> |
|
| 664 | + | ) |
|
| 665 | + | } |
|
| 666 | + | ||
| 667 | + | function SidebarMenuSubButton({ |
|
| 668 | + | asChild = false, |
|
| 669 | + | size = "md", |
|
| 670 | + | isActive = false, |
|
| 671 | + | className, |
|
| 672 | + | ...props |
|
| 673 | + | }: React.ComponentProps<"a"> & { |
|
| 674 | + | asChild?: boolean |
|
| 675 | + | size?: "sm" | "md" |
|
| 676 | + | isActive?: boolean |
|
| 677 | + | }) { |
|
| 678 | + | const Comp = asChild ? Slot : "a" |
|
| 679 | + | ||
| 680 | + | return ( |
|
| 681 | + | <Comp |
|
| 682 | + | data-slot="sidebar-menu-sub-button" |
|
| 683 | + | data-sidebar="menu-sub-button" |
|
| 684 | + | data-size={size} |
|
| 685 | + | data-active={isActive} |
|
| 686 | + | className={cn( |
|
| 687 | + | "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", |
|
| 688 | + | "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground", |
|
| 689 | + | size === "sm" && "text-xs", |
|
| 690 | + | size === "md" && "text-sm", |
|
| 691 | + | "group-data-[collapsible=icon]:hidden", |
|
| 692 | + | className |
|
| 693 | + | )} |
|
| 694 | + | {...props} |
|
| 695 | + | /> |
|
| 696 | + | ) |
|
| 697 | + | } |
|
| 698 | + | ||
| 699 | + | export { |
|
| 700 | + | Sidebar, |
|
| 701 | + | SidebarContent, |
|
| 702 | + | SidebarFooter, |
|
| 703 | + | SidebarGroup, |
|
| 704 | + | SidebarGroupAction, |
|
| 705 | + | SidebarGroupContent, |
|
| 706 | + | SidebarGroupLabel, |
|
| 707 | + | SidebarHeader, |
|
| 708 | + | SidebarInput, |
|
| 709 | + | SidebarInset, |
|
| 710 | + | SidebarMenu, |
|
| 711 | + | SidebarMenuAction, |
|
| 712 | + | SidebarMenuBadge, |
|
| 713 | + | SidebarMenuButton, |
|
| 714 | + | SidebarMenuItem, |
|
| 715 | + | SidebarMenuSkeleton, |
|
| 716 | + | SidebarMenuSub, |
|
| 717 | + | SidebarMenuSubButton, |
|
| 718 | + | SidebarMenuSubItem, |
|
| 719 | + | SidebarProvider, |
|
| 720 | + | SidebarRail, |
|
| 721 | + | SidebarSeparator, |
|
| 722 | + | SidebarTrigger, |
|
| 723 | + | useSidebar, |
|
| 724 | + | } |
| 1 | + | import { cn } from "@/lib/utils" |
|
| 2 | + | ||
| 3 | + | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { |
|
| 4 | + | return ( |
|
| 5 | + | <div |
|
| 6 | + | data-slot="skeleton" |
|
| 7 | + | className={cn("bg-accent animate-pulse rounded-md", className)} |
|
| 8 | + | {...props} |
|
| 9 | + | /> |
|
| 10 | + | ) |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | export { Skeleton } |
| 1 | + | import * as React from "react" |
|
| 2 | + | import * as SwitchPrimitive from "@radix-ui/react-switch" |
|
| 3 | + | ||
| 4 | + | import { cn } from "@/lib/utils" |
|
| 5 | + | ||
| 6 | + | function Switch({ |
|
| 7 | + | className, |
|
| 8 | + | ...props |
|
| 9 | + | }: React.ComponentProps<typeof SwitchPrimitive.Root>) { |
|
| 10 | + | return ( |
|
| 11 | + | <SwitchPrimitive.Root |
|
| 12 | + | data-slot="switch" |
|
| 13 | + | className={cn( |
|
| 14 | + | "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", |
|
| 15 | + | className |
|
| 16 | + | )} |
|
| 17 | + | {...props} |
|
| 18 | + | > |
|
| 19 | + | <SwitchPrimitive.Thumb |
|
| 20 | + | data-slot="switch-thumb" |
|
| 21 | + | className={cn( |
|
| 22 | + | "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0" |
|
| 23 | + | )} |
|
| 24 | + | /> |
|
| 25 | + | </SwitchPrimitive.Root> |
|
| 26 | + | ) |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | export { Switch } |
| 1 | + | import * as React from "react" |
|
| 2 | + | import * as TooltipPrimitive from "@radix-ui/react-tooltip" |
|
| 3 | + | ||
| 4 | + | import { cn } from "@/lib/utils" |
|
| 5 | + | ||
| 6 | + | function TooltipProvider({ |
|
| 7 | + | delayDuration = 0, |
|
| 8 | + | ...props |
|
| 9 | + | }: React.ComponentProps<typeof TooltipPrimitive.Provider>) { |
|
| 10 | + | return ( |
|
| 11 | + | <TooltipPrimitive.Provider |
|
| 12 | + | data-slot="tooltip-provider" |
|
| 13 | + | delayDuration={delayDuration} |
|
| 14 | + | {...props} |
|
| 15 | + | /> |
|
| 16 | + | ) |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | function Tooltip({ |
|
| 20 | + | ...props |
|
| 21 | + | }: React.ComponentProps<typeof TooltipPrimitive.Root>) { |
|
| 22 | + | return ( |
|
| 23 | + | <TooltipProvider> |
|
| 24 | + | <TooltipPrimitive.Root data-slot="tooltip" {...props} /> |
|
| 25 | + | </TooltipProvider> |
|
| 26 | + | ) |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | function TooltipTrigger({ |
|
| 30 | + | ...props |
|
| 31 | + | }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) { |
|
| 32 | + | return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} /> |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | function TooltipContent({ |
|
| 36 | + | className, |
|
| 37 | + | sideOffset = 0, |
|
| 38 | + | children, |
|
| 39 | + | ...props |
|
| 40 | + | }: React.ComponentProps<typeof TooltipPrimitive.Content>) { |
|
| 41 | + | return ( |
|
| 42 | + | <TooltipPrimitive.Portal> |
|
| 43 | + | <TooltipPrimitive.Content |
|
| 44 | + | data-slot="tooltip-content" |
|
| 45 | + | sideOffset={sideOffset} |
|
| 46 | + | className={cn( |
|
| 47 | + | "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", |
|
| 48 | + | className |
|
| 49 | + | )} |
|
| 50 | + | {...props} |
|
| 51 | + | > |
|
| 52 | + | {children} |
|
| 53 | + | <TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" /> |
|
| 54 | + | </TooltipPrimitive.Content> |
|
| 55 | + | </TooltipPrimitive.Portal> |
|
| 56 | + | ) |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } |
| 1 | + | import * as React from "react" |
|
| 2 | + | ||
| 3 | + | const MOBILE_BREAKPOINT = 768 |
|
| 4 | + | ||
| 5 | + | export function useIsMobile() { |
|
| 6 | + | const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined) |
|
| 7 | + | ||
| 8 | + | React.useEffect(() => { |
|
| 9 | + | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) |
|
| 10 | + | const onChange = () => { |
|
| 11 | + | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) |
|
| 12 | + | } |
|
| 13 | + | mql.addEventListener("change", onChange) |
|
| 14 | + | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) |
|
| 15 | + | return () => mql.removeEventListener("change", onChange) |
|
| 16 | + | }, []) |
|
| 17 | + | ||
| 18 | + | return !!isMobile |
|
| 19 | + | } |