mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
more ui refactoring & theme controller
This commit is contained in:
parent
faebd0afb6
commit
5069b6b32a
@ -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 }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='tw:dropdown tw:mr-2'>
|
||||
<div tabIndex={0} role='button' className='tw:btn tw:m-1'>
|
||||
Theme
|
||||
<svg
|
||||
width='12px'
|
||||
height='12px'
|
||||
className='tw:inline-block tw:h-2 tw:w-2 tw:fill-current tw:opacity-60'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 2048 2048'
|
||||
>
|
||||
<path d='M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z'></path>
|
||||
</svg>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className='tw:dropdown-content tw:bg-base-300 tw:rounded-box tw:z-1 tw:w-52 tw:p-2 tw:shadow-2xl'
|
||||
>
|
||||
<li>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme-dropdown'
|
||||
className='theme-controller tw:btn tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start'
|
||||
aria-label='Default'
|
||||
value='default'
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme-dropdown'
|
||||
className='theme-controller tw:btn tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start'
|
||||
aria-label='Dark'
|
||||
value='dark'
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme-dropdown'
|
||||
className='theme-controller tw:btn tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start'
|
||||
aria-label='Light'
|
||||
value='light'
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme-dropdown'
|
||||
className='theme-controller tw:btn tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start'
|
||||
aria-label='Retro'
|
||||
value='retro'
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme-dropdown'
|
||||
className='theme-controller tw:btn tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start'
|
||||
aria-label='Cyberpunk'
|
||||
value='cyberpunk'
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme-dropdown'
|
||||
className='theme-controller tw:btn tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start'
|
||||
aria-label='Valentine'
|
||||
value='valentine'
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme-dropdown'
|
||||
className='theme-controller tw:btn tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start'
|
||||
aria-label='Aqua'
|
||||
value='aqua'
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme-dropdown'
|
||||
className='theme-controller tw:btn tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start'
|
||||
aria-label='Caramellatte'
|
||||
value='caramellatte'
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme-dropdown'
|
||||
className='theme-controller tw:btn tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start'
|
||||
aria-label='Abyss'
|
||||
value='abyss'
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type='radio'
|
||||
name='theme-dropdown'
|
||||
className='theme-controller tw:btn tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start'
|
||||
aria-label='Silk'
|
||||
value='silk'
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{appState.showThemeControl && <ThemeControl />}
|
||||
|
||||
{isAuthenticated ? (
|
||||
<div className='tw:flex tw:mr-2'>
|
||||
|
||||
@ -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`}
|
||||
>
|
||||
<div
|
||||
className={`tw:flex tw:flex-col ${embedded ? 'tw:h-full' : 'tw:h-[calc(100dvh-64px)]'}`}
|
||||
>
|
||||
<ul
|
||||
className='tw:menu tw:w-full tw:bg-base-100 tw:text-base-content tw:p-0'
|
||||
data-te-sidenav-menu-ref
|
||||
>
|
||||
<ul className='tw:menu tw:w-full tw:bg-base-100 tw:text-base-content tw:p-0'>
|
||||
{routes.map((route, k) => {
|
||||
return (
|
||||
<li className='' key={k}>
|
||||
@ -145,7 +142,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
|
||||
|
||||
<ChevronRightIcon
|
||||
className={
|
||||
'tw:w-5 tw:h-5 tw:mb-4 tw:mr-4 tw:cursor-pointer tw:float-right tw:delay-400 tw:duration-500 tw:transition-all ' +
|
||||
'tw:w-5 tw:h-5 tw:mb-4 tw:mr-5 tw:mt-2 tw:cursor-pointer tw:float-right tw:delay-400 tw:duration-500 tw:transition-all ' +
|
||||
(!appState.sideBarSlim ? 'tw:rotate-180' : '')
|
||||
}
|
||||
onClick={() => toggleSidebarSlim()}
|
||||
|
||||
@ -8,6 +8,7 @@ interface AppState {
|
||||
assetsApi: AssetsApi
|
||||
sideBarOpen: boolean
|
||||
sideBarSlim: boolean
|
||||
showThemeControl: boolean
|
||||
}
|
||||
|
||||
type UseAppManagerResult = ReturnType<typeof useAppManager>
|
||||
@ -16,6 +17,7 @@ const initialAppState: AppState = {
|
||||
assetsApi: {} as AssetsApi,
|
||||
sideBarOpen: false,
|
||||
sideBarSlim: false,
|
||||
showThemeControl: false,
|
||||
}
|
||||
|
||||
const AppContext = createContext<UseAppManagerResult>({
|
||||
|
||||
12
src/Components/AppShell/hooks/useTheme.tsx
Normal file
12
src/Components/AppShell/hooks/useTheme.tsx
Normal file
@ -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])
|
||||
}
|
||||
@ -43,7 +43,7 @@ export const LocateControl = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='tw:card tw:h-12 tw:w-12 tw:bg-base-100 tw:shadow-xl tw:items-center tw:justify-center tw:hover:bg-slate-300 tw:hover:cursor-pointer tw:transition-all tw:duration-300 tw:ml-2'>
|
||||
<div className='tw:card tw:flex-none tw:h-12 tw:w-12 tw:bg-base-100 tw:shadow-xl tw:items-center tw:justify-center tw:hover:bg-slate-300 tw:hover:cursor-pointer tw:transition-all tw:duration-300 tw:ml-2'>
|
||||
<div
|
||||
className='tw:card-body tw:card tw:p-2 tw:h-10 tw:w-10 '
|
||||
onClick={() => {
|
||||
|
||||
@ -112,13 +112,13 @@ export const SearchControl = () => {
|
||||
<div className='tw:w-[calc(100vw-2rem)] tw:max-w-[22rem] '>
|
||||
<div className='tw:flex tw:flex-row'>
|
||||
{embedded && <SidebarControl />}
|
||||
<div className='tw:relative'>
|
||||
<div className='tw:relative tw:shrink tw:max-w-69 tw:w-full'>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='search ...'
|
||||
autoComplete='off'
|
||||
value={value}
|
||||
className='tw:input tw:input-bordered tw:h-12 tw:grow tw:shadow-xl tw:rounded-box tw:pr-12 tw:w-69'
|
||||
className='tw:input tw:input-bordered tw:h-12 tw:grow tw:shadow-xl tw:rounded-box tw:pr-12 tw:w-full'
|
||||
ref={searchInput}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onFocus={() => {
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<div className='tw:card tw:bg-base-100 tw:shadow-xl tw:items-center tw:justify-center tw:hover:bg-slate-300 tw:hover:cursor-pointer tw:transition-all tw:duration-300 tw:mr-2 tw:h-12 tw:w-12 '>
|
||||
<div className='tw:card-body tw:card tw:p-0'>
|
||||
<button
|
||||
className='tw:btn tw:btn-square tw:btn-ghost tw:rounded-2xl'
|
||||
data-te-sidenav-toggle-ref
|
||||
data-te-target='#sidenav'
|
||||
aria-controls='#sidenav'
|
||||
aria-haspopup='true'
|
||||
>
|
||||
<Bars3Icon className='tw:inline-block tw:w-5 tw:h-5' />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className='tw:card tw:justify-center tw:items-center tw:bg-base-100 tw:flex-none tw:shadow-xl tw:px-0 tw:hover:bg-slate-300 tw:hover:cursor-pointer tw:transition-all tw:duration-300 tw:mr-2 tw:h-12 tw:w-12 '
|
||||
onClick={() => toggleSidebar()}
|
||||
>
|
||||
<Bars3Icon className='tw:inline-block tw:w-5 tw:h-5' />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -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({
|
||||
</div>
|
||||
)}
|
||||
{subtitle && !hideSubname && (
|
||||
<div className={`tw:text-xs tw:text-gray-500 ${truncateSubname && 'tw:truncate'}`}>
|
||||
<div className={`tw:text-xs tw:opacity-50 ${truncateSubname && 'tw:truncate'}`}>
|
||||
{subtitle}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -111,7 +111,7 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) =>
|
||||
<div className='tw:flex tw:-mb-1 tw:flex-row tw:mr-2 tw:mt-1'>
|
||||
{infoExpanded ? (
|
||||
<p
|
||||
className={'tw:italic tw:min-h-[21px] tw:my-0! tw:text-gray-500'}
|
||||
className={'tw:italic tw:min-h-[21px] tw:my-0! tw:opacity-50'}
|
||||
>{`${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!)}`}</p>
|
||||
) : (
|
||||
<p
|
||||
|
||||
@ -20,6 +20,8 @@ function UtopiaMap({
|
||||
showFilterControl = false,
|
||||
showGratitudeControl = false,
|
||||
showLayerControl = true,
|
||||
showThemeControl = false,
|
||||
defaultTheme,
|
||||
infoText,
|
||||
donationWidget,
|
||||
}: UtopiaMapProps) {
|
||||
@ -39,6 +41,8 @@ function UtopiaMap({
|
||||
showLayerControl={showLayerControl}
|
||||
infoText={infoText}
|
||||
donationWidget={donationWidget}
|
||||
showThemeControl={showThemeControl}
|
||||
defaultTheme={defaultTheme}
|
||||
>
|
||||
{children}
|
||||
</UtopiaMapInner>
|
||||
|
||||
@ -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<ItemFormPopupProps | null>(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) {
|
||||
|
||||
@ -30,7 +30,7 @@ export const FormHeader = ({ item, state, setState }) => {
|
||||
}
|
||||
className={'tw:-left-6 tw:top-14 tw:-mr-6'}
|
||||
/>
|
||||
<div className='tw:grow tw:mr-4'>
|
||||
<div className='tw:grow tw:mr-4 tw:pt-1'>
|
||||
<TextInput
|
||||
placeholder='Name'
|
||||
defaultValue={item?.name ? item.name : ''}
|
||||
@ -40,7 +40,8 @@ export const FormHeader = ({ item, state, setState }) => {
|
||||
name: v,
|
||||
}))
|
||||
}
|
||||
containerStyle='tw:grow tw:input-md'
|
||||
containerStyle='tw:grow tw:px-4'
|
||||
inputStyle='tw:input-md'
|
||||
/>
|
||||
<TextInput
|
||||
placeholder='Subtitle'
|
||||
@ -52,7 +53,8 @@ export const FormHeader = ({ item, state, setState }) => {
|
||||
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'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -53,12 +53,12 @@ export const TabsForm = ({
|
||||
}, [location.search])
|
||||
|
||||
return (
|
||||
<div role='tablist' className='tw:tabs tw:tabs-lifted tw:mt-3'>
|
||||
<div role='tablist' className='tw:tabs tw:tabs-lift tw:mt-3'>
|
||||
<input
|
||||
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='Info'
|
||||
checked={activeTab === 1 && true}
|
||||
onChange={() => 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)}
|
||||
|
||||
@ -85,14 +85,12 @@ export const TabsView = ({
|
||||
}, [location.search])
|
||||
|
||||
return (
|
||||
<div role='tablist' className='tw:tabs tw:tabs-lifted tw:mt-2 tw:mb-2 tw:px-6'>
|
||||
<div role='tablist' className='tw:tabs tw:tabs-lift tw:mt-2 tw:mb-2 tw:px-6'>
|
||||
<input
|
||||
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 !== 1 ? '📝' : '📝\u00A0Info'}`}
|
||||
checked={activeTab === 1 && true}
|
||||
onChange={() => 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)}
|
||||
|
||||
63
src/Components/Templates/ThemeControl.tsx
Normal file
63
src/Components/Templates/ThemeControl.tsx
Normal file
@ -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<string>(() => {
|
||||
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 (
|
||||
<div className='tw:dropdown tw:mr-2'>
|
||||
<div tabIndex={0} role='button' className='tw:btn tw:m-1'>
|
||||
Theme
|
||||
<svg
|
||||
width='12px'
|
||||
height='12px'
|
||||
className='tw:inline-block tw:h-2 tw:w-2 tw:fill-current tw:opacity-60'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 2048 2048'
|
||||
>
|
||||
<path d='M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z'></path>
|
||||
</svg>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className='tw:dropdown-content tw:bg-base-300 tw:rounded-box tw:z-1 tw:w-52 tw:p-2 tw:shadow-2xl'
|
||||
>
|
||||
{themes.map((t) => (
|
||||
<li key={t}>
|
||||
<input
|
||||
className={`tw:btn ${theme === t ? 'tw:bg-primary' : ''} tw:btn-sm tw:btn-block tw:btn-ghost tw:justify-start`}
|
||||
type='radio'
|
||||
name='theme'
|
||||
value={t}
|
||||
checked={theme === t}
|
||||
onChange={() => setTheme(t)}
|
||||
aria-label={t.toLowerCase()}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -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%;
|
||||
}
|
||||
@ -77,8 +77,4 @@
|
||||
|
||||
.modal-box {
|
||||
max-height: calc(100dvh - 2em);
|
||||
}
|
||||
|
||||
.tab-content .container {
|
||||
height: 100%;
|
||||
}
|
||||
@ -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);
|
||||
|
||||
2
src/types/UtopiaMapProps.d.ts
vendored
2
src/types/UtopiaMapProps.d.ts
vendored
@ -12,6 +12,8 @@ export interface UtopiaMapProps {
|
||||
showFilterControl?: boolean
|
||||
showLayerControl?: boolean
|
||||
showGratitudeControl?: boolean
|
||||
showThemeControl?: boolean
|
||||
infoText?: string
|
||||
donationWidget?: boolean
|
||||
defaultTheme?: string
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user