From bfe80b5844b922ca46b286f1be935ac57d14ecdd Mon Sep 17 00:00:00 2001 From: Sean Edmunds Date: Fri, 27 Jun 2025 16:07:29 -0500 Subject: [PATCH] New Theme Builder UI --- package-lock.json | 54 +-- package.json | 8 +- .../components/Theming/ThemeExampleApp.tsx | 6 +- .../components/Theming/ThemeModal.tsx | 391 ++++++++++++------ .../Theming/ThemeModalColorSwatches.tsx | 4 +- .../components/Theming/ThemeModalContent.tsx | 323 ++++++++------- .../settings/components/Theming/Themes.tsx | 6 - 7 files changed, 479 insertions(+), 313 deletions(-) diff --git a/package-lock.json b/package-lock.json index 434d480..32c08b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,16 +13,16 @@ "@mantine/form": "^8.1.2", "@mantine/hooks": "^8.1.2", "@mantine/notifications": "^8.1.2", - "@tanstack/react-query": "^5.81.2", - "@tanstack/react-query-devtools": "^5.81.2", - "@tanstack/react-query-persist-client": "^5.81.2", + "@tanstack/react-query": "^5.81.4", + "@tanstack/react-query-devtools": "^5.81.4", + "@tanstack/react-query-persist-client": "^5.81.4", "idb-keyval": "^6.2.2", "jotai": "^2.12.5", "react": "^19.1.0", "react-dom": "^19.1.0", "react-icons": "^5.5.0", "react-router-dom": "^7.6.2", - "recharts": "^3.0.1" + "recharts": "^3.0.2" }, "devDependencies": { "@types/node": "^24.0.4", @@ -1630,9 +1630,9 @@ "license": "MIT" }, "node_modules/@tanstack/query-core": { - "version": "5.81.2", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.81.2.tgz", - "integrity": "sha512-QLYkPdrudoMATDFa3MiLEwRhNnAlzHWDf0LKaXUqJd0/+QxN8uTPi7bahRlxoAyH0UbLMBdeDbYzWALj7THOtw==", + "version": "5.81.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.81.4.tgz", + "integrity": "sha512-z0yBgRFTPIEYcjFFiahOwtEU3kEKpF5Rwls7UjBCsRghmhwIXyxd5U2p0yEmUfcwm85W4XZkt4dc1OWc5my3Yw==", "license": "MIT", "funding": { "type": "github", @@ -1650,12 +1650,12 @@ } }, "node_modules/@tanstack/query-persist-client-core": { - "version": "5.81.2", - "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.81.2.tgz", - "integrity": "sha512-FSLHr3DL+eezaKNiAexM7+KdgpXvLmUuMQhtORPIC7QuhemM6kfzcrQC5AN34zhvVh2PZXRcib2JnHKeTMjyyg==", + "version": "5.81.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.81.4.tgz", + "integrity": "sha512-8SlCCzIPBYCOfWRPUGkzJAdPMu+7JT42J51n6rWvaLVj20mzrVX2EKTtm7QoP19LdX+prS8ndCMClrILQi7nUA==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.81.2" + "@tanstack/query-core": "5.81.4" }, "funding": { "type": "github", @@ -1663,12 +1663,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.81.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.81.2.tgz", - "integrity": "sha512-pe8kFlTrL2zFLlcAj2kZk9UaYYHDk9/1hg9EBaoO3cxDhOZf1FRGJeziSXKrVZyxIfs7b3aoOj/bw7Lie0mDUg==", + "version": "5.81.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.81.4.tgz", + "integrity": "sha512-bBrf5kTNr9t5BnskvyHQGwe2LRZsgnmx1sClyRQbAHxhSJfnT6z9h27IfGpY6JtfEH5y5R9jJegTG/93zyox/A==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.81.2" + "@tanstack/query-core": "5.81.4" }, "funding": { "type": "github", @@ -1679,9 +1679,9 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.81.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.81.2.tgz", - "integrity": "sha512-TX0OQ4cbgX6z2uN8c9x0QUNbyePGyUGdcgrGnV6TYEJc7KPT8PqeASuzoA5NGw1CiMGvyFAkIGA2KipvhM9d1g==", + "version": "5.81.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.81.4.tgz", + "integrity": "sha512-zFg7rzOmM9Q+Ym/TujWR4HD+jKwQd+N8WuWZRmHU8sjI2flyxWTWIEQvl9BFt+HiNA6leS3ODZ9SHYmEb3zduw==", "license": "MIT", "dependencies": { "@tanstack/query-devtools": "5.81.2" @@ -1691,24 +1691,24 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.81.2", + "@tanstack/react-query": "^5.81.4", "react": "^18 || ^19" } }, "node_modules/@tanstack/react-query-persist-client": { - "version": "5.81.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.81.2.tgz", - "integrity": "sha512-HUlnB7FGqN/he4AOvR0vNA06Wn4dg3rEK7EgzenWfc0gQ9Aa6thqTWgT3Q27CnKiTkzhRn2uZJKRseTP4g54LQ==", + "version": "5.81.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.81.4.tgz", + "integrity": "sha512-8SlkNDZmWUZLKYYm6DvYybJYrZngnERx/7W1KLUQ3IPaANM9tfmtQjBQBhr1dagyebNK1h/c69IY24dfUaNVIA==", "license": "MIT", "dependencies": { - "@tanstack/query-persist-client-core": "5.81.2" + "@tanstack/query-persist-client-core": "5.81.4" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.81.2", + "@tanstack/react-query": "^5.81.4", "react": "^18 || ^19" } }, @@ -3785,9 +3785,9 @@ } }, "node_modules/recharts": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.0.1.tgz", - "integrity": "sha512-T5PZzeqzkdQpwdd/wG/hzuhaKAFTNY/PsezVlhdg4GbME4mfcau3DEYP4VCejEsUVOpX6hQTN6FJE4z9zL0RWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.0.2.tgz", + "integrity": "sha512-eDc3ile9qJU9Dp/EekSthQPhAVPG48/uM47jk+PF7VBQngxeW3cwQpPHb/GHC1uqwyCRWXcIrDzuHRVrnRryoQ==", "license": "MIT", "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", diff --git a/package.json b/package.json index ddcb564..2eb9106 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,16 @@ "@mantine/form": "^8.1.2", "@mantine/hooks": "^8.1.2", "@mantine/notifications": "^8.1.2", - "@tanstack/react-query": "^5.81.2", - "@tanstack/react-query-devtools": "^5.81.2", - "@tanstack/react-query-persist-client": "^5.81.2", + "@tanstack/react-query": "^5.81.4", + "@tanstack/react-query-devtools": "^5.81.4", + "@tanstack/react-query-persist-client": "^5.81.4", "idb-keyval": "^6.2.2", "jotai": "^2.12.5", "react": "^19.1.0", "react-dom": "^19.1.0", "react-icons": "^5.5.0", "react-router-dom": "^7.6.2", - "recharts": "^3.0.1" + "recharts": "^3.0.2" }, "devDependencies": { "@types/node": "^24.0.4", diff --git a/src/pages/settings/components/Theming/ThemeExampleApp.tsx b/src/pages/settings/components/Theming/ThemeExampleApp.tsx index 599c713..b32f6b9 100644 --- a/src/pages/settings/components/Theming/ThemeExampleApp.tsx +++ b/src/pages/settings/components/Theming/ThemeExampleApp.tsx @@ -22,7 +22,7 @@ function ExampleApp({ selectedTheme }: { selectedTheme: Theme }) { paddingTop: '1rem', }} > - + void; selectedTheme: Theme; setSelectedTheme: (selectedTheme: Theme) => void; - colorId: string; - setColorId: (colorId: string) => void; - colorValue: string; - setColorValue: (colorValue: string) => void; openPicker: () => void; backgroundList: string[]; pickerOpen: boolean; @@ -50,10 +45,18 @@ function ThemeModal({ const viewportRef = useRef(null); const [query, setQuery] = useState(''); const [hovered, setHovered] = useState(-1); + const [colorId, setColorId] = useState(''); + const [colorValue, setColorValue] = useState('#000000'); + const [designerTheme, setDesignerTheme] = useState({ + ...selectedTheme, + }); const filtered = backgroundList.filter((item: string) => item.toLowerCase().includes(query.toLowerCase()), ); + const [backgroundPicker, { open: openBackgroundPicker, close: closeBackgroundPicker }] = + useDisclosure(false); + const re = /(?:\.([^.]+))?$/; const items = filtered.map((item: string, index: number) => { return ( @@ -75,8 +78,8 @@ function ThemeModal({ alignItems: 'center', }} onClick={() => - setSelectedTheme({ - ...selectedTheme, + setDesignerTheme({ + ...designerTheme, backgroundImage: item, }) } @@ -116,18 +119,11 @@ function ThemeModal({ }); useEffect(() => { - if (selectedTheme) { - if (selectedTheme?.backgroundImage === 'none') { - setQuery(''); - } else { - setQuery(selectedTheme?.backgroundImage || ''); - } - } + setDesignerTheme(selectedTheme); }, [selectedTheme]); - return ( - - - - - {pickerOpen ? ( - + + + + + + { + closeBackgroundPicker(); + setColorId('primaryColor'); + openPicker(); + setColorValue(selectedTheme.primaryColor); + }} + color={selectedTheme.primaryColor} + bd={`1px solid ${selectedTheme.primaryColor}`} + pos={'absolute'} + top={'6.25rem'} + left={'1.5rem'} + /> + + + { + closeBackgroundPicker(); + setColorId('accentColor'); + openPicker(); + setColorValue(selectedTheme.accentColor); + }} + color={selectedTheme.accentColor} + bd={`1px solid ${selectedTheme.primaryColor}`} + pos={'absolute'} + bottom={'6rem'} + left={'11rem'} + /> + + + { + closeBackgroundPicker(); + setColorId('backgroundColor'); + openPicker(); + setColorValue(selectedTheme.backgroundColor); + }} + color={selectedTheme.backgroundColor} + bd={`1px solid ${selectedTheme.primaryColor}`} + pos={'absolute'} + bottom={'10rem'} + right={'3.5rem'} + /> + + + { + closeBackgroundPicker(); + setColorId('borderColor'); + openPicker(); + setColorValue(selectedTheme.borderColor); + }} + color={selectedTheme.borderColor} + bd={`1px solid ${selectedTheme.primaryColor}`} + pos={'absolute'} + top={'4.5rem'} + right={'7rem'} + /> + + + { + closeBackgroundPicker(); + setColorId('searchBackground'); + openPicker(); + setColorValue(selectedTheme.searchColor); + }} + color={selectedTheme.searchColor} + bd={`1px solid ${selectedTheme.primaryColor}`} + pos={'absolute'} + top={'3.25rem'} + left={'14.5rem'} + /> + + + { + closePicker(); + openBackgroundPicker(); + }} + pos={'absolute'} + right={0} + > + + + + + + {pickerOpen && ( + + + Change {colorId} + { + setColorValue(e); + setDesignerTheme({ + ...designerTheme, + [colorId]: e, + }); + }} + format={colorId === 'backgroundColor' ? 'hex' : 'hexa'} w={'90%'} - mb={colorId === 'background' ? '1.5rem' : undefined} + mb={colorId === 'backgroundColor' ? '1.5rem' : undefined} /> - - setColorValue(e.target.value)} - /> + setColorValue(e.target.value)} + /> + - - - ) : ( - + )} - - - - Search: - + + { + setQuery(''); + }} + /> + } + onChange={(event) => { + setQuery(event.currentTarget.value); + setHovered(-1); }} - onClick={() => { - setQuery(''); + onKeyDown={(event) => { + if (event.key === 'ArrowDown') { + event.preventDefault(); + setHovered((current) => { + const nextIndex = + current + 1 >= filtered.length ? current : current + 1; + viewportRef.current + ?.querySelectorAll('[data-list-item]') + ?.[nextIndex]?.scrollIntoView({ block: 'nearest' }); + return nextIndex; + }); + } + + if (event.key === 'ArrowUp') { + event.preventDefault(); + setHovered((current) => { + const nextIndex = current - 1 < 0 ? current : current - 1; + viewportRef.current + ?.querySelectorAll('[data-list-item]') + ?.[nextIndex]?.scrollIntoView({ block: 'nearest' }); + return nextIndex; + }); + } }} /> - } - onChange={(event) => { - setQuery(event.currentTarget.value); - setHovered(-1); - }} - onKeyDown={(event) => { - if (event.key === 'ArrowDown') { - event.preventDefault(); - setHovered((current) => { - const nextIndex = - current + 1 >= filtered.length ? current : current + 1; - viewportRef.current - ?.querySelectorAll('[data-list-item]') - ?.[nextIndex]?.scrollIntoView({ block: 'nearest' }); - return nextIndex; - }); - } - - if (event.key === 'ArrowUp') { - event.preventDefault(); - setHovered((current) => { - const nextIndex = current - 1 < 0 ? current : current - 1; - viewportRef.current - ?.querySelectorAll('[data-list-item]') - ?.[nextIndex]?.scrollIntoView({ block: 'nearest' }); - return nextIndex; - }); - } - }} - /> - - - - {items} + + + + {items} + + + + + + - + )} + {!pickerOpen && !backgroundPicker && } @@ -322,7 +468,10 @@ function ThemeModal({ diff --git a/src/pages/settings/components/Theming/ThemeModalColorSwatches.tsx b/src/pages/settings/components/Theming/ThemeModalColorSwatches.tsx index d77f17f..c31c637 100644 --- a/src/pages/settings/components/Theming/ThemeModalColorSwatches.tsx +++ b/src/pages/settings/components/Theming/ThemeModalColorSwatches.tsx @@ -1,5 +1,4 @@ import { ColorSwatch, Group, Stack, Text } from '@mantine/core'; -import { useCommonHooks } from 'hooks'; import { colorSwatchClasses, textClasses } from 'styles'; import type { Theme } from 'types'; @@ -14,9 +13,8 @@ function ThemeModalColorSwatches({ setColorId: (colorId: string) => void; setColorValue: (colorValue: string) => void; }) { - const { isMobile } = useCommonHooks(); return ( - + Primary Color diff --git a/src/pages/settings/components/Theming/ThemeModalContent.tsx b/src/pages/settings/components/Theming/ThemeModalContent.tsx index 513801e..6b78e42 100644 --- a/src/pages/settings/components/Theming/ThemeModalContent.tsx +++ b/src/pages/settings/components/Theming/ThemeModalContent.tsx @@ -1,4 +1,6 @@ -import { Group, Stack, Text } from '@mantine/core'; +import { Select, Stack, Text } from '@mantine/core'; +import SearchSelectItem from 'pages/homepage/components/Search/SearchSelectItem'; +import { selectClasses } from 'styles'; import type { Theme } from 'types'; import ThemeExampleApp from './ThemeExampleApp'; @@ -6,7 +8,6 @@ export default function ThemeModalContent({ selectedTheme }: { selectedTheme: Th return ( - } + renderOption={(item: any) => { + return ( + + ); }} - > - {selectedTheme.name} - - - Applications - - - - - - - - Bookmarks - - - + + - - Example Category - - - {'Bookmark 1'} - - - {'Bookmark 2'} - + Applications + + + + + + + + + Bookmarks + - - Example Category - - - {'Bookmark 1'} - - + Example Category + + + {'Bookmark 1'} + + + {'Bookmark 2'} + + + - {'Bookmark 2'} - + + Example Category + + + {'Bookmark 1'} + + + {'Bookmark 2'} + + diff --git a/src/pages/settings/components/Theming/Themes.tsx b/src/pages/settings/components/Theming/Themes.tsx index 1dcc125..a91e23f 100644 --- a/src/pages/settings/components/Theming/Themes.tsx +++ b/src/pages/settings/components/Theming/Themes.tsx @@ -34,8 +34,6 @@ function ThemesSettings() { backgroundModalOpened, { open: openBackgroundModal, close: closeBackgroundModal }, ] = useDisclosure(false); - const [colorId, setColorId] = useState(''); - const [colorValue, setColorValue] = useState('#000000'); const [backgroundList, setBackgroundList] = useState(['None']); const [inUseBackgrounds, setInUseBackgrounds] = useState([]); const [selectedTheme, setSelectedTheme] = useState({ @@ -144,13 +142,9 @@ function ThemesSettings() { handleClose={handleClose} selectedTheme={selectedTheme} setSelectedTheme={setSelectedTheme} - setColorId={setColorId} - colorId={colorId} - setColorValue={setColorValue} openPicker={openPicker} backgroundList={backgroundList} pickerOpen={pickerOpen} - colorValue={colorValue} closePicker={closePicker} handleSaveTheme={handleSaveTheme} />