Replace theme screenshots with color palette preview (#6722)

* Replace theme screenshots with color palette preview

- Replace screenshot images in theme installer with 3x2 color palette grid
- Add colors array to catalog themes in customThemeCatalog.json
- Update CatalogTheme type to include optional colors property
- Remove getThemeScreenshotUrl function
- Extract ColorPalette component to separate file
- Update documentation to reflect color palette instead of screenshots
- Update tests to verify color palettes instead of images
- Extract actual theme colors from GitHub repositories

* Update ColorPalette component to use data-swatch attribute for color swatches in ThemeInstaller tests

* Update packages/desktop-client/src/data/customThemeCatalog.json

Co-authored-by: Michael Clark <5285928+MikesGlitch@users.noreply.github.com>

* Update color palettes for Simple Dark and Okabe Ito themes

Co-authored-by: matiss <matiss@mja.lv>

---------

Co-authored-by: Michael Clark <5285928+MikesGlitch@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Matiss Janis Aboltins
2026-01-20 19:00:34 +01:00
committed by GitHub
parent 6a9df6562c
commit c9a0ffa91c
9 changed files with 99 additions and 61 deletions

View File

@@ -0,0 +1,30 @@
import { View } from '@actual-app/components/view';
const DEFAULT_COLORS = ['#ccc', '#999', '#666', '#333', '#111', '#000'];
type ColorPaletteProps = {
colors?: string[];
};
export function ColorPalette({ colors }: ColorPaletteProps) {
// Default fallback colors if not provided
const paletteColors = colors ?? DEFAULT_COLORS;
return (
<View
style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gridTemplateRows: 'repeat(2, 1fr)',
width: '100%',
height: 60,
borderRadius: 4,
overflow: 'hidden',
}}
>
{paletteColors.slice(0, 6).map((color, i) => (
<div key={i} data-swatch style={{ backgroundColor: color }} />
))}
</View>
);
}

View File

@@ -27,10 +27,6 @@ vi.mock('@desktop-client/style/customThemes', async () => {
normalizeGitHubRepo: vi.fn((repo: string) =>
repo.startsWith('http') ? repo : `https://github.com/${repo}`,
),
getThemeScreenshotUrl: vi.fn(
(repo: string) =>
`https://raw.githubusercontent.com/${repo}/refs/heads/main/screenshot.png`,
),
};
});
@@ -51,14 +47,38 @@ describe('ThemeInstaller', () => {
{
name: 'Demo Theme',
repo: 'actualbudget/demo-theme',
colors: [
'#1a1a2e',
'#16213e',
'#0f3460',
'#e94560',
'#533483',
'#f1f1f1',
],
},
{
name: 'Ocean Blue',
repo: 'actualbudget/ocean-theme',
colors: [
'#0d47a1',
'#1565c0',
'#1976d2',
'#1e88e5',
'#42a5f5',
'#90caf9',
],
},
{
name: 'Forest Green',
repo: 'actualbudget/forest-theme',
colors: [
'#1b5e20',
'#2e7d32',
'#388e3c',
'#43a047',
'#66bb6a',
'#a5d6a7',
],
},
];
@@ -562,21 +582,21 @@ describe('ThemeInstaller', () => {
});
describe('catalog theme display', () => {
it('displays theme screenshot URL correctly', () => {
it('displays theme color palette correctly', () => {
render(
<ThemeInstaller onInstall={mockOnInstall} onClose={mockOnClose} />,
);
const images = screen.getAllByRole('img');
expect(images.length).toBeGreaterThan(0);
// Check that color palettes are rendered instead of images
const images = screen.queryAllByRole('img');
expect(images.length).toBe(0);
// Check that images have the correct src pattern
images.forEach(img => {
expect(img).toHaveAttribute(
'src',
expect.stringContaining('raw.githubusercontent.com'),
);
// Check that color swatches are rendered (6 divs per theme)
const demoThemeButton = screen.getByRole('button', {
name: 'Demo Theme',
});
const colorSwatches = demoThemeButton.querySelectorAll('[data-swatch]');
expect(colorSwatches.length).toBe(6);
});
it('displays theme author correctly', () => {

View File

@@ -11,6 +11,8 @@ import { Text } from '@actual-app/components/text';
import { theme as themeStyle } from '@actual-app/components/theme';
import { View } from '@actual-app/components/view';
import { ColorPalette } from './ColorPalette';
import { Link } from '@desktop-client/components/common/Link';
import { FixedSizeList } from '@desktop-client/components/FixedSizeList';
import { useThemeCatalog } from '@desktop-client/hooks/useThemeCatalog';
@@ -18,7 +20,6 @@ import {
extractRepoOwner,
fetchThemeCss,
generateThemeId,
getThemeScreenshotUrl,
normalizeGitHubRepo,
validateThemeCss,
type CatalogTheme,
@@ -337,16 +338,7 @@ export function ThemeInstaller({
}}
/>
</View>
<img
src={getThemeScreenshotUrl(theme.repo)}
alt={theme.name}
style={{
width: '100%',
height: 60,
objectFit: 'cover',
borderRadius: 4,
}}
/>
<ColorPalette colors={theme.colors} />
<Text
style={{
fontSize: 12,

View File

@@ -1,34 +1,37 @@
[
{
"name": "Demo Theme",
"repo": "actualbudget/demo-theme"
"repo": "actualbudget/demo-theme",
"colors": ["#f5f1e8", "#5a4a38", "#b8956a", "#4a3f2e", "#fffef9", "#d4b896"]
},
{
"name": "Shades of Coffee",
"repo": "Juulz/shades-of-coffee"
"repo": "Juulz/shades-of-coffee",
"colors": ["#e2d8cf", "#1a0c00", "#cfb3ff", "#f5f2ef", "#604b39", "#c29670"]
},
{
"name": "Miami Beach",
"repo": "Juulz/miami-beach"
"repo": "Juulz/miami-beach",
"colors": ["#d095ca", "#5e2a91", "#c5ece4", "#24b2a0", "#f9deb8", "#1e9484"]
},
{
"name": "Simple Dark",
"repo": "Juulz/simple-dark"
"repo": "Juulz/simple-dark",
"colors": ["#222222", "#063446", "#14aeeb", "#434343", "#edbe5e", "#d9e2ec"]
},
{
"name": "Matrix",
"repo": "MatissJanis/actualbudget-matrix-theme"
"repo": "MatissJanis/actualbudget-matrix-theme",
"colors": ["#000000", "#001a00", "#00ff41", "#004400", "#39ff14", "#00ff00"]
},
{
"name": "Black Gold",
"repo": "MikesGlitch/actual-black-gold-theme"
},
{
"name": "Simple Dark",
"repo": "Juulz/miami-beach"
"repo": "MikesGlitch/actual-black-gold-theme",
"colors": ["#141520", "#242733", "#373B4A", "#E8ECF0", "#FFD700", "#8F7A20"]
},
{
"name": "Okabe Ito",
"repo": "Juulz/okabe-ito"
"repo": "Juulz/okabe-ito",
"colors": ["#222222", "#141520", "#e69f00", "#56b4e9", "#b88115", "#00304d"]
}
]

View File

@@ -2,8 +2,7 @@ import { useEffect, useState } from 'react';
import { type CatalogTheme } from '@desktop-client/style/customThemes';
const CATALOG_URL =
'https://raw.githubusercontent.com/actualbudget/actual/master/packages/desktop-client/src/data/customThemeCatalog.json';
const CATALOG_URL = `https://raw.githubusercontent.com/actualbudget/actual/${process.env.REACT_APP_BRANCH || 'master'}/packages/desktop-client/src/data/customThemeCatalog.json`;
/**
* Custom hook to fetch and manage the theme catalog from GitHub.

View File

@@ -5,6 +5,7 @@
export type CatalogTheme = {
name: string;
repo: string;
colors?: string[];
};
export type InstalledTheme = {
@@ -50,24 +51,6 @@ export function normalizeGitHubRepo(repo: string): string {
return `https://github.com/${owner}/${repoName}`;
}
/**
* Get the screenshot URL for a theme repo.
* Returns a safe fallback URL for malformed repos.
*/
export function getThemeScreenshotUrl(repo: string): string {
if (
!repo ||
typeof repo !== 'string' ||
!repo.trim() ||
!repo.includes('/')
) {
// Return a placeholder or empty data URL for malformed repos
return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQwIiBoZWlnaHQ9IjYwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxNDAiIGhlaWdodD0iNjAiIGZpbGw9IiNmNWY1ZjUiLz48L3N2Zz4=';
}
const trimmed = repo.trim();
return `https://raw.githubusercontent.com/${trimmed}/refs/heads/main/screenshot.png`;
}
/**
* Try fetching actual.css from main branch.
*/

View File

@@ -77,6 +77,7 @@ export default defineConfig(async ({ mode }) => {
// Forward Netlify env variables
if (process.env.REVIEW_ID) {
process.env.REACT_APP_REVIEW_ID = process.env.REVIEW_ID;
process.env.REACT_APP_BRANCH = process.env.BRANCH;
}
let resolveExtensions = [

View File

@@ -28,7 +28,7 @@ The easiest way to install a custom theme is to choose one from the catalog:
3. The theme installer will open showing available themes from the catalog
4. Click on any theme to install it immediately
Themes in the catalog are hosted on GitHub and are automatically fetched when you select them. Each theme shows a preview screenshot and includes a link to its source repository.
Themes in the catalog are hosted on GitHub and are automatically fetched when you select them. Each theme shows a color palette preview (6 colors in a 3x2 grid) and includes a link to its source repository.
### Installing a Theme by Pasting CSS
@@ -124,14 +124,12 @@ To share your theme with others or add it to the catalog, you can host it on Git
1. Create a new GitHub repository
2. Create a file named `actual.css` in the root directory (on the `main` branch)
3. Add your theme CSS to this file
4. Add a `screenshot.png` file for catalog display (recommended size: 140x60px or similar)
**Example repository structure:**
```text
your-theme-repo/
── actual.css # Your theme CSS
└── screenshot.png # Preview image
── actual.css # Your theme CSS
```
**Example `actual.css`:**
@@ -149,6 +147,8 @@ your-theme-repo/
The theme can then be referenced in the catalog using the format `owner/repo` (e.g., `actualbudget/demo-theme`).
When your theme is added to the catalog, it will display a color palette preview. The palette is defined in the catalog JSON file and should include 6 representative colors from your theme (typically background colors, accent colors, and text colors).
### Example Theme
For a complete example of a custom theme, check out the [demo theme repository](https://github.com/actualbudget/demo-theme). This repository contains multiple theme variations and demonstrates the proper structure and format.
@@ -157,7 +157,6 @@ The demo theme includes examples of:
- Proper CSS variable naming
- Complete theme definitions
- Screenshot assets for catalog display
You can use this as a template for creating your own themes.
@@ -176,5 +175,10 @@ To have your theme added to the official catalog, you'll need to:
1. Host your theme on GitHub following the structure above
2. Open an issue or pull request on the Actual repository requesting your theme be added to the catalog
3. Provide the repository name in `owner/repo` format
4. Include 6 representative colors for the color palette preview (as an array of hex color values)
The catalog is maintained in `packages/desktop-client/src/data/customThemeCatalog.json`.
The catalog is maintained in `packages/desktop-client/src/data/customThemeCatalog.json`. Each theme entry includes:
- `name`: The theme name
- `repo`: The GitHub repository in `owner/repo` format
- `colors`: An array of 6 hex color values for the palette preview (e.g., `["#1a1a2e", "#16213e", "#0f3460", "#e94560", "#533483", "#f1f1f1"]`)

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [MatissJanis]
---
Themes: use color palette for preview instead of screenshots