Skip to content

Commit

Permalink
feat: support custom conditions / psuedo props (#8218)
Browse files Browse the repository at this point in the history
* chore: wip

* chore: update generated conditions

* docs: update changeset

* feat: add new pseudos
  • Loading branch information
segunadebayo committed Jan 31, 2024
1 parent 80406fd commit a89c598
Show file tree
Hide file tree
Showing 18 changed files with 198 additions and 87 deletions.
32 changes: 32 additions & 0 deletions .changeset/pretty-cougars-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
"@chakra-ui/styled-system": minor
"@chakra-ui/react": minor
"@chakra-ui/theme": minor
---

Add support for custom theme conditions or pseudo props via `theme.conditions`

```ts
// theme.ts

const theme = extendTheme({
conditions: {
_closed: "[data-state='closed']", // pseudo prop
},
})
```

This allows you to use the pseudo prop in your components

```jsx
<Box data-state="closed" _closed={{ bg: "red.200" }}>
This box is closed
</Box>
```

**For TypeScript users**, you need to use the Chakra CLI to generate the types
for your custom conditions.

```sh
pnpm chakra-cli tokens src/theme/index.ts
```
20 changes: 20 additions & 0 deletions .changeset/tasty-scissors-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@chakra-ui/styled-system": minor
---

Add support for `_open` and `_closed` pseudo props for styling their respective
selectors.

- `_open`: `&[data-state=open], &[open]`
- `_closed`: `&[data-state=closed]`
- `_groupOpen`: `[data-group][data-state=open] &`
- `_groupClosed`: `[data-group][data-state=closed] &`

Extend the existing pseudo props to support new selectors`

- `_placeholder` now supports `&[data-placeholder]`
- `_placeholderShow` now supports `&[data-placeholder-shown]`
- `_fullscreen` now supports `&[data-fullscreen]`
- `_empty` now supports `&[data-empty]`
- `_expanded` now supports `&[data-state=expanded]`
- `_checked` now supports `&[data-state-checked]`
1 change: 1 addition & 0 deletions packages/cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export const themeKeyConfiguration: ThemeKeyOptions[] = [
{ key: "space", flatMap: (value) => [value, `-${value}`] },
{ key: "transition" },
{ key: "zIndices" },
{ key: "conditions", flatMap: (value) => [`_${value}`] },
]
5 changes: 4 additions & 1 deletion packages/cli/src/create-theme-typings-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,12 @@ export async function createThemeTypingsInterface(

const typingContent = `${printUnionMap(
{ ...unions, textStyles, layerStyles, colorSchemes },
strictTokenTypes,
(targetKey) => (targetKey === "conditions" ? true : strictTokenTypes),
)}
${printComponentTypes(componentTypes, strictComponentTypes)}`

const themeTypings = applyThemeTypingTemplate(typingContent, template)

return format ? formatWithPrettier(themeTypings) : themeTypings
}
9 changes: 5 additions & 4 deletions packages/cli/src/extract-property-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import { printUnionType } from "./utils/print-union-type.js"
*/
export function printUnionMap(
unions: Record<string, string[]>,
strict = false,
strict: boolean | ((targetKey: string) => boolean) = false,
) {
return Object.entries(unions)
.sort(([a], [b]) => a.localeCompare(b))
.map(
([targetKey, union]) => `${targetKey}: ${printUnionType(union, strict)};`,
)
.map(([targetKey, union]) => {
const isStrict = typeof strict === "function" ? strict(targetKey) : strict
return `${targetKey}: ${printUnionType(union, isStrict)};`
})
.join("\n")
}

Expand Down
20 changes: 20 additions & 0 deletions packages/cli/src/generate-theme-typings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const defaultRecord = {
}

const smallTheme: Record<string, unknown> = {
conditions: {
open: '[data-state="open"]',
closed: '[data-state="closed"]',
},
colors: {
niceColor: "",
suchWowColor: "",
Expand Down Expand Up @@ -106,6 +110,7 @@ describe("Theme typings", () => {
| "such.deep.color"
| (string & {})
colorSchemes: "onlyColorSchemeColor" | (string & {})
conditions: "_open" | "_closed"
fonts: "sm" | "md" | (string & {})
fontSizes: "sm" | "md" | (string & {})
fontWeights: "sm" | "md" | (string & {})
Expand All @@ -119,6 +124,7 @@ describe("Theme typings", () => {
textStyles: "small" | "large" | (string & {})
transition: "sm" | "md" | (string & {})
zIndices: "sm" | "md" | (string & {})
components: {
Button: {
sizes: "sm" | (string & {})
Expand Down Expand Up @@ -150,6 +156,7 @@ describe("Theme typings", () => {
breakpoints: string & {}
colors: string & {}
colorSchemes: string & {}
conditions: never
fonts: string & {}
fontSizes: string & {}
fontWeights: string & {}
Expand All @@ -163,6 +170,7 @@ describe("Theme typings", () => {
textStyles: string & {}
transition: string & {}
zIndices: string & {}
components: {}
}
"
Expand Down Expand Up @@ -201,6 +209,7 @@ describe("Theme typings", () => {
| "such.deep.color"
| (string & {})
colorSchemes: "onlyColorSchemeColor" | (string & {})
conditions: "_open" | "_closed"
fonts: "sm" | "md" | (string & {})
fontSizes: "sm" | "md" | (string & {})
fontWeights: "sm" | "md" | (string & {})
Expand All @@ -214,6 +223,7 @@ describe("Theme typings", () => {
textStyles: "small" | "large" | (string & {})
transition: "sm" | "md" | (string & {})
zIndices: "sm" | "md" | (string & {})
components: {
Button: {
sizes: "sm"
Expand Down Expand Up @@ -243,6 +253,7 @@ describe("Theme typings", () => {
breakpoints: "sm" | "md" | (string & {});
colors: "niceColor" | "suchWowColor" | "onlyColorSchemeColor.50" | "onlyColorSchemeColor.100" | "onlyColorSchemeColor.200" | "onlyColorSchemeColor.300" | "onlyColorSchemeColor.400" | "onlyColorSchemeColor.500" | "onlyColorSchemeColor.600" | "onlyColorSchemeColor.700" | "onlyColorSchemeColor.800" | "onlyColorSchemeColor.900" | "such.deep.color" | (string & {});
colorSchemes: "onlyColorSchemeColor" | (string & {});
conditions: "_open" | "_closed";
fonts: "sm" | "md" | (string & {});
fontSizes: "sm" | "md" | (string & {});
fontWeights: "sm" | "md" | (string & {});
Expand All @@ -256,6 +267,7 @@ describe("Theme typings", () => {
textStyles: "small" | "large" | (string & {});
transition: "sm" | "md" | (string & {});
zIndices: "sm" | "md" | (string & {});
components: {
Button: {
sizes: "sm" | (string & {});
Expand Down Expand Up @@ -332,6 +344,7 @@ describe("Theme typings", () => {
| "feedback.error"
| (string & {})
colorSchemes: string & {}
conditions: never
fonts: string & {}
fontSizes: string & {}
fontWeights: string & {}
Expand All @@ -345,6 +358,7 @@ describe("Theme typings", () => {
textStyles: string & {}
transition: string & {}
zIndices: string & {}
components: {}
}
"
Expand Down Expand Up @@ -382,6 +396,7 @@ describe("Theme typings", () => {
| "onlyColorSchemeColor.900"
| "such.deep.color"
colorSchemes: "onlyColorSchemeColor"
conditions: "_open" | "_closed"
fonts: "sm" | "md"
fontSizes: "sm" | "md"
fontWeights: "sm" | "md"
Expand All @@ -395,6 +410,7 @@ describe("Theme typings", () => {
textStyles: "small" | "large"
transition: "sm" | "md"
zIndices: "sm" | "md"
components: {
Button: {
sizes: "sm" | (string & {})
Expand Down Expand Up @@ -438,6 +454,7 @@ describe("Theme typings", () => {
| "onlyColorSchemeColor.900"
| "such.deep.color"
colorSchemes: "onlyColorSchemeColor"
conditions: "_open" | "_closed"
fonts: "sm" | "md"
fontSizes: "sm" | "md"
fontWeights: "sm" | "md"
Expand All @@ -451,6 +468,7 @@ describe("Theme typings", () => {
textStyles: "small" | "large"
transition: "sm" | "md"
zIndices: "sm" | "md"
components: {
Button: {
sizes: "sm"
Expand Down Expand Up @@ -495,6 +513,7 @@ describe("Theme typings", () => {
| "such.deep.color"
| (string & {})
colorSchemes: "onlyColorSchemeColor" | (string & {})
conditions: "_open" | "_closed"
fonts: "sm" | "md" | (string & {})
fontSizes: "sm" | "md" | (string & {})
fontWeights: "sm" | "md" | (string & {})
Expand All @@ -508,6 +527,7 @@ describe("Theme typings", () => {
textStyles: "small" | "large" | (string & {})
transition: "sm" | "md" | (string & {})
zIndices: "sm" | "md" | (string & {})
components: {
Button: {
sizes: "sm" | (string & {})
Expand Down
4 changes: 3 additions & 1 deletion packages/components/src/system/should-forward-prop.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { propNames } from "@chakra-ui/styled-system"
import { getPropNames } from "@chakra-ui/styled-system"
import { shouldForwardProp } from "./should-forward-prop"

const propNames = getPropNames({})

describe("does not forward styled-system props", () => {
test.each(propNames)("%s", (propName) => {
expect(shouldForwardProp(propName.toString())).toBe(false)
Expand Down
8 changes: 5 additions & 3 deletions packages/components/src/system/should-forward-prop.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { propNames } from "@chakra-ui/styled-system"
import { getPropNames } from "@chakra-ui/styled-system"

/**
* List of props for emotion to omit from DOM.
* It mostly consists of Chakra props
*/
const allPropNames = new Set([
...propNames,
...getPropNames({}),
"textStyle",
"layerStyle",
"apply",
Expand All @@ -32,5 +32,7 @@ const validHTMLProps = new Set([
])

export function shouldForwardProp(prop: string): boolean {
return validHTMLProps.has(prop) || !allPropNames.has(prop)
return (
(validHTMLProps.has(prop) || !allPropNames.has(prop)) && prop[0] !== "_"
)
}
8 changes: 5 additions & 3 deletions packages/components/src/system/system.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
css,
isStyleProp,
isStylePropFn,
StyleProps,
SystemStyleObject,
} from "@chakra-ui/styled-system"
Expand Down Expand Up @@ -50,8 +50,10 @@ interface GetStyleObject {
export const toCSSObject: GetStyleObject =
({ baseStyle }) =>
(props) => {
const { theme, css: cssProp, __css, sx, ...rest } = props
const [styleProps] = splitProps(rest, isStyleProp)
const { theme, css: cssProp, __css, sx, ...restProps } = props
const isStyleProp = isStylePropFn(theme)
const [styleProps] = splitProps(restProps, isStyleProp)

const finalBaseStyle = runIfFn(baseStyle, props)
const finalStyles = assignAfter(
{},
Expand Down
46 changes: 22 additions & 24 deletions packages/styled-system/src/create-theme-vars/create-theme-vars.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { isObject } from "@chakra-ui/utils/is"
import { mergeWith } from "@chakra-ui/utils/merge"
import { pseudoSelectors } from "../pseudos"
import { getPseudoSelectors } from "../pseudos"
import { calc, Operand } from "./calc"
import { cssVar } from "./css-var"
import { FlatToken, FlatTokens } from "./flatten-tokens"

export interface CreateThemeVarsOptions {
cssVarPrefix?: string
}
import { flattenTokens, FlatToken } from "./flatten-tokens"

export interface ThemeVars {
cssVars: Record<string, any>
Expand All @@ -28,16 +24,30 @@ function tokenToCssVar(token: string | number, prefix?: string) {
return cssVar(String(token).replace(/\./g, "-"), undefined, prefix)
}

export function createThemeVars(
flatTokens: FlatTokens,
options: CreateThemeVarsOptions,
) {
export function createThemeVars(theme: Record<string, any>) {
const flatTokens = flattenTokens(theme)

const cssVarPrefix = theme.config?.cssVarPrefix
const pseudoSelectors = getPseudoSelectors(theme)

let cssVars: Record<string, any> = {}
const cssMap: Record<string, any> = {}

function lookupToken(token: string, maybeToken: string) {
const scale = String(token).split(".")[0]
const withScale = [scale, maybeToken].join(".")

/** @example flatTokens['space.4'] === '16px' */
const resolvedTokenValue = flatTokens[withScale]
if (!resolvedTokenValue) return maybeToken

const { reference } = tokenToCssVar(withScale, cssVarPrefix)
return reference
}

for (const [token, tokenValue] of Object.entries<FlatToken>(flatTokens)) {
const { isSemantic, value } = tokenValue
const { variable, reference } = tokenToCssVar(token, options?.cssVarPrefix)
const { variable, reference } = tokenToCssVar(token, cssVarPrefix)

if (!isSemantic) {
if (token.startsWith("space")) {
Expand Down Expand Up @@ -65,26 +75,14 @@ export function createThemeVars(
continue
}

const lookupToken = (maybeToken: string) => {
const scale = String(token).split(".")[0]
const withScale = [scale, maybeToken].join(".")

/** @example flatTokens['space.4'] === '16px' */
const resolvedTokenValue = flatTokens[withScale]
if (!resolvedTokenValue) return maybeToken

const { reference } = tokenToCssVar(withScale, options?.cssVarPrefix)
return reference
}

const normalizedValue = isObject(value) ? value : { default: value }

cssVars = mergeWith(
cssVars,
Object.entries(normalizedValue).reduce(
(acc, [conditionAlias, conditionValue]) => {
if (!conditionValue) return acc
const tokenReference = lookupToken(`${conditionValue}`)
const tokenReference = lookupToken(token, `${conditionValue}`)

if (conditionAlias === "default") {
acc[variable] = tokenReference
Expand Down
16 changes: 9 additions & 7 deletions packages/styled-system/src/create-theme-vars/flatten-tokens.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { walkObject } from "@chakra-ui/utils/walk-object"
import { pseudoPropNames } from "../pseudos"
import { getPseudoPropNames } from "../pseudos"
import { Union } from "../utils"
import { extractSemanticTokens, extractTokens } from "./theme-tokens"

export type SemanticValue<
Conditions extends string,
Expand All @@ -26,13 +27,14 @@ export type FlattenTokensParam = {
semanticTokens?: object
}

const isSemanticCondition = (key: string) =>
pseudoPropNames.includes(key as any) || "default" === key
export function flattenTokens(theme: Record<string, any>) {
const tokens = extractTokens(theme)
const semanticTokens = extractSemanticTokens(theme)

const pseudoPropNames = getPseudoPropNames(theme)
const isSemanticCondition = (key: string) =>
pseudoPropNames.includes(key) || "default" === key

export function flattenTokens<T extends FlattenTokensParam>({
tokens,
semanticTokens,
}: T) {
const result: FlatTokens = {}

walkObject(tokens, (value, path) => {
Expand Down

0 comments on commit a89c598

Please sign in to comment.