Skip to content
View as Markdown

ActionPanel ​

ActionPanel is a presentational card component for rendering a vertical list of actions (icon + label). It is designed for ERP detail pages and side panels where available actions are often backend-driven.

Import ​

tsx
import { ActionPanel } from "@tailor-platform/app-shell";

Basic Usage ​

tsx
function BasicUsage() {
  return (
    <ActionPanel
      title="Actions"
      actions={[
        {
          key: "create-invoice",
          label: "Create new sales invoice",
          icon: <Receipt />,
          onClick: () => {},
        },
        {
          key: "view-docs",
          label: "View documentation",
          icon: <ExternalLink />,
          onClick: () => {},
        },
      ]}
    />
  );
}

Props ​

Generated from the type surface — first-party props only.

ActionPanelProps ​

PropTypeDefaultDescription
actionsActionItem[]-List of actions to render
className?string-Additional CSS classes for the card container
emptyContent?ReactNode-Content shown with consistent padding when actions is empty. Defaults to a localized "No actions available" message. Strings use the default body text styling; other React nodes render unchanged. Pass null to hide the empty content and its additional vertical spacing while keeping the card and title.
titlestring-Card title

Exports ​

  • ActionPanel — function
  • ActionPanelProps — interface

ActionItem ​

PropertyTypeDefaultDescription
keystringRequiredUnique key for stable rendering
labelstringRequiredAction label shown to users
iconReactNodeRequiredIcon rendered in a fixed 16px slot
onClick() => void | Promise<void>-Click handler for the action
disabledbooleanfalseDisables interaction and applies disabled styling
loadingbooleanfalseShows spinner, disables interaction, sets busy UI
variant"default" | "destructive""default"Visual variant; use "destructive" for dangerous actions like delete

Behavior ​

  • Rows are rendered as <button type="button"> elements.
  • disabled or loading makes the row non-interactive.
  • When loading is true, the icon slot renders a spinner without shifting layout.
  • Empty actions show emptyContent, which defaults to a styled message localized using the AppShell locale: No actions available in English or 利用可能なアクションはありません in Japanese.

Custom Empty Content ​

Pass a string or any ReactNode to emptyContent to replace the default content when actions is empty. Strings use the same muted Text body styling as the default message. Other React nodes render directly inside Card.Content without a paragraph wrapper and control their own typography. Card.Content owns the spacing, and the title, empty content, and action icons share the same 32px horizontal inset. Action rows retain horizontal padding around their hover backgrounds and focus rings. Custom empty content is ignored when actions are present.

tsx
function CustomEmptyContent() {
  return (
    <ActionPanel
      title="操作"
      actions={[]}
      emptyContent={
        <div>
          <p>利用可能な操作はありません</p>
          <p>ステータスが変わると、操作が表示されます。</p>
        </div>
      }
    />
  );
}

Pass null to hide the empty content and its additional vertical spacing while keeping the card and its title. To hide the entire panel when no actions are available, render it conditionally with actions.length > 0.

With Loading State ​

Use parent state to control which row is loading:

tsx
const [loadingKey, setLoadingKey] = useState<string | null>(null);

const handleConfirm = async () => {
  setLoadingKey("confirm");
  try {
    await confirmOrder(orderId);
  } finally {
    setLoadingKey(null);
  }
};

<ActionPanel
  title="Actions"
  actions={[
    {
      key: "confirm",
      label: "Confirm order",
      icon: <CheckIcon />,
      onClick: handleConfirm,
      loading: loadingKey === "confirm",
    },
  ]}
/>;

CommandPalette Integration ​

ActionPanel automatically registers its enabled actions to the CommandPalette. Actions that are not disabled, not loading, and have an onClick handler are registered under the panel's title as the group name. Registration is cleaned up when the component unmounts.

This means users can discover and trigger any visible ActionPanel action via Cmd+K / Ctrl+K without any additional setup.

tsx
// Actions in this panel will automatically appear in the CommandPalette
<ActionPanel
  title="Order Actions"
  actions={[
    { key: "confirm", label: "Confirm order", icon: <CheckIcon />, onClick: handleConfirm },
    { key: "cancel", label: "Cancel order", icon: <XIcon />, onClick: handleCancel },
  ]}
/>

Note: ActionPanel must be rendered inside AppShell (which provides the required CommandPaletteProvider). Rendering it outside that tree will throw.

Backend-Driven Pattern ​

In ERP apps, the backend often decides which actions are available for a document. A common setup:

  1. Backend returns document data and available action keys.
  2. Frontend maps each key to a registry entry (label, icon, execute).
  3. Page builds ActionItem[] and wires onClick.
  4. Page controls loading per key and passes it to ActionPanel.

This keeps ActionPanel simple and reusable across entity types.

Accessibility ​

  • Action list uses role="list" and each row wrapper uses role="listitem".
  • Buttons are keyboard accessible and include focus-visible states.
  • Loading rows set aria-busy="true".
  • Disabled rows set aria-disabled="true" and native disabled.

Styling ​

The component is styled with the library's internal astw:-prefixed utilities:

  • Full width card
  • Compact spacing for dense action lists
  • Fixed icon slot for stable alignment
  • Inline spinner for loading state

Use className on ActionPanel for card-level properties it does not set itself. Write plain utilities — see Styling AppShell components.

Examples in This Repo ​

  • examples/app-module includes an Action Panel Demo resource.
  • The 2 Columns layout page also includes an ActionPanel with loading behavior.
  • Layout - Compose page-level column layouts
  • DescriptionCard - Display structured record fields
  • Badge - Show statuses and semantic labels