Radish alpha
r
Radicle desktop app
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add theme switch to the header
✓ CI success Rūdolfs Ošiņš committed 1 year ago
commit bb68d6c577eaa5b92e384abcd86e4486d7532a28
parent c14566923632e83202532a219a20af1bbbe0eaf0
1 passed (1 total) View logs
8 files changed +253 -81
modified src/App.svelte
@@ -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>
modified src/components/Border.svelte
@@ -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" />

modified src/components/Fill.svelte
@@ -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" />
modified src/components/Header.svelte
@@ -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>
modified src/components/Icon.svelte
@@ -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" />
deleted src/components/Label.svelte
@@ -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>
added src/components/Popover.svelte
@@ -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>
added src/components/ThemeSwitch.svelte
@@ -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>