1334 lines
47 KiB
Markdown
1334 lines
47 KiB
Markdown
# Timebank.cc Style Guide
|
|
|
|
This document outlines the styling conventions and theme system for the Timebank.cc application. It serves as the authoritative reference for all UI styling patterns used throughout the application.
|
|
|
|
**Reference Implementation:** `resources/views/mailings/manage.blade.php` and `resources/views/livewire/mailings/manage.blade.php` demonstrate all core UI patterns and should be consulted when building new views.
|
|
|
|
## Theme System Overview
|
|
|
|
The application uses a sophisticated configuration-based multi-theme system that allows different installations to have unique visual identities. The system is built on four core components:
|
|
|
|
### Core Architecture
|
|
|
|
1. **Theme Configuration**: `config/themes.php` - Centralized theme definitions with colors, typography, and metadata
|
|
2. **Theme Helpers**: `app/Helpers/ThemeHelper.php` - PHP functions for accessing theme data throughout the application
|
|
3. **CSS Custom Properties**: `resources/css/app.css` - Automatically generated theme-aware CSS variables
|
|
4. **Tailwind Integration**: `tailwind.config.js` - Theme-aware utility classes using CSS custom properties
|
|
|
|
### Available Themes
|
|
|
|
1. **timebank_cc** (default) - Gray, black and white professional theme
|
|
2. **uuro** - Black, white and cyan theme with vibrant accents (inspired by buuro.net)
|
|
3. **vegetable** - Green, natural theme with earth tones (inspired by lekkernassuh.org)
|
|
4. **yellow** - High-contrast yellow and black theme (inspired by yellowbrick.nl)
|
|
|
|
Themes are activated via the `TIMEBANK_THEME` environment variable and can be switched per installation without code changes.
|
|
|
|
## Theme Configuration Structure
|
|
|
|
Each theme in `config/themes.php` follows this standardized structure:
|
|
|
|
```php
|
|
'theme_name' => [
|
|
'name' => 'Display Name',
|
|
'description' => 'Theme description',
|
|
'colors' => [
|
|
// Primary color scale (required)
|
|
'primary' => [
|
|
50 => '#F6F8F9', // Lightest
|
|
100 => '#EBEDEE',
|
|
200 => '#DCDEDF',
|
|
300 => '#CDCFD0', // Border colors
|
|
400 => '#B8BABB', // Muted elements
|
|
500 => '#999B9C', // Main brand color
|
|
600 => '#6E6F70',
|
|
700 => '#434343',
|
|
800 => '#242424',
|
|
900 => '#1A1A1A', // Darkest, headers
|
|
],
|
|
|
|
// Semantic colors (required)
|
|
'secondary' => '#6B7280', // Secondary brand color
|
|
'accent' => '#434343', // Accent color for highlights
|
|
'brand' => '#000000', // Brand/header backgrounds
|
|
'logo' => '#000000', // Logo and icon colors
|
|
'background' => '#FFFFFF', // Page background
|
|
'surface' => '#F9FAFB', // Card/surface backgrounds
|
|
|
|
// Text colors (required)
|
|
'text' => [
|
|
'primary' => '#111827', // Main text
|
|
'secondary' => '#6B7280', // Secondary text, subtitles
|
|
'light' => '#9CA3AF', // Muted text, placeholders
|
|
],
|
|
|
|
// Theme-specific colors (optional)
|
|
'success' => '#00D084', // Uuro: success states
|
|
'danger' => '#CF2E2E', // Uuro: error states
|
|
'warning' => '#FF6900', // Uuro: warning states
|
|
'info' => '#0693E3', // Uuro: info states
|
|
'surface_alt' => '#E2E2C7', // Vegetable: alternative surface
|
|
'neutral' => [ // Yellow: neutral color scale
|
|
'dark' => '#2F2E2E',
|
|
'medium' => '#5D5B5B',
|
|
'light' => '#EFEFEF',
|
|
],
|
|
'surface_dark' => '#000000', // Yellow: dark surface
|
|
'accent_dark' => '#001B2F', // Yellow: dark accent
|
|
],
|
|
'typography' => [
|
|
// Base typography (required)
|
|
'font_family_body' => 'Roboto, sans-serif',
|
|
'font_family_heading' => 'Oswald, sans-serif',
|
|
'font_size_base' => '16px',
|
|
'line_height_base' => '1.5',
|
|
'heading_transform' => 'uppercase', // or 'none'
|
|
|
|
// Theme-specific typography (optional)
|
|
'font_family_quote' => 'Georgia, serif', // Yellow theme
|
|
'line_height_heading' => '1.4', // Vegetable theme
|
|
'font_sizes' => [ // Uuro theme
|
|
'small' => '13px',
|
|
'medium' => '20px',
|
|
'large' => '36px',
|
|
'x-large' => '42px',
|
|
],
|
|
'heading_sizes' => [ // Vegetable theme
|
|
'h1' => '23px',
|
|
'h2' => '36px',
|
|
'h3' => '18px',
|
|
'h4' => '17px',
|
|
],
|
|
'font_weights' => [ // Yellow theme
|
|
'regular' => '400',
|
|
'medium' => '500',
|
|
'semibold' => '600',
|
|
'bold' => '700',
|
|
'extrabold' => '800',
|
|
],
|
|
],
|
|
]
|
|
```
|
|
|
|
## PHP Theme Helper Functions
|
|
|
|
The `app/Helpers/ThemeHelper.php` file provides convenient functions for accessing theme data throughout the application:
|
|
|
|
### Core Functions
|
|
|
|
```php
|
|
// Get current theme data
|
|
$theme = theme(); // Returns full theme array with 'id' key added
|
|
$themeName = theme_name(); // Returns theme display name (e.g., "Uuro")
|
|
$themeId = theme_id(); // Returns theme identifier (e.g., "uuro")
|
|
|
|
// Get specific theme values
|
|
$color = theme_color('primary.500'); // Returns '#64748B' (hex value)
|
|
$logoColor = theme_color('logo'); // Returns logo color
|
|
$bodyFont = theme_font('font_family_body'); // Returns typography setting
|
|
|
|
// Get theme values with defaults
|
|
$customColor = theme_color('custom.color', '#000000'); // Returns default if not found
|
|
$customFont = theme_font('custom_font', 'Arial'); // Returns default if not found
|
|
|
|
// Theme checking
|
|
if (is_theme('vegetable')) {
|
|
// Vegetable theme specific logic
|
|
}
|
|
|
|
// Access nested values
|
|
$textColor = theme('colors.text.primary'); // Direct config access
|
|
$fontWeight = theme('typography.font_weights.bold'); // Access nested arrays
|
|
```
|
|
|
|
### Dynamic CSS Variable Generation
|
|
|
|
```php
|
|
// Generate CSS custom properties for current theme
|
|
$cssVars = theme_css_vars();
|
|
// Returns: "--color-primary-500: 100 116 139; --color-brand: 25 2 54; --font-family-body: system-ui, -apple-system, sans-serif; ..."
|
|
|
|
// Helper function for color conversion
|
|
$rgbValue = hexToRgb('#64748B'); // Returns "100 116 139" (space-separated RGB)
|
|
```
|
|
|
|
## CSS Architecture
|
|
|
|
### Automatic CSS Custom Property Generation
|
|
|
|
Colors from `config/themes.php` are automatically converted to CSS custom properties in RGB format (enabling alpha transparency):
|
|
|
|
```css
|
|
/* Generated automatically via theme_css_vars() function */
|
|
[data-theme="uuro"] {
|
|
--color-primary-50: 248 250 252; /* From #F8FAFC */
|
|
--color-primary-500: 100 116 139; /* From #64748B */
|
|
--color-primary-900: 15 23 42; /* From #0F172A */
|
|
--color-brand: 25 2 54; /* From #190236 */
|
|
--color-logo: 25 2 54; /* From #190236 */
|
|
--color-background: 255 255 255; /* From #FFFFFF */
|
|
--color-text-primary: 0 0 0; /* From #000000 */
|
|
|
|
/* Typography variables */
|
|
--font-family-body: system-ui, -apple-system, sans-serif;
|
|
--font-family-heading: system-ui, -apple-system, sans-serif;
|
|
--line-height-base: 1.6;
|
|
--heading-transform: none;
|
|
}
|
|
```
|
|
|
|
### Tailwind CSS Integration
|
|
|
|
Theme colors integrate with Tailwind using RGB format with alpha value support:
|
|
|
|
```javascript
|
|
// tailwind.config.js
|
|
colors: {
|
|
// Primary color scale
|
|
primary: {
|
|
50: 'rgb(var(--color-primary-50) / <alpha-value>)',
|
|
500: 'rgb(var(--color-primary-500) / <alpha-value>)',
|
|
900: 'rgb(var(--color-primary-900) / <alpha-value>)',
|
|
},
|
|
|
|
// Semantic theme colors
|
|
'theme-secondary': 'rgb(var(--color-secondary) / <alpha-value>)',
|
|
'theme-accent': 'rgb(var(--color-accent) / <alpha-value>)',
|
|
'theme-brand': 'rgb(var(--color-brand) / <alpha-value>)',
|
|
'theme-logo': 'rgb(var(--color-logo) / <alpha-value>)',
|
|
'theme-background': 'rgb(var(--color-background) / <alpha-value>)',
|
|
'theme-surface': 'rgb(var(--color-surface) / <alpha-value>)',
|
|
|
|
'theme-text': {
|
|
primary: 'rgb(var(--color-text-primary) / <alpha-value>)',
|
|
secondary: 'rgb(var(--color-text-secondary) / <alpha-value>)',
|
|
light: 'rgb(var(--color-text-light) / <alpha-value>)',
|
|
},
|
|
}
|
|
```
|
|
|
|
### CSS Utility Classes
|
|
|
|
Additional utility classes are defined in `resources/css/app.css`:
|
|
|
|
```css
|
|
.bg-theme-brand {
|
|
background-color: rgb(var(--color-brand));
|
|
}
|
|
|
|
.fill-theme-logo {
|
|
fill: rgb(var(--color-logo));
|
|
}
|
|
|
|
.text-theme-logo {
|
|
color: rgb(var(--color-logo));
|
|
}
|
|
|
|
.border-theme-border {
|
|
border-color: rgb(var(--color-primary-300));
|
|
}
|
|
```
|
|
|
|
## Color Usage Guidelines
|
|
|
|
### Primary Color Scale Usage
|
|
- **50-200**: Light backgrounds, subtle accents, hover states
|
|
- **300-400**: Borders, muted elements, disabled states
|
|
- **500**: Main brand color, primary buttons, links
|
|
- **600-900**: Text colors, dark backgrounds, headers
|
|
|
|
### Semantic Color Guidelines
|
|
|
|
#### Background Colors
|
|
- `theme-background`: Main page background (typically white)
|
|
- `theme-surface`: Card backgrounds, modal backgrounds, raised surfaces
|
|
- `theme-brand`: Strong brand elements, page headers, primary buttons
|
|
|
|
#### Text Colors
|
|
- `theme-text-primary`: Main body text, headings
|
|
- `theme-text-secondary`: Secondary text, captions, metadata
|
|
- `theme-text-light`: Muted text, placeholders, disabled text
|
|
|
|
#### Accent Colors
|
|
- `theme-secondary`: Secondary brand elements, alternative buttons
|
|
- `theme-accent`: Highlights, badges, accent elements
|
|
- `theme-logo`: Logos, icons, brand symbols
|
|
|
|
### Theme-Specific Color Usage
|
|
|
|
#### Uuro Theme Status Colors
|
|
```php
|
|
// Available via theme_color() function
|
|
$success = theme_color('success'); // '#00D084' - vivid green cyan
|
|
$danger = theme_color('danger'); // '#CF2E2E' - vivid red
|
|
$warning = theme_color('warning'); // '#FF6900' - luminous orange
|
|
$info = theme_color('info'); // '#0693E3' - vivid cyan blue
|
|
```
|
|
|
|
#### Vegetable Theme Natural Colors
|
|
```php
|
|
$surfaceAlt = theme_color('surface_alt'); // '#E2E2C7' - light beige alternative
|
|
```
|
|
|
|
#### Yellow Theme Neutral System
|
|
```php
|
|
$darkNeutral = theme_color('neutral.dark'); // '#2F2E2E'
|
|
$mediumNeutral = theme_color('neutral.medium'); // '#5D5B5B'
|
|
$lightNeutral = theme_color('neutral.light'); // '#EFEFEF'
|
|
$surfaceDark = theme_color('surface_dark'); // '#000000'
|
|
$accentDark = theme_color('accent_dark'); // '#001B2F'
|
|
```
|
|
|
|
## UI Component Patterns
|
|
|
|
This section documents the standard UI patterns used throughout the application, based on the reference implementation in `resources/views/livewire/mailings/manage.blade.php`.
|
|
|
|
### Page Layout Structure
|
|
|
|
#### Standard Management Page Layout
|
|
```html
|
|
<x-app-layout>
|
|
<x-slot name="header">
|
|
{{ __('Page Title') }}
|
|
</x-slot>
|
|
|
|
<div class="py-6">
|
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
|
<div class="bg-theme-background overflow-hidden shadow-xl sm:rounded-lg">
|
|
<div class="p-12 px-6 sm:px-20 bg-theme-background">
|
|
|
|
<!-- Page title and description -->
|
|
<div class="mt-4 text-2xl">
|
|
{{ __('Page Heading') }}
|
|
</div>
|
|
|
|
<div class="mt-6 text-theme-secondary">
|
|
{{ __('Page description text...') }}
|
|
</div>
|
|
|
|
<!-- Main content (Livewire component) -->
|
|
@livewire('component.name')
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Optional: Scroll-to-top script -->
|
|
@push('scripts')
|
|
<script>
|
|
document.addEventListener('scroll-to-top', event => {
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
});
|
|
</script>
|
|
@endpush
|
|
</div>
|
|
</x-app-layout>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Header slot: `text-xl text-theme-light leading-tight`
|
|
- Container: `max-w-7xl mx-auto sm:px-6 lg:px-8`
|
|
- Card wrapper: `bg-theme-background overflow-hidden shadow-xl sm:rounded-lg`
|
|
- Inner padding: `p-12 px-6 sm:px-20 bg-theme-background`
|
|
- Page title: `mt-4 text-2xl`
|
|
- Description: `mt-6 text-theme-secondary`
|
|
|
|
### Action Buttons Section
|
|
|
|
#### Top Action Bar with Primary and Danger Actions
|
|
```html
|
|
<div class="mb-6 flex items-center justify-between">
|
|
<!-- Primary Action (left side) -->
|
|
<x-jetstream.button wire:click="openCreateModal" class="bg-theme-brand hover:bg-opacity-80">
|
|
{{ __('Create Item') }}
|
|
</x-jetstream.button>
|
|
|
|
<!-- Destructive Action (right side) -->
|
|
<x-jetstream.danger-button
|
|
:disabled="$bulkDisabled"
|
|
title="{{ __('Delete selection') }}"
|
|
wire:click.prevent="openBulkDeleteModal">
|
|
<x-icon class="mr-3 h-5 w-5" name="trash" />
|
|
{{ __('Delete') }} {{ __('selection') }}
|
|
</x-jetstream.danger-button>
|
|
</div>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Container: `mb-6 flex items-center justify-between`
|
|
- Primary button: `bg-theme-brand hover:bg-opacity-80`
|
|
- Icon spacing: `mr-3 h-5 w-5`
|
|
- Use `wire:click` for Livewire actions
|
|
- Use `:disabled` for reactive disabling
|
|
|
|
### Search and Filters
|
|
|
|
#### Search Input with Clear Button
|
|
```html
|
|
<div class="mb-6">
|
|
<div class="mb-4 flex items-center">
|
|
<div class="relative w-1/3">
|
|
<input
|
|
class="w-full rounded-md border border-theme-primary px-3 py-1 pr-10 text-theme-primary shadow-sm focus:border-theme-primary focus:outline-none focus:ring focus:ring-theme-primary sm:text-sm"
|
|
placeholder="{{ __('Search items') . '...' }}"
|
|
type="text"
|
|
wire:model.live="search">
|
|
|
|
@if ($search)
|
|
<button
|
|
class="absolute inset-y-0 right-0 flex items-center pr-3 text-theme-secondary hover:text-theme-primary focus:outline-none"
|
|
wire:click.prevent="$set('search', '')">
|
|
<x-icon mini name="backspace" solid />
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Search container: `relative w-1/3`
|
|
- Input classes: `w-full rounded-md border border-theme-primary px-3 py-1 pr-10 text-theme-primary shadow-sm focus:border-theme-primary focus:outline-none focus:ring focus:ring-theme-primary sm:text-sm`
|
|
- Clear button: positioned absolutely, `inset-y-0 right-0`, with `text-theme-secondary hover:text-theme-primary`
|
|
- Use `wire:model.live` for real-time search
|
|
|
|
#### Filter Dropdowns Row
|
|
```html
|
|
<div class="mb-4 flex items-center space-x-3">
|
|
<div>
|
|
<x-select
|
|
:clearable="true"
|
|
class="!w-60"
|
|
placeholder="{{ __('Filter by category') }}"
|
|
wire:model.live="categoryFilter">
|
|
<x-select.option label="{{ __('Option 1') }}" value="option1" />
|
|
<x-select.option label="{{ __('Option 2') }}" value="option2" />
|
|
</x-select>
|
|
</div>
|
|
|
|
<div>
|
|
<x-select
|
|
:clearable="true"
|
|
class="!w-60"
|
|
placeholder="{{ __('Filter by status') }}"
|
|
wire:model.live="statusFilter">
|
|
<x-select.option label="{{ __('Active') }}" value="active" />
|
|
<x-select.option label="{{ __('Inactive') }}" value="inactive" />
|
|
</x-select>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Container: `mb-4 flex items-center space-x-3`
|
|
- Select width: `!w-60` (use `!` to override default)
|
|
- Always use `:clearable="true"` for filters
|
|
- Use `wire:model.live` for immediate filtering
|
|
|
|
### Data Tables
|
|
|
|
#### Complete Table Structure
|
|
```html
|
|
<div class="bg-white shadow-sm rounded-lg overflow-hidden">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<!-- Checkbox column -->
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
<!-- Optional: Select all checkbox -->
|
|
</th>
|
|
|
|
<!-- Sortable column -->
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer align-middle"
|
|
wire:click="sortBy('title')">
|
|
<div class="flex items-center space-x-1">
|
|
<span>{{ __('Title') }}</span>
|
|
@if($sortField === 'title')
|
|
<span class="text-gray-400">
|
|
@if($sortDirection === 'asc') ↑ @else ↓ @endif
|
|
</span>
|
|
@endif
|
|
</div>
|
|
</th>
|
|
|
|
<!-- Non-sortable column -->
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider align-middle">
|
|
{{ __('Column Name') }}
|
|
</th>
|
|
|
|
<!-- Actions column (right-aligned) -->
|
|
<th class="px-3 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider align-middle w-32">
|
|
{{ __('Actions') }}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
@forelse($items as $item)
|
|
<tr>
|
|
<!-- Checkbox cell -->
|
|
<td class="px-3 py-4 whitespace-nowrap">
|
|
<input
|
|
type="checkbox"
|
|
value="{{ $item->id }}"
|
|
wire:model.live="bulkSelected"
|
|
class="rounded border-gray-300 text-theme-brand focus:ring-theme-brand">
|
|
</td>
|
|
|
|
<!-- Title cell with subtitle -->
|
|
<td class="px-3 py-4 whitespace-nowrap">
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-900">{{ $item->title }}</div>
|
|
<div class="text-sm text-gray-500">{{ $item->subtitle }}</div>
|
|
</div>
|
|
</td>
|
|
|
|
<!-- Standard text cell -->
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{{ $item->description }}
|
|
</td>
|
|
|
|
<!-- Status badge cell -->
|
|
<td class="px-3 py-4 whitespace-nowrap">
|
|
<span class="inline-flex px-2 py-1 text-xs font-medium rounded-full
|
|
@if($item->status === 'active') bg-green-100 text-green-800
|
|
@elseif($item->status === 'pending') bg-yellow-100 text-yellow-800
|
|
@elseif($item->status === 'inactive') bg-gray-100 text-gray-800
|
|
@else bg-theme-danger-light text-theme-danger-dark @endif">
|
|
{{ ucfirst($item->status) }}
|
|
</span>
|
|
</td>
|
|
|
|
<!-- Date cell (multi-line format) -->
|
|
<td class="px-3 py-4 text-sm text-gray-500">
|
|
@if($item->date)
|
|
<div class="leading-tight">
|
|
<div>{{ $item->date->format('M j') }}</div>
|
|
<div>{{ $item->date->format('Y') }}</div>
|
|
<div class="text-xs">{{ $item->date->format('H:i') }}</div>
|
|
</div>
|
|
@else
|
|
-
|
|
@endif
|
|
</td>
|
|
|
|
<!-- Avatar cell -->
|
|
<td class="px-3 py-4">
|
|
@if($item->user)
|
|
<div class="relative block cursor-pointer"
|
|
onclick="window.location='{{ url('user/' . $item->user->id) }}'"
|
|
title="{{ $item->user->name }}">
|
|
<img class="mx-auto h-6 w-6 rounded-full object-cover outline outline-1 outline-offset-0 outline-gray-600"
|
|
src="{{ $item->user->avatar_url }}"
|
|
alt="profile">
|
|
</div>
|
|
@else
|
|
<span class="text-sm text-gray-400">-</span>
|
|
@endif
|
|
</td>
|
|
|
|
<!-- Actions cell -->
|
|
<td class="px-3 py-4 whitespace-nowrap text-center text-sm font-medium">
|
|
<div class="flex items-center justify-center space-x-1">
|
|
<!-- Edit button -->
|
|
<x-jetstream.secondary-button
|
|
title="{{ __('Edit') }}"
|
|
wire:click="openEditModal({{ $item->id }})">
|
|
<span wire:loading.remove wire:target="openEditModal">
|
|
<x-icon class="h-5 w-5" name="pencil-square" />
|
|
</span>
|
|
<span wire:loading wire:target="openEditModal">
|
|
<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
</span>
|
|
</x-jetstream.secondary-button>
|
|
|
|
<!-- Delete button -->
|
|
<x-jetstream.danger-button
|
|
wire:click="delete({{ $item->id }})"
|
|
title="{{ __('Delete') }}">
|
|
<span wire:loading.remove wire:target="delete">
|
|
<x-icon class="h-5 w-5" name="trash" solid />
|
|
</span>
|
|
</x-jetstream.danger-button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="8" class="px-6 py-12 text-center text-gray-500">
|
|
{{ __('No items found.') }}
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Pagination -->
|
|
@if($items->hasPages())
|
|
<div class="px-6 py-3 border-t border-gray-200">
|
|
{{ $items->links('livewire.long-paginator') }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Table wrapper: `bg-white shadow-sm rounded-lg overflow-hidden`
|
|
- Table: `min-w-full divide-y divide-gray-200`
|
|
- Header: `bg-gray-50`, headers use `px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider align-middle`
|
|
- Sortable headers: add `cursor-pointer` and `wire:click="sortBy('field')"`
|
|
- Sort indicators: `text-gray-400` with ↑/↓ characters
|
|
- Body: `bg-white divide-y divide-gray-200`
|
|
- Cells: `px-3 py-4` (standard), `px-6 py-3` (checkbox column)
|
|
- Action buttons: `space-x-1` between buttons
|
|
- Empty state: full colspan, `px-6 py-12 text-center text-gray-500`
|
|
- Pagination wrapper: `px-6 py-3 border-t border-gray-200`
|
|
|
|
#### Status Badge Patterns
|
|
```html
|
|
<!-- Success/Active status -->
|
|
<span class="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800">
|
|
{{ __('Active') }}
|
|
</span>
|
|
|
|
<!-- Warning/Pending status -->
|
|
<span class="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-yellow-100 text-yellow-800">
|
|
{{ __('Pending') }}
|
|
</span>
|
|
|
|
<!-- Neutral/Draft status -->
|
|
<span class="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-gray-100 text-gray-800">
|
|
{{ __('Draft') }}
|
|
</span>
|
|
|
|
<!-- Theme-aware status -->
|
|
<span class="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-theme-surface text-theme-primary">
|
|
{{ __('Processing') }}
|
|
</span>
|
|
|
|
<!-- Danger/Error status -->
|
|
<span class="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-theme-danger-light text-theme-danger-dark">
|
|
{{ __('Failed') }}
|
|
</span>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Base classes: `inline-flex px-2 py-1 text-xs font-medium rounded-full`
|
|
- Use semantic colors: green (success), yellow (warning), gray (neutral), theme-danger (error)
|
|
|
|
### Modals
|
|
|
|
#### Standard Dialog Modal Structure
|
|
```html
|
|
<x-jetstream.dialog-modal wire:model.live="showModal" wire:key="showModal" maxWidth="2xl">
|
|
<x-slot name="title">
|
|
{{ __('Modal Title') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<!-- Modal content here -->
|
|
<div class="space-y-6">
|
|
<!-- Form fields, text, etc. -->
|
|
</div>
|
|
</x-slot>
|
|
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="closeModal">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
|
|
<x-jetstream.button wire:click="save" class="ml-3 bg-theme-brand">
|
|
{{ __('Save') }}
|
|
</x-jetstream.button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Use `wire:model.live` for modal visibility
|
|
- Add `wire:key` to prevent state issues
|
|
- Standard sizes: `sm`, `md`, `lg`, `xl`, `2xl`
|
|
- Footer buttons: Cancel (secondary) on left, Primary action on right with `ml-3`
|
|
- Primary actions use `bg-theme-brand`
|
|
|
|
#### Confirmation Modal Pattern
|
|
```html
|
|
<x-jetstream.dialog-modal wire:model.live="showConfirmModal" wire:key="showConfirmModal">
|
|
<x-slot name="title">
|
|
{{ __('Confirm Action') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
{{ __('Are you sure you want to perform this action?') }}<br>
|
|
{{ __('This action cannot be undone.') }}
|
|
<br><br>
|
|
<strong>{{ __('Details') }}: {{ $itemName }}</strong>
|
|
</x-slot>
|
|
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="$set('showConfirmModal', false)">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
|
|
<x-jetstream.danger-button wire:click="confirmAction" class="ml-3">
|
|
{{ __('Confirm') }}
|
|
</x-jetstream.danger-button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Destructive actions use `danger-button`
|
|
- Include warning text and action details
|
|
- Use `$set('property', value)` for simple state changes
|
|
|
|
#### Preview Modal with Iframe
|
|
```html
|
|
<x-jetstream.dialog-modal wire:model.live="showPreviewModal" wire:key="showPreviewModal" maxWidth="2xl">
|
|
<x-slot name="title">
|
|
{{ __('Preview') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<div class="space-y-6">
|
|
<!-- Mobile-sized preview frame -->
|
|
<div class="flex justify-center">
|
|
<div class="border border-gray-300 rounded-3xl overflow-hidden bg-white shadow-inner shadow-2xl" style="width: 322px; height: 570px;">
|
|
<iframe
|
|
srcdoc="{!! e($previewHtml) !!}"
|
|
style="width:100%;height:100%;border:0;background:#f4f4f4;"
|
|
></iframe>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Additional content below preview -->
|
|
<div class="border-t border-gray-200 pt-6">
|
|
<h3 class="text-sm font-medium text-gray-700 mb-3">{{ __('Additional Options') }}</h3>
|
|
<!-- More content here -->
|
|
</div>
|
|
</div>
|
|
</x-slot>
|
|
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="closePreview">
|
|
{{ __('Close') }}
|
|
</x-jetstream.secondary-button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Mobile preview: `width: 322px; height: 570px` in `rounded-3xl` container
|
|
- Sections separated by: `border-t border-gray-200 pt-6`
|
|
- Section headers: `text-sm font-medium text-gray-700 mb-3`
|
|
|
|
### Form Elements
|
|
|
|
#### Standard Input with Label
|
|
```html
|
|
<div>
|
|
<label class="block text-sm text-gray-700 mb-2">
|
|
{{ __('Field Label') }}
|
|
</label>
|
|
<x-input
|
|
wire:model="fieldName"
|
|
type="text"
|
|
placeholder="{{ __('Enter value') }}"
|
|
class="w-full"
|
|
/>
|
|
@error('fieldName')
|
|
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
|
@enderror
|
|
</div>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Label: `block text-sm text-gray-700 mb-2`
|
|
- Error message: `mt-1 text-sm text-red-600`
|
|
- Always include `@error` directives below inputs
|
|
|
|
#### Checkbox with Label
|
|
```html
|
|
<label class="flex items-center space-x-3">
|
|
<input
|
|
type="checkbox"
|
|
wire:model="fieldName"
|
|
class="rounded border-gray-300 text-theme-brand shadow-sm focus:border-theme-brand focus:ring focus:ring-theme-brand focus:ring-opacity-50"
|
|
>
|
|
<span class="text-sm text-gray-700">
|
|
{{ __('Option label') }}
|
|
</span>
|
|
</label>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Container: `flex items-center space-x-3`
|
|
- Checkbox classes: `rounded border-gray-300 text-theme-brand shadow-sm focus:border-theme-brand focus:ring focus:ring-theme-brand focus:ring-opacity-50`
|
|
- Label text: `text-sm text-gray-700`
|
|
|
|
#### Info Box Pattern
|
|
```html
|
|
<div class="mt-4 p-3 bg-theme-surface rounded-md border border-theme-border">
|
|
<p class="text-sm text-theme-primary">
|
|
<strong>{{ __('Label:') }}</strong> {{ $value }}
|
|
</p>
|
|
<p class="text-sm text-theme-secondary mt-1">
|
|
{{ __('Additional information text.') }}
|
|
</p>
|
|
</div>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Container: `p-3 bg-theme-surface rounded-md border border-theme-border`
|
|
- Primary text: `text-sm text-theme-primary`
|
|
- Secondary text: `text-sm text-theme-secondary mt-1`
|
|
|
|
### Notification Banners and Alerts
|
|
|
|
#### Informational Banner (Grayscale)
|
|
```html
|
|
<div class="mb-6 rounded-md bg-gray-50 border border-gray-300 p-4">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h3 class="text-sm font-medium text-gray-800">
|
|
{{ __('Banner Heading') }}
|
|
</h3>
|
|
<div class="mt-2 text-sm text-gray-600">
|
|
<p>
|
|
{{ __('Informational message content goes here. This is a neutral notification.') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Background: `bg-gray-50` (professional, non-alarming)
|
|
- Border: `border border-gray-300` (subtle, matches table styling)
|
|
- Icon: `h-5 w-5 text-gray-400` (muted)
|
|
- Heading: `text-sm font-medium text-gray-800`
|
|
- Body text: `text-sm text-gray-600`
|
|
- Use for: maintenance notices, informational messages, neutral alerts
|
|
|
|
#### Warning Banner (Grayscale with Warning Icon)
|
|
```html
|
|
<div class="mb-6 rounded-md bg-gray-50 border border-gray-300 p-4">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h3 class="text-sm font-medium text-gray-800">
|
|
{{ __('Site Under Maintenance') }}
|
|
</h3>
|
|
<div class="mt-2 text-sm text-gray-600">
|
|
<p>
|
|
{{ __('The site is currently undergoing maintenance. Only users with administrator access can log in at this time.') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Same styling as informational banner
|
|
- Use warning triangle icon for cautionary messages
|
|
- Keep grayscale to avoid alarm
|
|
- Use for: system notices, maintenance messages, non-critical warnings
|
|
|
|
#### Error Message (Red)
|
|
```html
|
|
<div class="mb-4 rounded-md bg-red-50 border border-red-200 p-4">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<div class="text-sm text-red-800">
|
|
<p>{{ __('Login is currently disabled due to site maintenance.') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Background: `bg-red-50`
|
|
- Border: `border border-red-200`
|
|
- Icon: `h-5 w-5 text-red-400` (X circle for errors)
|
|
- Text: `text-sm text-red-800`
|
|
- Use for: actual errors, failed actions, critical issues
|
|
|
|
#### Success Message (Green)
|
|
```html
|
|
<div class="mb-4 rounded-md bg-green-50 border border-green-200 p-4">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<div class="text-sm text-green-800">
|
|
<p>{{ __('Your changes have been saved successfully.') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Background: `bg-green-50`
|
|
- Border: `border border-green-200`
|
|
- Icon: `h-5 w-5 text-green-400` (checkmark circle)
|
|
- Text: `text-sm text-green-800`
|
|
- Use for: successful operations, confirmations
|
|
|
|
**Banner/Alert Usage Guidelines:**
|
|
- **Grayscale**: Default for informational, maintenance, or neutral system messages
|
|
- **Red**: Only for actual errors or failed actions
|
|
- **Green**: Only for successful completions
|
|
- **Yellow**: Avoid; use grayscale for warnings instead (more professional)
|
|
- Icon size: Always `h-5 w-5`
|
|
- Icon position: Always in flex-shrink-0 container with `ml-3` for text
|
|
- Common SVG icons: info circle, warning triangle, X circle, checkmark circle
|
|
|
|
### Buttons and Actions
|
|
|
|
#### Button Component Usage
|
|
```html
|
|
<!-- Primary action button -->
|
|
<x-jetstream.button wire:click="action" class="bg-theme-brand hover:bg-opacity-80">
|
|
{{ __('Primary Action') }}
|
|
</x-jetstream.button>
|
|
|
|
<!-- Secondary action button -->
|
|
<x-jetstream.secondary-button wire:click="action" title="{{ __('Tooltip') }}">
|
|
<x-icon class="h-5 w-5" name="icon-name" />
|
|
</x-jetstream.secondary-button>
|
|
|
|
<!-- Danger/destructive button -->
|
|
<x-jetstream.danger-button wire:click="delete" :disabled="$isDisabled">
|
|
<x-icon class="mr-3 h-5 w-5" name="trash" />
|
|
{{ __('Delete') }}
|
|
</x-jetstream.danger-button>
|
|
|
|
<!-- Light button (for pagination, filters) -->
|
|
<x-jetstream.light-button wire:click="action">
|
|
{{ __('Light Action') }}
|
|
</x-jetstream.light-button>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Primary buttons: `bg-theme-brand hover:bg-opacity-80`
|
|
- Icon-only buttons: use `title` attribute for accessibility
|
|
- Icons with text: `mr-3` for spacing
|
|
- Icon size: `h-5 w-5` (standard), `h-6 w-6` (large)
|
|
- Use `:disabled` for reactive disable state
|
|
|
|
#### Loading State Pattern
|
|
```html
|
|
<x-jetstream.button
|
|
wire:click="action"
|
|
wire:loading.attr="disabled"
|
|
wire:target="action"
|
|
class="bg-theme-brand">
|
|
<span wire:loading.remove wire:target="action">
|
|
{{ __('Submit') }}
|
|
</span>
|
|
<span wire:loading wire:target="action">
|
|
{{ __('Processing...') }}
|
|
</span>
|
|
</x-jetstream.button>
|
|
```
|
|
|
|
OR with spinner:
|
|
|
|
```html
|
|
<x-jetstream.secondary-button wire:click="action">
|
|
<span wire:loading.remove wire:target="action">
|
|
<x-icon class="h-5 w-5" name="icon-name" />
|
|
</span>
|
|
<span wire:loading wire:target="action">
|
|
<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
</span>
|
|
</x-jetstream.secondary-button>
|
|
```
|
|
|
|
**Key Patterns:**
|
|
- Use `wire:loading.attr="disabled"` to prevent double-clicks
|
|
- Use `wire:target` to scope loading state to specific action
|
|
- Provide text feedback OR spinner during loading
|
|
- Spinner classes: `animate-spin h-5 w-5 text-white`
|
|
|
|
### Spacing and Layout
|
|
|
|
#### Standard Spacing Scale
|
|
- **Section spacing:** `mt-12` (top margin for major sections)
|
|
- **Element spacing:** `mb-6` (bottom margin for groups), `mb-4` (between related items)
|
|
- **Inline spacing:** `space-x-1` (tight), `space-x-3` (standard), `mr-3` (icon before text)
|
|
- **Content spacing:** `space-y-6` (form sections), `space-y-3` (form fields)
|
|
- **Padding:** `p-3` (small boxes), `p-6` (standard), `p-12` (page content)
|
|
|
|
## Styling Best Practices
|
|
|
|
### 1. Use Theme-Aware Classes
|
|
|
|
✅ **Recommended:**
|
|
```html
|
|
<div class="bg-theme-brand text-theme-text-primary">
|
|
<button class="border-primary-300 bg-theme-surface hover:bg-primary-50">
|
|
<header class="bg-primary-900 text-white">
|
|
```
|
|
|
|
❌ **Avoid:**
|
|
```html
|
|
<div class="bg-black text-gray-900">
|
|
<button class="border-gray-300 bg-white hover:bg-gray-50">
|
|
<header class="bg-gray-900 text-white">
|
|
```
|
|
|
|
### 2. Consistent Component Styling
|
|
|
|
#### Form Components
|
|
```html
|
|
<input class="border border-primary-300 bg-theme-background rounded-md shadow-sm focus:border-primary-500">
|
|
<select class="border border-primary-300 bg-theme-background rounded-md">
|
|
```
|
|
|
|
#### Cards and Surfaces
|
|
```html
|
|
<div class="bg-theme-surface rounded-lg shadow border border-primary-200">
|
|
<div class="bg-theme-background p-6 rounded-md">
|
|
```
|
|
|
|
#### Headers and Navigation
|
|
```html
|
|
<header class="bg-theme-brand text-white">
|
|
<nav class="bg-primary-900 text-theme-text-primary">
|
|
```
|
|
|
|
### 3. Semi-Transparent Overlays
|
|
|
|
Use modern Tailwind opacity syntax for better performance:
|
|
|
|
```html
|
|
<!-- Recommended -->
|
|
<div class="bg-theme-brand/60"> <!-- 60% opacity -->
|
|
<div class="bg-primary-500/25"> <!-- 25% opacity -->
|
|
|
|
<!-- Avoid -->
|
|
<div class="bg-theme-brand bg-opacity-60">
|
|
<div class="bg-primary-500 bg-opacity-25">
|
|
```
|
|
|
|
### 4. Logo and Icon Theming
|
|
|
|
SVG elements should use theme-aware classes:
|
|
|
|
```html
|
|
<svg class="fill-theme-logo w-8 h-8">
|
|
<!-- SVG paths automatically use theme logo color -->
|
|
</svg>
|
|
|
|
<i class="text-theme-logo text-xl"></i> <!-- Icon fonts -->
|
|
```
|
|
|
|
## Component-Specific Guidelines
|
|
|
|
### Amount Input Component
|
|
Unified wrapper approach for multi-part inputs:
|
|
```html
|
|
<div class="amount-wrapper flex items-center rounded-md border border-primary-300 bg-theme-background shadow-sm overflow-hidden {{ $maxLengthHoursInput > 4 ? 'w-full' : 'w-40' }}">
|
|
<span class="text-theme-text-secondary">€</span>
|
|
<input class="border-0 focus:ring-0">
|
|
</div>
|
|
```
|
|
|
|
### Event Cards
|
|
Full-height cards with image backgrounds:
|
|
```html
|
|
<div class="relative flex h-[430px] flex-col justify-end bg-theme-background">
|
|
<!-- Image background -->
|
|
<div class="absolute inset-0 z-0 h-full w-full">
|
|
<img class="absolute inset-0 z-0 h-full w-full object-cover">
|
|
</div>
|
|
|
|
<!-- Semi-transparent overlay -->
|
|
<div class="absolute inset-0 z-10 bg-black/50"></div>
|
|
|
|
<!-- Fallback gradient -->
|
|
<div class="absolute inset-0 z-0 bg-gradient-to-br from-theme-secondary to-theme-brand"></div>
|
|
|
|
<!-- Content -->
|
|
<div class="relative z-20 p-14 text-white">
|
|
<h2 class="text-4xl font-semibold">Event Title</h2>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
## Typography System
|
|
|
|
### Font Family Configuration
|
|
Access theme fonts using helper functions:
|
|
|
|
```php
|
|
// In PHP/Livewire components
|
|
$bodyFont = theme_font('font_family_body'); // 'Roboto, sans-serif'
|
|
$headingFont = theme_font('font_family_heading'); // 'Oswald, sans-serif'
|
|
$quoteFont = theme_font('font_family_quote'); // 'Georgia, serif' (Yellow theme)
|
|
```
|
|
|
|
### Text Transform Rules
|
|
- **Timebank_cc**: Uppercase headings (`heading_transform: 'uppercase'`)
|
|
- **Other themes**: Normal case headings (`heading_transform: 'none'`)
|
|
|
|
```php
|
|
// Conditional styling based on theme
|
|
if (is_theme('timebank_cc')) {
|
|
$headingClass = 'uppercase';
|
|
} else {
|
|
$headingClass = 'normal-case';
|
|
}
|
|
```
|
|
|
|
## Development Workflow
|
|
|
|
### Adding New Theme Colors
|
|
|
|
1. **Add color to theme configuration** in `config/themes.php`:
|
|
```php
|
|
'themes' => [
|
|
'theme_name' => [
|
|
'colors' => [
|
|
'new-color' => '#FF0000', // Single color
|
|
'new-scale' => [ // Color scale
|
|
50 => '#FFF5F5',
|
|
500 => '#FF0000',
|
|
900 => '#7F1D1D',
|
|
],
|
|
],
|
|
],
|
|
]
|
|
```
|
|
|
|
2. **CSS custom properties are automatically generated** via `theme_css_vars()` function (no manual CSS needed)
|
|
|
|
3. **Add Tailwind utility classes** in `tailwind.config.js`:
|
|
```javascript
|
|
colors: {
|
|
'theme-new-color': 'rgb(var(--color-new-color) / <alpha-value>)',
|
|
'new-scale': {
|
|
50: 'rgb(var(--color-new-scale-50) / <alpha-value>)',
|
|
500: 'rgb(var(--color-new-scale-500) / <alpha-value>)',
|
|
900: 'rgb(var(--color-new-scale-900) / <alpha-value>)',
|
|
},
|
|
}
|
|
```
|
|
|
|
4. **Create additional CSS utility classes** if needed in `resources/css/app.css`:
|
|
```css
|
|
.bg-theme-new-color {
|
|
background-color: rgb(var(--color-new-color));
|
|
}
|
|
|
|
.border-new-color-500 {
|
|
border-color: rgb(var(--color-new-scale-500));
|
|
}
|
|
```
|
|
|
|
5. **Use in PHP/Livewire components**:
|
|
```php
|
|
$newColor = theme_color('new-color'); // Returns '#FF0000'
|
|
$lightShade = theme_color('new-scale.50'); // Returns '#FFF5F5'
|
|
$darkShade = theme_color('new-scale.900'); // Returns '#7F1D1D'
|
|
```
|
|
|
|
### Testing Themes
|
|
|
|
Test theme functionality using helper functions:
|
|
|
|
```bash
|
|
# Test individual theme colors
|
|
TIMEBANK_THEME=timebank_cc php artisan tinker --execute="echo 'Timebank_cc primary: ' . theme_color('primary.500');"
|
|
TIMEBANK_THEME=uuro php artisan tinker --execute="echo 'Uuro primary: ' . theme_color('primary.500');"
|
|
TIMEBANK_THEME=vegetable php artisan tinker --execute="echo 'Vegetable primary: ' . theme_color('primary.500');"
|
|
TIMEBANK_THEME=yellow php artisan tinker --execute="echo 'Yellow primary: ' . theme_color('primary.500');"
|
|
|
|
# Test theme metadata
|
|
php artisan tinker --execute="echo 'Current theme: ' . theme_name() . ' (' . theme_id() . ')';"
|
|
|
|
# Test theme-specific colors
|
|
TIMEBANK_THEME=uuro php artisan tinker --execute="echo 'Uuro success: ' . theme_color('success');"
|
|
TIMEBANK_THEME=vegetable php artisan tinker --execute="echo 'Vegetable surface_alt: ' . theme_color('surface_alt');"
|
|
|
|
# Test typography
|
|
php artisan tinker --execute="echo 'Body font: ' . theme_font('font_family_body');"
|
|
|
|
# Required after config changes
|
|
php artisan config:clear
|
|
```
|
|
|
|
### Environment Setup
|
|
|
|
```bash
|
|
# Set theme in .env file
|
|
TIMEBANK_THEME=uuro
|
|
|
|
# Clear Laravel config cache (required after .env changes)
|
|
php artisan config:clear
|
|
|
|
# Rebuild frontend assets with theme
|
|
npm run build
|
|
```
|
|
|
|
### Theme Switching Workflow
|
|
|
|
```bash
|
|
# Switch to different theme
|
|
echo "TIMEBANK_THEME=vegetable" >> .env
|
|
|
|
# Clear caches
|
|
php artisan config:clear
|
|
php artisan view:clear
|
|
|
|
# Rebuild assets
|
|
npm run build
|
|
|
|
# Test theme colors
|
|
php artisan tinker --execute="echo theme_name() . ': ' . theme_color('primary.500');"
|
|
```
|
|
|
|
## File Structure
|
|
|
|
```
|
|
config/
|
|
themes.php # Main theme configuration file
|
|
|
|
app/
|
|
Helpers/
|
|
ThemeHelper.php # Theme helper functions and utilities
|
|
|
|
resources/
|
|
css/
|
|
app.css # CSS custom properties and utility classes
|
|
views/
|
|
layouts/
|
|
app.blade.php # Theme data-attribute integration
|
|
components/
|
|
jetstream/
|
|
application-logo.blade.php # Theme-aware main logo
|
|
authentication-card-logo.blade.php # Theme-aware auth logo
|
|
|
|
tailwind.config.js # Tailwind theme color integration
|
|
references/
|
|
THEME_IMPLEMENTATION.md # Detailed implementation documentation
|
|
```
|
|
|
|
## Migration from Hard-coded Styles
|
|
|
|
### 1. Identify Hard-coded Colors
|
|
|
|
**Class Name Conversions:**
|
|
- `bg-black` → `bg-theme-brand`
|
|
- `text-gray-900` → `text-theme-text-primary`
|
|
- `border-gray-300` → `border-primary-300`
|
|
- `bg-white` → `bg-theme-background`
|
|
- `bg-gray-50` → `bg-theme-surface`
|
|
|
|
### 2. Use Theme Helper Functions in PHP
|
|
|
|
Replace hard-coded values with theme helpers:
|
|
|
|
```php
|
|
// Before: Hard-coded values
|
|
$primaryColor = '#999B9C';
|
|
$backgroundColor = '#FFFFFF';
|
|
$textColor = '#111827';
|
|
|
|
// After: Theme helpers
|
|
$primaryColor = theme_color('primary.500');
|
|
$backgroundColor = theme_color('background');
|
|
$textColor = theme_color('text.primary');
|
|
```
|
|
|
|
### 3. Conditional Theme Logic
|
|
|
|
Handle theme-specific differences:
|
|
|
|
```php
|
|
// Theme-specific behavior
|
|
if (is_theme('vegetable')) {
|
|
$surfaceColor = theme_color('surface_alt'); // Light beige
|
|
$lineHeight = theme_font('line_height_base'); // 1.8 for readability
|
|
} else {
|
|
$surfaceColor = theme_color('surface'); // Standard surface
|
|
$lineHeight = theme_font('line_height_base'); // Theme default
|
|
}
|
|
|
|
// Theme-specific status colors
|
|
if (is_theme('uuro')) {
|
|
$successColor = theme_color('success'); // Vivid green cyan
|
|
$dangerColor = theme_color('danger'); // Vivid red
|
|
} else {
|
|
$successColor = '#22c55e'; // Fallback green
|
|
$dangerColor = '#ef4444'; // Fallback red
|
|
}
|
|
```
|
|
|
|
### 4. Component Migration Process
|
|
|
|
1. **Audit component** for hard-coded colors and styles
|
|
2. **Replace classes** with theme-aware equivalents
|
|
3. **Add PHP logic** for theme-specific behavior using helper functions
|
|
4. **Test across all themes** using environment variable switching
|
|
5. **Verify accessibility** and contrast ratios
|
|
|
|
## Accessibility Guidelines
|
|
|
|
### Color Contrast Requirements
|
|
- Maintain WCAG AA contrast ratios (4.5:1) across all themes
|
|
- Test text readability on all background combinations
|
|
- Verify logo visibility on different header backgrounds
|
|
|
|
### Theme Testing Checklist
|
|
- [ ] Form elements clearly distinguishable in all themes
|
|
- [ ] Focus states visible with theme colors
|
|
- [ ] Error states readable with theme danger colors
|
|
- [ ] Interactive elements have sufficient contrast
|
|
- [ ] Logo/icons visible on theme backgrounds
|
|
|
|
## Performance Considerations
|
|
|
|
### CSS Custom Properties Benefits
|
|
- Efficient theme switching without rebuilding CSS
|
|
- Single CSS bundle supports all themes
|
|
- Runtime theme changes possible (if needed)
|
|
- Excellent browser support and performance
|
|
|
|
### Optimization Guidelines
|
|
- Use shared utility classes when possible
|
|
- Minimize theme-specific CSS overrides
|
|
- Leverage Tailwind's purging for unused theme classes
|
|
- Keep theme configurations lean and focused
|
|
|
|
### Build Performance
|
|
- Themes are compiled at build time for optimal performance
|
|
- CSS custom properties enable single bundle for all themes
|
|
- No runtime theme computation overhead |