This document lists the reusable components available in the client codebase.
Material Symbols icon wrapper.
import Icon from './components/Icon/Icon'
<Icon name="check_circle" />
<Icon name="edit" filled />
<Icon name="menu" className="custom-class" />Props:
name(required): Material Symbols icon namevariant: 'outlined' (default) | 'rounded' | 'sharp'filled: boolean - use filled variantclassName: additional CSS classes...props: additional props passed through (onClick, etc.)
Styled button with variants.
import Button from './components/Button/Button'
<Button variant="default">Primary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Delete</Button>
<Button size="sm" icon="add">Small with icon</Button>Props:
variant: 'default' | 'outline' | 'ghost' | 'destructive'size: 'sm' | 'md' | 'lg'icon: icon name to prependdisabled: booleanclassName: additional CSS classes
Small label/tag component.
import Badge from './components/Badge/Badge'
<Badge>Default</Badge>
<Badge variant="outline">Outline</Badge>Progress bar component.
import Progress from './components/Progress/Progress'
<Progress value={75} />
<Progress value={50} className="custom" />Props:
value: 0-100 percentageclassName: additional CSS classes
Base card component with variants and padding options.
import Card from './components/Card/Card'
<Card>Default card</Card>
<Card variant="outline">Outline card</Card>
<Card padding="lg">Large padding</Card>
<Card as="button" interactive onClick={handleClick}>Clickable card</Card>
<Card as="article" className="article-card">Semantic card</Card>Props:
variant: 'default' | 'outline'padding: 'sm' | 'md' | 'lg' | 'xl'interactive: boolean - adds hover effects and cursor pointeras: HTML element or component to render as ('div', 'button', 'article', etc.)onClick: click handler (useful with interactive)className: additional CSS classes
Reusable modal dialog with overlay, header, body, and footer.
import Modal from './components/Modal/Modal'
<Modal
isOpen={showModal}
onClose={() => setShowModal(false)}
title="Modal Title"
size="lg"
footer={<Button onClick={save}>Save</Button>}
>
<p>Modal content goes here</p>
</Modal>Props:
isOpen: boolean - controls visibilityonClose: callback when modal should closetitle: modal header titlesize: 'sm' | 'md' | 'lg' | 'xl'footer: JSX for footer actionsclassName: additional CSS classes
Positioned popover anchored to a rect, constrained within a container.
import Popover from './components/Popover/Popover'
// Get selection rect
const rect = window.getSelection().getRangeAt(0).getBoundingClientRect()
<Popover
isOpen={showPopover}
onClose={() => setShowPopover(false)}
anchorRect={rect}
containerRef={readerContentRef}
placement="top"
>
<div className="popover-toolbar">
<button onClick={handleAction}>Action</button>
</div>
</Popover>Props:
isOpen: boolean - controls visibilityonClose: callback when popover should closeanchorRect: DOMRect or{top, left, width, height, right, bottom}to position relative tocontainerRef: ref to container element (popover stays within bounds)placement: 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'right'offset: gap between anchor and popover (default: 8)className: additional CSS classes
CSS classes for content:
.popover-toolbar: horizontal button row.popover-content: padded content wrapper.popover-divider: vertical divider between button groups
Main app layout with sidebar/mobile nav.
import { Layout } from './components/Layout/Layout'
<Layout>
{/* Page content */}
</Layout>Desktop navigation sidebar (used internally by Layout).
Mobile bottom navigation (used internally by Layout).
Wrapper that redirects unauthenticated users to login.
import RequireAuth from './components/RequireAuth/RequireAuth'
<RequireAuth>
<ProtectedContent />
</RequireAuth>Catches React errors and displays fallback UI.
import ErrorBoundary from './components/ErrorBoundary/ErrorBoundary'
<ErrorBoundary>
<ComponentThatMightError />
</ErrorBoundary>Styled dropdown select wrapper.
import Select from './components/Select/Select'
<Select
options={[{ value: 'a', label: 'Option A' }]}
value={selected}
onChange={setSelected}
placeholder="Choose..."
/>Multi-select dropdown with search and optional inline creation.
import Combobox from './components/Combobox/Combobox'
<Combobox
options={[{ value: 'tag1', label: 'Tag 1' }]}
selected={selectedTags}
onSelect={toggleTag}
placeholder="Select tags..."
onCreate={(name) => createTag(name)}
/>Props:
options: array of{ value, label }objectsselected: array of selected valuesonSelect: callback when option is selected/deselectedplaceholder: placeholder textonCreate: callback to create new item (shows "Create [query]" option when provided)
Rich text editor using Quill.js. Uncontrolled component with ref for accessing Quill instance.
import { useRef } from 'preact/hooks'
import QuillEditor from './components/QuillEditor/QuillEditor'
const editorRef = useRef(null)
<QuillEditor
ref={editorRef}
defaultValue="<p>Initial content</p>"
placeholder="Start typing..."
onTextChange={(delta, oldDelta, source) => console.log('Changed')}
/>
// Access content: editorRef.current?.root.innerHTMLProps:
defaultValue: initial HTML contentplaceholder: placeholder textreadOnly: booleanmodules: Quill modules config (toolbar, etc.)boundsRef: ref to container element for tooltip positioning (prevents clipping in modals)onTextChange: callback on content changeonSelectionChange: callback on selection changeclassName: additional CSS classes