Skip to content

Commit

Permalink
feat: responsive (breakpoints)
Browse files Browse the repository at this point in the history
  • Loading branch information
mleralec committed Jul 7, 2022
1 parent 1525bb9 commit 97d9de9
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 27 deletions.
31 changes: 30 additions & 1 deletion README.md
Expand Up @@ -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

Expand Down Expand Up @@ -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 (
<Box $display="flex" $flexDirection={{ _: 'row', sm: 'column' }}>
<Box $w="200px" $h="200px">
A
</Box>
<Box $w="200px" $h="200px">
B
</Box>
</Box>
)
```

## 🍱 States

You can use **states** in your styled props ([codesandbox example](https://codesandbox.io/s/states-x587p5?file=/src/App.tsx))
Expand Down
1 change: 1 addition & 0 deletions src/theme.ts
Expand Up @@ -12,6 +12,7 @@ export type ThemeKeys =
| 'borderWidths'
| 'radii'
| 'states'
| 'breakpoints'
| (string & Record<never, never>)

/**
Expand Down
65 changes: 39 additions & 26 deletions src/utils/index.ts
Expand Up @@ -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]
Expand All @@ -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),
}
}

Expand All @@ -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),
}
}
})
}
Expand Down
60 changes: 60 additions & 0 deletions 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' })
})
})
41 changes: 41 additions & 0 deletions test/system.test.ts
Expand Up @@ -11,6 +11,9 @@ const theme = {
sm: '10px',
md: '20px',
},
breakpoints: {
md: '1000px',
},
states: {
hover: '&:hover',
},
Expand Down Expand Up @@ -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',
},
})
})
})

0 comments on commit 97d9de9

Please sign in to comment.