Reticle@etched/reticle 0.0.0

Inline edit

Text that turns into a one-line field on click. Enter or leaving the field saves; Escape cancels. It takes the font of the text around it, so it works in a heading or a table cell.

import { useState } from "react";
import { Text } from "@etched/reticle/text";
import { InlineEdit } from "@etched/reticle/inline-edit";

export function Preview() {
  const [name, setName] = useState("Ana Ruiz");

  return (
    <Text as="h3" variant="heading" size="lg">
      <InlineEdit aria-label="Name" value={name} onSave={setName} />
    </Text>
  );
}

Installation

import { InlineEdit } from "@etched/reticle/inline-edit";

Examples

Name, title and company

Click the text, or press Enter on it. Enter or leaving the field saves; Escape cancels.

at

import { useState } from "react";
import { Text } from "@etched/reticle/text";
import { InlineEdit } from "@etched/reticle/inline-edit";

export function NameTitleAndCompany() {
  const [person, setPerson] = useState({ name: "José Núñez", title: "", company: "Tenstorrent" });

  return (
    <div className="grid">
      <Text as="h3" variant="heading" size="lg">
        <InlineEdit aria-label="Name" value={person.name} onSave={(name) => setPerson({ ...person, name })} />
      </Text>
      <Text as="p">
        <InlineEdit aria-label="Title" placeholder="Add a title" value={person.title} onSave={(title) => setPerson({ ...person, title })} />
        {" at "}
        <InlineEdit aria-label="Company" value={person.company} onSave={(company) => setPerson({ ...person, company })} />
      </Text>
    </div>
  );
}

Errors and slow saves

Throw from onSave to show a message: the field stays open with the typed text. A slow save shows the field as busy.

import { useState } from "react";
import { InlineEdit } from "@etched/reticle/inline-edit";

export function ErrorsAndSlowSaves() {
  const [name, setName] = useState("Lee Park");

  const save = async (next: string) => {
    if (!next.trim()) throw new Error("Enter a name.");
    await new Promise((done) => setTimeout(done, 600));
    setName(next.trim());
  };

  return <InlineEdit aria-label="Name" value={name} onSave={save} />;
}

Opened from outside

editing and onEditingChange let the app open the field, for example when Tab moves to the next field. Keys other than Enter and Escape bubble up from the field.

import { useState } from "react";
import { Button } from "@etched/reticle/button";
import { InlineEdit } from "@etched/reticle/inline-edit";

export function OpenedFromOutside() {
  const [title, setTitle] = useState("Thermal engineer");
  const [editing, setEditing] = useState(false);

  return (
    <div className="row">
      <InlineEdit aria-label="Title" value={title} onSave={setTitle} editing={editing} onEditingChange={setEditing} />
      <Button size="sm" onClick={() => setEditing(true)}>
        Edit title
      </Button>
    </div>
  );
}

In a table

In a one-line table (Table truncate), long text is cut with an ellipsis; the field shows all of it.

Ana Ruiz
import { useState } from "react";
import { Table } from "@etched/reticle/table";
import { InlineEdit } from "@etched/reticle/inline-edit";

export function InATable() {
  const [title, setTitle] = useState("Distributed Systems Engineer, Infrastructure and Platform Reliability");

  return (
    <Table aria-label="People" truncate style={{ inlineSize: "24rem", tableLayout: "fixed" }}>
      <tbody>
        <tr>
          <td>Ana Ruiz</td>
          <td>
            <InlineEdit aria-label="Long title" value={title} onSave={setTitle} />
          </td>
        </tr>
      </tbody>
    </Table>
  );
}

API reference

PropTypeDefaultDescription
value *string–
onSave *(value: string) => void | Promise<void>–Saves the new text. Throw (or reject) with a message to show it as an error; the field then stays open. Runs only when the text changed.
aria-label *string–Names the field, for example "Name".
placeholderstring–Shown at rest while the value is empty.
disabledboolean–
editingboolean–Whether the field is open, to open it from outside (for example when Tab moves to the next field). Leave it out to let a click open it.
onEditingChange(editing: boolean) => void–Called when the field opens or closes.
classNamestring–Class on the outer element, for layout.