From a9ab060b781f1a31028f8e6e1a42e5b6a524639f Mon Sep 17 00:00:00 2001 From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com> Date: Fri, 24 Oct 2025 20:00:42 +0200 Subject: [PATCH] chore: theme names, unique ids and improved README (#1485) * fix: theme names * fix: theme names * chore: runtime validation for unique theme id * chore: docs * chore: README * chore: README * chore: README --- README.md | 8 +++- packages/theme-store/README.md | 42 ++++++++++++++++--- .../src/{github-contrast.ts => github.ts} | 4 +- packages/theme-store/src/index.ts | 29 ++++++++----- .../src/{default.ts => openstatus.ts} | 14 +++---- packages/theme-store/src/supabase.ts | 2 +- packages/theme-store/src/types.ts | 24 +++++------ packages/theme-store/src/utils.ts | 13 ++++++ 8 files changed, 96 insertions(+), 40 deletions(-) rename packages/theme-store/src/{github-contrast.ts => github.ts} (95%) rename packages/theme-store/src/{default.ts => openstatus.ts} (87%) create mode 100644 packages/theme-store/src/utils.ts diff --git a/README.md b/README.md index 33862a29..335b5011 100644 --- a/README.md +++ b/README.md @@ -129,15 +129,19 @@ In another terminal, run the following command: pnpm dx ``` -4. Launch the web app +4. Launch whatever app you wish to: ```sh pnpm dev:web +pnpm dev:status-page +pnpm dev:dashboard ``` +The above commands whill automatically run the libSQL client on `8080` so you might wanna kill the turso command from step 3. + 5. See the results: -- open [http://localhost:3000](http://localhost:3000) for the web app +- open [http://localhost:3000](http://localhost:3000) (default port) ### Videos diff --git a/packages/theme-store/README.md b/packages/theme-store/README.md index d1b7945d..62fa7b3d 100644 --- a/packages/theme-store/README.md +++ b/packages/theme-store/README.md @@ -1,5 +1,7 @@ # Community Themes +**Help us build epic status page themes!** + This directory contains community-contributed themes for openstatus status pages. These themes allow users to customize the visual appearance of their status pages with different color schemes and design styles. ## What are Community Themes? @@ -12,9 +14,10 @@ Community themes are predefined color schemes that users can apply to their stat ## Themes Examples -- **Default** - The standard openstatus theme -- **GitHub (High Contrast)** - High contrast theme inspired by GitHub's design +- **Openstatus** - The standard openstatus theme +- **Openstatus (Rounded)** - The rounded openstatus theme (similar to the legacy page) - **Supabase** - Theme matching Supabase's brand colors +- **GitHub (High Contrast)** - High contrast theme inspired by GitHub's design ## Creating a New Theme @@ -23,13 +26,26 @@ Community themes are predefined color schemes that users can apply to their stat Want to contribute a theme? Follow these steps: -### 1. Fork the Repository -Start by forking the openstatus repository to your GitHub account. +### 1. Run the project + +Start by forking the openstatus repository to your GitHub account and run the command `dev:status-page` locally following [Getting Started](https://github.com/openstatusHQ/openstatus?tab=readme-ov-file#getting-started-) steps. + +Once you run the `@openstatus/status-page` app, e.g. via the following command in the root directory: + +```bash +pnpm dev:status-page +``` + +You can either access [localhost:3000](http://localhost:3000) to see the theme explorer or [localhost:3000/status](http://localhost:3000/status) to have a status page with seeded entries. On the bottom right is a configration button which has access to settings, including the community theme. + +> If something is off, our you think improvements can be made, feel free to raise an issue, join Discord, or DM us! ### 2. Create Your Theme File + Create a new TypeScript file in this directory (e.g., `my-theme.ts`). You can copy an existing theme file as a starting template. ### 3. Define Your Theme + Your theme file should export a constant that matches the `Theme` interface: ```typescript @@ -40,7 +56,7 @@ export const MY_THEME = { name: "My Awesome Theme", // Display name author: { name: "@yourusername", - url: "https://github.com/yourusername" + url: "https://github.com/yourusername" // Add your personal website or a social link }, light: { // CSS custom properties for light mode @@ -57,10 +73,24 @@ export const MY_THEME = { } as const satisfies Theme; ``` +You don't need to add every single css var from the `THEME_VAR_NAMES` list. + ### 4. Add to Theme Registry + Update `index.ts` to include your theme in the `THEMES_LIST` array. +```typescript +const THEMES_LIST = [ + OPENSTATUS_THEME, + OPENSTATUS_ROUNDED_THEME, + //... + MY_THEME +] satisfies Theme[]; + +``` + ### 5. Submit a Pull Request + Create a pull request with your theme for review. ## Design Guidelines @@ -94,7 +124,7 @@ Before submitting: 3. Check accessibility with browser dev tools 4. Ensure all status indicators (operational, degraded, etc.) are clearly distinguishable -To test a theme, you can use the `sessionStorage.setItem("community-theme", "true");` on your stpg.dev or vercel preview link. It will open a floating button on the right left corner where you can choose between the themes and dark/light mode. +To test a theme on non-development environment, you can use the `sessionStorage.setItem("community-theme", "true");`. It will open a floating button on the right left corner where you can choose between the themes and dark/light mode. ## Questions? diff --git a/packages/theme-store/src/github-contrast.ts b/packages/theme-store/src/github.ts similarity index 95% rename from packages/theme-store/src/github-contrast.ts rename to packages/theme-store/src/github.ts index b7c9ef36..184d5457 100644 --- a/packages/theme-store/src/github-contrast.ts +++ b/packages/theme-store/src/github.ts @@ -1,8 +1,8 @@ import type { Theme } from "./types"; -export const GITHUB_CONTRAST = { +export const GITHUB_HIGH_CONTRAST_THEME = { id: "github-contrast", - name: "Github (High Contrast)", + name: "GitHub (High Contrast)", author: { name: "@openstatus", url: "https://openstatus.dev" }, light: { "--background": "oklch(100% 0 0)", diff --git a/packages/theme-store/src/index.ts b/packages/theme-store/src/index.ts index 7ea73e9d..35c6de81 100644 --- a/packages/theme-store/src/index.ts +++ b/packages/theme-store/src/index.ts @@ -1,17 +1,20 @@ export * from "./types"; -import { DEFAULT_ROUNDED_THEME, DEFAULT_THEME } from "./default"; -import { GITHUB_CONTRAST } from "./github-contrast"; -import { SUPABASE } from "./supabase"; +import { GITHUB_HIGH_CONTRAST_THEME } from "./github"; +import { OPENSTATUS_ROUNDED_THEME, OPENSTATUS_THEME } from "./openstatus"; +import { SUPABASE_THEME } from "./supabase"; import type { Theme, ThemeMap } from "./types"; +import { assertUniqueThemeIds } from "./utils"; -// TODO: Add validation to ensure that the theme IDs are unique const THEMES_LIST = [ - DEFAULT_THEME, - DEFAULT_ROUNDED_THEME, - SUPABASE, - GITHUB_CONTRAST, + OPENSTATUS_THEME, + OPENSTATUS_ROUNDED_THEME, + SUPABASE_THEME, + GITHUB_HIGH_CONTRAST_THEME, ] satisfies Theme[]; +// NOTE: runtime validation to ensure that the theme IDs are unique +assertUniqueThemeIds(THEMES_LIST); + export const THEMES = THEMES_LIST.reduce((acc, theme) => { acc[theme.id as keyof ThemeMap] = theme; return acc; @@ -20,8 +23,14 @@ export const THEMES = THEMES_LIST.reduce((acc, theme) => { export const THEME_KEYS = THEMES_LIST.map((theme) => theme.id); export type ThemeKey = (typeof THEME_KEYS)[number]; -export function generateThemeStyles(themeKey: ThemeKey = "default") { - const theme = THEMES[themeKey]; +export function generateThemeStyles(themeKey?: string) { + let theme = themeKey ? THEMES[themeKey] : undefined; + + if (!theme) { + // NOTE: fallback to openstatus theme if no theme is found + theme = OPENSTATUS_THEME; + } + const lightVars = Object.entries(theme.light) .map(([key, value]) => `${key}: ${value};`) .join("\n "); diff --git a/packages/theme-store/src/default.ts b/packages/theme-store/src/openstatus.ts similarity index 87% rename from packages/theme-store/src/default.ts rename to packages/theme-store/src/openstatus.ts index cd3b66ed..d67f83bf 100644 --- a/packages/theme-store/src/default.ts +++ b/packages/theme-store/src/openstatus.ts @@ -1,8 +1,8 @@ import type { Theme } from "./types"; -export const DEFAULT_THEME = { +export const OPENSTATUS_THEME = { id: "default" as const, - name: "Default", + name: "Openstatus", author: { name: "@openstatus", url: "https://openstatus.dev" }, light: { "--background": "oklch(100% 0 0)", @@ -46,16 +46,16 @@ export const DEFAULT_THEME = { }, } as const satisfies Theme; -export const DEFAULT_ROUNDED_THEME = { +export const OPENSTATUS_ROUNDED_THEME = { id: "default-rounded" as const, - name: "Default (Rounded)", - author: DEFAULT_THEME.author, + name: "Openstatus (Rounded)", + author: OPENSTATUS_THEME.author, light: { - ...DEFAULT_THEME.light, + ...OPENSTATUS_THEME.light, "--radius": "0.625rem", }, dark: { - ...DEFAULT_THEME.dark, + ...OPENSTATUS_THEME.dark, "--radius": "0.625rem", }, } as const satisfies Theme; diff --git a/packages/theme-store/src/supabase.ts b/packages/theme-store/src/supabase.ts index 614b49f5..e5dfdc8d 100644 --- a/packages/theme-store/src/supabase.ts +++ b/packages/theme-store/src/supabase.ts @@ -1,6 +1,6 @@ import type { Theme } from "./types"; -export const SUPABASE = { +export const SUPABASE_THEME = { id: "supabase", name: "Supabase", author: { name: "@supabase", url: "https://supabase.com/" }, diff --git a/packages/theme-store/src/types.ts b/packages/theme-store/src/types.ts index 83369259..35de586d 100644 --- a/packages/theme-store/src/types.ts +++ b/packages/theme-store/src/types.ts @@ -1,4 +1,5 @@ export const THEME_VAR_NAMES = [ + // NOTE: default shadcn/ui colors "--radius", "--background", "--foreground", @@ -14,26 +15,25 @@ export const THEME_VAR_NAMES = [ "--muted-foreground", "--accent", "--accent-foreground", - "--destructive", "--border", "--input", "--ring", + "--destructive", // red, outage/error status + // NOTE: the following colors are used for the public monitors UI to differentiate the percentiles of the response times "--chart-1", "--chart-2", "--chart-3", "--chart-4", "--chart-5", - "--sidebar", - "--sidebar-foreground", - "--sidebar-primary", - "--sidebar-primary-foreground", - "--sidebar-accent", - "--sidebar-accent-foreground", - "--sidebar-border", - "--sidebar-ring", - "--success", - "--warning", - "--info", + + // NOTE: the following colors are not part of shadcn/ui, but are essential part of the status page + "--success", // green, operational status + "--warning", // yellow, degraded status + "--info", // blue, monitoring status + + // NOTE: the following colors are used for the public monitors UI to differentiate the different regions + // It is not required to add them to your custom theme, but you can if you want to. + // DEFAULT: https://github.com/openstatusHQ/openstatus/blob/main/apps/status-page/src/app/globals.css#L98 "--rainbow-1", "--rainbow-2", "--rainbow-3", diff --git a/packages/theme-store/src/utils.ts b/packages/theme-store/src/utils.ts new file mode 100644 index 00000000..82b70b05 --- /dev/null +++ b/packages/theme-store/src/utils.ts @@ -0,0 +1,13 @@ +import type { Theme } from "./types"; + +export function assertUniqueThemeIds(themes: Theme[]) { + const seen = new Set(); + for (const theme of themes) { + if (seen.has(theme.id)) { + throw new Error( + `Duplicate theme ID detected: "${theme.id}" in theme "${theme.name}". All theme IDs must be unique.`, + ); + } + seen.add(theme.id); + } +} -- 2.51.2