From 9bcac4e9dbccd52afc53b5308bb7273659a58c19 Mon Sep 17 00:00:00 2001 From: mauroerta Date: Wed, 2 Jun 2021 00:23:11 +0200 Subject: [PATCH] feat: gradients parsers added to @morfeo/web package --- apps/web-sandbox/src/theme.ts | 34 +++++++- packages/spec/src/allProperties.ts | 6 ++ packages/spec/src/properties/gradients.ts | 1 + packages/spec/src/properties/index.ts | 1 + packages/spec/src/types/gradients.ts | 42 ++++++++++ packages/spec/src/types/index.ts | 1 + packages/spec/src/types/theme.ts | 2 + packages/web/README.md | 80 ++++++++++++++---- packages/web/src/index.ts | 6 +- packages/web/src/parsers/gradients.ts | 78 ++++++++++++++++++ packages/web/src/parsers/index.ts | 1 + packages/web/tests/gradient.test.ts | 99 +++++++++++++++++++++++ 12 files changed, 334 insertions(+), 17 deletions(-) create mode 100644 packages/spec/src/properties/gradients.ts create mode 100644 packages/spec/src/types/gradients.ts create mode 100644 packages/web/src/parsers/gradients.ts create mode 100644 packages/web/tests/gradient.test.ts diff --git a/apps/web-sandbox/src/theme.ts b/apps/web-sandbox/src/theme.ts index d2a8f9ac..e501d887 100644 --- a/apps/web-sandbox/src/theme.ts +++ b/apps/web-sandbox/src/theme.ts @@ -1,9 +1,39 @@ +export const gradients = { + primary: { + start: 0, + angle: 90, + end: 100, + colors: ['success', 'accent'], + kind: 'linear', + }, + secondary: { + start: 0, + end: 100, + angle: 0, + colors: ['accent', 'success'], + kind: 'linear', + }, + loading: { + start: 0, + end: 100, + angle: 90, + colors: ['transparent', 'grey', 'transparent'], + kind: 'linear', + }, +}; + export const lightTheme = { colors: { + grey: '#bdc3c7', primary: 'white', + danger: '#e74c3c', secondary: 'black', - danger: 'red', + success: '#2ecc71', + accent: '#3498db', + warning: '#f39c12', + transparent: 'transparent', }, + gradients, radii: { m: '10px', round: '50%', @@ -48,7 +78,7 @@ export const lightTheme = { borderStyle: 'solid', borderColor: 'primary', '&:hover': { - bg: 'secondary', + gradient: 'secondary', color: 'primary', }, }, diff --git a/packages/spec/src/allProperties.ts b/packages/spec/src/allProperties.ts index 575d4b5f..5af3d0e4 100644 --- a/packages/spec/src/allProperties.ts +++ b/packages/spec/src/allProperties.ts @@ -9,6 +9,7 @@ import { zIndicesMap, fontSizesMap, opacitiesMap, + gradientsMap, fontWeightsMap, lineHeightsMap, transitionsMap, @@ -23,6 +24,10 @@ export const fontProperties = createPropertiesMap(fontsMap, 'fonts'); export const spacesProperties = createPropertiesMap(spacesMap, 'space'); export const sizesProperties = createPropertiesMap(sizesMap, 'sizes'); export const colorProperties = createPropertiesMap(colorsMap, 'colors'); +export const gradientProperties = createPropertiesMap( + gradientsMap, + 'gradients', +); export const shadowsProperties = createPropertiesMap(shadowsMap, 'shadows'); export const fontSizeProperties = createPropertiesMap( fontSizesMap, @@ -69,6 +74,7 @@ export const allProperties = { ...bordersProperties, ...fontSizeProperties, ...zIndicesProperties, + ...gradientProperties, ...opacitiesProperties, ...fontWeightProperties, ...lineHeightProperties, diff --git a/packages/spec/src/properties/gradients.ts b/packages/spec/src/properties/gradients.ts new file mode 100644 index 00000000..fd5b2112 --- /dev/null +++ b/packages/spec/src/properties/gradients.ts @@ -0,0 +1 @@ +export const gradientsMap = ['gradient', 'bgGradient', 'textGradient'] as const; diff --git a/packages/spec/src/properties/index.ts b/packages/spec/src/properties/index.ts index 77e9df28..21c9fa81 100644 --- a/packages/spec/src/properties/index.ts +++ b/packages/spec/src/properties/index.ts @@ -6,6 +6,7 @@ export * from './colors'; export * from './borders'; export * from './shadows'; export * from './zIndices'; +export * from './gradients'; export * from './opacities'; export * from './components'; export * from './transitions'; diff --git a/packages/spec/src/types/gradients.ts b/packages/spec/src/types/gradients.ts new file mode 100644 index 00000000..7ac74603 --- /dev/null +++ b/packages/spec/src/types/gradients.ts @@ -0,0 +1,42 @@ +import { gradientsMap } from '../properties'; +import { Color } from './colors'; + +export type GradientCoordinate = { + x: number; + y: number; +}; + +export type GradientKind = 'linear' | 'radial'; + +export interface ReactNativeGradientConfig { + end: GradientCoordinate; + start: GradientCoordinate; + colors: Color[]; + locations?: number[]; + angle?: number; + useAngle?: boolean; + angleCenter?: GradientCoordinate; +} + +export interface GradientConfig { + end?: number; + kind?: GradientKind; + start?: number; + angle?: number; + colors: Color[]; +} + +export interface Gradients { + primary: GradientConfig; + secondary: GradientConfig; +} + +export type Gradient = keyof Gradients; + +type BaseGradientProps = { + [K in typeof gradientsMap[number]]: Gradient; +}; + +export interface GradientProps extends BaseGradientProps {} + +export type GradientProperty = keyof GradientProps; diff --git a/packages/spec/src/types/index.ts b/packages/spec/src/types/index.ts index 75886c1a..261ac1c4 100644 --- a/packages/spec/src/types/index.ts +++ b/packages/spec/src/types/index.ts @@ -9,6 +9,7 @@ export * from './borders'; export * from './shadows'; export * from './zIndices'; export * from './opacities'; +export * from './gradients'; export * from './components'; export * from './properties'; export * from './transitions'; diff --git a/packages/spec/src/types/theme.ts b/packages/spec/src/types/theme.ts index aae48fc2..f0c5a15b 100644 --- a/packages/spec/src/types/theme.ts +++ b/packages/spec/src/types/theme.ts @@ -14,6 +14,7 @@ import { Borders, BorderWidths, BorderStyles } from './borders'; import { Opacities } from './opacities'; import { Transitions } from './transitions'; import { ZIndices } from './zIndices'; +import { Gradients } from './gradients'; import { MediaQueries } from './mediaQueries'; import { Components } from './components'; @@ -27,6 +28,7 @@ export type BaseTheme = { borders: Borders; zIndices: ZIndices; fontSizes: FontSizes; + gradients: Gradients; opacities: Opacities; fontWeights: FontWeights; lineHeights: LineHeights; diff --git a/packages/web/README.md b/packages/web/README.md index bc505845..ffd9ad18 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -24,6 +24,7 @@ #### [Usage](#usage-1) - [pseudos](#pseudos) +- [gradients](#gradients) #### [Supported Pseudos](#supported-pseudos-1) @@ -45,24 +46,19 @@ yarn add @morfeo/web **@morfeo/web** re-export all the **@morfeo/core** library, check out its [documentation](https://github.com/VLK-STUDIO/morfeo/tree/main/packages/core) before continue. -In addition to the core library, the web package adds the parsers to handle **pseudo classes** and **pseudo elements**. - -:warning: Warning - -> You'll probably never use *directly* `@morfeo/web` or `@morfeo/core`, instead, you'll more likely to use [@morfeo/react](https://github.com/VLK-STUDIO/morfeo/tree/main/packages/react), [@morfeo/svelte](https://github.com/VLK-STUDIO/morfeo/tree/main/packages/svelte), [@morfeo/jss](https://github.com/VLK-STUDIO/morfeo/tree/main/packages/jss), or other packages that offer better integration of the morfeo eco-system in your framework of choice. -> In this particular case, it's important to know that you cannot define a style for pseudo-elements (or media queries) as inline-style, that's why you need some other tool like JSS or Styled Components to handle this behavior. Likely, we already thought about it, so feel free to check out our packages. +In addition to the core library, the web package adds the parsers to handle **pseudo classes**, **pseudo elements** and **gradients**. ### pseudos -You can pass to the `resolve` method any pseudo class with the format '&:{pseudo}', for example: +You can pass to the `resolve` method any pseudo class with the format '&:{pseudo}', for example: ```typescript -import { parsers } from "@morfeo/web"; +import { parsers } from '@morfeo/web'; const style = parsers.resolve({ - bg: "primary", - "&:hover": { - bg: "secondary", + bg: 'primary', + '&:hover': { + bg: 'secondary', }, }); ``` @@ -73,12 +69,33 @@ Will generate the style: { "backgroundColor": "black", "&:hover": { - "backgroundColor": "grey", - }, + "backgroundColor": "grey" + } } ``` -## Supported Pseudos +If you're using `@morfeo/web` to directly style a component without any other css-in-js library, you can use `getStyles` : + +```typescript +import { getStyles } from '@morfeo/web'; + +const element = document.querySelector('#myButton'); + +const { classes } = getStyles({ + button: { + bg: 'primary', + '&:hover': { + bg: 'secondary', + }, + }, +}); + +element.classList.add(classes.button); +``` + +In this case, @morfeo will generate plain css. To understand more about this topic we suggest you check our documentation about [@morfeo/jss](https://github.com/VLK-STUDIO/morfeo/tree/main/packages/jss), in fact, the function `getStyles` is re-exported from `@morfeo/jss`. + +#### Supported Pseudos For now morfeo support this pseudos: @@ -113,3 +130,38 @@ For now morfeo support this pseudos: ``` as specified [here](https://github.com/VLK-STUDIO/morfeo/tree/main/packages/core#add-a-custom-parser) yuo can always add more parser to extends morfeo, or simply add more pseudos in this list by editing this [file](https://github.com/VLK-STUDIO/morfeo/blob/main/packages/web/src/properties.ts) and open a pull request. + +### gradients + +You can specify inside the theme a set of gradients to use inside your application by using the `gradients` theme slice: + +```typescript +const myTheme = { + ...restOfTheTheme, + gradients: { + ...restOfTheTheme.gradients, + primary: { + start: 0, + angle: 90, + end: 100, + colors: ['primary', 'secondary'], + kind: 'linear', + }, + }, +}; +``` + +An example of usage is: + +```typescript +const buttonStyle = button.resolve({ gradient: 'primary' }); +const textStyle = button.resolve({ textGradient: 'primary' }); +``` + +with the results: + +[![gradient-Button.png](https://i.postimg.cc/k5B8tNMP/gradient-Button.png)](https://postimg.cc/XZ6XRCys) + +[![gradient-Text.png](https://i.postimg.cc/5NDMVbH5/gradient-Text.png)](https://postimg.cc/SJLPL0fj) + +check out [@morfeo/spec](https://github.com/VLK-STUDIO/morfeo/tree/main/packages/spec) for the complete specification of the type `GradientConfig` used inside the `gradients` theme slice. diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts index bd5fd711..1f70fd18 100644 --- a/packages/web/src/index.ts +++ b/packages/web/src/index.ts @@ -1,11 +1,15 @@ import { parsers, Property } from '@morfeo/core'; -import { pseudosParses } from './parsers'; +import { pseudosParses, gradientParsers } from './parsers'; import { pseudosProperties } from './properties'; pseudosProperties.forEach(property => { parsers.add(property as Property, pseudosParses[property] as any); }); +Object.keys(gradientParsers).forEach(property => { + parsers.add(property as Property, gradientParsers[property] as any); +}); + export * from './types'; /** re-export of @morfeo/core */ diff --git a/packages/web/src/parsers/gradients.ts b/packages/web/src/parsers/gradients.ts new file mode 100644 index 00000000..6f309025 --- /dev/null +++ b/packages/web/src/parsers/gradients.ts @@ -0,0 +1,78 @@ +import { + theme, + Gradient, + ParserParams, + SliceParsers, + GradientConfig, + gradientProperties, + GradientProperty, +} from '@morfeo/core'; + +type GradientsParsers = SliceParsers< + typeof gradientProperties, + keyof typeof gradientProperties +>; + +function getGradientPercentages({ + start = 0, + end = 100, + colors = [], +}: GradientConfig) { + const { length } = colors; + const diff = end - start; + const part = diff / (length - 1 > 0 ? length - 1 : 1); + let percentage = 0; + + return colors.reduce((prev, colorKey) => { + const color = theme.getValue('colors', colorKey) || colorKey; + const current = `${color} ${start + percentage}%`; + percentage += part; + return prev ? `${prev}, ${current}` : current; + }, ''); +} + +function getGradientProperty({ kind }: GradientConfig) { + if (kind === 'radial') { + return 'radial-gradient'; + } + + return 'linear-gradient'; +} + +function getGradientBackground(value: Gradient) { + const config = theme.getValue('gradients', value) || {}; + const { angle = 180, kind } = config; + const property = getGradientProperty(config); + const percentages = getGradientPercentages(config); + const gradientAngle = kind === 'radial' ? 'circle' : `${angle}deg`; + + if (!percentages) { + return undefined; + } + + return `${property}(${gradientAngle}, ${percentages})`; +} + +function gradient({ value }: ParserParams) { + const bg = getGradientBackground(value as any); + return { + background: bg, + }; +} + +function textGradient({ value }: ParserParams) { + const baseStyle = gradient({ value } as any); + return { + ...baseStyle, + backgroundClip: 'text', + textFillColor: 'transparent', + '-webkit-background-clip': 'text', + '-webkit-text-fill-color': 'transparent', + }; +} + +export const gradientParsers: GradientsParsers = { + gradient: gradient, + bgGradient: gradient, + textGradient: textGradient, +}; diff --git a/packages/web/src/parsers/index.ts b/packages/web/src/parsers/index.ts index a8907b84..4fd85206 100644 --- a/packages/web/src/parsers/index.ts +++ b/packages/web/src/parsers/index.ts @@ -1 +1,2 @@ export * from './pseudos'; +export * from './gradients'; diff --git a/packages/web/tests/gradient.test.ts b/packages/web/tests/gradient.test.ts new file mode 100644 index 00000000..619b0bc4 --- /dev/null +++ b/packages/web/tests/gradient.test.ts @@ -0,0 +1,99 @@ +import { parsers, theme } from '../src'; + +const THEME = { + colors: { + primary: 'red', + secondary: 'blue', + ternary: 'yellow', + }, + gradients: { + primary: { + colors: ['primary', 'secondary'], + angle: 0, + }, + secondary: { + start: 10, + end: 90, + colors: ['secondary', 'ternary', 'primary'], + angle: 180, + }, + radial: { + kind: 'radial', + colors: ['primary', 'secondary'], + angle: 0, + }, + defaultAngle: { + colors: ['primary', 'secondary'], + }, + notThemeColors: { + colors: ['grey', 'black'], + }, + }, +}; + +beforeEach(() => { + theme.set(THEME as any); +}); + +describe('gradient', () => { + test('should generate the prop `background` with a linear-gradient referred to primary', () => { + const style = parsers.resolve({ gradient: 'primary' }); + expect(style).toEqual({ + background: 'linear-gradient(0deg, red 0%, blue 100%)', + }); + }); + + test('should generate the prop `background` with a linear-gradient referred to secondary', () => { + const style = parsers.resolve({ bgGradient: 'secondary' }); + expect(style).toEqual({ + background: 'linear-gradient(180deg, blue 10%, yellow 50%, red 90%)', + }); + }); + + test('should generate the prop `backgroundColor` with a radial-gradient value', () => { + const style = parsers.resolve({ gradient: 'radial' as any }); + expect(style).toEqual({ + background: 'radial-gradient(circle, red 0%, blue 100%)', + }); + }); + + test('should set the default angle to 180deg if not passed', () => { + const style = parsers.resolve({ bgGradient: 'defaultAngle' as any }); + expect(style).toEqual({ + background: 'linear-gradient(180deg, red 0%, blue 100%)', + }); + }); + + test('should not generate any linear (or radial) gradient if the gradient does not exists', () => { + const style = parsers.resolve({ bgGradient: 'invalid' as any }); + expect(style).toEqual({ + background: undefined, + }); + }); + + test('should generate gradient for colors not provided by the theme', () => { + const style = parsers.resolve({ bgGradient: 'notThemeColors' as any }); + expect(style).toEqual({ + background: 'linear-gradient(180deg, grey 0%, black 100%)', + }); + }); + + test('should not generate any linear (or radial) gradient if any gradients inside the theme does exists', () => { + theme.reset(); + const style = parsers.resolve({ bgGradient: 'invalid' as any }); + expect(style).toEqual({ + background: undefined, + }); + }); + + test('should generate text gradient if the prop `textGradient` is passed', () => { + const style = parsers.resolve({ textGradient: 'primary' as any }); + expect(style).toEqual({ + background: 'linear-gradient(0deg, red 0%, blue 100%)', + backgroundClip: 'text', + textFillColor: 'transparent', + '-webkit-background-clip': 'text', + '-webkit-text-fill-color': 'transparent', + }); + }); +});