Skip to content

Record page

The page of one record, finished: pass the header’s data, the sections as items and the properties as label/value items, and fill in the content. Sections are tabs of the page (each with its content) or links to their own URLs (with the route’s content as children).

Language
Appearance

Patient: tabs of the page

The tab lives in the app’s state (?tab=); the Data tab keeps its draft while another shows.

import { ArchiveIcon, CalendarPlusIcon, PhoneIcon } from '@phosphor-icons/react';
import { useState } from 'react';
import { Avatar, Notice, RecordPage, SettingGroup, SettingToggle } from '@arbol-ai/ui';

const PatientPage = () => {
  const [tab, setTab] = useState('appointments');
  return (
    <RecordPage
      actions={[{ label: 'Call', icon: <PhoneIcon />, href: 'tel:+573005550101' }]}
      avatar={<Avatar name="Laura Gómez" size="xl" />}
      className="px-0 md:px-0"
      menu={[{ label: 'Archive patient', icon: <ArchiveIcon />, isDestructive: true }]}
      menuLabel="More actions for Laura Gómez"
      meta={['CC 1.020.304.050', '34 years', 'Sura EPS']}
      onSectionChange={setTab}
      primaryAction={{ label: 'Book', icon: <CalendarPlusIcon />, href: '#/record-page' }}
      section={tab}
      sections={[
        {
          id: 'appointments',
          label: 'Appointments',
          count: 12,
          content: <p className="text-sm">Monday 14 · Annual check-up with Dr. Ruiz</p>,
        },
        {
          id: 'notes',
          label: 'Notes',
          count: 3,
          content: <p className="text-sm">Prefers morning appointments. Allergic to penicillin.</p>,
        },
        {
          id: 'profile',
          label: 'Data',
          keepMounted: true,
          content: (
            <SettingGroup title="Contact">
              <SettingToggle label="Receives reminders by WhatsApp" />
            </SettingGroup>
          ),
        },
      ]}
      sectionsLabel="Patient sections"
      summary={<Notice tone="info">Next visit: Monday 14 at 9:30, with Dr. Ruiz.</Notice>}
      title="Laura Gómez"
    />
  );
};

<PatientPage />

Campaign: properties beside the content

Narrow the window below 1280px: the properties fold into “Campaign details” above the content.

import { PauseIcon, PencilSimpleIcon, TrashIcon } from '@phosphor-icons/react';
import { Badge, RecordPage } from '@arbol-ai/ui';

<RecordPage
  actions={[{ label: 'Pause', icon: <PauseIcon /> }]}
  className="px-0 md:px-0"
  menu={[{ label: 'Delete campaign', icon: <TrashIcon />, isDestructive: true }]}
  menuLabel="More actions for Spring reminders"
  meta="WhatsApp · started Monday"
  onTitleChange={() => undefined}
  properties={[
    { label: 'Agent', value: 'Front desk' },
    { label: 'Line', value: '+57 601 555 0101' },
    { label: 'Contact hours', value: 'Weekdays, 8:00 to 18:00' },
    { label: 'End', value: null },
  ]}
  propertiesAction={{ label: 'Edit settings', icon: <PencilSimpleIcon />, href: '#/record-page' }}
  propertiesLabel="Campaign details"
  status={<Badge tone="success">Running</Badge>}
  title="Spring reminders"
  titleLabel="Campaign name"
>
  <p className="text-muted-foreground rounded-card border p-6 text-sm">
    812 reached · 164 booked · 66% answered
  </p>
  <p className="text-muted-foreground rounded-card border p-6 text-sm">
    The recipients table goes here.
  </p>
</RecordPage>

Professional: sections at their own URLs

Each section is a page; the router’s content for it is the children. Here a small router in memory follows the links.

Dra. Ana Ruiz

Internal medicineRoom 4

Weekdays, 7:00 to 13:00. Closed on holidays.

import { CalendarBlankIcon } from '@phosphor-icons/react';
import { useState } from 'react';
import { RouterProvider } from 'react-aria-components';
import { Avatar, RecordPage } from '@arbol-ai/ui';

const PROFESSIONAL_SECTIONS = {
  schedule: 'Weekdays, 7:00 to 13:00. Closed on holidays.',
  services: 'General consultation, follow-up visit.',
  profile: 'Internal medicine · Room 4.',
} as const;

const ProfessionalPage = () => {
  const [section, setSection] = useState<keyof typeof PROFESSIONAL_SECTIONS>('schedule');
  return (
    <RouterProvider
      navigate={(href) => {
        const next = href.split('/').at(-1);
        if (next === 'schedule' || next === 'services' || next === 'profile') {
          setSection(next);
        }
      }}
    >
      <RecordPage
        actions={[{ label: 'View agenda', icon: <CalendarBlankIcon />, href: '#/record-page/agenda' }]}
        avatar={<Avatar name="Ana Ruiz" size="xl" />}
        className="px-0 md:px-0"
        meta={['Internal medicine', 'Room 4']}
        section={section}
        sections={[
          {
            id: 'schedule',
            label: 'Schedule',
            href: '#/record-page/schedule',
          },
          {
            id: 'services',
            label: 'Services',
            href: '#/record-page/services',
            attention: 'No services yet',
          },
          { id: 'profile', label: 'Profile', href: '#/record-page/profile' },
        ]}
        sectionsLabel="Professional sections"
        title="Dra. Ana Ruiz"
      >
        <p className="text-sm">{PROFESSIONAL_SECTIONS[section]}</p>
      </RecordPage>
    </RouterProvider>
  );
};

<ProfessionalPage />

Source

The examples above are src/blocks/record-page/record-page.examples.tsx, which the library’s tests render too.

Type to search…

↑↓ navigate↵ selectEsc close