39948-vm/frontend/docs/components-module.md
2026-07-03 16:11:24 +02:00

1080 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Frontend Components Module
## Overview
The Components module contains **194 TypeScript files** that provide the React component library for the Tour Builder Platform. Components are organized by domain, function, and factory patterns to maximize reuse.
**Location:** `frontend/src/components/`
---
## Architecture Diagram
```
frontend/src/components/
├── Entity Components (13 directories, 52+ files)
│ ├── Users/ # TableUsers, configureUsersCols, CardUsers, ListUsers
│ ├── Projects/ # TableProjects, configureProjectsCols, ...
│ ├── Assets/ # TableAssets, configureAssetsCols, useAssetUploader
│ ├── Roles/ # TableRoles, configureRolesCols, ...
│ └── ... (9 more entity directories)
├── Uploaders/
│ └── UploadService.js # Chunked upload with MIME validation
├── Factory Components
│ ├── Factory/createTableComponent.tsx # Table component generator
│ └── DataGrid/configBuilderFactory.tsx # Column config generator
├── Generic Components
│ ├── Generic/GenericTable.tsx # Base table with DataGrid
│ └── Generic/GenericFormField.tsx # Form field wrapper
├── Constructor Components (15 files)
│ ├── CanvasElement.tsx # Canvas element rendering
│ ├── ElementEditorPanel.tsx # Element settings sidebar
│ ├── ConstructorControlsPanel.tsx # Editor controls
│ └── ...
├── ElementSettings (23 files)
│ ├── CommonSettingsSection.tsx # Position, timing
│ ├── NavigationSettingsSection.tsx # Navigation links
│ ├── StyleSettingsSection.tsx # CSS properties
│ └── ... (Full + Compact variants, types, hooks)
├── UiElements (16 files)
│ ├── UiElementRenderer.tsx # Unified element renderer
│ ├── shared/useElementWrapperStyle.ts # Shared styling hook
│ ├── GalleryCarouselOverlay.tsx # Gallery/Carousel overlay component
│ └── elements/ (10 per-type components) # NavigationElement, GalleryElement, etc.
├── Offline/PWA (5 files)
│ ├── OfflineStatusIndicator.tsx
│ ├── DownloadProgressPanel.tsx
│ └── ...
├── Runtime/ (1 file)
│ └── RuntimeControls.tsx # Configurable offline/fullscreen/sound controls
├── Layout Components
│ ├── NavBar.tsx # Top navigation
│ ├── AsideMenu.tsx # Sidebar navigation
│ ├── FooterBar.tsx # Footer
│ └── SectionMain.tsx # Main content wrapper
└── Standalone Components (~50 files)
├── BaseButton.tsx # Button component
├── CardBox.tsx # Card container
├── FormField.tsx # Form field wrapper
├── RuntimePresentation.tsx # Tour playback
├── RuntimeElement.tsx # Runtime element wrapper
├── TourFlowManager.tsx # Page management
└── ...
```
---
## Component Categories
### 1. Entity Components (13 Directories)
Each entity has a dedicated directory with consistent structure.
**Entities:**
- `Users/`, `Roles/`, `Permissions/`
- `Projects/`, `Project_memberships/`
- `Assets/`, `Asset_variants/`
- `Tour_pages/`, `Project_audio_tracks/`
- `Publish_events/`, `Pwa_caches/`
- `Access_logs/`, `Presigned_url_requests/`
**Standard Files per Entity:**
| File | Purpose | Generator |
|------|---------|-----------|
| `Table[Entity].tsx` | Data grid table | `createTableComponent` |
| `configure[Entity]Cols.tsx` | Column definitions | `createColumnLoader` |
| `Card[Entity].tsx` | Card display | Manual |
| `List[Entity].tsx` | List display | Manual |
**Example: TableUsers.tsx (23 LOC)**
```typescript
import { createTableComponent } from '../Factory/createTableComponent';
import {
fetch, update, deleteItem, setRefetch, deleteItemsByIds,
} from '../../stores/users/usersSlice';
import { loadColumns } from './configureUsersCols';
import type { User } from '../../types/entities';
const TableUsers = createTableComponent<User>({
entityName: 'users',
sliceSelector: (state) => state.users,
fetchAction: fetch,
updateAction: update,
deleteAction: deleteItem,
deleteByIdsAction: deleteItemsByIds,
setRefetchAction: setRefetch,
loadColumnsFunction: loadColumns,
});
export default TableUsers;
```
**Example: configureUsersCols.tsx**
```typescript
import { createColumnLoader, ColumnMetadata } from '../DataGrid/configBuilderFactory';
const USERS_COLUMNS: ColumnMetadata[] = [
{ field: 'firstName', headerName: 'First Name', type: 'text', editable: true },
{ field: 'lastName', headerName: 'Last Name', type: 'text', editable: true },
{ field: 'email', headerName: 'E-Mail', type: 'text', editable: true },
{ field: 'disabled', headerName: 'Disabled', type: 'boolean', editable: true },
{
field: 'avatar',
headerName: 'Avatar',
type: 'image',
renderCell: (params) => <ImageField image={params?.row?.avatar} />,
},
{
field: 'app_role',
headerName: 'App Role',
type: 'singleSelectRelation',
entityRef: 'roles',
editable: true,
},
{ field: 'actions', headerName: '', type: 'actions' },
];
export const loadColumns = createColumnLoader({
entityName: 'users',
columns: USERS_COLUMNS,
});
```
---
### 2. Factory Components
#### createTableComponent (Factory/createTableComponent.tsx)
**Purpose:** Generates entity table components from configuration.
**Input Configuration:**
```typescript
interface TableComponentConfig<T extends BaseEntity> {
entityName: string;
sliceSelector: (state: RootState) => EntitySliceState<T>;
fetchAction: AsyncThunk<...>;
updateAction: AsyncThunk<...>;
deleteAction: AsyncThunk<...>;
deleteByIdsAction: AsyncThunk<...>;
setRefetchAction: (refetch: boolean) => Action;
loadColumnsFunction: (onDelete, entityName, user) => Promise<GridColDef[]>;
}
```
**Output:** React component wrapping `GenericTable`.
**Usage Pattern:**
```typescript
const TableAssets = createTableComponent<Asset>({
entityName: 'assets',
sliceSelector: (state) => state.assets,
fetchAction: fetch,
updateAction: update,
deleteAction: deleteItem,
deleteByIdsAction: deleteItemsByIds,
setRefetchAction: setRefetch,
loadColumnsFunction: loadColumns,
});
```
#### configBuilderFactory (DataGrid/configBuilderFactory.tsx)
**Purpose:** Generates MUI X DataGrid column configurations from metadata.
**Column Types Supported:**
| Type | Description | MUI Type |
|------|-------------|----------|
| `text` | Plain text | string |
| `boolean` | Checkbox | boolean |
| `date` | Date only | dateTime |
| `datetime` | Date and time | dateTime |
| `number` | Numeric | number |
| `relation` | Single relation | singleSelect |
| `relationMany` | Multiple relations | singleSelect + DataGridMultiSelect |
| `singleSelectRelation` | Dropdown relation | singleSelect |
| `image` | Image thumbnail | custom renderCell |
| `actions` | Row actions | actions |
**Features:**
- Auto-fetches relation options via `/[entity]/autocomplete?limit=50` to match the backend autocomplete limit
- Formats `singleSelectRelation` values from either loaded `{ id, label }` options or nested relation objects, so list tables display names instead of UUIDs
- AsyncPaginate relation fields (`SelectField`, `SelectFieldMany`, `RoleSelect`) use page size `50` for the same backend limit
- Permission-aware editable fields
- Custom formatters via `dataFormatter`
- Action column with view/edit/delete popover
---
### 3. Generic Components
#### GenericTable (Generic/GenericTable.tsx)
**Purpose:** Reusable data grid with full CRUD functionality.
**Features:**
- MUI X DataGrid v7 integration
- Server-side pagination, sorting, filtering
- Row editing (inline)
- Bulk selection and delete
- Toast notifications
- Filter panel with field selection
**Props:**
```typescript
interface GenericTableProps<T extends BaseEntity> {
entityName: string;
sliceSelector: (state: RootState) => EntitySliceState<T>;
fetchAction: AsyncThunk<...>;
updateAction: AsyncThunk<...>;
deleteAction: AsyncThunk<...>;
deleteByIdsAction?: AsyncThunk<...>;
setRefetchAction: (refetch: boolean) => Action;
loadColumnsFunction: (...) => Promise<GridColDef[]>;
filters: Filter[];
filterItems: FilterItem[];
setFilterItems: (items: FilterItem[]) => void;
extraQuery?: string;
}
```
**Data Flow:**
```
1. Component mounts → loadColumnsFunction loads columns
2. fetchAction dispatched with pagination/sort/filter query
3. Data stored in Redux slice under entityName key
4. DataGrid renders rows with server-side pagination
5. Edit saves via updateAction
6. Delete via deleteAction or deleteByIdsAction (bulk)
```
---
### 4. Constructor Components (15 files)
Components for the visual tour builder interface.
| Component | Purpose | LOC |
|-----------|---------|-----|
| `CanvasElement.tsx` | Renders element on canvas with positioning | 62 |
| `ElementEditorPanel.tsx` | Settings sidebar with tabs (General/CSS/Effects) | 592 |
| `ConstructorToolbar.tsx` | Floating main constructor toolbar with mode, page, element, save, stage, exit, and collapse actions | ~490 |
| `ConstructorControlsPanel.tsx` | Legacy/secondary controls panel for constructor actions | ~200 |
| `ConstructorMenu.tsx` | Left menu with element types | ~150 |
| `BackgroundSettingsEditor.tsx` | Image/video/audio background selection | ~100 |
| `CreateTransitionForm.tsx` | Transition video creation form | ~80 |
| `ElementEditorHeader.tsx` | Draggable header for editor panel | ~50 |
| `PageSelector.tsx` | Tour page dropdown selector with ordinal labels and optional toolbar sizing class | ~60 |
| `InteractionModeToggle.tsx` | Edit vs Interact mode toggle with compact toolbar layout | ~40 |
| `MenuActionButton.tsx` | Reusable menu button | ~30 |
| `AssetSelectCompact.tsx` | Asset dropdown selector | ~50 |
| `CanvasBackground.tsx` | Background rendering (image/video) | ~80 |
| `TransitionPreviewOverlay.tsx` | Video transition preview | ~100 |
| `index.ts` | Barrel exports | ~20 |
| `types.ts` | Constructor type definitions | ~50 |
**ConstructorToolbar.tsx action layout:**
- The left block contains the Edit/Interact mode toggle.
- `Page actions` groups page selector, page reorder, new page, duplicate page,
delete page, and background controls. All page-level controls use consistent
40px control height and explicit vertical dividers.
- `Elements actions` groups the Add Elements dropdown and element Copy/Paste
buttons. Copy is enabled only when an element is selected; Paste is enabled
only when the constructor-local element clipboard has content.
- Save, Stage, Exit, and Collapse are kept in the final action group. Save and
Stage use fixed compact widths and reserve the timestamp subtitle row even
when no timestamp is available, so the action group remains vertically
aligned as save status changes.
- Section labels are centered above their controls to distinguish page-level
copy/duplicate from element copy/paste.
**Page management controls:**
- `PageSelector.tsx` displays page labels with order prefixes so users can see
the current sequence directly in the dropdown.
- Page move buttons call the constructor page reorder handler with `up` or
`down`; the handler persists the full ordered ID list via
`POST /api/tour_pages/reorder`.
- Duplicate page saves the current dev page first, then calls
`POST /api/tour_pages/:id/duplicate`; the backend appends the new independent
page as the last page and regenerates page element IDs.
- Delete page opens the constructor confirmation modal and then calls the same
`DELETE /api/tour_pages/:id` endpoint used by Pages & Transitions.
- Buttons are disabled while their matching async action is saving, when the
user lacks the required permission, and at first/last page boundaries for
reorder.
**Element clipboard controls:**
- Element Copy/Paste lives in the main toolbar, not in the element editor panel,
so users can copy an element, switch pages, and paste it into another page.
- The clipboard is session-local to the active constructor page instance and is
managed by `useConstructorElements`.
- Paste appends a deep clone to the active page's inline
`ui_schema_json.elements[]` data. It preserves all settings, including
position, dimensions, links, navigation targets, transition fields, CSS
styles, and effects, while regenerating element and nested item IDs.
**CanvasElement.tsx Architecture:**
```typescript
interface CanvasElementProps {
element: CanvasElementType;
isSelected: boolean;
isEditMode: boolean;
isDisabled?: boolean;
onClick: () => void;
onMouseDown?: (event: React.MouseEvent) => void;
resolveUrl?: (url: string | undefined) => string;
}
const CanvasElement: React.FC<CanvasElementProps> = ({
element, isSelected, isEditMode, isDisabled, onClick, onMouseDown, resolveUrl,
}) => {
return (
<button
type='button'
data-constructor-element-id={element.id}
className='absolute'
style={{
left: `${element.xPercent}%`,
top: `${element.yPercent}%`,
transform: 'translate(-50%, -50%)',
background: 'transparent',
border: 'none',
padding: 0,
margin: 0,
}}
onMouseDown={isEditMode ? onMouseDown : undefined}
onClick={onClick}
>
<UiElementRenderer
element={element}
resolveUrl={resolveUrl}
isSelected={isSelected}
isEditMode={isEditMode}
isDisabled={isDisabled}
/>
</button>
);
};
```
**ElementEditorPanel Tabs:**
| Tab | Section | Properties |
|-----|---------|------------|
| General | CommonSettingsSection | label, position, timing |
| General | NavigationSettingsSection | type, target, transition |
| General | TooltipSettingsSection | title, text, icon |
| General | DescriptionSettingsSection | title, text, fonts, colors |
| General | MediaSettingsSection | url, autoplay, loop, muted |
| General | GallerySettingsSection | cards (image, title, link) |
| General | CarouselSettingsSection | slides, navigation icons |
| CSS | StyleSettingsSection | width, height, margin, padding, etc. |
| Effects | EffectsSettingsSection | appear animation, hover (incl. hover reveal), focus, active |
---
### 5. ElementSettings Components (23 files)
Settings panels for element configuration in the constructor.
**Full-Size Variants:**
- `CommonSettingsSection.tsx`
- `NavigationSettingsSection.tsx`
- `TooltipSettingsSection.tsx`
- `DescriptionSettingsSection.tsx`
- `MediaSettingsSection.tsx`
- `GallerySettingsSection.tsx`
- `CarouselSettingsSection.tsx`
- `InfoPanelSettingsSection.tsx`
- `StyleSettingsSection.tsx`
- `EffectsSettingsSection.tsx`
- `ElementSettingsTabs.tsx`
**Compact Variants (for ElementEditorPanel):**
- `CommonSettingsSectionCompact.tsx`
- `NavigationSettingsSectionCompact.tsx`
- `TooltipSettingsSectionCompact.tsx`
- `DescriptionSettingsSectionCompact.tsx`
- `MediaSettingsSectionCompact.tsx`
- `GallerySettingsSectionCompact.tsx`
- `GalleryCarouselSettingsSectionCompact.tsx` (shared gallery/carousel settings)
- `CarouselSettingsSectionCompact.tsx`
- `InfoPanelSettingsSectionCompact.tsx`
- `StyleSettingsSectionCompact.tsx`
- `EffectsSettingsSectionCompact.tsx`
**Info Panel Settings:**
- The constructor General tab renders `InfoPanelSettingsSectionCompact`.
- `Open by default` writes `infoPanelOpenByDefault` to the element JSON.
- Global and project element-default detail pages render the full `InfoPanelSettingsSection`, so Info Panel default state can be configured at platform, project, and instance scope.
- Disabled Info Panels (`infoPanelDisabled`) do not open by click or by default state.
**Supporting Files:**
- `index.ts` - Barrel exports
- `types.ts` - Type definitions and unit normalization helpers
- `useElementSettingsForm.ts` - Form state management hook
**Unit Normalization Helpers (types.ts):**
The `types.ts` file provides helper functions for CSS value handling:
```typescript
// Extract numeric value from CSS value with unit
extractNumericValue('24vw') // '24'
extractNumericValue('100px') // '100'
// Convert value to CSS value with unit (robust normalization)
toUnitValue('24', 'vw') // '24vw'
toUnitValue('24vw', 'vw') // '24vw' (preserved)
toUnitValue('0', 'px') // '0' (no unit for zero)
toUnitValue('calc(100%)', 'px') // 'calc(100%)' (CSS functions preserved)
toUnitValue('10px 20px', 'px') // '10px 20px' (complex values preserved)
// Normalize number string (removes invalid chars)
normalizeNumberString('24.5') // '24.5'
normalizeNumberString('abc') // ''
// Convert to optional trimmed value
toOptionalTrimmed('') // undefined
toOptionalTrimmed(' value ') // 'value'
```
**Edge Cases Handled by `toUnitValue`:**
| Input | Output | Reason |
|-------|--------|--------|
| `"24"` | `"24px"` | Plain number → add unit |
| `"24px"` | `"24px"` | Has unit → preserve |
| `"0"` | `"0"` | Zero → no unit needed |
| `"-10"` | `"-10px"` | Negative → add unit |
| `".5"` | `"0.5rem"` | Decimal → add unit |
| `"10px 20px"` | `"10px 20px"` | Complex → preserve |
| `"calc(100% - 20px)"` | preserved | CSS function → preserve |
| `"var(--spacing)"` | preserved | CSS variable → preserve |
**Index Re-exports:**
```typescript
// ElementSettings/index.ts
export { ElementSettingsTabsCompact } from './ElementSettingsTabs';
export { StyleSettingsSectionCompact } from './StyleSettingsSectionCompact';
export { EffectsSettingsSectionCompact } from './EffectsSettingsSectionCompact';
export { extractNumericValue, toUnitValue, toOptionalTrimmed } from './types';
// ... etc
```
---
### 6. UiElements Components (16 files)
Unified element rendering for WYSIWYG consistency between Constructor and Runtime.
**Directory Structure:**
```
UiElements/
├── UiElementRenderer.tsx # Main entry point
├── GalleryCarouselOverlay.tsx # Fullscreen overlay for gallery/carousel and Info Panel media
├── shared/
│ └── useElementWrapperStyle.ts # Shared styling hook
├── elements/
│ ├── NavigationElement.tsx # Forward/back buttons
│ ├── GalleryElement.tsx # Image grid
│ ├── TooltipElement.tsx # Tooltip popup
│ ├── DescriptionElement.tsx # Styled text block
│ ├── CarouselElement.tsx # Image slideshow
│ ├── LogoElement.tsx # Logo display
│ ├── SpotElement.tsx # Hotspot indicator
│ ├── VideoPlayerElement.tsx # Video with controls
│ ├── AudioPlayerElement.tsx # Audio with controls
│ └── PopupElement.tsx # Popup trigger
├── ElementPreview.tsx # Palette preview (separate system)
├── defaults.ts # Default values
└── types.ts # Type definitions
```
#### UiElementRenderer.tsx
**Purpose:** Unified entry point for rendering UI elements with consistent styling. Used by both CanvasElement (constructor) and RuntimeElement (presentation).
**Architecture:**
```
┌─────────────────────────────────────────────────────────────┐
│ UiElementRenderer - unified styling + content │
│ ├── useElementWrapperStyle (shared hook) │
│ └── Per-type components (10 files) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ CONSTRUCTOR: CanvasElement (position, selection, drag) │
│ └── UiElementRenderer │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ RUNTIME: RuntimeElement (position, effects) │
│ └── UiElementRenderer │
└─────────────────────────────────────────────────────────────┘
```
**Props:**
```typescript
interface UiElementRendererProps {
element: CanvasElement;
resolveUrl?: (url: string | undefined) => string;
// Constructor-specific props (optional)
isSelected?: boolean;
isEditMode?: boolean;
isDisabled?: boolean;
}
```
#### useElementWrapperStyle Hook
**Purpose:** Single source of truth for element wrapper styling. Ensures constructor and runtime render elements identically.
```typescript
interface UseElementWrapperStyleOptions {
element: CanvasElement;
isSelected?: boolean; // Constructor only
isEditMode?: boolean; // Constructor only
isDisabled?: boolean; // Constructor only
}
interface ElementWrapperStyle {
className: string; // Tailwind classes
style: CSSProperties; // Inline styles from buildElementStyle
}
```
#### Per-Type Element Components
Each element type has a dedicated component that handles both wrapper styling and content:
| Component | Element Type | Rendering |
|-----------|--------------|-----------|
| `NavigationElement` | `navigation_next/prev` | Icon image or text label |
| `TooltipElement` | `tooltip` | Icon or title/text popup |
| `DescriptionElement` | `description` | Icon or styled title/text block |
| `VideoPlayerElement` | `video_player` | `<video>` with controls |
| `AudioPlayerElement` | `audio_player` | `<audio>` with controls |
| `GalleryElement` | `gallery` | 3-column image grid |
| `CarouselElement` | `carousel` | Slides with dots and nav icons |
| `LogoElement` | `logo` | Icon image or text |
| `SpotElement` | `spot` | Icon or animated pulse circle |
| `PopupElement` | `popup` | Text label |
**Per-Type Component Interface:**
```typescript
interface ElementComponentProps {
element: CanvasElement;
resolveUrl?: (url: string | undefined) => string;
className: string; // From useElementWrapperStyle
style: CSSProperties; // From useElementWrapperStyle
}
```
---
### 7. Offline/PWA Components (4 files)
| Component | Purpose |
|-----------|---------|
| `OfflineStatusIndicator.tsx` | Shows online/offline status |
| `DownloadProgressPanel.tsx` | Shows download progress for PWA caching |
| `OfflineToggle.tsx` | Enable/disable offline mode |
| `StorageUsageDisplay.tsx` | Shows storage quota usage |
---
### 8. Uploaders (1 file)
#### UploadService.js
**Purpose:** File upload service with chunked uploads and MIME type validation.
**Location:** `frontend/src/components/Uploaders/UploadService.js`
**Key Features:**
- Single-file upload via `upload()`
- Chunked upload via `uploadChunked()` for large files
- MIME type validation with extension fallback
- Progress callbacks and abort support
**MIME Type Validation:**
```javascript
const VALID_MIME_TYPES = {
image: {
prefixes: ['image/'],
extensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico'],
},
video: {
prefixes: ['video/'],
extensions: ['mp4', 'webm', 'mov', 'avi', 'mkv', 'm4v', 'ogv'],
},
audio: {
prefixes: ['audio/'],
extensions: ['mp3', 'wav', 'ogg', 'aac', 'm4a', 'flac', 'weba'],
},
};
```
**Validation Schema:**
| Property | Type | Description |
|----------|------|-------------|
| `assetType` | string | Asset type: `'image'`, `'video'`, or `'audio'` |
| `image` | boolean | Legacy: Must be an image |
| `video` | boolean | Legacy: Must be a video |
| `audio` | boolean | Legacy: Must be audio |
| `size` | number | Max file size in bytes |
| `formats` | string[] | Allowed file extensions |
**Usage:**
```javascript
// Chunked upload with validation
const result = await FileUploader.uploadChunked(
'assets/project-id',
file,
{ assetType: 'image' }, // Validation schema
{
chunkSize: 5 * 1024 * 1024,
maxRetries: 3,
signal: abortController.signal,
onProgress: (percent) => console.log(`${percent}%`),
onStatus: (status) => console.log(status),
}
);
```
See [Asset Upload & Variants](../../documentation/asset-upload-variants.md) for complete upload flow documentation.
---
### 9. Layout Components
#### NavBar.tsx
**Purpose:** Top navigation bar with responsive menu.
**Features:**
- Fixed position with scroll shadow
- Mobile menu toggle
- Integrates with `NavBarMenuList` for menu items
- Theme-aware background
```typescript
type Props = {
menu: MenuNavBarItem[];
className: string;
children: ReactNode;
};
export default function NavBar({ menu, className, children }: Props) {
const [isMenuNavBarActive, setIsMenuNavBarActive] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
// ...
}
```
#### AsideMenu.tsx
**Purpose:** Sidebar navigation with mobile responsiveness.
```typescript
type Props = {
menu: MenuAsideItem[];
isAsideMobileExpanded: boolean;
isAsideLgActive: boolean;
onAsideLgClose: () => void;
};
```
#### SectionMain.tsx
**Purpose:** Main content wrapper with consistent padding.
#### FooterBar.tsx
**Purpose:** Footer with copyright.
---
### 10. Standalone Components (~50 files)
Key standalone components used throughout the application.
#### BaseButton.tsx
**Purpose:** Universal button component with variants.
```typescript
type Props = {
label?: string;
/** Optional subtitle displayed below the label in smaller text */
subtitle?: string;
icon?: string;
iconSize?: string | number;
href?: string;
target?: string;
type?: string;
color?: ColorButtonKey;
className?: string;
small?: boolean;
outline?: boolean;
active?: boolean;
disabled?: boolean;
roundedFull?: boolean;
onClick?: (e: React.MouseEvent) => void;
};
```
**Features:**
- Link or button rendering
- Icon support with BaseIcon
- Color variants via `getButtonColor`
- Theme-aware corners from Redux
- **Subtitle support** for secondary text (e.g., timestamps)
**Subtitle Usage:**
```typescript
// Show publish timestamp below button label
<BaseButton
label="Publish to Production"
subtitle={lastPublishedAt ? `Last: ${relativeTimestamp(lastPublishedAt)}` : undefined}
color="success"
onClick={handlePublish}
/>
// Renders:
// ┌──────────────────────────┐
// │ Publish to Production │
// │ Last: 5 min ago │ ← smaller, 80% opacity
// └──────────────────────────┘
```
#### CardBox.tsx
**Purpose:** Card container with consistent styling.
```typescript
type Props = {
rounded?: string;
flex?: string;
className?: string;
hasComponentLayout?: boolean;
hasTable?: boolean;
isHoverable?: boolean;
isModal?: boolean;
children?: ReactNode;
footer?: ReactNode;
};
```
#### FormField.tsx
**Purpose:** Form field wrapper with label and help text.
**Features:**
- Multi-column grid support (2-3 children)
- Icon prefix support
- Theme-aware styling
#### RuntimePresentation.tsx (18.5KB)
**Purpose:** Full-screen tour playback component.
**Hooks Used:**
- `usePageDataLoader` - Load page data
- `usePreloadOrchestrator` - Asset preloading
- `usePageSwitch` - Page navigation
- `useTransitionPlayback` - Video transitions
- `useBackgroundTransition` - Background changes
**Features:**
- Fullscreen mode
- Keyboard navigation (ArrowLeft/Right, Escape)
- Touch gestures
- Transition videos
- Element effects (appear animation, hover)
#### RuntimeElement.tsx
**Purpose:** Element wrapper for runtime with interactive effects. Delegates styling and content to UiElementRenderer.
```typescript
interface RuntimeElementProps {
element: any;
onClick: () => void;
resolveUrl?: (url: string | undefined) => string;
}
const RuntimeElement: React.FC<RuntimeElementProps> = ({
element,
onClick,
resolveUrl,
}) => {
// Extract effect properties for hover/focus/active states
const { effectStyle, eventHandlers } = useElementEffects(effectProperties);
// Build position style with effects merged
let positionStyle: React.CSSProperties = {
left: `${xPercent}%`,
top: `${yPercent}%`,
transform: `translate(-50%, -50%)${rotation ? ` rotate(${rotation}deg)` : ''}`,
};
// Merge effect styles and animations...
return (
<div
className='absolute cursor-pointer'
style={positionStyle}
onClick={onClick}
tabIndex={0}
{...eventHandlers}
>
<UiElementRenderer element={element} resolveUrl={resolveUrl} />
</div>
);
};
```
#### TourFlowManager.tsx (17KB)
**Purpose:** Page and transition management interface.
**Features:**
- Page list with thumbnails
- Drag-and-drop reordering
- Transition configuration
- Page CRUD operations
- Sort order management
#### Other Notable Components
| Component | Purpose |
|-----------|---------|
| `BaseIcon.tsx` | MDI icon wrapper |
| `ImageField.tsx` | Image display with fallback |
| `Search.tsx` | Global search input |
| `SectionTitleLineWithButton.tsx` | Section header with action |
| `DataGridMultiSelect.tsx` | Multi-select for DataGrid |
| `ListActionsPopover.tsx` | Row actions dropdown |
| `Divider.tsx` | Horizontal divider |
| `NotificationBar.tsx` | Toast notification wrapper |
| `OverlayLayer.tsx` | Modal overlay background |
| `PillTag.tsx` | Tag/badge component |
| `UserAvatar.tsx` | User avatar display |
---
## Component Patterns
### 1. Factory Pattern
**Tables:** 13 entity tables generated from configuration.
**Columns:** 13 column configs generated from metadata.
**Benefit:** ~1500 LOC reduced to ~300 LOC configuration.
### 2. Theme-Aware Styling
Components read theme values from Redux:
```typescript
const corners = useAppSelector((state) => state.style.corners);
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const iconsColor = useAppSelector((state) => state.style.iconsColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
```
### 3. Composition Pattern
Complex components compose smaller ones:
```
RuntimePresentation
├── RuntimeElement (×N)
│ └── UiElementRenderer
│ └── Per-type Element (NavigationElement, GalleryElement, etc.)
├── CanvasBackground
└── TransitionVideoOverlay
Constructor Page
├── CanvasElement (×N)
│ └── UiElementRenderer
│ └── Per-type Element (NavigationElement, GalleryElement, etc.)
├── CanvasBackground
└── ElementEditorPanel
```
### 4. Compact Variants
Settings sections have two variants:
- **Full:** Standalone use with labels
- **Compact:** Embedded use with compact layout
### 5. URL Resolution Pattern
Components accept optional URL resolver for blob URLs:
```typescript
interface Props {
resolveUrl?: (url: string | undefined) => string;
}
// Usage
const resolve = resolveUrl ?? resolveAssetPlaybackUrl;
const src = resolve(element.iconUrl);
```
---
## File Count Summary
| Category | Files | LOC (est.) |
|----------|-------|------------|
| Entity Components | 52 | ~1000 |
| Factory Components | 2 | ~420 |
| Generic Components | 2 | ~500 |
| Constructor | 15 | ~1600 |
| ElementSettings | 23 | ~2200 |
| UiElements | 16 | ~750 |
| Offline | 5 | ~350 |
| Layout | 10 | ~500 |
| Standalone | ~50 | ~4000 |
| **Total** | **~175** | **~11,300** |
---
## Component Dependencies
```
┌─────────────────────────────────────────────────────┐
│ Pages │
│ (dashboard.tsx, assets-list.tsx, constructor.tsx) │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Entity Components │
│ (TableUsers, TableAssets, TableProjects, ...) │
│ │
│ Uses: createTableComponent + configBuilderFactory │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ GenericTable │
│ MUI X DataGrid + filters + pagination + editing │
│ │
│ Uses: CardBox, BaseButton, Formik │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Base Components │
│ BaseButton, CardBox, FormField, BaseIcon │
│ │
│ Uses: Redux style selectors, Tailwind CSS │
└─────────────────────────────────────────────────────┘
```
---
## Styling System
### Tailwind CSS Classes
Components use Tailwind utilities with theme values:
```typescript
const componentClass = [
`flex dark:border-dark-700 dark:bg-dark-900`,
corners !== 'rounded-full' ? corners : 'rounded-3xl',
cardsStyle,
];
```
### Dark Mode
All components support dark mode via:
- `dark:` prefix classes
- `darkMode` state from Redux
- Applied at layout level
### Dynamic Styling
Constructor elements use inline styles from `buildElementStyle`:
```typescript
import { buildElementStyle } from '../../lib/elementStyles';
const style = buildElementStyle(element);
// Returns: { width, height, backgroundColor, color, fontSize, ... }
```
---
## Best Practices
### 1. Use Factories for Entity Components
```typescript
// Good - 23 LOC
const TableUsers = createTableComponent<User>({ ... });
// Avoid - 400+ LOC of boilerplate
```
### 2. Theme-Aware Styling
```typescript
// Good - reads from Redux
const corners = useAppSelector((state) => state.style.corners);
// Avoid - hardcoded
className="rounded-lg"
```
### 3. URL Resolution for Assets
```typescript
// Good - supports preloaded blob URLs
<img src={resolve(element.iconUrl)} />
// Avoid - only works online
<img src={element.iconUrl} />
```
### 4. Compact Variants for Embedded Use
```typescript
// Good - use compact in panels
<CommonSettingsSectionCompact {...props} />
// Avoid - full variant in compact space
<CommonSettingsSection {...props} />
```
---
## Related Documentation
- [Frontend Architecture](./frontend-architecture.md) - Overall frontend structure
- [Pages Module](./pages-module.md) - Page components
- [Hooks Reference](./hooks-reference.md) - Custom hooks
- [Constructor Page Editor](./constructor-page-editor.md) - Visual builder
- [Runtime Presentation](./runtime-presentation.md) - Tour playback