Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Refactor Dropdown component
Sebastian Martinez committed 2 years ago
commit 46494461f2b5c03081798131374ecae298df1a7c
parent 3465c92a7e9805682ad805d82105c224d68e4e67
8 files changed +142 -182
modified src/components/Dropdown.svelte
@@ -1,30 +1,12 @@
-
<script lang="ts" context="module">
-
  export interface Item<T> {
-
    title: string;
-
    value: T;
-
    alias?: T;
-
    badge: string | null;
-
  }
-
</script>
-

-
<script lang="ts" strictEvents>
-
  import { createEventDispatcher } from "svelte";
-
  import { twemoji } from "@app/lib/utils";
-
  import Badge from "@app/components/Badge.svelte";
-

+
<script lang="ts">
  type T = $$Generic;

-
  export let items: Item<T>[];
-
  export let selected: T | null = null;
-

-
  const dispatch = createEventDispatcher<{ select: Item<T> }>();
-
  const onSelect = (item: Item<T>) => {
-
    dispatch("select", item);
-
  };
+
  export let items: T[];
</script>

<style>
  .dropdown {
+
    align-items: center;
    background-color: var(--color-background-1);
    margin-top: 0.5rem;
    padding: 0.5rem 0;
@@ -32,37 +14,13 @@
    box-shadow: var(--elevation-low);
    z-index: 10;
    border-radius: var(--border-radius-small);
-
  }
-

-
  .dropdown-item {
-
    white-space: nowrap;
-
    cursor: pointer;
-
    padding: 0.5rem 1rem;
-
    display: flex;
-
    align-items: center;
-
    gap: 0.5rem;
-
  }
-
  .dropdown-item:hover,
-
  .selected {
-
    background-color: var(--color-foreground-2);
+
    overflow-y: auto;
+
    max-height: 60vh;
  }
</style>

<div class="dropdown">
  {#each items as item}
-
    <!-- svelte-ignore a11y-click-events-have-key-events -->
-
    <div
-
      class="dropdown-item"
-
      class:selected={item.value === selected}
-
      use:twemoji
-
      on:click={() => onSelect(item)}
-
      title={item.title}>
-
      <slot name="item" {item}>
-
        {item.title}
-
        {#if item.badge}
-
          <Badge variant="primary">{item.badge}</Badge>
-
        {/if}
-
      </slot>
-
    </div>
+
    <slot name="item" {item} />
  {/each}
</div>
added src/components/Dropdown/DropdownItem.svelte
@@ -0,0 +1,35 @@
+
<script lang="ts">
+
  export let selected: boolean;
+
  export let title: string | undefined = undefined;
+
  export let size: "small" | "tiny";
+
</script>
+

+
<style>
+
  .item {
+
    cursor: pointer;
+
    display: flex;
+
    align-items: center;
+
    flex-direction: row;
+
    gap: 0.5rem;
+
    padding: 0.5rem 1rem;
+
    white-space: nowrap;
+
    /* makes sure peer selector items with badges are same height
+
       as ones without */
+
    height: 34px;
+
  }
+
  .item:hover,
+
  .selected {
+
    background-color: var(--color-foreground-2);
+
  }
+
</style>
+

+
<!-- svelte-ignore a11y-click-events-have-key-events -->
+
<div
+
  class="item"
+
  class:selected
+
  {title}
+
  on:click
+
  class:txt-small={size === "small"}
+
  class:txt-tiny={size === "tiny"}>
+
  <slot />
+
</div>
modified src/views/projects/BranchSelector.svelte
@@ -2,6 +2,7 @@
  import * as utils from "@app/lib/utils";

  import Dropdown from "@app/components/Dropdown.svelte";
+
  import DropdownItem from "@app/components/Dropdown/DropdownItem.svelte";
  import Floating from "@app/components/Floating.svelte";
  import ProjectLink from "@app/components/ProjectLink.svelte";

@@ -80,12 +81,14 @@
          {branchLabel}
        </div>
        <svelte:fragment slot="modal">
-
          <Dropdown items={branchList} selected={branchLabel}>
-
            <div class="branch-item" slot="item" let:item>
+
          <Dropdown items={branchList}>
+
            <svelte:fragment slot="item" let:item>
              <ProjectLink projectParams={{ revision: item.value }} on:click>
-
                {item.value}
+
                <DropdownItem selected={item.value === branchLabel} size="tiny">
+
                  {item.value}
+
                </DropdownItem>
              </ProjectLink>
-
            </div>
+
            </svelte:fragment>
          </Dropdown>
        </svelte:fragment>
      </Floating>
modified src/views/projects/Cob/CobStateButton.svelte
@@ -1,25 +1,25 @@
<script lang="ts" strictEvents>
-
  import type { Item } from "@app/components/Dropdown.svelte";
-

  type T = $$Generic;

  import Button from "@app/components/Button.svelte";
  import Dropdown from "@app/components/Dropdown.svelte";
+
  import DropdownItem from "@app/components/Dropdown/DropdownItem.svelte";
  import Floating from "@app/components/Floating.svelte";
  import Icon from "@app/components/Icon.svelte";
+

  import { closeFocused } from "@app/components/Floating.svelte";
  import { createEventDispatcher } from "svelte";
  import { isEqual } from "lodash";

  export let state: T;
-
  export let selectedItem: Item<T>;
-
  export let items: Item<T>[];
+
  export let selectedItem: [string, T];
+
  export let items: [string, T][];

  const dispatch = createEventDispatcher<{
    saveStatus: T;
  }>();

-
  function switchCaption({ detail: item }: CustomEvent<Item<T>>) {
+
  function switchCaption(item: [string, T]) {
    selectedItem = item;
    closeFocused();
  }
@@ -58,9 +58,9 @@
  <Button
    variant="foreground"
    size="small"
-
    on:click={() => dispatch("saveStatus", selectedItem.value)}
+
    on:click={() => dispatch("saveStatus", selectedItem[1])}
    style={attachableStyle}>
-
    {selectedItem.title}
+
    {selectedItem[0]}
  </Button>
  <Floating>
    <svelte:fragment slot="toggle">
@@ -69,9 +69,16 @@
      </button>
    </svelte:fragment>
    <svelte:fragment slot="modal">
-
      <Dropdown
-
        on:select={switchCaption}
-
        items={items.filter(i => !isEqual(i.value, state))} />
+
      <Dropdown items={items.filter(i => !isEqual(i, state))}>
+
        <svelte:fragment slot="item" let:item>
+
          <DropdownItem
+
            selected={false}
+
            on:click={() => switchCaption(item)}
+
            size="small">
+
            {item[0]}
+
          </DropdownItem>
+
        </svelte:fragment>
+
      </Dropdown>
    </svelte:fragment>
  </Floating>
</div>
modified src/views/projects/Cob/Revision.svelte
@@ -12,6 +12,7 @@
  import CommentComponent from "@app/components/Comment.svelte";
  import DiffStatBadge from "@app/components/DiffStatBadge.svelte";
  import Dropdown from "@app/components/Dropdown.svelte";
+
  import DropdownItem from "@app/components/Dropdown/DropdownItem.svelte";
  import ErrorMessage from "@app/components/ErrorMessage.svelte";
  import Floating from "@app/components/Floating.svelte";
  import Icon from "@app/components/Icon.svelte";
@@ -181,39 +182,26 @@
        <svelte:fragment slot="modal">
          <Dropdown
            items={previousRevOid && previousRevId
-
              ? [
-
                  {
-
                    title: projectHead,
-
                    value: projectHead,
-
                    badge: null,
-
                  },
-
                  {
-
                    title: previousRevOid,
-
                    value: previousRevOid,
-
                    badge: null,
-
                  },
-
                ]
-
              : [
-
                  {
-
                    title: projectHead,
-
                    value: projectHead,
-
                    badge: null,
-
                  },
-
                ]}>
+
              ? [projectHead, previousRevOid]
+
              : [projectHead]}>
            <svelte:fragment slot="item" let:item>
              <ProjectLink
-
                title="{item.value}..{revisionOid}"
+
                title="{item}..{revisionOid}"
                projectParams={{
-
                  search: `diff=${item.value}..${revisionOid}`,
+
                  search: `diff=${item}..${revisionOid}`,
                }}>
-
                {#if item.value === projectHead}
-
                  Compare to {projectDefaultBranch} ({utils.formatObjectId(
-
                    projectHead,
-
                  )})
+
                {#if item === projectHead}
+
                  <DropdownItem selected={false} size="small">
+
                    Compare to {projectDefaultBranch} ({utils.formatObjectId(
+
                      projectHead,
+
                    )})
+
                  </DropdownItem>
                {:else if previousRevId}
-
                  Compare to previous revision ({utils.formatObjectId(
-
                    previousRevId,
-
                  )})
+
                  <DropdownItem selected={false} size="small">
+
                    Compare to previous revision ({utils.formatObjectId(
+
                      previousRevId,
+
                    )})
+
                  </DropdownItem>
                {/if}
              </ProjectLink>
            </svelte:fragment>
modified src/views/projects/Issue.svelte
@@ -1,6 +1,5 @@
<script lang="ts" strictEvents>
  import type { BaseUrl, Issue, IssueState } from "@httpd-client";
-
  import type { Item } from "@app/components/Dropdown.svelte";

  import { createEventDispatcher } from "svelte";

@@ -19,6 +18,7 @@
  import TagInput from "./Cob/TagInput.svelte";
  import Textarea from "@app/components/Textarea.svelte";
  import Thread from "@app/components/Thread.svelte";
+
  import { isEqual } from "lodash";

  export let issue: Issue;
  export let baseUrl: BaseUrl;
@@ -31,21 +31,11 @@

  const action: "create" | "edit" | "view" =
    $sessionStore && utils.isLocal(baseUrl.hostname) ? "edit" : "view";
-
  const items: Item<IssueState>[] = [
-
    { title: "Reopen issue", state: { status: "open" } as const },
-
    {
-
      title: "Close issue as solved",
-
      state: { status: "closed", reason: "solved" } as const,
-
    },
-
    {
-
      title: "Close issue as other",
-
      state: { status: "closed", reason: "other" } as const,
-
    },
-
  ].map(item => ({
-
    title: item.title,
-
    value: item.state,
-
    badge: null,
-
  }));
+
  const items: [string, IssueState][] = [
+
    ["Reopen issue", { status: "open" }],
+
    ["Close issue as solved", { status: "closed", reason: "solved" }],
+
    ["Close issue as other", { status: "closed", reason: "other" }],
+
  ];

  async function createReply({
    detail: reply,
@@ -275,7 +265,7 @@
            placeholder="Leave your comment" />
          <div class="actions txt-small">
            <CobStateButton
-
              {items}
+
              items={items.filter(([, state]) => !isEqual(state, issue.state))}
              {selectedItem}
              state={issue.state}
              on:saveStatus={saveStatus} />
modified src/views/projects/Patch.svelte
@@ -45,7 +45,6 @@
  import { capitalize } from "lodash";
  import { HttpdClient } from "@httpd-client";
  import { sessionStore } from "@app/lib/session";
-
  import { updateProjectRoute } from "@app/views/projects/router";

  import Authorship from "@app/components/Authorship.svelte";
  import Badge from "@app/components/Badge.svelte";
@@ -53,6 +52,7 @@
  import CobHeader from "@app/views/projects/Cob/CobHeader.svelte";
  import CommitTeaser from "@app/views/projects/Commit/CommitTeaser.svelte";
  import Dropdown from "@app/components/Dropdown.svelte";
+
  import DropdownItem from "@app/components/Dropdown/DropdownItem.svelte";
  import ErrorMessage from "@app/components/ErrorMessage.svelte";
  import Floating from "@app/components/Floating.svelte";
  import Icon from "@app/components/Icon.svelte";
@@ -333,26 +333,22 @@
            </SquareButton>
          </svelte:fragment>
          <svelte:fragment slot="modal">
-
            <Dropdown
-
              items={patch.revisions.map(r => {
-
                return {
-
                  title: `Revision ${utils.formatObjectId(r.id)}`,
-
                  value: r.id,
-
                  badge: null,
-
                };
-
              })}
-
              selected={currentRevision.id}
-
              on:select={({ detail: item }) => {
-
                updateProjectRoute({
-
                  view: {
-
                    resource: "patch",
-
                    params: { patch: patch.id, revision: item.value },
-
                  },
-
                });
-
              }}>
-
              <span slot="item" let:item>
-
                {item.title}
-
              </span>
+
            <Dropdown items={patch.revisions}>
+
              <svelte:fragment slot="item" let:item>
+
                <ProjectLink
+
                  projectParams={{
+
                    view: {
+
                      resource: "patch",
+
                      params: { patch: patch.id, revision: item.id },
+
                    },
+
                  }}>
+
                  <DropdownItem
+
                    selected={item.id === currentRevision.id}
+
                    size="tiny">
+
                    Revision {utils.formatObjectId(item.id)}
+
                  </DropdownItem>
+
                </ProjectLink>
+
              </svelte:fragment>
            </Dropdown>
          </svelte:fragment>
        </Floating>
modified src/views/projects/PeerSelector.svelte
@@ -1,14 +1,12 @@
<script lang="ts" strictEvents>
-
  import type { Item } from "@app/components/Dropdown.svelte";
  import type { Remote } from "@httpd-client";

-
  import { onMount } from "svelte";
-

  import { formatNodeId, truncateId } from "@app/lib/utils";

  import Avatar from "@app/components/Avatar.svelte";
  import Badge from "@app/components/Badge.svelte";
  import Dropdown from "@app/components/Dropdown.svelte";
+
  import DropdownItem from "@app/components/Dropdown/DropdownItem.svelte";
  import Floating from "@app/components/Floating.svelte";
  import Icon from "@app/components/Icon.svelte";
  import ProjectLink from "@app/components/ProjectLink.svelte";
@@ -16,9 +14,7 @@
  export let peer: string | undefined = undefined;
  export let peers: Remote[];

-
  let meta: Remote | undefined;
-

-
  let items: Item<string>[] = [];
+
  const meta = peers.find(p => p.id === peer);

  function createTitle(p: Remote): string {
    const nodeId = formatNodeId(p.id);
@@ -26,18 +22,6 @@
      ? `${nodeId} is a delegate of this project`
      : `${nodeId} is a peer tracked by this node`;
  }
-

-
  onMount(() => {
-
    meta = peers.find(p => p.id === peer);
-
    items = peers.map(p => {
-
      return {
-
        value: p.id,
-
        alias: p.alias,
-
        title: createTitle(p),
-
        badge: p.delegate ? "delegate" : null,
-
      };
-
    });
-
  });
</script>

<style>
@@ -62,12 +46,6 @@
  .peer:hover {
    background-color: var(--color-foreground-2);
  }
-
  .peer-item {
-
    display: flex;
-
    flex-direction: row;
-
    align-items: center;
-
    gap: 0.5rem;
-
  }
  .prefix {
    display: inline-block;
    color: var(--color-secondary-6);
@@ -122,37 +100,42 @@
  </div>

  <svelte:fragment slot="modal">
-
    <Dropdown {items} selected={peer}>
+
    <Dropdown items={peers}>
      <svelte:fragment slot="item" let:item>
-
        <ProjectLink
-
          on:click
-
          projectParams={{
-
            peer: item.value,
-
            revision: undefined,
-
          }}>
-
          <div class="peer-item">
-
            <span class="avatar-id">
-
              <Avatar nodeId={item.value} inline />
-
              <div class="layout-desktop">
-
                <!-- prettier-ignore -->
-
                <span><span class="prefix">did:key:</span>{item.value}</span>
-
                {#if item.alias}
-
                  <span class="alias">({item.alias})</span>
-
                {/if}
-
              </div>
-
              <div class="layout-mobile">
-
                <!-- prettier-ignore -->
-
                <span><span class="prefix">did:key:</span>{truncateId(item.value)}</span>
-
                {#if item.alias}
-
                  <span class="alias">({item.alias})</span>
-
                {/if}
-
              </div>
-
            </span>
-
            {#if item.badge}
-
              <Badge variant="primary">{item.badge}</Badge>
-
            {/if}
-
          </div>
-
        </ProjectLink>
+
        <div class="dropdown-item">
+
          <ProjectLink
+
            on:click
+
            projectParams={{
+
              peer: item.id,
+
              revision: undefined,
+
            }}>
+
            <DropdownItem
+
              selected={item.id === peer}
+
              title={createTitle(item)}
+
              size="tiny">
+
              <span class="avatar-id">
+
                <Avatar nodeId={item.id} inline />
+
                <div class="layout-desktop">
+
                  <!-- prettier-ignore -->
+
                  <span><span class="prefix">did:key:</span>{item.id}</span>
+
                  {#if item.alias}
+
                    <span class="alias">({item.alias})</span>
+
                  {/if}
+
                </div>
+
                <div class="layout-mobile">
+
                  <!-- prettier-ignore -->
+
                  <span><span class="prefix">did:key:</span>{truncateId(item.id)}</span>
+
                  {#if item.alias}
+
                    <span class="alias">({item.alias})</span>
+
                  {/if}
+
                </div>
+
              </span>
+
              {#if item.delegate}
+
                <Badge variant="primary">delegate</Badge>
+
              {/if}
+
            </DropdownItem>
+
          </ProjectLink>
+
        </div>
      </svelte:fragment>
    </Dropdown>
  </svelte:fragment>