Skip to content

Commit

Permalink
feat: add support for text style in component theme
Browse files Browse the repository at this point in the history
  • Loading branch information
segunadebayo committed Feb 2, 2021
1 parent 1b39646 commit ac99e08
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 54 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
"react/prop-types": "off",
"@typescript-eslint/no-shadow": "off",
"react-hooks/exhaustive-deps": "error",
"import/no-named-as-default": "off"
"import/no-named-as-default": "off",
"prefer-object-spread": "off"
},
"overrides": [
{
Expand Down
32 changes: 31 additions & 1 deletion packages/styled-system/src/config/others.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { memoizedGet as get } from "@chakra-ui/utils"
import * as CSS from "csstype"
import { Config, createParser, PropConfig, system } from "../core"
import { getIsRtl } from "../utils/directionality"
import { Length, ResponsiveValue } from "../utils"
import { getIsRtl } from "../utils/directionality"

const floatTransform: PropConfig["transform"] = (value, _, props = {}) => {
const map = { left: "right", right: "left" }
Expand Down Expand Up @@ -56,6 +57,21 @@ const config: Config = {
return {}
},
},
layerStyle: {
processResult: true,
property: "&",
transform: (value, _, theme) => get(theme, `layerStyles.${value}`, {}),
},
textStyle: {
processResult: true,
property: "&",
transform: (value, _, theme) => get(theme, `textStyles.${value}`, {}),
},
apply: {
processResult: true,
property: "&",
transform: (value, _, theme) => get(theme, value, {}),
},
}

export interface OtherProps {
Expand Down Expand Up @@ -120,6 +136,20 @@ export interface OtherProps {
* It creates a clipping region that sets what part of an element should be shown.
*/
clipPath?: ResponsiveValue<CSS.Property.ClipPath>
/**
* The layer style object to apply.
* Note: Styles must be located in `theme.layerStyles`
*/
layerStyle?: ResponsiveValue<string>
/**
* The text style object to apply.
* Note: Styles must be located in `theme.textStyles`
*/
textStyle?: ResponsiveValue<string>
/**
* Apply theme-aware style objects in `theme`
*/
apply?: ResponsiveValue<string>
}

export const others = system(config)
Expand Down
1 change: 1 addition & 0 deletions packages/styled-system/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface PropConfig {
scale?: string
defaultScale?: Scale
transform?(value: any, scale: any, props: Props): any
processResult?: boolean
}

export interface Config {
Expand Down
18 changes: 7 additions & 11 deletions packages/styled-system/src/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,7 @@ export const css = (styleOrFn: StyleObjectOrFn = {}) => (

if (config === true) {
// shortcut definition
config = {
property: key,
scale: key,
}
}

if (key === "apply") {
const apply = css(get(theme, val))(theme)
computedStyles = mergeWith({}, computedStyles, apply)
continue
config = { property: key, scale: key }
}

if (isObject(val)) {
Expand All @@ -169,7 +160,12 @@ export const css = (styleOrFn: StyleObjectOrFn = {}) => (
}

const scale = get(theme, config?.scale, {})
const value = config?.transform?.(val, scale, props) ?? get(scale, val, val)
let value = config?.transform?.(val, scale, props) ?? get(scale, val, val)
/**
* Useful for `layerStyle`, and `textStyle` to transform the returned
* result since it might use theme tokens
*/
value = config?.processResult ? css(value)(theme) : value

if (config?.properties) {
for (const property of config.properties) {
Expand Down
10 changes: 1 addition & 9 deletions packages/styled-system/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,9 @@ export interface StyleProps
System.OutlineProps,
System.OtherProps {}

export interface ApplyPropStyles {
/**
* Apply theme-aware style objects in `theme`
*/
apply?: ResponsiveValue<string>
}

export interface SystemCSSProperties
extends CSS.Properties,
Omit<StyleProps, keyof CSS.Properties>,
ApplyPropStyles {}
Omit<StyleProps, keyof CSS.Properties> {}

export type ThemeThunk<T> = T | ((theme: Dict) => T)

Expand Down
58 changes: 58 additions & 0 deletions packages/styled-system/tests/css.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,3 +493,61 @@ test("pseudo selectors are transformed", () => {
},
})
})

test("should expand textStyle and layerStyle", () => {
const theme = {
colors: { red: { 300: "#red" } },
breakpoints: createBreakpoints({
sm: "400px",
md: "768px",
lg: "1200px",
xl: "1800px",
}),
layerStyles: {
v1: {
color: "red.300",
bg: "tomato",
},
},
textStyles: {
caps: {
textTransform: "uppercase",
letterSpacing: "wide",
fontSize: "lg",
},
lower: {
textTransform: "lowercase",
letterSpacing: "0.2px",
fontSize: "sm",
},
},
}

expect(css({ layerStyle: "v1" })(theme)).toMatchInlineSnapshot(`
Object {
"background": "tomato",
"color": "#red",
}
`)

expect(css({ textStyle: "caps" })(theme)).toMatchInlineSnapshot(`
Object {
"fontSize": "lg",
"letterSpacing": "wide",
"textTransform": "uppercase",
}
`)

expect(css({ textStyle: ["caps", "lower"] })(theme)).toMatchInlineSnapshot(`
Object {
"@media screen and (min-width: 400px)": Object {
"fontSize": "sm",
"letterSpacing": "0.2px",
"textTransform": "lowercase",
},
"fontSize": "lg",
"letterSpacing": "wide",
"textTransform": "uppercase",
}
`)
})
43 changes: 13 additions & 30 deletions packages/system/src/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@ import {
css,
propNames,
ResponsiveValue,
SystemProps,
SystemStyleObject,
StyleProps,
SystemStyleObject,
} from "@chakra-ui/styled-system"
import {
memoizedGet as get,
objectFilter,
objectAssign,
Dict,
isFunction,
} from "@chakra-ui/utils"
import { Dict, isFunction, objectFilter } from "@chakra-ui/utils"
import _styled, {
CSSObject,
FunctionInterpolation,
Expand All @@ -25,26 +18,25 @@ import { domElements, DOMElements } from "./system.utils"
/**
* Convert propNames array to object to faster lookup perf
*/
const stylePropNames = propNames.reduce((keymirror, key) => {
if (typeof key !== "object" && typeof key !== "function") keymirror[key] = key
return keymirror
const stylePropNames = propNames.reduce((acc, key) => {
if (typeof key !== "object" && typeof key !== "function") acc[key] = key
return acc
}, {})

interface StyleResolverProps extends SystemProps {
type StyleResolverProps = SystemStyleObject & {
__css?: SystemStyleObject
sx?: SystemStyleObject
theme: Dict
css?: CSSObject
noOfLines?: ResponsiveValue<number>
isTruncated?: boolean
layerStyle?: string
textStyle?: string
apply?: ResponsiveValue<string>
}

type GetStyleObject = (options: {
baseStyle?: SystemStyleObject
}) => FunctionInterpolation<StyleResolverProps>
interface GetStyleObject {
(options: {
baseStyle?: SystemStyleObject
}): FunctionInterpolation<StyleResolverProps>
}

/**
* Style resolver function that manages how style props are merged
Expand All @@ -62,9 +54,6 @@ type GetStyleObject = (options: {
export const getStyleObject: GetStyleObject = ({ baseStyle }) => (props) => {
const {
theme,
layerStyle,
textStyle,
apply,
noOfLines,
isTruncated,
css: cssProp,
Expand All @@ -73,9 +62,6 @@ export const getStyleObject: GetStyleObject = ({ baseStyle }) => (props) => {
...rest
} = props

const _layerStyle = get(theme, `layerStyles.${layerStyle}`, {})
const _textStyle = get(theme, `textStyles.${textStyle}`, {})

// filter out props that aren't style props
const styleProps = objectFilter(rest, (_, prop) => prop in stylePropNames)

Expand All @@ -101,13 +87,10 @@ export const getStyleObject: GetStyleObject = ({ baseStyle }) => (props) => {
* The computed, theme-aware style object. The other of the properties
* within `objectAssign` determines how styles are overriden.
*/
const finalStyles = objectAssign(
const finalStyles = Object.assign(
{},
__css,
baseStyle,
{ apply },
_layerStyle,
_textStyle,
truncateStyle,
styleProps,
sx,
Expand All @@ -117,7 +100,7 @@ export const getStyleObject: GetStyleObject = ({ baseStyle }) => (props) => {
const computedCSS = css(finalStyles)(props.theme)

// Merge the computed css object with styles in css prop
const cssObject: Interpolation<StyleResolverProps> = objectAssign(
const cssObject: Interpolation<StyleResolverProps> = Object.assign(
computedCSS,
isFunction(cssProp) ? cssProp(theme) : cssProp,
)
Expand Down
23 changes: 23 additions & 0 deletions packages/system/stories/system.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,26 @@ export const WithRgbGradient = () => (
/>
</>
)

export const WithLayerStyle = () => (
<ThemeProvider
theme={{
layerStyles: {
base: {
bg: "pink",
color: "red",
},
},
textStyles: {
caps: {
textTransform: "uppercase",
fontWeight: "bold",
},
},
}}
>
<chakra.div layerStyle="base" textStyle="caps" color="white" px="2">
Welcome
</chakra.div>
</ThemeProvider>
)
4 changes: 2 additions & 2 deletions packages/system/tests/style-resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ test("should resolve styles correctly", () => {
"WebkitLineClamp": 3,
"background": "tomato",
"backgroundPosition": "top left",
"color": "#F687B3",
"color": "#FC8181",
"display": "-webkit-box",
"fontSize": 10,
"letterSpacing": "2px",
"overflow": "hidden",
"paddingLeft": 40,
"paddingRight": "1.25rem",
"textOverflow": "ellipsis",
"textTransform": "capitalize",
"textTransform": "uppercase",
}
`)
})
Expand Down

0 comments on commit ac99e08

Please sign in to comment.