zzzUI

Daily schedule

以游戏界面呈现的每日日程:四个时段并排,进行中的时段加框并标出 NOW,已过去的时段变暗,各时段的待办事项以任务目标的形式列出。

安装

terminal
# once per project: where @zzzui items come frompnpm dlx shadcn@latest registry add @zzzui=https://zzzui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zzzui/daily-schedule

预览

Today

  1. Dawn05–09
    Done: Charge the rig
    Done: Coffee at the yards
  2. Day09–17
    Done: Two market runs
    Done: Patch the hull
    Done: Sleep
  3. Dusk17–22Now
    Done: Pick up the route chip
    Current: Meet Rook at the canal2/2
  4. Night22–05
    To do: Collect the sealed crates
    To do: Cross before the gate shuts
    To do: Hand over at the North gate
daily-schedule.tsx
import { useState } from "react";import { Badge } from "@/components/zero/badge";import { Button } from "@/components/zero/button";import {  Objective,  ObjectiveCount,  Objectives,  ObjectiveTitle,} from "@/components/zero/objectives";/* * Today, as a game plans a day: four shifts side by side (they stack on a phone), the one under * way framed and marked NOW, those gone by dimmed, each with its errands as objectives. The * current errand can be marked done, and the next one becomes current. The day is made up; feed * your own clock and errands. */type Shift = {  id: string;  name: string;  hours: string;  errands: string[];};const shifts: Shift[] = [  {    id: "dawn",    name: "Dawn",    hours: "05–09",    errands: ["Charge the rig", "Coffee at the yards"],  },  {    id: "day",    name: "Day",    hours: "09–17",    errands: ["Two market runs", "Patch the hull", "Sleep"],  },  {    id: "dusk",    name: "Dusk",    hours: "17–22",    errands: ["Pick up the route chip", "Meet Rook at the canal"],  },  {    id: "night",    name: "Night",    hours: "22–05",    errands: [      "Collect the sealed crates",      "Cross before the gate shuts",      "Hand over at the North gate",    ],  },];const now = 2;export function DailySchedule() {  const [done, setDone] = useState(1);  const errands = shifts[now].errands;  return (    <section      aria-labelledby="today-title"      className="@container grid w-full max-w-6xl gap-5"    >      <div className="flex flex-wrap items-end justify-between gap-3">        <h2          id="today-title"          className="font-black font-zero text-zero-2xl italic"        >          Today        </h2>        <Button          size="sm"          variant="secondary"          disabled={done >= errands.length}          onClick={() => setDone((d) => d + 1)}        >          {done >= errands.length            ? "Shift's errands done"            : "Mark the current errand done"}        </Button>      </div>      <ol className="grid gap-3 @2xl:grid-cols-2 @5xl:grid-cols-4">        {shifts.map((shift, index) => {          const past = index < now;          const isNow = index === now;          return (            <li              key={shift.id}              aria-current={isNow ? "time" : undefined}              className={                "grid content-start gap-3 border-(length:--zero-stroke) border-zero-ink bg-zero-surface p-4 " +                (isNow                  ? "outline-(length:--zero-stroke-thin) outline-solid outline-zero-focus outline-offset-2"                  : past                    ? "opacity-60"                    : "")              }            >              <div className="flex items-center justify-between gap-2">                <span className="grid">                  <span className="font-black font-zero text-zero-xl uppercase italic">                    {shift.name}                  </span>                  <span className="font-semibold font-zero-text text-zero-sm text-zero-text-3 tabular-nums">                    {shift.hours}                  </span>                </span>                {isNow ? <Badge>Now</Badge> : null}              </div>              <Objectives                aria-label={`${shift.name} errands`}                className="max-w-none bg-none p-0"              >                {shift.errands.map((errand, i) => (                  <Objective                    key={errand}                    state={                      past                        ? "done"                        : isNow                          ? i < done                            ? "done"                            : i === done                              ? "active"                              : "pending"                          : "pending"                    }                  >                    <ObjectiveTitle className="text-zero-sm">                      {errand}                    </ObjectiveTitle>                    {isNow && i === done ? (                      <ObjectiveCount>                        {i + 1}/{shift.errands.length}                      </ObjectiveCount>                    ) : null}                  </Objective>                ))}              </Objectives>            </li>          );        })}      </ol>    </section>  );}

行为

每个时段是一个方框,包含名称、起止时间,以及以 Objectives 列出的待办事项:已完成的加删除线,当前事项以强调色显示并附带计数。进行中的时段加框并标出 NOW(对读屏软件而言,它是列表的当前项);已过去的时段变暗。

根据代码块自身的宽度(容器查询),各时段排成四列、两列,或上下堆叠。

由 Objectives、Badge、Button 组成,安装此代码块时会一并安装这些组件。