mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-12 23:36:00 +00:00
fix(lib): optimized layout elements (#424)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
15fbd3e6ce
commit
590be2b7e5
@ -149,6 +149,7 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
name: l.name, // name that appear in Sidebar
|
name: l.name, // name that appear in Sidebar
|
||||||
|
color: l.menuColor,
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||||
|
|||||||
@ -61,7 +61,7 @@
|
|||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"@vitest/coverage-v8": "^3.0.5",
|
"@vitest/coverage-v8": "^3.0.5",
|
||||||
"cypress": "^14.0.3",
|
"cypress": "^14.0.3",
|
||||||
"daisyui": "^5.0.6",
|
"daisyui": "^5.2.3",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.24.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-config-standard": "^17.1.0",
|
"eslint-config-standard": "^17.1.0",
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export interface Route {
|
|||||||
name: string
|
name: string
|
||||||
submenu?: Route[]
|
submenu?: Route[]
|
||||||
blank?: boolean
|
blank?: boolean
|
||||||
|
color?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,7 +36,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
|
|||||||
<nav
|
<nav
|
||||||
id='sidenav'
|
id='sidenav'
|
||||||
className={`${appState.sideBarOpen ? 'tw:translate-x-0' : 'tw:-translate-x-full'}
|
className={`${appState.sideBarOpen ? 'tw:translate-x-0' : 'tw:-translate-x-full'}
|
||||||
${appState.sideBarSlim ? 'tw:w-14' : 'tw:w-48'}
|
${appState.sideBarSlim ? 'tw:w-15' : 'tw:w-48'}
|
||||||
${appState.embedded ? 'tw:mt-5.5 tw:h-[calc(100dvh-22px)]' : 'tw:mt-16 tw:h-[calc(100dvh-64px)]'}
|
${appState.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: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`}
|
tw:overflow-hidden tw:shadow-xl tw:dark:bg-zinc-800`}
|
||||||
@ -62,7 +63,12 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
|
|||||||
if (screen.width < 640 && !appState.sideBarSlim) toggleSidebarOpen()
|
if (screen.width < 640 && !appState.sideBarSlim) toggleSidebarOpen()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{route.icon}
|
<div
|
||||||
|
className='tw:p-1.5 tw:rounded-selector tw:text-white'
|
||||||
|
style={{ backgroundColor: route.color ?? '#777' }}
|
||||||
|
>
|
||||||
|
{route.icon}
|
||||||
|
</div>
|
||||||
<span
|
<span
|
||||||
className={`${appState.sideBarSlim ? 'tw:hidden' : ''}`}
|
className={`${appState.sideBarSlim ? 'tw:hidden' : ''}`}
|
||||||
data-te-sidenav-slim='false'
|
data-te-sidenav-slim='false'
|
||||||
@ -72,7 +78,8 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
|
|||||||
{(location.pathname.includes(route.path) && route.path.length > 1) ||
|
{(location.pathname.includes(route.path) && route.path.length > 1) ||
|
||||||
location.pathname === route.path ? (
|
location.pathname === route.path ? (
|
||||||
<span
|
<span
|
||||||
className='tw:absolute tw:inset-y-0 tw:left-0 tw:w-1 tw:rounded-tr-md tw:rounded-br-md tw:bg-primary '
|
className='tw:absolute tw:inset-y-0 tw:left-0 tw:w-1 tw:rounded-tr-md tw:rounded-br-md'
|
||||||
|
style={{ backgroundColor: route.color ?? '#777' }}
|
||||||
aria-hidden='true'
|
aria-hidden='true'
|
||||||
></span>
|
></span>
|
||||||
) : null}
|
) : null}
|
||||||
@ -104,7 +111,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
|
|||||||
target={route.blank ? '_blank' : '_self'}
|
target={route.blank ? '_blank' : '_self'}
|
||||||
to={route.path}
|
to={route.path}
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`${isActive ? 'tw:font-semibold tw:bg-base-200 tw:rounded-none!' : 'tw:font-normal tw:rounded-none!'}`
|
`tw:px-4 ${isActive ? 'tw:font-semibold tw:bg-base-200 tw:rounded-none!' : 'tw:font-normal tw:rounded-none!'}`
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (screen.width < 640 && !appState.sideBarSlim) toggleSidebarOpen()
|
if (screen.width < 640 && !appState.sideBarSlim) toggleSidebarOpen()
|
||||||
|
|||||||
@ -94,7 +94,9 @@ export const UserControl = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className='tw:ml-2 tw:mr-2'>{userProfile.name ?? user?.first_name}</div>
|
<div className='tw:ml-2 tw:mr-2 tw:hidden tw:sm:block'>
|
||||||
|
{userProfile.name ?? user?.first_name}
|
||||||
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<div className='tw:dropdown tw:dropdown-end'>
|
<div className='tw:dropdown tw:dropdown-end'>
|
||||||
<label tabIndex={0} className='tw:btn tw:btn-ghost tw:btn-square'>
|
<label tabIndex={0} className='tw:btn tw:btn-ghost tw:btn-square'>
|
||||||
|
|||||||
@ -1,12 +1,34 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a theme based on the saved preference, a provided default theme, or the user's system preference.
|
||||||
|
* Falls back to the system dark/light preference when no theme is provided.
|
||||||
|
*/
|
||||||
export const useTheme = (defaultTheme = 'default') => {
|
export const useTheme = (defaultTheme = 'default') => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedTheme = localStorage.getItem('theme')
|
const savedThemeRaw = localStorage.getItem('theme')
|
||||||
const initialTheme = savedTheme ? (JSON.parse(savedTheme) as string) : defaultTheme
|
const savedTheme = savedThemeRaw ? (JSON.parse(savedThemeRaw) as string) : undefined
|
||||||
if (initialTheme !== 'default') {
|
|
||||||
document.documentElement.setAttribute('data-theme', defaultTheme)
|
const prefersDark =
|
||||||
localStorage.setItem('theme', JSON.stringify(initialTheme))
|
typeof window !== 'undefined' && typeof window.matchMedia === 'function'
|
||||||
|
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
: false
|
||||||
|
|
||||||
|
const fallbackTheme =
|
||||||
|
defaultTheme && defaultTheme !== 'default' ? defaultTheme : prefersDark ? 'dark' : 'light'
|
||||||
|
|
||||||
|
const themeToApply = savedTheme ?? fallbackTheme
|
||||||
|
|
||||||
|
if (themeToApply === 'default') {
|
||||||
|
document.documentElement.removeAttribute('data-theme')
|
||||||
|
localStorage.removeItem('theme')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
document.documentElement.setAttribute('data-theme', themeToApply)
|
||||||
|
|
||||||
|
if (!savedTheme || savedTheme !== themeToApply) {
|
||||||
|
localStorage.setItem('theme', JSON.stringify(themeToApply))
|
||||||
}
|
}
|
||||||
}, [defaultTheme])
|
}, [defaultTheme])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
import { useState } from 'react'
|
||||||
import SVG from 'react-inlinesvg'
|
import SVG from 'react-inlinesvg'
|
||||||
|
|
||||||
import PlusSVG from '#assets/plus.svg'
|
import PlusSVG from '#assets/plus.svg'
|
||||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||||
import { useLayers } from '#components/Map/hooks/useLayers'
|
import { useLayers } from '#components/Map/hooks/useLayers'
|
||||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||||
|
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
|
||||||
|
|
||||||
export default function AddButton({
|
export default function AddButton({
|
||||||
triggerAction,
|
triggerAction,
|
||||||
@ -15,6 +17,9 @@ export default function AddButton({
|
|||||||
const layers = useLayers()
|
const layers = useLayers()
|
||||||
const hasUserPermission = useHasUserPermission()
|
const hasUserPermission = useHasUserPermission()
|
||||||
const appState = useAppState()
|
const appState = useAppState()
|
||||||
|
const { width } = useWindowDimensions()
|
||||||
|
const isMobile = width < 768
|
||||||
|
const [hideTooltips, setHideTooltips] = useState(false)
|
||||||
|
|
||||||
const canAddItems = () => {
|
const canAddItems = () => {
|
||||||
let canAdd = false
|
let canAdd = false
|
||||||
@ -30,6 +35,14 @@ export default function AddButton({
|
|||||||
return canAdd
|
return canAdd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleLayerClick = (layer: any) => {
|
||||||
|
triggerAction(layer)
|
||||||
|
// Verstecke Tooltips auf Mobile nach Layer-Auswahl
|
||||||
|
if (isMobile) {
|
||||||
|
setHideTooltips(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{canAddItems() ? (
|
{canAddItems() ? (
|
||||||
@ -37,10 +50,23 @@ export default function AddButton({
|
|||||||
<label
|
<label
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className='tw:z-500 tw:btn tw:btn-circle tw:btn-lg tw:shadow tw:bg-base-100'
|
className='tw:z-500 tw:btn tw:btn-circle tw:btn-lg tw:shadow tw:bg-base-100'
|
||||||
|
onClick={() => {
|
||||||
|
if (hideTooltips) {
|
||||||
|
setHideTooltips(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onTouchEnd={() => {
|
||||||
|
if (hideTooltips) {
|
||||||
|
setHideTooltips(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SVG src={PlusSVG} className='tw:h-5 tw:w-5' />
|
<SVG src={PlusSVG} className='tw:h-5 tw:w-5' />
|
||||||
</label>
|
</label>
|
||||||
<ul tabIndex={0} className='tw:dropdown-content tw:pr-1 tw:list-none'>
|
<ul
|
||||||
|
tabIndex={0}
|
||||||
|
className='tw:dropdown-content tw:pr-1 tw:list-none tw:space-y-3 tw:pb-3'
|
||||||
|
>
|
||||||
{layers.map(
|
{layers.map(
|
||||||
(layer) =>
|
(layer) =>
|
||||||
layer.api?.createItem &&
|
layer.api?.createItem &&
|
||||||
@ -48,16 +74,23 @@ export default function AddButton({
|
|||||||
layer.listed && (
|
layer.listed && (
|
||||||
<li key={layer.name}>
|
<li key={layer.name}>
|
||||||
<a>
|
<a>
|
||||||
<div className='tw:tooltip tw:tooltip-left' data-tip={layer.menuText}>
|
<div
|
||||||
|
className={`tw:tooltip tw:tooltip-left ${isMobile && !hideTooltips ? 'tw:tooltip-open' : ''}`}
|
||||||
|
data-tip={layer.menuText}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
'--tooltip-color': layer.menuColor || '#777',
|
||||||
|
'--tooltip-text-color': '#ffffff',
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className='tw:z-500 tw:border-0 tw:p-0 tw:mb-3 tw:w-10 tw:h-10 tw:cursor-pointer tw:rounded-full tw:mouse tw:drop-shadow-md tw:transition tw:ease-in tw:duration-200 tw:focus:outline-hidden tw:flex tw:items-center tw:justify-center'
|
className='tw:z-500 tw:border-0 tw:p-0 tw:w-10 tw:h-10 tw:cursor-pointer tw:rounded-full tw:mouse tw:drop-shadow-md tw:transition tw:ease-in tw:duration-200 tw:focus:outline-hidden tw:flex tw:items-center tw:justify-center'
|
||||||
style={{ backgroundColor: layer.menuColor || '#777' }}
|
style={{ backgroundColor: layer.menuColor || '#777' }}
|
||||||
onClick={() => {
|
onClick={() => handleLayerClick(layer)}
|
||||||
triggerAction(layer)
|
|
||||||
}}
|
|
||||||
onTouchEnd={(e) => {
|
onTouchEnd={(e) => {
|
||||||
triggerAction(layer)
|
handleLayerClick(layer)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -38,8 +38,9 @@ export function LayerControl({ expandLayerControl = false }: { expandLayerContro
|
|||||||
id={layer.name}
|
id={layer.name}
|
||||||
onChange={() => toggleVisibleLayer(layer)}
|
onChange={() => toggleVisibleLayer(layer)}
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
className='tw:checkbox tw:checkbox-xs tw:checkbox-success'
|
className='tw:checkbox tw:checkbox-xs tw:checkbox-success tw:text-white'
|
||||||
checked={isLayerVisible(layer)}
|
checked={isLayerVisible(layer)}
|
||||||
|
style={{ backgroundColor: layer.menuColor, borderColor: layer.menuColor }}
|
||||||
/>
|
/>
|
||||||
<span className='tw:text-sm tw:label-text tw:mx-2 tw:cursor-pointer'>
|
<span className='tw:text-sm tw:label-text tw:mx-2 tw:cursor-pointer'>
|
||||||
{layer.name}
|
{layer.name}
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
import type { Item } from '#types/Item'
|
|
||||||
import type { LayerProps } from '#types/LayerProps'
|
|
||||||
|
|
||||||
export const SelectPosition = ({
|
|
||||||
setSelectNewItemPosition,
|
|
||||||
selectNewItemPosition,
|
|
||||||
}: {
|
|
||||||
setSelectNewItemPosition: React.Dispatch<React.SetStateAction<Item | LayerProps | null>>
|
|
||||||
selectNewItemPosition?: Item | LayerProps | null
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className='tw:animate-pulseGrow tw:button tw:z-1000 tw:absolute tw:right-5 tw:top-4 tw:drop-shadow-md'>
|
|
||||||
<label
|
|
||||||
className='tw:btn tw:btn-sm tw:rounded-2xl tw:btn-circle tw:btn-ghost tw:hover:bg-transparent tw:absolute tw:right-0 tw:top-0 tw:text-gray-600'
|
|
||||||
onClick={() => {
|
|
||||||
setSelectNewItemPosition(null)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p className='tw:text-center '>✕</p>
|
|
||||||
</label>
|
|
||||||
<div className='tw:alert tw:bg-base-100 tw:text-base-content'>
|
|
||||||
<div>
|
|
||||||
{selectNewItemPosition && 'layer' in selectNewItemPosition && (
|
|
||||||
<span className='tw:text-lg'>
|
|
||||||
Select new position of <b>{selectNewItemPosition.name}</b> on the map!
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{selectNewItemPosition && 'markerIcon' in selectNewItemPosition && (
|
|
||||||
<span className='tw:text-lg'>Select position on the map!</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
142
lib/src/Components/Map/Subcomponents/SelectPositionToast.tsx
Normal file
142
lib/src/Components/Map/Subcomponents/SelectPositionToast.tsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import { useEffect, useMemo, useRef } from 'react'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
import { SVG } from '#components/AppShell'
|
||||||
|
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||||
|
|
||||||
|
import type { Item } from '#types/Item'
|
||||||
|
import type { LayerProps } from '#types/LayerProps'
|
||||||
|
import type { Dispatch, SetStateAction } from 'react'
|
||||||
|
|
||||||
|
const isItemSelection = (value: Item | LayerProps): value is Item => 'layer' in value
|
||||||
|
|
||||||
|
interface SelectPositionToastProps {
|
||||||
|
selectNewItemPosition: Item | LayerProps | null
|
||||||
|
setSelectNewItemPosition: Dispatch<SetStateAction<Item | LayerProps | null>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SelectPositionToast = ({
|
||||||
|
selectNewItemPosition,
|
||||||
|
setSelectNewItemPosition,
|
||||||
|
}: SelectPositionToastProps) => {
|
||||||
|
const toastIdRef = useRef<string | number | null>(null)
|
||||||
|
const toastId = 'select-position-toast'
|
||||||
|
const appState = useAppState()
|
||||||
|
|
||||||
|
// Escape-Key Listener
|
||||||
|
useEffect(() => {
|
||||||
|
const handleEscape = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Escape' && selectNewItemPosition) {
|
||||||
|
toast.dismiss(toastId)
|
||||||
|
toastIdRef.current = null
|
||||||
|
setSelectNewItemPosition(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleEscape)
|
||||||
|
return () => window.removeEventListener('keydown', handleEscape)
|
||||||
|
}, [selectNewItemPosition, setSelectNewItemPosition])
|
||||||
|
|
||||||
|
const toastContent = useMemo(() => {
|
||||||
|
if (!selectNewItemPosition) return null
|
||||||
|
|
||||||
|
const itemSelection = isItemSelection(selectNewItemPosition)
|
||||||
|
const layer: LayerProps | null = itemSelection
|
||||||
|
? (selectNewItemPosition.layer ?? null)
|
||||||
|
: selectNewItemPosition
|
||||||
|
const markerIcon = itemSelection
|
||||||
|
? (selectNewItemPosition.layer?.markerIcon ?? selectNewItemPosition.markerIcon)
|
||||||
|
: selectNewItemPosition.markerIcon
|
||||||
|
const message = itemSelection
|
||||||
|
? `Select the new position of ${selectNewItemPosition.name ?? ''} on the map!`
|
||||||
|
: 'Select the position on the map!'
|
||||||
|
|
||||||
|
const dismissToast = () => {
|
||||||
|
toast.dismiss(toastId)
|
||||||
|
toastIdRef.current = null
|
||||||
|
setSelectNewItemPosition(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetsApiUrl = appState.assetsApi.url
|
||||||
|
const assetsBaseUrl: string | undefined = assetsApiUrl.length > 0 ? assetsApiUrl : undefined
|
||||||
|
|
||||||
|
const resolveColor = (...candidates: (string | null | undefined)[]): string => {
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (typeof candidate === 'string' && candidate.length > 0) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '#777'
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemColor =
|
||||||
|
itemSelection && typeof selectNewItemPosition.color === 'string'
|
||||||
|
? selectNewItemPosition.color
|
||||||
|
: undefined
|
||||||
|
const itemLayerColor =
|
||||||
|
itemSelection && typeof selectNewItemPosition.layer?.menuColor === 'string'
|
||||||
|
? selectNewItemPosition.layer.menuColor
|
||||||
|
: undefined
|
||||||
|
const layerMenuColor = layer?.menuColor
|
||||||
|
const baseLayerColor = typeof layerMenuColor === 'string' ? layerMenuColor : undefined
|
||||||
|
|
||||||
|
const backgroundColor = resolveColor(itemColor, itemLayerColor, baseLayerColor)
|
||||||
|
const iconSrc: string | undefined =
|
||||||
|
markerIcon?.image != null
|
||||||
|
? assetsBaseUrl
|
||||||
|
? `${assetsBaseUrl}${markerIcon.image}`
|
||||||
|
: markerIcon.image
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='tw:flex tw:flex-row tw:items-center'>
|
||||||
|
<div
|
||||||
|
className='tw:flex tw:items-center tw:gap-3 tw:p-2 tw:rounded-selector tw:text-white tw:mr-2'
|
||||||
|
style={{ backgroundColor }}
|
||||||
|
>
|
||||||
|
{iconSrc && <SVG src={iconSrc} className='tw:h-4 tw:w-4 tw:object-contain' />}
|
||||||
|
</div>
|
||||||
|
<div className='tw:flex tw:flex-col tw:gap-0.5'>
|
||||||
|
<span className='tw:text-sm'>{message}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={dismissToast}
|
||||||
|
className='tw:btn tw:btn-sm tw:btn-ghost tw:btn-circle tw:absolute tw:top-0 tw:right-0'
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}, [appState.assetsApi.url, selectNewItemPosition, setSelectNewItemPosition, toastId])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectNewItemPosition && toastContent) {
|
||||||
|
if (!toastIdRef.current) {
|
||||||
|
toastIdRef.current = toast(toastContent, {
|
||||||
|
toastId,
|
||||||
|
autoClose: false,
|
||||||
|
closeButton: false,
|
||||||
|
closeOnClick: false,
|
||||||
|
draggable: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
toast.update(toastId, {
|
||||||
|
render: toastContent,
|
||||||
|
autoClose: false,
|
||||||
|
closeButton: false,
|
||||||
|
closeOnClick: false,
|
||||||
|
draggable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectNewItemPosition && toastIdRef.current) {
|
||||||
|
toast.dismiss(toastIdRef.current)
|
||||||
|
toastIdRef.current = null
|
||||||
|
}
|
||||||
|
}, [selectNewItemPosition, toastContent, toastId])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@ -115,6 +115,7 @@ function UtopiaMap({
|
|||||||
zoom={zoom}
|
zoom={zoom}
|
||||||
zoomControl={showZoomControl}
|
zoomControl={showZoomControl}
|
||||||
maxZoom={19}
|
maxZoom={19}
|
||||||
|
minZoom={2}
|
||||||
>
|
>
|
||||||
<UtopiaMapInner
|
<UtopiaMapInner
|
||||||
geo={geo}
|
geo={geo}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ import { SearchControl } from './Subcomponents/Controls/SearchControl'
|
|||||||
import { TagsControl } from './Subcomponents/Controls/TagsControl'
|
import { TagsControl } from './Subcomponents/Controls/TagsControl'
|
||||||
import { TextView } from './Subcomponents/ItemPopupComponents/TextView'
|
import { TextView } from './Subcomponents/ItemPopupComponents/TextView'
|
||||||
import { MapLibreLayer } from './Subcomponents/MapLibreLayer'
|
import { MapLibreLayer } from './Subcomponents/MapLibreLayer'
|
||||||
import { SelectPosition } from './Subcomponents/SelectPosition'
|
import { SelectPositionToast } from './Subcomponents/SelectPositionToast'
|
||||||
|
|
||||||
import type { Feature, Geometry as GeoJSONGeometry, GeoJsonObject } from 'geojson'
|
import type { Feature, Geometry as GeoJSONGeometry, GeoJsonObject } from 'geojson'
|
||||||
|
|
||||||
@ -332,12 +332,10 @@ export function UtopiaMapInner({
|
|||||||
)}
|
)}
|
||||||
<MapEventListener />
|
<MapEventListener />
|
||||||
<AddButton triggerAction={setSelectNewItemPosition} />
|
<AddButton triggerAction={setSelectNewItemPosition} />
|
||||||
{selectNewItemPosition != null && (
|
<SelectPositionToast
|
||||||
<SelectPosition
|
selectNewItemPosition={selectNewItemPosition}
|
||||||
selectNewItemPosition={selectNewItemPosition}
|
setSelectNewItemPosition={setSelectNewItemPosition}
|
||||||
setSelectNewItemPosition={setSelectNewItemPosition}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,10 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leaflet-container{
|
||||||
|
background-color: var(--color-base-300);
|
||||||
|
}
|
||||||
|
|
||||||
.leaflet-control-attribution a{
|
.leaflet-control-attribution a{
|
||||||
color: #000 !important;
|
color: #000 !important;
|
||||||
}
|
}
|
||||||
@ -62,7 +66,6 @@ display: none !important;
|
|||||||
|
|
||||||
.leaflet-tooltip {
|
.leaflet-tooltip {
|
||||||
border-radius: var(--radius-box);
|
border-radius: var(--radius-box);
|
||||||
transition: opacity 500ms;
|
|
||||||
transition-delay: 50ms;
|
transition-delay: 50ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -78,3 +78,19 @@
|
|||||||
.modal-box {
|
.modal-box {
|
||||||
max-height: calc(100dvh - 2em);
|
max-height: calc(100dvh - 2em);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom tooltip colors for layer-based tooltips */
|
||||||
|
.tw\:tooltip[style*='--tooltip-color']::before,
|
||||||
|
.tw\:tooltip[style*='--tooltip-color']::after {
|
||||||
|
background-color: rgba(0,0,0,0.67);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw\:tooltip[style*='--tooltip-color']::before {
|
||||||
|
color: var(--tooltip-text-color, #ffffff) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure tooltips sit above Leaflet panes when used inside map popups */
|
||||||
|
.tw\:tooltip::before,
|
||||||
|
.tw\:tooltip::after {
|
||||||
|
z-index: 4000;
|
||||||
|
}
|
||||||
|
|||||||
14
package-lock.json
generated
14
package-lock.json
generated
@ -121,7 +121,7 @@
|
|||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"@vitest/coverage-v8": "^3.0.5",
|
"@vitest/coverage-v8": "^3.0.5",
|
||||||
"cypress": "^14.0.3",
|
"cypress": "^14.0.3",
|
||||||
"daisyui": "^5.0.6",
|
"daisyui": "^5.2.3",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.24.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-config-standard": "^17.1.0",
|
"eslint-config-standard": "^17.1.0",
|
||||||
@ -7004,9 +7004,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/daisyui": {
|
"node_modules/daisyui": {
|
||||||
"version": "5.1.29",
|
"version": "5.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.1.29.tgz",
|
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.2.3.tgz",
|
||||||
"integrity": "sha512-4eZhqCXO7CJVNGytTZEIQYJz3fah2gPleuqp4qUD4fD8WoEQIYzKwlOewi8nPaz6NX7vvSLZ+YSjt5Z5zqacGw==",
|
"integrity": "sha512-sldBQUIFCsSPoF4LvoHhIi9GnvBX/3aZD9NoTOvpTSX8sDjO484wQx7yEvRyREMpn4rZMvQSKKskHAHdM8+B4Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
@ -9221,9 +9221,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/happy-dom/node_modules/@types/node": {
|
"node_modules/happy-dom/node_modules/@types/node": {
|
||||||
"version": "20.19.20",
|
"version": "20.19.21",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.20.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.21.tgz",
|
||||||
"integrity": "sha512-2Q7WS25j4pS1cS8yw3d6buNCVJukOTeQ39bAnwR6sOJbaxvyCGebzTMypDFN82CxBLnl+lSWVdCCWbRY6y9yZQ==",
|
"integrity": "sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user