Skip to content
  1. Keyline UI
  2. Inspector
Keyline UIComponents
  • Get started
    • Installation
    • Theming
    • Modes
    • Configuration
    • Composition
  • Actions
    • Button
    • Button group
    • Split button
    • Toggle
    • Toggle group
    • Toolbar
    • Menu
    • Menubar
    • Context menu
  • Inputs
    • Field and form
    • Checkbox
    • Checkbox group
    • Radio group
    • Switch
    • Slider
    • Segmented
    • Select
    • Native select
    • Combobox
    • Multiselect
    • Number field
    • OTP field
    • Time field
    • Date picker and calendar
    • Date range
  • Data
    • Table
    • Chart
    • Stat and meter
    • Status and badge
    • Progress
    • Skeleton
    • Empty
    • Avatar
    • Kbd
    • Marker
  • Navigation
    • Tabs
    • Breadcrumb
    • Pagination
    • Nav
    • Navigation menu
    • Command menu
  • Overlays
    • Dialog
    • Alert dialog
    • Drawer
    • Sheet
    • Popover
    • Hover card
    • Tooltip
    • Toast
    • Notice
  • Layout
    • App shell
    • Sidebar
    • Panel
    • Resizable
    • Scroll area
    • Accordion
    • Collapsible
    • Carousel
    • Inspector
    • Item
    • Direction
  • Conversation
    • Message
    • Bubble
    • Message scroller
    • Attachment
    • Questionnaire
  • Blocks
    • Shipments

Inspector

A panel beside a list that shows the chosen record. Its header cites the same code as the row, so the two share an address. The body scrolls and the actions stay put at the foot. It doesn't block the page: you can choose another row while it's open.

Loading the live example…

"use client"

import { useState } from "react"
import { Button } from "@/components/keyline/ui/button"
import { Inspector, InspectorActions, InspectorBody, InspectorFacts, InspectorHeader, InspectorRow, InspectorSection } from "@/components/keyline/ui/inspector"
import { Item, ItemContent, ItemList, ItemMedia } from "@/components/keyline/ui/item"
import { Status } from "@/components/keyline/ui/status"

const ORDERS = [
  { id: "DM-4107", client: "Lackawanna County", cases: 120, rep: "Dwight Schrute", due: "Thu 12 Nov", held: false },
  { id: "DM-4109", client: "Vance Refrigeration", cases: 12, rep: "Phyllis Vance", due: "Wed 11 Nov", held: true },
  { id: "DM-4112", client: "Scranton Business Park", cases: 40, rep: "Jim Halpert", due: "Fri 13 Nov", held: false },
]

export function OrderInspector() {
  const [id, setId] = useState<string | null>("DM-4109")
  const [released, setReleased] = useState<string[]>([])
  const o = ORDERS.find((x) => x.id === id)
  const held = o ? o.held && !released.includes(o.id) : false

  return (
    <div className="grid items-start gap-4 @container md:grid-cols-[1fr_minmax(0,300px)]">
      <ItemList aria-label="Orders">
        {ORDERS.map((x) => (
          <Item key={x.id} current={x.id === id} render={<button type="button" />} onClick={() => setId(x.id)}>
            <ItemMedia>{x.id.slice(3)}</ItemMedia>
            <ItemContent title={x.client} description={x.rep} />
          </Item>
        ))}
      </ItemList>
      {o ? (
        <Inspector aria-label={`Order ${o.id}`}>
          <InspectorHeader
            code={o.id}
            title={o.client}
            status={held ? <Status kind="attention">Held</Status> : <Status kind="normal">Scheduled</Status>}
            onClose={() => setId(null)}
          />
          <InspectorBody>
            <InspectorFacts
              items={[
                { label: "Cases", value: o.cases },
                { label: "Due", value: o.due },
              ]}
            />
            <InspectorRow>
              <InspectorSection>
                <h3 className="m-0 text-[13px] font-semibold">Rep</h3>
                <p className="m-0 text-[14px]">{o.rep}</p>
              </InspectorSection>
              <InspectorSection>
                <h3 className="m-0 text-[13px] font-semibold">Note</h3>
                <p className="m-0 text-[14px]">{held ? "No one at the dock to sign." : "None."}</p>
              </InspectorSection>
            </InspectorRow>
          </InspectorBody>
          <InspectorActions>
            <Button layout="cell" variant="primary" disabled={!held} onClick={() => setReleased((r) => [...r, o.id])}>
              {held ? "Release the hold" : "Not held"}
            </Button>
          </InspectorActions>
        </Inspector>
      ) : (
        <p className="m-0 text-[14px] text-kl-ink-2">Choose an order to inspect it.</p>
      )}
    </div>
  )
}

Installation

npx shadcn@latest add @keyline-ui/inspector

Run it where your components.json is. The first time, map the @keyline-ui registry: one command, in Installation.

Usage

import { Inspector, InspectorActions, InspectorBody, InspectorFacts, InspectorHeader, InspectorRow, InspectorSection } from "@/components/keyline/ui/inspector"
<Inspector aria-label="Order DM-4109">
  <InspectorHeader code="DM-4109" title="Vance Refrigeration" status={<Status kind="attention">Held</Status>} onClose={close} />
  <InspectorBody>
    <InspectorFacts items={[{ label: "Cases", value: 12 }]} />
  </InspectorBody>
  <InspectorActions>…</InspectorActions>
</Inspector>

Props

PropTypeDefaultDescription
code · title · statusReactNodeDefault —On InspectorHeader: the record's code, its name and a Status.
onClose · closeLabel() => void · stringDefault — · "Close inspector"Adds a close cell.
items{ label, value }[]Default —On InspectorFacts: facts two to a row.
InspectorSection · InspectorRowdivDefault —A labelled cell, and a row that puts two short sections side by side.
InspectorActionsdivDefault —A row of Button layout="cell" at the foot.

In an AppShell, pass it as aside and it becomes a sheet on narrow screens; the App shell page shows that.

Sheet 65 of 73PreviousCarouselNextItem
On this page