How to Create a Theme for Cyca
This document will guide you through creating a theme for Cyca.
Install pre-requisites
To create a theme for Cyca, you will need to install a few things:
- git
- node.js (Cyca always use the LTS version)
- Cyca’s code base
git clone https://github.com/RichardDern/Cyca
Install Cyca’s dependencies:
cd Cyca
npm i
A theme in Cyca is basically a tailwind configuration file. If you already know how to configure tailwind, it’s going to be really easy. Otherwise, you should look at tailwind’s documentation.
If you look at the resources/themes/baseTheme.js file, you will see some elements have already been taken care of. All you need to do in your theme is to extend this object.
So, let’s see how to create your theme.
Create the theme
Create a new directory in the resources/themes/ folder.
mkdir resources/themes/my_theme
The scructure of a theme is quite simple. In your theme folder, you should have the following files and sub-folders:
- dist/ : This folder will be created automatically during the compilation process
- resources/ : You will put in this folder (and maybe sub-folders) the resources your theme needs, like icons and fonts
- theme.css : Which is the entry-point to the PostCSS parsing and compiling process
- theme.js : Which contains tailwind’s configuration
- theme.json : Which contains your theme’s meta-data, such as author, link, etc.
Let’s begin with the simplest file you could have: theme.css. All it needs is to import Cyca’s base stylesheet, so just add the following to this file:
@import "../../css/app.css";
Of course, you can add all the CSS you need after this line.
Next, create your theme.json file:
{
"author": "Richard Dern",
"name": "Cyca Dark",
"description": "Cyca's default theme - Dark colors",
"url": "https://github.com/RichardDern/cyca_theme_dark",
"screenshot": "images/screenshot.png",
"icons": "images/icons.svg",
"inherits": null
}
theme.json reference
Key | Data |
---|---|
author | Author’s name of the theme |
name | Theme’s name |
description | Theme’s short description |
url | URL to your theme’s website or repository |
screenshot | Path relative to your theme’s resources/ directory to a screenshot of your theme |
icons | Path relative to your theme’s resources/ directory to the icons used by your theme |
inherits | Name of inherited theme |
The screenshot file must be a valid image, either jpg or png, with a maximum size of 1280x720 pixels.
Icons reference
The icons file must be an single file of SVG sprites. The following symbol ids are used in Cyca:
Symbol id | Represents |
---|---|
account | Account’s link |
add | Add button’s icon |
check | Tick |
checkmark | Tick ribbon |
collapsed | Folder’s caret in collasped mode |
expanded | Folder’s caret in expanded mode |
folder | Regular folder icon |
house | The root directory |
logout | The logout icon |
open | Open button’s icon |
unread_items | Unread items folder |
update | Update button’s icon |
share | Share button’s icon |
theme.js file
This is where you define your fonts, your color scheme, everything related to Cyca’s UI.
Let’s take Cyca’s default dark theme as an example:
// We will use lodash's merge method to recursively merge base theme object and ours
const _ = require('lodash');
// We will create a plugin to add a locally stored font
const plugin = require("tailwindcss/plugin");
// We begin the theme's definition by merging it with Cyca's provided base configuration
const theme = _.merge(require("../baseTheme.js"), {
theme: {
// Re-defining the "sans-serif" font family to add Quicksand, "our" custom font
fontFamily: {
sans:
'Quicksand, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'
},
// Here, we import a colors file which contains our color scheme
colors: require("./colors")
},
plugins: [
// And we create a plugin to reference our font.
// Note: the same could have been achieved directly in the CSS
// /themes/cyca-dark/fonts is relative to Cyca's public/ directory
plugin(function({ addBase, config }) {
const fontUrl =
"/themes/cyca-dark/fonts/Quicksand-Medium.ttf";
addBase({
"@font-face": {
fontFamily: "Quicksand",
src: "url(" + fontUrl + ")"
}
});
})
]
});
// Finally, we export the module so tailwind can use it
module.exports = theme;
The Quicksand font is stored in theme’s directory structure, more precisely in (relative to Cyca’s root directory):
- resources
- themes
- cyca-dark
- resources
- fonts
- Quicksand-Medium.ttf
- fonts
- resources
- cyca-dark
- themes
Everything in theme’s resources/ directory will be copied in the public/themes/theme_name/ directory during compilation.
Now, you may have noticed we require a colors.js file. You don’t know it yet, unless you’ve already looked at cyca-dark’s theme source, but colors.js itself calls the colorScheme.js file.
The colorScheme.js file stores the “database” of colors used in the theme. While not strictly required to have it appart, it’s a good practice to keep it separated from the rest of your theme and reference it when you need it. In this case, it’s just a module:
module.exports = {
white: "#ffffff",
red: {
"800": "#9B2C2C",
"900": "#742A2A"
},
yellow: {
"500": "#ED8936"
},
green: {
"400": "#68D391",
"600": "#38A169",
"800": "#276749",
"900": "#22543D"
},
blue: {
"400": "#63B3ED",
"500": "#4299E1",
"700": "#2B6CB0",
"800": "#2C5282"
},
purple: {
"500": "#9F7AEA"
},
"cool-gray": {
"500": "#A0AEC0"
},
gray: {
"100": "#5c5c5c",
"200": "#525252",
"300": "#474747",
"400": "#3d3d3d",
"500": "#333333",
"600": "#292929",
"700": "#1f1f1f",
"800": "#141414",
"900": "#0a0a0a"
}
}
Tailwind will only generate color classes for color described here, so the final CSS will be lighter. Otherwise, it will use the whole default color palette and generate a quite large CSS.
Now, back to the colors.js file, which will map colors names used in Cyca’s base CSS files with colors defined in your theme.
// Include colors we just defined
const baseColors = require('./colorScheme');
/**
* Colors used in the CSS by calling ```theme("colors.")```
* For a hovered danger button background color, we will call
* ```theme("colors.button.danger.bg-hover")``` in our CSS
*/
module.exports = {
baseColors,
/**
* Base
*/
a: {
normal: baseColors.blue[400],
hover: baseColors.blue[500]
},
article: {
text: baseColors.white,
bg: baseColors.gray[300],
border: baseColors.gray[700],
body: baseColors.white
},
body: {
text: baseColors.white,
bg: baseColors.gray[900]
},
dl: {
text: baseColors.white
},
dt: {
bg: baseColors.gray[700]
},
dd: {
bg: baseColors.gray[400]
},
button: {
text: baseColors.white,
danger: {
bg: baseColors.red[800],
"bg-hover": baseColors.red[900]
},
success: {
bg: baseColors.green[600],
"bg-hover": baseColors.green[800]
},
info: {
bg: baseColors.blue[700],
"bg-hover": baseColors.blue[800]
}
},
label: {
text: baseColors.gray[100]
},
formGroup: {
text: baseColors.white
},
input: {
text: baseColors.white,
bg: baseColors.gray[300]
},
scrollbar: baseColors.gray[300],
/**
* Components
*/
badge: {
text: baseColors.white,
bg: baseColors.gray[300],
"bg-article": baseColors.gray[500]
},
caret: baseColors.gray[100],
"folders-tree": baseColors.gray[800],
"documents-list": baseColors.gray[700],
"feeditems-list": baseColors.gray[600],
"details-view": baseColors.gray[500],
"list-item": {
text: baseColors.white,
active: {
text: baseColors.white,
bg: baseColors.gray[400]
},
"dragged-over": {
bg: baseColors.green[900]
},
"cannot-drop": {
bg: baseColors.red[900]
}
},
"feed-item": {
text: baseColors.white,
active: {
text: baseColors.white,
bg: baseColors.gray[400]
},
read: baseColors.gray[100],
meta: baseColors["cool-gray"][500]
},
account: {
menu: {
bg: baseColors.gray[800],
item: {
text: baseColors.gray[300],
hovered: baseColors.blue[500],
selected: {
text: baseColors.white
}
}
},
content: {
bg: baseColors.gray[700]
}
},
folders: {
common: baseColors.yellow[500],
unread: {
"not-empty": baseColors.purple[500],
empty: baseColors.gray[100]
},
root: baseColors.blue[500],
account: baseColors.green[600],
logout: baseColors.red[800]
},
"themes-browser": {
card: {
"border-color": baseColors.gray[300],
selected: {
"border-color": baseColors.green[600],
},
hovered: {
"border-color": baseColors.blue[500],
}
}
},
alerts: {
success: {
bg: baseColors.green[600],
text: baseColors.white
},
error: {
bg: baseColors.red[900],
text: baseColors.white
},
warning: {
bg: baseColors.yellow[500],
text: baseColors.white
}
}
};
This is the most complete up-to-date example I can give you. It shows every customizable component, but things are getting a lot simpler when you inherit your theme from another one.
Themes inheritance
This time, I will use Cyca’s light theme as an example, as it inherits from default dark theme we just saw.
When your theme inherits from another one, and unless you make it otherwise, it will use parent theme’s configuration, including fonts, custom icons, colors, plugins, etc.
In the case of Cyca’s light theme, I inherited it from cyca-dark. So, its theme.json file looks like that:
{
"author": "Richard Dern",
"name": "Cyca Light",
"description": "Cyca's default theme - Light colors",
"url": "https://github.com/RichardDern/cyca_theme_light",
"screenshot": "images/screenshot.png",
"icons": null,
"inherits": "cyca-dark"
}
Note that icons is set to null, because we won’t provide any icons in that theme: icons will be inherited from the cyca-dark theme, which we explicitely mention with the inherits key.
Our theme.js file got considerably lighter:
const _ = require('lodash');
const theme = _.merge(require("../cyca_theme_dark/theme"), {
theme: {
colors: require("./colors")
}
});
module.exports = theme;
The colorScheme.js file still references all the colors used in the theme, which, obviously, has changed from the cyca-dark theme:
module.exports = {
white: "#ffffff",
black: "#000000",
red: {
"100": "#FFF5F5",
"800": "#9B2C2C",
"900": "#742A2A"
},
yellow: {
"100": "#FFFAF0",
"500": "#ED8936"
},
green: {
"100": "#F0FFF4",
"400": "#68D391",
"600": "#38A169",
"800": "#276749",
"900": "#22543D"
},
blue: {
"400": "#63B3ED",
"500": "#4299E1",
"700": "#2B6CB0",
"800": "#2C5282"
},
purple: {
"500": "#9F7AEA"
},
"cool-gray": {
"700": "#4A5568"
},
gray: {
"100": "#F3F3F3",
"200": "#E8E8E8",
"300": "#DCDCDC",
"400": "#D0D0D0",
"500": "#C5C5C5",
"600": "#B9B9B9",
"700": "#ADADAD",
"800": "#A2A2A2",
"900": "#969696"
}
}
But the colors.js file also got lighter:
const baseColors = require('./colorScheme');
module.exports = {
/**
* Base
*/
a: {
normal: baseColors.blue[800],
hover: baseColors.blue[700]
},
article: {
text: baseColors.black,
bg: baseColors.gray[500],
border: baseColors.gray[500],
body: baseColors.black
},
body: {
text: baseColors.black,
bg: baseColors.gray[100]
},
label: {
text: baseColors.gray[900]
},
input: {
text: baseColors.black,
bg: baseColors.white
},
scrollbar: baseColors.gray[900],
/**
* Components
*/
badge: {
text: baseColors.black,
bg: baseColors.gray[700],
"bg-article": baseColors.gray[700]
},
caret: baseColors.gray[900],
"folders-tree": baseColors.gray[500],
"documents-list": baseColors.gray[400],
"feeditems-list": baseColors.gray[300],
"details-view": baseColors.gray[200],
"list-item": {
text: baseColors.black,
active: {
text: baseColors.black,
bg: baseColors.gray[700]
},
},
"feed-item": {
text: baseColors.black,
active: {
text: baseColors.black,
bg: baseColors.gray[500]
},
read: baseColors.gray[900],
meta: baseColors["cool-gray"][700]
},
account: {
menu: {
bg: baseColors.gray[300],
item: {
text: baseColors.gray[900],
selected: {
text: baseColors.black
}
}
},
content: {
bg: baseColors.gray[200]
}
},
alerts: {
success: {
bg: baseColors.green[100],
text: baseColors.green[900]
},
error: {
bg: baseColors.red[100],
text: baseColors.red[900]
},
warning: {
bg: baseColors.yellow[100],
text: baseColors.yellow[500]
}
}
};
Note that compared to cyca-dark theme’s colors.js file, this is smaller, because we inherited some components colors, so we don’t need to re-declare them unless we changed it in the new theme.
As your theme inherits from another one, you need to install parent’s theme as well, because compilation process requires files from it.
You will need to know parent theme’s repository URL. In the light theme example, we need the cyca-dark theme’s repository URL, which is https://github.com/RichardDern/cyca_theme_dark.
Please note that if parent theme itself inherits from another theme, you will need to install that theme too.
To install a theme and once you know it’s repository URL, you can use the following command, while in Cyca’s root directory:
cd resources/themes/
git submodules add https://github.com/RichardDern/cyca_theme_dark
As for now, this procedure can be quite difficult, but I am working on making it a lot simpler, especially regarding dependencies with other themes.
Compiling your theme
You can use Laravel’s default assets compilation pipeline to make your development easier.
For instance, you can use the following command:
npm run watch
Which will compile resources as soon as you saved them.
Once you are satisfied with your work, you will want to compile your theme for production, which is done easily by using the following command:
npm run prod
This command will create a dist/ directory in your theme’s structure. It is important you provide this directory in your theme’s repository, because it is the one Cyca will use when someone will install your theme.
You can look at Cyca’s official themes repositories to see how yours should look like:
- https://github.com/RichardDern/cyca_theme_dark
- https://github.com/RichardDern/cyca_theme_light
Publishing your theme
To make your theme available to Cyca’s users, you need at least a git repository (GitHub or self-hosted, it doesn’t matter as long as your repository has the aformentioned structure). Then you need to make a pull request to Cyca’s themes database repository.
You need to add your theme in the following form:
{
"community": {
"your-theme-name": ""
}
}
Your theme will be examinated, then, in the best case, included to Cyca’s themes database.