Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps
npm install @djangocfg/ui-toolsHeavy React tools with lazy loading (React.lazy + Suspense).
No Next.js dependencies — works with Electron, Vite, CRA, and any React environment.
Part of DjangoCFG — modern Django framework for production-ready SaaS applications.
``bash`
pnpm add @djangocfg/ui-tools
This package contains heavy components that are loaded lazily to keep your initial bundle small. Each tool is loaded only when used.
| Package | Use Case |
|---------|----------|
| @djangocfg/ui-core | Lightweight UI components (60+ components) |@djangocfg/ui-tools
| | Heavy tools with lazy loading |@djangocfg/ui-nextjs
| | Next.js apps (extends ui-core) |
| Tool | Bundle Size | Description |
|------|-------------|-------------|
| Gallery | ~50KB | Image/video gallery with carousel, grid, lightbox |Map
| | ~800KB | MapLibre GL maps with markers, clusters, layers |Mermaid
| | ~800KB | Diagram rendering |PrettyCode
| | ~500KB | Code syntax highlighting |OpenapiViewer
| | ~400KB | OpenAPI schema viewer & playground |JsonForm
| | ~300KB | JSON Schema form generator |LottiePlayer
| | ~200KB | Lottie animation player |AudioPlayer
| | ~200KB | Audio player with WaveSurfer.js |VideoPlayer
| | ~150KB | Professional video player with Vidstack |JsonTree
| | ~100KB | JSON visualization with modes (full/compact/inline) |ImageViewer
| | ~50KB | Image viewer with zoom/pan/rotate |CronScheduler
| | ~15KB | Cron expression builder with intuitive UI |
For better bundle optimization, use subpath imports:
`tsx
// Only loads Gallery (~50KB instead of full package)
import { Gallery, GalleryLightbox } from '@djangocfg/ui-tools/gallery';
// Only loads Map (~800KB)
import { MapContainer, MapMarker } from '@djangocfg/ui-tools/map';
`
Full-featured image/video gallery with carousel, grid view, and fullscreen lightbox.
`tsx
import { Gallery } from '@djangocfg/ui-tools/gallery';
const images = [
{ id: '1', src: '/photo1.jpg', alt: 'Photo 1' },
{ id: '2', src: '/photo2.jpg', alt: 'Photo 2' },
{ id: '3', src: '/video.mp4', alt: 'Video', type: 'video' },
];
function PhotoGallery() {
return (
previewMode="carousel" // or "grid"
showThumbnails
enableLightbox
aspectRatio={16 / 9}
/>
);
}
`
| Component | Description |
|-----------|-------------|
| Gallery | Complete gallery with carousel/grid + lightbox |GalleryCompact
| | Minimal carousel for cards |GalleryGrid
| | Grid layout with "show more" badge |GalleryLightbox
| | Fullscreen lightbox viewer |GalleryCarousel
| | Embla-based carousel |GalleryThumbnails
| | Thumbnail strip navigation |
| Hook | Description |
|------|-------------|
| useGallery | Gallery state management |useSwipe
| | Touch swipe gestures |usePinchZoom
| | Pinch-to-zoom for mobile |usePreloadImages
| | Image preloading |
MapLibre GL maps with React components for markers, clusters, popups, and custom layers.
`tsx
import { MapContainer, MapMarker, MapPopup } from '@djangocfg/ui-tools/map';
const markers = [
{ id: '1', lat: 37.7749, lng: -122.4194, title: 'San Francisco' },
{ id: '2', lat: 34.0522, lng: -118.2437, title: 'Los Angeles' },
];
function LocationMap() {
return (
style="streets"
>
{markers.map((m) => (
))}
);
}
`
| Component | Description |
|-----------|-------------|
| MapContainer | Main map container with controls |MapMarker
| | Custom marker with React children |MapPopup
| | Popup attached to marker |MapCluster
| | Clustered markers with spiderfy |MapSource
| / MapLayer | Custom GeoJSON layers |MapControls
| | Navigation controls |MapLegend
| | Map legend component |LayerSwitcher
| | Toggle map layers |DrawControl
| | Drawing tools (optional) |GeocoderControl
| | Search/geocoding (optional) |
When multiple markers are at the same location, use offsetOverlappingMarkers to spread them out before rendering:
`tsx
import { MapCluster } from '@djangocfg/ui-tools/map';
import { offsetOverlappingMarkers } from '@djangocfg/ui-tools/map';
// Pre-process data to offset overlapping points
const processedData = useMemo(() => {
const markers = geojson.features.map((f, i) => ({
id: f.properties?.id || point-${i},
longitude: f.geometry.coordinates[0],
latitude: f.geometry.coordinates[1],
data: f.properties,
}));
const offsetMarkers = offsetOverlappingMarkers(markers, {
spiralRadius: 0.0003, // ~30m spread
});
return {
type: 'FeatureCollection',
features: offsetMarkers.map((m) => ({
type: 'Feature',
properties: m.data,
geometry: { type: 'Point', coordinates: [m.longitude, m.latitude] },
})),
};
}, [geojson]);
`
| Utility | Description |
|---------|-------------|
| offsetOverlappingMarkers(markers, options) | Spread overlapping markers using Fermat spiral |hasOverlappingMarkers(markers)
| | Check if any markers overlap |getOverlapStats(markers)
| | Get statistics about overlapping markers |
| Hook | Description |
|------|-------------|
| useMap | Access map instance |useMapControl
| | Programmatic map control |useMarkers
| | Marker management |useMapEvents
| | Map event handlers |useMapViewport
| | Viewport state |useMapLayers
| | Layer management |
`tsx
import { MAP_STYLES, getMapStyle } from '@djangocfg/ui-tools/map';
// Available styles: streets, satellite, dark, light, terrain
`
`tsx`
import {
createClusterLayers,
createPointLayer,
createPolygonLayer,
createLineLayer,
} from '@djangocfg/ui-tools/map';
`tsx
import { VideoPlayer } from '@djangocfg/ui-tools';
poster="/thumbnail.jpg"
autoplay={false}
/>
`
`tsx
import { HybridAudioPlayer } from '@djangocfg/ui-tools';
showWaveform
/>
`
`tsx
import { Mermaid } from '@djangocfg/ui-tools';
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Action]
B -->|No| D[End]} />`
`tsx
import { PrettyCode } from '@djangocfg/ui-tools';
}`
language="typescript"
/>
`tsx
import { JsonSchemaForm } from '@djangocfg/ui-tools';
const schema = {
type: 'object',
properties: {
name: { type: 'string', title: 'Name' },
email: { type: 'string', format: 'email', title: 'Email' },
},
};
onSubmit={(data) => console.log(data)}
/>
`
| Component | Description |
|-----------|-------------|
| Markdown | Markdown renderer with GFM support |
| Store | Description |
|-------|-------------|
| useMediaCacheStore | Media caching for video/audio players |
Compact cron expression builder with intuitive UI. Supports Daily, Weekly, Monthly schedules and custom cron expressions.
`tsx
import { CronScheduler } from '@djangocfg/ui-tools';
onChange={(cron) => console.log(cron)}
showPreview
allowCopy
/>
`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| value | string | - | Cron expression (Unix 5-field format) |onChange
| | (cron: string) => void | - | Callback when schedule changes |defaultType
| | 'daily' \| 'weekly' \| 'monthly' \| 'custom' | 'daily' | Initial schedule type |showPreview
| | boolean | true | Show human-readable preview |showCronExpression
| | boolean | true | Show cron expression in preview |allowCopy
| | boolean | false | Enable copy to clipboard |timeFormat
| | '12h' \| '24h' | '24h' | Time display format |disabled
| | boolean | false | Disable all interactions |
For custom compositions, use the context hooks:
`tsx`
import {
CronSchedulerProvider,
useCronType,
useCronTime,
useCronWeekDays,
useCronMonthDays,
useCronPreview,
} from '@djangocfg/ui-tools';
`tsx`
import {
buildCron, // State → Cron expression
parseCron, // Cron → State
humanizeCron, // Cron → Human description
isValidCron, // Validate cron syntax
} from '@djangocfg/ui-tools';
JSON visualization with three display modes:
`tsx
import { LazyJsonTree } from '@djangocfg/ui-tools';
// Full mode (default) - with toolbar (Expand All, Copy, Download)
// Compact mode - no toolbar, subtle border
// Inline mode - minimal, no border, for embedding
`
| Mode | Toolbar | Border | Use Case |
|------|---------|--------|----------|
| full | Yes | Yes | Standalone viewer |compact
| | No | Subtle | Cards, panels |inline
| | No | No | Embedded in lists, logs |
All heavy tools have unified lazy-loaded versions with built-in Suspense fallbacks:
`tsx
import {
LazyMapContainer, // ~800KB
LazyMermaid, // ~800KB
LazyPrettyCode, // ~500KB
LazyOpenapiViewer, // ~400KB
LazyJsonSchemaForm, // ~300KB
LazyLottiePlayer, // ~200KB
LazyHybridAudioPlayer, // ~200KB
LazyVideoPlayer, // ~150KB
LazyJsonTree, // ~100KB
LazyImageViewer, // ~50KB
LazyCronScheduler, // ~15KB
} from '@djangocfg/ui-tools';
// Just use them - no Suspense wrapper needed!
`
Create your own lazy components with createLazyComponent:
`tsx
import { createLazyComponent, CardLoadingFallback } from '@djangocfg/ui-tools';
const LazyMyComponent = createLazyComponent(
() => import('./MyHeavyComponent'),
{
displayName: 'LazyMyComponent',
fallback:
}
);
`
Built-in fallback components for different use cases:
| Component | Use Case |
|-----------|----------|
| LoadingFallback | Generic spinner with optional text |CardLoadingFallback
| | Card-styled loading with title |MapLoadingFallback
| | Map-specific with location icon |Spinner
| | Simple spinning loader |LazyWrapper
| | Suspense wrapper with configurable fallback |
- React >= 18 or >= 19
- Tailwind CSS >= 4
- Zustand >= 5
- @djangocfg/ui-core (peer dependency)
`bashFor drawing tools
pnpm add @mapbox/mapbox-gl-draw
MIT