Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions src/__tests__/vendor/tailwind/groups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { fireEvent, screen } from "@testing-library/react-native";
import { View } from "react-native-css/components";

import { render } from "./_tailwind";

const grandparentID = "grandparentID";
const parentID = "parent";
const childID = "child";

test("Styling based on parent state (group-{modifier})", async () => {
await render(
<View testID={parentID} className="group">
<View testID={childID} className="group-hover:text-white" />
</View>,
);

const parent = screen.getByTestId(parentID);
const child = screen.getByTestId(childID);

expect(parent).toHaveStyle(undefined);
expect(child).toHaveStyle(undefined);

fireEvent(parent, "hoverIn");

expect(child).toHaveStyle({ color: "#fff" });
});

test("Differentiating nested groups", async () => {
await render(
<View testID={grandparentID} className="group/grandparent">
<View testID={parentID} className="group/parent">
<View className="group-hover/grandparent:text-white" />
<View testID={childID} className="group-hover/parent:text-white" />
</View>
</View>,
);

const grandparent = screen.getByTestId(grandparentID);
const parent = screen.getByTestId(parentID);
const child = screen.getByTestId(childID);

expect(grandparent).toHaveStyle(undefined);
expect(parent).toHaveStyle(undefined);
expect(child).toHaveStyle(undefined);

fireEvent(grandparent, "hoverIn");

expect(child).toHaveStyle(undefined);

fireEvent(parent, "hoverIn");

expect(child).toHaveStyle({ color: "#fff" });
});

test("arbitrary groups - single className", async () => {
const { rerender } = await render(
<View testID={parentID} className="group">
<View testID={childID} className="group-[.test]:text-white" />
</View>,
);

const parent = screen.getByTestId(parentID);
const child = screen.getByTestId(childID);

expect(parent).toHaveStyle(undefined);
expect(child).toHaveStyle(undefined);

rerender(
<View testID={parentID} className="group test">
<View testID={childID} className="group-[.test]:text-white" />
</View>,
);

expect(child).toHaveStyle({ color: "#fff" });
});

test("arbitrary groups - multiple className", async () => {
const { rerender } = await render(
<View testID={parentID} className="group">
<View testID={childID} className="group-[.test.test2]:text-white" />
</View>,
);

const parent = screen.getByTestId(parentID);
const child = screen.getByTestId(childID);

expect(parent).toHaveStyle(undefined);
expect(child).toHaveStyle(undefined);

rerender(
<View testID={parentID} className="group test">
<View testID={childID} className="group-[.test.test2]:text-white" />
</View>,
);

expect(parent).toHaveStyle(undefined);
expect(child).toHaveStyle(undefined);

rerender(
<View testID={parentID} className="group test test2">
<View testID={childID} className="group-[.test.test2]:text-white" />
</View>,
);

expect(child).toHaveStyle({ color: "#fff" });
});

test("arbitrary groups - props", async () => {
const { rerender } = await render(
<View testID={parentID} className="group" accessibilityLabel="test">
<View
testID={childID}
className="group-[[accessibilityLabel=works]]:text-white"
/>
</View>,
);

const parent = screen.getByTestId(parentID);
const child = screen.getByTestId(childID);

expect(parent).toHaveStyle(undefined);
expect(child).toHaveStyle(undefined);

rerender(
<View testID={parentID} className="group" accessibilityLabel="works">
<View
testID={childID}
className="group-[[accessibilityLabel=works]]:text-white"
/>
</View>,
);

expect(child).toHaveStyle({ color: "#fff" });
});
66 changes: 66 additions & 0 deletions src/compiler/__tests__/selectors.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,69 @@ test(".my-class { &:is(:where(.my-parent, .my-second-parent):hover *) {} }", ()
},
]);
});

test(".group-[.test]:text-white { &:is(:where(.group):is(.test) *) {}", () => {
const result = getClassNameSelectors([
[
{
type: "class",
name: "group-[.test]:text-white",
},
{
type: "nesting",
},
{
type: "pseudo-class",
kind: "is",
selectors: [
[
{
type: "pseudo-class",
kind: "where",
selectors: [
[
{
type: "class",
name: "group",
},
],
],
},
{
type: "pseudo-class",
kind: "is",
selectors: [
[
{
type: "class",
name: "test",
},
],
],
},
{
type: "combinator",
value: "descendant",
},
{
type: "universal",
},
],
],
},
],
]);

expect(result).toStrictEqual([
{
className: "group-[.test]:text-white",
containerQuery: [
{
n: "g:group.test",
},
],
specificity: [0, 3],
type: "className",
},
]);
});
5 changes: 0 additions & 5 deletions src/compiler/selector-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,6 @@ function parseIsWhereComponents(
}
case "where":
case "is": {
// :is() and :where() need to be at the start of the selector,
if (index !== 0) {
return null;
}

// Now get the selectors inside the `is` or `where` pseudo-class
queries = component.selectors.flatMap((selector) => {
return parseIsWhereComponents(type, selector, 0, queries) ?? [];
Expand Down
19 changes: 7 additions & 12 deletions src/runtime/native/react/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function updateRules(
const inlineVariables = new Set<InlineVariable>();

let animated = false;
let pressable = false;

for (const config of state.configs) {
const source = currentProps?.[config.source];
Expand Down Expand Up @@ -94,22 +95,19 @@ export function updateRules(
}

for (let rule of styleRuleSet) {
// Even if a rule does not match, make sure we register that it could set
// a variable or container or be animated.
if (rule.v) usesVariables = true;
if (rule.c) containers ??= inheritedContainers;
if (rule.a) animated = true;

usesVariables ||= Boolean(rule.dv);

// We do this even if the rule doesn't match so we can maintain a consistent render tree
// We we need to inject React context
if (rule.a) animated = true;

if (rule.v) {
variables ??= inheritedVariables;
}

if (rule.c) {
containers ??= inheritedContainers;
activeFamily(state.ruleEffectGetter);
}

if (
Expand Down Expand Up @@ -171,7 +169,7 @@ export function updateRules(

if (process.env.NODE_ENV !== "production") {
if (isRerender) {
let pressable = activeFamily.has(state.ruleEffectGetter);
const pressable = activeFamily.has(state.ruleEffectGetter);

if (Boolean(variables) !== Boolean(state.variables)) {
console.log(
Expand All @@ -194,11 +192,7 @@ export function updateRules(
}
}

// We only track this in development
let pressable =
process.env.NODE_ENV === "production"
? undefined
: activeFamily.has(state.ruleEffectGetter);
pressable = activeFamily.has(state.ruleEffectGetter);

if (!rules.size && !state.stylesObs && !inlineVariables.size) {
return {
Expand All @@ -208,6 +202,7 @@ export function updateRules(
animated,
pressable,
variables,
containers,
};
}

Expand Down
Loading