diff options
| author | Yash <[email protected]> | 2024-04-01 02:19:54 +0000 |
|---|---|---|
| committer | Yash <[email protected]> | 2024-04-01 02:19:54 +0000 |
| commit | fed4aa58c9de71f5cd02449e482d38373c58277b (patch) | |
| tree | ef595d7a096885e4115818a52567a8955eeaf4e0 /apps/web | |
| parent | fix delete button on dark mode (diff) | |
| download | supermemory-fed4aa58c9de71f5cd02449e482d38373c58277b.tar.xz supermemory-fed4aa58c9de71f5cd02449e482d38373c58277b.zip | |
add new page popover
Diffstat (limited to 'apps/web')
| -rw-r--r-- | apps/web/package.json | 11 | ||||
| -rw-r--r-- | apps/web/pnpm-lock.yaml | 468 | ||||
| -rw-r--r-- | apps/web/src/app/globals.css | 6 | ||||
| -rw-r--r-- | apps/web/src/app/layout.tsx | 6 | ||||
| -rw-r--r-- | apps/web/src/app/ui/page.tsx | 2 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar.tsx | 145 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/ListItem.tsx | 189 | ||||
| -rw-r--r-- | apps/web/src/components/Sidebar/index.tsx | 46 | ||||
| -rw-r--r-- | apps/web/src/components/ui/drawer.tsx | 118 | ||||
| -rw-r--r-- | apps/web/src/components/ui/input.tsx | 18 | ||||
| -rw-r--r-- | apps/web/src/components/ui/popover.tsx | 31 | ||||
| -rw-r--r-- | apps/web/src/components/ui/textarea.tsx | 24 | ||||
| -rw-r--r-- | apps/web/src/lib/utils.ts | 19 | ||||
| -rw-r--r-- | apps/web/tailwind.config.ts | 27 |
14 files changed, 946 insertions, 164 deletions
diff --git a/apps/web/package.json b/apps/web/package.json index 297de1a7..d80e9de5 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -14,17 +14,20 @@ "prepare-local-db": "drizzle-kit generate:sqlite --out db/prepare.sql && wrangler d1 execute dev-d1-anycontext --local --file=db/prepare.sql" }, "dependencies": { - "react": "^18", - "react-dom": "^18", - "next": "14.1.0", "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "lucide-react": "^0.338.0", + "next": "14.1.0", + "react": "^18", + "react-dom": "^18", "tailwind-merge": "^2.2.1", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "vaul": "^0.9.0" }, "devDependencies": { "@cloudflare/next-on-pages": "1", diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index a66884c0..583b6b2a 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -8,9 +8,15 @@ dependencies: '@radix-ui/react-avatar': specifier: ^1.0.4 version: 1.0.4(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-dialog': + specifier: ^1.0.5 + version: 1.0.5(@types/[email protected])(@types/[email protected])([email protected])([email protected]) '@radix-ui/react-icons': specifier: ^1.3.0 version: 1.3.0([email protected]) + '@radix-ui/react-popover': + specifier: ^1.0.7 + version: 1.0.7(@types/[email protected])(@types/[email protected])([email protected])([email protected]) '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/[email protected])([email protected]) @@ -38,6 +44,9 @@ dependencies: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7([email protected]) + vaul: + specifier: ^0.9.0 + version: 0.9.0(@types/[email protected])(@types/[email protected])([email protected])([email protected]) devDependencies: '@cloudflare/next-on-pages': @@ -497,6 +506,34 @@ packages: engines: {node: '>=14'} dev: true + /@floating-ui/[email protected]: + resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} + dependencies: + '@floating-ui/utils': 0.2.1 + dev: false + + /@floating-ui/[email protected]: + resolution: {integrity: sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==} + dependencies: + '@floating-ui/core': 1.6.0 + '@floating-ui/utils': 0.2.1 + dev: false + + resolution: {integrity: sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/dom': 1.6.3 + react: 18.2.0 + react-dom: 18.2.0([email protected]) + dev: false + + /@floating-ui/[email protected]: + resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} + dev: false + /@humanwhocodes/[email protected]: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -693,6 +730,33 @@ packages: requiresBuild: true optional: true + /@radix-ui/[email protected]: + resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} + dependencies: + '@babel/runtime': 7.24.1 + dev: false + + /@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): + resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-primitive': 1.0.3(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@types/react': 18.2.73 + '@types/react-dom': 18.2.23 + react: 18.2.0 + react-dom: 18.2.0([email protected]) + dev: false + /@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==} peerDependencies: @@ -745,6 +809,102 @@ packages: react: 18.2.0 dev: false + /@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): + resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-context': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-focus-guards': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-focus-scope': 1.0.4(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-id': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-portal': 1.0.4(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-presence': 1.0.1(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-primitive': 1.0.3(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-slot': 1.0.2(@types/[email protected])([email protected]) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/[email protected])([email protected]) + '@types/react': 18.2.73 + '@types/react-dom': 18.2.23 + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0([email protected]) + react-remove-scroll: 2.5.5(@types/[email protected])([email protected]) + dev: false + + /@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-primitive': 1.0.3(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/[email protected])([email protected]) + '@types/react': 18.2.73 + '@types/react-dom': 18.2.23 + react: 18.2.0 + react-dom: 18.2.0([email protected]) + dev: false + + resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@types/react': 18.2.73 + react: 18.2.0 + dev: false + + /@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): + resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-primitive': 1.0.3(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/[email protected])([email protected]) + '@types/react': 18.2.73 + '@types/react-dom': 18.2.23 + react: 18.2.0 + react-dom: 18.2.0([email protected]) + dev: false + /@radix-ui/[email protected]([email protected]): resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==} peerDependencies: @@ -753,6 +913,129 @@ packages: react: 18.2.0 dev: false + resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/[email protected])([email protected]) + '@types/react': 18.2.73 + react: 18.2.0 + dev: false + + /@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): + resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-context': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-focus-guards': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-focus-scope': 1.0.4(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-id': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-popper': 1.1.3(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-portal': 1.0.4(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-presence': 1.0.1(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-primitive': 1.0.3(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-slot': 1.0.2(@types/[email protected])([email protected]) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/[email protected])([email protected]) + '@types/react': 18.2.73 + '@types/react-dom': 18.2.23 + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0([email protected]) + react-remove-scroll: 2.5.5(@types/[email protected])([email protected]) + dev: false + + /@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): + resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@floating-ui/react-dom': 2.0.8([email protected])([email protected]) + '@radix-ui/react-arrow': 1.0.3(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-compose-refs': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-context': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-primitive': 1.0.3(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-use-rect': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-use-size': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/rect': 1.0.1 + '@types/react': 18.2.73 + '@types/react-dom': 18.2.23 + react: 18.2.0 + react-dom: 18.2.0([email protected]) + dev: false + + /@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): + resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-primitive': 1.0.3(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + '@types/react': 18.2.73 + '@types/react-dom': 18.2.23 + react: 18.2.0 + react-dom: 18.2.0([email protected]) + dev: false + + /@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/[email protected])([email protected]) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/[email protected])([email protected]) + '@types/react': 18.2.73 + '@types/react-dom': 18.2.23 + react: 18.2.0 + react-dom: 18.2.0([email protected]) + dev: false + /@radix-ui/[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: @@ -803,6 +1086,36 @@ packages: react: 18.2.0 dev: false + resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/[email protected])([email protected]) + '@types/react': 18.2.73 + react: 18.2.0 + dev: false + + resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/[email protected])([email protected]) + '@types/react': 18.2.73 + react: 18.2.0 + dev: false + resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} peerDependencies: @@ -817,6 +1130,42 @@ packages: react: 18.2.0 dev: false + resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/rect': 1.0.1 + '@types/react': 18.2.73 + react: 18.2.0 + dev: false + + resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/[email protected])([email protected]) + '@types/react': 18.2.73 + react: 18.2.0 + dev: false + + /@radix-ui/[email protected]: + resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} + dependencies: + '@babel/runtime': 7.24.1 + dev: false + /@rollup/[email protected]: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -1280,6 +1629,13 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + dependencies: + tslib: 2.6.2 + dev: false + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: @@ -1812,6 +2168,10 @@ packages: engines: {node: '>=8'} dev: true + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dev: false + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -2982,6 +3342,11 @@ packages: hasown: 2.0.2 dev: true + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + dev: false + resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} dependencies: @@ -3225,6 +3590,12 @@ packages: side-channel: 1.0.6 dev: true + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + dependencies: + loose-envify: 1.4.0 + dev: false + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -4248,6 +4619,58 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true + resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.73 + react: 18.2.0 + react-style-singleton: 2.2.1(@types/[email protected])([email protected]) + tslib: 2.6.2 + dev: false + + resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.73 + react: 18.2.0 + react-remove-scroll-bar: 2.3.6(@types/[email protected])([email protected]) + react-style-singleton: 2.2.1(@types/[email protected])([email protected]) + tslib: 2.6.2 + use-callback-ref: 1.3.2(@types/[email protected])([email protected]) + use-sidecar: 1.1.2(@types/[email protected])([email protected]) + dev: false + + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.73 + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.2.0 + tslib: 2.6.2 + dev: false + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -5028,6 +5451,37 @@ packages: punycode: 2.3.1 dev: true + resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.73 + react: 18.2.0 + tslib: 2.6.2 + dev: false + + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.73 + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.6.2 + dev: false + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -5041,6 +5495,20 @@ packages: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true + /[email protected](@types/[email protected])(@types/[email protected])([email protected])([email protected]): + resolution: {integrity: sha512-bZSySGbAHiTXmZychprnX/dE0EsSige88xtyyL3/MCRbrFotRPQZo7UdydGXZWw+CKbNOw5Ow8gwAo93/nB/Cg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@radix-ui/react-dialog': 1.0.5(@types/[email protected])(@types/[email protected])([email protected])([email protected]) + react: 18.2.0 + react-dom: 18.2.0([email protected]) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + dev: false + resolution: {integrity: sha512-9tpC+wAtGSKiZf1TPjXZK6ntQDK0IZPd5xl99Ka91bcNQS9Z/8jJ/IKmTbgKw80z7i769ozMPtKj3/jTRnWuhw==} engines: {node: '>= 16'} diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 394bda43..2bb3159a 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -20,7 +20,7 @@ } body { - @apply bg-rgray-1 text-rgray-11; + @apply bg-rgray-2 text-rgray-11; /* color: rgb(var(--foreground-rgb)); background: linear-gradient( to bottom, @@ -30,6 +30,10 @@ body { rgb(var(--background-start-rgb)); */ } +[vaul-drawer-wrapper] { + @apply bg-rgray-1; +} + @layer utilities { .text-balance { text-wrap: balance; diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 5464c1d3..5b2ced94 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -16,7 +16,11 @@ export default function RootLayout({ }>) { return ( <html lang="en"> - <body className={roboto.className}>{children}</body> + <body className={roboto.className}> + <div vaul-drawer-wrapper="" className="min-w-screen overflow-x-hidden"> + {children} + </div> + </body> </html> ); } diff --git a/apps/web/src/app/ui/page.tsx b/apps/web/src/app/ui/page.tsx index 2f7c6a4b..43cff341 100644 --- a/apps/web/src/app/ui/page.tsx +++ b/apps/web/src/app/ui/page.tsx @@ -1,4 +1,4 @@ -import Sidebar from "@/components/Sidebar"; +import Sidebar from "@/components/Sidebar/index"; export default async function Home() { return ( diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx deleted file mode 100644 index 0644d779..00000000 --- a/apps/web/src/components/Sidebar.tsx +++ /dev/null @@ -1,145 +0,0 @@ -"use client"; -import { StoredContent } from "@/server/db/schema"; -import { - Plus, - MoreHorizontal, - ArrowUpRight, - Edit3, - Trash2, -} from "lucide-react"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "./ui/dropdown-menu"; - -import { useState, useEffect, useRef } from "react"; - -export default function Sidebar() { - const websties: StoredContent[] = [ - { - id: 1, - content: "", - title: "Visual Studio Code", - url: "https://code.visualstudio.com", - description: "", - image: "https://code.visualstudio.com/favicon.ico", - baseUrl: "https://code.visualstudio.com", - savedAt: new Date(), - }, - { - id: 1, - content: "", - title: "yxshv/vscode: An unofficial remake of vscode's landing page", - url: "https://github.com/yxshv/vscode", - description: "", - image: "https://github.com/favicon.ico", - baseUrl: "https://github.com", - savedAt: new Date(), - }, - ]; - - return ( - <aside className="bg-rgray-3 flex h-screen w-[25%] flex-col items-start justify-between py-5 pb-[50vh] font-light"> - <div className="flex items-center justify-center gap-1 px-5 text-xl font-normal"> - <img src="/brain.png" alt="logo" className="h-10 w-10" /> - SuperMemory - </div> - <div className="flex w-full flex-col items-start justify-center p-2"> - <h1 className="mb-1 flex w-full items-center justify-center px-3 font-normal"> - Websites - <button className="ml-auto "> - <Plus className="h-4 w-4 min-w-4" /> - </button> - </h1> - {websties.map((item) => ( - <ListItem key={item.id} item={item} /> - ))} - </div> - </aside> - ); -} - -export const ListItem: React.FC<{ item: StoredContent }> = ({ item }) => { - const [isEditing, setIsEditing] = useState(false); - const editInputRef = useRef<HTMLInputElement>(null); - - useEffect(() => { - if (isEditing) { - setTimeout(() => { - editInputRef.current?.focus(); - }, 500); - } - }, [isEditing]); - - return ( - <div className="hover:bg-rgray-5 focus-within:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>a>[data-upright-icon]]:block [&:hover>a>img]:hidden [&:hover>button]:opacity-100"> - <a - href={item.url} - target="_blank" - onClick={(e) => isEditing && e.preventDefault()} - className="flex w-[90%] items-center gap-2 focus:outline-none" - > - {isEditing ? ( - <Edit3 className="h-4 w-4" strokeWidth={1.5} /> - ) : ( - <> - <img - src={item.image ?? "/brain.png"} - alt={item.title ?? "Untitiled website"} - className="h-4 w-4" - /> - <ArrowUpRight - data-upright-icon - className="hidden h-4 w-4 min-w-4 scale-125" - strokeWidth={1.5} - /> - </> - )} - {isEditing ? ( - <input - ref={editInputRef} - autoFocus - className="text-rgray-12 w-full bg-transparent focus:outline-none" - placeholder={item.title ?? "Untitled website"} - onBlur={(e) => setIsEditing(false)} - onKeyDown={(e) => e.key === "Escape" && setIsEditing(false)} - /> - ) : ( - <span className="w-full truncate text-nowrap"> - {item.title ?? "Untitled website"} - </span> - )} - </a> - <DropdownMenu> - <DropdownMenuTrigger asChild> - <button className="ml-auto w-4 min-w-4 rounded-[0.15rem] opacity-0 focus:opacity-100 focus:outline-none"> - <MoreHorizontal className="h-4 w-4 min-w-4" /> - </button> - </DropdownMenuTrigger> - <DropdownMenuContent className="w-5"> - <DropdownMenuItem onClick={() => window.open(item.url)}> - <ArrowUpRight - className="mr-2 h-4 w-4 scale-125" - strokeWidth={1.5} - /> - Open - </DropdownMenuItem> - <DropdownMenuItem - onClick={(e) => { - setIsEditing(true); - }} - > - <Edit3 className="mr-2 h-4 w-4 " strokeWidth={1.5} /> - Edit - </DropdownMenuItem> - <DropdownMenuItem className="focus:bg-red-100 focus:text-red-400 dark:focus:bg-red-100/10"> - <Trash2 className="mr-2 h-4 w-4 " strokeWidth={1.5} /> - Delete - </DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - </div> - ); -}; diff --git a/apps/web/src/components/Sidebar/ListItem.tsx b/apps/web/src/components/Sidebar/ListItem.tsx new file mode 100644 index 00000000..e2648e4b --- /dev/null +++ b/apps/web/src/components/Sidebar/ListItem.tsx @@ -0,0 +1,189 @@ +"use client"; +import { cleanUrl } from "@/lib/utils"; +import { StoredContent } from "@/server/db/schema"; +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, +} from "../ui/dropdown-menu"; +import { Label } from "../ui/label"; +import { + ArrowUpRight, + MoreHorizontal, + Edit3, + Trash2, + Save, + ChevronRight, + Plus, +} from "lucide-react"; +import { useState } from "react"; +import { + Drawer, + DrawerContent, + DrawerHeader, + DrawerTitle, + DrawerDescription, + DrawerFooter, + DrawerClose, +} from "../ui/drawer"; +import { Input } from "../ui/input"; +import { Textarea } from "../ui/textarea"; +import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; + +export const ListItem: React.FC<{ item: StoredContent }> = ({ item }) => { + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [isEditDrawerOpen, setIsEditDrawerOpen] = useState(false); + + return ( + <div className="hover:bg-rgray-5 focus-within:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>a>div>[data-upright-icon]]:scale-125 [&:hover>a>div>[data-upright-icon]]:opacity-100 [&:hover>a>div>[data-upright-icon]]:delay-150 [&:hover>a>div>img]:scale-75 [&:hover>a>div>img]:opacity-0 [&:hover>a>div>img]:delay-0 [&:hover>button]:opacity-100"> + <a + href={item.url} + target="_blank" + className="flex w-[90%] items-center gap-2 focus-visible:outline-none" + > + <div className="relative h-4 min-w-4"> + <img + src={item.image ?? "/brain.png"} + alt={item.title ?? "Untitiled website"} + className="z-1 h-4 w-4 transition-[transform,opacity] delay-150 duration-150" + /> + <ArrowUpRight + data-upright-icon + className="absolute left-1/2 top-1/2 z-[2] h-4 w-4 min-w-4 -translate-x-1/2 -translate-y-1/2 scale-75 opacity-0 transition-[transform,opacity] duration-150" + strokeWidth={1.5} + /> + </div> + + <span className="w-full truncate text-nowrap"> + {item.title ?? "Untitled website"} + </span> + </a> + <DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}> + <DropdownMenuTrigger asChild> + <button className="ml-auto w-4 min-w-4 rounded-[0.15rem] opacity-0 focus-visible:opacity-100 focus-visible:outline-none"> + <MoreHorizontal className="h-4 w-4 min-w-4" /> + </button> + </DropdownMenuTrigger> + <DropdownMenuContent className="w-5"> + <DropdownMenuItem onClick={() => window.open(item.url)}> + <ArrowUpRight + className="mr-2 h-4 w-4 scale-125" + strokeWidth={1.5} + /> + Open + </DropdownMenuItem> + <DropdownMenuItem + onClick={() => { + setIsDropdownOpen(false); + setIsEditDrawerOpen(true); + }} + > + <Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} /> + Edit + </DropdownMenuItem> + <DropdownMenuItem className="focus-visible:bg-red-100 focus-visible:text-red-400 dark:focus-visible:bg-red-100/10"> + <Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} /> + Delete + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + <Drawer + shouldScaleBackground + open={isEditDrawerOpen} + onOpenChange={setIsEditDrawerOpen} + > + <DrawerContent className="pb-10 lg:px-[25vw]"> + <DrawerHeader className="relative mt-10 px-0"> + <DrawerTitle className=" flex w-full justify-between"> + Edit Page Details + </DrawerTitle> + <DrawerDescription>Change the page details</DrawerDescription> + <a + target="_blank" + href={item.url} + className="text-rgray-11/90 bg-rgray-3 text-md absolute right-0 top-0 flex w-min translate-y-1/2 items-center justify-center gap-1 rounded-full px-5 py-1" + > + <img src={item.image ?? "/brain.png"} className="h-4 w-4" /> + {cleanUrl(item.url)} + </a> + </DrawerHeader> + + <div className="mt-5"> + <Label>Title</Label> + <Input + className="" + required + value={item.title ?? ""} + placeholder={item.title ?? "Enter the title for the page"} + /> + </div> + <div className="mt-5"> + <Label>Additional Context</Label> + <Textarea + className="" + value={item.content ?? ""} + placeholder={"Enter additional context for this page"} + /> + </div> + <DrawerFooter className="flex flex-row-reverse items-center justify-end px-0 pt-5"> + <DrawerClose className="flex items-center justify-center rounded-md px-3 py-2 ring-2 ring-transparent transition hover:bg-blue-100 hover:text-blue-400 focus-visible:bg-blue-100 focus-visible:text-blue-400 focus-visible:outline-none focus-visible:ring-blue-200 dark:hover:bg-blue-100/10 dark:focus-visible:bg-blue-100/10 dark:focus-visible:ring-blue-200/30"> + <Save className="mr-2 h-4 w-4 " strokeWidth={1.5} /> + Save + </DrawerClose> + <DrawerClose className="hover:bg-rgray-3 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 flex items-center justify-center rounded-md px-3 py-2 ring-2 ring-transparent transition focus-visible:outline-none"> + Cancel + </DrawerClose> + <DrawerClose className="mr-auto flex items-center justify-center rounded-md bg-red-100 px-3 py-2 text-red-400 ring-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-red-200 dark:bg-red-100/10 dark:focus-visible:ring-red-200/30"> + <Trash2 className="mr-2 h-4 w-4 " strokeWidth={1.5} /> + Delete + </DrawerClose> + </DrawerFooter> + </DrawerContent> + </Drawer> + </div> + ); +}; + +export const AddNewPagePopover: React.FC<{ + addNewUrl?: (url: string) => Promise<void>; +}> = ({ addNewUrl }) => { + const [isOpen, setIsOpen] = useState(false); + const [url, setUrl] = useState(""); + + return ( + <Popover open={isOpen} onOpenChange={setIsOpen}> + <PopoverTrigger asChild> + <button className="focus-visible:ring-rgray-7 ring-offset-rgray-3 ml-auto rounded-sm ring-2 ring-transparent ring-offset-2 focus-visible:outline-none"> + <Plus className="h-4 w-4 min-w-4" /> + </button> + </PopoverTrigger> + <PopoverContent align="start" side="top"> + <h1 className="mb-2 flex items-center justify-between "> + Add a new page + <button + onClick={() => { + setIsOpen(false); + addNewUrl?.(url); + }} + className="hover:bg-rgray-3 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 ring-offset-rgray-3 rounded-sm ring-2 ring-transparent ring-offset-2 transition focus-visible:outline-none" + > + <ChevronRight className="h-4 w-4" /> + </button> + </h1> + <Input + className="w-full" + autoFocus + onChange={(e) => setUrl(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + setIsOpen(false); + addNewUrl?.(url); + } + }} + placeholder="Enter the URL of the page" + /> + </PopoverContent> + </Popover> + ); +}; diff --git a/apps/web/src/components/Sidebar/index.tsx b/apps/web/src/components/Sidebar/index.tsx new file mode 100644 index 00000000..0a658b77 --- /dev/null +++ b/apps/web/src/components/Sidebar/index.tsx @@ -0,0 +1,46 @@ +"use server"; +import { StoredContent } from "@/server/db/schema"; +import { AddNewPagePopover, ListItem } from "./ListItem"; + +export default async function Sidebar() { + const pages: StoredContent[] = [ + { + id: 1, + content: "", + title: "Visual Studio Code", + url: "https://code.visualstudio.com", + description: "", + image: "https://code.visualstudio.com/favicon.ico", + baseUrl: "https://code.visualstudio.com", + savedAt: new Date(), + }, + { + id: 2, + content: "", + title: "yxshv/vscode: An unofficial remake of vscode's landing page", + url: "https://github.com/yxshv/vscode", + description: "", + image: "https://github.com/favicon.ico", + baseUrl: "https://github.com", + savedAt: new Date(), + }, + ]; + + return ( + <aside className="bg-rgray-3 flex h-screen w-[25%] flex-col items-start justify-between py-5 pb-[50vh] font-light"> + <div className="flex items-center justify-center gap-1 px-5 text-xl font-normal"> + <img src="/brain.png" alt="logo" className="h-10 w-10" /> + SuperMemory + </div> + <div className="flex w-full flex-col items-start justify-center p-2"> + <h1 className="mb-1 flex w-full items-center justify-center px-3 font-normal"> + Pages + <AddNewPagePopover /> + </h1> + {pages.map((item) => ( + <ListItem key={item.id} item={item} /> + ))} + </div> + </aside> + ); +} diff --git a/apps/web/src/components/ui/drawer.tsx b/apps/web/src/components/ui/drawer.tsx new file mode 100644 index 00000000..705ca01c --- /dev/null +++ b/apps/web/src/components/ui/drawer.tsx @@ -0,0 +1,118 @@ +"use client"; + +import * as React from "react"; +import { Drawer as DrawerPrimitive } from "vaul"; + +import { cn } from "@/lib/utils"; + +const Drawer = ({ + shouldScaleBackground = true, + ...props +}: React.ComponentProps<typeof DrawerPrimitive.Root>) => ( + <DrawerPrimitive.Root + shouldScaleBackground={shouldScaleBackground} + {...props} + /> +); +Drawer.displayName = "Drawer"; + +const DrawerTrigger = DrawerPrimitive.Trigger; + +const DrawerPortal = DrawerPrimitive.Portal; + +const DrawerClose = DrawerPrimitive.Close; + +const DrawerOverlay = React.forwardRef< + React.ElementRef<typeof DrawerPrimitive.Overlay>, + React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay> +>(({ className, ...props }, ref) => ( + <DrawerPrimitive.Overlay + ref={ref} + className={cn("fixed inset-0 z-50 bg-black/80", className)} + {...props} + /> +)); +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName; + +const DrawerContent = React.forwardRef< + React.ElementRef<typeof DrawerPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> +>(({ className, children, ...props }, ref) => ( + <DrawerPortal> + <DrawerOverlay /> + <DrawerPrimitive.Content + ref={ref} + className={cn( + "border-rgray-6 bg-rgray-2 fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border", + className, + )} + {...props} + > + <div className="bg-rgray-4 mx-auto mt-4 h-2 w-[100px] rounded-full " /> + {children} + </DrawerPrimitive.Content> + </DrawerPortal> +)); +DrawerContent.displayName = "DrawerContent"; + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} + {...props} + /> +); +DrawerHeader.displayName = "DrawerHeader"; + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn("mt-auto flex flex-col gap-2 p-4", className)} + {...props} + /> +); +DrawerFooter.displayName = "DrawerFooter"; + +const DrawerTitle = React.forwardRef< + React.ElementRef<typeof DrawerPrimitive.Title>, + React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title> +>(({ className, ...props }, ref) => ( + <DrawerPrimitive.Title + ref={ref} + className={cn( + "text-rgray-12 text-xl font-medium leading-none tracking-tight", + className, + )} + {...props} + /> +)); +DrawerTitle.displayName = DrawerPrimitive.Title.displayName; + +const DrawerDescription = React.forwardRef< + React.ElementRef<typeof DrawerPrimitive.Description>, + React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description> +>(({ className, ...props }, ref) => ( + <DrawerPrimitive.Description + ref={ref} + className={cn("text-rgray-11 text-md", className)} + {...props} + /> +)); +DrawerDescription.displayName = DrawerPrimitive.Description.displayName; + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +}; diff --git a/apps/web/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx index aae15c80..8a7a9340 100644 --- a/apps/web/src/components/ui/input.tsx +++ b/apps/web/src/components/ui/input.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {} @@ -11,15 +11,15 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>( <input type={type} className={cn( - "flex h-10 w-full rounded-md border border-gray-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-800 dark:bg-gray-950 dark:ring-offset-gray-950 dark:placeholder:text-gray-400 dark:focus-visible:ring-gray-300", - className + "border-rgray-6 text-rgray-12 ring-offset-rgray-2 placeholder:text-rgray-11 focus-visible:ring-rgray-7 flex h-10 w-full rounded-md border bg-transparent px-3 py-2 text-sm transition file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ", + className, )} ref={ref} {...props} /> - ) - } -) -Input.displayName = "Input" + ); + }, +); +Input.displayName = "Input"; -export { Input } +export { Input }; diff --git a/apps/web/src/components/ui/popover.tsx b/apps/web/src/components/ui/popover.tsx new file mode 100644 index 00000000..0c4563a8 --- /dev/null +++ b/apps/web/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +"use client"; + +import * as React from "react"; +import * as PopoverPrimitive from "@radix-ui/react-popover"; + +import { cn } from "@/lib/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverContent = React.forwardRef< + React.ElementRef<typeof PopoverPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + <PopoverPrimitive.Portal> + <PopoverPrimitive.Content + ref={ref} + align={align} + sideOffset={sideOffset} + className={cn( + "border-rgray-6 bg-rgray-3 text-rgray-11 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 rounded-md border p-4 shadow-md outline-none", + className, + )} + {...props} + /> + </PopoverPrimitive.Portal> +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent }; diff --git a/apps/web/src/components/ui/textarea.tsx b/apps/web/src/components/ui/textarea.tsx new file mode 100644 index 00000000..68d8e79e --- /dev/null +++ b/apps/web/src/components/ui/textarea.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface TextareaProps + extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} + +const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>( + ({ className, ...props }, ref) => { + return ( + <textarea + className={cn( + "border-rgray-6 ring-offset-rgray-2 placeholder:text-rgray-11 focus-visible:ring-rgray-7 flex min-h-[80px] w-full rounded-md border bg-transparent px-3 py-2 text-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", + className, + )} + ref={ref} + {...props} + /> + ); + }, +); +Textarea.displayName = "Textarea"; + +export { Textarea }; diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts index d084ccad..6b8f3fd9 100644 --- a/apps/web/src/lib/utils.ts +++ b/apps/web/src/lib/utils.ts @@ -1,6 +1,19 @@ -import { type ClassValue, clsx } from "clsx" -import { twMerge } from "tailwind-merge" +"use client"; +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); +} + +// removes http(s?):// and / from the url +export function cleanUrl(url: string) { + if (url.endsWith("/")) { + url = url.slice(0, -1); + } + return url.startsWith("https://") + ? url.slice(8) + : url.startsWith("http://") + ? url.slice(7) + : url; } diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts index e7d6dd55..19cb25be 100644 --- a/apps/web/tailwind.config.ts +++ b/apps/web/tailwind.config.ts @@ -8,6 +8,32 @@ const config: Config = { ], theme: { extend: { + keyframes: { + "scale-in": { + "0%": { + transform: "scale(0)", + transformOrigin: "var(--radix-popover-content-transform-origin)", + }, + "100%": { + transform: "scale(1)", + transformOrigin: "var(--radix-popover-content-transform-origin)", + }, + }, + "scale-out": { + "0%": { + transform: "scale(1)", + transformOrigin: "var(--radix-popover-content-transform-origin)", + }, + "100%": { + transform: "scale(0)", + transformOrigin: "var(--radix-popover-content-transform-origin)", + }, + }, + }, + animation: { + "scale-in": "scale-in 0.2s cubic-bezier(0.16, 1, 0.3, 1)", + "scale-out": "scale-out 0.2s cubic-bezier(0.16, 1, 0.3, 1)", + }, backgroundImage: { "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", "gradient-conic": @@ -31,6 +57,7 @@ const config: Config = { }, }, }, + darkMode: "class", plugins: [require("tailwindcss-animate")], }; export default config; |