VVibeFlui

FluiKitAction

FluiKitAction renders one action as a button or icon button.

It resolves permission, visibility, disabled state, confirmation, action execution, backend feedback status, and action events.

Props

TS
type FluiKitActionProps = {
  schema: NormalizedFluiKitConfig;
  action: FluiKitActionConfig;
  operation: string;
  row?: Record<string, unknown>;
  className?: string;
  execute?: boolean;
  display?: "button" | "icon";
  onAction?: (resolution, result?) => unknown | Promise<unknown>;
  onActionError?: (error, resolution) => void | Promise<void>;
};

Minimal example

schemas/users/users.schema.ts

TS
export const usersSchema = {
  version: "1.0.0",
  resource: "users",
  identity: { key: "id", param: "id" },
  actions: {
    delete: {
      enabled: true,
      label: "Delete",
      icon: "lucide:trash-2",
      variant: "danger",
      handler: "users.delete",
      confirm: true
    }
  },
  registry: {
    actionHandlers: ["users.delete"]
  }
} as const;

lib/vibeflui/registry.ts

TS
import { createRegistry } from "@vibeflui/core";

export const registry = createRegistry({
  actionHandlers: {
    "users.delete": async ({ identityValue }) => ({
      status: true,
      code: 200,
      message: `User ${String(identityValue)} deleted`
    })
  }
});

app/users/delete-action-preview.tsx

TSX
"use client";

import { FluiKitAction, FluiKitProvider, normalizeSchema } from "@vibeflui/core";
import { usersSchema } from "@/schemas/users/users.schema";
import { registry } from "@/lib/vibeflui/registry";

const schema = normalizeSchema(usersSchema);
const row = { id: "usr_1", name: "Ada Lovelace" };

function DeleteUserButton() {
  return <FluiKitAction schema={schema} action={schema.actions.delete} operation="delete" row={row} />;
}

export function DeleteUserButtonPage() {
  return (
    <FluiKitProvider registry={registry}>
      <DeleteUserButton />
    </FluiKitProvider>
  );
}

This example needs FluiKitProvider because handler: "users.delete" resolves from registry.actionHandlers.

Default behavior

execute defaults to true.

The action renders nothing when:

  • schema.uiMode is readonly — unless this is the view/detail action with action.enabled explicitly true; viewing isn't a mutation, so it's the one action that can opt back in under readonly
  • action.enabled is false
  • permission resolution denies the action
  • action.visibleWhen evaluates to false for the current row

disabledWhen disables the rendered button without hiding it.

Display resolution

The display mode is resolved in this order:

  1. display prop
  2. action.display
  3. action.iconOnly ? "icon" : "button"

The icon is resolved from action.icon, then from built-in defaults for create, edit/update, delete, view/detail, and bulk operations. Icon-only actions get mdi:dots-horizontal when no better default exists.

Confirmation

If action.confirm is truthy, the action opens FluiKitConfirmDialog before running.

confirm.secondConfirm can add a second confirmation step. This replaces the plain confirm: true on the delete action in the schema above:

TS
actions: {
  delete: {
    enabled: true,
    label: "Delete",
    icon: "lucide:trash-2",
    variant: "danger",
    handler: "users.delete",
    confirm: {
      title: "Delete user?",
      description: "This action cannot be undone.",
      confirmLabel: "Delete",
      secondConfirm: {
        title: "Delete permanently?",
        confirmLabel: "Yes, delete"
      }
    }
  }
}

Execution flow

When clicked, the component:

  1. emits actionClicked
  2. resolves the action with resolveAction
  3. executes executeOperation when execute is true
  4. checks backend feedback status from the operation result
  5. calls onAction
  6. checks backend feedback status from onAction
  7. emits actionSucceeded or actionFailed

If onActionError is not provided, thrown errors are rethrown.