mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-28 16:00:45 +08:00
Add theme switch to the header
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { Router, Route } from "svelte-routing";
|
||||
import { theme } from "@app/components/ThemeSwitch.svelte";
|
||||
|
||||
import DesignSystem from "@app/views/DesignSystem.svelte";
|
||||
import Repos from "@app/views/Repos.svelte";
|
||||
import Startup from "@app/views/Startup.svelte";
|
||||
|
||||
$: document.documentElement.setAttribute("data-theme", $theme);
|
||||
</script>
|
||||
|
||||
<Router>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let variant: "primary" | "secondary" | "ghost";
|
||||
export let stylePadding: string | undefined = undefined;
|
||||
export let styleHeight: string | undefined = undefined;
|
||||
|
||||
$: style = `--button-color-1: var(--color-fill-${variant});`;
|
||||
</script>
|
||||
@@ -123,7 +125,13 @@
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="container" on:click role="button" tabindex="0" {style}>
|
||||
<div
|
||||
class="container"
|
||||
on:click
|
||||
role="button"
|
||||
tabindex="0"
|
||||
{style}
|
||||
style:height={styleHeight}>
|
||||
<div class="pixel p1-1" />
|
||||
<div class="pixel p1-2" />
|
||||
<div class="pixel p1-3" />
|
||||
@@ -138,7 +146,9 @@
|
||||
|
||||
<div class="pixel p3-1" />
|
||||
<div class="pixel p3-2" />
|
||||
<div class="pixel p3-3 txt-semibold txt-small"><slot /></div>
|
||||
<div class="pixel p3-3 txt-semibold txt-small" style:padding={stylePadding}>
|
||||
<slot />
|
||||
</div>
|
||||
<div class="pixel p3-4" />
|
||||
<div class="pixel p3-5" />
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let variant: "primary" | "secondary" | "ghost" | "transparent";
|
||||
export let stylePadding: string | undefined = undefined;
|
||||
export let styleHeight: string | undefined = undefined;
|
||||
|
||||
$: style =
|
||||
variant === "transparent"
|
||||
@@ -71,7 +72,13 @@
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="container" on:click role="button" tabindex="0" {style}>
|
||||
<div
|
||||
class="container"
|
||||
on:click
|
||||
role="button"
|
||||
tabindex="0"
|
||||
{style}
|
||||
style:height={styleHeight}>
|
||||
<div class="pixel p1-1" />
|
||||
<div class="pixel p1-2" />
|
||||
<div class="pixel p1-3" />
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<script lang="ts">
|
||||
import Background from "./Header/Background.svelte";
|
||||
import Border from "./Border.svelte";
|
||||
import Fill from "./Fill.svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
import Label from "./Label.svelte";
|
||||
import Popover from "./Popover.svelte";
|
||||
import ThemeSwitch from "./ThemeSwitch.svelte";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -11,7 +14,7 @@
|
||||
}
|
||||
header {
|
||||
padding: 0 0.5rem;
|
||||
gap: 1.5rem;
|
||||
gap: 0.25rem;
|
||||
height: 3rem;
|
||||
}
|
||||
.wrapper {
|
||||
@@ -32,20 +35,36 @@
|
||||
<Icon name="arrow-left" />
|
||||
<Icon name="arrow-right" />
|
||||
</div>
|
||||
<Label
|
||||
styleBorderColor="var(--color-fill-ghost)"
|
||||
styleFillColor="var(--color-fill-ghost)">
|
||||
<Fill variant="ghost" stylePadding="0 0.5rem" styleHeight="32px">
|
||||
Repositories
|
||||
</Label>
|
||||
<Label styleBorderColor="var(--color-fill-ghost)">
|
||||
</Fill>
|
||||
<Border variant="ghost" stylePadding="0 0.25rem" styleHeight="32px">
|
||||
<Icon name="plus" />
|
||||
</Label>
|
||||
</Border>
|
||||
</div>
|
||||
<div class="flex-item">
|
||||
<Label styleBorderColor="var(--color-fill-ghost)">
|
||||
|
||||
<div class="flex-item" style:gap="0.5rem">
|
||||
<Border variant="ghost" stylePadding="0 0.5rem" styleHeight="32px">
|
||||
<Icon name="offline" /> Offline
|
||||
</Label>
|
||||
</Border>
|
||||
<Popover popoverPositionRight="0" popoverPositionTop="3rem">
|
||||
<Border
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}
|
||||
variant="ghost"
|
||||
stylePadding="0 0.25rem"
|
||||
styleHeight="32px">
|
||||
<Icon name="settings" />
|
||||
</Border>
|
||||
<Border variant="ghost" slot="popover" stylePadding="0.5rem 1rem">
|
||||
<div style="display: flex; gap: 2rem; align-items: center;">
|
||||
Theme <ThemeSwitch />
|
||||
</div>
|
||||
</Border>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Background />
|
||||
</header>
|
||||
|
||||
@@ -12,10 +12,13 @@
|
||||
| "diff"
|
||||
| "file"
|
||||
| "inbox"
|
||||
| "moon"
|
||||
| "offline"
|
||||
| "plus"
|
||||
| "repo"
|
||||
| "seedling"
|
||||
| "settings"
|
||||
| "sun"
|
||||
| "warning";
|
||||
</script>
|
||||
|
||||
@@ -127,6 +130,30 @@
|
||||
<path d="M6 9H10L10 10H6L6 9Z" />
|
||||
<path d="M3 13H13V14H3L3 13Z" />
|
||||
<path d="M3 2H13V3H3L3 2Z" />
|
||||
{:else if name === "moon"}
|
||||
<path d="M4 3H6V4H4V3Z" />
|
||||
<path d="M3 4L4 4L4 6H3V4Z" />
|
||||
<path d="M2 6L3 6L3 10H2V6Z" />
|
||||
<path d="M3 10H4V12H3L3 10Z" />
|
||||
<path d="M4 12H6V13H4L4 12Z" />
|
||||
<path d="M6 13H10V14H6L6 13Z" />
|
||||
<path d="M6 13H10V14H6L6 13Z" />
|
||||
<path d="M12 10H13V12H12V10Z" />
|
||||
<path d="M8 9H10V10H8V9Z" />
|
||||
<path d="M6 6H7V7H6V6Z" />
|
||||
<path d="M6 7H7V8H6V7Z" />
|
||||
<path d="M7 8H8V9H7V8Z" />
|
||||
<path d="M10 10H12V11H10V10Z" />
|
||||
<path d="M5 5H6V6H5V5Z" />
|
||||
<path d="M10 12H12V13H10L10 12Z" />
|
||||
<path d="M5 4H6L6 5H5L5 4Z" />
|
||||
<path d="M13 9H14V11H13V9Z" />
|
||||
<path d="M5 2H7V3H5V2Z" />
|
||||
<path d="M5 2H7V3H5V2Z" />
|
||||
<path d="M5 2H7V3H5V2Z" />
|
||||
<path d="M11 4H12V7H11V4Z" />
|
||||
<path d="M10 5H13V6H10V5Z" />
|
||||
<path d="M9 3H10V4H9V3Z" />
|
||||
{:else if name === "offline"}
|
||||
<path d="M3 6L3 8H2L2 6H3Z" />
|
||||
<path d="M13 10V8H14V10H13Z" />
|
||||
@@ -226,6 +253,44 @@
|
||||
<path d="M7.33301 8V7L6.33301 7L6.33301 8H7.33301Z" />
|
||||
<path d="M7.33301 9L7.33301 7L6.33301 7L6.33301 9H7.33301Z" />
|
||||
<path d="M6.33301 10V9H5.33301L5.33301 10H6.33301Z" />
|
||||
{:else if name === "settings"}
|
||||
<path d="M7 5H14V6H7V5Z" />
|
||||
<path d="M9 11L2 11L2 10L9 10V11Z" />
|
||||
<path d="M2 5H5V6H2V5Z" />
|
||||
<path d="M14 11H11V10H14V11Z" />
|
||||
<path d="M9 8H11V9H9V8Z" />
|
||||
<path d="M5 3H7V4H5V3Z" />
|
||||
<path d="M9 12H11V13H9V12Z" />
|
||||
<path d="M5 7H7V8H5V7Z" />
|
||||
<path d="M8 9L9 9V12H8V9Z" />
|
||||
<path d="M4 4L5 4L5 7H4V4Z" />
|
||||
<path d="M11 9L12 9V12H11V9Z" />
|
||||
<path d="M7 4L8 4V7L7 7V4Z" />
|
||||
{:else if name === "sun"}
|
||||
<path d="M8 2H9V3H8V2Z" />
|
||||
<path d="M14 8V9H13V8H14Z" />
|
||||
<path d="M4 7V8H3V7H4Z" />
|
||||
<path d="M7 12H8V13H7V12Z" />
|
||||
<path d="M8 3H9L9 4H8L8 3Z" />
|
||||
<path d="M13 8V9H12V8H13Z" />
|
||||
<path d="M3 7V8H2L2 7H3Z" />
|
||||
<path d="M7 13H8V14H7V13Z" />
|
||||
<path d="M7 5H9V6H7V5Z" />
|
||||
<path d="M7 10H9V11H7V10Z" />
|
||||
<path d="M5 7H6V9H5V7Z" />
|
||||
<path d="M10 7H11V9H10V7Z" />
|
||||
<path d="M11 5H12V6H11V5Z" />
|
||||
<path d="M10 12H11V11H10V12Z" />
|
||||
<path d="M6 4H5V5H6V4Z" />
|
||||
<path d="M5 11H4V10H5V11Z" />
|
||||
<path d="M12 4H13V5H12V4Z" />
|
||||
<path d="M11 13H12V12H11V13Z" />
|
||||
<path d="M5 3H4V4H5L5 3Z" />
|
||||
<path d="M4 12H3V11H4L4 12Z" />
|
||||
<path d="M6 6H7V7L6 7V6Z" />
|
||||
<path d="M6 9L7 9L7 10H6V9Z" />
|
||||
<path d="M9 9L10 9L10 10H9L9 9Z" />
|
||||
<path d="M9 6H10V7H9V6Z" />
|
||||
{:else if name === "warning"}
|
||||
<path d="M7 2H9V3H7V2Z" />
|
||||
<path d="M6 3H7V5H6V3Z" />
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let styleBorderColor: string;
|
||||
export let styleFillColor: string = "transparent";
|
||||
export let emptyPixels: 2 | 1 = 2;
|
||||
|
||||
const gridTemplate = `${"2px ".repeat(emptyPixels)}auto ${"2px ".repeat(emptyPixels)}`;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
padding: 2px 8px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
.container {
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
column-gap: 0;
|
||||
height: 2rem;
|
||||
row-gap: 0;
|
||||
display: grid;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
class="container"
|
||||
style:grid-template-columns={gridTemplate}
|
||||
style:grid-template-rows={gridTemplate}>
|
||||
{@html "<div></div>".repeat(emptyPixels)}
|
||||
<div style:background-color={styleBorderColor} />
|
||||
{@html "<div></div>".repeat(emptyPixels)}
|
||||
|
||||
{#if emptyPixels === 2}
|
||||
<div />
|
||||
<div style:background-color={styleBorderColor} />
|
||||
<div style:background-color={styleFillColor} />
|
||||
<div style:background-color={styleBorderColor} />
|
||||
<div />
|
||||
{/if}
|
||||
|
||||
<div style:background-color={styleBorderColor} />
|
||||
{#if emptyPixels === 2}
|
||||
<div style:background-color={styleFillColor} />
|
||||
{/if}
|
||||
<div
|
||||
class="content txt-semibold txt-small"
|
||||
style:background-color={styleFillColor}>
|
||||
<slot />
|
||||
</div>
|
||||
{#if emptyPixels === 2}
|
||||
<div style:background-color={styleFillColor} />
|
||||
{/if}
|
||||
<div style:background-color={styleBorderColor} />
|
||||
|
||||
{#if emptyPixels === 2}
|
||||
<div />
|
||||
<div style:background-color={styleBorderColor} />
|
||||
<div style:background-color={styleFillColor} />
|
||||
<div style:background-color={styleBorderColor} />
|
||||
<div />
|
||||
{/if}
|
||||
|
||||
{@html "<div></div>".repeat(emptyPixels)}
|
||||
<div style:background-color={styleBorderColor} />
|
||||
{@html "<div></div>".repeat(emptyPixels)}
|
||||
</div>
|
||||
@@ -0,0 +1,70 @@
|
||||
<script lang="ts" context="module">
|
||||
import { writable } from "svelte/store";
|
||||
const focused = writable<HTMLDivElement | undefined>(undefined);
|
||||
|
||||
export function closeFocused() {
|
||||
focused.set(undefined);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let popoverContainerMinWidth: string | undefined = undefined;
|
||||
export let popoverBorderRadius: string | undefined = undefined;
|
||||
export let popoverPadding: string | undefined = undefined;
|
||||
export let popoverPositionBottom: string | undefined = undefined;
|
||||
export let popoverPositionLeft: string | undefined = undefined;
|
||||
export let popoverPositionRight: string | undefined = undefined;
|
||||
export let popoverPositionTop: string | undefined = undefined;
|
||||
|
||||
export let expanded = false;
|
||||
let thisComponent: HTMLDivElement;
|
||||
|
||||
function clickOutside(ev: MouseEvent | TouchEvent) {
|
||||
if ($focused && !ev.composedPath().includes($focused)) {
|
||||
closeFocused();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
expanded = !expanded;
|
||||
if ($focused === thisComponent) {
|
||||
closeFocused();
|
||||
} else {
|
||||
focused.set(thisComponent);
|
||||
}
|
||||
}
|
||||
|
||||
$: expanded = $focused === thisComponent;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
.popover {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:window on:click={clickOutside} on:touchstart={clickOutside} />
|
||||
|
||||
<div
|
||||
bind:this={thisComponent}
|
||||
class="container"
|
||||
style:min-width={popoverContainerMinWidth}>
|
||||
<slot name="toggle" {expanded} {toggle} />
|
||||
|
||||
{#if expanded}
|
||||
<div
|
||||
class="popover"
|
||||
style:bottom={popoverPositionBottom}
|
||||
style:left={popoverPositionLeft}
|
||||
style:right={popoverPositionRight}
|
||||
style:top={popoverPositionTop}
|
||||
style:padding={popoverPadding}
|
||||
style:border-radius={popoverBorderRadius}>
|
||||
<slot name="popover" {toggle} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,65 @@
|
||||
<script lang="ts" context="module">
|
||||
type Theme = "dark" | "light";
|
||||
|
||||
export const theme = writable<Theme>(loadTheme());
|
||||
|
||||
function loadTheme(): Theme {
|
||||
const { matches } = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const storedTheme = localStorage ? localStorage.getItem("theme") : null;
|
||||
|
||||
if (storedTheme === null) {
|
||||
return matches ? "dark" : "light";
|
||||
} else {
|
||||
return storedTheme as Theme;
|
||||
}
|
||||
}
|
||||
|
||||
export function storeTheme(newTheme: Theme): void {
|
||||
theme.set(newTheme);
|
||||
if (localStorage) {
|
||||
localStorage.setItem("theme", newTheme);
|
||||
} else {
|
||||
console.warn(
|
||||
"localStorage isn't available, not able to persist the selected theme without it.",
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
import Border from "./Border.svelte";
|
||||
import Fill from "./Fill.svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
</script>
|
||||
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<Border styleHeight="32px" variant="secondary">
|
||||
<Fill
|
||||
stylePadding="0 0.5rem"
|
||||
variant={$theme === "dark" ? "secondary" : "transparent"}
|
||||
on:click={() => {
|
||||
storeTheme("dark");
|
||||
}}>
|
||||
<Icon name="moon" />
|
||||
Dark
|
||||
</Fill>
|
||||
|
||||
<Fill
|
||||
stylePadding="0 0.5rem"
|
||||
variant={$theme === "light" ? "secondary" : "transparent"}
|
||||
on:click={() => {
|
||||
storeTheme("light");
|
||||
}}>
|
||||
<span
|
||||
style="display: flex; align-items: center; gap: 0.5rem"
|
||||
style:color={$theme === "light"
|
||||
? "var(--color-foreground-white)"
|
||||
: "var(--color-foreground-contrast)"}>
|
||||
<Icon name="sun" />
|
||||
Light
|
||||
</span>
|
||||
</Fill>
|
||||
</Border>
|
||||
</div>
|
||||
Reference in New Issue
Block a user