Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Extract <TextInput /> component
Rūdolfs Ošiņš committed 3 years ago
commit b8e7239ba4b2919dd6aee4f0e7e6874c1416cbd7
parent 2f9c00ba4b11910874061f072313f436d1f9a4e0
13 files changed +216 -240
modified public/index.css
@@ -123,33 +123,6 @@ a.address {
  border-bottom-color: transparent;
}

-
input[type="text"] {
-
  font-family: var(--font-family-sans-serif);
-
  line-height: 1.6;
-
}
-
input[type="text"] {
-
  outline: none;
-
  border: none;
-
  font-size: var(--font-size-regular);
-
  color: var(--color-foreground);
-
  background: transparent;
-
  border: 1px solid var(--color-secondary);
-
  border-radius: var(--border-radius-round);
-
  padding: 1rem 1.5rem;
-
  margin: 1rem;
-
  height: var(--button-regular-height);
-
}
-
input[type="text"]::placeholder {
-
  color: var(--color-secondary);
-
  opacity: 1 !important;
-
}
-
input[type="text"].small {
-
  font-size: var(--font-size-small);
-
}
-
input.wide {
-
  width: 44ch;
-
}
-

.error {
  color: var(--color-negative) !important;
  border-color: var(--color-negative) !important;
modified src/Form.svelte
@@ -1,8 +1,7 @@
<script context="module" lang="ts">
-
  import Button from "@app/Button.svelte";
  export interface Field {
    name: string;
-
    value?: string;
+
    value: string;
    label?: string;
    validate?: string;
    placeholder?: string;
@@ -45,6 +44,9 @@
</script>

<script lang="ts">
+
  import type { Config } from "@app/config";
+

+
  import cloneDeep from "lodash/cloneDeep";
  import { link } from "svelte-routing";
  import { createEventDispatcher } from "svelte";
  import { marked } from "marked";
@@ -55,15 +57,17 @@
    isAddress,
    formatSeedId,
  } from "@app/utils";
+

  import Address from "@app/Address.svelte";
-
  import type { Config } from "@app/config";
+
  import Button from "@app/Button.svelte";
+
  import TextInput from "@app/TextInput.svelte";

  export let fields: Field[];
  export let editable = false;
  export let disabled = false;
  export let config: Config;

-
  let formFields = fields;
+
  let formFields = cloneDeep(fields);
  let hasErrors = false;

  marked.use({ extensions });
@@ -104,8 +108,7 @@
  const save = () => dispatch("save", cleanup(formFields));
  const validate = (event: Event) => dispatch("validate", check(event));
  const cancel = () => {
-
    formFields = fields;
-
    dispatch("cancel");
+
    formFields = cloneDeep(fields);
  };
</script>

@@ -124,7 +127,7 @@
    display: flex;
    align-items: center;
    width: 28rem;
-
    height: 2.125rem;
+
    height: 2.5rem;
    border: 1px dashed transparent;
    padding: 0.25rem 1rem;
    margin: 0;
@@ -145,22 +148,9 @@
    margin: 0;
  }

-
  input.field {
-
    border-radius: var(--border-radius-round);
-
    overflow: hidden;
-
    text-overflow: ellipsis;
-
    border-color: var(--color-secondary) !important;
-
  }
  .description.invalid {
    color: var(--color-negative) !important;
  }
-
  input.field::placeholder {
-
    color: var(--color-secondary);
-
    font-style: italic;
-
  }
-
  input.field[disabled] {
-
    color: var(--color-secondary);
-
  }

  .label {
    border: 1px solid transparent;
@@ -180,10 +170,16 @@
  .actions.editable {
    visibility: visible;
  }
+
  .text-input {
+
    width: 28rem;
+
  }
  @media (max-width: 720px) {
    .field {
      width: unset;
    }
+
    .text-input {
+
      width: 14rem;
+
    }
  }
</style>

@@ -194,15 +190,16 @@
    </div>
    <div>
      {#if field.editable && editable}
-
        <input
-
          name={field.name}
-
          class="field"
-
          placeholder={field.placeholder}
-
          on:change={validate}
-
          on:input={() => (field.error = null)}
-
          value={field.value || ""}
-
          type="text"
-
          {disabled} />
+
        <div class="text-input">
+
          <TextInput
+
            variant="dashed"
+
            name={field.name}
+
            placeholder={field.placeholder}
+
            on:change={validate}
+
            on:input={() => (field.error = null)}
+
            bind:value={field.value}
+
            {disabled} />
+
        </div>
      {:else}
        <span class="field">
          {#if field.value}
@@ -241,7 +238,7 @@
              {field.value}
            {/if}
          {:else}
-
            <span class="txt-missing not-set">&cross; Not set</span>
+
            <span class="txt-missing">&cross; Not set</span>
          {/if}
        </span>
      {/if}
modified src/Header.svelte
@@ -70,6 +70,7 @@
    display: flex;
    height: var(--button-regular-height);
    align-items: center;
+
    margin-right: 0.5rem;
  }
  .error {
    text-align: center;
@@ -86,10 +87,7 @@
    text-decoration: none;
  }
  .search {
-
    height: var(--button-regular-height);
    width: 16rem;
-
    margin-left: 0.5rem;
-
    display: inline-block;
  }
  .connect {
    display: inline-block;
modified src/MobileNavbar.svelte
@@ -66,7 +66,7 @@
  <div use:clickOutside={handleClickOutside} class="modal">
    <div class="modal-title">
      <div style="padding-bottom: 1rem;">
-
        <Search size={20} {config} on:search />
+
        <Search {config} on:search />
      </div>
      <div>
        <a use:link on:click={() => dispatch("select")} href="/registrations">
modified src/Search.svelte
@@ -3,8 +3,8 @@
  import type { Config } from "@app/config";
  import { createEventDispatcher } from "svelte";
  import Loading from "@app/Loading.svelte";
+
  import TextInput from "@app/TextInput.svelte";

-
  export let size = 40;
  export let config: Config;

  let input = "";
@@ -25,43 +25,14 @@
  };
</script>

-
<style>
-
  input {
-
    height: 100%;
-
    width: 100%;
-
    font-size: var(--font-size-small);
-
    text-overflow: ellipsis;
-
    margin: 0;
-
    padding: 0.5rem 1.25rem;
-
    border-style: dashed;
-
    height: var(--button-regular-height);
-
  }
-
  input[disabled] {
-
    color: var(--color-secondary);
-
  }
-
  .wrapper {
-
    position: relative;
-
    display: flex;
-
    flex-direction: row;
-
    align-items: center;
-
  }
-
  .loading {
-
    position: absolute;
-
    right: 12px;
-
  }
-
</style>
-

-
<div class="wrapper">
-
  <input
-
    {size}
-
    type="text"
-
    disabled={searching}
-
    bind:value={input}
-
    on:keydown={handleKeydown}
-
    placeholder="Search a name or address…" />
-
  {#if searching}
-
    <div class="loading">
+
<TextInput
+
  variant="dashed"
+
  bind:value={input}
+
  on:keydown={handleKeydown}
+
  placeholder="Search a name or address…">
+
  <svelte:fragment slot="right">
+
    {#if searching}
      <Loading small />
-
    </div>
-
  {/if}
-
</div>
+
    {/if}
+
  </svelte:fragment>
+
</TextInput>
added src/TextInput.svelte
@@ -0,0 +1,95 @@
+
<script lang="ts">
+
  import { onMount } from "svelte";
+

+
  export let name: string | undefined = undefined;
+
  export let placeholder: string | undefined = undefined;
+
  export let value: string | undefined = undefined;
+

+
  export let variant: "regular" | "dashed" = "regular";
+

+
  export let autofocus: boolean = false;
+
  export let disabled: boolean = false;
+

+
  let rightContainerWidth: number;
+
  let inputElement: HTMLInputElement | undefined = undefined;
+
  onMount(() => {
+
    if (autofocus && inputElement) {
+
      // We set preventScroll to true for Svelte animations to work.
+
      inputElement.focus({ preventScroll: true });
+
    }
+
  });
+
</script>
+

+
<style>
+
  .wrapper {
+
    display: flex;
+
    flex-direction: column;
+
    margin: 0;
+
    position: relative;
+
  }
+
  input {
+
    background: transparent;
+
    border-radius: var(--border-radius-round);
+
    color: var(--color-foreground);
+
    font-family: var(--font-family-sans-serif);
+
    height: var(--button-regular-height);
+
    line-height: 1.6;
+
    margin: 0;
+
    outline: none;
+
    text-overflow: ellipsis;
+
  }
+
  input::placeholder {
+
    color: var(--color-secondary);
+
    opacity: 1 !important;
+
  }
+
  input[disabled] {
+
    color: var(--color-secondary);
+
    cursor: not-allowed;
+
  }
+
  .regular {
+
    border: 1px solid var(--color-secondary);
+
    font-size: var(--font-size-regular);
+
    padding: 1rem 1.5rem;
+
  }
+
  .dashed {
+
    border: 1px dashed var(--color-secondary);
+
    font-size: var(--font-size-small);
+
    padding: 0.5rem 1.25rem;
+
  }
+
  .right-container {
+
    color: var(--color-secondary);
+
    position: absolute;
+
    right: 0;
+
    top: 0;
+
    display: flex;
+
    align-items: center;
+
    height: var(--button-regular-height);
+
    padding-right: 1rem;
+
    padding-left: 0.5rem;
+
  }
+
</style>
+

+
<div class="wrapper">
+
  <input
+
    class:regular={variant === "regular"}
+
    class:dashed={variant === "dashed"}
+
    style:padding-right={rightContainerWidth
+
      ? `${rightContainerWidth}px`
+
      : "auto"}
+
    bind:this={inputElement}
+
    type="text"
+
    {name}
+
    {placeholder}
+
    {disabled}
+
    bind:value
+
    on:input
+
    on:keydown
+
    on:click
+
    on:change />
+

+
  {#if $$slots.right}
+
    <div class="right-container" bind:clientWidth={rightContainerWidth}>
+
      <slot name="right" />
+
    </div>
+
  {/if}
+
</div>
modified src/base/faucet/Index.svelte
@@ -11,6 +11,7 @@
    calculateTimeLock,
  } from "./lib";
  import Button from "@app/Button.svelte";
+
  import TextInput from "@app/TextInput.svelte";

  export let config: Config;

@@ -79,29 +80,18 @@
</script>

<style>
-
  div.input-caption {
+
  .input-caption {
    font-size: var(--font-size-medium);
    text-align: left;
-
    margin-left: 1.5rem;
-
    padding-left: 1.5rem;
    color: var(--color-secondary);
+
    margin-bottom: 2rem;
+
    margin-left: 0.5rem;
  }
-
  div.input-main {
+
  .input-main {
    display: flex;
    align-items: flex-start;
-
    flex-direction: column;
-
    margin: 1rem 1.5rem 0rem;
    color: var(--color-secondary);
-
  }
-
  input[type="text"] {
-
    margin: 0;
-
    margin-right: 1.5rem;
-
  }
-
  .name {
-
    display: flex;
-
    flex-direction: row;
-
    margin: 1rem;
-
    margin-bottom: 0;
+
    flex-direction: column;
  }
  .error {
    padding-left: 1rem;
@@ -114,6 +104,10 @@
  .description.invalid {
    color: var(--color-negative) !important;
  }
+
  .name {
+
    display: flex;
+
    gap: 1.5rem;
+
  }
</style>

<svelte:head>
@@ -144,11 +138,12 @@
      </div>
      <div class="input-main">
        <div class="name">
-
          <input
-
            type="text"
-
            placeholder="Set amount to withdraw"
-
            bind:value={amount}
-
            on:input={() => (error = "")} />
+
          <div style="width: 14.5rem;">
+
            <TextInput
+
              placeholder="Set amount to withdraw"
+
              bind:value={amount}
+
              on:input={() => (error = "")} />
+
          </div>
          <Button variant="primary" on:click={withdraw}>Withdraw</Button>
        </div>
        {#if error}
modified src/base/registrations/Index.svelte
@@ -2,7 +2,7 @@
  import { navigate } from "svelte-routing";
  import type { Config } from "@app/config";

-
  import DomainInput from "@app/ens/DomainInput.svelte";
+
  import TextInput from "@app/TextInput.svelte";
  import Button from "@app/Button.svelte";

  export let config: Config;
@@ -50,6 +50,7 @@
  }
  .name {
    margin: 1rem;
+
    width: 22rem;
  }
  .input-info {
    position: absolute;
@@ -82,11 +83,11 @@
    </div>
    <div class="input-main">
      <span class="name">
-
        <DomainInput
-
          bind:value={input}
-
          autofocus
-
          placeholder=""
-
          root={config.registrar.domain} />
+
        <TextInput bind:value={input} autofocus>
+
          <svelte:fragment slot="right">
+
            .{config.registrar.domain}
+
          </svelte:fragment>
+
        </TextInput>
        {#if errors}
          <div class="input-info">
            {#each errors as error}
modified src/base/registrations/View.svelte
@@ -80,7 +80,7 @@
              : "The reverse record for this address is **not set**. " +
                "For this name to be correctly associated with the address, " +
                "a reverse record should be set."),
-
          value: r.profile.address,
+
          value: r.profile.address ?? "",
          editable: true,
        },
        {
@@ -89,7 +89,7 @@
          validate: "URL",
          placeholder: "https://acme.org",
          description: "A homepage or other URL associated with this name.",
-
          value: r.profile.url,
+
          value: r.profile.url ?? "",
          editable: true,
        },
        {
@@ -97,7 +97,7 @@
          validate: "URL",
          placeholder: "https://acme.org/avatar.png",
          description: "An avatar or square image associated with this name.",
-
          value: r.profile.avatar,
+
          value: r.profile.avatar ?? "",
          editable: true,
        },
        {
@@ -105,7 +105,7 @@
          validate: "handle",
          placeholder: "Twitter username, eg. 'acme'",
          description: "The Twitter handle associated with this name.",
-
          value: r.profile.twitter,
+
          value: r.profile.twitter ?? "",
          editable: true,
        },
        {
@@ -114,7 +114,7 @@
          label: "GitHub",
          placeholder: "GitHub username, eg. 'acme'",
          description: "The GitHub username associated with this name.",
-
          value: r.profile.github,
+
          value: r.profile.github ?? "",
          editable: true,
        },
        {
@@ -123,7 +123,7 @@
          validate: "identity",
          placeholder: "Radicle URN, eg. rad:git:hnrkqdpm9ub19oc8d…",
          description: "The local radicle identity associated with this name.",
-
          value: r.profile.id,
+
          value: r.profile.id ?? "",
          editable: true,
        },
        {
@@ -136,7 +136,7 @@
            "The seed host address. " +
            "Only domain names with TLS are supported. " +
            `HTTP(S) API requests use port ${config.seed.api.port}.`,
-
          value: r.profile.seed?.host,
+
          value: r.profile.seed?.host ?? "",
          editable: true,
        },
        {
@@ -146,7 +146,7 @@
          placeholder: "hynkyndc6w3p8urucakobzncqny7xxtw88…",
          description:
            "The Device ID of a Radicle Link node that hosts entities associated with this name.",
-
          value: r.profile.seed?.id,
+
          value: r.profile.seed?.id ?? "",
          editable: true,
        },
        {
@@ -157,7 +157,7 @@
          description:
            "URN under which associated project anchors can be found. " +
            "To point to a Radicle org on Ethereum, use the CAIP-10 ID, eg. *eip155:1:0x4a9cf21…*",
-
          value: r.profile.anchorsAccount,
+
          value: r.profile.anchorsAccount ?? "",
          editable: true,
        },
      ];
modified src/base/vesting/Index.svelte
@@ -1,5 +1,4 @@
<script lang="ts">
-
  import { onMount } from "svelte";
  import { State, state } from "./state";
  import { getInfo, withdrawVested } from "./vesting";
  import type { VestingInfo } from "./vesting";
@@ -9,12 +8,7 @@
  import Address from "@app/Address.svelte";
  import { formatAddress, isAddressEqual } from "@app/utils";
  import Button from "@app/Button.svelte";
-

-
  let input: HTMLElement;
-

-
  onMount(() => {
-
    input.focus();
-
  });
+
  import TextInput from "@app/TextInput.svelte";

  export let config: Config;
  export let session: Session | null;
@@ -38,20 +32,21 @@
</script>

<style>
-
  div.input-caption {
+
  .input-caption {
    font-size: var(--font-size-medium);
    text-align: left;
    margin-left: 1.5rem;
-
    padding-left: 1.5rem;
-
    margin-bottom: 1rem;
+
    margin-bottom: 2rem;
    color: var(--color-secondary);
  }
-
  div.input-main {
+
  .input-main {
    display: flex;
    align-items: center;
    flex-direction: row;
    margin-left: 1.5rem;
    color: var(--color-secondary);
+
    gap: 1rem;
+
    justify-content: center;
  }
  table {
    table-layout: fixed;
@@ -142,14 +137,10 @@
      </div>
      <div class="input-main">
        <span class="name">
-
          <div>
-
            <input
-
              size="40"
-
              placeholder=""
-
              class="subdomain"
+
          <div style="width: 25rem;">
+
            <TextInput
+
              autofocus
              disabled={$state === State.Loading}
-
              type="text"
-
              bind:this={input}
              bind:value={contractAddress} />
          </div>
        </span>
modified src/components/TransferOwnership.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
-
  import { onMount, createEventDispatcher } from "svelte";
+
  import { createEventDispatcher } from "svelte";
  import Modal from "@app/Modal.svelte";
  import type { Config } from "@app/config";
  import { formatAddress, isAddress } from "@app/utils";
@@ -8,6 +8,7 @@
  import * as utils from "@app/utils";
  import Address from "@app/Address.svelte";
  import Button from "@app/Button.svelte";
+
  import TextInput from "@app/TextInput.svelte";

  import type { Org } from "@app/base/orgs/Org";

@@ -31,14 +32,9 @@
    Failed,
  }

-
  let newOwner: string | null = null;
+
  let newOwner: string | undefined = undefined;
  let state = State.Idle;
  let error: string | null = null;
-
  let input: HTMLInputElement | null = null;
-

-
  onMount(() => {
-
    input && input.focus();
-
  });

  const resetForm = () => {
    state = State.Idle;
@@ -73,6 +69,14 @@
  };
</script>

+
<style>
+
  .actions {
+
    gap: 1rem;
+
    display: flex;
+
    justify-content: center;
+
  }
+
</style>
+

{#if state === State.Success && newOwner}
  <Modal floating small>
    <div slot="title">✅</div>
@@ -142,14 +146,14 @@
      {/if}
    </div>

-
    <div slot="body">
+
    <div slot="body" style="display: flex;justify-content: center;">
      {#if state === State.Idle}
-
        <input
-
          type="text"
-
          size="40"
-
          disabled={state !== State.Idle}
-
          bind:this={input}
-
          bind:value={newOwner} />
+
        <div style="width: 25rem;">
+
          <TextInput
+
            autofocus
+
            disabled={state !== State.Idle}
+
            bind:value={newOwner} />
+
        </div>
      {:else if state === State.Pending || state === State.Proposing || state === State.Signing}
        <Loading small center />
      {:else if state === State.Failed}
@@ -157,7 +161,7 @@
      {/if}
    </div>

-
    <div slot="actions">
+
    <div slot="actions" class="actions">
      {#if state === State.Signing}
        <Button variant="text" on:click={() => dispatch("close")}>
          Cancel
deleted src/ens/DomainInput.svelte
@@ -1,62 +0,0 @@
-
<script lang="ts">
-
  import { onMount } from "svelte";
-

-
  export let root: string;
-
  export let placeholder = "";
-
  export let disabled = false;
-
  export let value = "";
-
  export let autofocus = false;
-

-
  let element: HTMLInputElement;
-

-
  onMount(() => {
-
    if (autofocus) {
-
      element.focus();
-
    }
-
  });
-
</script>
-

-
<style>
-
  span.root {
-
    /* margin: 1rem; */
-
    /* margin-left: 0; */
-
    /* margin-right: 0; */
-
    /* padding: 0.7rem 2rem; */
-
    padding: 0.4rem 1rem;
-
    color: var(--color-secondary);
-
    border-radius: 0 var(--border-radius-round) var(--border-radius-round) 0;
-
    border: 1px solid var(--color-secondary);
-
    border-left: none;
-
    height: var(--button-regular-height);
-
  }
-
  input {
-
    line-height: 1.5;
-
    margin: 0;
-
    margin-right: 0;
-
    border-top-right-radius: 0;
-
    border-bottom-right-radius: 0;
-
    border-radius: var(--border-radius-round) 0 0 var(--border-radius-round);
-
    border-right: none;
-
  }
-
  input[disabled] {
-
    color: var(--color-secondary);
-
  }
-
  main {
-
    display: flex;
-
    flex-direction: row;
-
    align-items: center;
-
    justify-content: center;
-
  }
-
</style>
-

-
<main>
-
  <input
-
    type="text"
-
    {disabled}
-
    {placeholder}
-
    bind:this={element}
-
    bind:value
-
    on:input
-
    on:click />
-
  <span class="root">.{root}</span>
-
</main>
modified src/ens/SetName.svelte
@@ -4,7 +4,6 @@
  import Modal from "@app/Modal.svelte";
  import type { Config } from "@app/config";
  import { formatAddress, isAddressEqual } from "@app/utils";
-
  import DomainInput from "@app/ens/DomainInput.svelte";
  import { Org } from "@app/base/orgs/Org";
  import type { User } from "@app/base/users/User";
  import Loading from "@app/Loading.svelte";
@@ -12,6 +11,7 @@
  import Address from "@app/Address.svelte";
  import * as utils from "@app/utils";
  import Button from "@app/Button.svelte";
+
  import TextInput from "@app/TextInput.svelte";

  const dispatch = createEventDispatcher();

@@ -73,6 +73,14 @@
  };
</script>

+
<style>
+
  .actions {
+
    display: flex;
+
    justify-content: center;
+
    gap: 1rem;
+
  }
+
</style>
+

{#if state === State.Success}
  <Modal floating>
    <div slot="title">✅</div>
@@ -158,19 +166,24 @@
      {/if}
    </div>

-
    <div slot="body">
+
    <div slot="body" style="display: flex; justify-content:center;">
      {#if state === State.Idle || state === State.Checking}
-
        <DomainInput
-
          root={config.registrar.domain}
-
          autofocus
-
          disabled={state !== State.Idle}
-
          bind:value={name} />
+
        <div style="width: 22rem;">
+
          <TextInput
+
            autofocus
+
            disabled={state !== State.Idle}
+
            bind:value={name}>
+
            <svelte:fragment slot="right">
+
              .{config.registrar.domain}
+
            </svelte:fragment>
+
          </TextInput>
+
        </div>
      {:else}
        <Loading small center />
      {/if}
    </div>

-
    <div slot="actions">
+
    <div slot="actions" class="actions">
      {#if state === State.Signing}
        <Button variant="secondary" on:click={() => dispatch("close")}>
          Cancel