zebflow 0.10.12

Reactive web automation platform — pipelines, templates, and real-time collaboration in a single binary.
Documentation
import { cx } from "zeb/react";
import Badge from "@/components/ui/badge";
import { formatBytes } from "@/components/lib/format";
import { LockIcon, LockOpenIcon } from "@/pages/project-studio/components/icons";
import { FsFileIcon, FsImageIcon, FsTrashIcon } from "@/pages/project-studio/files/components/file-icons";
import { AccessToggleButton, RowIconButton } from "@/pages/project-studio/files/components/access-controls";
import { ExposureMark, ownRule } from "@/pages/project-studio/files/components/exposure-mark";

const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "bmp"];

/** One file in the explorer. `ctx` is what every row shares; `actions` is what this row can do. */
export function FileRow({ file, ctx, actions }) {
  const ext = (file.name?.split(".").pop() ?? "").toLowerCase();
  const exposed = file.access && file.access !== "private";
  const rule = ownRule(ctx.rules, file.path);
  const accessTitle = exposed ? "Make file private" : "Make file public";
  return (
    <div
      className={cx(
        "group flex items-center gap-2 min-h-[2.1rem] px-2 py-1.5 rounded-md border bg-muted transition-colors",
        file.access === "public_execute" ? "border-red-500/50" : exposed ? "border-amber-400/50" : "border-border",
      )}
    >
      {IMAGE_EXTENSIONS.includes(ext) ? <FsImageIcon /> : <FsFileIcon />}
      <a
        className="flex-1 min-w-0 truncate font-medium text-[0.78rem] text-foreground hover:text-primary hover:underline"
        href={file.url}
        target="_blank"
        rel="noopener"
      >
        {file.name}
      </a>
      <span className="text-[0.7rem] text-muted-foreground whitespace-nowrap shrink-0">
        {formatBytes(file.size)}
        {file.modified ? ` · ${new Date(file.modified * 1000).toLocaleDateString()}` : ""}
      </span>
      <ExposureMark access={file.access} inherited={exposed && !rule} serve={rule?.serve ?? []} />
      {exposed ? null : (
        <Badge variant="outline" className="text-[0.65rem] shrink-0">private</Badge>
      )}
      <AccessToggleButton
        item={file}
        scope="object"
        ctx={ctx}
        title={accessTitle}
        className="flex items-center justify-center w-6 h-6 rounded shrink-0 text-muted-foreground transition-colors hover:text-primary hover:bg-primary/10"
        onToggle={actions.toggleAccess}
      >
        {exposed ? <LockOpenIcon className="w-3.5 h-3.5" /> : <LockIcon className="w-3.5 h-3.5" />}
      </AccessToggleButton>
      <RowIconButton title="Delete file" tone="danger" onClick={actions.remove}>
        <FsTrashIcon />
      </RowIconButton>
    </div>
  );
}