Skip to content

Examples

Resource table

Compare table and card examples, switch views in More, and adjust card columns while keeping the same row actions and collection states.

Public contractsNxButtonResourcePrimaryCellResourceRowActionsMenuResourceStatusBadgeResourceTableResourceTextCell

Live preview

Live

Resource collection

Switch query states, then use More to choose Table or Cards and adjust card columns while resizing.

ResourceTable

Last action: None

Use a preset or drag the lower-right edge of the frame.

Table view
MachineOwnerStatusActions
MC-001Line A

Jin Lee

Line A

Ready
MC-014Line B

Mina Park

Line B

Maintenance
MC-021Line C

Sora Kim

Line C

Scheduled
Card view
MC-001Line A
Owner

Jin Lee

Line A

Status
Ready
MC-014Line B
Owner

Mina Park

Line B

Status
Maintenance
MC-021Line C
Owner

Sora Kim

Line C

Status
Scheduled

Copyable source

Code example
TSX
import { useMemo, useState } from "react";

import type { ResourceTableColumn } from "@nexia/sdk";
import {
    NxButton,
    ResourcePrimaryCell,
    ResourceRowActionsMenu,
    ResourceStatusBadge,
    ResourceTable,
    ResourceTextCell,
} from "@nexia/sdk/host";

type TableState = "data" | "empty" | "noResults" | "loading" | "error";
type MachineRow = {
    id: string;
    name: string;
    code: string;
    line: string;
    owner: string;
    status: string;
};

const rows: MachineRow[] = [
    {
        id: "cnc-a1",
        name: "CNC A-1",
        code: "MC-001",
        line: "Line A",
        owner: "Jin Lee",
        status: "ready",
    },
    {
        id: "press-b2",
        name: "Press B-2",
        code: "MC-014",
        line: "Line B",
        owner: "Mina Park",
        status: "maintenance",
    },
];

export function ResourceTableExample() {
    const [state, setState] = useState<TableState>("data");
    const [lastAction, setLastAction] = useState("None");
    const columns = useMemo<ResourceTableColumn<MachineRow>[]>(
        () => [
            {
                key: "machine",
                header: "Machine",
                size: "primary",
                cell: (row) => (
                    <ResourcePrimaryCell
                        resource={{
                            type: "manufacturing.machine",
                            id: row.id,
                            label: row.name,
                        }}
                        label={row.name}
                        meta={[
                            { kind: "text", key: "code", label: row.code },
                            { kind: "badge", key: "line", label: row.line },
                        ]}
                        onActivate={() => setLastAction(`View ${row.name}`)}
                    />
                ),
            },
            {
                key: "owner",
                header: "Owner",
                size: "text",
                priority: 2,
                defaultVisible: false,
                cell: (row) => (
                    <ResourceTextCell
                        primary={row.owner}
                        secondary={row.line}
                    />
                ),
            },
            {
                key: "status",
                header: "Status",
                size: "meta",
                cell: (row) => <ResourceStatusBadge status={row.status} />,
            },
            {
                key: "actions",
                header: "Actions",
                size: "actions",
                hideable: false,
                cell: (row) => (
                    <ResourceRowActionsMenu
                        label={`Actions: ${row.name}`}
                        items={[
                            {
                                id: "view",
                                label: "View",
                                onSelect: () =>
                                    setLastAction(`View ${row.name}`),
                            },
                            {
                                id: "edit",
                                label: "Edit",
                                onSelect: () =>
                                    setLastAction(`Edit ${row.name}`),
                            },
                            {
                                id: "schedule-maintenance",
                                label: "Schedule maintenance",
                                onSelect: () =>
                                    setLastAction(
                                        `Schedule maintenance for ${row.name}`
                                    ),
                            },
                            {
                                id: "deactivate",
                                label: "Deactivate",
                                destructive: true,
                                onSelect: () =>
                                    setLastAction(`Deactivate ${row.name}`),
                            },
                        ]}
                    />
                ),
            },
        ],
        []
    );
    const renderCard = (row: MachineRow, index: number) => {
        const primary = columns.find((column) => column.key === "machine");
        const actions = columns.find((column) => column.key === "actions");
        const details = columns.filter(
            (column) => column.key !== "machine" && column.key !== "actions"
        );

        return (
            <div className="space-y-3">
                <div className="flex items-start justify-between gap-2">
                    {primary?.cell(row, index)}
                    <div className="shrink-0">
                        {actions?.cell(row, index)}
                    </div>
                </div>
                <dl className="space-y-2">
                    {details.map((column) => (
                        <div key={column.key}>
                            <dt className="text-sm text-text-tertiary">
                                {column.header}
                            </dt>
                            <dd>{column.cell(row, index)}</dd>
                        </div>
                    ))}
                </dl>
            </div>
        );
    };
    const noResults = state === "noResults";

    return (
        <div className="grid gap-4">
            <div className="flex flex-wrap gap-2">
                {(
                    ["data", "empty", "noResults", "loading", "error"] as const
                ).map((item) => (
                    <NxButton
                        key={item}
                        size="xs"
                        variant={state === item ? "primary" : "secondary"}
                        onClick={() => setState(item)}
                    >
                        {item}
                    </NxButton>
                ))}
            </div>
            <p role="status" className="text-sm text-text-secondary">
                Last action: {lastAction}
            </p>
            {(["table", "cards"] as const).map((defaultView) => (
                <ResourceTable
                    key={defaultView}
                    tableId={`developer-example.production-machines.${defaultView}`}
                    label={
                        defaultView === "table" ? "Table view" : "Card view"
                    }
                    showLabel
                    rows={state === "data" ? rows : []}
                    columns={columns}
                    renderCard={renderCard}
                    defaultView={defaultView}
                    getKey={(row) => row.id}
                    toolbar={{}}
                    loading={state === "loading"}
                    error={state === "error"}
                    errorDescription="Machines could not be loaded."
                    onRetry={() => setState("data")}
                    hasActiveQuery={noResults}
                    emptyState={{
                        kind: "empty",
                        title: "No machines yet",
                        description: "Create a machine to start planning capacity.",
                    }}
                />
            ))}
        </div>
    );
}