diff --git a/apps/svelte-sandbox/src/main.js b/apps/svelte-sandbox/src/main.js index 43dda413..0bb4056d 100644 --- a/apps/svelte-sandbox/src/main.js +++ b/apps/svelte-sandbox/src/main.js @@ -1,8 +1,18 @@ -import { theme, resetCss } from '@morfeo/web'; +import { theme, resetCss, loadFont } from '@morfeo/web'; import { enableMorfeoDevTool } from '@morfeo/dev-tools'; import App from './App.svelte'; import { lightTheme } from './theme'; +loadFont({ + importFontFace: true, + urls: [ + { + url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap', + }, + ], + name: 'regular', + family: 'Roboto', +}); enableMorfeoDevTool(); resetCss(); theme.set(lightTheme); diff --git a/apps/web-sandbox/src/App.tsx b/apps/web-sandbox/src/App.tsx index a48b9475..ea95b32b 100644 --- a/apps/web-sandbox/src/App.tsx +++ b/apps/web-sandbox/src/App.tsx @@ -33,12 +33,14 @@ function App() { const { containerStyle, blockStyle, codeStyle } = useStyles({ containerStyle: { bg: 'secondary', - width: '100vw' as any, + width: '100%' as any, display: 'flex', - height: '100vh' as any, + minHeight: '100vh' as any, alignItems: 'center', justifyContent: 'space-evenly', transition: 'light', + pt: 's', + pb: 's' }, blockStyle: { flex: 1, diff --git a/apps/web-sandbox/src/index.tsx b/apps/web-sandbox/src/index.tsx index 843afe90..79ae6b73 100644 --- a/apps/web-sandbox/src/index.tsx +++ b/apps/web-sandbox/src/index.tsx @@ -1,13 +1,20 @@ import React from 'react'; -import { theme, resetCss } from '@morfeo/react'; +import { theme, resetCss, loadFont } from '@morfeo/react'; import ReactDOM from 'react-dom'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { lightTheme } from './theme'; import { enableMorfeoDevTool } from '@morfeo/dev-tools'; +loadFont({ + importFontFace: true, + urls: [{ + url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap' + }], + name: 'regular', + family: 'Roboto', +}) enableMorfeoDevTool(); - resetCss(); theme.set(lightTheme as any); diff --git a/apps/web-sandbox/src/theme.ts b/apps/web-sandbox/src/theme.ts index 6bd31967..11007b1f 100644 --- a/apps/web-sandbox/src/theme.ts +++ b/apps/web-sandbox/src/theme.ts @@ -33,6 +33,9 @@ export const lightTheme = { warning: '#f39c12', transparent: 'transparent', }, + fontFamilies: { + regular: 'Roboto', + }, gradients, radii: { m: '10px', @@ -42,6 +45,15 @@ export const lightTheme = { s: '40px', m: '100px', }, + lineHeights: { + s: 1.7, + }, + letterSpacings: { + s: 1.6, + }, + fontSizes: { + s: '14px', + }, sizes: { s: '10px', m: '100px', @@ -137,7 +149,12 @@ export const lightTheme = { }, Typography: { tag: 'p', - style: {}, + style: { + fontFamily: 'regular', + fontSize: 's', + lineHeight: 's', + letterSpacing: 's', + }, variants: { h1: { tag: 'h1', diff --git a/packages/fonts/LICENSE b/packages/fonts/LICENSE new file mode 100644 index 00000000..13491c21 --- /dev/null +++ b/packages/fonts/LICENSE @@ -0,0 +1,25 @@ + +The MIT License (MIT) + +Copyright (c) 2021 Mauro Erta. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/fonts/README.md b/packages/fonts/README.md new file mode 100644 index 00000000..0664ecf7 --- /dev/null +++ b/packages/fonts/README.md @@ -0,0 +1,123 @@ +
+

@morfeo/fonts

+
+ +**@morfeo/fonts** provide 2 helpers to easily load a font on your document style and your mofeo theme both. + +**@morfeo/fonts** is part of the [@morfeo](https://github.com/VLK-STUDIO/morfeo) eco-system, a set of **framework-agnostic** tools that help you to create beautiful design systems for your web and mobile apps. + +--- + +
+ Documentation | + API | + Contributing | + Slack +
+ +--- + +## Table of contents + +#### [Installation](#installation-1) + +#### [APIS](#apis-1) + +- [mountFont](#mountFont) + - [@import fontFace](#@import) + - [define fontFace](#@fontFace) + - [references](#@references) + +--- + +## Installation + +with [npm](https://www.npmjs.com/package/@morfeo/fonts): + +```bash +npm install @morfeo/fonts +``` + +or [yarn](https://yarn.pm/@morfeo/fonts): + +```bash + yarn add @morfeo/fonts +``` + +**note:** @morfeo/fonts is also included on **@morfeo/web** + +--- + +## Apis + +### mountFont + +The `mountFont` Api mount a font on the document head + +#### @import + +Setting the prop `importFontFace` to `true` the style will be resolve to `@import ...`. + +It is usefull if you want to import a css file with the fontFace definitions. + +It is also the case of Google Font. + +```typescript +import { mountFont } from '@morfeo/fonts'; + +mountFont({ + urls: [ + { + url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap' + }, + ], + name: 'bold', + family: 'Roboto', + importFontFace: true, +}) +``` + +#### @fontFace + +By not defining or setting to `true` the `importFontFace` prop the style will resolve on `@fontFace ...`. +It allows to create `@fontFace` definition from scratch + +```typescript +import { mountFont } from '@morfeo/fonts'; + +mountFont({ + urls: [ + { + url: 'src/fonts/Roboto-bold.woff', + format: 'woff' + }, + { + url: 'src/fonts/Roboto-bold.woff2', + format: 'woff2' + }, + ], + name: 'bold', + family: 'Roboto', + weight: 'bold' +}) +``` + +#### References + +```typescript +export type FontType = 'woff' | 'woff2' | 'trueType' | 'embedded-opentype'; + +export type FontUrl = { + url: string; + format?: FontType; +}; + +export type MountFontParams = { + name: string; + urls: FontUrl[]; + family: string; + importFontFace?: boolean; + weight?: string; +}; +``` + diff --git a/packages/fonts/package.json b/packages/fonts/package.json new file mode 100644 index 00000000..0d4bb90a --- /dev/null +++ b/packages/fonts/package.json @@ -0,0 +1,38 @@ +{ + "name": "@morfeo/fonts", + "author": { + "name": "Andrea Simone Porceddu", + "email": "andrea@vlkstudio.com" + }, + "private": false, + "version": "0.1.5", + "license": "MIT", + "main": "build/index.js", + "module": "build/index.js", + "types": "build/index", + "typings": "build/index", + "keywords": [ + "design", + "system", + "font", + "load" + ], + "scripts": { + "build": "rimraf build && tsc", + "watch": "tsc -w" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "build" + ], + "repository": { + "type": "git", + "url": "https://github.com/VLK-STUDIO/morfeo" + }, + "homepage": "https://github.com/VLK-STUDIO/morfeo/tree/main/packages/fonts", + "bugs": { + "url": "https://github.com/VLK-STUDIO/morfeo/issues" + } +} diff --git a/packages/fonts/src/index.ts b/packages/fonts/src/index.ts new file mode 100644 index 00000000..5b976cb3 --- /dev/null +++ b/packages/fonts/src/index.ts @@ -0,0 +1 @@ +export * from './mountFont'; diff --git a/packages/fonts/src/mountFont.ts b/packages/fonts/src/mountFont.ts new file mode 100644 index 00000000..eee1df3c --- /dev/null +++ b/packages/fonts/src/mountFont.ts @@ -0,0 +1,103 @@ +export type FontType = 'woff' | 'woff2' | 'trueType' | 'embedded-opentype'; + +export type FontUrl = { + url: string; + format?: FontType; +}; + +export type MountFontParams = { + name: string; + urls: FontUrl[]; + family: string; + /** + * set to true if you are providing a css url with predefined fontFace(example: Google Font) + * Please note: it support **one font family** per css import + */ + importFontFace?: boolean; + weight?: string; +}; + +export function unmountFont(name: string) { + const currentFontStyle = document.getElementById(`font-${name}`); + + if (currentFontStyle) { + currentFontStyle.remove(); + } +} + +/** + * Mount a fontFace on document head style + * + * --- + * + * ### mount a google font + * + * ```ts + * mountFont({ + * urls: [{ + * url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap' + * }], + * importFontFace: true, + * fontFamily: 'Roboto', + * name: 'bold', + * }) + * ``` + * + * --- + * + * ### mount a local font + * + * ```ts + * mountFont({ + * urls: [ + * { + * url: './fonts/Roboto-bold.woff', + * format: 'woff' + * }, + * { + * url: './fonts/Roboto-bold.woff2', + * format: 'woff2' + * }, + * ], + * importFontFace: true, + * fontFamily: 'Roboto', + * name: 'bold', + * }) + * ``` + */ + +export function mountFont(font: MountFontParams) { + let newFontFaceContent = ''; + + if (font.importFontFace) { + newFontFaceContent += ` + @import ${font.urls.reduce((_, value) => `url(${value.url})`, '')} + `; + } + + if (!font.importFontFace) { + newFontFaceContent += ` + @fontFace { + font-family: ${font.family}; + ${font.weight ? `font-weight: ${font.weight};` : ''} + url: ${font.urls.reduce( + (acc, value) => + `${acc ? acc + '\n' : acc} url('${value.url}') ${ + value.format ? `format('${value.format}')` : '' + }`, + '', + )} + } + `; + } + + const newFontStyle = ` + + `; + + unmountFont(font.name); + + document.head.innerHTML += newFontStyle; +} diff --git a/packages/fonts/tests/mountFont.test.ts b/packages/fonts/tests/mountFont.test.ts new file mode 100644 index 00000000..4c47b1d1 --- /dev/null +++ b/packages/fonts/tests/mountFont.test.ts @@ -0,0 +1,66 @@ +import { mountFont, MountFontParams } from '../src'; + +const newImportedFontFace: MountFontParams = { + importFontFace: true, + urls: [ + { + url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap', + }, + ], + name: 'bold', + family: 'Roboto', +}; + +const newFontFace: MountFontParams = { + urls: [ + { + url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap', + }, + ], + name: 'bold', + family: 'Roboto', +}; + +const newFontWithFormat: MountFontParams = { + urls: [ + { + url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap', + format: 'woff', + }, + { + url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap', + format: 'woff2', + }, + ], + name: 'bold', + family: 'Roboto', +}; + +describe('mountFont', () => { + describe('new fontFace', () => { + test('should add a style with the fontFace definition', () => { + mountFont(newFontFace); + const boldStyle = document.getElementById('font-bold'); + expect(boldStyle?.textContent).toContain('@fontFace'); + }); + + test('should set a style that contain font-weight', () => { + mountFont({ ...newFontFace, weight: 'bold' }); + const boldStyle = document.getElementById('font-bold'); + expect(boldStyle?.textContent).toContain('font-weight: bold'); + }); + + test('should set a style that contain format', () => { + mountFont(newFontWithFormat); + const boldStyle = document.getElementById('font-bold'); + expect(boldStyle?.textContent).toContain("format('woff')"); + }); + }); + describe('imported fontFace', () => { + test('should add a style with the fontface import', () => { + mountFont(newImportedFontFace); + const boldStyle = document.getElementById('font-bold'); + expect(boldStyle?.textContent).toContain('@import'); + }); + }); +}); diff --git a/packages/fonts/tsconfig.json b/packages/fonts/tsconfig.json new file mode 100644 index 00000000..e3f631ae --- /dev/null +++ b/packages/fonts/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./build", + "declaration": true + }, + "include": ["./src"], + "exclude": ["./node_modules", "./src/**/*.test.ts", "./src/**/*.spec.ts"] +} diff --git a/packages/web/package.json b/packages/web/package.json index b492ce07..e2636ad5 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -21,7 +21,8 @@ }, "dependencies": { "@morfeo/core": "^0.1.5", - "@morfeo/jss": "^0.1.5" + "@morfeo/jss": "^0.1.5", + "@morfeo/fonts": "^0.1.5" }, "peerDependencies": { "csstype": "^3.0.8" diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts index cd70245a..fc169591 100644 --- a/packages/web/src/index.ts +++ b/packages/web/src/index.ts @@ -18,3 +18,5 @@ export * from './types'; export * from '@morfeo/core'; /** re-export of @morfeo/jss */ export * from '@morfeo/jss'; +/** re-export of @morfeo/fonts */ +export * from '@morfeo/fonts'; diff --git a/packages/web/src/utils/index.ts b/packages/web/src/utils/index.ts index b8494d6b..beca1935 100644 --- a/packages/web/src/utils/index.ts +++ b/packages/web/src/utils/index.ts @@ -1 +1,2 @@ export * from './resetCss'; +export * from './loadFont'; diff --git a/packages/web/src/utils/loadFont.ts b/packages/web/src/utils/loadFont.ts new file mode 100644 index 00000000..e2018b57 --- /dev/null +++ b/packages/web/src/utils/loadFont.ts @@ -0,0 +1,49 @@ +import { theme, Font } from '@morfeo/core'; +import { MountFontParams, mountFont } from '@morfeo/fonts'; + +export type LoadFontParams = Omit & { name: Font }; +/** + * Load a fontFace on document head style and add it to the morfeo theme + * + * --- + * + * ### load a google font + * + * ```ts + * loadFont({ + * urls: [{ + * url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap' + * }], + * importFontFace: true, + * fontFamily: 'Roboto', + * name: 'bold', + * }) + * ``` + * + * --- + * + * ### load a local font + * + * ```ts + * loadFont({ + * urls: [ + * { + * url: './fonts/Roboto-bold.woff', + * format: 'woff' + * }, + * { + * url: './fonts/Roboto-bold.woff2', + * format: 'woff2' + * }, + * ], + * importFontFace: true, + * fontFamily: 'Roboto', + * name: 'bold', + * }) + * ``` + */ + +export function loadFont(font: LoadFontParams) { + mountFont(font); + theme.setValue('fonts', font.name, font.family); +} diff --git a/packages/web/src/utils/resetCss.ts b/packages/web/src/utils/resetCss.ts index b748cb2e..3b5dc39a 100644 --- a/packages/web/src/utils/resetCss.ts +++ b/packages/web/src/utils/resetCss.ts @@ -18,6 +18,7 @@ time, mark, audio, video { font-size: 100%; font: inherit; vertical-align: baseline; + font-smooth: auto; } :focus { diff --git a/packages/web/tests/loadFont.test.ts b/packages/web/tests/loadFont.test.ts new file mode 100644 index 00000000..19248057 --- /dev/null +++ b/packages/web/tests/loadFont.test.ts @@ -0,0 +1,43 @@ +import { loadFont, LoadFontParams } from '../src/utils'; +import { theme } from '@morfeo/core'; + +const newImportedFontFace: LoadFontParams = { + importFontFace: true, + urls: [ + { + url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap', + }, + ], + name: 'regular', + family: 'Roboto', +}; + +const newFontFace: LoadFontParams = { + urls: [ + { + url: 'https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap', + }, + ], + name: 'bold', + family: 'Roboto', +}; + +beforeEach(() => { + theme.reset(); +}); +describe('loadFont', () => { + describe('new fontFace', () => { + test('should add a font on theme', () => { + loadFont(newFontFace); + const themeFont = theme.getValue('fonts', newFontFace.name); + expect(themeFont).toBe(newFontFace.family); + }); + }); + describe('imported fontFace', () => { + test('should add a font on theme', () => { + loadFont(newImportedFontFace); + const themeFont = theme.getValue('fonts', newImportedFontFace.name); + expect(themeFont).toBe(newImportedFontFace.family); + }); + }); +});