Game UI

Panel

The menu window: header, body, and a footer for key hints.

stablev0.2.0 Chromium 103 ui-scale safe

Install

Copies the file into components/ds/, with its registry dependencies and npm packages. See Installation for the one-time setup.
bunx shadcn@3 add @fastrp/panel

Examples

An inventory window

Open alone
Tony Montana

Envanter

Ağırlık18.4 / 30 KG
ESCKapatKullanBırak

The HUD, over the city

Meters, ring meters and key hints composed as a game draws them.

Open alone
$15,150
$700
EBuzdolabıGKapıyı kilitle

API

<Panel />

A menu window: the game panel — translucent, blurred, framed, lit along its top edge — with a header, a body and a footer for its key hints. The surface of every in-game menu; `Card` is the same surface for content in a page.

PropTypeDefaultNotes
accentbooleanfalseAn accent line along the top edge — the panel with focus.
texture"none" | "scanlines" | "grain" | "hatch"noneA texture over the surface: display scanlines, film grain or diagonal hatching.
<PanelFooter />Takes the props of what it wraps

The foot of the window, darker, for its key hints and the primary action.

<PanelHeader />

The title bar: eyebrow and title on the left; key hints, tabs or a close on the right.

PropTypeDefaultNotes
title*ReactNode—
eyebrowReactNode—The small label over the title: a section, a character name.

Dependencies

npm

  • None

Registry

  • @fastrp/utils

Source

components/ds/panel.tsxShow
import type * as React from 'react';
import { cn } from '@/lib/utils';

// @fast-ds [email protected]

const TEXTURE = {
  none: '',
  scanlines: 'tex-scanlines',
  grain: 'tex-grain',
  hatch: 'tex-hatch',
} as const;

export type PanelProps = React.ComponentProps<'section'> & {
  /** A texture over the surface: display scanlines, film grain or diagonal hatching. */
  texture?: keyof typeof TEXTURE;
  /** An accent line along the top edge — the panel with focus. */
  accent?: boolean;
};

/**
 * A menu window: the game panel — translucent, blurred, framed, lit along its top edge — with a
 * header, a body and a footer for its key hints. The surface of every in-game menu; `Card` is
 * the same surface for content in a page.
 */
function Panel({ texture = 'none', accent = false, className, children, ...props }: PanelProps) {
  return (
    <section
      data-slot="panel"
      className={cn('panel flex flex-col overflow-hidden', className)}
      {...props}
    >
      {texture !== 'none' && (
        <span
          aria-hidden
          className={cn('pointer-events-none absolute inset-0', TEXTURE[texture])}
        />
      )}
      {accent && (
        <span
          aria-hidden
          className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-primary to-transparent"
        />
      )}
      {children}
    </section>
  );
}

export type PanelHeaderProps = Omit<React.ComponentProps<'header'>, 'title'> & {
  title: React.ReactNode;
  /** The small label over the title: a section, a character name. */
  eyebrow?: React.ReactNode;
};

/** The title bar: eyebrow and title on the left; key hints, tabs or a close on the right. */
function PanelHeader({ title, eyebrow, className, children, ...props }: PanelHeaderProps) {
  return (
    <header
      data-slot="panel-header"
      className={cn(
        'relative flex items-end justify-between gap-6 border-b border-foreground/10 px-[var(--card-pad)] pb-4 pt-5',
        className,
      )}
      {...props}
    >
      <div className="flex min-w-0 flex-col gap-1">
        {eyebrow && <span className="text-eyebrow uppercase text-muted-foreground">{eyebrow}</span>}
        <h2 className="truncate font-display text-3xl leading-none text-foreground">{title}</h2>
      </div>
      {children && <div className="flex shrink-0 items-center gap-3">{children}</div>}
    </header>
  );
}

function PanelBody({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div
      data-slot="panel-body"
      className={cn('relative min-h-0 flex-1 overflow-y-auto p-[var(--card-pad)]', className)}
      {...props}
    />
  );
}

/** The foot of the window, darker, for its key hints and the primary action. */
function PanelFooter({ className, ...props }: React.ComponentProps<'footer'>) {
  return (
    <footer
      data-slot="panel-footer"
      className={cn(
        'relative flex items-center justify-between gap-4 border-t border-foreground/10 bg-black/20 px-[var(--card-pad)] py-3',
        className,
      )}
      {...props}
    />
  );
}

export { Panel, PanelBody, PanelFooter, PanelHeader };