diff --git a/.gitignore b/.gitignore index d1c2992e..7dc8648a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # See https://help.github.com/ignore-files/ for more about ignoring files. build +__snapshots__ # dependencies node_modules diff --git a/apps/web-sandbox/package.json b/apps/web-sandbox/package.json index b6753ad8..539fd839 100644 --- a/apps/web-sandbox/package.json +++ b/apps/web-sandbox/package.json @@ -5,6 +5,7 @@ "dependencies": { "@morfeo/hooks": "^0.0.3", "@morfeo/web": "^0.0.3", + "@morfeo/styled-components-web": "^0.0.3", "@testing-library/jest-dom": "^5.12.0", "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^12.8.3", diff --git a/apps/web-sandbox/src/App.tsx b/apps/web-sandbox/src/App.tsx index 8e345d8a..2b741752 100644 --- a/apps/web-sandbox/src/App.tsx +++ b/apps/web-sandbox/src/App.tsx @@ -1,13 +1,12 @@ import { useState, useCallback, FC } from 'react'; -import { ThemeProvider } from 'styled-components'; +import styled, { ThemeProvider } from '@morfeo/styled-components-web'; import { theme, Component } from '@morfeo/web'; import { useTheme, useStyles } from '@morfeo/hooks'; -import { customStyled } from './styled'; import { darkTheme, lightTheme } from './theme'; theme.set(lightTheme); -const Button = customStyled('Button'); +const Button = styled.Button({}); const StyledProvider: FC = ({ children }) => { const currentTheme = useTheme(); diff --git a/apps/web-sandbox/src/styled.tsx b/apps/web-sandbox/src/styled.tsx deleted file mode 100644 index c416ea23..00000000 --- a/apps/web-sandbox/src/styled.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { theme, parsers, Component, Style, Theme } from '@morfeo/web'; -import styled, { StyledComponent } from 'styled-components'; - -export function customStyled(component: Component) { - const { components } = theme.get(); - const config = components[component]; - const tag = (config.style.componentTag || component) as - | keyof JSX.IntrinsicElements - | React.ComponentType; - - const result = function (props: any) { - const variant = props.variant as any; - const variantTag = (props.variant && config.variants - ? config.variants[variant].componentTag - : tag) as keyof JSX.IntrinsicElements | React.ComponentType; - - const Component = styled(variantTag || (tag as any))( - ({ theme: styledTheme, children, ...style }) => { - return parsers.resolve({ - style: { componentName: component, ...(style as Style) }, - }); - }, - ) as StyledComponent; - - return ; - }; - - return result as StyledComponent; -} diff --git a/package.json b/package.json index 4e43d7c7..1955dcf1 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "prettify": "npm run prettify:js && npm run prettify:ts", "prettify:js": "npx prettier --write \"**/*.js\"", "prettify:ts": "npx prettier --write \"**/*.ts\"", - "test": "jest", - "test:watch": "jest --watch", - "test:coverage": "jest --coverage", + "test": "jest --updateSnapshot", + "test:watch": "jest --watch --updateSnapshot", + "test:coverage": "jest --coverage --updateSnapshot", "test:badges": "npm run test:coverage && jest-coverage-badges", "publish": "npx lerna publish from-package --yes", "version": "npx lerna version patch --no-push --no-git-tag-version --conventional-commits" diff --git a/packages/core/package.json b/packages/core/package.json index ec24ec71..636e6439 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -9,8 +9,8 @@ "license": "MIT", "main": "build/index.js", "module": "build/index.js", - "types": "build/index", - "typings": "build/index", + "types": "src/index", + "typings": "src/index", "keywords": [ "design", "system" diff --git a/packages/core/src/parsers/parsers.ts b/packages/core/src/parsers/parsers.ts index 53000de9..37a34daa 100644 --- a/packages/core/src/parsers/parsers.ts +++ b/packages/core/src/parsers/parsers.ts @@ -41,7 +41,6 @@ const ADDITIONAL_PARSERS = { const INITIAL_PARSERS = { ...DEFAULT_PARSERS, ...ADDITIONAL_PARSERS, - children: () => ({}), }; type ParsersContext = { diff --git a/packages/core/tests/parsers/components.test.ts b/packages/core/tests/parsers/components.test.ts index bd5ad488..bd94fa28 100644 --- a/packages/core/tests/parsers/components.test.ts +++ b/packages/core/tests/parsers/components.test.ts @@ -38,7 +38,7 @@ describe('components', () => { property: 'componentName', value: 'Button', }); - expect(result.componentTag).not.toBeDefined(); + expect((result as any).componentTag).not.toBeDefined(); }); test('should return the default components style', () => { diff --git a/packages/core/tests/parsers/shadows.test.ts b/packages/core/tests/parsers/shadows.test.ts index 09aec1c8..9e3bb1bc 100644 --- a/packages/core/tests/parsers/shadows.test.ts +++ b/packages/core/tests/parsers/shadows.test.ts @@ -1,6 +1,6 @@ import { Theme } from '@morfeo/spec'; import { shadows } from '../../src/parsers/shadows'; -import { theme } from '../../src'; +import { parsers, theme } from '../../src'; const THEME: Theme = { colors: { @@ -55,6 +55,13 @@ describe('shadows', () => { }); }); + test('should generate the property `boxShadow` if the prop `shadow` is passed', () => { + const result = parsers.resolve({ style: { shadow: 'strong' } }); + expect(result).toEqual({ + boxShadow: '10px 10px 30px white', + }); + }); + test('should generate the property `boxShadow` based on the light shadow', () => { const result = shadows({ property: 'boxShadow', value: 'light' }); expect(result).toEqual({ diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 862569e5..3ba135b9 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -9,8 +9,8 @@ "license": "MIT", "main": "build/index.js", "module": "build/index.js", - "types": "build/index", - "typings": "build/index", + "types": "src/index", + "typings": "src/index", "keywords": [ "design", "system" @@ -20,7 +20,7 @@ "watch": "tsc -w" }, "peerDependencies": { - "@morfeo/core": "^0.0.1", + "@morfeo/core": "^0.0.3", "react": "17.0.2", "react-dom": "^17.0.2" }, diff --git a/packages/native/CHANGELOG.md b/packages/native/CHANGELOG.md deleted file mode 100644 index 0e9163a3..00000000 --- a/packages/native/CHANGELOG.md +++ /dev/null @@ -1,34 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## 0.0.3 (2021-05-06) - - -### Bug Fixes - -* fixed typing of packages ([57267c5](https://bitbucket.org/me-sign/design-system/commits/57267c5f904cbeece433e7bb2573fd9d7a4b3fd4)) - - -### Features - -* added pubish config to packages ([23241fc](https://bitbucket.org/me-sign/design-system/commits/23241fcb4a1ef76615661e5b8e9e4ed53060b912)) -* hooks package added ([0637789](https://bitbucket.org/me-sign/design-system/commits/0637789d23e12bb3dfb295039e92d2a4f815927a)) - - - - - -## 0.0.2 (2021-05-06) - - -### Bug Fixes - -* fixed typing of packages ([57267c5](https://bitbucket.org/me-sign/design-system/commits/57267c5f904cbeece433e7bb2573fd9d7a4b3fd4)) - - -### Features - -* added pubish config to packages ([23241fc](https://bitbucket.org/me-sign/design-system/commits/23241fcb4a1ef76615661e5b8e9e4ed53060b912)) -* hooks package added ([0637789](https://bitbucket.org/me-sign/design-system/commits/0637789d23e12bb3dfb295039e92d2a4f815927a)) diff --git a/packages/native/package.json b/packages/native/package.json index c42c362e..07c615d1 100644 --- a/packages/native/package.json +++ b/packages/native/package.json @@ -9,8 +9,8 @@ "license": "MIT", "main": "build/index.js", "module": "build/index.js", - "types": "build/index", - "typings": "build/index", + "types": "src/index", + "typings": "src/index", "keywords": [ "design", "system", diff --git a/packages/native/src/parsers/shadows.ts b/packages/native/src/parsers/shadows.ts index be4ceed8..dcdb09db 100644 --- a/packages/native/src/parsers/shadows.ts +++ b/packages/native/src/parsers/shadows.ts @@ -1,6 +1,7 @@ import { - parsers, theme, + parsers, + Style, Shadows, ParserParams, ShadowProperty, @@ -43,17 +44,8 @@ export function shadowOffset({ value }) { } export function shadows({ value }: ParserParams) { - if ( - !theme || - !theme.getSlice('shadows') || - !theme.getValue('shadows', value as keyof Shadows) - ) { - return {}; - } - const { color, offset, opacity, radius, elevation } = theme.getValue( - 'shadows', - value as keyof Shadows, - ); + const { color, offset, opacity, radius, elevation } = + theme.getValue('shadows', value as keyof Shadows) || {}; return parsers.resolve({ style: { @@ -62,6 +54,6 @@ export function shadows({ value }: ParserParams) { ...(offset ? { shadowOffset: offset } : {}), ...(radius ? { shadowRadius: radius } : {}), ...(opacity ? { shadowOpacity: opacity } : {}), - } as any, + } as Style, }); } diff --git a/packages/native/src/types/core.ts b/packages/native/src/types/core.ts index 9f0d38a6..66c6082c 100644 --- a/packages/native/src/types/core.ts +++ b/packages/native/src/types/core.ts @@ -1,20 +1,27 @@ import { Color, Opacity, Space } from '@morfeo/core'; import { ViewStyle, ImageStyle, TextStyle } from 'react-native'; -type CustomNativeProperties = { +type CustomNativeStyle = { shadowColor?: Color; shadowOffset?: Space; - shadowRadius?: number; + shadowRadius?: Space; shadowOpacity?: Opacity; }; +type CustomNativeProperties = { + shadowColor: 'colors'; + shadowOffset: 'space'; + shadowRadius: 'space'; + shadowOpacity: 'opacities'; +}; + type NativeStyle = ViewStyle & ImageStyle & TextStyle; -type NativeCustomStyle = Omit & - CustomNativeProperties; +type NativeCustomStyle = Omit & + CustomNativeStyle; interface NativeShadowConfig { - elevation?: number; + elevation?: Space; } declare module '@morfeo/core' { diff --git a/packages/native/tests/shadows.test.ts b/packages/native/tests/shadows.test.ts new file mode 100644 index 00000000..ef16479d --- /dev/null +++ b/packages/native/tests/shadows.test.ts @@ -0,0 +1,115 @@ +import { parsers, theme } from '../src'; +import { shadowOffset } from '../src/parsers'; + +const THEME = { + colors: { + primary: 'black', + secondary: 'white', + }, + space: { + s: 10, + m: 20, + }, + sizes: { + l: 30, + xl: 40, + }, + shadows: { + strong: { + color: 'primary', + offset: { height: 's', width: 's' }, + opacity: 0.4, + radius: 30, + }, + light: { + color: 'secondary', + offset: { width: 'xl' }, + opacity: 0.4, + radius: 30, + }, + medium: { + elevation: 2, + color: 'secondary', + offset: { height: 30 }, + opacity: 0.4, + }, + custom: { + color: 'not inside theme', + offset: undefined, + }, + noOffset: { + offset: {}, + }, + }, +} as any; + +afterAll(() => { + theme.set(THEME); +}); + +describe('shadows', () => { + beforeAll(() => { + theme.set(THEME); + }); + afterAll(() => { + theme.reset(); + }); + + test('should generate the property `boxShadow` based on the strong shadow', () => { + const result = parsers.resolve({ style: { shadow: 'strong' } }); + expect(result).toEqual({ + shadowColor: 'black', + shadowOffset: { height: 10, width: 10 }, + shadowOpacity: 0.4, + shadowRadius: 30, + }); + }); + + test('should generate the property `shadow` based on the light shadow', () => { + const result = parsers.resolve({ style: { shadow: 'light' } }); + expect(result).toEqual({ + shadowColor: 'white', + shadowOffset: { height: 0, width: 40 }, + shadowOpacity: 0.4, + shadowRadius: 30, + }); + }); + + test('should generate the property `shadow` based on the medium shadow', () => { + const result = parsers.resolve({ style: { shadow: 'medium' } }); + expect(result).toEqual({ + elevation: 2, + shadowColor: 'white', + shadowOffset: { height: 30, width: 0 }, + shadowOpacity: 0.4, + }); + }); + + test('should return an empty object if the shadow is not found', () => { + const result = parsers.resolve({ style: { shadow: 'none' } }); + expect(result).toEqual({}); + }); + + test('should use custom colors if they are not inside the theme', () => { + const result = parsers.resolve({ + style: { shadow: 'custom' as any }, + }); + expect(result).toEqual({ + shadowColor: 'not inside theme', + }); + }); + + test('should not put any value with any empty configuration', () => { + const result = parsers.resolve({ + style: { shadow: 'noOffset' as any }, + }); + expect(result).toEqual({}); + }); + + test('should return an empty object if `shadowOffset` is undefined', () => { + const result = shadowOffset({ + value: undefined, + }); + expect(result).toEqual({}); + }); +}); diff --git a/packages/spec/package.json b/packages/spec/package.json index 989f95e9..888e9a73 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -9,8 +9,8 @@ "license": "MIT", "main": "build/index.js", "module": "build/index.js", - "types": "build/index", - "typings": "build/index", + "types": "src/index", + "typings": "src/index", "keywords": [ "design", "system" diff --git a/packages/styled-components-web/package.json b/packages/styled-components-web/package.json new file mode 100644 index 00000000..3f370433 --- /dev/null +++ b/packages/styled-components-web/package.json @@ -0,0 +1,36 @@ +{ + "name": "@morfeo/styled-components-web", + "author": { + "name": "Mauro Erta", + "email": "mauro@vlkstudio.com" + }, + "private": false, + "version": "0.0.3", + "license": "MIT", + "main": "build/index.js", + "module": "build/index.js", + "types": "src/index", + "typings": "src/index", + "keywords": [ + "design", + "system", + "react", + "styled-components" + ], + "scripts": { + "build": "rimraf build && tsc", + "watch": "tsc -w" + }, + "peerDependencies": { + "@morfeo/web": "^0.0.3", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "styled-components": "^5.2.3" + }, + "devDependencies": { + "@morfeo/web": "^0.0.3" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/styled-components-web/src/index.ts b/packages/styled-components-web/src/index.ts new file mode 100644 index 00000000..be92c1c2 --- /dev/null +++ b/packages/styled-components-web/src/index.ts @@ -0,0 +1,6 @@ +import { morfeoStyled, propsParser } from './styled'; +export * from 'styled-components'; +export * from './types'; + +export { propsParser }; +export default morfeoStyled; diff --git a/packages/styled-components-web/src/styled.ts b/packages/styled-components-web/src/styled.ts new file mode 100644 index 00000000..fd3a385c --- /dev/null +++ b/packages/styled-components-web/src/styled.ts @@ -0,0 +1,77 @@ +import { parsers, theme, Theme, Style, Component } from '@morfeo/web'; +import styled, { StyledInterface, StyledComponent } from 'styled-components'; + +type ComponentTag = keyof StyledInterface | Component; +type MorfeoStyledComponent< + K extends keyof StyledInterface, + P extends Style +> = StyledComponent; + +type MorfeoStyledCallback =

( + tag: keyof StyledInterface, +) => MorfeoStyledComponent; + +type MorfeoStyled< + P extends Style | TemplateStringsArray = Style +> = MorfeoStyledCallback & + { + [K in keyof StyledInterface]: ( + props: P | TemplateStringsArray, + ) => P extends Style + ? MorfeoStyledComponent + : MorfeoStyledComponent; + } & + { + [K in Component]: ( + props: P | TemplateStringsArray, + ) => P extends Style + ? MorfeoStyledComponent + : MorfeoStyledComponent; + }; + +function isStyleProps(arg: any): arg is Style { + return !Array.isArray(arg) && typeof arg === 'object'; +} + +export function propsParser(...props: any[]) { + const allProps = props.reduce((acc, currentProps) => { + const { theme: _, children, ...rest } = currentProps; + const currentStyle = parsers.resolve({ style: rest }); + return { + ...acc, + ...currentStyle, + }; + }, {}); + + return allProps; +} + +const morfeoStyledHandler: MorfeoStyled = ((tag: ComponentTag) => { + const { style } = theme.getValue('components', tag as any) || {}; + const componentTag = style?.componentTag || tag; + const styledFunction = styled[componentTag]; + + if (typeof styledFunction === 'function') { + return (componentProps: Style | TemplateStringsArray = {}) => { + if (!isStyleProps(componentProps)) { + return styledFunction(componentProps); + } + + return styledFunction( + props => + propsParser({ componentName: tag, ...props }, componentProps) as any, + ); + }; + } +}) as any; + +export const morfeoStyled = new Proxy(morfeoStyledHandler, { + get(target, prop, receiver) { + const result = target(prop as any); + if (result) { + return result; + } + + return Reflect.get(styled, prop, receiver); + }, +}); diff --git a/packages/styled-components-web/src/types.ts b/packages/styled-components-web/src/types.ts new file mode 100644 index 00000000..2c6917d6 --- /dev/null +++ b/packages/styled-components-web/src/types.ts @@ -0,0 +1,5 @@ +import { Theme } from '@morfeo/web'; + +declare module 'styled-components' { + export interface DefaultTheme extends Theme {} +} diff --git a/packages/styled-components-web/tests/styled.test.tsx b/packages/styled-components-web/tests/styled.test.tsx new file mode 100644 index 00000000..b5a7005a --- /dev/null +++ b/packages/styled-components-web/tests/styled.test.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { theme } from '@morfeo/web'; +import morfeoStyled from '../src'; +import renderer from 'react-test-renderer'; +import 'jest-styled-components'; + +beforeAll(() => { + theme.set({ + colors: { + primary: 'black', + secondary: 'white', + }, + components: { + Button: { + style: { color: 'primary', componentTag: 'button' }, + }, + }, + } as any); +}); + +describe('morfeoStyled', () => { + test('should work as the default styled interface', () => { + const Button = morfeoStyled.button` + color: red; + `; + const tree = renderer.create(