Skip to main content

Components Library

Overview

Scoriet uses a combination of custom components and PrimeReact components to build the user interface. This document covers the key reusable components and patterns used throughout the application.

UI Component Libraries

PrimeReact Components

PrimeReact provides pre-built, styled components:

import { Button } from 'primereact/button';
import { InputText } from 'primereact/inputtext';
import { DataTable } from 'primereact/datatable';
import { Dialog } from 'primereact/dialog';
import { Dropdown } from 'primereact/dropdown';
import { Toast } from 'primereact/toast';
// ... and many more

Commonly Used Components:

ComponentPurposeExample
ButtonClickable button<Button label="Click" onClick={...} />
InputTextSingle-line text input<InputText value={val} onChange={...} />
InputTextareaMulti-line text<InputTextarea rows={5} />
InputNumberNumeric input<InputNumber value={num} />
DropdownSelect dropdown<Dropdown options={items} onChange={...} />
CalendarDate picker<Calendar value={date} onChange={...} />
CheckboxCheckbox control<Checkbox checked={val} onChange={...} />
DataTableTable display<DataTable value={data}><Column field="name" /></DataTable>
DialogModal dialog<Dialog visible={show} onHide={...}>content</Dialog>
ToastNotifications<Toast ref={toast} />
MenuContext/dropdown menu<Menu model={items} popup ref={menu} />
CardContent card<Card><p>Content</p></Card>
SplitterResizable panels<Splitter><SplitterPanel>Left</SplitterPanel></Splitter>
TabViewTabbed content<TabView><TabPanel header="Tab 1">Content</TabPanel></TabView>
MessageAlert message<Message severity="error">Error text</Message>
ProgressSpinnerLoading spinner<ProgressSpinner />
TagBadge/label<Tag value="status" />
BadgeNumeric badge<Badge value="5" />

Lucide Icons

import {
Plus, Trash2, Edit, Settings,
ChevronDown, Search, Mail
} from 'lucide-react';

<Plus size={20} />
<Trash2 size={20} className="text-red-500" />

PrimeIcons

Built into PrimeReact:

<i className="pi pi-home"></i>
<i className="pi pi-search" style={{ fontSize: '20px' }}></i>

Custom Components

TopBar

Top navigation and global controls.

// Location: resources/js/Components/TopBar.tsx
<TopBar
onProjectChange={(project) => setProject(project)}
onSettings={() => openSettingsPanel()}
/>

Features:

  • Project selector
  • User menu
  • Settings access
  • Help/documentation links

ClassicTreeView

Hierarchical tree navigation component.

// Location: resources/js/Components/ClassicTreeView.tsx
interface TreeNode {
id: string | number;
label: string;
icon?: string;
children?: TreeNode[];
data?: any;
}

<ClassicTreeView
nodes={projectTree}
onNodeSelect={(node) => handleSelect(node)}
expandedKeys={expanded}
onToggle={(keys) => setExpanded(keys)}
/>

Features:

  • Expand/collapse nodes
  • Custom icons
  • Selection handling
  • Lazy loading support
  • Search/filter

AuthModalManager

Orchestrates authentication modals.

// Location: resources/js/Components/AuthModals/AuthModalManager.tsx
<AuthModalManager
initialModal="login"
onAuthSuccess={() => navigate('/app')}
/>

Available Modals:

  • LoginModal
  • RegisterModal
  • ForgotPasswordModal
  • ResetPasswordModal
  • TwoFactorVerifyDialog
  • ProfileModal
  • PlanModal
  • CreditPurchaseModal

Dialog (PrimeReact)

const [visible, setVisible] = useState(false);

<Dialog
visible={visible}
onHide={() => setVisible(false)}
header="Dialog Title"
modal
>
Dialog content here
</Dialog>

<Button
label="Open"
onClick={() => setVisible(true)}
/>

Confirm Dialog

import { confirmDialog } from 'primereact/confirmdialog';
import { ConfirmDialog } from 'primereact/confirmdialog';

const confirm = () => {
confirmDialog({
message: 'Are you sure?',
header: 'Confirmation',
icon: 'pi pi-exclamation-triangle',
accept: () => handleDelete(),
reject: () => { }
});
};

return (
<>
<ConfirmDialog />
<Button onClick={confirm} label="Delete" />
</>
);

Form Components

InputField Pattern

import { InputText } from 'primereact/inputtext';
import { useForm, Controller } from 'react-hook-form';

const { control, handleSubmit, formState: { errors } } =
useForm<FormData>({
defaultValues: { name: '' }
});

<Controller
name="name"
control={control}
rules={{
required: 'Name is required',
minLength: { value: 3, message: 'Min 3 chars' }
}}
render={({ field }) => (
<div>
<label>Name</label>
<InputText
{...field}
className={errors.name ? 'p-invalid' : ''}
/>
{errors.name && (
<small className="p-error">{errors.name.message}</small>
)}
</div>
)}
/>
interface Option {
label: string;
value: any;
}

const options: Option[] = [
{ label: 'Option 1', value: 1 },
{ label: 'Option 2', value: 2 }
];

<Controller
name="status"
control={control}
render={({ field }) => (
<Dropdown
{...field}
options={options}
placeholder="Select..."
optionLabel="label"
optionValue="value"
/>
)}
/>

Checkbox Field

<Controller
name="isActive"
control={control}
render={({ field }) => (
<Checkbox
{...field}
inputId="active"
label="Active"
/>
)}
/>

Data Display Components

DataTable

import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';

const [data, setData] = useState([]);
const [selectedRows, setSelectedRows] = useState([]);

<DataTable
value={data}
selection={selectedRows}
onSelectionChange={(e) => setSelectedRows(e.value)}
selectionMode="checkbox"
paginator
rows={10}
dataKey="id"
sortMode="multiple"
>
<Column
selectionMode="multiple"
headerStyle={{ width: '3rem' }}
/>
<Column field="name" header="Name" sortable />
<Column field="email" header="Email" sortable />
<Column
header="Actions"
body={(row) => (
<>
<Button onClick={() => editRow(row)}>Edit</Button>
<Button onClick={() => deleteRow(row)}>Delete</Button>
</>
)}
/>
</DataTable>

Features:

  • Sorting (single/multiple)
  • Pagination
  • Selection (single/checkbox)
  • Filtering
  • Virtual scrolling
  • Resizable columns
  • Custom column templates
  • Expandable rows

Virtual Scrolling

<DataTable
value={largeDataset}
scrollable
scrollHeight="400px"
virtualScroll
rows={20}
>
<Column field="name" header="Name" />
</DataTable>
import { Menu } from 'primereact/menu';

const menuItems = [
{ label: 'Save', icon: 'pi pi-save', command: () => save() },
{ label: 'Delete', icon: 'pi pi-trash', command: () => delete() },
{ separator: true },
{ label: 'Help', icon: 'pi pi-question', command: () => help() }
];

const menu = useRef(null);

<Menu model={menuItems} popup ref={menu} />
<Button
onClick={(e) => menu.current.toggle(e)}
icon="pi pi-bars"
/>

Toolbar

import { Toolbar } from 'primereact/toolbar';

<Toolbar
left={<Button label="New" icon="pi pi-plus" />}
right={<Button label="Delete" icon="pi pi-trash" />}
/>

Layout Components

Splitter

import { Splitter, SplitterPanel } from 'primereact/splitter';

<Splitter layout="horizontal" style={{ height: '400px' }}>
<SplitterPanel size={30} minSize={20}>
Left Panel
</SplitterPanel>
<SplitterPanel size={70} minSize={30}>
Right Panel
</SplitterPanel>
</Splitter>

TabView

import { TabView, TabPanel } from 'primereact/tabview';

const [activeIndex, setActiveIndex] = useState(0);

<TabView activeIndex={activeIndex} onTabChange={(e) => setActiveIndex(e.index)}>
<TabPanel header="General">
General content
</TabPanel>
<TabPanel header="Advanced">
Advanced content
</TabPanel>
</TabView>

Notification Components

Toast

import { Toast } from 'primereact/toast';

const toast = useRef(null);

const showSuccess = () => {
toast.current?.show({
severity: 'success',
summary: 'Success',
detail: 'Operation successful',
life: 3000
});
};

return (
<>
<Toast ref={toast} />
<Button onClick={showSuccess} label="Show Success" />
</>
);

Severity Levels:

  • success
  • info
  • warn
  • error

Message

import { Message } from 'primereact/message';

<Message severity="error" text="An error occurred" />
<Message severity="info" text="Information message" />
<Message severity="success" text="Success message" />
<Message severity="warn" text="Warning message" />

Diagram Components

ReactFlow (XyFlow)

Used for visual designers (forms, reports, workflows):

import {
ReactFlow,
Background,
Controls,
MiniMap,
useNodesState,
useEdgesState,
BackgroundVariant
} from '@xyflow/react';

const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
>
<Background variant={BackgroundVariant.Dots} />
<Controls />
<MiniMap />
</ReactFlow>

Features:

  • Node drag-and-drop
  • Custom node types
  • Edge connections
  • Zoom and pan
  • Mini map
  • Grid background
  • Selection

Drag & Drop Components

dnd-kit

Used for sortable lists and kanban boards:

import {
DndContext,
DragOverlay,
closestCorners,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';

import {
SortableContext,
verticalListSortingStrategy,
useSortable,
} from '@dnd-kit/sortable';

const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor)
);

function SortableItem({ id }) {
const { attributes, listeners, setNodeRef, transform } = useSortable({ id });

return (
<div
ref={setNodeRef}
style={{ transform: CSS.Transform.toString(transform) }}
{...attributes}
{...listeners}
>
Item {id}
</div>
);
}

<DndContext
sensors={sensors}
collisionDetection={closestCorners}
onDragEnd={handleDragEnd}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items.map(id => <SortableItem key={id} id={id} />)}
</SortableContext>
<DragOverlay>
{activeId ? <SortableItem id={activeId} /> : null}
</DragOverlay>
</DndContext>

Editor Components

CodeMirror

For SQL, JavaScript, and code editing:

import CodeMirror from '@uiw/react-codemirror';
import { sql } from '@codemirror/lang-sql';

<CodeMirror
value={code}
onChange={setCode}
extensions={[sql()]}
height="400px"
/>

Quill (Rich Text Editor)

For rich text editing:

import Quill from 'quill';

// Initialized in component
const quillRef = useRef(null);
const [value, setValue] = useState('');

useEffect(() => {
const quill = new Quill(quillRef.current, {
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
]
},
theme: 'snow'
});

quill.on('text-change', () => {
setValue(quill.getContents());
});
}, []);

return <div ref={quillRef} />;

Icon Components

FlagIcon, CountryFlag, CSSFlag

For language/country selection:

import FlagIcon from '@/Components/FlagIcon';
import CountryFlag from '@/Components/CountryFlag';
import CSSFlag from '@/Components/CSSFlag';

<FlagIcon countryCode="US" />
<CountryFlag code="DE" />
<CSSFlag code="FR" />

LanguageSelector

Pre-built language selection component:

import LanguageSelector from '@/Components/LanguageSelector';

<LanguageSelector
onLanguageChange={(lang) => setLanguage(lang)}
/>

Custom Patterns

Loading State

const [loading, setLoading] = useState(false);

return (
<>
{loading && <ProgressSpinner />}
{!loading && <div>Content</div>}
</>
);

Error Boundary

import ErrorFallback from '@/Components/ErrorFallback';
import { ErrorBoundary } from 'react-error-boundary';

<ErrorBoundary FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>

useToast Hook

import { useToast } from '@/contexts/ToastContext';

const toast = useToast();

const handleSave = async () => {
try {
await save();
toast.showSuccess('Saved successfully');
} catch (error) {
toast.showError('Failed to save');
}
};

useTheme Hook

import { useTheme } from '@/contexts/ThemeContext';

const { colors, isDarkMode, setIsDarkMode } = useTheme();

return (
<div style={{ backgroundColor: colors.background }}>
<button onClick={() => setIsDarkMode(!isDarkMode)}>
Toggle Theme
</button>
</div>
);

Component Best Practices

  1. Use TypeScript - Define prop interfaces
  2. Prop Drilling Avoidance - Use contexts for global state
  3. Memoization - React.memo() for expensive components
  4. Error Handling - ErrorBoundary + try/catch
  5. Loading States - Always show progress during async ops
  6. Accessibility - Use semantic HTML, ARIA labels
  7. Responsive Design - Mobile-first approach
  8. Testing - Unit tests for components

Next Steps