Skip to content

Commit

Permalink
fix: dark mode variables
Browse files Browse the repository at this point in the history
  • Loading branch information
marklawlor committed Aug 1, 2023
1 parent fe3b9bd commit a116367
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 125 deletions.
15 changes: 12 additions & 3 deletions packages/nativewind/src/__tests__/custom-theme.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { View } from "react-native";
import { createMockComponent, renderTailwind } from "../test-utils";
import { resetStyles } from "react-native-css-interop/testing-library";
import { screen } from "@testing-library/react-native";
import { act, screen } from "@testing-library/react-native";
import { StyleSheet } from "react-native-css-interop";

const testID = "react-native-css-interop";
const A = createMockComponent(View);
Expand All @@ -19,7 +20,11 @@ test("Using css variables", async () => {
@layer base {
:root {
--color-primary: 255 115 179;
--color-secondary: 111 114 185;
}
@media (prefers-color-scheme: dark) {
:root {
--color-primary: 155 100 255;
}
}
}
`,
Expand All @@ -35,5 +40,9 @@ test("Using css variables", async () => {

const component = screen.getByTestId(testID);

expect(component).toHaveStyle({ color: "rgba(0, 0, 0, 1)" });
expect(component).toHaveStyle({ color: "rgba(255,115,179,1)" });

act(() => StyleSheet.setColorScheme("dark"));

expect(component).toHaveStyle({ color: "rgba(155,100,255,1)" });
});
81 changes: 47 additions & 34 deletions packages/nativewind/src/__tests__/dark-mode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,42 @@ test("darkMode: media", async () => {
expect(component).toHaveStyle({ color: "rgba(0, 0, 0, 1)" });
});

test("darkMode: class", async () => {
await renderTailwind(<A testID={testID} className="dark:text-black" />, {
base: true,
config: {
darkMode: "class",
test("darkMode: media variable switching", async () => {
await renderTailwind(
<A testID={testID} className="text-[color:rgb(var(--color))]" />,
{
css: `
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--color: 255 115 179;
}
@media (prefers-color-scheme: dark) {
:root {
--color: 155 100 255;
}
}
}`,
},
});
);

const component = screen.getByTestId(testID);

expect(component).toHaveStyle({});
expect(component).toHaveStyle({ color: "rgb(255,115,179)" });

act(() => StyleSheet.setColorScheme("dark"));

expect(component).toHaveStyle({ color: "rgba(0, 0, 0, 1)" });
expect(component).toHaveStyle({ color: "rgb(155,100,255)" });
});

test("darkMode: custom class", async () => {
test("darkMode: class", async () => {
await renderTailwind(<A testID={testID} className="dark:text-black" />, {
base: true,
config: {
darkMode: ["class", ".dark-mode"],
darkMode: "class",
},
});

Expand All @@ -60,37 +74,36 @@ test("darkMode: custom class", async () => {
expect(component).toHaveStyle({ color: "rgba(0, 0, 0, 1)" });
});

test.only("darkMode: root variables", async () => {
await renderTailwind(<A testID={testID} className="dark:text-primary" />, {
css: `
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
@media(prefers-color-scheme: dark) {
:root {
--color-primary: 255 115 179;
--color-secondary: 111 114 185;
test.only("darkMode: class variable switching", async () => {
await renderTailwind(
<A testID={testID} className="text-[color:rgb(var(--color))]" />,
{
css: `
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--color: 255 115 179;
}
.dark {
--color: 155 100 255;
}
}
}
}
`,
config: {
theme: {
colors: {
primary: "rgb(var(--color-primary) / <alpha-value>)",
secondary: "rgb(var(--color-secondary) / <alpha-value>)",
},
`,
config: {
darkMode: "class",
},
},
});
);

const component = screen.getByTestId(testID);

expect(component).toHaveStyle({});
expect(component).toHaveStyle({ color: "rgb(255,115,179)" });

act(() => StyleSheet.setColorScheme("dark"));

expect(component).toHaveStyle({ color: "rgba(0, 0, 0, 1)" });
expect(component).toHaveStyle({ color: "rgb(155,100,255)" });
});
16 changes: 15 additions & 1 deletion packages/nativewind/src/tailwind/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Config } from "tailwindcss";
import plugin from "tailwindcss/plugin";

import { darkModeAtRule } from "./dark-mode";
import { ContentConfig } from "tailwindcss/types/config";

export default function nativewindPreset() {
const preset: Config = {
Expand All @@ -27,12 +28,25 @@ export default function nativewindPreset() {
},
},
},
plugins: [platforms, darkModeAtRule],
plugins: [forceDark, platforms, darkModeAtRule],
};

return preset;
}

/**
* The native module requires the `.dark` selector to pickup darkMode variables
* when using darkMode: 'class'
*
* If the user never uses the word 'dark' the selector will never be processed
* This is an edge, but one we often encounter in testing (where .dark)
* will only contain CSS variables and never referenced directly
*/
const forceDark = plugin(function ({ config }) {
const content = config<Extract<ContentConfig, { files: any }>>("content");
content.files.push({ raw: "dark" });
});

const platforms = plugin(function ({ addVariant }) {
const nativePlatforms = ["android", "ios", "windows", "macos"];

Expand Down
2 changes: 1 addition & 1 deletion packages/nativewind/src/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function renderTailwind<T extends { className: string }>(
}),
]).process(css, { from: undefined });

console.log(output);
// console.log(output);

registerCSS(output, cssToReactNativeRuntimeOptions);

Expand Down
29 changes: 9 additions & 20 deletions packages/react-native-css-interop/src/css-to-rn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ function extractRule(
rule: Rule | CSSInteropAtRule,
extractOptions: ExtractRuleOptions,
parseOptions: CssToReactNativeRuntimeOptions,
partialStyle: Partial<ExtractedStyle> = {},
) {
// Check the rule's type to determine which extraction function to call
switch (rule.type) {
Expand All @@ -141,7 +142,7 @@ function extractRule(
if (rule.value.declarations) {
setStyleForSelectorList(
{
...extractOptions.style,
...partialStyle,
...getExtractedStyle(rule.value.declarations, parseOptions),
},
rule.value.selectors,
Expand Down Expand Up @@ -215,16 +216,9 @@ function extractMedia(
return;
}

const newExtractOptions: ExtractRuleOptions = {
...extractOptions,
style: {
media,
},
};

// Iterate over all rules in the mediaRule and extract their styles using the updated ExtractRuleOptions
for (const rule of mediaRule.rules) {
extractRule(rule, newExtractOptions, parseOptions);
extractRule(rule, extractOptions, parseOptions, { media });
}
}

Expand All @@ -238,22 +232,16 @@ function extractedContainer(
extractOptions: ExtractRuleOptions,
parseOptions: CssToReactNativeRuntimeOptions,
) {
// Create a new ExtractRuleOptions object with the updated container query information
const newExtractOptions: ExtractRuleOptions = {
...extractOptions,
style: {
// Iterate over all rules inside the containerRule and extract their styles using the updated ExtractRuleOptions
for (const rule of containerRule.rules) {
extractRule(rule, extractOptions, parseOptions, {
containerQuery: [
{
name: containerRule.name,
condition: containerRule.condition,
},
],
},
};

// Iterate over all rules inside the containerRule and extract their styles using the updated ExtractRuleOptions
for (const rule of containerRule.rules) {
extractRule(rule, newExtractOptions, parseOptions);
});
}
}

Expand Down Expand Up @@ -293,7 +281,7 @@ function setStyleForSelectorList(
(!options.darkMode || options.darkMode?.type === "media")
) {
// You can only have 1 media condition
if (style.media.length > 1) {
if (style.media.length !== 1) {
continue;
}

Expand All @@ -320,6 +308,7 @@ function setStyleForSelectorList(
Object.assign<ExtractRuleOptions, Partial<ExtractRuleOptions>>(options, {
[key]: style.variables,
});

continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function normalizeSelectors(
for (let selector of selectorList) {
// Ignore `:is()`, and just process its selectors
if (isIsPseudoClass(selector)) {
console.log(selector[0].selectors);
normalizeSelectors(selector[0].selectors, options, normalizedSelectors);
continue;
}
Expand All @@ -39,7 +40,7 @@ export function normalizeSelectors(
continue;
}

// Matches: :root .dark {}
// Matches: .dark {}
if (isRootDarkVariableSelector(selector, options)) {
normalizedSelectors.push({
type: "variables",
Expand Down Expand Up @@ -210,20 +211,15 @@ function isDefaultVariableSelector([first, second]: Selector) {

// Matches: :root .dark {}
function isRootDarkVariableSelector(
[first, second, third]: Selector,
[first, second]: Selector,
options: ExtractRuleOptions,
) {
return (
options.darkMode?.type === "class" &&
first &&
second &&
third &&
first.type === "pseudo-class" &&
first.kind === "root" &&
second.type === "combinator" &&
second.value === "descendant" &&
third.type === "class" &&
third.name === options.darkMode.value
!second &&
first.type === "class" &&
first.name === options.darkMode.value
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,12 @@ function parseUnparsed(
}
case "function": {
switch (tokenOrValue.value.name) {
case "rgb":
tokenOrValue.value.name = "rgb";
return unparsedFunction(tokenOrValue, options);
case "hsl":
tokenOrValue.value.name = "hsla";
return unparsedFunction(tokenOrValue, options);
case "translate":
return unparsedKnownShorthand(
{
Expand All @@ -1521,7 +1527,7 @@ function parseUnparsed(
case "platformColor":
case "getPixelSizeForLayoutSize":
case "roundToNearestPixel":
case "getfontScale":
case "getFontScale":
case "getPixelRatio":
return unparsedFunction(tokenOrValue, options);
case "hairlineWidth":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ import { flattenStyleProps } from "./flatten-style";
import { ContainerContext, globalStyles, styleMetaMap } from "./globals";
import { useInteractionHandlers, useInteractionSignals } from "./interaction";
import { useComputation } from "../shared/signals";
import { StyleSheet, VariableContext, useVariables } from "./stylesheet";
import {
StyleSheet,
VariableContext,
defaultVariables,
rootVariables,
} from "./stylesheet";
import { DevHotReloadSubscription } from "../../shared";

type CSSInteropWrapperProps = {
Expand Down Expand Up @@ -148,10 +153,27 @@ const CSSInteropWrapper = forwardRef(function CSSInteropWrapper(
ref,
) {
const rerender = useRerender();
const inheritedVariables = useVariables();
const inheritedContainers = useContext(ContainerContext);
const interaction = useInteractionSignals();

const $variables = useContext(VariableContext);

const inheritedVariables = useComputation(
() => {
// $variables will be null if this is a top-level component
if ($variables === null) {
return rootVariables.get();
} else {
return {
...$variables,
...defaultVariables.get(),
};
}
},
[$variables],
rerender,
);

/**
* If the development environment is enabled, we should rerender all components if the StyleSheet updates.
* This is because things like :root variables may have updated.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,19 @@ function extractValue(
spreadCallbackArgs: true,
});
}
case "rgb": {
return createRuntimeFunction(value, flatStyle, flatStyleMeta, options, {
joinArgs: false,
callback(value: any) {
const args = value.slice(4, -1).split(",");

if (args.length === 4) {
return `rgba(${args.join(",")})`;
}
return value;
},
});
}
default: {
return createRuntimeFunction(value, flatStyle, flatStyleMeta, options);
}
Expand Down
Loading

0 comments on commit a116367

Please sign in to comment.