Skip to content

Commit

Permalink
Improve forced color mode (#6595)
Browse files Browse the repository at this point in the history
* feat: add forced to color mode context

* fix: css var resolution bug

* feat: attach data-theme attr if forced

* chore: add storybook example

* fix: allow data attr to be overriden

* docs: add changeset

* fix: ts errors
  • Loading branch information
segunadebayo committed Sep 9, 2022
1 parent 1ce7017 commit c2d1c36
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 8 deletions.
19 changes: 19 additions & 0 deletions .changeset/sour-pans-confess.md
@@ -0,0 +1,19 @@
---
"@chakra-ui/styled-system": patch
"@chakra-ui/system": patch
---

Improve style resolution when components are wrapped within forced color mode
elements (`DarkMode`, `LightMode`).

We now dynamically attach the `data-theme` attribute to chakra elements when in
forced color mode.

```jsx live=false
<DarkMode>
<chakra.div bg="gray.800" padding="40px">
{/* Forced: Badge will now have `data-theme='dark' attached` */}
<Badge>Total</Badge>
</chakra.div>
</DarkMode>
```
3 changes: 3 additions & 0 deletions packages/components/color-mode/src/color-mode-provider.tsx
Expand Up @@ -110,6 +110,7 @@ export function ColorModeProvider(props: ColorModeProviderProps) {
colorMode: value ?? (resolvedValue as ColorMode),
toggleColorMode: value ? noop : toggleColorMode,
setColorMode: value ? noop : setColorMode,
forced: value !== undefined,
}),
[resolvedValue, toggleColorMode, setColorMode, value],
)
Expand All @@ -132,6 +133,7 @@ export function DarkMode(props: React.PropsWithChildren<{}>) {
colorMode: "dark",
toggleColorMode: noop,
setColorMode: noop,
forced: true,
}),
[],
)
Expand All @@ -150,6 +152,7 @@ export function LightMode(props: React.PropsWithChildren<{}>) {
colorMode: "light",
toggleColorMode: noop,
setColorMode: noop,
forced: true,
}),
[],
)
Expand Down
1 change: 1 addition & 0 deletions packages/components/color-mode/src/color-mode-types.ts
Expand Up @@ -14,6 +14,7 @@ export interface ColorModeOptions {
}

export interface ColorModeContextType {
forced?: boolean
colorMode: ColorMode
toggleColorMode: () => void
setColorMode: (value: any) => void
Expand Down
4 changes: 2 additions & 2 deletions packages/components/styled-system/src/css.ts
Expand Up @@ -4,6 +4,7 @@ import { pseudoSelectors } from "./pseudos"
import { systemProps as systemPropConfigs } from "./system"
import { StyleObjectOrFn } from "./system.types"
import { Config } from "./utils/prop-config"
import { splitByComma } from "./utils/split-by-comma"
import { CssTheme } from "./utils/types"

function isCssVar(value: string): boolean {
Expand All @@ -19,8 +20,7 @@ const resolveTokenValue = (theme: Record<string, any>, value: string) => {
const getVar = (val: string) => theme.__cssMap?.[val]?.varRef
const getValue = (val: string) => getVar(val) ?? val

const valueSplit = value.split(",").map((v) => v.trim())
const [tokenValue, fallbackValue] = valueSplit
const [tokenValue, fallbackValue] = splitByComma(value)
value = getVar(tokenValue) ?? getValue(fallbackValue) ?? getValue(value)

return value
Expand Down
27 changes: 27 additions & 0 deletions packages/components/styled-system/src/utils/split-by-comma.ts
@@ -0,0 +1,27 @@
export function splitByComma(value: string) {
const chunks = []
let chunk = ""
let inParens = false
for (let i = 0; i < value.length; i++) {
const char = value[i]
if (char === "(") {
inParens = true
chunk += char
} else if (char === ")") {
inParens = false
chunk += char
} else if (char === "," && !inParens) {
chunks.push(chunk)
chunk = ""
} else {
chunk += char
}
}

chunk = chunk.trim()
if (chunk) {
chunks.push(chunk)
}

return chunks
}
20 changes: 18 additions & 2 deletions packages/components/system/src/system.ts
@@ -1,3 +1,4 @@
import { useColorMode } from "@chakra-ui/color-mode"
import {
css,
isStyleProp,
Expand All @@ -9,6 +10,7 @@ import emotionStyled, {
CSSObject,
FunctionInterpolation,
} from "@emotion/styled"
import React from "react"
import { shouldForwardProp } from "./should-forward-prop"
import { As, ChakraComponent, ChakraProps, PropsOf } from "./system.types"
import { DOMElements } from "./system.utils"
Expand Down Expand Up @@ -77,10 +79,24 @@ export function styled<T extends As, P = {}>(
}

const styleObject = toCSSObject({ baseStyle })
return emotionStyled(
const Component = emotionStyled(
component as React.ComponentType<any>,
styledOptions,
)(styleObject) as ChakraComponent<T, P>
)(styleObject)

const chakraComponent = React.forwardRef(function ChakraComponent(
props,
ref,
) {
const { colorMode, forced } = useColorMode()
return React.createElement(Component, {
ref,
"data-theme": forced ? colorMode : undefined,
...props,
})
})

return chakraComponent as ChakraComponent<T, P>
}

export type HTMLChakraComponents = {
Expand Down
29 changes: 26 additions & 3 deletions packages/components/system/stories/system.stories.tsx
@@ -1,7 +1,12 @@
// import theme from "@chakra-ui/theme"
import * as React from "react"
import { motion } from "framer-motion"
import React from "react"
import { chakra, ThemeProvider, useStyleConfig, HTMLChakraProps } from "../src"
import {
chakra,
ThemeProvider,
useStyleConfig,
HTMLChakraProps,
DarkMode,
} from "../src"

export default {
title: "System / Core",
Expand Down Expand Up @@ -188,3 +193,21 @@ export const WithSemanticTokens = () => {
</div>
)
}

export const WithColorMode = () => {
const styles = useStyleConfig("Badge", {
variant: "solid",
colorScheme: "blue",
})
return (
<>
<chakra.span>Not forced</chakra.span>
<DarkMode>
<chakra.div bg="gray.800" padding="40px">
<chakra.p color="chakra-body-text">Forced color mode</chakra.p>
<chakra.span __css={styles}>Badge</chakra.span>
</chakra.div>
</DarkMode>
</>
)
}
9 changes: 8 additions & 1 deletion packages/components/toast/src/create-standalone-toast.tsx
Expand Up @@ -30,6 +30,7 @@ export const defaultStandaloneParam: CreateStandAloneToastParam &
toggleColorMode: () => {},
setColorMode: () => {},
defaultOptions: defaults,
forced: false,
}

export type CreateStandaloneToastReturn = {
Expand All @@ -49,8 +50,14 @@ export function createStandaloneToast({
motionVariants,
toastSpacing,
component,
forced,
}: CreateStandAloneToastParam = defaultStandaloneParam): CreateStandaloneToastReturn {
const colorModeContextValue = { colorMode, setColorMode, toggleColorMode }
const colorModeContextValue = {
colorMode,
setColorMode,
toggleColorMode,
forced,
}
const ToastContainer = () => (
<ThemeProvider theme={theme}>
<ColorModeContext.Provider value={colorModeContextValue}>
Expand Down

1 comment on commit c2d1c36

@vercel
Copy link

@vercel vercel bot commented on c2d1c36 Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.