Component Patterns

Composable examples showing how Davis components work together in real-world scenarios.


Form with Validation

A complete form with field validation, error states, and submission:

import { Input, Textarea, Select, Button, Alert } from "@libretexts/davis-react";
import { useState } from "react";

function ContactForm() {
  const [errors, setErrors] = useState({});
  const [submitted, setSubmitted] = useState(false);

  return (
    <form className="max-w-2xl space-y-6" onSubmit={handleSubmit}>
      {submitted && (
        <Alert variant="success" title="Message sent!">
          We'll get back to you within 24 hours.
        </Alert>
      )}

      <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
        <Input
          name="firstName"
          label="First Name"
          required
          variant={errors.firstName ? "error" : "default"}
          errorMessage={errors.firstName}
        />
        <Input
          name="lastName"
          label="Last Name"
          required
          variant={errors.lastName ? "error" : "default"}
          errorMessage={errors.lastName}
        />
      </div>

      <Input
        name="email"
        label="Email"
        type="email"
        required
        variant={errors.email ? "error" : "default"}
        errorMessage={errors.email}
      />

      <Select
        label="Subject"
        options={[
          { value: "general", label: "General Inquiry" },
          { value: "support", label: "Technical Support" },
          { value: "feedback", label: "Feedback" },
        ]}
      />

      <Textarea
        name="message"
        label="Message"
        required
        rows={4}
        variant={errors.message ? "error" : "default"}
        errorMessage={errors.message}
      />

      <div className="flex gap-3 justify-end">
        <Button variant="outline" type="button">Cancel</Button>
        <Button type="submit">Send Message</Button>
      </div>
    </form>
  );
}

Card Grid

A responsive grid of cards, typical for course listings or resource libraries:

import { Card, Badge, Avatar } from "@libretexts/davis-react";

function CourseGrid({ courses }) {
  return (
    <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
      {courses.map((course) => (
        <Card key={course.id} variant="default">
          <Card.Header image={{ src: course.image, alt: course.title }} />
          <Card.Body>
            <div className="flex items-center gap-2 mb-2">
              <Badge variant="primary" size="sm">{course.subject}</Badge>
              <Badge variant="default" size="sm">{course.level}</Badge>
            </div>
            <h3 className="font-semibold text-lg mb-1">{course.title}</h3>
            <p className="text-sm text-gray-600 mb-3">{course.description}</p>
            <div className="flex items-center gap-2">
              <Avatar size="xs" name={course.instructor} />
              <span className="text-sm text-gray-500">{course.instructor}</span>
            </div>
          </Card.Body>
        </Card>
      ))}
    </div>
  );
}

Confirmation Dialog

A destructive action confirmation with proper focus management:

import { Dialog, Button } from "@libretexts/davis-react";

function DeleteConfirmation({ open, onClose, onConfirm, itemName }) {
  return (
    <Dialog open={open} onClose={onClose} size="sm">
      <Dialog.Header>
        <Dialog.Title>Delete {itemName}?</Dialog.Title>
        <Dialog.Description>
          This action cannot be undone. The item and all associated data
          will be permanently removed.
        </Dialog.Description>
      </Dialog.Header>
      <Dialog.Footer>
        <Button variant="outline" onClick={onClose}>
          Cancel
        </Button>
        <Button variant="destructive" onClick={onConfirm}>
          Delete
        </Button>
      </Dialog.Footer>
    </Dialog>
  );
}

Notification Feedback

Using the notification system for async operation feedback:

import { Button, useNotifications } from "@libretexts/davis-react";

function SaveButton({ onSave }) {
  const { addNotification } = useNotifications();

  const handleSave = async () => {
    try {
      await onSave();
      addNotification({
        variant: "success",
        title: "Saved",
        message: "Your changes have been saved.",
        duration: 4000,
      });
    } catch (error) {
      addNotification({
        variant: "error",
        title: "Save failed",
        message: "Something went wrong. Please try again.",
        duration: 6000,
      });
    }
  };

  return <Button onClick={handleSave}>Save Changes</Button>;
}

Wrap your app with NotificationsProvider:

import { NotificationsProvider } from "@libretexts/davis-react";

function App() {
  return (
    <NotificationsProvider position="top-right">
      <YourApp />
    </NotificationsProvider>
  );
}

Empty State

A centered empty state with call-to-action:

import { EmptyState, Button } from "@libretexts/davis-react";

function EmptyCoursesView() {
  return (
    <EmptyState
      title="No courses yet"
      description="Get started by creating your first course. You can add
        content, assignments, and invite students."
      action={
        <Button>Create Course</Button>
      }
    />
  );
}

Loading Patterns

Full-page loading

import { Spinner } from "@libretexts/davis-react";

function PageLoader() {
  return (
    <div className="flex items-center justify-center min-h-[400px]">
      <Spinner size="lg" />
    </div>
  );
}

Inline loading (button)

import { Button, Spinner } from "@libretexts/davis-react";

function SubmitButton({ loading }) {
  return (
    <Button disabled={loading}>
      {loading && <Spinner size="xs" color="white" />}
      {loading ? "Saving..." : "Save"}
    </Button>
  );
}

Skeleton loading (custom)

function CardSkeleton() {
  return (
    <div className="border border-gray-200 rounded-lg p-6 animate-pulse">
      <div className="h-4 bg-gray-200 rounded w-3/4 mb-4" />
      <div className="h-3 bg-gray-200 rounded w-full mb-2" />
      <div className="h-3 bg-gray-200 rounded w-5/6" />
    </div>
  );
}