enabled react-compiler; added color picker; menu bar sorted
This commit is contained in:
parent
66aadfd04d
commit
96b989799a
@ -5,6 +5,10 @@
|
|||||||
import "./src/env.js";
|
import "./src/env.js";
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
/** @type {import("next").NextConfig} */
|
||||||
const config = {};
|
const config = {
|
||||||
|
experimental: {
|
||||||
|
reactCompiler: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@ -45,6 +45,7 @@
|
|||||||
"@trpc/client": "^11.0.0-rc.446",
|
"@trpc/client": "^11.0.0-rc.446",
|
||||||
"@trpc/react-query": "^11.0.0-rc.446",
|
"@trpc/react-query": "^11.0.0-rc.446",
|
||||||
"@trpc/server": "^11.0.0-rc.446",
|
"@trpc/server": "^11.0.0-rc.446",
|
||||||
|
"babel-plugin-react-compiler": "19.0.0-beta-40c6c23-20250301",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"drizzle-orm": "^0.33.0",
|
"drizzle-orm": "^0.33.0",
|
||||||
@ -55,6 +56,7 @@
|
|||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"postgres": "^3.4.4",
|
"postgres": "^3.4.4",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-colorful": "^5.6.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-textarea-autosize": "^8.5.7",
|
"react-textarea-autosize": "^8.5.7",
|
||||||
|
|||||||
78
pnpm-lock.yaml
generated
78
pnpm-lock.yaml
generated
@ -83,6 +83,9 @@ importers:
|
|||||||
'@trpc/server':
|
'@trpc/server':
|
||||||
specifier: ^11.0.0-rc.446
|
specifier: ^11.0.0-rc.446
|
||||||
version: 11.0.0-rc.824(typescript@5.8.2)
|
version: 11.0.0-rc.824(typescript@5.8.2)
|
||||||
|
babel-plugin-react-compiler:
|
||||||
|
specifier: 19.0.0-beta-40c6c23-20250301
|
||||||
|
version: 19.0.0-beta-40c6c23-20250301
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
@ -94,16 +97,16 @@ importers:
|
|||||||
version: 0.33.0(@types/react@18.3.18)(postgres@3.4.5)(react@18.3.1)
|
version: 0.33.0(@types/react@18.3.18)(postgres@3.4.5)(react@18.3.1)
|
||||||
geist:
|
geist:
|
||||||
specifier: ^1.3.0
|
specifier: ^1.3.0
|
||||||
version: 1.3.1(next@15.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
version: 1.3.1(next@15.2.1(babel-plugin-react-compiler@19.0.0-beta-40c6c23-20250301)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.477.0
|
specifier: ^0.477.0
|
||||||
version: 0.477.0(react@18.3.1)
|
version: 0.477.0(react@18.3.1)
|
||||||
next:
|
next:
|
||||||
specifier: ^15.0.1
|
specifier: ^15.0.1
|
||||||
version: 15.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 15.2.1(babel-plugin-react-compiler@19.0.0-beta-40c6c23-20250301)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
next-auth:
|
next-auth:
|
||||||
specifier: 5.0.0-beta.25
|
specifier: 5.0.0-beta.25
|
||||||
version: 5.0.0-beta.25(next@15.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
version: 5.0.0-beta.25(next@15.2.1(babel-plugin-react-compiler@19.0.0-beta-40c6c23-20250301)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.4.4
|
specifier: ^0.4.4
|
||||||
version: 0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@ -113,6 +116,9 @@ importers:
|
|||||||
react:
|
react:
|
||||||
specifier: ^18.3.1
|
specifier: ^18.3.1
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
|
react-colorful:
|
||||||
|
specifier: ^5.6.1
|
||||||
|
version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.3.1
|
specifier: ^18.3.1
|
||||||
version: 18.3.1(react@18.3.1)
|
version: 18.3.1(react@18.3.1)
|
||||||
@ -233,10 +239,22 @@ packages:
|
|||||||
'@auth/drizzle-adapter@1.8.0':
|
'@auth/drizzle-adapter@1.8.0':
|
||||||
resolution: {integrity: sha512-cxApE0h5WcyDsgGix9hzmWmCz0qxvmMJexAOQmI6R/YXYxrZ/mKBKu0BlfgQBR6z2XvNWl4wbEGchwSenSCksQ==}
|
resolution: {integrity: sha512-cxApE0h5WcyDsgGix9hzmWmCz0qxvmMJexAOQmI6R/YXYxrZ/mKBKu0BlfgQBR6z2XvNWl4wbEGchwSenSCksQ==}
|
||||||
|
|
||||||
|
'@babel/helper-string-parser@7.25.9':
|
||||||
|
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.25.9':
|
||||||
|
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/runtime@7.26.9':
|
'@babel/runtime@7.26.9':
|
||||||
resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==}
|
resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/types@7.26.9':
|
||||||
|
resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@cspotcode/source-map-support@0.8.1':
|
'@cspotcode/source-map-support@0.8.1':
|
||||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -1719,6 +1737,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
|
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
babel-plugin-react-compiler@19.0.0-beta-40c6c23-20250301:
|
||||||
|
resolution: {integrity: sha512-himtjPafvMbA7PYnV2L+jprpB3h4rhx/n5s4L3gC654FOUsmsv5n4p8d6ufvK2zqUQs4kTOjgT2b4wnuDU32CA==}
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
@ -2990,6 +3011,12 @@ packages:
|
|||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
|
react-colorful@5.6.1:
|
||||||
|
resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
|
||||||
react-dom@18.3.1:
|
react-dom@18.3.1:
|
||||||
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3525,10 +3552,19 @@ snapshots:
|
|||||||
- '@simplewebauthn/server'
|
- '@simplewebauthn/server'
|
||||||
- nodemailer
|
- nodemailer
|
||||||
|
|
||||||
|
'@babel/helper-string-parser@7.25.9': {}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.25.9': {}
|
||||||
|
|
||||||
'@babel/runtime@7.26.9':
|
'@babel/runtime@7.26.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.14.1
|
regenerator-runtime: 0.14.1
|
||||||
|
|
||||||
|
'@babel/types@7.26.9':
|
||||||
|
dependencies:
|
||||||
|
'@babel/helper-string-parser': 7.25.9
|
||||||
|
'@babel/helper-validator-identifier': 7.25.9
|
||||||
|
|
||||||
'@cspotcode/source-map-support@0.8.1':
|
'@cspotcode/source-map-support@0.8.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/trace-mapping': 0.3.9
|
'@jridgewell/trace-mapping': 0.3.9
|
||||||
@ -4782,6 +4818,10 @@ snapshots:
|
|||||||
|
|
||||||
axobject-query@4.1.0: {}
|
axobject-query@4.1.0: {}
|
||||||
|
|
||||||
|
babel-plugin-react-compiler@19.0.0-beta-40c6c23-20250301:
|
||||||
|
dependencies:
|
||||||
|
'@babel/types': 7.26.9
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
balanced-match@1.0.2: {}
|
||||||
|
|
||||||
binary-extensions@2.3.0: {}
|
binary-extensions@2.3.0: {}
|
||||||
@ -5186,8 +5226,8 @@ snapshots:
|
|||||||
'@typescript-eslint/parser': 8.26.0(eslint@8.57.1)(typescript@5.8.2)
|
'@typescript-eslint/parser': 8.26.0(eslint@8.57.1)(typescript@5.8.2)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.8.3(eslint-plugin-import@2.31.0)(eslint@8.57.1)
|
eslint-import-resolver-typescript: 3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1)
|
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
||||||
eslint-plugin-react: 7.37.4(eslint@8.57.1)
|
eslint-plugin-react: 7.37.4(eslint@8.57.1)
|
||||||
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
|
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
|
||||||
@ -5206,7 +5246,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0)(eslint@8.57.1):
|
eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nolyfill/is-core-module': 1.0.39
|
'@nolyfill/is-core-module': 1.0.39
|
||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
@ -5217,18 +5257,18 @@ snapshots:
|
|||||||
stable-hash: 0.0.4
|
stable-hash: 0.0.4
|
||||||
tinyglobby: 0.2.12
|
tinyglobby: 0.2.12
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1)
|
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1):
|
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/parser': 8.26.0(eslint@8.57.1)(typescript@5.8.2)
|
'@typescript-eslint/parser': 8.26.0(eslint@8.57.1)(typescript@5.8.2)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.8.3(eslint-plugin-import@2.31.0)(eslint@8.57.1)
|
eslint-import-resolver-typescript: 3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -5236,7 +5276,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
|
|
||||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1):
|
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rtsao/scc': 1.1.0
|
'@rtsao/scc': 1.1.0
|
||||||
array-includes: 3.1.8
|
array-includes: 3.1.8
|
||||||
@ -5247,7 +5287,7 @@ snapshots:
|
|||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1)
|
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@ -5458,9 +5498,9 @@ snapshots:
|
|||||||
|
|
||||||
functions-have-names@1.2.3: {}
|
functions-have-names@1.2.3: {}
|
||||||
|
|
||||||
geist@1.3.1(next@15.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
|
geist@1.3.1(next@15.2.1(babel-plugin-react-compiler@19.0.0-beta-40c6c23-20250301)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
next: 15.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
next: 15.2.1(babel-plugin-react-compiler@19.0.0-beta-40c6c23-20250301)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|
||||||
get-intrinsic@1.3.0:
|
get-intrinsic@1.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5832,10 +5872,10 @@ snapshots:
|
|||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
next-auth@5.0.0-beta.25(next@15.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
|
next-auth@5.0.0-beta.25(next@15.2.1(babel-plugin-react-compiler@19.0.0-beta-40c6c23-20250301)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@auth/core': 0.37.2
|
'@auth/core': 0.37.2
|
||||||
next: 15.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
next: 15.2.1(babel-plugin-react-compiler@19.0.0-beta-40c6c23-20250301)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
next-themes@0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
next-themes@0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
@ -5843,7 +5883,7 @@ snapshots:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
next@15.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
next@15.2.1(babel-plugin-react-compiler@19.0.0-beta-40c6c23-20250301)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 15.2.1
|
'@next/env': 15.2.1
|
||||||
'@swc/counter': 0.1.3
|
'@swc/counter': 0.1.3
|
||||||
@ -5863,6 +5903,7 @@ snapshots:
|
|||||||
'@next/swc-linux-x64-musl': 15.2.1
|
'@next/swc-linux-x64-musl': 15.2.1
|
||||||
'@next/swc-win32-arm64-msvc': 15.2.1
|
'@next/swc-win32-arm64-msvc': 15.2.1
|
||||||
'@next/swc-win32-x64-msvc': 15.2.1
|
'@next/swc-win32-x64-msvc': 15.2.1
|
||||||
|
babel-plugin-react-compiler: 19.0.0-beta-40c6c23-20250301
|
||||||
sharp: 0.33.5
|
sharp: 0.33.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
@ -6159,6 +6200,11 @@ snapshots:
|
|||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
|
react-colorful@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
react-dom@18.3.1(react@18.3.1):
|
react-dom@18.3.1(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import "./styles.css";
|
|
||||||
import { Article } from "@/server/db/schema";
|
import { Article } from "@/server/db/schema";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -18,37 +17,44 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { articleSchema } from "@/lib/validation/zod/article";
|
import { articleSchema } from "@/lib/validation/zod/article";
|
||||||
import { debounce } from "@/lib/utils";
|
import { cn, debounce } from "@/lib/utils";
|
||||||
import { updateArticle } from "@/server/actions/article";
|
import { updateArticle } from "@/server/actions/article";
|
||||||
import Editor from "./editor";
|
import Editor from "./editor";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
export default ({ server_article }: { server_article: Article }) => {
|
export default ({ server_article }: { server_article: Article }) => {
|
||||||
const form = useForm<z.infer<typeof articleSchema>>({
|
const defaultValues = {
|
||||||
resolver: zodResolver(articleSchema),
|
|
||||||
defaultValues: {
|
|
||||||
title: server_article?.title ?? "",
|
title: server_article?.title ?? "",
|
||||||
content:
|
content:
|
||||||
server_article?.content ??
|
server_article?.content ??
|
||||||
`<h2>
|
`<h2>
|
||||||
Hey bearbeite mich!
|
Hey bearbeite mich!
|
||||||
</h2>`,
|
</h2>`,
|
||||||
},
|
};
|
||||||
|
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
const form = useForm<z.infer<typeof articleSchema>>({
|
||||||
|
resolver: zodResolver(articleSchema),
|
||||||
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Define a submit handler.
|
// 2. Define a submit handler.
|
||||||
async function onSubmit(values: z.infer<typeof articleSchema>) {
|
async function onSubmit(values: z.infer<typeof articleSchema>) {
|
||||||
|
setLoading(true);
|
||||||
await updateArticle(values, server_article.id);
|
await updateArticle(values, server_article.id);
|
||||||
|
setLoading(false);
|
||||||
|
form.reset(values);
|
||||||
}
|
}
|
||||||
const debouncedSubmit = React.useCallback(
|
const debouncedSubmit = React.useCallback(
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
form.handleSubmit(onSubmit)();
|
form.handleSubmit(onSubmit)();
|
||||||
}, 1000),
|
}, 3000),
|
||||||
[form],
|
[form],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="title"
|
name="title"
|
||||||
@ -56,6 +62,7 @@ export default ({ server_article }: { server_article: Article }) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
|
className="w-full resize-none text-4xl font-bold focus-visible:outline-none"
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
field.onChange(e);
|
field.onChange(e);
|
||||||
@ -67,15 +74,16 @@ export default ({ server_article }: { server_article: Article }) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<div className="flex w-full gap-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="content"
|
name="content"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem className="w-full">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Editor
|
<Editor
|
||||||
editorProviderProps={{
|
editorProviderProps={{
|
||||||
|
editorProps: { attributes: { class: "min-h-64" } },
|
||||||
content: field.value,
|
content: field.value,
|
||||||
onUpdate: (value) => {
|
onUpdate: (value) => {
|
||||||
field.onChange(value.editor.getHTML());
|
field.onChange(value.editor.getHTML());
|
||||||
@ -89,6 +97,19 @@ export default ({ server_article }: { server_article: Article }) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"sticky top-4 h-max w-full max-w-md rounded-md border-t-2 bg-muted p-4",
|
||||||
|
loading && "border-t-blue-600",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Badge>
|
||||||
|
{!form.formState.isDirty && !loading
|
||||||
|
? "gespeichert"
|
||||||
|
: "nicht gespeichert"}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { EditorProvider, EditorProviderProps } from "@tiptap/react";
|
import { EditorProvider, EditorProviderProps } from "@tiptap/react";
|
||||||
import { MenuBar } from "./menu-bar";
|
import { MenuBar } from "./menu-bar";
|
||||||
@ -12,12 +14,14 @@ function Editor({
|
|||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
<div className="rounded-md bg-gradient-to-b from-muted to-background p-2">
|
||||||
<EditorProvider
|
<EditorProvider
|
||||||
immediatelyRender={false}
|
immediatelyRender={false}
|
||||||
extensions={extensions}
|
extensions={extensions}
|
||||||
slotBefore={!readOnly && <MenuBar />}
|
slotBefore={!readOnly && <MenuBar />}
|
||||||
{...editorProviderProps}
|
{...editorProviderProps}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,9 @@ export const extensions = [
|
|||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
TextStyle.configure({ types: [ListItem.name] }),
|
TextStyle.configure({ types: [ListItem.name] }),
|
||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
|
code: false,
|
||||||
|
codeBlock: false,
|
||||||
|
horizontalRule: {},
|
||||||
bulletList: {
|
bulletList: {
|
||||||
keepMarks: true,
|
keepMarks: true,
|
||||||
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
|
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
import ColorPickerPopover from "@/components/color-picker/color-picker-popover";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { RGBAToHexA } from "@/lib/utils";
|
||||||
import { useCurrentEditor } from "@tiptap/react";
|
import { useCurrentEditor } from "@tiptap/react";
|
||||||
import {
|
import {
|
||||||
BoldIcon,
|
BoldIcon,
|
||||||
@ -17,6 +20,7 @@ import {
|
|||||||
SeparatorHorizontalIcon,
|
SeparatorHorizontalIcon,
|
||||||
StrikethroughIcon,
|
StrikethroughIcon,
|
||||||
TextIcon,
|
TextIcon,
|
||||||
|
TypeIcon,
|
||||||
UndoIcon,
|
UndoIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
@ -28,7 +32,10 @@ export const MenuBar = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="control-group my-4">
|
<div className="menu-bar mb-4 flex items-center justify-between space-y-2">
|
||||||
|
{/* <div className="flex w-full items-center justify-between">
|
||||||
|
|
||||||
|
</div> */}
|
||||||
<div className="Button-group flex items-center gap-1">
|
<div className="Button-group flex items-center gap-1">
|
||||||
<Button
|
<Button
|
||||||
size={"icon"}
|
size={"icon"}
|
||||||
@ -44,7 +51,9 @@ export const MenuBar = () => {
|
|||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||||
disabled={!editor.can().chain().focus().toggleItalic().run()}
|
disabled={!editor.can().chain().focus().toggleItalic().run()}
|
||||||
className={editor.isActive("italic") ? "is-active" : ""}
|
className={
|
||||||
|
editor.isActive("italic") ? "bg-foreground text-background" : ""
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ItalicIcon className="size-4" />
|
<ItalicIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -57,7 +66,7 @@ export const MenuBar = () => {
|
|||||||
>
|
>
|
||||||
<StrikethroughIcon className="size-4" />
|
<StrikethroughIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{/* <Button
|
||||||
size={"icon"}
|
size={"icon"}
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
onClick={() => editor.chain().focus().toggleCode().run()}
|
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||||
@ -65,7 +74,7 @@ export const MenuBar = () => {
|
|||||||
className={editor.isActive("code") ? "is-active" : ""}
|
className={editor.isActive("code") ? "is-active" : ""}
|
||||||
>
|
>
|
||||||
<CodeIcon className="size-4" />
|
<CodeIcon className="size-4" />
|
||||||
</Button>
|
</Button> */}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
@ -73,7 +82,7 @@ export const MenuBar = () => {
|
|||||||
onClick={() => editor.chain().focus().setParagraph().run()}
|
onClick={() => editor.chain().focus().setParagraph().run()}
|
||||||
className={editor.isActive("paragraph") ? "is-active" : ""}
|
className={editor.isActive("paragraph") ? "is-active" : ""}
|
||||||
>
|
>
|
||||||
<TextIcon className="size-4" />
|
<TypeIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -152,14 +161,14 @@ export const MenuBar = () => {
|
|||||||
>
|
>
|
||||||
<ListOrderedIcon className="size-4" />
|
<ListOrderedIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{/* <Button
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
size={"icon"}
|
size={"icon"}
|
||||||
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||||
className={editor.isActive("codeBlock") ? "is-active" : ""}
|
className={editor.isActive("codeBlock") ? "is-active" : ""}
|
||||||
>
|
>
|
||||||
<CodeSquareIcon className="size-4" />
|
<CodeSquareIcon className="size-4" />
|
||||||
</Button>
|
</Button> */}
|
||||||
<Button
|
<Button
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
size={"icon"}
|
size={"icon"}
|
||||||
@ -175,35 +184,12 @@ export const MenuBar = () => {
|
|||||||
>
|
>
|
||||||
<SeparatorHorizontalIcon className="size-4" />
|
<SeparatorHorizontalIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<ColorPickerPopover
|
||||||
variant={"outline"}
|
// initialColor={editor.getAttributes("textStyle").color}
|
||||||
onClick={() => editor.chain().focus().setHardBreak().run()}
|
onInput={(color) => editor.chain().focus().setColor(color).run()}
|
||||||
>
|
/>
|
||||||
Hard Break
|
</div>
|
||||||
</Button>
|
<div className="flex items-center gap-1">
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={() => editor.chain().focus().setColor("#958DF1").run()}
|
|
||||||
className={
|
|
||||||
editor.isActive("textStyle", { color: "#958DF1" })
|
|
||||||
? "is-active"
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Purple
|
|
||||||
</Button>
|
|
||||||
{/* <Button
|
|
||||||
variant={"outline"}
|
|
||||||
onClick={() => editor.chain().focus().unsetAllMarks().run()}
|
|
||||||
>
|
|
||||||
Formatierung aufheben
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={"outline"}
|
|
||||||
onClick={() => editor.chain().focus().clearNodes().run()}
|
|
||||||
>
|
|
||||||
Clear nodes
|
|
||||||
</Button> */}
|
|
||||||
<Button
|
<Button
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
size={"icon"}
|
size={"icon"}
|
||||||
|
|||||||
@ -7,16 +7,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* List styles */
|
/* List styles */
|
||||||
.tiptap ul,
|
/* .tiptap ul,
|
||||||
.tiptap ol {
|
.tiptap ol {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
margin: 1.25rem 1rem 1.25rem 0.4rem;
|
margin: 1.25rem 1rem 1.25rem 0.4rem;
|
||||||
}
|
} */
|
||||||
|
|
||||||
.tiptap ul li p,
|
.tiptap ul li p,
|
||||||
.tiptap ol li p {
|
.tiptap ol li p {
|
||||||
margin-top: 0.25em;
|
@apply my-1;
|
||||||
margin-bottom: 0.25em;
|
}
|
||||||
|
|
||||||
|
.tiptap ul,
|
||||||
|
ol {
|
||||||
|
@apply ml-4 p-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiptap ul {
|
||||||
|
@apply list-disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiptap ol {
|
||||||
|
@apply list-decimal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Heading styles */
|
/* Heading styles */
|
||||||
@ -26,42 +38,38 @@
|
|||||||
.tiptap h4,
|
.tiptap h4,
|
||||||
.tiptap h5,
|
.tiptap h5,
|
||||||
.tiptap h6 {
|
.tiptap h6 {
|
||||||
|
@apply my-2;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
margin-top: 2.5rem;
|
|
||||||
text-wrap: pretty;
|
text-wrap: pretty;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap h1,
|
.tiptap h1,
|
||||||
.tiptap h2 {
|
.tiptap h2 {
|
||||||
margin-top: 3.5rem;
|
@apply my-4;
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tiptap h1 {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap h2 {
|
.tiptap h2 {
|
||||||
font-size: 1.2rem;
|
@apply text-4xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap h3 {
|
.tiptap h3 {
|
||||||
font-size: 1.1rem;
|
@apply text-3xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap h4,
|
.tiptap h4 {
|
||||||
.tiptap h5,
|
@apply text-2xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiptap h5 {
|
||||||
|
@apply text-xl;
|
||||||
|
}
|
||||||
.tiptap h6 {
|
.tiptap h6 {
|
||||||
font-size: 1rem;
|
@apply text-lg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Code and preformatted text styles */
|
/* Code and preformatted text styles */
|
||||||
.tiptap code {
|
/* .tiptap code {
|
||||||
background-color: var(--purple-light);
|
@apply bg-foreground p-1 text-background;
|
||||||
border-radius: 0.4rem;
|
|
||||||
color: var(--black);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
padding: 0.25em 0.3em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap pre {
|
.tiptap pre {
|
||||||
@ -78,18 +86,29 @@
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
} */
|
||||||
|
|
||||||
/* Blockquote styles */
|
/* Blockquote styles */
|
||||||
.tiptap blockquote {
|
.tiptap blockquote {
|
||||||
border-left: 3px solid var(--gray-3);
|
@apply m-6 border-l-2 pl-4;
|
||||||
|
/* border-left: 3px solid var(--gray-3);
|
||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
padding-left: 1rem;
|
padding-left: 1rem; */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Horizontal rule styles */
|
/* Horizontal rule styles */
|
||||||
.tiptap hr {
|
.tiptap hr {
|
||||||
border: none;
|
@apply my-1;
|
||||||
border-top: 1px solid var(--gray-2);
|
}
|
||||||
margin: 2rem 0;
|
|
||||||
|
::selection {
|
||||||
|
@apply rounded-md bg-muted-foreground/25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-bar button {
|
||||||
|
@apply duration-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-bar .is-active {
|
||||||
|
@apply border-primary;
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/components/color-picker/color-picker-popover.tsx
Normal file
20
src/components/color-picker/color-picker-popover.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import ColorPicker, { type ColorPickerProps } from ".";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
|
|
||||||
|
export default function ColorPickerPopover(props: ColorPickerProps) {
|
||||||
|
return (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button style={{ backgroundColor: props.initialColor }} className="">
|
||||||
|
color
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<ColorPicker {...props} />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
src/components/color-picker/color-picker.css
Normal file
16
src/components/color-picker/color-picker.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.color-picker .react-colorful {
|
||||||
|
width: 100%;
|
||||||
|
height: 240px;
|
||||||
|
}
|
||||||
|
.color-picker .react-colorful__saturation {
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
.color-picker .react-colorful__hue {
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
}
|
||||||
|
.color-picker .react-colorful__hue-pointer {
|
||||||
|
width: 12px;
|
||||||
|
height: inherit;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
103
src/components/color-picker/index.tsx
Normal file
103
src/components/color-picker/index.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
"use client";
|
||||||
|
import "./color-picker.css";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { HexColorPicker } from "react-colorful";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { debounce } from "@/lib/utils";
|
||||||
|
|
||||||
|
const STORAGE_KEY = "savedColors";
|
||||||
|
const MAX_COLORS = 12;
|
||||||
|
const SELECT_DEBOUNCE = 500;
|
||||||
|
|
||||||
|
export type ColorPickerProps = {
|
||||||
|
onInput?: (color: string) => void;
|
||||||
|
initialColor?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ColorPicker({ onInput, initialColor }: ColorPickerProps) {
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [customColors, setCustomColors] = useState<string[]>([]);
|
||||||
|
const [color, setColor] = useState(initialColor ?? "#ff0000");
|
||||||
|
|
||||||
|
// Load colors from localStorage on mount
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialColor?.length) setColor(initialColor);
|
||||||
|
const storageColors = localStorage.getItem(STORAGE_KEY);
|
||||||
|
const storedColors = storageColors ? JSON.parse(storageColors) : [];
|
||||||
|
if (storedColors.length) {
|
||||||
|
setCustomColors(storedColors);
|
||||||
|
if (!initialColor?.length) setColor(storedColors[0]);
|
||||||
|
setMounted(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const persistColor = (newColor: string) => {
|
||||||
|
if (newColor.length < 2) return;
|
||||||
|
if (customColors[0] === newColor) return; // Prevent duplicate consecutive colors
|
||||||
|
|
||||||
|
const updatedColors = [
|
||||||
|
newColor,
|
||||||
|
...customColors.filter((c) => c !== newColor),
|
||||||
|
].slice(0, MAX_COLORS);
|
||||||
|
console.log(updatedColors);
|
||||||
|
|
||||||
|
setCustomColors(updatedColors);
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(updatedColors));
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectColor = (newColor: string) => {
|
||||||
|
persistColor(newColor);
|
||||||
|
onInput?.(newColor);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectColorDebounced = debounce((newColor: string) => {
|
||||||
|
selectColor(newColor);
|
||||||
|
}, SELECT_DEBOUNCE);
|
||||||
|
|
||||||
|
const handleColorChange = (newColor: string, skipDebounce = false) => {
|
||||||
|
setColor(newColor);
|
||||||
|
if (skipDebounce)
|
||||||
|
selectColor(newColor); // Delayed save
|
||||||
|
else selectColorDebounced(newColor);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="color-picker flex flex-col items-center gap-4">
|
||||||
|
{/* React Colorful Picker */}
|
||||||
|
<HexColorPicker
|
||||||
|
color={color}
|
||||||
|
onChange={handleColorChange}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Default Input (Native Color Picker) */}
|
||||||
|
<div className="flex w-full gap-2">
|
||||||
|
<div
|
||||||
|
className="size-8 rounded-md border"
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
className="h-8"
|
||||||
|
value={color}
|
||||||
|
onInput={(e) => handleColorChange(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Display Recent Colors */}
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{customColors.map((col) => (
|
||||||
|
<button
|
||||||
|
key={col}
|
||||||
|
className="size-8 rounded border"
|
||||||
|
style={{ backgroundColor: col }}
|
||||||
|
onClick={() => handleColorChange(col, true)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ColorPicker;
|
||||||
@ -20,3 +20,18 @@ export function debounce<T extends (...args: any[]) => void>(
|
|||||||
timeoutId = setTimeout(() => func(...args), delay);
|
timeoutId = setTimeout(() => func(...args), delay);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RGBAToHexA(rgba: string, forceRemoveAlpha = false) {
|
||||||
|
return (
|
||||||
|
"#" +
|
||||||
|
rgba
|
||||||
|
.replace(/^rgba?\(|\s+|\)$/g, "") // Get's rgba / rgb string values
|
||||||
|
.split(",") // splits them at ","
|
||||||
|
.filter((string, index) => !forceRemoveAlpha || index !== 3)
|
||||||
|
.map((string) => parseFloat(string)) // Converts them to numbers
|
||||||
|
.map((number, index) => (index === 3 ? Math.round(number * 255) : number)) // Converts alpha to 255 number
|
||||||
|
.map((number) => number.toString(16)) // Converts numbers to hex
|
||||||
|
.map((string) => (string.length === 1 ? "0" + string : string)) // Adds 0 when length of one number is 1
|
||||||
|
.join("")
|
||||||
|
); // Puts the array to togehter to a string
|
||||||
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export async function updateArticle(
|
|||||||
articleId,
|
articleId,
|
||||||
});
|
});
|
||||||
// if (!result[0]?.id?.length) return false;
|
// if (!result[0]?.id?.length) return false;
|
||||||
// return revalidatePath(`/artikel/${result[0].id}/edit`);
|
// return revalidatePath(`/artikel/${result[0]?.slug}/edit`);
|
||||||
}
|
}
|
||||||
export async function deleteArticle(articleId: string) {
|
export async function deleteArticle(articleId: string) {
|
||||||
const result = await api.article.delete({
|
const result = await api.article.delete({
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export const articleRouter = createTRPCRouter({
|
|||||||
.set(input.article)
|
.set(input.article)
|
||||||
.where(eq(articles.id, input.articleId))
|
.where(eq(articles.id, input.articleId))
|
||||||
.returning({
|
.returning({
|
||||||
id: articles.id,
|
slug: articles.slug,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
delete: protectedProcedure
|
delete: protectedProcedure
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user