From 5069b6b32ac9955c6625a208ca1d193abf3369b2 Mon Sep 17 00:00:00 2001 From: Anton Tranelis Date: Fri, 21 Mar 2025 19:09:34 +0000 Subject: [PATCH] more ui refactoring & theme controller --- src/Components/AppShell/NavBar.tsx | 111 +----------------- src/Components/AppShell/SideBar.tsx | 9 +- src/Components/AppShell/hooks/useAppState.tsx | 2 + src/Components/AppShell/hooks/useTheme.tsx | 12 ++ .../Subcomponents/Controls/LocateControl.tsx | 2 +- .../Subcomponents/Controls/SearchControl.tsx | 4 +- .../Subcomponents/Controls/SidebarControl.tsx | 24 ++-- .../ItemPopupComponents/HeaderView.tsx | 6 +- .../Map/Subcomponents/ItemViewPopup.tsx | 2 +- src/Components/Map/UtopiaMap.tsx | 4 + src/Components/Map/UtopiaMapInner.tsx | 12 ++ .../Profile/Subcomponents/FormHeader.tsx | 8 +- src/Components/Profile/Templates/TabsForm.tsx | 10 +- src/Components/Profile/Templates/TabsView.tsx | 14 +-- src/Components/Templates/ThemeControl.tsx | 63 ++++++++++ src/assets/css/leaflet.css | 5 + src/assets/css/tailwind.css | 4 - src/assets/css/toastify.css | 2 +- src/types/UtopiaMapProps.d.ts | 2 + 19 files changed, 138 insertions(+), 158 deletions(-) create mode 100644 src/Components/AppShell/hooks/useTheme.tsx create mode 100644 src/Components/Templates/ThemeControl.tsx diff --git a/src/Components/AppShell/NavBar.tsx b/src/Components/AppShell/NavBar.tsx index dcfc25cd..3eae1284 100644 --- a/src/Components/AppShell/NavBar.tsx +++ b/src/Components/AppShell/NavBar.tsx @@ -7,6 +7,7 @@ import { toast } from 'react-toastify' import { useAuth } from '#components/Auth/useAuth' import { useItems } from '#components/Map/hooks/useItems' +import { ThemeControl } from '#components/Templates/ThemeControl' import { useAppState, useSetAppState } from './hooks/useAppState' @@ -100,115 +101,7 @@ export default function NavBar({ appName }: { appName: string }) { -
-
- Theme - - - -
- -
+ {appState.showThemeControl && } {isAuthenticated ? (
diff --git a/src/Components/AppShell/SideBar.tsx b/src/Components/AppShell/SideBar.tsx index 3bbcbc2e..69a2c77c 100644 --- a/src/Components/AppShell/SideBar.tsx +++ b/src/Components/AppShell/SideBar.tsx @@ -45,17 +45,14 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute id='sidenav' className={`${appState.sideBarOpen ? 'tw:translate-x-0' : 'tw:-translate-x-full'} ${appState.sideBarSlim ? 'tw:w-14' : 'tw:w-48'} - ${embedded ? 'tw:mt-0 tw:h-[100dvh]' : 'tw:mt-16 tw:h-[calc(100dvh-64px)]'} + ${embedded ? 'tw:mt-5.5 tw:h-[calc(100dvh-22px)]' : 'tw:mt-16 tw:h-[calc(100dvh-64px)]'} tw:fixed tw:left-0 tw:transition-all tw:duration-300 tw:top-0 tw:z-10035 tw:overflow-hidden tw:shadow-xl tw:dark:bg-zinc-800`} >
-
    +
      {routes.map((route, k) => { return (
    • @@ -145,7 +142,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute toggleSidebarSlim()} diff --git a/src/Components/AppShell/hooks/useAppState.tsx b/src/Components/AppShell/hooks/useAppState.tsx index bdd94859..3a520118 100644 --- a/src/Components/AppShell/hooks/useAppState.tsx +++ b/src/Components/AppShell/hooks/useAppState.tsx @@ -8,6 +8,7 @@ interface AppState { assetsApi: AssetsApi sideBarOpen: boolean sideBarSlim: boolean + showThemeControl: boolean } type UseAppManagerResult = ReturnType @@ -16,6 +17,7 @@ const initialAppState: AppState = { assetsApi: {} as AssetsApi, sideBarOpen: false, sideBarSlim: false, + showThemeControl: false, } const AppContext = createContext({ diff --git a/src/Components/AppShell/hooks/useTheme.tsx b/src/Components/AppShell/hooks/useTheme.tsx new file mode 100644 index 00000000..367d8fd3 --- /dev/null +++ b/src/Components/AppShell/hooks/useTheme.tsx @@ -0,0 +1,12 @@ +import { useEffect } from 'react' + +export const useTheme = (defaultTheme = 'default') => { + useEffect(() => { + const savedTheme = localStorage.getItem('theme') + const initialTheme = savedTheme ? (JSON.parse(savedTheme) as string) : defaultTheme + if (initialTheme !== 'default') { + document.documentElement.setAttribute('data-theme', defaultTheme) + localStorage.setItem('theme', JSON.stringify(initialTheme)) + } + }, [defaultTheme]) +} diff --git a/src/Components/Map/Subcomponents/Controls/LocateControl.tsx b/src/Components/Map/Subcomponents/Controls/LocateControl.tsx index fd2f52f8..ecc3a061 100644 --- a/src/Components/Map/Subcomponents/Controls/LocateControl.tsx +++ b/src/Components/Map/Subcomponents/Controls/LocateControl.tsx @@ -43,7 +43,7 @@ export const LocateControl = () => { return ( <> -
      +
      { diff --git a/src/Components/Map/Subcomponents/Controls/SearchControl.tsx b/src/Components/Map/Subcomponents/Controls/SearchControl.tsx index 40eb98ce..1b408ef8 100644 --- a/src/Components/Map/Subcomponents/Controls/SearchControl.tsx +++ b/src/Components/Map/Subcomponents/Controls/SearchControl.tsx @@ -112,13 +112,13 @@ export const SearchControl = () => {
      {embedded && } -
      +
      setValue(e.target.value)} onFocus={() => { diff --git a/src/Components/Map/Subcomponents/Controls/SidebarControl.tsx b/src/Components/Map/Subcomponents/Controls/SidebarControl.tsx index b809ab3a..464e4364 100644 --- a/src/Components/Map/Subcomponents/Controls/SidebarControl.tsx +++ b/src/Components/Map/Subcomponents/Controls/SidebarControl.tsx @@ -1,21 +1,21 @@ import Bars3Icon from '@heroicons/react/16/solid/Bars3Icon' +import { useAppState, useSetAppState } from '#components/AppShell/hooks/useAppState' + // Converts leaflet.locatecontrol to a React Component export const SidebarControl = () => { + const appState = useAppState() + const setAppState = useSetAppState() + const toggleSidebar = () => { + setAppState({ sideBarOpen: !appState.sideBarOpen }) + } return ( <> -
      -
      - -
      +
      toggleSidebar()} + > +
      ) diff --git a/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx b/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx index b2820dae..95847b40 100644 --- a/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx +++ b/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx @@ -57,9 +57,7 @@ export function HeaderView({ const [imageLoaded, setImageLoaded] = useState(false) - const avatar = - item.image && - appState.assetsApi.url + item.image + `${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}` + const avatar = item.image && appState.assetsApi.url + item.image + '?width=160&heigth=160' const title = item.name const subtitle = item.subname @@ -111,7 +109,7 @@ export function HeaderView({
      )} {subtitle && !hideSubname && ( -
      +
      {subtitle}
      )} diff --git a/src/Components/Map/Subcomponents/ItemViewPopup.tsx b/src/Components/Map/Subcomponents/ItemViewPopup.tsx index c18f4dae..402f09bc 100644 --- a/src/Components/Map/Subcomponents/ItemViewPopup.tsx +++ b/src/Components/Map/Subcomponents/ItemViewPopup.tsx @@ -111,7 +111,7 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) =>
      {infoExpanded ? (

      {`${props.item.date_updated && props.item.date_updated !== props.item.date_created ? 'updated' : 'posted'} ${props.item && props.item.user_created && props.item.user_created.first_name ? `by ${props.item.user_created.first_name}` : ''} ${props.item.date_updated ? timeAgo(props.item.date_updated) : timeAgo(props.item.date_created!)}`}

      ) : (

      {children} diff --git a/src/Components/Map/UtopiaMapInner.tsx b/src/Components/Map/UtopiaMapInner.tsx index d41e87d5..2bf6c94c 100644 --- a/src/Components/Map/UtopiaMapInner.tsx +++ b/src/Components/Map/UtopiaMapInner.tsx @@ -12,6 +12,8 @@ import MarkerClusterGroup from 'react-leaflet-cluster' import { Outlet, useLocation } from 'react-router-dom' import { toast } from 'react-toastify' +import { useSetAppState } from '#components/AppShell/hooks/useAppState' +import { useTheme } from '#components/AppShell/hooks/useTheme' import { containsUUID } from '#utils/ContainsUUID' import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef' @@ -43,6 +45,8 @@ export function UtopiaMapInner({ showFilterControl = false, showGratitudeControl = false, showLayerControl = true, + showThemeControl = false, + defaultTheme = '', donationWidget, }: UtopiaMapProps) { const selectNewItemPosition = useSelectPosition() @@ -52,6 +56,8 @@ export function UtopiaMapInner({ const setMapClicked = useSetMapClicked() const [itemFormPopup, setItemFormPopup] = useState(null) + useTheme(defaultTheme) + const layers = useLayers() const addVisibleLayer = useAddVisibleLayer() const leafletRefs = useLeafletRefs() @@ -64,6 +70,12 @@ export function UtopiaMapInner({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [layers]) + const setAppState = useSetAppState() + + useEffect(() => { + setAppState({ showThemeControl }) + }, [setAppState, showThemeControl]) + const init = useRef(false) useEffect(() => { if (!init.current) { diff --git a/src/Components/Profile/Subcomponents/FormHeader.tsx b/src/Components/Profile/Subcomponents/FormHeader.tsx index 165a9c27..55eced5f 100644 --- a/src/Components/Profile/Subcomponents/FormHeader.tsx +++ b/src/Components/Profile/Subcomponents/FormHeader.tsx @@ -30,7 +30,7 @@ export const FormHeader = ({ item, state, setState }) => { } className={'tw:-left-6 tw:top-14 tw:-mr-6'} /> -

      +
      { name: v, })) } - containerStyle='tw:grow tw:input-md' + containerStyle='tw:grow tw:px-4' + inputStyle='tw:input-md' /> { subname: v, })) } - containerStyle='tw:grow tw:input-sm tw:px-4 tw:mt-1' + containerStyle='tw:grow tw:px-4 tw:mt-1' + inputStyle='tw:input-sm' />
      diff --git a/src/Components/Profile/Templates/TabsForm.tsx b/src/Components/Profile/Templates/TabsForm.tsx index e8fced36..a38fa6ae 100644 --- a/src/Components/Profile/Templates/TabsForm.tsx +++ b/src/Components/Profile/Templates/TabsForm.tsx @@ -53,12 +53,12 @@ export const TabsForm = ({ }, [location.search]) return ( -
      +
      updateActiveTab(1)} @@ -124,9 +124,7 @@ export const TabsForm = ({ type='radio' name='my_tabs_2' role='tab' - className={ - 'tw:tab tw:min-w-[10em] tw:[--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]' - } + className={'tw:tab tw:min-w-[10em] '} aria-label='Offers & Needs' checked={activeTab === 3 && true} onChange={() => updateActiveTab(3)} @@ -172,7 +170,7 @@ export const TabsForm = ({ type='radio' name='my_tabs_2' role='tab' - className='tw:tab tw:[--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]' + className='tw:tab ' aria-label='Links' checked={activeTab === 7 && true} onChange={() => updateActiveTab(7)} diff --git a/src/Components/Profile/Templates/TabsView.tsx b/src/Components/Profile/Templates/TabsView.tsx index 9b4c38be..bccb2cc8 100644 --- a/src/Components/Profile/Templates/TabsView.tsx +++ b/src/Components/Profile/Templates/TabsView.tsx @@ -85,14 +85,12 @@ export const TabsView = ({ }, [location.search]) return ( -
      +
      updateActiveTab(1)} @@ -116,9 +114,7 @@ export const TabsView = ({ type='radio' name='my_tabs_2' role='tab' - className={ - 'tw:tab tw:font-bold tw:ps-2! tw:pe-2! tw:[--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]' - } + className={'tw:tab tw:font-bold tw:ps-2! tw:pe-2!'} aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 2 ? '❤️' : '❤️\u00A0Trust'}`} checked={activeTab === 2 && true} onChange={() => updateActiveTab(2)} @@ -199,7 +195,7 @@ export const TabsView = ({ type='radio' name='my_tabs_2' role='tab' - className={`tw:tab tw:font-bold tw:ps-2! tw:pe-2! ${!(item.layer.itemType.icon_as_labels && activeTab !== 3) && 'tw:min-w-[10.4em]'} tw:[--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} + className={`tw:tab tw:font-bold tw:ps-2! tw:pe-2! ${!(item.layer.itemType.icon_as_labels && activeTab !== 3) && 'tw:min-w-[10.4em]'} `} aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 3 ? '♻️' : '♻️\u00A0Offers & Needs'}`} checked={activeTab === 3 && true} onChange={() => updateActiveTab(3)} @@ -252,7 +248,7 @@ export const TabsView = ({ type='radio' name='my_tabs_2' role='tab' - className='tw:tab tw:font-bold tw:ps-2! tw:pe-2! tw:[--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]' + className='tw:tab tw:font-bold tw:ps-2! tw:pe-2! ' aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 7 ? '🔗' : '🔗\u00A0Links'}`} checked={activeTab === 7 && true} onChange={() => updateActiveTab(7)} diff --git a/src/Components/Templates/ThemeControl.tsx b/src/Components/Templates/ThemeControl.tsx new file mode 100644 index 00000000..6aa09f42 --- /dev/null +++ b/src/Components/Templates/ThemeControl.tsx @@ -0,0 +1,63 @@ +import { useState, useEffect } from 'react' + +const themes = [ + 'default', + 'light', + 'dark', + 'valentine', + 'retro', + 'aqua', + 'cyberpunk', + 'caramellatte', + 'abyss', + 'silk', +] + +export const ThemeControl = () => { + const [theme, setTheme] = useState(() => { + const savedTheme = localStorage.getItem('theme') + return savedTheme ? (JSON.parse(savedTheme) as string) : 'default' + }) + + useEffect(() => { + if (theme !== 'default') { + localStorage.setItem('theme', JSON.stringify(theme)) + document.documentElement.setAttribute('data-theme', theme) + } + }, [theme]) + + return ( +
      +
      + Theme + + + +
      +
        + {themes.map((t) => ( +
      • + setTheme(t)} + aria-label={t.toLowerCase()} + /> +
      • + ))} +
      +
      + ) +} diff --git a/src/assets/css/leaflet.css b/src/assets/css/leaflet.css index b8e63719..1092c697 100644 --- a/src/assets/css/leaflet.css +++ b/src/assets/css/leaflet.css @@ -47,4 +47,9 @@ background-repeat: no-repeat; background-attachment: fixed; background-position: 50% 80%; +} + +.leaflet-popup-close-button span { + color: var(--color-base-content); + opacity: 50%; } \ No newline at end of file diff --git a/src/assets/css/tailwind.css b/src/assets/css/tailwind.css index 97a175bb..409668fc 100644 --- a/src/assets/css/tailwind.css +++ b/src/assets/css/tailwind.css @@ -77,8 +77,4 @@ .modal-box { max-height: calc(100dvh - 2em); -} - -.tab-content .container { - height: 100%; } \ No newline at end of file diff --git a/src/assets/css/toastify.css b/src/assets/css/toastify.css index dec3026f..422510e9 100644 --- a/src/assets/css/toastify.css +++ b/src/assets/css/toastify.css @@ -6,7 +6,7 @@ } .Toastify__toast { - border-radius: 1rem; + border-radius: var(--radius-box); --shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --shadow-colored: 0 20px 25px -5px var(--shadow-color), 0 8px 10px -6px var(--shadow-color); box-shadow: var(--ring-offset-shadow, 0 0 #0000), var(--ring-shadow, 0 0 #0000), var(--shadow); diff --git a/src/types/UtopiaMapProps.d.ts b/src/types/UtopiaMapProps.d.ts index cbcf02ce..eaa455a2 100644 --- a/src/types/UtopiaMapProps.d.ts +++ b/src/types/UtopiaMapProps.d.ts @@ -12,6 +12,8 @@ export interface UtopiaMapProps { showFilterControl?: boolean showLayerControl?: boolean showGratitudeControl?: boolean + showThemeControl?: boolean infoText?: string donationWidget?: boolean + defaultTheme?: string }