fix(source): external svg theming (#192)

* allow include of external svgs without breaking theming

* 3.0.77

* 3.0.78

* fixed LocateControl and added react-inlinesvg to external dependencies

* theming toast close button

* fixed typing

* theming search resuts

* theming search resuts

* theming search resuts

* theming donation widget

* theming donation widget

---------

Co-authored-by: Ulf Gebhardt <ulf.gebhardt@webcraft-media.de>
This commit is contained in:
Anton Tranelis 2025-03-19 23:28:04 +00:00 committed by GitHub
parent 8b7cff2b32
commit 4fc9516715
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 73 additions and 24 deletions

24
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "utopia-ui", "name": "utopia-ui",
"version": "3.0.76", "version": "3.0.78",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "utopia-ui", "name": "utopia-ui",
"version": "3.0.76", "version": "3.0.78",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"dependencies": { "dependencies": {
"@heroicons/react": "^2.0.17", "@heroicons/react": "^2.0.17",
@ -18,6 +18,7 @@
"radash": "^12.1.0", "radash": "^12.1.0",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
"react-image-crop": "^10.1.8", "react-image-crop": "^10.1.8",
"react-inlinesvg": "^4.2.0",
"react-leaflet": "^4.2.1", "react-leaflet": "^4.2.1",
"react-leaflet-cluster": "^2.1.0", "react-leaflet-cluster": "^2.1.0",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
@ -10203,6 +10204,14 @@
"react": "^18.3.1" "react": "^18.3.1"
} }
}, },
"node_modules/react-from-dom": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/react-from-dom/-/react-from-dom-0.7.5.tgz",
"integrity": "sha512-CO92PmMKo/23uYPm6OFvh5CtZbMgHs/Xn+o095Lz/TZj9t8DSDhGdSOMLxBxwWI4sr0MF17KUn9yJWc5Q00R/w==",
"peerDependencies": {
"react": "16.8 - 19"
}
},
"node_modules/react-image-crop": { "node_modules/react-image-crop": {
"version": "10.1.8", "version": "10.1.8",
"resolved": "https://registry.npmjs.org/react-image-crop/-/react-image-crop-10.1.8.tgz", "resolved": "https://registry.npmjs.org/react-image-crop/-/react-image-crop-10.1.8.tgz",
@ -10212,6 +10221,17 @@
"react": ">=16.13.1" "react": ">=16.13.1"
} }
}, },
"node_modules/react-inlinesvg": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/react-inlinesvg/-/react-inlinesvg-4.2.0.tgz",
"integrity": "sha512-V59P6sFU7NACIbvoay9ikYKVFWyIIZFGd7w6YT1m+H7Ues0fOI6B6IftE6NPSYXXv7RHVmrncIyJeYurs3OJcA==",
"dependencies": {
"react-from-dom": "^0.7.5"
},
"peerDependencies": {
"react": "16.8 - 19"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "utopia-ui", "name": "utopia-ui",
"version": "3.0.76", "version": "3.0.78",
"description": "Reuseable React Components to build mapping apps for real life communities and networks", "description": "Reuseable React Components to build mapping apps for real life communities and networks",
"repository": "https://github.com/utopia-os/utopia-ui", "repository": "https://github.com/utopia-os/utopia-ui",
"homepage": "https://utopia-os.org/", "homepage": "https://utopia-os.org/",
@ -99,6 +99,7 @@
"radash": "^12.1.0", "radash": "^12.1.0",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
"react-image-crop": "^10.1.8", "react-image-crop": "^10.1.8",
"react-inlinesvg": "^4.2.0",
"react-leaflet": "^4.2.1", "react-leaflet": "^4.2.1",
"react-leaflet-cluster": "^2.1.0", "react-leaflet-cluster": "^2.1.0",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",

View File

@ -76,6 +76,7 @@ export default [
'leaflet.locatecontrol/dist/L.Control.Locate.css', 'leaflet.locatecontrol/dist/L.Control.Locate.css',
'yet-another-react-lightbox', 'yet-another-react-lightbox',
'react-photo-album', 'react-photo-album',
'react-inlinesvg',
], ],
}, },
{ {

View File

@ -15,9 +15,20 @@ import { TagsProvider } from '#components/Map/hooks/useTags'
import { AppStateProvider } from './hooks/useAppState' import { AppStateProvider } from './hooks/useAppState'
import type { CloseButtonProps } from 'react-toastify'
// Helper context to determine if the ContextWrapper is already present. // Helper context to determine if the ContextWrapper is already present.
const ContextCheckContext = createContext(false) const ContextCheckContext = createContext(false)
const CloseButton = ({ closeToast }: CloseButtonProps) => (
<button
className='tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-2 tw-top-2 focus:tw-outline-none'
onClick={closeToast}
>
</button>
)
export const ContextWrapper = ({ children }: { children: React.ReactNode }) => { export const ContextWrapper = ({ children }: { children: React.ReactNode }) => {
const isWrapped = useContext(ContextCheckContext) const isWrapped = useContext(ContextCheckContext)
@ -67,6 +78,7 @@ export const Wrappers = ({ children }) => {
draggable draggable
pauseOnHover pauseOnHover
theme='light' theme='light'
closeButton={CloseButton}
/> />
{children} {children}
</QuestsProvider> </QuestsProvider>

View File

@ -1,3 +1,4 @@
export * from './AppShell' export * from './AppShell'
export { SideBar } from './SideBar' export { SideBar } from './SideBar'
export { Content } from './Content' export { Content } from './Content'
export { default as SVG } from 'react-inlinesvg'

View File

@ -1,5 +1,7 @@
/* 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 SVG from 'react-inlinesvg'
import PlusSVG from '#assets/plus.svg' import PlusSVG from '#assets/plus.svg'
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'
@ -31,7 +33,7 @@ export default function AddButton({
{canAddItems() ? ( {canAddItems() ? (
<div className='tw-dropdown tw-dropdown-top tw-dropdown-end tw-dropdown-hover tw-z-500 tw-absolute tw-right-4 tw-bottom-4'> <div className='tw-dropdown tw-dropdown-top tw-dropdown-end tw-dropdown-hover tw-z-500 tw-absolute tw-right-4 tw-bottom-4'>
<label tabIndex={0} className='tw-z-500 tw-btn tw-btn-circle tw-shadow tw-bg-base-100'> <label tabIndex={0} className='tw-z-500 tw-btn tw-btn-circle tw-shadow tw-bg-base-100'>
<img src={PlusSVG} alt='Layers' 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'>
{layers.map( {layers.map(

View File

@ -1,4 +1,5 @@
import { useState } from 'react' import { useState } from 'react'
import SVG from 'react-inlinesvg'
import LayerSVG from '#assets/layer.svg' import LayerSVG from '#assets/layer.svg'
import { useIsLayerVisible, useToggleVisibleLayer } from '#components/Map/hooks/useFilter' import { useIsLayerVisible, useToggleVisibleLayer } from '#components/Map/hooks/useFilter'
@ -56,7 +57,7 @@ export function LayerControl() {
setOpen(true) setOpen(true)
}} }}
> >
<img src={LayerSVG} alt='Layers' /> <SVG src={LayerSVG} />
</div> </div>
)} )}
</div> </div>

View File

@ -6,6 +6,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
import { control } from 'leaflet' import { control } from 'leaflet'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import SVG from 'react-inlinesvg'
import { useMap, useMapEvents } from 'react-leaflet' import { useMap, useMapEvents } from 'react-leaflet'
import TargetSVG from '#assets/target.svg' import TargetSVG from '#assets/target.svg'
@ -58,9 +59,8 @@ export const LocateControl = () => {
{loading ? ( {loading ? (
<span className='tw-loading tw-loading-spinner tw-loading-md tw-mt-1'></span> <span className='tw-loading tw-loading-spinner tw-loading-md tw-mt-1'></span>
) : ( ) : (
<img <SVG
src={TargetSVG} src={TargetSVG}
alt='x'
className='tw-mt-1 tw-p-[1px]' className='tw-mt-1 tw-p-[1px]'
style={{ fill: `${active ? '#fc8702' : 'currentColor'}` }} style={{ fill: `${active ? '#fc8702' : 'currentColor'}` }}
/> />

View File

@ -15,6 +15,7 @@ import MagnifyingGlassIcon from '@heroicons/react/24/outline/MagnifyingGlassIcon
import axios from 'axios' import axios from 'axios'
import { LatLng, LatLngBounds, marker } from 'leaflet' import { LatLng, LatLngBounds, marker } from 'leaflet'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import SVG from 'react-inlinesvg'
import { useMap, useMapEvents } from 'react-leaflet' import { useMap, useMapEvents } from 'react-leaflet'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
@ -169,7 +170,7 @@ export const SearchControl = () => {
{itemsResults.slice(0, 5).map((item) => ( {itemsResults.slice(0, 5).map((item) => (
<div <div
key={item.id} key={item.id}
className='tw-cursor-pointer hover:tw-font-bold' className='tw-cursor-pointer hover:tw-font-bold tw-flex tw-flex-row'
onClick={() => { onClick={() => {
const marker = Object.entries(leafletRefs).find((r) => r[1].item === item)?.[1] const marker = Object.entries(leafletRefs).find((r) => r[1].item === item)?.[1]
.marker .marker
@ -182,18 +183,25 @@ export const SearchControl = () => {
} }
}} }}
> >
<div className='tw-flex tw-flex-row'> {item.layer?.menuIcon ? (
<img <SVG
src={item.layer?.menuIcon} src={item.layer.menuIcon}
className='tw-text-current tw-w-5 tw-mr-2 tw-mt-0' className='tw-text-current tw-mr-2 tw-mt-0 tw-w-5'
preProcessor={(code: string): string => {
code = code.replace(/fill=".*?"/g, 'fill="currentColor"')
code = code.replace(/stroke=".*?"/g, 'stroke="currentColor"')
return code
}}
/> />
<div> ) : (
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'> <div className='tw-w-5' />
{item.name} )}
</div> <div>
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'> <div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>
{item.text} {item.name}
</div> </div>
<div className='tw-text-xs tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>
{item.text}
</div> </div>
</div> </div>
</div> </div>
@ -236,7 +244,7 @@ export const SearchControl = () => {
hide() hide()
}} }}
> >
<MagnifyingGlassIcon className='tw-text-current tw-mr-2 tw-mt-0 tw-w-4' /> <MagnifyingGlassIcon className='tw-text-current tw-mr-2 tw-mt-0 tw-w-5' />
<div> <div>
<div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'> <div className='tw-text-sm tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-max-w-[17rem]'>
{geo?.properties.name ? geo?.properties.name : value} {geo?.properties.name ? geo?.properties.name : value}

View File

@ -13,6 +13,7 @@ import EllipsisVerticalIcon from '@heroicons/react/16/solid/EllipsisVerticalIcon
import PencilIcon from '@heroicons/react/24/solid/PencilIcon' import PencilIcon from '@heroicons/react/24/solid/PencilIcon'
import TrashIcon from '@heroicons/react/24/solid/TrashIcon' import TrashIcon from '@heroicons/react/24/solid/TrashIcon'
import { useState } from 'react' import { useState } from 'react'
import SVG from 'react-inlinesvg'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import TargetDotSVG from '#assets/targetDot.svg' import TargetDotSVG from '#assets/targetDot.svg'
@ -159,7 +160,7 @@ export function HeaderView({
className='!tw-text-base-content tw-cursor-pointer' className='!tw-text-base-content tw-cursor-pointer'
onClick={setPositionCallback} onClick={setPositionCallback}
> >
<img src={TargetDotSVG} alt='Position' className='tw-w-5 tw-h-5' /> <SVG src={TargetDotSVG} className='tw-w-5 tw-h-5' />
</a> </a>
</li> </li>
)} )}

View File

@ -75,10 +75,12 @@ export function UtopiaMapInner({
<div> <div>
<TextView <TextView
itemId='' itemId=''
rawText={'Support us building free opensource maps and help us grow 🌱☀️'} rawText={
'Support us building free opensource maps for communities and help us grow 🌱☀️'
}
/> />
<a href='https://opencollective.com/utopia-project'> <a href='https://opencollective.com/utopia-project'>
<div className='tw-btn tw-btn-sm tw-float-right'>Donate</div> <div className='tw-btn tw-btn-sm tw-float-right tw-btn-primary'>Donate</div>
</a> </a>
</div> </div>
</>, </>,