ComponentsSelection

Lozenge dropdown trigger

A stage lozenge that opens the stage menu. You change an Application's stage where you read it, in the cell or on the record.

Playground

Change the controls to try each prop. The code under the preview updates as you go and lists only the props you changed; copy it into your page.

States
import { LozengeDropdownTrigger } from '@/ui/lozenge-dropdown-trigger';<LozengeDropdownTrigger label="Stage" />

Default

The Stage row of an Application. Hover or focus shows the 12px chevron; Enter, Space or a click opens the stage menu with search on top. The new stage shows at once and a spinner holds the chevron's place until it saves, then a toast offers Undo.

import { useState } from 'react';
import { LozengeDropdownTrigger } from '@/ui/lozenge-dropdown-trigger';
import { toast } from '@/ui/toast';
import { stages } from './stages';

// The Stage row of an Application: the new stage shows at once, the spinner holds the chevron's place until it saves.
export default function Stage() {
  const [stage, setStage] = useState('interview');
  const [saving, setSaving] = useState(false);
  return (
    <LozengeDropdownTrigger
      label="Stage"
      items={stages}
      value={stage}
      loading={saving}
      onValueChange={(next) => {
        const before = stage;
        setStage(next);
        setSaving(true);
        setTimeout(() => {
          setSaving(false);
          toast.add({ title: `Maya Chen moved to ${stages.find((s) => s.value === next)?.label}`, actionProps: { children: 'Undo', onClick: () => setStage(before) } });
        }, 900);
      }}
    />
  );
}

Appearances

Each stage keeps one hue everywhere, one per phase (Decided 1): Short list and Interview grey, CV sent blue, Offer purple, Placed green, closed red. The trigger only adds the chevron.

import { LozengeDropdownTrigger } from '@/ui/lozenge-dropdown-trigger';
import { stages } from './stages';

// One hue per phase: the trigger only adds the chevron.
export default function Appearances() {
  return (
    <div className="flex flex-wrap items-center gap-150">
      {stages.map((stage) => (
        <LozengeDropdownTrigger key={stage.value} label="Stage" items={stages} value={stage.value} />
      ))}
    </div>
  );
}

Saving and read only

Saving: a spinner in the chevron's place, and the menu waits. Read only, for archived rows and members without edit rights: a plain lozenge with no button.

Placed
import { LozengeDropdownTrigger } from '@/ui/lozenge-dropdown-trigger';
import { stages } from './stages';

// Saving holds a spinner; read only (an archived row, a member without edit rights) is a plain lozenge.
export default function States() {
  return (
    <div className="flex flex-wrap items-center gap-300">
      <LozengeDropdownTrigger label="Stage" items={stages} value="offer" loading />
      <LozengeDropdownTrigger label="Stage" items={stages} value="placed" readOnly />
    </div>
  );
}

Failed move and long names

A move that fails puts the old stage back and says so in an error toast that stays until closed. A long stage name ends in an ellipsis at 200px.

import { useState } from 'react';
import { LozengeDropdownTrigger, type LozengeOption } from '@/ui/lozenge-dropdown-trigger';
import { toast } from '@/ui/toast';
import { stages } from './stages';

const long: LozengeOption[] = [...stages, { value: 'final', label: 'Final interview with the hiring committee', phase: 'client' }];

// A move that fails puts the old stage back and says so in an error toast that stays until closed. A long stage name
// ends in an ellipsis at 200px.
export default function Failure() {
  const [stage, setStage] = useState('final');
  const [saving, setSaving] = useState(false);
  return (
    <LozengeDropdownTrigger
      label="Stage"
      items={long}
      value={stage}
      loading={saving}
      onValueChange={(next) => {
        const before = stage;
        setStage(next);
        setSaving(true);
        setTimeout(() => {
          setSaving(false);
          setStage(before);
          toast.add({ type: 'error', title: 'Couldn’t move Nguyễn Thị Minh Khai', description: 'The stage is back as it was. Check your connection and try again.' });
        }, 900);
      }}
    />
  );
}

On this page