Audit Log

Compliance-style event log with expandable rows that reveal actor, action, status and request metadata. Compound disclosure API.

Example

Installation

npx shadcn@latest add https://hirael.com/r/audit-log.json

API

<AuditLog />

+ native element props

No props of its own — forwards everything to the underlying element.

<AuditLogItem />

+ native element props
PropTypeDefault
openboolean
defaultOpenboolean
onOpenChange((open: boolean) => void)

<AuditLogTrigger />

+ native element props

No props of its own — forwards everything to the underlying element.

<AuditLogActor />

+ native element props

No props of its own — forwards everything to the underlying element.

<AuditLogAction />

+ native element props

No props of its own — forwards everything to the underlying element.

<AuditLogTime />

+ native element props

No props of its own — forwards everything to the underlying element.

<AuditLogStatus />

+ native element props
PropTypeDefault
tone"default" | "success" | "warning" | "danger" | null"default"

<AuditLogDetail />

+ native element props

No props of its own — forwards everything to the underlying element.

<AuditLogField />

+ native element props
PropTypeDefault
label*React.ReactNode

Component source

"use client";

import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { ChevronRight } from "lucide-react";

import { cn } from "@/lib/utils";
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/registry/hirael/ui/collapsible";

type AuditLogProps = React.ComponentProps<"ul">;

function AuditLog({ className, ...props }: AuditLogProps) {
  return (
    <ul
      data-slot="audit-log"
      className={cn(
        "divide-y divide-border overflow-hidden rounded-md border border-border bg-card",
        className,
      )}
      {...props}
    />
  );
}

type AuditLogItemProps = Omit<React.ComponentProps<"li">, "onToggle"> & {
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean) => void;
};

function AuditLogItem({
  open,
  defaultOpen,
  onOpenChange,
  className,
  children,
  ...props
}: AuditLogItemProps) {
  return (
    <Collapsible
      asChild
      open={open}
      defaultOpen={defaultOpen}
      onOpenChange={onOpenChange}
    >
      <li
        data-slot="audit-log-item"
        className={cn("flex flex-col", className)}
        {...props}
      >
        {children}
      </li>
    </Collapsible>
  );
}

type AuditLogTriggerProps = React.ComponentProps<typeof CollapsibleTrigger>;

function AuditLogTrigger({
  className,
  children,
  ...props
}: AuditLogTriggerProps) {
  return (
    <CollapsibleTrigger
      data-slot="audit-log-trigger"
      className={cn(
        "group flex w-full items-center gap-3 px-4 py-3 text-start transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
        className,
      )}
      {...props}
    >
      <ChevronRight
        aria-hidden
        className="size-3.5 shrink-0 text-muted-foreground transition-transform duration-150 group-data-[state=open]:rotate-90 rtl:group-data-[state=closed]:rotate-180"
      />
      {children}
    </CollapsibleTrigger>
  );
}

type AuditLogActorProps = React.ComponentProps<"span">;

function AuditLogActor({ className, ...props }: AuditLogActorProps) {
  return (
    <span
      data-slot="audit-log-actor"
      className={cn("shrink-0 text-sm font-medium text-foreground", className)}
      {...props}
    />
  );
}

type AuditLogActionProps = React.ComponentProps<"span">;

function AuditLogAction({ className, ...props }: AuditLogActionProps) {
  return (
    <span
      data-slot="audit-log-action"
      className={cn("truncate text-sm text-muted-foreground", className)}
      {...props}
    />
  );
}

type AuditLogTimeProps = React.ComponentProps<"time">;

function AuditLogTime({ className, ...props }: AuditLogTimeProps) {
  return (
    <time
      data-slot="audit-log-time"
      className={cn(
        "ms-auto shrink-0 font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground",
        className,
      )}
      {...props}
    />
  );
}

const auditLogStatusVariants = cva(
  "inline-flex shrink-0 items-center rounded-sm border px-1.5 py-0.5 font-mono text-[10px] uppercase leading-none tracking-[0.08em]",
  {
    variants: {
      tone: {
        default: "border-border text-muted-foreground",
        success: "border-success/30 text-success",
        warning: "border-warning/30 text-warning",
        danger: "border-destructive/30 text-destructive",
      },
    },
    defaultVariants: {
      tone: "default",
    },
  },
);

type AuditLogStatusProps = React.ComponentProps<"span"> &
  VariantProps<typeof auditLogStatusVariants>;

function AuditLogStatus({
  tone = "default",
  className,
  ...props
}: AuditLogStatusProps) {
  return (
    <span
      data-slot="audit-log-status"
      data-tone={tone}
      className={cn(auditLogStatusVariants({ tone }), className)}
      {...props}
    />
  );
}

type AuditLogDetailProps = React.ComponentProps<"dl">;

function AuditLogDetail({
  className,
  children,
  ...props
}: AuditLogDetailProps) {
  return (
    <CollapsibleContent asChild>
      <dl
        data-slot="audit-log-detail"
        className={cn(
          "grid grid-cols-[max-content_1fr] gap-x-6 gap-y-1.5 border-t border-border bg-muted/30 px-4 py-3 ps-11",
          className,
        )}
        {...props}
      >
        {children}
      </dl>
    </CollapsibleContent>
  );
}

type AuditLogFieldProps = React.ComponentProps<"div"> & {
  label: React.ReactNode;
};

function AuditLogField({
  label,
  className,
  children,
  ...props
}: AuditLogFieldProps) {
  return (
    <div
      data-slot="audit-log-field"
      className={cn("contents", className)}
      {...props}
    >
      <dt className="font-mono text-[11px] uppercase tracking-[0.08em] text-muted-foreground">
        {label}
      </dt>
      <dd className="min-w-0 break-words font-mono text-[12px] text-foreground">
        {children}
      </dd>
    </div>
  );
}

export {
  AuditLog,
  AuditLogItem,
  AuditLogTrigger,
  AuditLogActor,
  AuditLogAction,
  AuditLogTime,
  AuditLogStatus,
  AuditLogDetail,
  AuditLogField,
};

Dependencies

shadcn registry

collapsible

npm

lucide-reactclass-variance-authority