Reticle@etched/reticle 0.0.0

Collapsible

A text trigger and the content it shows, with a line at the content's start edge. For other layouts, use the parts.

Kumo source

Strong on clock trees. Wants to stay close to silicon bring-up.

import { Text } from "@etched/reticle/text";
import { Collapsible } from "@etched/reticle/collapsible";

export function Preview() {
  return (
    <div style={{ inlineSize: "16rem" }}>
      <Collapsible label="View note" defaultOpen>
        <Text as="p">Strong on clock trees. Wants to stay close to silicon bring-up.</Text>
      </Collapsible>
    </div>
  );
}

Installation

import { Collapsible, CollapsiblePanel, CollapsibleRoot, CollapsibleTrigger } from "@etched/reticle/collapsible";

Examples

View note

The all-in-one: a text trigger and the content it shows. The closed note stays mounted, so find in page reaches it and opens it.

Accepted by Sam Lee · Oct 14

import { Text } from "@etched/reticle/text";
import { Collapsible } from "@etched/reticle/collapsible";

export function ViewNote() {
  return (
    <div style={{ maxInlineSize: "26rem" }}>
      <Text as="p" size="sm" variant="secondary">
        Accepted by Sam Lee · Oct 14
      </Text>
      <Collapsible label="View note">
        <Text as="p">The Ashby owner was wrong. Moved to Kai Ito, who ran the first screen.</Text>
      </Collapsible>
    </div>
  );
}

Unsaved edits

Starts open with defaultOpen, like Orbit's inbox when a send fails and keeps the drafts.

Ana Ruiz

Thanks for the call today. Could you send the tape-out timeline before Friday?

Kai Ito

Moving our chat to Thursday at 10:00. The Taipei trip is still on.

import { Text } from "@etched/reticle/text";
import { Collapsible } from "@etched/reticle/collapsible";

const drafts = [
  { name: "Ana Ruiz", body: "Thanks for the call today. Could you send the tape-out timeline before Friday?" },
  { name: "Kai Ito", body: "Moving our chat to Thursday at 10:00. The Taipei trip is still on." },
];

export function UnsavedEdits() {
  return (
    <div style={{ maxInlineSize: "26rem" }}>
      <Collapsible label="2 unsaved edits" defaultOpen>
        {drafts.map((draft) => (
          <div key={draft.name}>
            <Text as="p" bold>
              {draft.name}
            </Text>
            <Text as="p" variant="secondary">
              {draft.body}
            </Text>
          </div>
        ))}
      </Collapsible>
    </div>
  );
}

Filters panel

The parts, controlled with open and onOpenChange. The trigger is a Button in the header and the panel sits below it. Done closes the panel and moves focus back to the trigger.

Submitted referrals
import { useRef, useState } from "react";
import { Button } from "@etched/reticle/button";
import { Select } from "@etched/reticle/select";
import { Text } from "@etched/reticle/text";
import { CollapsiblePanel, CollapsibleRoot, CollapsibleTrigger } from "@etched/reticle/collapsible";

interface Referral {
  name: string;
  role: string;
  by: string;
}

const referrals: [string, Referral[]][] = [
  [
    "Active",
    [
      { name: "Ana Ruiz", role: "ASIC design engineer", by: "Sam Lee" },
      { name: "José Núñez", role: "Firmware engineer", by: "Kai Ito" },
    ],
  ],
  [
    "Inactive",
    [
      { name: "Lee Park", role: "Thermal engineer", by: "Sam Lee" },
      { name: "Mira Osei", role: "Physical design lead", by: "Ana Ruiz" },
    ],
  ],
];

export function FiltersPanel() {
  const [open, setOpen] = useState(false);
  const trigger = useRef<HTMLButtonElement>(null);

  return (
    <CollapsibleRoot open={open} onOpenChange={setOpen}>
      <div className="row" style={{ justifyContent: "space-between", maxInlineSize: "32rem" }}>
        <Text variant="heading">Submitted referrals</Text>
        <CollapsibleTrigger ref={trigger} render={<Button size="sm" />}>
          Filters
        </CollapsibleTrigger>
      </div>
      <CollapsiblePanel>
        <div className="row" style={{ alignItems: "end", paddingBlockStart: "var(--et-space-3)" }}>
          <Select label="Status" items={{ new: "New", accepted: "Accepted", declined: "Declined" }} placeholder="Any status" />
          <Select label="Referred by" items={{ sam: "Sam Lee", kai: "Kai Ito", ana: "Ana Ruiz" }} placeholder="Anyone" />
          <Button
            onClick={() => {
              setOpen(false);
              trigger.current?.focus();
            }}
          >
            Done
          </Button>
        </div>
      </CollapsiblePanel>
    </CollapsibleRoot>
  );
}

Table row groups

Each group folds its own rows: the panel renders as a tbody, and the trigger sits in the group row's heading. A row group folds at once, without the height animation.

CandidateRoleReferred by
Ana RuizASIC design engineerSam Lee
José NúñezFirmware engineerKai Ito
import { Table } from "@etched/reticle/table";
import { CollapsiblePanel, CollapsibleRoot, CollapsibleTrigger } from "@etched/reticle/collapsible";

interface Referral {
  name: string;
  role: string;
  by: string;
}

const referrals: [string, Referral[]][] = [
  [
    "Active",
    [
      { name: "Ana Ruiz", role: "ASIC design engineer", by: "Sam Lee" },
      { name: "José Núñez", role: "Firmware engineer", by: "Kai Ito" },
    ],
  ],
  [
    "Inactive",
    [
      { name: "Lee Park", role: "Thermal engineer", by: "Sam Lee" },
      { name: "Mira Osei", role: "Physical design lead", by: "Ana Ruiz" },
    ],
  ],
];

export function TableRowGroups() {
  return (
    <div className="card">
      <Table aria-label="Referrals">
        <thead>
          <tr>
            <th style={{ inlineSize: "30%" }}>Candidate</th>
            <th>Role</th>
            <th style={{ inlineSize: "20%" }}>Referred by</th>
          </tr>
        </thead>
        {referrals.map(([group, rows]) => (
          <CollapsibleRoot key={group} defaultOpen={group === "Active"}>
            <tbody>
              <tr>
                <th scope="rowgroup" colSpan={3}>
                  <CollapsibleTrigger>
                    {group} ({rows.length})
                  </CollapsibleTrigger>
                </th>
              </tr>
            </tbody>
            <CollapsiblePanel render={<tbody />}>
              {rows.map((row) => (
                <tr key={row.name}>
                  <th scope="row">{row.name}</th>
                  <td>{row.role}</td>
                  <td>{row.by}</td>
                </tr>
              ))}
            </CollapsiblePanel>
          </CollapsibleRoot>
        ))}
      </Table>
    </div>
  );
}

API reference

CollapsiblePanel

PropTypeDefaultDescription
classNamestring–

Collapsible

PropTypeDefaultDescription
childrenReactNode–
label *ReactNode–The trigger's text, such as "View note".
classNamestring–Class on the wrapper, for layout.

CollapsibleRoot

PropTypeDefaultDescription
childrenReactNode–

CollapsibleTrigger

PropTypeDefaultDescription
classNamestring–