81 lines
5.4 KiB
Markdown
81 lines
5.4 KiB
Markdown
# Theme System
|
|
|
|
How the look of an installation is chosen and applied. Setting up a platform's theme and branding step by step:
|
|
[MULTI_PLATFORM_INSTANCES.md](MULTI_PLATFORM_INSTANCES.md), sections 3, 5 and 6.
|
|
|
|
Last checked against the code: 2026-10-19 (branch `laravel-11`).
|
|
|
|
## Where the active theme comes from
|
|
|
|
1. `platform/theme.php` in the installation's platform folder: one theme definition with its own `id`. When it
|
|
exists, `App\Providers\PlatformServiceProvider` registers it in `themes.themes.<id>` and makes it the active theme.
|
|
2. Otherwise `config('themes.active')`, which is `TIMEBANK_THEME` in `.env` (default `timebank_cc`), one of the themes
|
|
in `config/themes.php`: `timebank_cc`, `uuro`, `vegetable`, `yellow` (template `config/themes.php.example`, from
|
|
which `deploy.sh` creates the file and `config:merge` adds new keys).
|
|
|
|
A theme definition has `name`, `description`, `logos`, `colors` and `typography`. `colors` needs every key the
|
|
templates have (`primary`, `secondary`, `button-secondary`, `button-secondary-text`, `accent`, `success`, `warning`,
|
|
`danger-dark`, `danger`, `danger-light`, `info`, `brand`, `background`, `border`, `surface`, `reaction`,
|
|
`reaction-hover`, `text`, `logo`): a missing key leaves its CSS variable empty. Copy a whole block from
|
|
`config/themes.php.example` when you make a theme.
|
|
|
|
## How it is applied: at runtime, one build for all themes
|
|
|
|
- `theme_css_vars()` turns the active theme's colours and typography into CSS custom properties
|
|
(`--color-primary-500: 37 99 235;`, `--font_family_body: ...`). The layouts (`layouts/app`, `layouts/guest`) put
|
|
them in a `<style>` block on every page.
|
|
- `tailwind.config.js` defines the theme colours as `rgb(var(--color-...) / <alpha-value>)`, so classes such as
|
|
`bg-theme-brand`, `text-theme-primary`, `border-theme-border` and `bg-primary-500` follow the active theme.
|
|
- So switching themes needs no `npm run build`: one asset build serves every theme. Only `VITE_*` values in `.env`
|
|
are compiled into the build.
|
|
- `<html data-theme="@themeId">` carries the theme id; no CSS depends on it.
|
|
|
|
## Logos, favicons and the preview image
|
|
|
|
Each asset is looked up in the platform folder first, then through the theme's `logos` settings:
|
|
|
|
| Asset | Platform folder | Theme setting | Helper |
|
|
|---|---|---|---|
|
|
| Website logo (inline SVG) | `platform/views/logos/<theme>.blade.php` | `logos.svg_inline`, a view name such as `logos.uuro` (`resources/views/logos/`) | `theme_logo('svg_inline')` |
|
|
| Email and PDF logo | `platform/public/mail-logo.png` | `logos.email_logo`, relative to `storage/app/public/` | `theme_email_logo_url()` (emails), `theme_email_logo_file()` (PDFs, inline images) |
|
|
| Email logo for dark headers | `platform/public/mail-logo-inverted.png` | `logos.email_logo_inverted` | the same with `true` |
|
|
| Favicons | `platform/public/` | `logos.favicon_dir`, relative to `public/` | `theme_favicon('favicon.ico')`; `theme_favicon_optional()` for `favicon.svg`, `favicon-96x96.png`, `site.webmanifest` |
|
|
| Preview image for shared links | `platform/public/og-image.png` | `logos.og_image`, relative to `public/` | `theme_og_image()` |
|
|
|
|
Without any of these, an email gets a generated PNG with the platform name (`App\Services\GeneratedLogo`; the
|
|
`timebank_cc` theme falls back to the tracked `app_mail_logo.png`), favicons come from `public/`, and there is no
|
|
preview image. The title, the icons and the Open Graph tags are in one partial that both layouts include:
|
|
`resources/views/layouts/partials/brand-head.blade.php`.
|
|
|
|
## Helpers and Blade directives
|
|
|
|
| PHP (`app/Helpers/ThemeHelper.php`) | Blade (`app/Providers/ThemeServiceProvider.php`) | Returns |
|
|
|---|---|---|
|
|
| `theme($key = null, $default = null)` | `@theme('key')` | the active theme's definition, or one key of it |
|
|
| `theme_id()` | `@themeId` | the active theme's id |
|
|
| `theme_name()` | `@themeName` | its display name |
|
|
| `theme_color('primary.500')` | `@themeColor('primary.500')` | a colour |
|
|
| `theme_font('font_family_body')` | `@themeFont('font_family_body')` | a typography value |
|
|
| `is_theme('uuro')` | `@isTheme('uuro') ... @endIsTheme` | whether that theme is active |
|
|
| `theme_css_vars()` | `@themeCssVars` | the CSS custom properties |
|
|
| `theme_logo($type)`, `theme_email_logo($inverted)` and the helpers in the table above | | logos, favicons, preview image |
|
|
|
|
Prefer theme classes and helpers over theme checks: code that asks `is_theme('uuro')` breaks for a theme of a new
|
|
platform. Only the generated email logo checks the id (`timebank_cc` keeps its own PNG).
|
|
|
|
In views always use theme classes, never fixed colours. UI patterns: [STYLE_GUIDE.md](STYLE_GUIDE.md),
|
|
[THEME_CLASS_CONVERSION_GUIDE.md](THEME_CLASS_CONVERSION_GUIDE.md).
|
|
|
|
## Adding a theme
|
|
|
|
- **For one platform**: in its `platform/theme.php`, with its logo in `platform/views/logos/<id>.blade.php`. Nothing
|
|
in the code changes. `php artisan platform:init --from-current` writes the active theme there as a start.
|
|
- **For every installation**: a block in `config/themes.php.example` and a logo view in `resources/views/logos/`,
|
|
committed to git. `config:merge themes` then adds it to each installation's `config/themes.php`.
|
|
|
|
## Testing
|
|
|
|
- `tests/Feature/ThemeButtonColorsTest.php`, `PlatformBrandingTest.php`, `PlatformFolderTest.php` (theme from the
|
|
platform folder) and `PlatformHeadTest.php` (icons and Open Graph).
|
|
- By hand: switch `TIMEBANK_THEME` in `.env` and reload; with a `platform/theme.php` present, that one wins.
|