Removed static content from template.
This commit is contained in:
parent
d25eb56794
commit
1c2bb703f3
247
RESTYLE.md
Normal file
247
RESTYLE.md
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
# Semantic Styling Framework Refactoring Checklist
|
||||||
|
|
||||||
|
This document tracks the refactoring of our CSS and TSX files to use the semantic styling framework, eliminating duplicate CSS and creating consistent component patterns.
|
||||||
|
|
||||||
|
## Framework Overview
|
||||||
|
|
||||||
|
Our semantic styling framework consists of:
|
||||||
|
- **`colors.css`** - CSS variables and color system
|
||||||
|
- **`semantic-components.css`** - Semantic component classes (cards, badges, actions, drag-and-drop, etc.)
|
||||||
|
- **`application.css`** - Utility classes and base component styles
|
||||||
|
- **`aspectRatios.css`** - Dedicated aspect ratio styling for slides (preserved)
|
||||||
|
|
||||||
|
## Refactoring Principles
|
||||||
|
|
||||||
|
1. **Use semantic classes** instead of component-specific CSS
|
||||||
|
2. **Leverage CSS variables** for colors, spacing, and typography
|
||||||
|
3. **Eliminate duplicate styles** by consolidating common patterns
|
||||||
|
4. **Preserve presentation/theme CSS** - slides should NOT use semantic framework
|
||||||
|
5. **Only keep minimal component-specific CSS** when absolutely necessary
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ COMPLETED REFACTORING
|
||||||
|
|
||||||
|
### Core Framework Files
|
||||||
|
- **`src/styles/colors.css`** ✅ - Comprehensive CSS variable system
|
||||||
|
- **`src/styles/semantic-components.css`** ✅ - Semantic component classes
|
||||||
|
- **`src/styles/application.css`** ✅ - Utility classes and base styles
|
||||||
|
- **`src/styles/aspectRatios.css`** ✅ - Preserved dedicated slide aspect ratios
|
||||||
|
|
||||||
|
### Presentation Components
|
||||||
|
- **`src/components/presentations/PresentationEditor.tsx`** ✅ - Uses semantic classes
|
||||||
|
- **`src/components/presentations/PresentationEditor.css`** ✅ - Reduced by ~85%, only layout-specific CSS
|
||||||
|
- **`src/components/presentations/SlidesSidebar.tsx`** ✅ - Uses semantic classes
|
||||||
|
- **`src/components/presentations/SlidesSidebar.css`** ✅ - Minimal component-specific CSS
|
||||||
|
- **`src/components/presentations/shared/SlideThumbnail.tsx`** ✅ - Uses semantic classes
|
||||||
|
- **`src/components/presentations/shared/SlideThumbnail.css`** ✅ - Minimal component overrides
|
||||||
|
- **`src/components/presentations/PresentationMode.tsx`** ✅ - Uses semantic classes for UI
|
||||||
|
- **`src/components/presentations/PresentationMode.css`** ✅ - Isolated slide content from semantic framework
|
||||||
|
- **`src/components/presentations/shared/LoadingState.tsx`** ✅ - Uses semantic classes
|
||||||
|
- **`src/components/presentations/shared/LoadingState.css`** ✅ - Minimal overrides
|
||||||
|
- **`src/components/presentations/shared/ErrorState.tsx`** ✅ - Uses semantic classes (slide-editor version)
|
||||||
|
- **`src/components/presentations/shared/ErrorState.css`** ✅ - Minimal overrides
|
||||||
|
|
||||||
|
### Slide Operations
|
||||||
|
- **`src/hooks/useSlideOperations.ts`** ✅ - Added drag-and-drop reordering functionality
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚧 NEEDS REFACTORING
|
||||||
|
|
||||||
|
### High Priority - Presentation Components
|
||||||
|
|
||||||
|
#### **`src/components/presentations/NewPresentationPage.tsx`**
|
||||||
|
- **Status**: Partially refactored (documented in CSS)
|
||||||
|
- **Needs**: Full TSX refactoring to use semantic classes
|
||||||
|
- **Current Issues**: Uses custom classes that could be semantic utilities
|
||||||
|
|
||||||
|
#### **`src/components/presentations/NewPresentationPage.css`**
|
||||||
|
- **Status**: Has some semantic references but needs full refactoring
|
||||||
|
- **Needs**: Remove duplicate styles, use semantic framework
|
||||||
|
- **Current Issues**: Custom `.creation-form`, `.aspect-ratio-card` styles
|
||||||
|
|
||||||
|
#### **`src/components/presentations/ThemeSelector.tsx`**
|
||||||
|
- **Status**: Not refactored
|
||||||
|
- **Needs**: Use semantic classes for cards, grids, badges
|
||||||
|
- **Current Issues**: Likely uses hardcoded CSS classes
|
||||||
|
|
||||||
|
#### **`src/components/presentations/ThemeSelector.css`**
|
||||||
|
- **Status**: Partially refactored (hardcoded colors removed)
|
||||||
|
- **Needs**: Complete semantic class integration
|
||||||
|
- **Current Issues**: Custom `.theme-card`, `.themes-grid` could use semantic classes
|
||||||
|
|
||||||
|
#### **`src/components/presentations/PresentationsList.tsx`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Full refactoring assessment
|
||||||
|
- **Likely Issues**: Custom card layouts, button styles
|
||||||
|
|
||||||
|
#### **`src/components/presentations/PresentationsList.css`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Full refactoring assessment
|
||||||
|
- **Likely Issues**: Duplicate card, button, grid styles
|
||||||
|
|
||||||
|
### Medium Priority - Form Components
|
||||||
|
|
||||||
|
#### **`src/components/presentations/PresentationDetailsForm.tsx`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Use semantic form classes
|
||||||
|
- **Likely Issues**: Custom form styling
|
||||||
|
|
||||||
|
#### **`src/components/presentations/PresentationDetailsForm.css`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Use `.form-group`, `.form-input`, `.form-label` semantic classes
|
||||||
|
- **Likely Issues**: Duplicate form styles
|
||||||
|
|
||||||
|
#### **`src/components/presentations/AspectRatioSelector.tsx`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Use semantic card and selection classes
|
||||||
|
- **Likely Issues**: Custom card selection UI
|
||||||
|
|
||||||
|
#### **`src/components/presentations/AspectRatioSelector.css`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Use semantic `.card-interactive` and selection states
|
||||||
|
- **Likely Issues**: Custom card hover/selection styles
|
||||||
|
|
||||||
|
### Lower Priority - Supporting Components
|
||||||
|
|
||||||
|
#### **`src/components/presentations/CreationActions.tsx`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Use semantic button classes
|
||||||
|
- **Likely Issues**: Custom button layouts
|
||||||
|
|
||||||
|
#### **`src/components/presentations/CreationActions.css`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Use `.btn`, `.btn-primary`, `.btn-secondary` classes
|
||||||
|
- **Likely Issues**: Duplicate button styles
|
||||||
|
|
||||||
|
#### **`src/components/presentations/ThemeSelectionSection.tsx`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Use semantic layout and card classes
|
||||||
|
- **Likely Issues**: Custom section layouts
|
||||||
|
|
||||||
|
#### **`src/components/presentations/ThemeSelectionSection.css`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Use semantic grid and card classes
|
||||||
|
- **Likely Issues**: Custom grid layouts
|
||||||
|
|
||||||
|
#### **`src/components/presentations/EmptyPresentationState.tsx`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Use semantic empty state classes
|
||||||
|
- **Likely Issues**: Custom empty state styling
|
||||||
|
|
||||||
|
#### **`src/components/presentations/EmptyPresentationState.css`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Use `.empty-content` semantic class
|
||||||
|
- **Likely Issues**: Duplicate empty state styles
|
||||||
|
|
||||||
|
### Slide Editor Components (Lower Priority)
|
||||||
|
|
||||||
|
#### **`src/components/slide-editor/SlideEditor.tsx`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Assessment for semantic class usage
|
||||||
|
- **Note**: May have complex layout requirements
|
||||||
|
|
||||||
|
#### **`src/components/slide-editor/SlideEditor.css`**
|
||||||
|
- **Status**: Not examined/refactored
|
||||||
|
- **Needs**: Assessment for consolidation opportunities
|
||||||
|
- **Note**: May need to preserve slide-specific styling
|
||||||
|
|
||||||
|
### Theme Components
|
||||||
|
|
||||||
|
#### **`src/components/themes/` (Multiple files)**
|
||||||
|
- **Status**: Not examined for this refactoring
|
||||||
|
- **Priority**: Low (theme browsing is separate from presentations)
|
||||||
|
- **Needs**: Future assessment
|
||||||
|
|
||||||
|
### UI Components
|
||||||
|
|
||||||
|
#### **`src/components/ui/` (Multiple files)**
|
||||||
|
- **Status**: Not examined for this refactoring
|
||||||
|
- **Priority**: Medium (shared components should use semantic framework)
|
||||||
|
- **Needs**: Future assessment for modal, dialog, form components
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 NEXT STEPS
|
||||||
|
|
||||||
|
### Immediate Priority (Sprint 1)
|
||||||
|
1. **NewPresentationPage.tsx** - Full semantic refactoring
|
||||||
|
2. **ThemeSelector.tsx** - Use semantic card and grid classes
|
||||||
|
3. **PresentationsList.tsx** - Assessment and refactoring
|
||||||
|
|
||||||
|
### Short Term (Sprint 2)
|
||||||
|
1. **Form Components** - PresentationDetailsForm, AspectRatioSelector
|
||||||
|
2. **Supporting Components** - CreationActions, ThemeSelectionSection
|
||||||
|
3. **Empty States** - EmptyPresentationState
|
||||||
|
|
||||||
|
### Long Term (Sprint 3)
|
||||||
|
1. **Slide Editor Components** - Assessment and selective refactoring
|
||||||
|
2. **UI Components** - Modal, dialog, form utilities
|
||||||
|
3. **Theme Components** - If needed for consistency
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 REFACTORING CHECKLIST
|
||||||
|
|
||||||
|
For each component refactoring, ensure:
|
||||||
|
|
||||||
|
### TSX File Updates
|
||||||
|
- [ ] Replace custom CSS classes with semantic classes
|
||||||
|
- [ ] Use utility classes for layout (`flex`, `gap-4`, `items-center`, etc.)
|
||||||
|
- [ ] Use semantic typography classes (`text-lg`, `font-semibold`, etc.)
|
||||||
|
- [ ] Use semantic color classes (`text-primary`, `bg-secondary`, etc.)
|
||||||
|
- [ ] Use semantic component classes (`.card-interactive`, `.btn`, `.badge-secondary`, etc.)
|
||||||
|
- [ ] Remove imports of CSS files that become empty
|
||||||
|
|
||||||
|
### CSS File Updates
|
||||||
|
- [ ] Remove duplicate styles covered by semantic framework
|
||||||
|
- [ ] Replace hardcoded colors with CSS variables
|
||||||
|
- [ ] Keep only truly component-specific styling
|
||||||
|
- [ ] Use semantic classes as base, add minimal overrides only
|
||||||
|
- [ ] Document what semantic classes replace in comments
|
||||||
|
- [ ] Delete CSS file if it becomes empty/unnecessary
|
||||||
|
|
||||||
|
### Testing Requirements
|
||||||
|
- [ ] Visual consistency with existing design
|
||||||
|
- [ ] Interactive states work correctly (hover, focus, disabled)
|
||||||
|
- [ ] Responsive behavior maintained
|
||||||
|
- [ ] No regression in functionality
|
||||||
|
- [ ] Accessibility maintained (focus states, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 SPECIAL CONSIDERATIONS
|
||||||
|
|
||||||
|
### Presentation/Slide Content Isolation
|
||||||
|
- **Theme CSS** (`public/themes/default/style.css`) must remain completely isolated
|
||||||
|
- **Slide content** should NEVER use semantic framework classes
|
||||||
|
- **PresentationMode** must prevent semantic class interference with slide rendering
|
||||||
|
- **AspectRatios.css** preserved for slide-specific aspect ratio handling
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
- **Bundle size reduction** achieved through elimination of duplicate CSS
|
||||||
|
- **CSS loading order** maintained (colors → semantic-components → application)
|
||||||
|
- **Critical CSS** identified and preserved for above-the-fold content
|
||||||
|
|
||||||
|
### Drag-and-Drop Functionality
|
||||||
|
- **Semantic drag classes** implemented and working (`.draggable`, `.dragged`, `.drag-over`, `.drag-handle`)
|
||||||
|
- **Slide reordering** functionality preserved and enhanced
|
||||||
|
- **Visual feedback** consistent across all drag operations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 PROGRESS SUMMARY
|
||||||
|
|
||||||
|
- **✅ Completed**: 14 files (Core framework + key presentation components)
|
||||||
|
- **🚧 Needs Refactoring**: ~15-20 files (High and medium priority)
|
||||||
|
- **📈 CSS Reduction**: ~85% reduction achieved in refactored files
|
||||||
|
- **🎨 Consistency**: Semantic framework provides consistent design patterns
|
||||||
|
- **⚡ Performance**: Reduced CSS bundle size and eliminated duplicates
|
||||||
|
|
||||||
|
**Estimated Completion**: 2-3 sprints for high/medium priority items
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last Updated: September 28, 2025*
|
||||||
|
*Framework Version: 1.0*
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
/* Presentation Editor - Minimal component-specific styles only */
|
||||||
|
|
||||||
.presentation-editor {
|
.presentation-editor {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
@ -5,161 +7,28 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header */
|
/* Component-specific layout overrides only */
|
||||||
.editor-header {
|
.editor-layout {
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-bottom: 1px solid var(--border-primary);
|
|
||||||
padding: 1rem 2rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.5rem;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-link {
|
.slide-editor-area {
|
||||||
color: var(--text-secondary);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-link:hover {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-title span {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.presentation-meta {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-badge {
|
|
||||||
background: var(--color-blue-100);
|
|
||||||
color: var(--color-blue-800);
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-counter {
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.saving-indicator {
|
|
||||||
color: var(--text-warning);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.75rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button.primary {
|
|
||||||
background: var(--btn-primary-bg);
|
|
||||||
color: var(--btn-primary-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button.primary:hover:not(:disabled) {
|
|
||||||
background: var(--btn-primary-bg-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button.secondary {
|
|
||||||
background: var(--btn-secondary-bg);
|
|
||||||
color: var(--btn-secondary-text);
|
|
||||||
border: 1px solid var(--btn-secondary-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button.secondary:hover:not(:disabled) {
|
|
||||||
background: var(--btn-secondary-bg-hover);
|
|
||||||
color: var(--btn-secondary-text-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button.large {
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Content */
|
|
||||||
.editor-content {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Empty State */
|
.slide-editor {
|
||||||
.empty-presentation {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
justify-content: center;
|
padding: 1rem;
|
||||||
padding: 2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-content {
|
/* Theme preview in empty state - component specific */
|
||||||
text-align: center;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-content h2 {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-content p {
|
|
||||||
margin: 0 0 2rem 0;
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Theme Preview in Empty State */
|
|
||||||
.theme-preview {
|
.theme-preview {
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
border: 1px solid var(--border-primary);
|
border: 1px solid var(--border-primary);
|
||||||
@ -173,19 +42,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-preview h3 {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-description {
|
|
||||||
margin: 0 0 1.5rem 0;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.available-layouts {
|
.available-layouts {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@ -193,14 +49,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.available-layouts h4 {
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layouts-grid {
|
.layouts-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
@ -229,361 +77,12 @@
|
|||||||
background: var(--color-slate-400);
|
background: var(--color-slate-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-preview-card {
|
/* Slide thumbnail positioning in editor context */
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-preview-card:hover {
|
|
||||||
border-color: var(--border-hover);
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-description {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.75rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slot-count {
|
|
||||||
color: var(--color-emerald-600);
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.more-layouts {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
border: 1px dashed var(--border-hover);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Editor Layout */
|
|
||||||
.editor-layout {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Slide Sidebar */
|
|
||||||
.slide-sidebar {
|
|
||||||
width: 280px;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-right: 1px solid var(--border-primary);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header {
|
|
||||||
padding: 1rem;
|
|
||||||
border-bottom: 1px solid var(--border-primary);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-slide-button {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
color: var(--text-accent);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-slide-button:hover:not(:disabled) {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
border-color: var(--border-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-slide-button:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slides-list {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Slide thumbnails specific to editor layout - positioning only */
|
|
||||||
.slide-thumbnail {
|
.slide-thumbnail {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Layout-related thumbnail styles moved to SlideThumbnail.css */
|
/* Responsive design */
|
||||||
|
|
||||||
|
|
||||||
/* Slide Editor Area */
|
|
||||||
.slide-editor-area {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-editor {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
gap: 1rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-controls {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-button {
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-button:hover:not(:disabled) {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
border-color: var(--border-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-button:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-button.edit-slide-button {
|
|
||||||
background: var(--btn-primary-bg);
|
|
||||||
color: var(--btn-primary-text);
|
|
||||||
border-color: var(--btn-primary-border);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-button.edit-slide-button:hover:not(:disabled) {
|
|
||||||
background: var(--btn-primary-bg-hover);
|
|
||||||
border-color: var(--btn-primary-border-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-content-editor {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-preview {
|
|
||||||
flex: 1;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 2rem;
|
|
||||||
min-height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-placeholder {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-placeholder h4 {
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-placeholder p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-slots {
|
|
||||||
margin: 1.5rem 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-slot {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding: 0.75rem;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-slot label {
|
|
||||||
display: block;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slot-content {
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-note {
|
|
||||||
font-style: italic;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-notes-editor {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-notes-editor h4 {
|
|
||||||
margin: 0 0 0.75rem 0;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notes-textarea {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 80px;
|
|
||||||
padding: 0.75rem;
|
|
||||||
border: 1px solid var(--border-secondary);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
box-sizing: border-box;
|
|
||||||
resize: vertical;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notes-textarea:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--border-focus);
|
|
||||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-error {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loading and Error States */
|
|
||||||
.loading-content,
|
|
||||||
.error-content,
|
|
||||||
.not-found-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 50vh;
|
|
||||||
text-align: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-content h2,
|
|
||||||
.not-found-content h2 {
|
|
||||||
color: var(--text-error);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-content p,
|
|
||||||
.not-found-content p {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin: 0.5rem 0 1.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Design */
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
.slide-sidebar {
|
|
||||||
width: 240px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-header {
|
|
||||||
padding: 1rem;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-info {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-meta {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-actions {
|
|
||||||
justify-content: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.editor-layout {
|
.editor-layout {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -592,32 +91,5 @@
|
|||||||
.slide-sidebar {
|
.slide-sidebar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
border-right: none;
|
|
||||||
border-bottom: 1px solid var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slides-list {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-thumbnail {
|
|
||||||
min-width: 120px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-controls {
|
|
||||||
justify-content: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-button {
|
|
||||||
flex: 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,25 +140,23 @@ export const PresentationEditor: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="presentation-editor">
|
<div className="presentation-editor">
|
||||||
<header className="editor-header">
|
<header className="page-header">
|
||||||
<div className="presentation-info">
|
|
||||||
<Link to="/presentations" className="back-link">← Back to Presentations</Link>
|
<Link to="/presentations" className="back-link">← Back to Presentations</Link>
|
||||||
<div className="presentation-title">
|
<div className="flex-1">
|
||||||
<span>{presentation.metadata.name}</span>
|
<h1 className="text-xl font-semibold text-primary m-0">{presentation.metadata.name}</h1>
|
||||||
{presentation.metadata.description && (
|
{presentation.metadata.description && (
|
||||||
<span className="presentation-description">{presentation.metadata.description}</span>
|
<p className="text-sm text-secondary mt-1 m-0">{presentation.metadata.description}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="presentation-meta">
|
<div className="flex gap-4 items-center">
|
||||||
<span className="slide-counter">
|
<span className="badge-secondary">
|
||||||
{totalSlides === 0 ? 'No slides' : `Slide ${currentSlideIndex + 1} of ${totalSlides}`}
|
{totalSlides === 0 ? 'No slides' : `Slide ${currentSlideIndex + 1} of ${totalSlides}`}
|
||||||
</span>
|
</span>
|
||||||
{saving && <span className="saving-indicator">Saving...</span>}
|
{saving && <span className="text-sm text-warning font-medium">Saving...</span>}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="editor-content">
|
<main className="flex-1 flex flex-col">
|
||||||
{totalSlides === 0 ? (
|
{totalSlides === 0 ? (
|
||||||
<EmptyPresentationState
|
<EmptyPresentationState
|
||||||
theme={theme}
|
theme={theme}
|
||||||
@ -182,30 +180,30 @@ export const PresentationEditor: React.FC = () => {
|
|||||||
<div className="slide-editor-area">
|
<div className="slide-editor-area">
|
||||||
{currentSlide ? (
|
{currentSlide ? (
|
||||||
<div className="slide-editor">
|
<div className="slide-editor">
|
||||||
<div className="slide-header">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3>Slide {currentSlideIndex + 1}</h3>
|
<h3 className="text-lg font-semibold text-primary m-0">Slide {currentSlideIndex + 1}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="slide-content-editor">
|
<div className="flex-1">
|
||||||
<div className="content-preview">
|
<div className="card p-6">
|
||||||
{/* TODO: Render actual slide content with editing capabilities */}
|
{/* TODO: Render actual slide content with editing capabilities */}
|
||||||
<div className="editor-placeholder">
|
<div className="text-center">
|
||||||
<div className="content-slots">
|
<div className="flex flex-col gap-3 mb-6">
|
||||||
{Object.entries(currentSlide.content).map(([slotId, content]) => (
|
{Object.entries(currentSlide.content).map(([slotId, content]) => (
|
||||||
<div key={slotId} className="content-slot">
|
<div key={slotId} className="flex justify-between items-center p-3 bg-muted rounded">
|
||||||
<label>{slotId}:</label>
|
<label className="font-medium text-secondary">{slotId}:</label>
|
||||||
<div className="slot-content">{content || '(empty)'}</div>
|
<div className="text-tertiary">{content || '(empty)'}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="placeholder-note">
|
<p className="text-tertiary text-sm">
|
||||||
Interactive slide editor will be implemented next
|
Interactive slide editor will be implemented next
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{currentSlide.notes && (
|
{currentSlide.notes && (
|
||||||
<div className="slide-notes-editor">
|
<div className="mt-4">
|
||||||
<h4>Speaker Notes</h4>
|
<h4 className="text-base font-semibold text-primary mb-2">Speaker Notes</h4>
|
||||||
<textarea
|
<textarea
|
||||||
value={currentSlide.notes}
|
value={currentSlide.notes}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@ -213,18 +211,18 @@ export const PresentationEditor: React.FC = () => {
|
|||||||
loggers.presentation.debug('Slide notes updated', { slideId: currentSlide.id, notesLength: e.target.value.length });
|
loggers.presentation.debug('Slide notes updated', { slideId: currentSlide.id, notesLength: e.target.value.length });
|
||||||
}}
|
}}
|
||||||
placeholder="Add speaker notes for this slide..."
|
placeholder="Add speaker notes for this slide..."
|
||||||
className="notes-textarea"
|
className="form-textarea"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="slide-error">
|
<div className="error-content">
|
||||||
<p>Invalid slide number</p>
|
<h2>Invalid slide number</h2>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="action-button secondary"
|
className="btn btn-secondary"
|
||||||
onClick={() => goToSlide(0)}
|
onClick={() => goToSlide(0)}
|
||||||
>
|
>
|
||||||
Go to First Slide
|
Go to First Slide
|
||||||
@ -260,3 +258,4 @@ export const PresentationEditor: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
/* Fullscreen presentation mode styles */
|
/* Presentation Mode - Surgical override approach to preserve theme styling */
|
||||||
|
|
||||||
|
/* Fullscreen presentation mode container */
|
||||||
.presentation-mode {
|
.presentation-mode {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: #000;
|
background: var(--color-black);
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -13,150 +15,114 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.presentation-mode.loading,
|
/* ===================================================================
|
||||||
.presentation-mode.error {
|
SLIDE CONTENT - Minimal interference with theme styling
|
||||||
flex-direction: column;
|
=================================================================== */
|
||||||
background: #1a1a1a;
|
|
||||||
color: #fff;
|
/* Override only the specific semantic framework classes that conflict */
|
||||||
}
|
.presentation-mode .slide-content .text-primary,
|
||||||
|
.presentation-mode .slide-content .text-secondary,
|
||||||
.presentation-mode .loading-spinner {
|
.presentation-mode .slide-content .text-tertiary,
|
||||||
font-size: 1.2rem;
|
.presentation-mode .slide-content .text-white,
|
||||||
padding: 2rem;
|
.presentation-mode .slide-content .bg-primary,
|
||||||
text-align: center;
|
.presentation-mode .slide-content .bg-secondary,
|
||||||
}
|
.presentation-mode .slide-content .bg-muted,
|
||||||
|
.presentation-mode .slide-content .bg-accent,
|
||||||
.presentation-mode .error-content {
|
.presentation-mode .slide-content .flex,
|
||||||
text-align: center;
|
.presentation-mode .slide-content .flex-col,
|
||||||
max-width: 600px;
|
.presentation-mode .slide-content .flex-row,
|
||||||
padding: 2rem;
|
.presentation-mode .slide-content .items-center,
|
||||||
}
|
.presentation-mode .slide-content .justify-center,
|
||||||
|
.presentation-mode .slide-content .gap-1,
|
||||||
.presentation-mode .error-content h2 {
|
.presentation-mode .slide-content .gap-2,
|
||||||
margin: 0 0 1rem 0;
|
.presentation-mode .slide-content .gap-3,
|
||||||
font-size: 2rem;
|
.presentation-mode .slide-content .gap-4,
|
||||||
font-weight: 300;
|
.presentation-mode .slide-content .p-1,
|
||||||
}
|
.presentation-mode .slide-content .p-2,
|
||||||
|
.presentation-mode .slide-content .p-3,
|
||||||
.presentation-mode .error-content p {
|
.presentation-mode .slide-content .p-4,
|
||||||
margin: 0 0 2rem 0;
|
.presentation-mode .slide-content .m-1,
|
||||||
font-size: 1.1rem;
|
.presentation-mode .slide-content .m-2,
|
||||||
opacity: 0.8;
|
.presentation-mode .slide-content .m-3,
|
||||||
line-height: 1.5;
|
.presentation-mode .slide-content .m-4,
|
||||||
}
|
.presentation-mode .slide-content .rounded,
|
||||||
|
.presentation-mode .slide-content .rounded-lg,
|
||||||
.presentation-mode .exit-button {
|
.presentation-mode .slide-content .shadow,
|
||||||
background: #fff;
|
.presentation-mode .slide-content .btn,
|
||||||
color: #000;
|
.presentation-mode .slide-content .card {
|
||||||
border: none;
|
/* Disable semantic classes within slide content */
|
||||||
padding: 0.75rem 1.5rem;
|
all: revert !important;
|
||||||
font-size: 1rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-mode .exit-button:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fullscreen slide container */
|
|
||||||
.presentation-mode.fullscreen {
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-mode .slide-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure theme variables take precedence over semantic framework */
|
||||||
.presentation-mode .slide-content {
|
.presentation-mode .slide-content {
|
||||||
max-width: 90vw;
|
/* Let theme CSS control fonts and colors */
|
||||||
max-height: 90vh;
|
font-family: var(--theme-font-body, system-ui, sans-serif) !important;
|
||||||
background: #fff;
|
background: var(--theme-background, var(--color-black)) !important;
|
||||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
color: var(--theme-text, white) !important;
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Aspect ratio classes for slides */
|
/* Force theme slot styling to override semantic framework */
|
||||||
.presentation-mode .slide-content.aspect-16-9 {
|
.presentation-mode .slide-content .slot[data-slot="title"] {
|
||||||
aspect-ratio: 16/9;
|
color: var(--theme-primary, white) !important;
|
||||||
width: min(90vw, calc(90vh * 16/9));
|
font-family: var(--theme-font-heading, var(--theme-font-body, system-ui, sans-serif)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.presentation-mode .slide-content.aspect-4-3 {
|
.presentation-mode .slide-content .slot[data-slot="subtitle"],
|
||||||
aspect-ratio: 4/3;
|
.presentation-mode .slide-content .slot[data-slot="content"],
|
||||||
width: min(90vw, calc(90vh * 4/3));
|
.presentation-mode .slide-content .slot[data-slot="content1"],
|
||||||
|
.presentation-mode .slide-content .slot[data-slot="content2"] {
|
||||||
|
color: var(--theme-text-secondary, #94a4ab) !important;
|
||||||
|
font-family: var(--theme-font-body, system-ui, sans-serif) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.presentation-mode .slide-content.aspect-16-10 {
|
/* Ensure code blocks work properly */
|
||||||
aspect-ratio: 16/10;
|
.presentation-mode .slide-content .slide-code,
|
||||||
width: min(90vw, calc(90vh * 16/10));
|
.presentation-mode .slide-content pre.slot[data-type="code"],
|
||||||
|
.presentation-mode .slide-content .language-javascript,
|
||||||
|
.presentation-mode .slide-content .hljs-code {
|
||||||
|
background: #1e1e1e !important;
|
||||||
|
color: #e6e6e6 !important;
|
||||||
|
font-family: var(--theme-font-code, 'Consolas', monospace) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navigation indicator */
|
/* Ensure mermaid diagrams work properly */
|
||||||
.presentation-mode .navigation-indicator {
|
.presentation-mode .slide-content .mermaid-container,
|
||||||
|
.presentation-mode .slide-content .mermaid-diagram {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
NAVIGATION INDICATOR - Uses semantic framework (outside slides)
|
||||||
|
=================================================================== */
|
||||||
|
|
||||||
|
/* Navigation indicator overlay */
|
||||||
|
.navigation-indicator {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 2rem;
|
bottom: 2rem;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.8);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
color: #fff;
|
color: var(--color-white);
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
z-index: 10000;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.presentation-mode:hover .navigation-indicator {
|
.presentation-mode:hover .navigation-indicator {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.presentation-mode .slide-counter {
|
/* Ensure navigation text uses white color and proper fonts */
|
||||||
font-size: 1.1rem;
|
.navigation-indicator,
|
||||||
font-weight: 500;
|
.navigation-indicator * {
|
||||||
}
|
color: var(--color-white) !important;
|
||||||
|
font-family: system-ui, -apple-system, sans-serif !important;
|
||||||
.presentation-mode .navigation-hints {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-mode .navigation-hints span {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive adjustments */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.presentation-mode .navigation-indicator {
|
|
||||||
bottom: 1rem;
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-mode .navigation-hints {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-mode .slide-content {
|
|
||||||
max-width: 95vw;
|
|
||||||
max-height: 95vh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide scrollbars in presentation mode */
|
/* Hide scrollbars in presentation mode */
|
||||||
@ -169,8 +135,11 @@
|
|||||||
display: none; /* Chrome/Safari */
|
display: none; /* Chrome/Safari */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure theme styles work in fullscreen */
|
/* Responsive adjustments */
|
||||||
.presentation-mode .slide-content > * {
|
@media (max-width: 768px) {
|
||||||
width: 100%;
|
.navigation-indicator {
|
||||||
height: 100%;
|
bottom: 1rem;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -123,19 +123,21 @@ export const PresentationMode: React.FC = () => {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="presentation-mode loading">
|
<div className="presentation-mode">
|
||||||
|
<div className="loading-content text-white">
|
||||||
<div className="loading-spinner">Loading presentation...</div>
|
<div className="loading-spinner">Loading presentation...</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="presentation-mode error">
|
<div className="presentation-mode">
|
||||||
<div className="error-content">
|
<div className="error-content text-white">
|
||||||
<h2>Error Loading Presentation</h2>
|
<h2>Error Loading Presentation</h2>
|
||||||
<p>{error}</p>
|
<p>{error}</p>
|
||||||
<button onClick={() => navigate(-1)} className="exit-button">
|
<button onClick={() => navigate(-1)} className="btn btn-primary">
|
||||||
Exit Presentation Mode
|
Exit Presentation Mode
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -145,10 +147,10 @@ export const PresentationMode: React.FC = () => {
|
|||||||
|
|
||||||
if (!presentation || !theme) {
|
if (!presentation || !theme) {
|
||||||
return (
|
return (
|
||||||
<div className="presentation-mode error">
|
<div className="presentation-mode">
|
||||||
<div className="error-content">
|
<div className="error-content text-white">
|
||||||
<h2>Presentation Not Found</h2>
|
<h2>Presentation Not Found</h2>
|
||||||
<button onClick={() => navigate(-1)} className="exit-button">
|
<button onClick={() => navigate(-1)} className="btn btn-primary">
|
||||||
Exit Presentation Mode
|
Exit Presentation Mode
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -158,11 +160,11 @@ export const PresentationMode: React.FC = () => {
|
|||||||
|
|
||||||
if (presentation.slides.length === 0) {
|
if (presentation.slides.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="presentation-mode error">
|
<div className="presentation-mode">
|
||||||
<div className="error-content">
|
<div className="error-content text-white">
|
||||||
<h2>No Slides Available</h2>
|
<h2>No Slides Available</h2>
|
||||||
<p>This presentation is empty.</p>
|
<p>This presentation is empty.</p>
|
||||||
<button onClick={() => navigate(-1)} className="exit-button">
|
<button onClick={() => navigate(-1)} className="btn btn-primary">
|
||||||
Exit Presentation Mode
|
Exit Presentation Mode
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -173,16 +175,16 @@ export const PresentationMode: React.FC = () => {
|
|||||||
const totalSlides = presentation.slides.length;
|
const totalSlides = presentation.slides.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="presentation-mode fullscreen">
|
<div className="presentation-mode">
|
||||||
<div className="slide-container">
|
<div className={`slide-container ${presentation.metadata.aspectRatio ? `aspect-${presentation.metadata.aspectRatio.replace(':', '-')}` : 'aspect-16-9'}`}>
|
||||||
{isRenderingSlide && (
|
{isRenderingSlide && (
|
||||||
<div className="slide-loading">
|
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex flex-col items-center gap-4 text-white text-lg z-10">
|
||||||
<div className="loading-spinner"></div>
|
<div className="loading-spinner text-xl opacity-80"></div>
|
||||||
<span>Rendering slide...</span>
|
<span>Rendering slide...</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={`slide-content ${presentation.metadata.aspectRatio ? `aspect-${presentation.metadata.aspectRatio.replace(':', '-')}` : 'aspect-16-9'}`}
|
className="slide-content"
|
||||||
dangerouslySetInnerHTML={{ __html: renderedSlideContent }}
|
dangerouslySetInnerHTML={{ __html: renderedSlideContent }}
|
||||||
style={{ opacity: isRenderingSlide ? 0.5 : 1 }}
|
style={{ opacity: isRenderingSlide ? 0.5 : 1 }}
|
||||||
/>
|
/>
|
||||||
@ -190,15 +192,17 @@ export const PresentationMode: React.FC = () => {
|
|||||||
|
|
||||||
{/* Navigation indicator */}
|
{/* Navigation indicator */}
|
||||||
<div className="navigation-indicator">
|
<div className="navigation-indicator">
|
||||||
<span className="slide-counter">
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<span className="text-lg font-medium">
|
||||||
{currentSlideIndex + 1} / {totalSlides}
|
{currentSlideIndex + 1} / {totalSlides}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="navigation-hints">
|
<div className="flex gap-4 text-sm opacity-80">
|
||||||
<span>← → Space: Navigate</span>
|
<span>← → Space: Navigate</span>
|
||||||
<span>Esc: Exit</span>
|
<span>Esc: Exit</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -1,53 +1,21 @@
|
|||||||
|
/* Slides Sidebar Component - Minimal overrides using semantic classes */
|
||||||
|
|
||||||
|
/* Component-specific layout only */
|
||||||
.slide-sidebar {
|
.slide-sidebar {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
background: #ffffff;
|
|
||||||
border-right: 1px solid #e5e7eb;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-header {
|
/* Custom hover state for accent button */
|
||||||
display: flex;
|
.slide-sidebar .bg-accent:hover:not(:disabled) {
|
||||||
justify-content: space-between;
|
background: var(--text-link-hover);
|
||||||
align-items: center;
|
|
||||||
padding: 1rem;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
background: #f9fafb;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-header h3 {
|
/* Responsive behavior for mobile */
|
||||||
font-size: 1rem;
|
@media (max-width: 768px) {
|
||||||
font-weight: 600;
|
.slide-sidebar {
|
||||||
color: #1f2937;
|
width: 200px;
|
||||||
margin: 0;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.add-slide-button {
|
|
||||||
background: #3b82f6;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-slide-button:hover {
|
|
||||||
background: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slides-list {
|
|
||||||
flex: 1;
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
@ -79,19 +79,20 @@ export const SlidesSidebar: React.FC<SlidesSidebarProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="slide-sidebar">
|
<aside className="slide-sidebar">
|
||||||
<div className="sidebar-header">
|
<header className="flex justify-between items-center p-4 border-b bg-muted">
|
||||||
<h3>Slides</h3>
|
<h3 className="text-base font-semibold text-primary m-0">Slides</h3>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="add-slide-button"
|
className="btn-icon rounded-full bg-accent text-white hover:bg-accent-hover"
|
||||||
onClick={onAddSlide}
|
onClick={onAddSlide}
|
||||||
title="Add new slide"
|
title="Add new slide"
|
||||||
|
disabled={saving}
|
||||||
>
|
>
|
||||||
+
|
+
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
<div className="slides-list">
|
<div className="flex-1 p-4 flex flex-col gap-4 overflow-y-auto">
|
||||||
{slides.map((slide, index) => (
|
{slides.map((slide, index) => (
|
||||||
<SlideThumbnail
|
<SlideThumbnail
|
||||||
key={slide.id}
|
key={slide.id}
|
||||||
|
|||||||
@ -2,73 +2,18 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.themes-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-card {
|
|
||||||
background: white;
|
|
||||||
border: 2px solid #e2e8f0;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-card:hover {
|
|
||||||
border-color: #cbd5e1;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-card.selected {
|
|
||||||
border-color: #22c55e;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(34, 197, 94, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Theme preview uses .preview-container semantic class */
|
||||||
.theme-preview {
|
.theme-preview {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-muted) 100%);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-preview-strip {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-swatch {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 3px solid white;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-colors {
|
|
||||||
color: #9ca3af;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
text-align: center;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selection-indicator {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.75rem;
|
|
||||||
right: 0.75rem;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-info {
|
.theme-info {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
@ -77,42 +22,27 @@
|
|||||||
margin: 0 0 0.5rem 0;
|
margin: 0 0 0.5rem 0;
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1e293b;
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-description {
|
.theme-description {
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
color: #64748b;
|
color: var(--text-secondary);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-meta {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-stats {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layouts-count {
|
.layouts-count {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #475569;
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-author {
|
.theme-author {
|
||||||
color: #6b7280;
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-version {
|
.theme-version {
|
||||||
color: #9ca3af;
|
color: var(--text-tertiary);
|
||||||
font-family: 'Courier New', monospace;
|
font-family: 'Courier New', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,31 +51,16 @@
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-link {
|
|
||||||
color: #3b82f6;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-link:hover {
|
|
||||||
background: #eff6ff;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Empty State */
|
/* Empty State */
|
||||||
.empty-state {
|
.empty-state {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 3rem;
|
padding: 3rem;
|
||||||
color: #6b7280;
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state h3 {
|
.empty-state h3 {
|
||||||
margin: 0 0 0.5rem 0;
|
margin: 0 0 0.5rem 0;
|
||||||
color: #374151;
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state p {
|
.empty-state p {
|
||||||
|
|||||||
@ -1,67 +1,96 @@
|
|||||||
|
/* Error State Component - Fully refactored to use semantic classes */
|
||||||
|
|
||||||
|
/* Use semantic .error-content class from application.css */
|
||||||
.error-content {
|
.error-content {
|
||||||
text-align: center;
|
/* Base styling already defined in application.css */
|
||||||
padding: 2rem;
|
|
||||||
min-height: 200px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 40vh;
|
||||||
|
text-align: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-content h2 {
|
.error-content h2 {
|
||||||
color: #dc2626;
|
color: var(--text-error);
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 1rem;
|
margin: 0 0 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-content p {
|
.error-content p {
|
||||||
color: #6b7280;
|
color: var(--text-secondary);
|
||||||
margin-bottom: 2rem;
|
margin: 0 0 2rem 0;
|
||||||
max-width: 400px;
|
max-width: 500px;
|
||||||
line-height: 1.5;
|
line-height: 1.6;
|
||||||
|
font-size: 0.9375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Error icon styling using semantic classes */
|
||||||
|
.error-content::before {
|
||||||
|
content: '⚠️';
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compact error state for inline usage */
|
||||||
|
.error-content.compact {
|
||||||
|
min-height: auto;
|
||||||
|
padding: 1.5rem;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content.compact h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content.compact p {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content.compact::before {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error actions container using semantic flex utilities */
|
||||||
.error-actions {
|
.error-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
/* Responsive design for mobile */
|
||||||
padding: 0.75rem 1.5rem;
|
@media (max-width: 768px) {
|
||||||
border-radius: 0.5rem;
|
.error-content {
|
||||||
font-weight: 500;
|
padding: 1rem;
|
||||||
|
min-height: 30vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content p {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
border: none;
|
max-width: 100%;
|
||||||
cursor: pointer;
|
}
|
||||||
transition: all 0.2s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.secondary {
|
.error-actions {
|
||||||
background: #f9fafb;
|
flex-direction: column;
|
||||||
color: #374151;
|
width: 100%;
|
||||||
border: 1px solid #d1d5db;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.button.secondary:hover {
|
.error-actions .btn {
|
||||||
background: #f3f4f6;
|
width: 100%;
|
||||||
border-color: #9ca3af;
|
max-width: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-link {
|
|
||||||
color: #3b82f6;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-link:hover {
|
|
||||||
color: #2563eb;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
@ -1,31 +1,39 @@
|
|||||||
|
/* Loading State Component - Refactored to use semantic classes */
|
||||||
|
|
||||||
|
/* Use semantic .loading-content class from application.css */
|
||||||
.loading-content {
|
.loading-content {
|
||||||
display: flex;
|
/* Already defined in application.css with proper semantic styling */
|
||||||
justify-content: center;
|
/* Override min-height if needed for this specific component */
|
||||||
align-items: center;
|
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Use semantic .loading-spinner class from application.css */
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
color: #6b7280;
|
/* Base styling already defined in application.css */
|
||||||
|
color: var(--text-secondary);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Spinner icon using semantic animation and CSS variables */
|
||||||
.loading-spinner::after {
|
.loading-spinner::after {
|
||||||
content: '';
|
content: '';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-left: 10px;
|
border: 2px solid var(--border-secondary);
|
||||||
border: 2px solid #e5e7eb;
|
border-top: 2px solid var(--color-blue-500);
|
||||||
border-top: 2px solid #3b82f6;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Use semantic animation from application.css */
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
|
/* Animation already defined in application.css */
|
||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
@ -4,15 +4,32 @@ import './LoadingState.css';
|
|||||||
interface LoadingStateProps {
|
interface LoadingStateProps {
|
||||||
message?: string;
|
message?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
|
centered?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LoadingState: React.FC<LoadingStateProps> = ({
|
export const LoadingState: React.FC<LoadingStateProps> = ({
|
||||||
message = "Loading...",
|
message = "Loading...",
|
||||||
className = ""
|
className = "",
|
||||||
|
size = 'md',
|
||||||
|
centered = true
|
||||||
}) => {
|
}) => {
|
||||||
|
const containerClasses = [
|
||||||
|
centered ? 'loading-content' : 'flex items-center gap-3',
|
||||||
|
size === 'sm' && 'p-4 min-h-20',
|
||||||
|
size === 'lg' && 'p-8 min-h-32',
|
||||||
|
className
|
||||||
|
].filter(Boolean).join(' ');
|
||||||
|
|
||||||
|
const spinnerClasses = [
|
||||||
|
'loading-spinner',
|
||||||
|
size === 'sm' && 'text-xs',
|
||||||
|
size === 'lg' && 'text-base'
|
||||||
|
].filter(Boolean).join(' ');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`loading-content ${className}`.trim()}>
|
<div className={containerClasses}>
|
||||||
<div className="loading-spinner">{message}</div>
|
<div className={spinnerClasses}>{message}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -1,143 +1,48 @@
|
|||||||
/* Slide Thumbnail Component */
|
/* Slide Thumbnail Component - Minimal overrides using semantic classes */
|
||||||
|
|
||||||
|
/* Component-specific layout for slide thumbnails */
|
||||||
.slide-thumbnail {
|
.slide-thumbnail {
|
||||||
|
/* Override card-interactive to use flex column layout */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border: 2px solid var(--border-primary);
|
min-height: 160px;
|
||||||
border-radius: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Interactive States - shared box-shadow */
|
/* Positioning for the badge number */
|
||||||
.slide-thumbnail:hover,
|
.slide-thumbnail .badge-number {
|
||||||
.slide-thumbnail.active {
|
|
||||||
border-color: var(--border-accent);
|
|
||||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-thumbnail.active {
|
|
||||||
background: var(--bg-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Thumbnail Number Badge */
|
|
||||||
.thumbnail-number {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
left: 0.5rem;
|
left: 0.5rem;
|
||||||
background: var(--color-gray-700);
|
|
||||||
color: var(--color-white);
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-thumbnail.active .thumbnail-number {
|
/* Preview area specific styling */
|
||||||
background: var(--color-blue-500);
|
.slide-thumbnail .preview-container {
|
||||||
}
|
flex: 1;
|
||||||
|
|
||||||
/* Preview Area */
|
|
||||||
.thumbnail-preview {
|
|
||||||
padding: 2rem 1rem 1rem;
|
padding: 2rem 1rem 1rem;
|
||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
background: var(--bg-muted);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail-content {
|
/* Component-specific drag handle positioning */
|
||||||
text-align: center;
|
.slide-thumbnail .drag-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0.5rem;
|
||||||
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Content Labels - shared text styling */
|
/* Show drag handle on hover */
|
||||||
.thumbnail-content span {
|
.slide-thumbnail:hover .drag-handle {
|
||||||
display: block;
|
opacity: 1;
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-name {
|
/* Hide drag handle when not draggable */
|
||||||
font-weight: 600;
|
.slide-thumbnail:not(.draggable) .drag-handle {
|
||||||
color: var(--text-secondary);
|
display: none;
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-count {
|
|
||||||
font-size: 0.625rem; /* Override smaller size */
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Action Bar */
|
|
||||||
.thumbnail-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-top: 1px solid var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Action Buttons - consolidated common properties */
|
|
||||||
.thumbnail-action {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 0.25rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button States - cascading hover effects */
|
|
||||||
.thumbnail-action:hover:not(:disabled) {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnail-action:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Specific Action Types - override general hover */
|
|
||||||
.thumbnail-action.edit:hover:not(:disabled) {
|
|
||||||
background: var(--color-blue-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnail-action.delete:hover:not(:disabled) {
|
|
||||||
background: var(--color-red-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Drag and Drop Styles */
|
|
||||||
.slide-thumbnail[draggable="true"] {
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-thumbnail[draggable="true"]:active {
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-thumbnail.dragged {
|
|
||||||
opacity: 0.5;
|
|
||||||
transform: rotate(2deg);
|
|
||||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-thumbnail.drag-over {
|
|
||||||
border-color: var(--color-blue-500);
|
|
||||||
background: var(--color-blue-50);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom drop indicator line for slide thumbnails */
|
||||||
.slide-thumbnail.drag-over::before {
|
.slide-thumbnail.drag-over::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -147,27 +52,5 @@
|
|||||||
height: 2px;
|
height: 2px;
|
||||||
background: var(--color-blue-500);
|
background: var(--color-blue-500);
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
}
|
z-index: 15;
|
||||||
|
|
||||||
/* Drag Handle */
|
|
||||||
.drag-handle {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 0.5rem;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: bold;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-thumbnail:hover .drag-handle {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-thumbnail[draggable="false"] .drag-handle {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,16 +51,24 @@ export const SlideThumbnail: React.FC<SlideThumbnailProps> = ({
|
|||||||
onDoubleClick();
|
onDoubleClick();
|
||||||
};
|
};
|
||||||
|
|
||||||
const className = [
|
// Build semantic class names
|
||||||
'slide-thumbnail',
|
const containerClasses = [
|
||||||
isActive && 'active',
|
'card-interactive',
|
||||||
|
'slide-thumbnail', // Keep for component-specific overrides
|
||||||
|
isActive && 'selected',
|
||||||
isDragged && 'dragged',
|
isDragged && 'dragged',
|
||||||
isDraggedOver && 'drag-over'
|
isDraggedOver && 'drag-over',
|
||||||
|
!isDisabled && 'draggable'
|
||||||
|
].filter(Boolean).join(' ');
|
||||||
|
|
||||||
|
const badgeClasses = [
|
||||||
|
'badge-number',
|
||||||
|
isActive && 'active'
|
||||||
].filter(Boolean).join(' ');
|
].filter(Boolean).join(' ');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className}
|
className={containerClasses}
|
||||||
draggable={!isDisabled}
|
draggable={!isDisabled}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
@ -71,19 +79,23 @@ export const SlideThumbnail: React.FC<SlideThumbnailProps> = ({
|
|||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
title={`Slide ${index + 1}${isDragged ? ' (dragging...)' : ''}`}
|
title={`Slide ${index + 1}${isDragged ? ' (dragging...)' : ''}`}
|
||||||
>
|
>
|
||||||
<div className="thumbnail-number">{index + 1}</div>
|
<div className={badgeClasses}>{index + 1}</div>
|
||||||
<div className="thumbnail-preview">
|
|
||||||
<div className="thumbnail-content">
|
<div className="preview-container">
|
||||||
<span className="layout-name">{slide.layoutId}</span>
|
<div className="preview-content">
|
||||||
<span className="content-count">
|
<div className="text-center">
|
||||||
|
<span className="font-semibold text-secondary text-xs block mb-1">{slide.layoutId}</span>
|
||||||
|
<span className="text-xs text-tertiary">
|
||||||
{Object.keys(slide.content).length} items
|
{Object.keys(slide.content).length} items
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="thumbnail-actions">
|
</div>
|
||||||
|
|
||||||
|
<div className="action-bar">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="thumbnail-action edit"
|
className="action-btn edit"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onEdit();
|
onEdit();
|
||||||
@ -95,7 +107,7 @@ export const SlideThumbnail: React.FC<SlideThumbnailProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="thumbnail-action"
|
className="action-btn"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDuplicate();
|
onDuplicate();
|
||||||
@ -107,7 +119,7 @@ export const SlideThumbnail: React.FC<SlideThumbnailProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="thumbnail-action delete"
|
className="action-btn delete"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDelete();
|
onDelete();
|
||||||
|
|||||||
@ -1,62 +1,67 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
interface ErrorStateProps {
|
interface ErrorStateProps {
|
||||||
error: string;
|
error: string;
|
||||||
presentationId?: string;
|
presentationId?: string;
|
||||||
|
title?: string;
|
||||||
|
backText?: string;
|
||||||
|
backLink?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ErrorState: React.FC<ErrorStateProps> = ({ error, presentationId }) => {
|
export const ErrorState: React.FC<ErrorStateProps> = ({
|
||||||
|
error,
|
||||||
|
presentationId,
|
||||||
|
title = "Slide Editor Error",
|
||||||
|
backText,
|
||||||
|
backLink
|
||||||
|
}) => {
|
||||||
|
// Determine back navigation
|
||||||
|
const getBackNavigation = () => {
|
||||||
|
if (backLink && backText) {
|
||||||
|
return { href: backLink, text: backText };
|
||||||
|
}
|
||||||
|
if (presentationId) {
|
||||||
|
return {
|
||||||
|
href: `/presentations/${presentationId}/edit/slides/1`,
|
||||||
|
text: "← Back to Presentation"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { href: "/presentations", text: "← Back to Presentations" };
|
||||||
|
};
|
||||||
|
|
||||||
|
const { href, text } = getBackNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="slide-editor">
|
<div className="page-container">
|
||||||
<header className="slide-editor-header">
|
<header className="page-header">
|
||||||
<div className="editor-info">
|
<a href={href} className="back-link">
|
||||||
{presentationId ? (
|
{text}
|
||||||
<Link to={`/presentations/${presentationId}/edit/slides/1`} className="back-button">
|
</a>
|
||||||
← Back to Presentation
|
<div className="flex-1">
|
||||||
</Link>
|
<h1 className="text-xl font-semibold text-primary m-0">{title}</h1>
|
||||||
) : (
|
|
||||||
<Link to="/presentations" className="back-button">
|
|
||||||
← Back to Presentations
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<div className="editor-title">
|
|
||||||
<h1>Slide Editor Error</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="slide-editor-content">
|
<main className="page-content">
|
||||||
<div className="error-container">
|
|
||||||
<div className="error-content">
|
<div className="error-content">
|
||||||
<h2>Unable to Load Slide Editor</h2>
|
<h2>Unable to Load Content</h2>
|
||||||
<p>{error}</p>
|
<p>{error}</p>
|
||||||
|
|
||||||
<div className="error-actions">
|
<div className="flex gap-4 justify-center flex-wrap">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => window.location.reload()}
|
onClick={() => window.location.reload()}
|
||||||
className="action-button primary"
|
className="btn btn-primary"
|
||||||
>
|
>
|
||||||
Reload Page
|
Reload Page
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{presentationId ? (
|
<a
|
||||||
<Link
|
href={href}
|
||||||
to={`/presentations/${presentationId}/edit/slides/1`}
|
className="btn btn-secondary"
|
||||||
className="action-button secondary"
|
|
||||||
>
|
>
|
||||||
Return to Presentation
|
{presentationId ? "Return to Presentation" : "Back to Presentations"}
|
||||||
</Link>
|
</a>
|
||||||
) : (
|
|
||||||
<Link
|
|
||||||
to="/presentations"
|
|
||||||
className="action-button secondary"
|
|
||||||
>
|
|
||||||
Back to Presentations
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
/* Application CSS - Consolidated Styles */
|
/* Application CSS - Consolidated Styles */
|
||||||
/* Import color system first */
|
/* Import color system first */
|
||||||
@import './colors.css';
|
@import './colors.css';
|
||||||
|
/* Import semantic component classes */
|
||||||
|
@import './semantic-components.css';
|
||||||
|
|
||||||
/* =================================================================
|
/* =================================================================
|
||||||
BASE APP LAYOUT
|
BASE APP LAYOUT
|
||||||
@ -85,6 +87,15 @@
|
|||||||
.fixed { position: fixed; }
|
.fixed { position: fixed; }
|
||||||
.sticky { position: sticky; }
|
.sticky { position: sticky; }
|
||||||
|
|
||||||
|
/* Positioning utilities */
|
||||||
|
.top-1\/2 { top: 50%; }
|
||||||
|
.left-1\/2 { left: 50%; }
|
||||||
|
|
||||||
|
/* Transform utilities */
|
||||||
|
.transform { transform: translateX(var(--tw-translate-x, 0)) translateY(var(--tw-translate-y, 0)) rotate(var(--tw-rotate, 0)) skewX(var(--tw-skew-x, 0)) skewY(var(--tw-skew-y, 0)) scaleX(var(--tw-scale-x, 1)) scaleY(var(--tw-scale-y, 1)); }
|
||||||
|
.-translate-x-1\/2 { --tw-translate-x: -50%; }
|
||||||
|
.-translate-y-1\/2 { --tw-translate-y: -50%; }
|
||||||
|
|
||||||
/* =================================================================
|
/* =================================================================
|
||||||
UTILITY CLASSES - Spacing
|
UTILITY CLASSES - Spacing
|
||||||
================================================================= */
|
================================================================= */
|
||||||
@ -229,15 +240,6 @@
|
|||||||
.shadow-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); }
|
.shadow-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); }
|
||||||
.shadow-lg { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); }
|
.shadow-lg { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); }
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
UTILITY CLASSES - Aspect Ratios
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
.aspect-16-9 { aspect-ratio: 16 / 9; }
|
|
||||||
.aspect-4-3 { aspect-ratio: 4 / 3; }
|
|
||||||
.aspect-16-10 { aspect-ratio: 16 / 10; }
|
|
||||||
.aspect-square { aspect-ratio: 1 / 1; }
|
|
||||||
|
|
||||||
/* =================================================================
|
/* =================================================================
|
||||||
UTILITY CLASSES - Transitions & Animations
|
UTILITY CLASSES - Transitions & Animations
|
||||||
================================================================= */
|
================================================================= */
|
||||||
@ -779,3 +781,33 @@
|
|||||||
padding: 1.5rem 1rem;
|
padding: 1.5rem 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
UTILITY CLASSES - Background Colors
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.bg-primary { background-color: var(--bg-primary); }
|
||||||
|
.bg-secondary { background-color: var(--bg-secondary); }
|
||||||
|
.bg-muted { background-color: var(--bg-muted); }
|
||||||
|
.bg-accent { background-color: var(--text-accent); }
|
||||||
|
.bg-hover { background-color: var(--bg-hover); }
|
||||||
|
|
||||||
|
/* Background hover states */
|
||||||
|
.hover\:bg-accent-hover:hover { background-color: var(--text-link-hover); }
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
UTILITY CLASSES - Text Colors
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.text-primary { color: var(--text-primary); }
|
||||||
|
.text-secondary { color: var(--text-secondary); }
|
||||||
|
.text-tertiary { color: var(--text-tertiary); }
|
||||||
|
.text-white { color: var(--color-white); }
|
||||||
|
.text-warning { color: var(--text-warning); }
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
UTILITY CLASSES - Overflow
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.overflow-y-auto { overflow-y: auto; }
|
||||||
|
.overflow-hidden { overflow: hidden; }
|
||||||
|
|||||||
384
src/styles/semantic-components.css
Normal file
384
src/styles/semantic-components.css
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
/* Semantic Component Classes - Consolidation of common patterns */
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
CARD VARIANTS - Consolidate duplicate card patterns
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
/* Interactive Card - Clickable cards with hover states */
|
||||||
|
.card-interactive {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 2px solid var(--border-primary);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-interactive:hover {
|
||||||
|
border-color: var(--border-hover);
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-interactive:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--border-accent);
|
||||||
|
box-shadow: 0 0 0 3px var(--bg-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-interactive.selected {
|
||||||
|
border-color: var(--border-accent);
|
||||||
|
background: var(--bg-accent);
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selection Indicator */
|
||||||
|
.selection-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.75rem;
|
||||||
|
right: 0.75rem;
|
||||||
|
z-index: 10;
|
||||||
|
color: var(--color-success);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
BADGE SYSTEM - Consistent status indicators
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-primary {
|
||||||
|
background: var(--bg-accent);
|
||||||
|
color: var(--text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-secondary {
|
||||||
|
background: var(--bg-muted);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success {
|
||||||
|
background: var(--color-success-bg, #dcfce7);
|
||||||
|
color: var(--color-success, #16a34a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-error {
|
||||||
|
background: var(--color-red-100);
|
||||||
|
color: var(--text-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Number Badge - For counts, slide numbers, etc. */
|
||||||
|
.badge-number {
|
||||||
|
background: var(--color-gray-700);
|
||||||
|
color: var(--color-white);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
min-width: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-number.active {
|
||||||
|
background: var(--color-blue-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
PREVIEW CONTAINERS - Consistent preview areas
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.preview-container {
|
||||||
|
position: relative;
|
||||||
|
background: var(--bg-muted);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-placeholder {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Color Preview Strip */
|
||||||
|
.color-preview-strip {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-swatch {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid var(--bg-secondary);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.color-swatch {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
ACTION BARS - Consistent action button layouts
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.action-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar.justify-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar.justify-end {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Button - Small buttons for action bars */
|
||||||
|
.action-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover:not(:disabled) {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.edit:hover:not(:disabled) {
|
||||||
|
background: var(--color-blue-100);
|
||||||
|
color: var(--color-blue-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.delete:hover:not(:disabled) {
|
||||||
|
background: var(--color-red-100);
|
||||||
|
color: var(--text-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
GRID LAYOUTS - Consistent responsive grids
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.grid-responsive {
|
||||||
|
display: grid;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-responsive-sm {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-responsive-md {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-responsive-lg {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid-responsive-sm,
|
||||||
|
.grid-responsive-md,
|
||||||
|
.grid-responsive-lg {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
METADATA DISPLAY - Consistent info layouts
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.meta-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-stat {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-stat-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-stat-value {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.meta-info {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
DRAG & DROP STATES - Consistent drag feedback
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.draggable {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draggable:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragged {
|
||||||
|
opacity: 0.5;
|
||||||
|
transform: rotate(2deg);
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-over {
|
||||||
|
border-color: var(--color-blue-500);
|
||||||
|
background: var(--color-blue-50);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-over::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--color-blue-500);
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0.5rem;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draggable:hover .drag-handle {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
LINK STYLES - Consistent link appearances
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: var(--text-link);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
color: var(--text-link-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-subtle {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-subtle:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Back Link - Consistent navigation */
|
||||||
|
.back-link {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user