added theme manager

This commit is contained in:
PhilReact 2025-04-28 16:13:41 +03:00
parent efc83c89aa
commit ba9062dbcf
11 changed files with 880 additions and 53 deletions

416
package-lock.json generated
View File

@ -41,6 +41,7 @@
"@tiptap/starter-kit": "^2.5.9",
"@transistorsoft/capacitor-background-fetch": "^6.0.1",
"@types/chrome": "^0.0.263",
"@uiw/react-color": "^2.5.1",
"adm-zip": "^0.5.16",
"asmcrypto.js": "2.3.2",
"axios": "^1.7.7",
@ -5900,6 +5901,399 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@uiw/color-convert": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.5.1.tgz",
"integrity": "sha512-p+P8Ho0Z1AbUprES0hcLEDAaXbGH92TmjckkRQZ5S7HcyQ+9ZXlSsDFILjFbYu/okVjx5VG59T57Dx84lv9AWA==",
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0"
}
},
"node_modules/@uiw/react-color": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color/-/react-color-2.5.1.tgz",
"integrity": "sha512-u6Kj7rdhsMOls2KItpHLkG8WTghDS2jYBucLeOLLJXJDs25TuEBI9d1o939og8cUJtTwBrowWFFU63a1kGsciA==",
"license": "MIT",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-alpha": "2.5.1",
"@uiw/react-color-block": "2.5.1",
"@uiw/react-color-chrome": "2.5.1",
"@uiw/react-color-circle": "2.5.1",
"@uiw/react-color-colorful": "2.5.1",
"@uiw/react-color-compact": "2.5.1",
"@uiw/react-color-editable-input": "2.5.1",
"@uiw/react-color-editable-input-hsla": "2.5.1",
"@uiw/react-color-editable-input-rgba": "2.5.1",
"@uiw/react-color-github": "2.5.1",
"@uiw/react-color-hue": "2.5.1",
"@uiw/react-color-material": "2.5.1",
"@uiw/react-color-name": "2.5.1",
"@uiw/react-color-saturation": "2.5.1",
"@uiw/react-color-shade-slider": "2.5.1",
"@uiw/react-color-sketch": "2.5.1",
"@uiw/react-color-slider": "2.5.1",
"@uiw/react-color-swatch": "2.5.1",
"@uiw/react-color-wheel": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-alpha": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-alpha/-/react-color-alpha-2.5.1.tgz",
"integrity": "sha512-hPsIgsnuOQrqinXt3Gt+87fHudbUvvPW+TpvRY0HS9v4ptFu5UsCc/7DPTVKTaL+p+0oaA6eTbziLzPLRLzgsQ==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-drag-event-interactive": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-block": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-block/-/react-color-block-2.5.1.tgz",
"integrity": "sha512-qvubiV0z0P3OxpNt6o1UQ3CVsjVBY1/n/oz6Gzzxx9YPqSClI04AtFjwOQxF7M17SYqXv+88y77gfEfPIqk5+A==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-editable-input": "2.5.1",
"@uiw/react-color-swatch": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-chrome": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-chrome/-/react-color-chrome-2.5.1.tgz",
"integrity": "sha512-m/CyRaWgmkW5aQTQ8AZwyvopYm+bhvX06uS+ezQjXDYDtjLvq7RbM0JLLNIOyMXke964R58fhoX4G06ZWd8ycA==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-alpha": "2.5.1",
"@uiw/react-color-editable-input": "2.5.1",
"@uiw/react-color-editable-input-hsla": "2.5.1",
"@uiw/react-color-editable-input-rgba": "2.5.1",
"@uiw/react-color-github": "2.5.1",
"@uiw/react-color-hue": "2.5.1",
"@uiw/react-color-saturation": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-circle": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-circle/-/react-color-circle-2.5.1.tgz",
"integrity": "sha512-+8zb/Ork1Q5f2bq0jN+GF7OyqY+2ZDYGrdZovN3EBZLMmERbg6TM2+1gTweeFsdiEM/gpteupJpwKpO1aBCocg==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-swatch": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-colorful": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-colorful/-/react-color-colorful-2.5.1.tgz",
"integrity": "sha512-Y/8Y2Kman6IZQpgs4tPTGPuTNr3fJIJxf4f13jll6xuaOsVZeDq9q+DlMErggL+5ICtaBr8gG+w68nCiY+QqKg==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-alpha": "2.5.1",
"@uiw/react-color-hue": "2.5.1",
"@uiw/react-color-saturation": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-compact": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-compact/-/react-color-compact-2.5.1.tgz",
"integrity": "sha512-5jHJcXEkjMwcghzCgSBU2rPMVjuuaJ7B6IxypNkafRQ4FkW/6bP9WpPkzcNXCZ/gPvSJ1OMQ+Y600mdO78qG5Q==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-editable-input": "2.5.1",
"@uiw/react-color-editable-input-rgba": "2.5.1",
"@uiw/react-color-swatch": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-editable-input": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input/-/react-color-editable-input-2.5.1.tgz",
"integrity": "sha512-0kr5vQJGPln8LObXwfI2YLiHFz2DW3Atgi51JXlrZUyyaVujXRgMTAc1fz/1RQR6cU2A4bweFaCQljcTsv+Cdg==",
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-editable-input-hsla": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input-hsla/-/react-color-editable-input-hsla-2.5.1.tgz",
"integrity": "sha512-gmnXB6JrYFAd8VN/EfNDJaTdkFHAnUxjzcsQjQyOEr046jDjWgEc/5o2uE1LwIvoJNg9Lo6LYsr37LnFWwsiLw==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-editable-input-rgba": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-editable-input-rgba": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input-rgba/-/react-color-editable-input-rgba-2.5.1.tgz",
"integrity": "sha512-rk6OxL9lTdRI45aNe3GbUghvaELk4knkEf0gvF/mPHxoeE+nNphSrO5gHm3HhoDOgaplp81VP3q4gUwcdjBzvw==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-editable-input": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-github": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-github/-/react-color-github-2.5.1.tgz",
"integrity": "sha512-t05rIy2ifReiVnjv3x+IVlJH7wvwtZugMeouDa/1Y7jIGZswO0zw3zMxz7qfHrzf5NVYWjmEF8QCj85ngv9brg==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-swatch": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-hue": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-hue/-/react-color-hue-2.5.1.tgz",
"integrity": "sha512-o7mjZhm+U4gHxaBXFxjPINeE3jWfiZAl7RUFqwn4PDZC8wvhU5hEKgJUvcXzErYro0ZYrE1fC/wUHRpI+vcEBg==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-alpha": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-material": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-material/-/react-color-material-2.5.1.tgz",
"integrity": "sha512-iPB4YfKVTNO1lSIQ16DMdDurDKvGTjv6Qwi/nq47yE3nnhB0YbOFwb/IZbWBS1sCTPx1an7dM2IZ+hYoYcjrXg==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-editable-input": "2.5.1",
"@uiw/react-color-editable-input-rgba": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-name": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-name/-/react-color-name-2.5.1.tgz",
"integrity": "sha512-JFb6DFz9kF2jI42MS/vtXZu1XzIrzcSIOqCwVkYWCQnSxOM9h+vd4pv2Yi1oy7IPgaadXUDkrGQSAvEkXU593Q==",
"dependencies": {
"colors-named": "^1.0.1",
"colors-named-hex": "^1.0.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0"
}
},
"node_modules/@uiw/react-color-saturation": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-saturation/-/react-color-saturation-2.5.1.tgz",
"integrity": "sha512-mQ6eGmn6dUXfScQrb5tP0TBGCpZWzrQuYOAiwK9u31IJaxFwD1NNAzkiienWe4MQkA5zmgz7Ol6FEdLN8K+vGw==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-drag-event-interactive": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-shade-slider": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-shade-slider/-/react-color-shade-slider-2.5.1.tgz",
"integrity": "sha512-hrscAmqmy/Od/usUPETaEuvsNRhUGvNArl73d7HK6e6FjbRFPDBq40LkvjETe8BJMbxrBXTMo6dK7DO08lYq9g==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-alpha": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-sketch": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-sketch/-/react-color-sketch-2.5.1.tgz",
"integrity": "sha512-eQgAnlSZvqoTt6frZa/j+tFdaIBEFneIdxEUfidD8hwvyu5OR/WLHnDy/4fYAxhehDp9Ej8eS3ZsCgPACBMOtA==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-alpha": "2.5.1",
"@uiw/react-color-editable-input": "2.5.1",
"@uiw/react-color-editable-input-rgba": "2.5.1",
"@uiw/react-color-hue": "2.5.1",
"@uiw/react-color-saturation": "2.5.1",
"@uiw/react-color-swatch": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-slider": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-slider/-/react-color-slider-2.5.1.tgz",
"integrity": "sha512-2yluI0Akp6UMXTeAJ4CEjL8flhIFpn3xUPsFXbQmBSzMYJygleVFmwhMye8LSA2PCe3UdaqA2cWXxWsTL0FbIg==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-color-alpha": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-swatch": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-swatch/-/react-color-swatch-2.5.1.tgz",
"integrity": "sha512-EQ7UEzxdohfsdpXmcEWNmK/uiznZovEKo6+j3OLrSU5pZGO7pxjR9sQMlscikvd8Mu1Mm3U0E6bJseo2acD4Lg==",
"dependencies": {
"@uiw/color-convert": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-color-wheel": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-color-wheel/-/react-color-wheel-2.5.1.tgz",
"integrity": "sha512-e3tDwDoC2T7zTapRRm/QxcOJ7IWJwNCoxZ/f97RL1Ib3gAN/k67H1bkR9TK7euRCUxGy031guxTgdKO9v19XFg==",
"dependencies": {
"@uiw/color-convert": "2.5.1",
"@uiw/react-drag-event-interactive": "2.5.1"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@uiw/react-drag-event-interactive": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@uiw/react-drag-event-interactive/-/react-drag-event-interactive-2.5.1.tgz",
"integrity": "sha512-GNxhxk5L4O5Gpi20A/BG5sO0GNBNwtNWJidJsJu3pgHUBErN4rhqTDXXu3BQTz5C8yOG5D02Y6Zq/6yu6ckImw==",
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
},
"peerDependencies": {
"@babel/runtime": ">=7.19.0",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@ -7774,6 +8168,28 @@
"color-support": "bin.js"
}
},
"node_modules/colors-named": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/colors-named/-/colors-named-1.0.2.tgz",
"integrity": "sha512-2ANq2r393PV9njYUD66UdfBcxR1slMqRA3QRTWgCx49JoCJ+kOhyfbQYxKJbPZQIhZUcNjVOs5AlyY1WwXec3w==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
}
},
"node_modules/colors-named-hex": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/colors-named-hex/-/colors-named-hex-1.0.2.tgz",
"integrity": "sha512-k6kq1e1pUCQvSVwIaGFq2l0LrkAPQZWyeuZn1Z8nOiYSEZiKoFj4qx690h2Kd34DFl9Me0gKS6MUwAMBJj8nuA==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",

View File

@ -46,6 +46,7 @@
"@tiptap/starter-kit": "^2.5.9",
"@transistorsoft/capacitor-background-fetch": "^6.0.1",
"@types/chrome": "^0.0.263",
"@uiw/react-color": "^2.5.1",
"adm-zip": "^0.5.16",
"asmcrypto.js": "2.3.2",
"axios": "^1.7.7",

View File

@ -48,7 +48,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
const [password, setPassword] = useState('');
const [isOpenSeedModal, setIsOpenSeedModal] = useState(false);
const [isLoadingEncryptSeed, setIsLoadingEncryptSeed] = useState(false);
const theme = useTheme();
const { isShow, onCancel, onOk, show } = useModal();
const { getRootProps, getInputProps } = useDropzone({
@ -216,7 +216,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
maxHeight: '60vh',
overflowY: 'auto',
overflowX: 'hidden',
backgroundColor: 'rgb(30 30 32 / 70%)',
backgroundColor: theme.palette.background.paper,
}}
>
{wallets?.map((wallet, idx) => {
@ -429,7 +429,7 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
bgcolor: theme.palette.background.default,
flexGrow: 1,
'&:hover': {
backgroundColor: theme.palette.background.paper,
backgroundColor: theme.palette.action.hover,
transform: 'scale(1.01)',
},
transition: 'all 0.1s ease-in-out',

View File

@ -110,7 +110,6 @@ export const BuyQortInformation = ({ balance }) => {
</Typography>
<List
sx={{
bgcolor: theme.palette.background.default,
maxWidth: 360,
width: '100%',
}}
@ -118,21 +117,13 @@ export const BuyQortInformation = ({ balance }) => {
>
<ListItem disablePadding>
<ListItemIcon>
<RadioButtonCheckedIcon
sx={{
color: theme.palette.primary.dark,
}}
/>
<RadioButtonCheckedIcon />
</ListItemIcon>
<ListItemText primary="Create transactions on the Qortal Blockchain" />
</ListItem>
<ListItem disablePadding>
<ListItemIcon>
<RadioButtonCheckedIcon
sx={{
color: theme.palette.primary.dark,
}}
/>
<RadioButtonCheckedIcon />
</ListItemIcon>
<ListItemText primary="Having at least 4 QORT in your balance allows you to send chat messages at near instant speed." />
</ListItem>

View File

@ -48,6 +48,7 @@ import { useVirtualizer } from '@tanstack/react-virtual';
import ErrorBoundary from '../../common/ErrorBoundary';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { getFee } from '../../background';
export const requestQueuePromos = new RequestQueueWithPromise(20);
export function utf8ToBase64(inputString: string): string {

View File

@ -18,6 +18,7 @@ import { TransitionProps } from '@mui/material/transitions';
import { Box, FormControlLabel, Switch, styled, useTheme } from '@mui/material';
import { enabledDevModeAtom } from '../../atoms/global';
import { useRecoilState } from 'recoil';
import ThemeManager from '../Theme/ThemeManager';
const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
padding: 8,
@ -185,6 +186,7 @@ export const Settings = ({ address, open, setOpen }) => {
label="Enable dev mode"
/>
)}
<ThemeManager />
</Box>
</Dialog>
</Fragment>

View File

@ -6,57 +6,129 @@ import {
useEffect,
useCallback,
} from 'react';
import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
import { darkTheme } from '../../styles/theme-dark';
import { lightTheme } from '../../styles/theme-light';
import {
ThemeProvider as MuiThemeProvider,
createTheme,
} from '@mui/material/styles';
import { lightThemeOptions } from '../../styles/theme-light';
import { darkThemeOptions } from '../../styles/theme-dark';
const defaultTheme = {
id: 'default',
name: 'Default Theme',
light: lightThemeOptions.palette,
dark: darkThemeOptions.palette,
};
const ThemeContext = createContext({
themeMode: 'light',
toggleTheme: () => {},
userThemes: [defaultTheme],
addUserTheme: (themes) => {},
setUserTheme: (theme) => {},
currentThemeId: 'default',
});
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
export const ThemeProvider = ({ children }) => {
const [themeMode, setThemeMode] = useState('light');
const [userThemes, setUserThemes] = useState([defaultTheme]);
const [currentThemeId, setCurrentThemeId] = useState('default');
const theme = useMemo(
() => (themeMode === 'light' ? lightTheme : darkTheme),
[themeMode]
);
const currentTheme =
userThemes.find((theme) => theme.id === currentThemeId) || defaultTheme;
const muiTheme = useMemo(() => {
if (themeMode === 'light') {
return createTheme({
...lightThemeOptions,
palette: {
...currentTheme.light,
},
});
} else {
return createTheme({
...lightThemeOptions,
palette: {
...currentTheme.dark,
},
});
}
}, [themeMode, currentTheme]);
const saveSettings = (
themes = userThemes,
mode = themeMode,
themeId = currentThemeId
) => {
localStorage.setItem(
'saved_ui_theme',
JSON.stringify({
mode,
userThemes: themes,
currentThemeId: themeId,
})
);
};
const toggleTheme = () => {
setThemeMode((prevMode) => {
const newMode = prevMode === 'light' ? 'dark' : 'light';
const themeProperties = {
mode: newMode,
};
localStorage.setItem('saved_ui_theme', JSON.stringify(themeProperties));
setThemeMode((prev) => {
const newMode = prev === 'light' ? 'dark' : 'light';
saveSettings(userThemes, newMode, currentThemeId);
return newMode;
});
};
const getSavedTheme = useCallback(async () => {
try {
const themeProperties = JSON.parse(
localStorage.getItem(`saved_ui_theme`) || '{}'
);
const addUserTheme = (themes) => {
setUserThemes(themes);
saveSettings(themes);
};
const theme = themeProperties?.mode || 'light';
setThemeMode(theme);
} catch (error) {
console.log('error', error);
const setUserTheme = (theme) => {
if (theme.id === 'default') {
setCurrentThemeId('default');
saveSettings(userThemes, themeMode, 'default');
} else {
setCurrentThemeId(theme.id);
saveSettings(userThemes, themeMode, theme.id);
}
};
const loadSettings = useCallback(() => {
const saved = localStorage.getItem('saved_ui_theme');
if (saved) {
try {
const parsed = JSON.parse(saved);
if (parsed.mode === 'light' || parsed.mode === 'dark')
setThemeMode(parsed.mode);
if (Array.isArray(parsed.userThemes)) {
const filteredThemes = parsed.userThemes.filter(
(theme) => theme.id !== 'default'
);
setUserThemes([defaultTheme, ...filteredThemes]);
}
if (parsed.currentThemeId) setCurrentThemeId(parsed.currentThemeId);
} catch (error) {
console.error('Failed to parse saved_ui_theme:', error);
}
}
}, []);
useEffect(() => {
getSavedTheme();
}, [getSavedTheme]);
loadSettings();
}, [loadSettings]);
return (
<ThemeContext.Provider value={{ themeMode, toggleTheme }}>
<MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>
<ThemeContext.Provider
value={{
themeMode,
toggleTheme,
userThemes,
addUserTheme,
setUserTheme,
currentThemeId,
}}
>
<MuiThemeProvider theme={muiTheme}>{children}</MuiThemeProvider>
</ThemeContext.Provider>
);
};

View File

@ -0,0 +1,309 @@
import React, { useState, useRef, useEffect } from 'react';
import {
Box,
Button,
IconButton,
Typography,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
List,
ListItemText,
ListItemSecondaryAction,
TextField,
Tabs,
Tab,
ListItemButton,
} from '@mui/material';
import { Sketch } from '@uiw/react-color';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
import AddIcon from '@mui/icons-material/Add';
import CheckIcon from '@mui/icons-material/Check';
import { useThemeContext } from './ThemeContext';
import { darkThemeOptions } from '../../styles/theme-dark';
import { lightThemeOptions } from '../../styles/theme-light';
import ShortUniqueId from 'short-unique-id';
import { rgbStringToHsva, rgbaStringToHsva } from '@uiw/color-convert';
const uid = new ShortUniqueId({ length: 8 });
function detectColorFormat(color) {
if (typeof color !== 'string') return null;
if (color.startsWith('rgba')) return 'rgba';
if (color.startsWith('rgb')) return 'rgb';
return null;
}
export default function ThemeManager() {
const { userThemes, addUserTheme, setUserTheme, currentThemeId } =
useThemeContext();
const [openEditor, setOpenEditor] = useState(false);
const [themeDraft, setThemeDraft] = useState({
id: '',
name: '',
light: {},
dark: {},
});
const [currentTab, setCurrentTab] = useState('light');
const nameInputRef = useRef(null);
useEffect(() => {
if (openEditor && nameInputRef.current) {
nameInputRef.current.focus();
}
}, [openEditor]);
const handleAddTheme = () => {
setThemeDraft({
id: '',
name: '',
light: structuredClone(lightThemeOptions.palette),
dark: structuredClone(darkThemeOptions.palette),
});
setOpenEditor(true);
};
const handleEditTheme = (themeId) => {
const themeToEdit = userThemes.find((theme) => theme.id === themeId);
if (themeToEdit) {
setThemeDraft({ ...themeToEdit });
setOpenEditor(true);
}
};
const handleSaveTheme = () => {
if (themeDraft.id) {
const updatedThemes = [...userThemes];
const index = updatedThemes.findIndex(
(theme) => theme.id === themeDraft.id
);
if (index !== -1) {
updatedThemes[index] = themeDraft;
addUserTheme(updatedThemes);
}
} else {
const newTheme = { ...themeDraft, id: uid.rnd() };
const updatedThemes = [...userThemes, newTheme];
addUserTheme(updatedThemes);
setUserTheme(newTheme);
}
setOpenEditor(false);
};
const handleDeleteTheme = (id) => {
const updatedThemes = userThemes.filter((theme) => theme.id !== id);
addUserTheme(updatedThemes);
if (id === currentThemeId) {
// Find the default theme object in the list
const defaultTheme = updatedThemes.find(
(theme) => theme.id === 'default'
);
if (defaultTheme) {
setUserTheme(defaultTheme);
} else {
// Emergency fallback
setUserTheme({
light: lightThemeOptions,
dark: darkThemeOptions,
});
}
}
};
const handleApplyTheme = (theme) => {
setUserTheme(theme);
};
const handleColorChange = (mode, fieldPath, color) => {
setThemeDraft((prev) => {
const updated = { ...prev };
const paths = fieldPath.split('.');
updated[mode][paths[0]][paths[1]] = color.hex;
return updated;
});
};
const renderColorPicker = (mode, label, fieldPath, currentValue) => {
let color = currentValue || '#ffffff';
const format = detectColorFormat(currentValue);
if (format === 'rgba') {
color = rgbaStringToHsva(currentValue);
} else if (format === 'rgb') {
color = rgbStringToHsva(currentValue);
}
return (
<Box
mb={2}
{...{ 'data-color-mode': mode === 'dark' ? 'dark' : 'light' }}
>
<Typography variant="body2" mb={1}>
{label}
</Typography>
<Sketch
key={`${mode}-${fieldPath}`}
color={color}
onChange={(color) => handleColorChange(mode, fieldPath, color)}
/>
</Box>
);
};
return (
<Box p={2}>
<Typography variant="h5" gutterBottom>
Theme Manager
</Typography>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={handleAddTheme}
>
Add Theme
</Button>
<List>
{userThemes?.map((theme, index) => (
<ListItemButton
key={theme?.id || index}
selected={theme?.id === currentThemeId}
>
<ListItemText
primary={`${theme?.name || `Theme ${index + 1}`} ${theme?.id === currentThemeId ? '(Current)' : ''}`}
/>
<ListItemSecondaryAction>
{theme.id !== 'default' && (
<>
<IconButton onClick={() => handleEditTheme(theme.id)}>
<EditIcon />
</IconButton>
<IconButton onClick={() => handleDeleteTheme(theme.id)}>
<DeleteIcon />
</IconButton>
</>
)}
<IconButton onClick={() => handleApplyTheme(theme)}>
<CheckIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItemButton>
))}
</List>
<Dialog
open={openEditor}
onClose={() => setOpenEditor(false)}
fullWidth
maxWidth="md"
>
<DialogTitle>
{themeDraft.id ? 'Edit Theme' : 'Add New Theme'}
</DialogTitle>
<DialogContent>
<TextField
inputRef={nameInputRef}
margin="dense"
label="Theme Name"
fullWidth
value={themeDraft.name}
onChange={(e) =>
setThemeDraft((prev) => ({ ...prev, name: e.target.value }))
}
/>
<Tabs
value={currentTab}
onChange={(e, newValue) => setCurrentTab(newValue)}
sx={{ mt: 2, mb: 2 }}
>
<Tab label="Light" value="light" />
<Tab label="Dark" value="dark" />
</Tabs>
<Box>
{renderColorPicker(
currentTab,
'Primary Main',
'primary.main',
themeDraft[currentTab]?.primary?.main
)}
{renderColorPicker(
currentTab,
'Primary Dark',
'primary.dark',
themeDraft[currentTab]?.primary?.dark
)}
{renderColorPicker(
currentTab,
'Primary Light',
'primary.light',
themeDraft[currentTab]?.primary?.light
)}
{renderColorPicker(
currentTab,
'Secondary Main',
'secondary.main',
themeDraft[currentTab]?.secondary?.main
)}
{renderColorPicker(
currentTab,
'Background Default',
'background.default',
themeDraft[currentTab]?.background?.default
)}
{renderColorPicker(
currentTab,
'Background Paper',
'background.paper',
themeDraft[currentTab]?.background?.paper
)}
{renderColorPicker(
currentTab,
'Background Surface',
'background.surface',
themeDraft[currentTab]?.background?.surface
)}
{renderColorPicker(
currentTab,
'Text Primary',
'text.primary',
themeDraft[currentTab]?.text?.primary
)}
{renderColorPicker(
currentTab,
'Text Secondary',
'text.secondary',
themeDraft[currentTab]?.text?.secondary
)}
{renderColorPicker(
currentTab,
'Border Main',
'border.main',
themeDraft[currentTab]?.border?.main
)}
{renderColorPicker(
currentTab,
'Border Subtle',
'border.subtle',
themeDraft[currentTab]?.border?.subtle
)}
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenEditor(false)}>Cancel</Button>
<Button
disabled={!themeDraft.name}
onClick={handleSaveTheme}
variant="contained"
>
Save
</Button>
</DialogActions>
</Dialog>
</Box>
);
}

View File

@ -0,0 +1,39 @@
[data-color-mode*='dark'] .w-color-sketch {
--sketch-background: #323232 !important;
}
[data-color-mode*='dark'] .w-color-swatch {
--sketch-swatch-border-top: 1px solid #525252 !important;
}
[data-color-mode*='dark'] .w-color-block {
--block-background-color: #323232 !important;
--block-box-shadow: rgb(0 0 0 / 10%) 0 1px !important;
}
[data-color-mode*='dark'] .w-color-editable-input {
--editable-input-label-color: #757575 !important;
--editable-input-box-shadow: #616161 0px 0px 0px 1px inset !important;
--editable-input-color: #bbb !important;
}
[data-color-mode*='dark'] .w-color-github {
--github-border: 1px solid rgba(0, 0, 0, 0.2) !important;
--github-background-color: #323232 !important;
--github-box-shadow: rgb(0 0 0 / 15%) 0px 3px 12px !important;
--github-arrow-border-color: rgba(0, 0, 0, 0.15) !important;
}
[data-color-mode*='dark'] .w-color-compact {
--compact-background-color: #323232 !important;
}
[data-color-mode*='dark'] .w-color-material {
--material-background-color: #323232 !important;
--material-border-bottom-color: #707070 !important;
}
[data-color-mode*='dark'] .w-color-alpha {
--alpha-pointer-background-color: #6a6a6a !important;
--alpha-pointer-box-shadow: rgb(0 0 0 / 37%) 0px 1px 4px 0px !important;
}

View File

@ -1,14 +1,14 @@
import { createTheme, ThemeOptions } from '@mui/material/styles';
import { commonThemeOptions } from './theme-common';
const darkThemeOptions: ThemeOptions = {
export const darkThemeOptions: ThemeOptions = {
...commonThemeOptions,
palette: {
mode: 'dark',
primary: {
main: 'rgb(46, 61, 96)',
dark: 'rgb(5, 20, 53)',
light: 'rgb(45, 92, 201)',
main: 'rgb(100, 155, 240)',
dark: 'rgb(45, 92, 201)',
light: 'rgb(130, 185, 255)',
},
secondary: {
main: 'rgb(69, 173, 255)',

View File

@ -1,7 +1,7 @@
import { createTheme, ThemeOptions } from '@mui/material/styles';
import { commonThemeOptions } from './theme-common';
const lightThemeOptions: ThemeOptions = {
export const lightThemeOptions: ThemeOptions = {
...commonThemeOptions,
palette: {
mode: 'light',
@ -26,10 +26,6 @@ const lightThemeOptions: ThemeOptions = {
main: 'rgba(0, 0, 0, 0.12)',
subtle: 'rgba(0, 0, 0, 0.08)',
},
border: {
main: 'rgba(0, 0, 0, 0.12)',
subtle: 'rgba(0, 0, 0, 0.08)',
},
},
components: {
MuiCard: {