Skip to content

Commit

Permalink
add theme generator
Browse files Browse the repository at this point in the history
  • Loading branch information
Houguiram committed May 5, 2024
1 parent 2c012c2 commit 43e352b
Show file tree
Hide file tree
Showing 12 changed files with 452 additions and 34 deletions.
26 changes: 11 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
# bun starter
# Omegui theme generator

## Getting Started
## Disclaimer

Click the [Use this template](https://github.com/wobsoriano/bun-lib-starter/generate) button to create a new repository with the contents starter.
This tool is still in early development and may not work as expected. Please report any issues you encounter.

OR
## Description

Run `bun create wobsoriano/bun-lib-starter ./my-lib`.
Omegui is a set of tools to make Tamagui easier to set up and customize.

## Setup
This is the theme generator, which allows you to create a theme for Tamagui's component library from a minimal set of colors. You can also use preset themes to get started quickly.

```bash
# install dependencies
bun install
## Upcoming

# test the app
bun test

# build the app, available under dist
bun run build
```
- Support for dark themes
- Better documentation
- Wrapper for Tamagui components to add color variants (e.g. primary, secondary, etc.)
- Support for theming border radius, shadows, animations and other properties (aiming for parity with daisyUI themes)

## License

Expand Down
Binary file modified bun.lockb
Binary file not shown.
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@
"bun"
],
"license": "MIT",
"homepage": "https://github.com/wobsoriano/pkg-name#readme",
"homepage": "TODO",
"repository": {
"type": "git",
"url": "git+https://github.com/wobsoriano/pkg-name.git"
"url": "TODO"
},
"bugs": "https://github.com/wobsoriano/pkg-name/issues",
"author": "Robert Soriano <sorianorobertc@gmail.com>",
"bugs": "TODO",
"author": "Marin Godechot <marin.godechot@gmail.com>",
"devDependencies": {
"bun-plugin-dts": "^0.2.1",
"@types/bun": "^1.0.0",
"typescript": "^5.2.2"
},
"dependencies": {
"colorjs.io": "^0.5.0"
}
}
}
25 changes: 25 additions & 0 deletions src/colorUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Color from "colorjs.io";

// TODO: Restrict type of color utils to make sure they are in the right color space

export const stringColorToOklch = (color: string): Color => {
const parsedColor = new Color(color);
parsedColor;
return parsedColor.to("oklch");
};

export const colorToHex = (color: Color): string => {
return color.to("srgb").toString({ format: "hex" });
};

export const colorToShade = (color: Color, shade: number): Color => {
const newColor = color.clone();
newColor.l += shade;
return newColor;
};

export const hasEnoughContrast = (color1: Color, color2: Color): boolean => {
const contrast = color1.contrast(color2, "WCAG21");
const hasEnoughContrast = contrast > 4.5;
return hasEnoughContrast;
};
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export const one = 1
export const two = 2
export { omeguiThemeGenerator } from "./themeGenerator";
83 changes: 83 additions & 0 deletions src/presetThemes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
export const presetThemes = {
emerald: {
primary: "#66cc8a",
primaryContent: "#223D30",
secondary: "#377cfb",
secondaryContent: "#fff",
accent: "#f68067",
accentContent: "#000",
neutral: "#333c4d",
neutralContent: "#f9fafb",
background: "oklch(100% 0 0)",
foreground: "#333c4d",
info: "#00d3ee",
infoContent: "#ffffff",
success: "#39DA8A",
successContent: "#ffffff",
warning: "#ffaa00",
warningContent: "#ffffff",
error: "#f44336",
errorContent: "#ffffff",
},
retro: {
primary: "#ef9995",
primaryContent: "#282425",
secondary: "#a4cbb4",
secondaryContent: "#282425",
accent: "#DC8850",
accentContent: "#282425",
neutral: "#2E282A",
neutralContent: "#EDE6D4",
background: "#ece3ca",
background2: "#e4d8b4",
background3: "#DBCA9A",
foreground: "#282425",
info: "#2563eb",
success: "#16a34a",
warning: "#d97706",
error: "oklch(65.72% 0.199 27.33)",
},
cyberpunk: {
primary: "oklch(74.22% 0.209 6.35)",
secondary: "oklch(83.33% 0.184 204.72)",
accent: "oklch(71.86% 0.2176 310.43)",
neutral: "oklch(23.04% 0.065 269.31)",
neutralContent: "oklch(94.51% 0.179 104.32)",
background: "oklch(94.51% 0.179 104.32)",
},
pastel: {
primary: "#d1c1d7",
secondary: "#f6cbd1",
accent: "#b4e9d6",
neutral: "#70acc7",
background: "oklch(100% 0 0)",
background2: "#f9fafb",
background3: "#d1d5db",
},
nord: {
primary: "#5E81AC",
secondary: "#81A1C1",
accent: "#88C0D0",
neutral: "#4C566A",
neutralContent: "#D8DEE9",
background: "#ECEFF4",
background2: "#E5E9F0",
background3: "#D8DEE9",
foreground: "#2E3440",
info: "#B48EAD",
success: "#A3BE8C",
warning: "#EBCB8B",
error: "#BF616A",
},
autumn: {
primary: "#8C0327",
secondary: "#D85251",
accent: "#D59B6A",
neutral: "#826A5C",
background: "#f1f1f1",
info: "#42ADBB",
success: "#499380",
warning: "#E97F14",
error: "oklch(53.07% 0.241 24.16)",
},
};
51 changes: 51 additions & 0 deletions src/themeGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { presetThemes } from "./presetThemes";
import { InputTheme } from "./themeTypes";
import { partialThemeToFullTheme, themeColorsToHexTheme } from "./themeUtils";

const inputThemeToTamaguiTheme = (inputTheme: InputTheme) => {
const convertedFullTheme = themeColorsToHexTheme(
partialThemeToFullTheme(inputTheme)
);
return {
...convertedFullTheme,
background: convertedFullTheme.background,
backgroundHover: convertedFullTheme.background3,
backgroundPress: convertedFullTheme.background3,
backgroundFocus: convertedFullTheme.background3,
borderColor: convertedFullTheme.background3,
borderColorPress: convertedFullTheme.background3,
borderColorFocus: convertedFullTheme.background3,
borderColorHover: convertedFullTheme.neutral,
color: convertedFullTheme.foreground,
colorHover: convertedFullTheme.foreground,
colorPress: convertedFullTheme.foreground,
colorFocus: convertedFullTheme.foreground,
shadowColor: "#363A3F1A",
shadowColorHover: "#363A3F26",
shadowColorPress: "#363A3F26",
shadowColorFocus: "#363A3F26",
placeholderColor: convertedFullTheme.neutral,
};
};

type ThemeGeneratorInput = keyof typeof presetThemes | InputTheme;

/**
* Generate a Tamagui theme from the provided input theme.
* @param light - The light theme, either a string corresponding to a preset theme or a custom theme object.
* @param dark - The dark theme, either a string corresponding to a preset theme or a custom theme object.
*/
export const omeguiThemeGenerator = ({
light,
dark,
}: {
light: ThemeGeneratorInput;
dark: ThemeGeneratorInput;
}) => {
const lightTheme = typeof light === "string" ? presetThemes[light] : light;
const darkTheme = typeof dark === "string" ? presetThemes[dark] : dark;
return {
light: inputThemeToTamaguiTheme(lightTheme),
dark: inputThemeToTamaguiTheme(darkTheme),
};
};
52 changes: 52 additions & 0 deletions src/themeTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export type InputTheme = {
// colorScheme: 'light' | 'dark'
primary: string;
primaryContent?: string;
secondary: string;
secondaryContent?: string;
accent: string;
accentContent?: string;
neutral: string;
neutralContent?: string;
background?: string;
background2?: string;
background3?: string;
foreground?: string;
info?: string;
infoContent?: string;
success?: string;
successContent?: string;
warning?: string;
warningContent?: string;
error?: string;
errorContent?: string;
};

export type FullTheme = Required<InputTheme> & {
primary2: string;
secondary2: string;
accent2: string;
neutral2: string;
info2: string;
success2: string;
warning2: string;
error2: string;
// tamagui
// background: string;
// backgroundHover: string;
// backgroundPress: string;
// backgroundFocus: string;
// borderColor: string;
// borderColorPress: string;
// borderColorFocus: string;
// borderColorHover: string;
// color: string;
// colorHover: string;
// colorPress: string;
// colorFocus: string;
// shadowColor: string;
// shadowColorHover: string;
// shadowColorPress: string;
// shadowColorFocus: string;
// placeholderColor: string;
};
103 changes: 103 additions & 0 deletions src/themeUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { FullTheme, InputTheme } from "./themeTypes";
import {
colorToHex,
colorToShade,
hasEnoughContrast,
stringColorToOklch,
} from "./colorUtils";

export const themeColorsToHexTheme = (theme: InputTheme): any => {
return Object.keys(theme).reduce((acc, key) => {
return {
...acc,
// @ts-ignore - TODO typescript magic to make this work
[key]: colorToHex(stringColorToOklch(theme[key])),
};
}, {});
};

const colorToContent = (
color: string,
foreground: string,
background: string
): string => {
const goodContrast = hasEnoughContrast(
stringColorToOklch(color),
stringColorToOklch(foreground)
);
if (goodContrast) {
return foreground;
}
return background;
};

// TODO check for darkness to set shade correctly
// TODO support dark themes
export const partialThemeToFullTheme = (
partialTheme: InputTheme
): FullTheme => {
const background = partialTheme.background ?? "#FFF";
const foreground = partialTheme.foreground ?? "#000";
const info = partialTheme.info ?? "oklch(0.7206 0.191 231.6)";
const success = partialTheme.success ?? "oklch(0.648 0.15 160)";
const warning = partialTheme.warning ?? "oklch(0.8471 0.199 83.87)";
const error = partialTheme.error ?? "oklch(0.7176 0.221 22.18)";
return {
...partialTheme,
background,
foreground,
neutralContent:
partialTheme.neutralContent ??
colorToContent(partialTheme.neutral, foreground, background),
primaryContent:
partialTheme.primaryContent ??
colorToContent(partialTheme.primary, foreground, background),
secondaryContent:
partialTheme.secondaryContent ??
colorToContent(partialTheme.secondary, foreground, background),
accentContent:
partialTheme.accentContent ??
colorToContent(partialTheme.accent, foreground, background),
background2:
partialTheme.background2 ??
colorToShade(stringColorToOklch(background), -0.1).toString(),
background3:
partialTheme.background3 ??
colorToShade(stringColorToOklch(background), -0.2).toString(),
primary2: colorToShade(
stringColorToOklch(partialTheme.primary),
-0.1
).toString(),
secondary2: colorToShade(
stringColorToOklch(partialTheme.secondary),
-0.1
).toString(),
accent2: colorToShade(
stringColorToOklch(partialTheme.accent),
-0.1
).toString(),
neutral2: colorToShade(
stringColorToOklch(partialTheme.neutral),
-0.1
).toString(),
info,
info2: colorToShade(stringColorToOklch(info), -0.1).toString(),
infoContent:
partialTheme.infoContent ?? colorToContent(info, foreground, background),
success,
success2: colorToShade(stringColorToOklch(success), -0.1).toString(),
successContent:
partialTheme.successContent ??
colorToContent(success, foreground, background),
warning,
warning2: colorToShade(stringColorToOklch(warning), -0.1).toString(),
warningContent:
partialTheme.warningContent ??
colorToContent(warning, foreground, background),
error,
error2: colorToShade(stringColorToOklch(error), -0.1).toString(),
errorContent:
partialTheme.errorContent ??
colorToContent(error, foreground, background),
};
};
Loading

0 comments on commit 43e352b

Please sign in to comment.