跳到正文
GitHub

模板

控制台

一个后台:侧边栏、数字带、标签页、可筛选的表格,以及两张任务卡。

密度。十二个组件挤在一栏里,近到任何只在孤立状态下检查过的间距决定都会露出来。

All systems normal
Releases
12
last 30 days
Median build
2m 14s
down from 3m 40s
Rollbacks
0
Uptime
99.98%
measured at the edge
Recent deploys
CommitBranchDurationState
a1b2c3dmain2m 14spassed
9f8e7d6codex/ui-library2m 41spassed
4c5b6a7codex/photo-cache1m 02sfailed
77aa2b1main2m 20spassed

由这些组件搭成

NavItemFigureBandTabsToggleGroupTableBadgeProgressSelectCardStatusPillAvatarButton

这里没有任何东西是为模板单独写的样式。只有这样它才能随着系统变化而保持诚实——带自己 CSS 的模板不再是对组件的检验,只是一张截图。

源码

'use client'

import {
  Avatar,
  Badge,
  Button,
  Card,
  CardBody,
  CardHeader,
  CardTitle,
  FigureBand,
  NavItem,
  Progress,
  Select,
  SelectItem,
  StatusPill,
  TBody,
  TD,
  TH,
  THead,
  TR,
  Table,
  Tabs,
  TabsContent,
  TabsList,
  TabsTrigger,
  ToggleGroup,
  ToggleGroupItem,
} from '@misoto22/design'
import { Activity, Boxes, Home, Settings } from 'lucide-react'
import { useState } from 'react'

const DEPLOYS = [
  { sha: 'a1b2c3d', branch: 'main', duration: '2m 14s', state: 'passed' as const },
  { sha: '9f8e7d6', branch: 'codex/ui-library', duration: '2m 41s', state: 'passed' as const },
  { sha: '4c5b6a7', branch: 'codex/photo-cache', duration: '1m 02s', state: 'failed' as const },
  { sha: '77aa2b1', branch: 'main', duration: '2m 20s', state: 'passed' as const },
]

/**
 * A console, assembled from the set.
 *
 * The point of a template is not the layout — it is showing which components a
 * real screen actually needs together, and how they space against each other
 * once there are twelve of them rather than one. A gallery answers "what does a
 * Table look like"; this answers "what does a Table look like beside a figure
 * band, under a tab strip, in a column that has to hold a sidebar".
 *
 * Every element here is from the package. Nothing was styled specially for the
 * template, which is the only way it stays honest as the system changes.
 */
export function Dashboard() {
  const [range, setRange] = useState('30')

  return (
    <div className="grid min-h-[36rem] grid-cols-1 @3xl:grid-cols-[13rem_minmax(0,1fr)]">
      <aside className="hidden flex-col gap-1 border-e border-(--rule) p-3 @3xl:flex">
        <div className="flex items-center gap-2 px-3 pb-3">
          <Avatar alt="" fallback="M" size="sm" />
          <span className="font-heading text-[15px] text-(--ink)">Console</span>
        </div>
        <NavItem href="#overview" icon={Home} active>
          Overview
        </NavItem>
        <NavItem href="#deploys" icon={Boxes}>
          Deploys
        </NavItem>
        <NavItem href="#activity" icon={Activity}>
          Activity
        </NavItem>
        <NavItem href="#settings" icon={Settings}>
          Settings
        </NavItem>
      </aside>

      <div className="flex min-w-0 flex-col">
        <header className="flex flex-wrap items-center justify-between gap-3 border-b border-(--rule) px-5 py-3">
          <StatusPill>All systems normal</StatusPill>
          <div className="flex items-center gap-2">
            <Select label="Range" value={range} onValueChange={setRange} className="w-36">
              <SelectItem value="7">Last 7 days</SelectItem>
              <SelectItem value="30">Last 30 days</SelectItem>
              <SelectItem value="90">Last 90 days</SelectItem>
            </Select>
            <Button size="sm">Deploy</Button>
          </div>
        </header>

        <div className="flex flex-col gap-6 p-5">
          <FigureBand
            label="At a glance"
            scale="sub"
            figures={[
              { id: 'releases', label: 'Releases', value: '12', note: `last ${range} days` },
              { id: 'duration', label: 'Median build', value: '2m 14s', note: 'down from 3m 40s' },
              { id: 'rollbacks', label: 'Rollbacks', value: '0' },
              { id: 'uptime', label: 'Uptime', value: '99.98%', note: 'measured at the edge' },
            ]}
          />

          <Tabs defaultValue="deploys">
            <TabsList>
              <TabsTrigger value="deploys">Deploys</TabsTrigger>
              <TabsTrigger value="jobs">Jobs</TabsTrigger>
            </TabsList>

            <TabsContent value="deploys" className="flex flex-col gap-4">
              <ToggleGroup type="single" defaultValue="all" aria-label="Filter">
                <ToggleGroupItem value="all">All</ToggleGroupItem>
                <ToggleGroupItem value="passed">Passed</ToggleGroupItem>
                <ToggleGroupItem value="failed">Failed</ToggleGroupItem>
              </ToggleGroup>

              <Table caption="Recent deploys">
                <THead>
                  <TR>
                    <TH>Commit</TH>
                    <TH>Branch</TH>
                    <TH className="text-end">Duration</TH>
                    <TH>State</TH>
                  </TR>
                </THead>
                <TBody>
                  {DEPLOYS.map((deploy) => (
                    <TR key={deploy.sha}>
                      <TD className="font-mono text-xs">{deploy.sha}</TD>
                      <TD>{deploy.branch}</TD>
                      <TD className="text-end tabular-nums">{deploy.duration}</TD>
                      <TD>
                        <Badge tone={deploy.state === 'passed' ? 'success' : 'danger'}>
                          {deploy.state}
                        </Badge>
                      </TD>
                    </TR>
                  ))}
                </TBody>
              </Table>
            </TabsContent>

            <TabsContent value="jobs">
              <div className="grid gap-4 @2xl:grid-cols-2">
                <Card>
                  <CardHeader>
                    <CardTitle>Reindex</CardTitle>
                    <Badge tone="warning">running</Badge>
                  </CardHeader>
                  <CardBody className="flex flex-col gap-4">
                    <Progress value={62} label="Reindexing documents" showValue />
                    <p className="m-0 text-[13px] text-(--ink-3-aa)">
                      44,102 of 71,300 documents. Started 6 minutes ago.
                    </p>
                  </CardBody>
                </Card>
                <Card>
                  <CardHeader>
                    <CardTitle>Thumbnails</CardTitle>
                    <Badge tone="success">idle</Badge>
                  </CardHeader>
                  <CardBody className="flex flex-col gap-4">
                    <Progress label="Waiting for work" />
                    <p className="m-0 text-[13px] text-(--ink-3-aa)">
                      Nothing queued. Last run finished 2 hours ago.
                    </p>
                  </CardBody>
                </Card>
              </div>
            </TabsContent>
          </Tabs>
        </div>
      </div>
    </div>
  )
}