Component Reference
API reference for all exported Sightline components.
For customization examples, see Customization Guide. For complete implementation patterns, see Examples & Recipes.
Core Components
MapEngine
The main map component that renders incidents.
import { MapEngine } from '@/core';
<MapEngine
incidents={incidents}
selectedIncident={selectedIncident}
onIncidentSelect={setSelectedIncident}
currentDate={currentDate}
sidebarOpen={isSidebarOpen}
rightSidebarOpen={isRightSidebarOpen}
renderMarker={(props) => <CustomMarker {...props} />}
renderPopup={(props) => <CustomPopup {...props} />}
renderCluster={(props) => <CustomCluster {...props} />}
/>Props
| Prop | Type | Description |
|---|---|---|
incidents | IncidentDisplay[] | Array of incidents to display |
selectedIncident | IncidentDisplay | null | Currently selected incident |
onIncidentSelect | (incident: IncidentDisplay | null) => void | Selection callback |
currentDate | Date | null | Current timeline date for filtering |
sidebarOpen | boolean | Whether left sidebar is open (for layout adjustment) |
rightSidebarOpen | boolean | Whether right sidebar is open (for layout adjustment) |
renderMarker | (props: MarkerRendererProps) => ReactNode | Custom marker renderer |
renderPopup | (props: PopupRendererProps) => ReactNode | Custom popup renderer |
renderCluster | (props: ClusterRendererProps) => ReactNode | Custom cluster renderer |
dataSources | DataSourceConfig[] | Data sources for color mapping |
fitBoundsKey | number | Change to trigger fit bounds |
recenterPopupKey | number | Change to trigger popup re-centering |
onViewportChange | (bounds: ViewportBounds) => void | Viewport change callback (bounds: north, south, east, west, zoom) |
onMapReady | () => void | Callback when map is fully loaded and ready |
DataProvider
Context provider for incident data and state management.
import { DataProvider } from '@/core';
<DataProvider>
<YourApp />
</DataProvider>Provides data and actions via the useData hook.
ThemeProvider
Context provider for theme management (included in DataProvider).
import { ThemeProvider } from '@/core';
<ThemeProvider>
<YourApp />
</ThemeProvider>Control Components
FilterSidebar
Sidebar with filter controls.
import { FilterSidebar } from '@/controls/filters';
<FilterSidebar
isOpen={isSidebarOpen}
filters={filters}
onFilterChange={updateFilter}
incidentCount={filteredIncidents.length}
countries={filterOptions.countries}
// ... additional filter option props
/>FilterSidebar requires many props for filter options. In most cases, use the pre-wired version from page.tsx which connects to the data context automatically.
TimelineControl
Timeline playback control.
import { TimelineControl } from '@/controls';
<TimelineControl />IncidentFeed
Scrollable incident list.
import { IncidentFeed } from '@/controls';
<IncidentFeed
incidents={filteredIncidents}
selectedIncident={selectedIncident}
onIncidentSelect={setSelectedIncident}
renderFeedItem={(props) => <CustomFeedItem {...props} />}
/>Props
| Prop | Type | Description |
|---|---|---|
incidents | IncidentDisplay[] | Incidents to display |
selectedIncident | IncidentDisplay | null | Currently selected incident |
onIncidentSelect | (incident: IncidentDisplay | null) => void | Selection callback |
currentDate | Date | null | Current timeline date |
renderFeedItem | (props: FeedItemRendererProps) => ReactNode | Custom item renderer |
dataSources | DataSourceConfig[] | Data sources for color mapping |
showHeader | boolean | Show feed header (default: true) |
initialSortBy | 'date' | 'sensitivity' | 'location' | Initial sort field |
initialSortDirection | 'asc' | 'desc' | Initial sort direction |
initialCount | number | Items to show initially (default: 20) |
pageSize | number | Items to load per page (default: 10) |
hasMore | boolean | Whether more data is available |
onLoadMore | () => void | Callback to load more data |
isLoading | boolean | Whether currently loading |
className | string | Additional CSS classes |
StatsPanel
Statistics display panel showing incident counts and metrics.
import { StatsPanel } from '@/controls';
<StatsPanel
incidents={filteredIncidents}
totalIncidents={incidents.length}
/>Props
| Prop | Type | Description |
|---|---|---|
incidents | IncidentDisplay[] | Required. Currently visible/filtered incidents |
totalIncidents | number | Required. Total incident count (before filtering) |
ThemeToggle
Theme switching control.
import { ThemeToggle } from '@/controls';
<ThemeToggle variant="icon-only" />
<ThemeToggle variant="button" size="sm" showLabel />
<ThemeToggle variant="dropdown" showLabel />Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'icon-only' | 'button' | 'dropdown' | 'button' | Toggle style |
size | 'sm' | 'md' | 'lg' | 'md' | Control size |
showLabel | boolean | false | Show text label |
className | string | — | Additional CSS classes |
SourceToggle
Data source toggle control for enabling/disabling data sources.
import { SourceToggle } from '@/controls';
<SourceToggle
sources={dataSources}
enabledIds={enabledSourceIds}
onToggle={(sourceId) => toggleSource(sourceId)}
incidentCounts={countsBySource}
/>Props
| Prop | Type | Description |
|---|---|---|
sources | DataSourceConfig[] | Required. Available data sources |
enabledIds | string[] | Required. IDs of enabled sources |
onToggle | (sourceId: string) => void | Required. Toggle callback |
incidentCounts | Record<string, number> | Incident count per source |
className | string | Additional CSS classes |
Example Renderers
Markers
import { DefaultMarker, PulseMarker } from '@/examples/renderers';
// Simple colored marker
<DefaultMarker incident={incident} isSelected={false} onClick={() => {}} />
// Animated pulsing marker
<PulseMarker incident={incident} isSelected={false} onClick={() => {}} />Popups
import { CompactPopup, StandardPopup, FullViewModal } from '@/examples/renderers';
// Minimal popup
<CompactPopup incident={incident} onClose={() => {}} />
// Full details popup
<StandardPopup incident={incident} onClose={() => {}} />
// Modal with all sections
<FullViewModal incident={incident} onClose={() => {}} />Feed Items
import { DefaultFeedItem, CompactFeedItem, MediaPreviewFeedItem } from '@/examples/renderers';
// Standard feed item
<DefaultFeedItem incident={incident} isSelected={false} onClick={() => {}} />
// Minimal height
<CompactFeedItem incident={incident} isSelected={false} onClick={() => {}} />
// With thumbnail
<MediaPreviewFeedItem incident={incident} isSelected={false} onClick={() => {}} />Clusters
import { DefaultCluster } from '@/examples/renderers';
// React clustering mode only
<DefaultCluster cluster={cluster} incidents={incidents} onClick={() => {}} />Hooks
useData
Access incident data and state.
import { useData } from '@/core';
function MyComponent() {
const {
// Data
incidents, // All loaded incidents
filteredIncidents, // After filters applied
// Selection
selectedIncident,
setSelectedIncident,
// Filters
filters,
updateFilter,
resetFilters, // Reset all filters to defaults
// Data sources
dataSources,
enabledSourceIds, // Array of enabled source IDs
toggleSource,
// Loading state
isLoading,
error,
// Actions
refresh, // Refresh data from all sources
} = useData();
}useTheme
Access theme state and controls.
import { useTheme } from '@/core';
function MyComponent() {
const {
theme, // Current setting: 'light' | 'dark' | 'system'
resolvedTheme, // Actual theme: 'light' | 'dark'
setTheme, // Set theme
toggleTheme, // Toggle between light/dark
} = useTheme();
}useMapUIState
Manage map UI state including sidebars, timeline, and modal state.
import { useMapUIState } from '@/hooks';
const {
// Sidebar state
sidebarOpen,
setSidebarOpen,
toggleSidebar, // (hasPopupOpen?: boolean) => void
rightSidebarOpen,
setRightSidebarOpen,
toggleRightSidebar, // (hasPopupOpen?: boolean) => void
// Timeline state
timelineOpen,
setTimelineOpen,
toggleTimeline,
isPlaying,
setIsPlaying,
togglePlayback,
// Full view modal
fullViewIncident,
setFullViewIncident,
openFullView,
closeFullView,
// Map refit triggers
fitBoundsKey,
triggerFitBounds,
recenterPopupKey,
triggerRecenterPopup,
} = useMapUIState();The toggleSidebar and toggleRightSidebar functions accept an optional hasPopupOpen parameter. When true, the map recenters on the current popup instead of refitting all incidents — this provides smoother UX when a popup is open.
useRendererConfig
Access renderer configuration.
import { useRendererConfig } from '@/hooks';
const {
markerStyle,
popupStyle,
feedStyle,
} = useRendererConfig();Renderer Props
MarkerRendererProps
interface MarkerRendererProps {
/** Incidents at this location (may be multiple stacked) */
incidents: IncidentDisplay[];
/** Whether any incident at this location is selected */
isSelected: boolean;
/** Whether any incident is newly appeared (for animation) */
isNew: boolean;
/** Callback when marker is clicked */
onClick: () => void;
/** Computed marker size based on incident count and sensitivity */
size: number;
/** Primary color for this marker (highest sensitivity color) */
color: string;
}PopupRendererProps
interface PopupRendererProps {
/** The incident being displayed */
incident: IncidentDisplay;
/** Close the popup */
onClose: () => void;
/** Data source this incident belongs to */
dataSource?: DataSourceConfig;
/** Open full view modal for this incident (optional) */
onViewDetails?: (incident: IncidentDisplay) => void;
}FeedItemRendererProps
interface FeedItemRendererProps {
/** The incident being displayed */
incident: IncidentDisplay;
/** Whether this item is currently selected */
isSelected: boolean;
/** Whether this item is highlighted (e.g., newly appeared) */
isHighlighted: boolean;
/** Callback when item is clicked */
onClick: () => void;
/** Data source this incident belongs to */
dataSource?: DataSourceConfig;
}ClusterRendererProps
interface ClusterRendererProps {
/** Number of incidents in this cluster */
pointCount: number;
/** Alias for pointCount (for convenience) */
count: number;
/** Total number of points that could be in cluster */
pointCountAbbreviated: string;
/** Incidents in this cluster (if available) */
incidents?: IncidentDisplay[];
/** Cluster coordinates [longitude, latitude] */
coordinates: [number, number];
/** Callback to expand/zoom into cluster */
onExpand: () => void;
/** Callback when cluster is clicked */
onClick: () => void;
/** Computed cluster size */
size: number;
/** Primary color for this cluster (highest sensitivity color) */
color: string;
}UI Components
InfoPopover
Lightweight hover-triggered popover for displaying informational content.
import { InfoPopover } from '@/ui';
<InfoPopover
content="This is helpful information."
position="bottom"
maxWidth={280}
>
<span>Hover me</span>
</InfoPopover>
// With JSX content
<InfoPopover
content={
<div>
<p className="font-medium">Title</p>
<p className="text-muted-foreground text-xs">Description text.</p>
</div>
}
showIcon
>
<span>More info</span>
</InfoPopover>Props
| Prop | Type | Default | Description |
|---|---|---|---|
content | ReactNode | — | Required. Content shown in the popover |
children | ReactNode | — | Required. Trigger element users hover over |
position | 'top' | 'bottom' | 'left' | 'right' | 'bottom' | Popover position relative to trigger (left-aligned) |
maxWidth | number | 280 | Width of popover in pixels |
showDelay | number | 200 | Delay before showing popover (ms) |
hideDelay | number | 150 | Delay before hiding popover (ms) |
showIcon | boolean | false | Show info icon next to children |
className | string | — | Additional CSS classes for wrapper |
The popover is left-aligned with the trigger element and includes an arrow pointer. For top and bottom positions, the popover aligns to the left edge of the trigger with spacing (mt-3 or mb-3).
DemoBadge
Pre-configured demo indicator badge with built-in disclaimer popover.
import { DemoBadge } from '@/ui';
// Default disclaimer
<DemoBadge />
// Custom disclaimer text
<DemoBadge disclaimer="Sample data for testing purposes only." />
// With custom styling
<DemoBadge className="text-sm" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
disclaimer | string | Default message | Custom disclaimer text |
className | string | — | Additional CSS classes for the badge |
The default disclaimer reads: "This application displays sample data for demonstration purposes only. The incidents shown are fictional and do not represent real events."
Loader
Modern loading animations with multiple variants.
import { Loader, InlineLoader, LoaderOverlay } from '@/ui';
// Basic loader with variant
<Loader variant="orbit" size="lg" />
<Loader variant="dots" size="sm" />
<Loader variant="spinner" size="md" />
// Inline loader for buttons/text
<InlineLoader size="sm" />
// Overlay loader for cards/sections
<LoaderOverlay visible={isLoading} message="Loading..." variant="orbit" />Loader Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'spinner' | 'dots' | 'orbit' | 'pulse' | 'bars' | 'spinner' | Animation style |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Loader size |
className | string | — | Additional CSS classes |
label | string | 'Loading' | Accessible label for screen readers |
Variant Descriptions
| Variant | Description |
|---|---|
spinner | Gradient arc that rotates with a fade trail |
dots | Three dots that pulse in sequence |
orbit | Single dot orbiting around a track ring |
pulse | Expanding concentric rings |
bars | Equalizer-style bars that animate up/down |
FullPageLoading
Full-page loading state with configurable loader.
import { FullPageLoading } from '@/ui';
<FullPageLoading
message="Initializing..."
variant="orbit"
size="lg"
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
message | string | 'Loading...' | Message displayed below loader |
variant | LoaderVariant | 'orbit' | Loader animation variant |
size | LoaderSize | 'lg' | Loader size |
Related
- Types Reference — Complete TypeScript types
- Customization Guide — Custom renderers and theming
- Examples & Recipes — Real-world component examples
- Configuration Reference — Control configuration
- Accessibility Guide — Building accessible components