Skip to content

Commit

Permalink
Merge pull request #295 from dmlb/dark-mode-toggle
Browse files Browse the repository at this point in the history
dark mode toggle, modularization into Svelte components, filter "and/or" mode
  • Loading branch information
VeckoTheGecko authored Jul 25, 2023
2 parents 3d09d9e + ce1b731 commit 0e86224
Show file tree
Hide file tree
Showing 17 changed files with 665 additions and 401 deletions.
13 changes: 4 additions & 9 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,10 @@ details:not([open]) > summary svg {
outline: auto;
}

/* user prefered / OS colour scheme for tag colors */
@media (prefers-color-scheme: light) {
.tag-color {
background-color: var(--tag-color) !important;
}
.tag-color {
background-color: var(--tag-color) !important;
}

@media (prefers-color-scheme: dark) {
.tag-color {
background-color: var(--tag-color-dark) !important;
}
.dark .tag-color {
background-color: var(--tag-color-dark) !important;
}
13 changes: 13 additions & 0 deletions src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%

<script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
if (
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
</script>
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
Expand Down
70 changes: 70 additions & 0 deletions src/lib/components/ButtonLinks.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<script lang="ts">
// DESIGN
export let version: "hollow" | "filled" = "hollow";
export let color: "green" | "red" = "green";
export let isCircle: boolean = false;
export let extraClasses: string = "";
// button el
export let disabled: boolean = false;
export let type: "button" | "reset" | "submit" = "button";
// anchor el
export let link: boolean = false;
export let url: string = "#";
export let newTab: boolean = false;
export let download: boolean = false;
let target: "_blank" | undefined;
let rel: string | undefined;
if (newTab) {
target = "_blank";
rel = "noreferrer";
}
let designClasses: string = isCircle ? "rounded-full" : "rounded-lg";
const designClassesMap = {
hollow: {
green: "border-green-500 dark:border-green-700 text-green-700 dark:text-green-500 hover:text-black hover:bg-green-500 dark:hover:text-white dark:hover:bg-green-900",
red: "border-red-500 dark:border-red-700 text-red-500 dark:text-red-400 hover:bg-red-500 hover:text-black dark:hover:text-white dark:hover:bg-red-900",
},
filled: {
green: "bg-green-700 text-white dark:bg-green-900/75 border-green-700 hover:border-green-500 dark:hover:border-green-700 dark:border-green-900/75",
red: "bg-red-700 text-white dark:bg-red-900/75 border-red-700 hover:border-red-500 dark:hover:border-red-700 dark:border-red-900/75",
},
};
designClasses = `${designClasses} ${designClassesMap[version][color]}`;
</script>

{#if link}
<a
class="btn-base {designClasses} {extraClasses}"
href={url}
{target}
{rel}
{download}
><slot name="icon" />
<slot name="label" />
{#if newTab}<span class="sr-only">in a new tab</span>{/if}
</a>
{:else}
<button
{type}
{disabled}
on:click
class="btn-base {designClasses} {extraClasses} disabled:text-zinc-400 disabled:border-current disabled:bg-zinc-200 dark:disabled:bg-zinc-700 disabled:hover:text-zinc-400 disabled:cursor-not-allowed"
>
<slot name="icon" />
<slot name="label" />
</button>
{/if}

<style>
.btn-base {
@apply inline-flex items-center gap-1;
@apply border-2 p-2;
@apply transition-colors;
}
</style>
28 changes: 28 additions & 0 deletions src/lib/components/Checkbox.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script lang="ts">
const removeWhitespace = (str: string) => {
return str.replace(/\W+/g, "");
};
export let name: string;
export let id: string | undefined = undefined;
export let checked: boolean;
let checkboxId: string = id ? id : removeWhitespace(name);
</script>

<label
class="cursor-pointer py-2 px-3 rounded-full flex items-center gap-2 text-sm"
for={checkboxId}
>
<input
type="checkbox"
class="appearance-none cursor-pointer w-6 h-6 rounded-full bg-white dark:bg-black checked:bg-zinc-800 dark:checked:bg-green-600 transition duration-200"
bind:checked
id={checkboxId}
name={checkboxId}
/>
<span>
{name}
<slot />
</span>
</label>
45 changes: 45 additions & 0 deletions src/lib/components/DarkModeControl.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script lang="ts">
import { browser } from "$app/environment";
export let cssClass: string;
let darkMode: boolean;
$: darkMode = browser
? localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
: false;
const setColorMode = () => {
if (darkMode) {
document.documentElement.classList.remove("dark");
darkMode = false;
if (browser) {
localStorage.theme = "light";
}
} else {
document.documentElement.classList.add("dark");
darkMode = true;
if (browser) {
localStorage.theme = "dark";
}
}
return;
};
</script>

<button type="button" role="switch" aria-checked={darkMode} class={cssClass} on:click={setColorMode}>
{#if darkMode}
<!-- sun-fill icon -->
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sun-fill" viewBox="0 0 16 16">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
</svg>
<span class="sr-only">Light</span>
{:else}
<!-- moon-stars-fill icon -->
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-moon-stars-fill" viewBox="0 0 16 16">
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
<path d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z"/>
</svg>
<span class="sr-only">Dark</span>
{/if}
<span class="sr-only">&nbsp;Colour Scheme Mode</span>
</button>
142 changes: 142 additions & 0 deletions src/lib/components/FilterForm.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import type { FilterOption, FilterLogic, CustomFilterEvent } from "$lib/interfaces";
import Collapsible from "$lib/components/Collapsible.svelte";
import TagWrapper from "$lib/components/TagWrapper.svelte";
import Checkbox from "$lib/components/Checkbox.svelte";
import ButtonLinks from "$lib/components/ButtonLinks.svelte";
const dispatch = createEventDispatcher<CustomFilterEvent>();
let form: HTMLFormElement;
export let filterOptions: FilterOption[];
export let showFilterLogic: boolean = true;
// Whether all the selected tags must match the resource (vs any of the selected tags)
export let filterLogicAnd: boolean = true;
let filterLogic: FilterLogic
$: filterLogic = filterLogicAnd ? "and" : "or";
let isFilterDirty: boolean;
$: isFilterDirty = filterOptions.some(
(option: FilterOption) => option.active === true
);
const resetFilters = () => {
filterOptions.forEach(option => option.active = false)
dispatch("filter", {filterOptions, filterLogic})
}
const onSubmit = () => {
dispatch("filter", {filterOptions, filterLogic})
}
</script>

{#if filterOptions}
<Collapsible label="Filter">
<form
bind:this={form}
on:submit|preventDefault={onSubmit}
class="p-4 space-y-4"
>
<div class="flex flex-row flex-wrap gap-2">
{#each filterOptions as filterOption}
<!-- checkboxes -->
<TagWrapper
tagColor={filterOption.color}
extraClasses="input-wrapper-focus flex justify-between gap-2"
>
<Checkbox
name={filterOption.name}
bind:checked={filterOption.active}
>
<span
class="text-zinc-700 dark:text-zinc-300 italic"
>({filterOption.count})</span
>
</Checkbox>
</TagWrapper>
{/each}
</div>
<div class="flex gap-2 justify-end">
{#if showFilterLogic}
<label aria-label="filter with and / or"
for="switch"
class="inline-flex items-center rounded-md cursor-pointer outline-2 outline-offset-1 focus-within:outline text-white border-2 border-green-700 dark:border-green-900/75"
>
<input
bind:checked={filterLogicAnd}
aria-checked={filterLogicAnd}
id="switch"
role="switch"
type="checkbox"
class="opacity-0 absolute peer"
/>
<span
class="px-4 py-3 rounded-l-sm
bg-white text-zinc-500 dark:text-zinc-400 dark:bg-zinc-800 peer-checked:bg-green-700 peer-checked:text-white dark:peer-checked:bg-green-900/75"
>
<!-- intersect icon -->
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-intersect" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v2h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2H2a2 2 0 0 1-2-2V2zm5 10v2a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2v5a2 2 0 0 1-2 2H5zm6-8V2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2V6a2 2 0 0 1 2-2h5z"/>
</svg><span class="sr-only">and</span></span
>
<span
class="px-4 py-3 rounded-r-sm
bg-green-700 dark:bg-green-900/75
peer-checked:text-zinc-500 peer-checked:bg-white dark:peer-checked:bg-zinc-800 dark:peer-checked:text-zinc-400"
>
<!-- union icon -->

<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-union" viewBox="0 0 16 16">
<path d="M0 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v2h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2H2a2 2 0 0 1-2-2V2z"/>
</svg><span class="sr-only">or</span></span
>
</label>
{/if}
<ButtonLinks
type="reset"
on:click={resetFilters}
disabled={!isFilterDirty}
version="filled"
color="green"
>
<svg
slot="icon"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 inline"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
<span slot="label">Clear All</span>
</ButtonLinks>

<ButtonLinks type="submit" version="filled" color="green">
<svg
slot="icon"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 inline"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z"
/>
</svg>
<span slot="label">Filter</span>
</ButtonLinks>
</div>
</form>
</Collapsible>
{/if}
Loading

0 comments on commit 0e86224

Please sign in to comment.