From 97d9de9ccdbd3fc6dc3e409dba100210667025bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Le=20Ralec?= Date: Thu, 7 Jul 2022 22:30:31 +0200 Subject: [PATCH] feat: responsive (breakpoints) --- README.md | 31 ++++++++++++++++++++- src/theme.ts | 1 + src/utils/index.ts | 65 +++++++++++++++++++++++++++------------------ test/space.test.ts | 60 +++++++++++++++++++++++++++++++++++++++++ test/system.test.ts | 41 ++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 test/space.test.ts diff --git a/README.md b/README.md index 02b9cba..2162a4f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Inspired by [xstyled](https://xstyled.dev/) and [styled-system](https://styled-system.com/), **jsx-to-styled** adds styled props to your React styled-components. The main idea of this library is to stay really **simple** and **performant**. -> All styled props injected by **jsx-to-styled** are prefixed by `$` symbol to prevent forwarding props to html element, check the [styled-component transiant props section](https://styled-components.com/docs/api#transient-props) for more informations. +> All styled props injected by **jsx-to-styled** are prefixed by `$` symbol to prevent forwarding props to html element, check the [styled-components transiant props section](https://styled-components.com/docs/api#transient-props) for more informations. ## 🔧 Installation @@ -96,6 +96,35 @@ declare module 'jsx-to-styled' { } ``` +## 📱 Responsive (breakpoints) + +You can use **breakpoints** in your styled props ([codesandbox example](https://codesandbox.io/s/breakpoints-lun0gq?file=/src/App.tsx)) + +```tsx +// add "breakpoints" key in your theme +const theme = { + colors: { + primary: 'white', + secondary: 'tomato', + }, + breakpoints: { + sm: '600px', + }, +} + +// now you can use props with object style +return ( + + + A + + + B + + +) +``` + ## 🍱 States You can use **states** in your styled props ([codesandbox example](https://codesandbox.io/s/states-x587p5?file=/src/App.tsx)) diff --git a/src/theme.ts b/src/theme.ts index ca52505..ef73865 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -12,6 +12,7 @@ export type ThemeKeys = | 'borderWidths' | 'radii' | 'states' + | 'breakpoints' | (string & Record) /** diff --git a/src/utils/index.ts b/src/utils/index.ts index bbdee04..3b8194d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,10 +6,31 @@ const get = (key: string, theme: Theme, scope: ThemeKeys): string => { return theme?.[scope]?.[key] || key } -// const createMediaQuery = (breakpoint: string) => `@media(min-width: ${breakpoint})` +const createMediaQuery = (breakpoint: string) => `@media(min-width: ${breakpoint})` + +const parse = ( + cssProperties: string[], + jsxProperty: string, + value: string, + theme: Theme, + scope: ThemeKeys +) => { + const styles: CSSObject = {} + const result = get(value, theme, scope) + + if (cssProperties) { + cssProperties.forEach(cssProp => { + styles[cssProp] = result + }) + } else { + styles[jsxProperty] = result + } + + return styles +} export const getStyles = (config: Config[], props: SystemProps & ThemeProp) => { - const styles: CSSObject = {} + let styles: CSSObject = {} config.forEach(({ jsxProperty, scope, cssProperties }) => { const value = props[jsxProperty] @@ -19,12 +40,9 @@ export const getStyles = (config: Config[], props: SystemProps & ThemeProp) => { if (!value) return if (typeof value === 'string') { - if (cssProperties) { - cssProperties.forEach(cssProp => { - styles[cssProp] = get(value, theme, scope) - }) - } else { - styles[formattedJsxProperty] = get(value, theme, scope) + styles = { + ...styles, + ...parse(cssProperties, formattedJsxProperty, value, theme, scope), } } @@ -36,33 +54,28 @@ export const getStyles = (config: Config[], props: SystemProps & ThemeProp) => { const v = value[key as ObjectPropsKey] as string if (key === '_') { - if (cssProperties) { - cssProperties.forEach(cssProp => { - styles[cssProp] = get(v, theme, scope) - }) - } else { - styles[formattedJsxProperty] = get(v, theme, scope) + styles = { + ...styles, + ...parse(cssProperties, formattedJsxProperty, v, theme, scope), } } if (states.includes(key)) { const state = theme.states[key] - const s: CSSObject = {} - - if (cssProperties) { - cssProperties.forEach(cssProp => { - s[cssProp] = get(v, props.theme, scope) - }) - } else { - s[formattedJsxProperty] = get(v, props.theme, scope) - } - styles[state] = { ...(styles[state] as CSSObject), ...s } + styles[state] = { + ...(styles[state] as CSSObject), + ...parse(cssProperties, formattedJsxProperty, v, theme, scope), + } } if (breakpoints.includes(key)) { - // todo apply breakpoints styles - // styles[createMediaQuery(breakpoint)] = get(v, props.theme, scope) + const breakpoint = createMediaQuery(theme.breakpoints[key]) + + styles[breakpoint] = { + ...(styles[breakpoint] as CSSObject), + ...parse(cssProperties, formattedJsxProperty, v, theme, scope), + } } }) } diff --git a/test/space.test.ts b/test/space.test.ts new file mode 100644 index 0000000..3e7eae1 --- /dev/null +++ b/test/space.test.ts @@ -0,0 +1,60 @@ +import { describe, it, expect } from 'vitest' + +import { space } from '../src' + +const theme = { + spaces: { + sm: '10px', + md: '20px', + }, +} + +describe('space', () => { + it('should return margin-top + margin-bottom with "my"', () => { + const props = { $my: '20px' } + + expect(space(props)).toEqual({ marginTop: '20px', marginBottom: '20px' }) + }) + + it('should return margin-top + margin-bottom with "my" from theme', () => { + const props = { $my: 'md', theme } + + expect(space(props)).toEqual({ marginTop: '20px', marginBottom: '20px' }) + }) + + it('should return margin-right + margin-left with "mx"', () => { + const props = { $mx: '20px' } + + expect(space(props)).toEqual({ marginRight: '20px', marginLeft: '20px' }) + }) + + it('should return margin-right + margin-left with "mx" from theme', () => { + const props = { $mx: 'md', theme } + + expect(space(props)).toEqual({ marginRight: '20px', marginLeft: '20px' }) + }) + + it('should return padding-top + padding-bottom with "my"', () => { + const props = { $py: '20px' } + + expect(space(props)).toEqual({ paddingTop: '20px', paddingBottom: '20px' }) + }) + + it('should return padding-top + padding-bottom with "my" from theme', () => { + const props = { $py: 'md', theme } + + expect(space(props)).toEqual({ paddingTop: '20px', paddingBottom: '20px' }) + }) + + it('should return padding-right + padding-left with "mx"', () => { + const props = { $px: '20px' } + + expect(space(props)).toEqual({ paddingRight: '20px', paddingLeft: '20px' }) + }) + + it('should return padding-right + padding-left with "mx" from theme', () => { + const props = { $px: 'md', theme } + + expect(space(props)).toEqual({ paddingRight: '20px', paddingLeft: '20px' }) + }) +}) diff --git a/test/system.test.ts b/test/system.test.ts index 573f80d..42ae137 100644 --- a/test/system.test.ts +++ b/test/system.test.ts @@ -11,6 +11,9 @@ const theme = { sm: '10px', md: '20px', }, + breakpoints: { + md: '1000px', + }, states: { hover: '&:hover', }, @@ -81,4 +84,42 @@ describe('system', () => { }, }) }) + + it('should apply responsive props', () => { + const props = { + $color: { _: 'primary', md: 'secondary' }, + $m: { _: 'sm', md: 'md' }, + theme, + } + + expect(system(props)).toEqual({ + color: 'tomato', + margin: '10px', + '@media(min-width: 1000px)': { + color: 'purple', + margin: '20px', + }, + }) + }) + + it('should apply responsive & hover props', () => { + const props = { + $color: { _: 'primary', md: 'secondary', hover: 'secondary' }, + $m: { _: 'sm', md: 'md', hover: 'md' }, + theme, + } + + expect(system(props)).toEqual({ + color: 'tomato', + margin: '10px', + '&:hover': { + color: 'purple', + margin: '20px', + }, + '@media(min-width: 1000px)': { + color: 'purple', + margin: '20px', + }, + }) + }) })