diff --git a/database/migrations/2024_01_22_205110_add_theme_settings.php b/database/migrations/2024_01_22_205110_add_theme_settings.php new file mode 100644 index 00000000..d7232d04 --- /dev/null +++ b/database/migrations/2024_01_22_205110_add_theme_settings.php @@ -0,0 +1,17 @@ + $this->migrator->add('theme.accent', 'cachet')); + rescue(fn () => $this->migrator->add('theme.accent_content', 'zinc')); + rescue(fn () => $this->migrator->add('theme.accent_pairing', true)); + } +}; diff --git a/resources/css/cachet.css b/resources/css/cachet.css index 55247e28..ca5e5fd3 100644 --- a/resources/css/cachet.css +++ b/resources/css/cachet.css @@ -16,7 +16,7 @@ } .button--secondary { - @apply bg-zinc-300 text-zinc-800 hover:bg-zinc-400 focus-visible:outline-zinc-300 focus-visible:outline-zinc-800 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-700; + @apply bg-zinc-300 text-zinc-800 hover:bg-zinc-400 focus-visible:outline-zinc-300 focus-visible:outline-zinc-800 dark:bg-white/5 dark:text-zinc-400 dark:hover:bg-zinc-700; } } diff --git a/resources/css/dashboard/theme.css b/resources/css/dashboard/theme.css index c3a62491..0147dc45 100644 --- a/resources/css/dashboard/theme.css +++ b/resources/css/dashboard/theme.css @@ -1,3 +1,7 @@ @import '/vendor/filament/filament/resources/css/theme.css'; @config 'tailwind.config.js'; + +.theme-swatch { + @apply inline-flex w-4 h-4 rounded-full mr-2 bg-[rgb(var(--swatch))]; +} diff --git a/resources/views/components/about.blade.php b/resources/views/components/about.blade.php index edac2170..fbe5ddad 100644 --- a/resources/views/components/about.blade.php +++ b/resources/views/components/about.blade.php @@ -1,7 +1,7 @@ @if ($about !== '')

{{ $title }}

-
+
{!! $about !!}
diff --git a/resources/views/components/cachet.blade.php b/resources/views/components/cachet.blade.php index e7855210..8b59c33c 100644 --- a/resources/views/components/cachet.blade.php +++ b/resources/views/components/cachet.blade.php @@ -1,6 +1,6 @@ @use('Cachet\Cachet') - + @@ -35,13 +35,7 @@ {!! $cachet_header !!} diff --git a/resources/views/components/component-group.blade.php b/resources/views/components/component-group.blade.php index 666fab6e..7588efdb 100644 --- a/resources/views/components/component-group.blade.php +++ b/resources/views/components/component-group.blade.php @@ -6,7 +6,7 @@ ])) ->class(['overflow-hidden rounded-lg border dark:border-zinc-700']) }}> -
+
-
+
    @foreach($componentGroup->components as $component) diff --git a/resources/views/components/component-ungrouped.blade.php b/resources/views/components/component-ungrouped.blade.php index 8d78facf..51dd9833 100644 --- a/resources/views/components/component-ungrouped.blade.php +++ b/resources/views/components/component-ungrouped.blade.php @@ -1,7 +1,7 @@ @props(['component' => null])
    -
    +
    diff --git a/resources/views/components/footer.blade.php b/resources/views/components/footer.blade.php index 7663c374..9b34e385 100644 --- a/resources/views/components/footer.blade.php +++ b/resources/views/components/footer.blade.php @@ -1,5 +1,5 @@ @if ($showSupport || $showTimezone) -
    +
    @if($showSupport)
    Powered by diff --git a/resources/views/components/header.blade.php b/resources/views/components/header.blade.php index fbb3339c..6b4821d5 100644 --- a/resources/views/components/header.blade.php +++ b/resources/views/components/header.blade.php @@ -12,7 +12,7 @@ @if ($dashboardLoginLink)
    - {{ __('Dashboard') }} + Dashboard @auth {{-- TODO: This form sucks... --}}
    @@ -20,8 +20,6 @@
    @endauth - {{-- Conditional Button. --}} -{{-- Subscribers--}}
    @endif
    diff --git a/resources/views/components/incident.blade.php b/resources/views/components/incident.blade.php index 5548b27e..0fc5261c 100644 --- a/resources/views/components/incident.blade.php +++ b/resources/views/components/incident.blade.php @@ -7,9 +7,9 @@

    @forelse($incidents as $incident) -
    +
    $incident->updates->isNotEmpty(), 'rounded-lg' => $incident->updates->isEmpty(), ])> @@ -43,8 +43,8 @@
    -
    -
    +
    +
    @foreach ($incident->updates as $update) @@ -54,7 +54,7 @@ {{ $update->created_at->diffForHumans() }} — -
    {!! $update->formattedMessage() !!}
    +
    {!! $update->formattedMessage() !!}
    @endforeach
    @@ -63,15 +63,15 @@ {{ $incident->timestamp->diffForHumans() }} — -
    {!! $incident->formattedMessage() !!}
    +
    {!! $incident->formattedMessage() !!}
    @empty -
    +
    -
    +
    {{ __('No incidents reported.') }}
    diff --git a/resources/views/components/schedule.blade.php b/resources/views/components/schedule.blade.php index 1ec66c48..d62af673 100644 --- a/resources/views/components/schedule.blade.php +++ b/resources/views/components/schedule.blade.php @@ -19,7 +19,7 @@
    -
    {!! $schedule->formattedMessage() !!}
    +
    {!! $schedule->formattedMessage() !!}
    @@ -31,7 +31,7 @@ {{ $update->created_at->diffForHumans() }} — -
    {!! $update->formattedMessage() !!}
    +
    {!! $update->formattedMessage() !!}
    @endforeach
    diff --git a/resources/views/components/schedules.blade.php b/resources/views/components/schedules.blade.php index 567218f8..5c0b027a 100644 --- a/resources/views/components/schedules.blade.php +++ b/resources/views/components/schedules.blade.php @@ -1,9 +1,9 @@
    -
    +

    {{ __('Planned Maintenance') }}

    -
    +
      @foreach($schedules as $schedule) diff --git a/resources/views/setup/index.blade.php b/resources/views/setup/index.blade.php index 3c85736e..72703b0e 100644 --- a/resources/views/setup/index.blade.php +++ b/resources/views/setup/index.blade.php @@ -31,33 +31,33 @@
    -
    +
    - +
    - +
    - +
    - +
    - diff --git a/src/CachetCoreServiceProvider.php b/src/CachetCoreServiceProvider.php index 8162b8b3..48d73284 100644 --- a/src/CachetCoreServiceProvider.php +++ b/src/CachetCoreServiceProvider.php @@ -6,6 +6,8 @@ use Cachet\Models\Incident; use Cachet\Models\Schedule; use Cachet\Settings\AppSettings; +use Filament\Support\Colors\Color; +use Filament\Support\Facades\FilamentColor; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Foundation\Application; @@ -56,6 +58,10 @@ public function boot(): void Http::globalRequestMiddleware(fn ($request) => $request->withHeader( 'User-Agent', Cachet::USER_AGENT )); + + FilamentColor::register([ + 'cachet' => Color::rgb('rgb(4, 193, 71)'), + ]); } /** diff --git a/src/Data/Cachet/ThemeData.php b/src/Data/Cachet/ThemeData.php new file mode 100644 index 00000000..e7a33594 --- /dev/null +++ b/src/Data/Cachet/ThemeData.php @@ -0,0 +1,370 @@ + 'zinc', + 'red' => 'zinc', + 'orange' => 'neutral', + 'amber' => 'neutral', + 'yellow' => 'stone', + 'lime' => 'zinc', + 'green' => 'zinc', + 'emerald' => 'zinc', + 'teal' => 'gray', + 'cyan' => 'gray', + 'sky' => 'gray', + 'blue' => 'slate', + 'indigo' => 'slate', + 'violet' => 'gray', + 'purple' => 'gray', + 'fuchsia' => 'zinc', + 'pink' => 'zinc', + 'rose' => 'zinc', + ]; + + private const THEMES = [ + 'gray' => [ + 'light' => [ + 'accent' => Color::Gray[800], + 'accent-content' => Color::Gray[800], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => '255, 255, 255', + 'accent-content' => '255, 255, 255', + 'accent-foreground' => Color::Gray[800], + ], + ], + 'zinc' => [ + 'light' => [ + 'accent' => Color::Zinc[800], + 'accent-content' => Color::Zinc[800], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => '255, 255, 255', + 'accent-content' => '255, 255, 255', + 'accent-foreground' => Color::Zinc[800], + ], + ], + 'neutral' => [ + 'light' => [ + 'accent' => Color::Neutral[800], + 'accent-content' => Color::Neutral[800], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => '255, 255, 255', + 'accent-content' => '255, 255, 255', + 'accent-foreground' => Color::Neutral[800], + ], + ], + 'stone' => [ + 'light' => [ + 'accent' => Color::Stone[800], + 'accent-content' => Color::Stone[800], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => '255, 255, 255', + 'accent-content' => '255, 255, 255', + 'accent-foreground' => Color::Stone[800], + ], + ], + 'red' => [ + 'light' => [ + 'accent' => Color::Red[500], + 'accent-content' => Color::Red[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Red[500], + 'accent-content' => Color::Red[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'orange' => [ + 'light' => [ + 'accent' => Color::Orange[500], + 'accent-content' => Color::Orange[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Orange[400], + 'accent-content' => Color::Orange[400], + 'accent-foreground' => Color::Orange[950], + ], + ], + 'amber' => [ + 'light' => [ + 'accent' => Color::Amber[400], + 'accent-content' => Color::Amber[600], + 'accent-foreground' => Color::Amber[950], + ], + 'dark' => [ + 'accent' => Color::Amber[400], + 'accent-content' => Color::Amber[400], + 'accent-foreground' => Color::Amber[950], + ], + ], + 'yellow' => [ + 'light' => [ + 'accent' => Color::Yellow[400], + 'accent-content' => Color::Yellow[600], + 'accent-foreground' => Color::Yellow[950], + ], + 'dark' => [ + 'accent' => Color::Yellow[400], + 'accent-content' => Color::Yellow[400], + 'accent-foreground' => Color::Yellow[950], + ], + ], + 'lime' => [ + 'light' => [ + 'accent' => Color::Lime[400], + 'accent-content' => Color::Lime[600], + 'accent-foreground' => Color::Lime[900], + ], + 'dark' => [ + 'accent' => Color::Lime[400], + 'accent-content' => Color::Lime[400], + 'accent-foreground' => Color::Lime[950], + ], + ], + 'green' => [ + 'light' => [ + 'accent' => Color::Green[600], + 'accent-content' => Color::Green[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Green[600], + 'accent-content' => Color::Green[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'emerald' => [ + 'light' => [ + 'accent' => Color::Emerald[600], + 'accent-content' => Color::Emerald[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Emerald[600], + 'accent-content' => Color::Emerald[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'teal' => [ + 'light' => [ + 'accent' => Color::Teal[600], + 'accent-content' => Color::Teal[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Teal[600], + 'accent-content' => Color::Teal[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'cyan' => [ + 'light' => [ + 'accent' => Color::Cyan[600], + 'accent-content' => Color::Cyan[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Cyan[600], + 'accent-content' => Color::Cyan[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'sky' => [ + 'light' => [ + 'accent' => Color::Sky[600], + 'accent-content' => Color::Sky[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Sky[600], + 'accent-content' => Color::Sky[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'blue' => [ + 'light' => [ + 'accent' => Color::Blue[500], + 'accent-content' => Color::Blue[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Blue[500], + 'accent-content' => Color::Blue[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'indigo' => [ + 'light' => [ + 'accent' => Color::Indigo[500], + 'accent-content' => Color::Indigo[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Indigo[500], + 'accent-content' => Color::Indigo[300], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'violet' => [ + 'light' => [ + 'accent' => Color::Violet[500], + 'accent-content' => Color::Violet[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Violet[500], + 'accent-content' => Color::Violet[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'purple' => [ + 'light' => [ + 'accent' => Color::Purple[500], + 'accent-content' => Color::Purple[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Purple[500], + 'accent-content' => Color::Purple[300], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'fuchsia' => [ + 'light' => [ + 'accent' => Color::Fuchsia[600], + 'accent-content' => Color::Fuchsia[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Fuchsia[600], + 'accent-content' => Color::Fuchsia[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'pink' => [ + 'light' => [ + 'accent' => Color::Pink[600], + 'accent-content' => Color::Pink[600], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Pink[600], + 'accent-content' => Color::Pink[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + 'rose' => [ + 'light' => [ + 'accent' => Color::Rose[500], + 'accent-content' => Color::Rose[500], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => Color::Rose[500], + 'accent-content' => Color::Rose[400], + 'accent-foreground' => '255, 255, 255', + ], + ], + ]; + + #[Computed] + public string $styles; + + public function __construct( + #[Required] + public readonly ThemeSettings $themeSettings, + ) { + $this->generate(); + } + + /** + * Generate the theme from the given settings. + */ + protected function generate(): void + { + $accent = $this->themeSettings->accent; + if ($accent === 'cachet') { + $primaryColor = FilamentColor::getColors()['cachet']; + + $theme = [ + 'light' => [ + 'accent' => $primaryColor[500], + 'accent-content' => $primaryColor[500], + 'accent-foreground' => '255, 255, 255', + ], + 'dark' => [ + 'accent' => $primaryColor[500], + 'accent-content' => $primaryColor[400], + 'accent-foreground' => $primaryColor[950], + ], + ]; + } else { + $theme = self::THEMES[$accent]; + } + + if ($this->themeSettings->accent_pairing) { + $pairing = self::THEME_PAIRINGS[$accent]; + } else { + $pairing = $this->themeSettings->accent_content; + } + + $this->styles = $this->compileCss($theme, $pairing); + } + + /** + * Compile the CSS from the theme and background pairing. + */ + protected function compileCss(array $theme, string $pairing): string + { + $pairingKey = ucwords($pairing); + $pairingColor = constant("Filament\Support\Colors\Color::{$pairingKey}"); + + return <<columnSpanFull(), ]), - Forms\Components\Section::make()->columns(2)->schema([ - Forms\Components\ColorPicker::make('primary') - ->label(__('Primary')) - ->rgba(), + Forms\Components\Section::make()->columns(2) + ->heading(__('Status Page Accent')) + ->description(__('Customize the accent color of your status page. Cachet can automatically select a matching base color.')) + ->schema([ + Forms\Components\Select::make('accent') + ->label(__('Accent Color')) + ->options([ + ...collect(Color::all()) + ->except(ThemeData::GRAYS) + ->prepend(FilamentColor::getColors()['cachet'], 'cachet') + ->map(function (array $shades, string $color) { + $colorName = __(ucwords($color)); - Forms\Components\ColorPicker::make('secondary') - ->label(__('Secondary')) - ->rgba(), - ]), + return "
    {$colorName}"; + }), + ]) + ->native(false) + ->allowHtml() + ->reactive() + ->afterStateUpdated(function (Forms\Get $get, Forms\Set $set, ?string $old, ?string $state) { + $accentPairing = $get('accent_pairing'); + + if ($accentPairing) { + $set('accent_content', ThemeData::matchPairing($state)); + } + }), + + Forms\Components\Select::make('accent_content') + ->label(__('Base Color')) + ->options(function () { + return [ + ...collect(Color::all())->only(ThemeData::GRAYS)->map(function (array $shades, string $color) { + $colorName = __(ucwords($color)); + + return "
    {$colorName}"; + }), + ]; + }) + ->native(false) + ->disabled(fn (Forms\Get $get) => $get('accent_pairing') === true) + ->allowHtml(), + + Forms\Components\Toggle::make('accent_pairing') + ->label(__('Accent Pairing')) + ->reactive() + ->afterStateUpdated(function (Forms\Get $get, Forms\Set $set, ?bool $old, ?bool $state) { + $accent = $get('accent'); + + if ($state) { + $set('accent_content', ThemeData::matchPairing($accent)); + } + }), + ]), ]); } } diff --git a/src/Settings/ThemeSettings.php b/src/Settings/ThemeSettings.php index a7dad7fc..337c45b9 100644 --- a/src/Settings/ThemeSettings.php +++ b/src/Settings/ThemeSettings.php @@ -8,6 +8,12 @@ class ThemeSettings extends Settings { public ?string $app_banner; + public ?string $accent; + + public ?string $accent_content; + + public bool $accent_pairing = true; + public static function group(): string { return 'theme'; diff --git a/src/View/Components/Cachet.php b/src/View/Components/Cachet.php index bd197539..7b0c8574 100644 --- a/src/View/Components/Cachet.php +++ b/src/View/Components/Cachet.php @@ -2,8 +2,10 @@ namespace Cachet\View\Components; +use Cachet\Data\Cachet\ThemeData; use Cachet\Settings\AppSettings; use Cachet\Settings\CustomizationSettings; +use Cachet\Settings\ThemeSettings; use Closure; use Illuminate\Contracts\View\View; use Illuminate\Support\Str; @@ -17,6 +19,7 @@ class Cachet extends Component public function __construct( private readonly AppSettings $appSettings, private readonly CustomizationSettings $customizationSettings, + private readonly ThemeSettings $themeSettings, private ?string $title = null, private ?string $description = null ) { @@ -47,6 +50,7 @@ public function render(): View|Closure|string 'cachet_css' => $this->customizationSettings->stylesheet, 'cachet_footer' => $this->customizationSettings->footer, 'refresh_rate' => $this->appSettings->refresh_rate, + 'theme' => new ThemeData($this->themeSettings), ]); } } diff --git a/tailwind.config.js b/tailwind.config.js index 1794a79a..1229785d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -10,6 +10,12 @@ export default { theme: { extend: { colors: { + accent: { + DEFAULT: 'rgba(var(--accent), )', + content: 'rgba(var(--accent-content), )', + foreground: 'rgba(var(--accent-foreground), )', + background: 'rgba(var(--accent-background), )', + }, primary: { '50': '#e6f7ed', '100': '#c1edce', @@ -35,14 +41,6 @@ export default { 900: 'rgba(var(--c-900), )', 950: 'rgba(var(--c-950), )', }, - background: { - light: 'var(--background-light)', - dark: 'var(--background-dark)', - }, - base: { - light: 'var(--text-light)', - dark: 'var(--text-dark)', - }, }, }, },