ComponentsTables and views

Add column and field editor

+ at the end of the header row creates a custom field and adds it to this View. Settings › Fields uses the same form.

Playground

Change the controls to try each prop. The code under the preview lists only the props you changed.

import { AddColumnButton } from '@/ui/add-column-and-field-editor';<AddColumnButton />

Add a column

  • sits in the header row's last cell, through the table's headerEnd. It opens the field form in a popover; a created field joins this View only and confirms with a toast.
CandidateCompany
Maya ChenHalcyon Bank
import { useState } from 'react';
import { AddColumnButton, FieldEditor } from '@/ui/add-column-and-field-editor';
import { Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from '@/ui/popover';
import { Table, type TableColumn } from '@/ui/table';
import { toast } from '@/ui/toast';

type Candidate = { id: string; name: string; company: string };

const base: TableColumn<Candidate>[] = [
  { key: 'name', label: 'Candidate', cell: (row) => row.name },
  { key: 'company', label: 'Company', width: 180, cell: (row) => row.company },
];

// Press + at the end of the header row, name the field and create it: it joins this table.
export default function AddColumnInTheHeader() {
  const [columns, setColumns] = useState(base);
  const [open, setOpen] = useState(false);
  const [pending, setPending] = useState(false);
  return (
    <Table
      label="Candidates"
      columns={columns}
      rows={[{ id: 'c1', name: 'Maya Chen', company: 'Halcyon Bank' }]}
      headerEnd={
        <Popover open={open} onOpenChange={setOpen}>
          <PopoverTrigger render={<AddColumnButton />} />
          <PopoverContent align="end">
            <PopoverHeader>
              <PopoverTitle>New field on Candidates</PopoverTitle>
            </PopoverHeader>
            <FieldEditor
              noun="candidate"
              autoFocus
              pending={pending}
              onCancel={() => setOpen(false)}
              onSubmit={(field) => {
                setPending(true);
                setTimeout(() => {
                  setPending(false);
                  setOpen(false);
                  setColumns((all) => [...all, { key: field.name, label: field.name, cell: () => null }]);
                  toast.add({ type: 'success', title: `${field.name} added` });
                }, 700);
              }}
            />
          </PopoverContent>
        </Popover>
      }
    />
  );
}

Locked

Only Owners and Admins add fields. Everyone else sees a lock that stays focusable, with a tooltip that names who can.

import { AddColumnButton } from '@/ui/add-column-and-field-editor';

// A member who can't add fields sees a lock; its tooltip names who can.
export default function AddColumnLocked() {
  return (
    <div className="flex items-center gap-200">
      <AddColumnButton />
      <AddColumnButton locked="Only Owners and Admins can add fields. Ask Joseph." />
    </div>
  );
}

The form

Name and Type first; the form grows only for the type picked: options for selects, format and decimal places for numbers, a default currency for currency. Each option is a lozenge; press it to change its colour. Enter in Add option adds the option without submitting. Create field with no name says what to do. In Settings › Fields the form joins no View.

The type locks once a candidate has a value.

No View changes. Anyone can add it to a View from Display.

import { FieldEditor } from '@/ui/add-column-and-field-editor';

// The same form inline in Settings › Fields: the field joins no View. Press Create field with no name to see the message.
// Pick Single select or Number to see the form grow.
export default function AddColumnSettingsForm() {
  return (
    <div className="w-80 rounded-large bg-surface p-150 shadow-raised">
      <FieldEditor noun="candidate" attach="none" onCancel={() => {}} onSubmit={() => {}} />
    </div>
  );
}

When the create fails

The form keeps what was typed and says what happened and what to do.

The type locks once a company has a value.

Added to this View only. Other Views get it from Display.

import { FieldEditor } from '@/ui/add-column-and-field-editor';

// A create that fails keeps what was typed and says what to do.
export default function AddColumnCreateFails() {
  return (
    <div className="w-80 rounded-large bg-surface p-150 shadow-raised">
      <FieldEditor noun="company" onCancel={() => {}} onSubmit={() => {}} error="Couldn't create the field. Check your connection and try again." />
    </div>
  );
}

On this page