Skip to content

Commit d1a72cf

Browse files
aileenallouisminimaluminiumpeterzimonsanne-san
authored
Custom fonts (#21337)
- Adding custom fonts for themes behind a feature flag - Introduces new `@tryghost/custom-fonts` module to manage custom fonts - UI updates for Branding and Theme settings --------- Co-authored-by: Fabien O'Carroll <[email protected]> Co-authored-by: Sodbileg Gansukh <[email protected]> Co-authored-by: Peter Zimon <[email protected]> Co-authored-by: Sanne de Vries <[email protected]> Co-authored-by: Daniël van der Winden <[email protected]>
0 parents  commit d1a72cf

File tree

8 files changed

+449
-0
lines changed

8 files changed

+449
-0
lines changed

packages/custom-fonts/.eslintrc.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
parser: '@typescript-eslint/parser',
3+
plugins: ['ghost'],
4+
extends: [
5+
'plugin:ghost/node'
6+
]
7+
};

packages/custom-fonts/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Custom Fonts
2+
3+
Custom fonts mapping module
4+
5+
6+
## Usage
7+
8+
9+
## Develop
10+
11+
This is a monorepo package.
12+
13+
Follow the instructions for the top-level repo.
14+
1. `git clone` this repo & `cd` into it as usual
15+
2. Run `yarn` to install top-level dependencies.
16+
17+
18+
19+
## Test
20+
21+
- `yarn lint` run just eslint
22+
- `yarn test` run lint and tests
23+

packages/custom-fonts/package.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "@tryghost/custom-fonts",
3+
"version": "0.0.0",
4+
"repository": "https://github.com/TryGhost/Ghost/tree/main/packages/custom-fonts",
5+
"author": "Ghost Foundation",
6+
"private": true,
7+
"main": "./build/cjs/index.js",
8+
"module": "./build/esm/index.js",
9+
"types": "./build/types/index.d.ts",
10+
"exports": {
11+
".": {
12+
"import": "./build/esm/index.js",
13+
"require": "./build/cjs/index.js",
14+
"types": "./build/types/index.d.ts"
15+
}
16+
},
17+
"scripts": {
18+
"dev": "tsc --watch --preserveWatchOutput --sourceMap",
19+
"build": "yarn build:cjs && yarn build:esm && yarn build:ts",
20+
"build:types": "tsc -p tsconfig.json --emitDeclarationOnly --declaration --declarationDir ./build/types",
21+
"build:cjs": "tsc -p tsconfig.json --outDir ./build/cjs --module CommonJS",
22+
"build:esm": "tsc -p tsconfig.esm.json --outDir ./build/esm --module ES2020",
23+
"build:ts": "yarn build:cjs && yarn build:esm && yarn build:types",
24+
"prepare": "yarn build",
25+
"test:unit": "NODE_ENV=testing c8 --src src --all --check-coverage --100 --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'",
26+
"test": "yarn test:ts && yarn test:unit",
27+
"test:ts": "tsc --noEmit",
28+
"lint:code": "eslint src/ --ext .ts --cache",
29+
"lint": "yarn lint:code && yarn lint:test",
30+
"lint:test": "eslint -c test/.eslintrc.js test/ --ext .ts --cache"
31+
},
32+
"files": [
33+
"build"
34+
],
35+
"devDependencies": {
36+
"c8": "10.1.2",
37+
"mocha": "10.7.3",
38+
"sinon": "19.0.2",
39+
"ts-node": "10.9.2",
40+
"typescript": "5.6.2"
41+
},
42+
"dependencies": {}
43+
}

packages/custom-fonts/src/index.ts

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
export type BodyFont = 'Fira Mono' | 'Fira Sans' | 'IBM Plex Serif' | 'Inter' | 'JetBrains Mono' | 'Lora' | 'Manrope' | 'Merriweather' | 'Nunito' | 'Noto Sans' | 'Noto Serif' | 'Poppins' | 'Roboto' | 'Space Mono';
2+
export type HeadingFont = 'Cardo' | 'Chakra Petch' | 'Old Standard TT' | 'Prata' | 'Rufina' | 'Space Grotesk' | 'Tenor Sans' | BodyFont;
3+
export type CustomFonts = {heading: HeadingFont[], body: BodyFont[]};
4+
5+
export type FontSelection = {
6+
heading?: HeadingFont,
7+
body?: BodyFont
8+
};
9+
10+
export const CUSTOM_FONTS: CustomFonts = {
11+
heading: [
12+
'Cardo',
13+
'Chakra Petch',
14+
'Fira Mono',
15+
'Fira Sans',
16+
'IBM Plex Serif',
17+
'Inter',
18+
'JetBrains Mono',
19+
'Lora',
20+
'Manrope',
21+
'Merriweather',
22+
'Noto Sans',
23+
'Noto Serif',
24+
'Nunito',
25+
'Old Standard TT',
26+
'Poppins',
27+
'Prata',
28+
'Roboto',
29+
'Rufina',
30+
'Space Grotesk',
31+
'Space Mono',
32+
'Tenor Sans'
33+
],
34+
body: [
35+
'Fira Mono',
36+
'Fira Sans',
37+
'IBM Plex Serif',
38+
'Inter',
39+
'JetBrains Mono',
40+
'Lora',
41+
'Manrope',
42+
'Merriweather',
43+
'Noto Sans',
44+
'Noto Serif',
45+
'Nunito',
46+
'Poppins',
47+
'Roboto',
48+
'Space Mono'
49+
]
50+
};
51+
52+
const classFontNames = {
53+
Cardo: 'cardo',
54+
Manrope: 'manrope',
55+
Merriweather: 'merriweather',
56+
Nunito: 'nunito',
57+
'Old Standard TT': 'old-standard-tt',
58+
Prata: 'prata',
59+
Roboto: 'roboto',
60+
Rufina: 'rufina',
61+
'Tenor Sans': 'tenor-sans',
62+
'Space Grotesk': 'space-grotesk',
63+
'Chakra Petch': 'chakra-petch',
64+
'Noto Sans': 'noto-sans',
65+
Poppins: 'poppins',
66+
'Fira Sans': 'fira-sans',
67+
Inter: 'inter',
68+
'Noto Serif': 'noto-serif',
69+
Lora: 'lora',
70+
'IBM Plex Serif': 'ibm-plex-serif',
71+
'Space Mono': 'space-mono',
72+
'Fira Mono': 'fira-mono',
73+
'JetBrains Mono': 'jetbrains-mono'
74+
};
75+
76+
export function generateCustomFontCss(fonts: FontSelection) {
77+
let fontImports: string = '';
78+
let fontCSS: string = '';
79+
80+
const importStrings = {
81+
Cardo: {
82+
url: '@import url(https://fonts.bunny.net/css?family=cardo:400,700)'
83+
},
84+
Manrope: {
85+
url: '@import url(https://fonts.bunny.net/css?family=manrope:300,500,700)'
86+
},
87+
Merriweather: {
88+
url: '@import url(https://fonts.bunny.net/css?family=merriweather:300,700)'
89+
},
90+
Nunito: {
91+
url: '@import url(https://fonts.bunny.net/css?family=nunito:400,600,700)'
92+
},
93+
'Old Standard TT': {
94+
url: '@import url(https://fonts.bunny.net/css?family=old-standard-tt:400,700)'
95+
},
96+
Prata: {
97+
url: '@import url(https://fonts.bunny.net/css?family=prata:400)'
98+
},
99+
Roboto: {
100+
url: '@import url(https://fonts.bunny.net/css?family=roboto:400,500,700)'
101+
},
102+
Rufina: {
103+
url: '@import url(https://fonts.bunny.net/css?family=rufina:400,500,700)'
104+
},
105+
'Tenor Sans': {
106+
url: '@import url(https://fonts.bunny.net/css?family=tenor-sans:400)'
107+
},
108+
'Space Grotesk': {
109+
url: '@import url(https://fonts.bunny.net/css?family=space-grotesk:700)'
110+
},
111+
'Chakra Petch': {
112+
url: '@import url(https://fonts.bunny.net/css?family=chakra-petch:400)'
113+
},
114+
'Noto Sans': {
115+
url: '@import url(https://fonts.bunny.net/css?family=noto-sans:400,700)'
116+
},
117+
Poppins: {
118+
url: '@import url(https://fonts.bunny.net/css?family=poppins:400,500,600)'
119+
},
120+
'Fira Sans': {
121+
url: '@import url(https://fonts.bunny.net/css?family=fira-sans:400,500,600)'
122+
},
123+
Inter: {
124+
url: '@import url(https://fonts.bunny.net/css?family=inter:400,500,600)'
125+
},
126+
'Noto Serif': {
127+
url: '@import url(https://fonts.bunny.net/css?family=noto-serif:400,700)'
128+
},
129+
Lora: {
130+
url: '@import url(https://fonts.bunny.net/css?family=lora:400,700)'
131+
},
132+
'IBM Plex Serif': {
133+
url: '@import url(https://fonts.bunny.net/css?family=ibm-plex-serif:400,500,600)'
134+
},
135+
'Space Mono': {
136+
url: '@import url(https://fonts.bunny.net/css?family=space-mono:400,700)'
137+
},
138+
'Fira Mono': {
139+
url: '@import url(https://fonts.bunny.net/css?family=fira-mono:400,700)'
140+
},
141+
'JetBrains Mono': {
142+
url: '@import url(https://fonts.bunny.net/css?family=jetbrains-mono:400,700)'
143+
}
144+
};
145+
146+
if (fonts?.heading && fonts?.body && fonts?.heading === fonts?.body) {
147+
fontImports = `${importStrings[fonts?.heading]?.url};`;
148+
} else {
149+
fontImports = '';
150+
151+
if (fonts?.heading) {
152+
fontImports += `${importStrings[fonts?.heading]?.url};`;
153+
}
154+
155+
if (fonts?.body) {
156+
fontImports += `${importStrings[fonts?.body]?.url};`;
157+
}
158+
}
159+
160+
if (fonts?.body || fonts?.heading) {
161+
fontCSS = ':root {';
162+
163+
if (fonts?.heading) {
164+
fontCSS += `--gh-font-heading: ${fonts.heading};`;
165+
}
166+
167+
if (fonts?.body) {
168+
fontCSS += `--gh-font-body: ${fonts.body};`;
169+
}
170+
171+
fontCSS += '}';
172+
}
173+
174+
return `<style>${fontImports}${fontCSS}</style>`;
175+
}
176+
177+
export function generateCustomFontBodyClass(fonts: FontSelection) {
178+
let bodyClass = '';
179+
180+
if (fonts?.heading) {
181+
bodyClass += getCustomFontClassName({font: fonts.heading, heading: true});
182+
if (fonts?.body) {
183+
bodyClass += ' ';
184+
}
185+
}
186+
187+
if (fonts?.body) {
188+
bodyClass += getCustomFontClassName({font: fonts.body, heading: false});
189+
}
190+
191+
return bodyClass;
192+
}
193+
194+
export function getCSSFriendlyFontClassName(font: string) {
195+
return classFontNames[font as keyof typeof classFontNames] || '';
196+
}
197+
198+
export function getCustomFontClassName({font, heading}: {font: string, heading: boolean}) {
199+
const cssFriendlyFontClassName = getCSSFriendlyFontClassName(font);
200+
201+
if (!cssFriendlyFontClassName) {
202+
return '';
203+
}
204+
205+
return `gh-font-${heading ? 'heading' : 'body'}-${cssFriendlyFontClassName}`;
206+
}
207+
208+
export function getCustomFonts(): CustomFonts {
209+
return CUSTOM_FONTS;
210+
}
211+
212+
export function isValidCustomFont(font: string): font is BodyFont {
213+
return CUSTOM_FONTS.body.includes(font as BodyFont);
214+
}
215+
216+
export function isValidCustomHeadingFont(font: string): font is HeadingFont {
217+
return CUSTOM_FONTS.heading.includes(font as HeadingFont);
218+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
parser: '@typescript-eslint/parser',
3+
plugins: ['ghost'],
4+
extends: [
5+
'plugin:ghost/test'
6+
]
7+
};

0 commit comments

Comments
 (0)