forked from MapComplete/MapComplete
		
	Accessibility: add (translatable) aria labels, update to translation system, see #1181
This commit is contained in:
		
							parent
							
								
									825fd03adb
								
							
						
					
					
						commit
						8a7d8a43ce
					
				
					 12 changed files with 130 additions and 72 deletions
				
			
		|  | @ -289,7 +289,7 @@ | ||||||
|             "generatedWith": "Generated with mapcomplete.org/{layoutid}", |             "generatedWith": "Generated with mapcomplete.org/{layoutid}", | ||||||
|             "versionInfo": "v{version} - generated on {date}" |             "versionInfo": "v{version} - generated on {date}" | ||||||
|         }, |         }, | ||||||
|         "pickLanguage": "Choose a language: ", |         "pickLanguage": "Select language", | ||||||
|         "poweredByOsm": "Powered by OpenStreetMap", |         "poweredByOsm": "Powered by OpenStreetMap", | ||||||
|         "questionBox": { |         "questionBox": { | ||||||
|             "answeredMultiple": "You answered {answered} questions", |             "answeredMultiple": "You answered {answered} questions", | ||||||
|  |  | ||||||
|  | @ -2469,7 +2469,7 @@ button.link:hover { | ||||||
|   fill: var(--foreground-color) !important; |   fill: var(--foreground-color) !important; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label { | label:not(.neutral-label) { | ||||||
|   /** |   /** | ||||||
|      * Label should _contain_ the input element |      * Label should _contain_ the input element | ||||||
|      */ |      */ | ||||||
|  | @ -2485,27 +2485,27 @@ label { | ||||||
|   transition: all 250ms; |   transition: all 250ms; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label:hover { | label:hover:not(.neutral-label) { | ||||||
|   background-color: var(--catch-detail-color); |   background-color: var(--catch-detail-color); | ||||||
|   color: var(--catch-detail-foregroundcolor); |   color: var(--catch-detail-foregroundcolor); | ||||||
|   border: 2px solid var(--interactive-contrast) |   border: 2px solid var(--interactive-contrast) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label:not(.no-image-background) img { | label:not(.no-image-background):not(.neutral-label) img { | ||||||
|   padding: 0.25rem; |   padding: 0.25rem; | ||||||
|   border-radius: 0.25rem; |   border-radius: 0.25rem; | ||||||
|   background: var(--low-interaction-background); |   background: var(--low-interaction-background); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label svg path { | label:not(.neutral-label) svg path { | ||||||
|   transition: all 250ms; |   transition: all 250ms; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label:hover:not(.no-image-background) svg path { | label:hover:not(.no-image-background):not(.neutral-label) svg path { | ||||||
|   fill: var(--catch-detail-foregroundcolor) !important; |   fill: var(--catch-detail-foregroundcolor) !important; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label.checked { | label.checked:not(.neutral-label) { | ||||||
|   border: 2px solid var(--foreground-color); |   border: 2px solid var(--foreground-color); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -96,7 +96,12 @@ export abstract class Store<T> implements Readable<T> { | ||||||
| 
 | 
 | ||||||
|     abstract map<J>(f: (t: T) => J): Store<J> |     abstract map<J>(f: (t: T) => J): Store<J> | ||||||
|     abstract map<J>(f: (t: T) => J, extraStoresToWatch: Store<any>[]): Store<J> |     abstract map<J>(f: (t: T) => J, extraStoresToWatch: Store<any>[]): Store<J> | ||||||
| 
 |     abstract map<J>( | ||||||
|  |         f: (t: T) => J, | ||||||
|  |         extraStoresToWatch: Store<any>[], | ||||||
|  |         callbackDestroyFunction: (f: () => void) => void | ||||||
|  |     ): Store<J> | ||||||
|  |     M | ||||||
|     public mapD<J>( |     public mapD<J>( | ||||||
|         f: (t: Exclude<T, undefined | null>) => J, |         f: (t: Exclude<T, undefined | null>) => J, | ||||||
|         extraStoresToWatch?: Store<any>[] |         extraStoresToWatch?: Store<any>[] | ||||||
|  | @ -329,9 +334,13 @@ export class ImmutableStore<T> extends Store<T> { | ||||||
|         return ImmutableStore.pass |         return ImmutableStore.pass | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     map<J>(f: (t: T) => J, extraStores: Store<any>[] = undefined): ImmutableStore<J> { |     map<J>( | ||||||
|  |         f: (t: T) => J, | ||||||
|  |         extraStores: Store<any>[] = undefined, | ||||||
|  |         ondestroyCallback?: (f: () => void) => void | ||||||
|  |     ): ImmutableStore<J> { | ||||||
|         if (extraStores?.length > 0) { |         if (extraStores?.length > 0) { | ||||||
|             return new MappedStore(this, f, extraStores, undefined, f(this.data)) |             return new MappedStore(this, f, extraStores, undefined, f(this.data), ondestroyCallback) | ||||||
|         } |         } | ||||||
|         return new ImmutableStore<J>(f(this.data)) |         return new ImmutableStore<J>(f(this.data)) | ||||||
|     } |     } | ||||||
|  | @ -463,7 +472,11 @@ class MappedStore<TIn, T> extends Store<T> { | ||||||
|         return this._data |         return this._data | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     map<J>(f: (t: T) => J, extraStores: Store<any>[] = undefined): Store<J> { |     map<J>( | ||||||
|  |         f: (t: T) => J, | ||||||
|  |         extraStores: Store<any>[] = undefined, | ||||||
|  |         ondestroyCallback?: (f: () => void) => void | ||||||
|  |     ): Store<J> { | ||||||
|         let stores: Store<any>[] = undefined |         let stores: Store<any>[] = undefined | ||||||
|         if (extraStores?.length > 0 || this._extraStores?.length > 0) { |         if (extraStores?.length > 0 || this._extraStores?.length > 0) { | ||||||
|             stores = [] |             stores = [] | ||||||
|  | @ -483,7 +496,8 @@ class MappedStore<TIn, T> extends Store<T> { | ||||||
|             f, // we could fuse the functions here (e.g. data => f(this._f(data), but this might result in _f being calculated multiple times, breaking things
 |             f, // we could fuse the functions here (e.g. data => f(this._f(data), but this might result in _f being calculated multiple times, breaking things
 | ||||||
|             stores, |             stores, | ||||||
|             this._callbacks, |             this._callbacks, | ||||||
|             f(this.data) |             f(this.data), | ||||||
|  |             ondestroyCallback | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ | ||||||
|   import { UIEventSource } from "../../Logic/UIEventSource.js" |   import { UIEventSource } from "../../Logic/UIEventSource.js" | ||||||
| 
 | 
 | ||||||
|   export let value: UIEventSource<any> |   export let value: UIEventSource<any> | ||||||
|   let i: any = value.data |  | ||||||
|   let htmlElement: HTMLSelectElement |   let htmlElement: HTMLSelectElement | ||||||
|   function selectAppropriateValue() { |   function selectAppropriateValue() { | ||||||
|     if (!htmlElement) { |     if (!htmlElement) { | ||||||
|  |  | ||||||
|  | @ -3,36 +3,20 @@ | ||||||
|    * Properly renders a translation |    * Properly renders a translation | ||||||
|    */ |    */ | ||||||
|   import { Translation } from "../i18n/Translation" |   import { Translation } from "../i18n/Translation" | ||||||
|   import { onDestroy } from "svelte" |  | ||||||
|   import Locale from "../i18n/Locale" |  | ||||||
|   import { Utils } from "../../Utils" |  | ||||||
|   import FromHtml from "./FromHtml.svelte" |  | ||||||
|   import WeblateLink from "./WeblateLink.svelte" |   import WeblateLink from "./WeblateLink.svelte" | ||||||
|  |   import { Store } from "../../Logic/UIEventSource" | ||||||
| 
 | 
 | ||||||
|   export let t: Translation |   export let t: Translation | ||||||
|   export let cls: string = "" |   export let cls: string = "" | ||||||
|   export let tags: Record<string, string> | undefined = undefined |  | ||||||
|   // Text for the current language |   // Text for the current language | ||||||
|   let txt: string | undefined |   let txt: Store<string | undefined> = t.current | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|   $: onDestroy( |  | ||||||
|     Locale.language.addCallbackAndRunD((l) => { |  | ||||||
|       const translation = t?.textFor(l) |  | ||||||
|       if (translation === undefined) { |  | ||||||
|         return |  | ||||||
|       } |  | ||||||
|       if (tags) { |  | ||||||
|         txt = Utils.SubstituteKeys(txt, tags) |  | ||||||
|       } else { |  | ||||||
|         txt = translation |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|   ) |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if t} | {#if t} | ||||||
|   <span class={cls}> |   <span class={cls}> | ||||||
|     <FromHtml src={txt} /> |     {$txt} | ||||||
|     <WeblateLink context={t.context} /> |     <WeblateLink context={t.context} /> | ||||||
|   </span> |   </span> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import { UIEventSource } from "../../Logic/UIEventSource" |   import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" | ||||||
|   import type { Feature } from "geojson" |   import type { Feature } from "geojson" | ||||||
|   import ToSvelte from "../Base/ToSvelte.svelte" |   import ToSvelte from "../Base/ToSvelte.svelte" | ||||||
|   import Svg from "../../Svg.js" |   import Svg from "../../Svg.js" | ||||||
|  | @ -30,7 +30,20 @@ | ||||||
|   let inputElement: HTMLInputElement |   let inputElement: HTMLInputElement | ||||||
| 
 | 
 | ||||||
|   let feedback: string = undefined |   let feedback: string = undefined | ||||||
| 
 |    | ||||||
|  |   let placeholder = Translations.t.general.search.search.current | ||||||
|  |   $:{ | ||||||
|  |     if(inputElement){ | ||||||
|  |     inputElement.placeholder = placeholder.data | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   onDestroy(placeholder.addCallbackAndRunD(placeholder => { | ||||||
|  |     if(inputElement){ | ||||||
|  |       inputElement.placeholder = placeholder | ||||||
|  |     } | ||||||
|  |   })) | ||||||
|  |    | ||||||
|  |    | ||||||
|   Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => { |   Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => { | ||||||
|     feedback = undefined |     feedback = undefined | ||||||
|     requestAnimationFrame(() => { |     requestAnimationFrame(() => { | ||||||
|  | @ -111,7 +124,6 @@ | ||||||
|         bind:this={inputElement} |         bind:this={inputElement} | ||||||
|         on:keypress={(keypr) => (keypr.key === "Enter" ? performSearch() : undefined)} |         on:keypress={(keypr) => (keypr.key === "Enter" ? performSearch() : undefined)} | ||||||
|         bind:value={searchContents} |         bind:value={searchContents} | ||||||
|         placeholder={Translations.t.general.search.search} |  | ||||||
|       /> |       /> | ||||||
|     {/if} |     {/if} | ||||||
|   </form> |   </form> | ||||||
|  |  | ||||||
|  | @ -41,6 +41,7 @@ | ||||||
|       on:click={() => { |       on:click={() => { | ||||||
|       expanded = true |       expanded = true | ||||||
|     }} |     }} | ||||||
|  |       aria-expanded={expanded} | ||||||
|     > |     > | ||||||
|       <Camera_plus class="mr-2 block h-8 w-8 p-1" /> |       <Camera_plus class="mr-2 block h-8 w-8 p-1" /> | ||||||
|       <Tr t={t.seeNearby} /> |       <Tr t={t.seeNearby} /> | ||||||
|  |  | ||||||
|  | @ -1,16 +1,17 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|     // Languages in the language itself |   // Languages in the language itself | ||||||
|     import native from "../../assets/language_native.json" |   import native from "../../assets/language_native.json" | ||||||
|     // Translated languages |   // Translated languages | ||||||
|     import language_translations from "../../assets/language_translations.json" |   import language_translations from "../../assets/language_translations.json" | ||||||
| 
 | 
 | ||||||
|     import { UIEventSource } from "../../Logic/UIEventSource" |   import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||||
|     import Locale from "../i18n/Locale" |   import Locale from "../i18n/Locale" | ||||||
|     import { LanguageIcon } from "@babeard/svelte-heroicons/solid" |   import { LanguageIcon } from "@babeard/svelte-heroicons/solid" | ||||||
|     import Dropdown from "../Base/Dropdown.svelte" |   import Dropdown from "../Base/Dropdown.svelte" | ||||||
|     import { twMerge } from "tailwind-merge" |   import { twMerge } from "tailwind-merge" | ||||||
| 
 |   import Translations from "../i18n/Translations" | ||||||
|     /** | import { ariaLabel } from "../../Utils/ariaLabel" | ||||||
|  |   /** | ||||||
|    * Languages one can choose from |    * Languages one can choose from | ||||||
|    * Defaults to _all_ languages known by MapComplete |    * Defaults to _all_ languages known by MapComplete | ||||||
|    */ |    */ | ||||||
|  | @ -19,7 +20,7 @@ | ||||||
|    * EventStore to assign to, defaults to 'Locale.langauge' |    * EventStore to assign to, defaults to 'Locale.langauge' | ||||||
|    */ |    */ | ||||||
|   export let assignTo: UIEventSource<string> = Locale.language |   export let assignTo: UIEventSource<string> = Locale.language | ||||||
|   export let preferredLanguages: UIEventSource<string[]> = undefined |   export let preferredLanguages: Store<string[]> = undefined | ||||||
|   let preferredFiltered: string[] = undefined |   let preferredFiltered: string[] = undefined | ||||||
|   preferredLanguages?.addCallbackAndRunD((preferredLanguages) => { |   preferredLanguages?.addCallbackAndRunD((preferredLanguages) => { | ||||||
|     let lng = navigator.language |     let lng = navigator.language | ||||||
|  | @ -31,34 +32,40 @@ | ||||||
|     } |     } | ||||||
|     preferredFiltered = preferredLanguages?.filter((l) => availableLanguages.indexOf(l) >= 0) |     preferredFiltered = preferredLanguages?.filter((l) => availableLanguages.indexOf(l) >= 0) | ||||||
|   }) |   }) | ||||||
|   export let clss : string = undefined |   export let clss: string = undefined | ||||||
|   let current = Locale.language |   let current = Locale.language | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if availableLanguages?.length > 1} | {#if availableLanguages?.length > 1} | ||||||
|   <form class={twMerge("flex items-center max-w-full pr-4", clss)}> |   <form class={twMerge("flex items-center max-w-full pr-4", clss)}> | ||||||
|     <LanguageIcon class="h-4 w-4 mr-1 shrink-0" /> |     <label class="flex neutral-label" use:ariaLabel={Translations.t.general.pickLanguage}> | ||||||
|     <Dropdown cls="max-w-full" value={assignTo}> |       <LanguageIcon class="h-4 w-4 mr-1 shrink-0" aria-hidden="true" /> | ||||||
|       {#if preferredFiltered} |       <Dropdown cls="max-w-full" value={assignTo}> | ||||||
|         {#each preferredFiltered as language} |         {#if preferredFiltered} | ||||||
|  |           {#each preferredFiltered as language} | ||||||
|  |             <option value={language} class="font-bold"> | ||||||
|  |               {native[language] ?? ""} | ||||||
|  |               {#if language !== $current} | ||||||
|  |                 ({language_translations[language]?.[$current] ?? language}) | ||||||
|  |               {/if} | ||||||
|  |             </option> | ||||||
|  |           {/each} | ||||||
|  |           <option disabled /> | ||||||
|  |         {/if} | ||||||
|  | 
 | ||||||
|  |         {#each availableLanguages.filter(l => l !== "_context") as language} | ||||||
|           <option value={language} class="font-bold"> |           <option value={language} class="font-bold"> | ||||||
|             {native[language] ?? ""} |             {native[language] ?? ""} | ||||||
|             {#if language !== $current} |             {#if language !== $current} | ||||||
|               ({language_translations[language]?.[$current] ?? language}) |               {#if language_translations[language]?.[$current] !== undefined} | ||||||
|  |                 ({ language_translations[language]?.[$current] + " - " + language ?? language}) | ||||||
|  |               {:else} | ||||||
|  |                 ({language}) | ||||||
|  |               {/if} | ||||||
|             {/if} |             {/if} | ||||||
|           </option> |           </option> | ||||||
|         {/each} |         {/each} | ||||||
|         <option disabled /> |       </Dropdown> | ||||||
|       {/if} |     </label> | ||||||
| 
 |  | ||||||
|       {#each availableLanguages as language} |  | ||||||
|         <option value={language} class="font-bold"> |  | ||||||
|           {native[language] ?? ""} |  | ||||||
|           {#if language !== $current} |  | ||||||
|             ({language_translations[language]?.[$current] + " - " + language ?? language}) |  | ||||||
|           {/if} |  | ||||||
|         </option> |  | ||||||
|       {/each} |  | ||||||
|     </Dropdown> |  | ||||||
|   </form> |   </form> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -63,6 +63,10 @@ export default class Locale { | ||||||
|             source = LocalStorageSource.Get("language", browserLanguage) |             source = LocalStorageSource.Get("language", browserLanguage) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         source.addCallbackAndRun((l) => { | ||||||
|  |             document.documentElement.setAttribute("lang", l) | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|         if (!Utils.runningFromConsole) { |         if (!Utils.runningFromConsole) { | ||||||
|             // @ts-ignore
 |             // @ts-ignore
 | ||||||
|             window.setLanguage = function (language: string) { |             window.setLanguage = function (language: string) { | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import Locale from "./Locale" | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| import BaseUIElement from "../BaseUIElement" | import BaseUIElement from "../BaseUIElement" | ||||||
| import LinkToWeblate from "../Base/LinkToWeblate" | import LinkToWeblate from "../Base/LinkToWeblate" | ||||||
|  | import { Store } from "../../Logic/UIEventSource" | ||||||
| 
 | 
 | ||||||
| export class Translation extends BaseUIElement { | export class Translation extends BaseUIElement { | ||||||
|     public static forcedLanguage = undefined |     public static forcedLanguage = undefined | ||||||
|  | @ -9,6 +10,9 @@ export class Translation extends BaseUIElement { | ||||||
|     public readonly translations: Record<string, string> |     public readonly translations: Record<string, string> | ||||||
|     public readonly context?: string |     public readonly context?: string | ||||||
| 
 | 
 | ||||||
|  |     private _current: Store<string> | ||||||
|  |     private onDestroy: () => void | ||||||
|  | 
 | ||||||
|     constructor(translations: string | Record<string, string>, context?: string) { |     constructor(translations: string | Record<string, string>, context?: string) { | ||||||
|         super() |         super() | ||||||
|         if (translations === undefined) { |         if (translations === undefined) { | ||||||
|  | @ -66,6 +70,18 @@ export class Translation extends BaseUIElement { | ||||||
|         return this.textFor(Translation.forcedLanguage ?? Locale.language.data) |         return this.textFor(Translation.forcedLanguage ?? Locale.language.data) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     get current(): Store<string> { | ||||||
|  |         if (!this._current) { | ||||||
|  |             this._current = Locale.language.map( | ||||||
|  |                 (l) => this.textFor(l), | ||||||
|  |                 [], | ||||||
|  |                 (f) => { | ||||||
|  |                     this.onDestroy = f | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |         return this._current | ||||||
|  |     } | ||||||
|     static ExtractAllTranslationsFrom( |     static ExtractAllTranslationsFrom( | ||||||
|         object: any, |         object: any, | ||||||
|         context = "" |         context = "" | ||||||
|  | @ -108,6 +124,7 @@ export class Translation extends BaseUIElement { | ||||||
| 
 | 
 | ||||||
|     Destroy() { |     Destroy() { | ||||||
|         super.Destroy() |         super.Destroy() | ||||||
|  |         this.onDestroy() | ||||||
|         this.isDestroyed = true |         this.isDestroyed = true | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								src/Utils/ariaLabel.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/Utils/ariaLabel.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | import { Translation } from "../UI/i18n/Translation" | ||||||
|  | 
 | ||||||
|  | export function ariaLabel(htmlElement: Element, t: Translation) { | ||||||
|  |     let onDestroy: () => void = undefined | ||||||
|  | 
 | ||||||
|  |     t.current.map( | ||||||
|  |         (label) => { | ||||||
|  |             console.log("Setting arialabel", label, "to", htmlElement) | ||||||
|  |             htmlElement.setAttribute("aria-label", label) | ||||||
|  |         }, | ||||||
|  |         [], | ||||||
|  |         (f) => { | ||||||
|  |             onDestroy = f | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     return { | ||||||
|  |         destroy() {}, | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -307,7 +307,7 @@ button.link:hover { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| label { | label:not(.neutral-label) { | ||||||
|     /** |     /** | ||||||
|      * Label should _contain_ the input element |      * Label should _contain_ the input element | ||||||
|      */ |      */ | ||||||
|  | @ -323,27 +323,27 @@ label { | ||||||
|     transition: all 250ms; |     transition: all 250ms; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label:hover { | label:hover:not(.neutral-label) { | ||||||
|     background-color: var(--catch-detail-color); |     background-color: var(--catch-detail-color); | ||||||
|     color: var(--catch-detail-foregroundcolor); |     color: var(--catch-detail-foregroundcolor); | ||||||
|     border: 2px solid var(--interactive-contrast) |     border: 2px solid var(--interactive-contrast) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label:not(.no-image-background) img { | label:not(.no-image-background):not(.neutral-label) img { | ||||||
|     padding: 0.25rem; |     padding: 0.25rem; | ||||||
|     border-radius: 0.25rem; |     border-radius: 0.25rem; | ||||||
|     background: var(--low-interaction-background); |     background: var(--low-interaction-background); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label svg path { | label:not(.neutral-label) svg path { | ||||||
|     transition: all 250ms; |     transition: all 250ms; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label:hover:not(.no-image-background) svg path { | label:hover:not(.no-image-background):not(.neutral-label) svg path { | ||||||
|     fill: var(--catch-detail-foregroundcolor) !important; |     fill: var(--catch-detail-foregroundcolor) !important; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| label.checked { | label.checked:not(.neutral-label) { | ||||||
|     border: 2px solid var(--foreground-color); |     border: 2px solid var(--foreground-color); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue