Bug fixes and style updates to theme builder

This commit is contained in:
Sean Edmunds
2026-02-04 14:25:53 -06:00
parent 471ae99333
commit aa9fdd5be0
7 changed files with 485 additions and 296 deletions

16
package-lock.json generated
View File

@@ -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"
},

View File

@@ -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",

View File

@@ -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,
}}

View File

@@ -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)',
},

View File

@@ -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>
)
);

View File

@@ -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;

View File

@@ -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%;
}
}
}
}