Bug fixes and style updates to theme builder
This commit is contained in:
16
package-lock.json
generated
16
package-lock.json
generated
@@ -20,7 +20,7 @@
|
||||
"@tanstack/react-query-persist-client": "^5.90.22",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"jotai": "^2.17.0",
|
||||
"jotai": "^2.17.1",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-fontpicker-ts": "^1.3.0",
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.2.0",
|
||||
"@types/react": "^19.2.10",
|
||||
"@types/react": "^19.2.11",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.3",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
@@ -1846,9 +1846,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
|
||||
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
||||
"version": "19.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.11.tgz",
|
||||
"integrity": "sha512-tORuanb01iEzWvMGVGv2ZDhYZVeRMrw453DCSAIn/5yvcSVnMoUMTyf33nQJLahYEnv9xqrTNbgz4qY5EfSh0g==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
@@ -2936,9 +2936,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jotai": {
|
||||
"version": "2.17.0",
|
||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.17.0.tgz",
|
||||
"integrity": "sha512-DBPT7Vx7E7R/ZJL7Uq7yUA9+LLR0IRiRM+Bw5ugVwv8L8++RjlXVXZ2//ccucypc5lcyuHOW09rYcW77RurduQ==",
|
||||
"version": "2.17.1",
|
||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.17.1.tgz",
|
||||
"integrity": "sha512-TFNZZDa/0ewCLQyRC/Sq9crtixNj/Xdf/wmj9631xxMuKToVJZDbqcHIYN0OboH+7kh6P6tpIK7uKWClj86PKw==",
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@tanstack/react-query-persist-client": "^5.90.22",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"jotai": "^2.17.0",
|
||||
"jotai": "^2.17.1",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-fontpicker-ts": "^1.3.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.2.0",
|
||||
"@types/react": "^19.2.10",
|
||||
"@types/react": "^19.2.11",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.3",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
|
||||
@@ -85,7 +85,7 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
|
||||
>
|
||||
<Stack gap={0} h={'fit-content'} w={'73%'}>
|
||||
<Text className={textClasses.currentTheme} classNames={textClasses}>
|
||||
Current theme:
|
||||
Current:
|
||||
</Text>
|
||||
<Text
|
||||
w={'100%'}
|
||||
@@ -204,7 +204,7 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
|
||||
wrapper: selectClasses.menuSelectWrapper,
|
||||
root: selectClasses.menuSelectRoot,
|
||||
input: selectClasses.menuSelectInput,
|
||||
label: selectClasses.menuSelectLabel,
|
||||
label: selectClasses.menuSelectLabelNoOverflow,
|
||||
options: selectClasses.menuSelectOptions,
|
||||
option: selectClasses.menuSelectOption,
|
||||
}}
|
||||
|
||||
@@ -120,8 +120,8 @@ export function ThemeModal({
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
},
|
||||
input: {
|
||||
color: designerTheme.primaryColor,
|
||||
backgroundColor: designerTheme.backgroundColor,
|
||||
color: selectedTheme.primaryColor,
|
||||
backgroundColor: selectedTheme.backgroundColor,
|
||||
border: 'none',
|
||||
borderBottom: '1px solid var(--mantine-color-primary-0)',
|
||||
},
|
||||
|
||||
@@ -20,9 +20,9 @@ import { notifications } from '@mantine/notifications';
|
||||
import { useCommonHooks } from 'hooks';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import FontPicker from 'react-fontpicker-ts';
|
||||
import { FaClipboard, FaFont, FaGoogle } from 'react-icons/fa';
|
||||
import { FaCheck, FaClipboard, FaFont, FaGoogle } from 'react-icons/fa';
|
||||
import { FaShuffle } from 'react-icons/fa6';
|
||||
import { FiImage, FiSave, FiShuffle, FiX } from 'react-icons/fi';
|
||||
import { FiImage, FiRefreshCcw, FiSave, FiShuffle, FiX } from 'react-icons/fi';
|
||||
import {
|
||||
buttonClasses,
|
||||
colorSwatchClasses,
|
||||
@@ -68,6 +68,11 @@ export function ThemeEditor({
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
const [colorId, setColorId] = useState<string>('');
|
||||
const [colorValue, setColorValue] = useState('#000000');
|
||||
const [rgba, setRgba] = useState<{ r: number; g: number; b: number; a?: number }>({
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
});
|
||||
const [font, setFont] = useState<Theme['themeFont']>();
|
||||
const [customFontEnabled, setCustomFontEnabled] = useState<boolean>(false);
|
||||
const re = /(?:\.([^.]+))?$/;
|
||||
@@ -146,13 +151,17 @@ export function ThemeEditor({
|
||||
});
|
||||
|
||||
const handleSwatchClick = (colorId: string, colorValue: string) => {
|
||||
setRgba({
|
||||
r: Number.parseInt(colorValue.substring(1, 3), 16),
|
||||
g: Number.parseInt(colorValue.substring(3, 5), 16),
|
||||
b: Number.parseInt(colorValue.substring(5, 7), 16),
|
||||
a: Number.parseInt(colorValue.substring(7, 9), 16) || undefined,
|
||||
});
|
||||
setColorId(colorId || '');
|
||||
setColorValue(colorValue || '');
|
||||
closeBackgroundPicker();
|
||||
openPicker();
|
||||
setDesignerTheme(designerTheme);
|
||||
};
|
||||
|
||||
const randomColor = (withAlpha?: boolean) => {
|
||||
const letters = '0123456789ABCDEF';
|
||||
let color = '#';
|
||||
@@ -181,7 +190,16 @@ export function ThemeEditor({
|
||||
setFont(designerTheme.themeFont);
|
||||
}
|
||||
}, [selectedTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (colorValue) {
|
||||
setRgba({
|
||||
r: Number.parseInt(colorValue.substring(1, 3), 16),
|
||||
g: Number.parseInt(colorValue.substring(3, 5), 16),
|
||||
b: Number.parseInt(colorValue.substring(5, 7), 16),
|
||||
a: Number.parseInt(colorValue.substring(7, 9), 16) || undefined,
|
||||
});
|
||||
}
|
||||
}, [colorValue]);
|
||||
return (
|
||||
<>
|
||||
<Group p={'2rem'} w={'100%'} justify='space-between'>
|
||||
@@ -325,7 +343,7 @@ export function ThemeEditor({
|
||||
setColorValue(e);
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
backgroundColor: e,
|
||||
[colorId]: e,
|
||||
});
|
||||
}}
|
||||
format={'hexa'}
|
||||
@@ -347,14 +365,116 @@ export function ThemeEditor({
|
||||
mb={'1.5rem'}
|
||||
/>
|
||||
)}
|
||||
<Stack w={'90%'} gap={0}>
|
||||
<TextInput
|
||||
className={textInputClasses.default}
|
||||
className={textInputClasses.colorSwatchInput}
|
||||
classNames={textInputClasses}
|
||||
w={'90%'}
|
||||
label={'Hex Value'}
|
||||
value={colorValue}
|
||||
onChange={(e) => setColorValue(e.target.value)}
|
||||
/>
|
||||
<Group mt={'auto'} justify='center'>
|
||||
<Group gap={0} w={'100%'} justify='space-between'>
|
||||
<TextInput
|
||||
className={textInputClasses.colorSwatchInput}
|
||||
classNames={textInputClasses}
|
||||
label={'Red'}
|
||||
w={'23%'}
|
||||
value={rgba.r}
|
||||
onChange={(e) => {
|
||||
if (e.target.value) {
|
||||
setRgba({
|
||||
...rgba,
|
||||
r: Number.parseInt(e.target.value, 10),
|
||||
});
|
||||
setColorValue(
|
||||
`${colorValue.substring(0, 1)}${Number.parseInt(e.target.value, 10).toString(16).padStart(2, '0')}${colorValue.substring(3)}`,
|
||||
);
|
||||
} else {
|
||||
setRgba({
|
||||
...rgba,
|
||||
r: 0,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
className={textInputClasses.colorSwatchInput}
|
||||
classNames={textInputClasses}
|
||||
label={'Green'}
|
||||
w={'23%'}
|
||||
value={rgba.g}
|
||||
onChange={(e) => {
|
||||
if (e.target.value) {
|
||||
setRgba({
|
||||
...rgba,
|
||||
g: Number.parseInt(e.target.value, 10),
|
||||
});
|
||||
setColorValue(
|
||||
`${colorValue.substring(0, 3)}${Number.parseInt(e.target.value, 10).toString(16).padStart(2, '0')}${colorValue.substring(5)}`,
|
||||
);
|
||||
} else {
|
||||
setRgba({
|
||||
...rgba,
|
||||
g: 0,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
className={textInputClasses.colorSwatchInput}
|
||||
classNames={textInputClasses}
|
||||
label={'Blue'}
|
||||
w={'23%'}
|
||||
value={rgba.b}
|
||||
onChange={(e) => {
|
||||
if (e.target.value) {
|
||||
setRgba({
|
||||
...rgba,
|
||||
b: Number.parseInt(e.target.value, 10),
|
||||
});
|
||||
setColorValue(
|
||||
`${colorValue.substring(0, 5)}${Number.parseInt(e.target.value, 10).toString(16).padStart(2, '0')}${colorValue.substring(7)}`,
|
||||
);
|
||||
} else {
|
||||
setRgba({
|
||||
...rgba,
|
||||
b: 0,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
disabled={colorId !== 'searchColor'}
|
||||
className={textInputClasses.colorSwatchInput}
|
||||
classNames={textInputClasses}
|
||||
label={'Alpha'}
|
||||
w={'23%'}
|
||||
value={rgba.a}
|
||||
onChange={(e) => {
|
||||
if (e.target.value) {
|
||||
setRgba({
|
||||
...rgba,
|
||||
a: Number.parseInt(e.target.value, 10),
|
||||
});
|
||||
setColorValue(
|
||||
`${colorValue.substring(0, 7)}${Number.parseInt(e.target.value, 10).toString(16).padStart(2, '0')}`,
|
||||
);
|
||||
} else {
|
||||
setRgba({
|
||||
...rgba,
|
||||
a: 0,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Group mt={'auto'} justify='center' mih={'36px'} pb={'1.5rem'}>
|
||||
<Tooltip
|
||||
label={'Random Color'}
|
||||
className={toolTipClasses.default}
|
||||
classNames={toolTipClasses}
|
||||
>
|
||||
<Button
|
||||
c={'white'}
|
||||
onClick={() => {
|
||||
@@ -368,6 +488,31 @@ export function ThemeEditor({
|
||||
>
|
||||
<FiShuffle />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Reset Color'}
|
||||
className={toolTipClasses.default}
|
||||
classNames={toolTipClasses}
|
||||
>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
setColorValue(defaultTheme[colorId as keyof Theme] as string);
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
[colorId]: defaultTheme[colorId as keyof Theme],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FiRefreshCcw />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Done'}
|
||||
className={toolTipClasses.default}
|
||||
classNames={toolTipClasses}
|
||||
>
|
||||
<Button
|
||||
color={'green'}
|
||||
onClick={() => {
|
||||
@@ -379,21 +524,9 @@ export function ThemeEditor({
|
||||
closePicker();
|
||||
}}
|
||||
>
|
||||
<FiSave />
|
||||
</Button>
|
||||
<Button
|
||||
color={'red'}
|
||||
styles={{ label: { color: 'black' } }}
|
||||
onClick={() => {
|
||||
closePicker();
|
||||
setDesignerTheme({
|
||||
...designerTheme,
|
||||
[colorId]: designerTheme[colorId as keyof Theme],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FiX />
|
||||
<FaCheck />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -710,14 +843,22 @@ export function ThemeEditor({
|
||||
gap={0}
|
||||
key={key}
|
||||
>
|
||||
<Group w={'100%'} justify='space-between'>
|
||||
<Group
|
||||
w={'100%'}
|
||||
justify='space-between'
|
||||
pos={'relative'}
|
||||
>
|
||||
<Text
|
||||
w={'100%'}
|
||||
key={key}
|
||||
pl={2}
|
||||
style={{
|
||||
color: 'var(--mantine-color-primary-0)',
|
||||
backgroundColor:
|
||||
'var(--mantine-color-background-0)',
|
||||
overflow: 'clip',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{
|
||||
@@ -745,32 +886,33 @@ export function ThemeEditor({
|
||||
[key]: defaultTheme[key as keyof Theme],
|
||||
});
|
||||
}}
|
||||
pos={'absolute'}
|
||||
right={0}
|
||||
>
|
||||
<FiX />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
{key === 'name' && (
|
||||
<Group
|
||||
w={'100%'}
|
||||
justify='center'
|
||||
justify={
|
||||
key.includes('Color') ? 'space-between' : 'center'
|
||||
}
|
||||
gap={key.includes('Color') ? 4 : 0}
|
||||
bg='var(--mantine-color-accent-0)'
|
||||
c='var(--mantine-color-background-0)'
|
||||
style={{ borderRadius: 8, overflow: 'clip' }}
|
||||
h={48}
|
||||
>
|
||||
{key === 'name' && (
|
||||
<>
|
||||
<Text>{defaultTheme[key as keyof Theme]}</Text>
|
||||
<Text>→</Text>
|
||||
<Text>{designerTheme[key as keyof Theme]}</Text>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
{key.includes('Shadow') && (
|
||||
<Group
|
||||
w={'100%'}
|
||||
justify='center'
|
||||
bg='var(--mantine-color-accent-0)'
|
||||
style={{ borderRadius: 8, overflow: 'clip' }}
|
||||
h={48}
|
||||
>
|
||||
<>
|
||||
{defaultTheme[key as keyof Theme] === 0 ? (
|
||||
<Text>Off</Text>
|
||||
) : (
|
||||
@@ -782,16 +924,10 @@ export function ThemeEditor({
|
||||
) : (
|
||||
<Text>On</Text>
|
||||
)}
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
{key.includes('Font') && (
|
||||
<Group
|
||||
w={'100%'}
|
||||
justify='center'
|
||||
bg='var(--mantine-color-accent-0)'
|
||||
h={48}
|
||||
style={{ borderRadius: 8, overflow: 'clip' }}
|
||||
>
|
||||
<>
|
||||
<Text
|
||||
style={{
|
||||
fontFamily:
|
||||
@@ -815,16 +951,10 @@ export function ThemeEditor({
|
||||
{designerTheme[key as keyof Theme] ||
|
||||
`Default (${currentUser.defaultFont})`}
|
||||
</Text>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
{key === 'backgroundImage' && (
|
||||
<Group
|
||||
w={'100%'}
|
||||
justify='center'
|
||||
gap={4}
|
||||
bg='var(--mantine-color-accent-0)'
|
||||
style={{ borderRadius: 8, overflow: 'clip' }}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
w={'40%'}
|
||||
src={
|
||||
@@ -846,15 +976,10 @@ export function ThemeEditor({
|
||||
: `../../api/backgroundImages/thumbs/${designerTheme[key as keyof Theme] as string}`
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
{key.includes('Color') && (
|
||||
<Group
|
||||
w={'100%'}
|
||||
justify='space-between'
|
||||
bg='var(--mantine-color-accent-0)'
|
||||
style={{ borderRadius: 8, overflow: 'clip' }}
|
||||
>
|
||||
<>
|
||||
<Tooltip
|
||||
label='Copy color code'
|
||||
classNames={toolTipClasses}
|
||||
@@ -864,7 +989,9 @@ export function ThemeEditor({
|
||||
h={50}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
defaultTheme[key as keyof Theme] as string,
|
||||
defaultTheme[
|
||||
key as keyof Theme
|
||||
] as string,
|
||||
);
|
||||
notifications.show({
|
||||
title: (
|
||||
@@ -928,7 +1055,9 @@ export function ThemeEditor({
|
||||
h={50}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
defaultTheme[key as keyof Theme] as string,
|
||||
defaultTheme[
|
||||
key as keyof Theme
|
||||
] as string,
|
||||
);
|
||||
notifications.show({
|
||||
title: (
|
||||
@@ -982,8 +1111,9 @@ export function ThemeEditor({
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -18,6 +18,24 @@
|
||||
z-index: 1;
|
||||
top: 18px;
|
||||
}
|
||||
.menuSelectLabelNoOverflow {
|
||||
width: inherit;
|
||||
position: absolute;
|
||||
font-size: .75rem !important;
|
||||
padding-left: .5rem;
|
||||
padding-right: .5rem;
|
||||
padding-top: .125rem;
|
||||
padding-bottom: .125rem;
|
||||
margin-left: -8px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--mantine-color-primary-0);
|
||||
color: var(--mantine-color-background-0);
|
||||
z-index: 1;
|
||||
top: 18px;
|
||||
overflow: clip;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.menuSelectInput {
|
||||
border-color: var(--mantine-color-border-0) !important;
|
||||
background-color: var(--mantine-color-background-0) !important;
|
||||
|
||||
@@ -49,8 +49,8 @@
|
||||
border-radius: 4px;
|
||||
.input {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--mantine-color-primary-0);
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
@@ -67,3 +67,44 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.colorSwatchInput {
|
||||
&.root {
|
||||
padding-top: 0px;
|
||||
.label {
|
||||
position: relative;
|
||||
font-size: .75rem;
|
||||
padding-left: .5rem;
|
||||
padding-right: .5rem;
|
||||
padding-top: .125rem;
|
||||
padding-bottom: .125rem;
|
||||
margin-left: -8px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--mantine-color-primary-0);
|
||||
color: var(--mantine-color-background-0);
|
||||
z-index: 1;
|
||||
}
|
||||
.wrapper {
|
||||
display: flex;
|
||||
margin-top: -16px;
|
||||
padding-top: 8px;
|
||||
border-radius: 4px;
|
||||
.input {
|
||||
border: none;
|
||||
background-color: var(--mantine-color-background-0);
|
||||
color: var(--mantine-color-primary-0);
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
.section {
|
||||
color: var(--mantine-color-border-0);
|
||||
padding-left: .5rem;
|
||||
opacity: 1;
|
||||
}
|
||||
::placeholder {
|
||||
color: var(--mantine-color-accent-0);
|
||||
opacity: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user