Zum Inhalt springen
UI Kit
Accent

Sheet

Ein modales Panel, das von jeder Kante hereingleitet, mit festem Kopf, scrollendem Inhalt und haftender Fußzeile.

Eine von 9 Components in Overlays

2 Variants

Variants

Filter von rechts

Der Klassiker für Filter, Warenkorb und Details. Die Fußzeile bleibt, während der Inhalt scrollt.

Links und unten

Navigation von links, kurze Hinweise von unten mit Griff.

Details

Was Sheet mitbringt, aus Registry und Quellcode gelesen.

Kategorie
Overlays
Bereich
Elements
Variants
2
Version
1.0.0 · seit 1.0.0
Datei
components/ui/sheet.tsx
Bringt mit
utilskiticondialog
npm-Pakete
Keine
Sprachen
Englisch und Deutsch, 1 eigener Text
Studio
Im Code anpassen

Props

Die Schnittstelle SheetProps aus components/ui/sheet.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch. Nimmt außerdem alle Props von Omit<SheetPanelProps, 'onClose' | 'titleId' | 'descriptionId' | 'footer'> an.

  • triggeroptional
    React.ReactElement
  • openoptional
    boolean
  • defaultOpenoptional
    boolean
  • onOpenChangeoptional
    (open: boolean) => void
  • footeroptional
    React.ReactNode | ((close: () => void) => React.ReactNode)

Eingebaute Texte

Diese Texte schreibt Sheet selbst. Die Sprache folgt dem KitProvider, über die Prop labels ersetzt du einzelne.

Alle 1 Texte anzeigen
closeEnglisch CloseDeutsch Schließen

Barrierefreiheit

Aus dem Quellcode der Component abgeleitet.

  • Baut auf dem nativen dialog-Element auf. Der Browser hält den Fokus im Fenster und schließt es mit Escape.
  • Setzt aria-describedby, aria-label und aria-labelledby, damit Screenreader Zweck und Zustand ansagen.
  • Kommt ohne Client-State aus. Der Server schreibt sie vollständig, sie ist auch ohne JavaScript lesbar.

Bewegung

  • Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig. 1 Stellen im Code schalten Bewegung dann ab.
  • Ohne Animationen, sie steht sofort ruhig da.

Quellcode

Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.

Datei anzeigen, 109 Zeilen
components/ui/sheet.tsx
'use client';

import { useId } from 'react';
import { cn, useTexts } from '@/lib/kit';
import { Icon } from '@/components/ui/icon';
import { DialogMotionStyle, scrim, useDialogState, useModalMotion, withTrigger } from '@/components/ui/dialog';

const TEXTS = { en: { close: 'Close' }, de: { close: 'Schließen' } };

export type SheetSide = 'left' | 'right' | 'top' | 'bottom';

/* Where the sheet rests. It slides in and out from its edge (see useModalMotion), so no translate here. */
const PLACE: Record<SheetSide, string> = {
  right: 'ms-auto me-0 h-dvh max-h-dvh w-[min(26rem,calc(100vw-2.5rem))]',
  left: 'ms-0 me-auto h-dvh max-h-dvh w-[min(22rem,calc(100vw-2.5rem))]',
  top: 'mt-0 mb-auto w-full max-w-none',
  bottom: 'mb-0 mt-auto max-h-[88dvh] w-full max-w-none',
};

const SURFACE: Record<SheetSide, string> = {
  right: 'border-s',
  left: 'border-e',
  top: 'border-b rounded-b-2xl',
  bottom: 'border-t rounded-t-2xl',
};

export interface SheetPanelProps {
  side?: SheetSide;
  title: React.ReactNode;
  description?: React.ReactNode;
  footer?: React.ReactNode;
  onClose?: () => void;
  titleId?: string;
  descriptionId?: string;
  labels?: Partial<(typeof TEXTS)['en']>;
  className?: string;
  children?: React.ReactNode;
}

/** The surface of a sheet: header with close, a scrolling body and a sticky footer. */
export function SheetPanel({ side = 'right', title, description, footer, onClose, titleId, descriptionId, labels, className, children }: SheetPanelProps) {
  const t = useTexts(TEXTS, labels);
  const vertical = side === 'top' || side === 'bottom';
  return (
    <div className={cn('flex size-full max-h-[inherit] flex-col border-border bg-popover text-popover-foreground shadow-[var(--shadow-lg)]', SURFACE[side], className)}>
      {side === 'bottom' && <span aria-hidden className="mx-auto mt-2.5 h-1 w-10 shrink-0 rounded-full bg-border-strong" />}
      <div className={cn('kitfx-dialog-part flex shrink-0 items-start gap-4 px-6 pb-4', side === 'bottom' ? 'pt-4' : 'pt-6', vertical && 'mx-auto w-full max-w-[44rem]')}>
        <div className="min-w-0 flex-1">
          <h2 id={titleId} className="text-[17px] font-semibold leading-snug tracking-[-0.015em] text-foreground">
            {title}
          </h2>
          {description && (
            <p id={descriptionId} className="mt-1 text-pretty text-sm leading-relaxed text-muted-foreground">
              {description}
            </p>
          )}
        </div>
        <button type="button" onClick={onClose} aria-label={t.close} className="group/close -me-2 -mt-1 grid size-9 shrink-0 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
          <Icon name="x" size={17} className="transition-transform duration-300 ease-[var(--ease-kit)] group-hover/close:rotate-90 motion-reduce:transition-none" />
        </button>
      </div>
      {children && <div className={cn('kitfx-dialog-part kit-scrollbar min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 pb-6', vertical && 'mx-auto w-full max-w-[44rem]')}>{children}</div>}
      {footer && (
        <div className="kitfx-dialog-part shrink-0 border-t border-border bg-surface px-6 py-4">
          <div className={cn('flex flex-col-reverse gap-2.5 sm:flex-row sm:justify-end [&>*]:max-sm:w-full', vertical && 'mx-auto max-w-[44rem]')}>{footer}</div>
        </div>
      )}
    </div>
  );
}

export interface SheetProps extends Omit<SheetPanelProps, 'onClose' | 'titleId' | 'descriptionId' | 'footer'> {
  trigger?: React.ReactElement;
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean) => void;
  footer?: React.ReactNode | ((close: () => void) => React.ReactNode);
}

/**
 * A panel that slides in from an edge: filters, carts, menus, details. Modal, on the native <dialog>.
 * It slides back out before it closes, its parts arrive one after another.
 *   <Sheet side="right" trigger={<Button>Filters</Button>} title="Filters">…</Sheet>
 */
export function Sheet({ trigger, open: openProp, defaultOpen, onOpenChange, side = 'right', footer, children, ...panel }: SheetProps) {
  const [open, setOpen] = useDialogState(openProp, defaultOpen, onOpenChange);
  const [modal, present] = useModalMotion(open, setOpen, { motion: side });
  const id = useId();
  const close = () => setOpen(false);
  return (
    <>
      {trigger && withTrigger(trigger, open, setOpen, id)}
      <DialogMotionStyle />
      <dialog
        {...modal}
        id={id}
        aria-labelledby={`${id}-t`}
        aria-describedby={panel.description ? `${id}-d` : undefined}
        className={cn('kitfx-dialog overflow-visible bg-transparent p-0 text-left outline-none data-[closing]:pointer-events-none', PLACE[side], scrim)}
      >
        {present && (
          <SheetPanel {...panel} side={side} titleId={`${id}-t`} descriptionId={`${id}-d`} onClose={close} footer={typeof footer === 'function' ? footer(close) : footer}>
            {children}
          </SheetPanel>
        )}
      </dialog>
    </>
  );
}

Häufige Fragen

Wie installiere ich Sheet?

Mit aiw add @ai-website/sheet. Der Befehl legt die Datei components/ui/sheet.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind icon und dialog. Weitere npm-Pakete braucht sie nicht.

Welche Varianten hat Sheet?

2 Varianten, Filter von rechts und Links und unten.

Kann ich die Texte von Sheet übersetzen?

Ja. Sheet bringt 1 eigene Texte auf Deutsch und Englisch mit, etwa „Schließen“. Die Sprache folgt dem KitProvider, einzelne Texte ersetzt du über die Prop labels.

Kann ein KI-Assistent Sheet einbauen?

Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Sheet mit aiw_components und installieren sie mit aiw_add @ai-website/sheet.

Oft gesucht

Direkt zu

In die Zwischenablage kopiert