Skip to content

Commit a3cba9e

Browse files
feat: add inset shadow parsing support
Add runtime pattern matching for CSS inset shadows through variables. The shorthand handler now recognizes the inset keyword at the start of shadow values and normalizeInsetValue converts inset: "inset" to inset: true as React Native's boxShadow expects. Removed Tailwind-specific root variable defaults (--tw-shadow, etc.) per review - these belong in nativewind/theme.css, not the generic CSS engine.
1 parent 930095f commit a3cba9e

File tree

2 files changed

+192
-3
lines changed

2 files changed

+192
-3
lines changed

src/__tests__/native/box-shadow.test.tsx

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,169 @@ test("shadow values - multiple nested variables", () => {
9393
],
9494
});
9595
});
96+
97+
test("inset shadow - basic", () => {
98+
registerCSS(`
99+
.test { box-shadow: inset 0 2px 4px 0 #000; }
100+
`);
101+
102+
render(<View testID={testID} className="test" />);
103+
const component = screen.getByTestId(testID);
104+
105+
expect(component.props.style).toStrictEqual({
106+
boxShadow: [
107+
{
108+
inset: true,
109+
offsetX: 0,
110+
offsetY: 2,
111+
blurRadius: 4,
112+
spreadDistance: 0,
113+
color: "#000",
114+
},
115+
],
116+
});
117+
});
118+
119+
test("inset shadow - with color first", () => {
120+
registerCSS(`
121+
.test { box-shadow: inset #fb2c36 0 0 24px 0; }
122+
`);
123+
124+
render(<View testID={testID} className="test" />);
125+
const component = screen.getByTestId(testID);
126+
127+
expect(component.props.style).toStrictEqual({
128+
boxShadow: [
129+
{
130+
inset: true,
131+
color: "#fb2c36",
132+
offsetX: 0,
133+
offsetY: 0,
134+
blurRadius: 24,
135+
spreadDistance: 0,
136+
},
137+
],
138+
});
139+
});
140+
141+
test("inset shadow - without color inherits default", () => {
142+
registerCSS(`
143+
.test { box-shadow: inset 0 0 10px 5px; }
144+
`);
145+
146+
render(<View testID={testID} className="test" />);
147+
const component = screen.getByTestId(testID);
148+
149+
// Shadows without explicit color inherit the default text color (__rn-css-color)
150+
expect(component.props.style.boxShadow).toHaveLength(1);
151+
expect(component.props.style.boxShadow[0]).toMatchObject({
152+
inset: true,
153+
offsetX: 0,
154+
offsetY: 0,
155+
blurRadius: 10,
156+
spreadDistance: 5,
157+
});
158+
// Color is inherited from platform default (PlatformColor)
159+
expect(component.props.style.boxShadow[0].color).toBeDefined();
160+
});
161+
162+
test("mixed inset and regular shadows", () => {
163+
registerCSS(`
164+
.test { box-shadow: 0 4px 6px -1px #000, inset 0 2px 4px 0 #fff; }
165+
`);
166+
167+
render(<View testID={testID} className="test" />);
168+
const component = screen.getByTestId(testID);
169+
170+
expect(component.props.style).toStrictEqual({
171+
boxShadow: [
172+
{
173+
offsetX: 0,
174+
offsetY: 4,
175+
blurRadius: 6,
176+
spreadDistance: -1,
177+
color: "#000",
178+
},
179+
{
180+
inset: true,
181+
offsetX: 0,
182+
offsetY: 2,
183+
blurRadius: 4,
184+
spreadDistance: 0,
185+
color: "#fff",
186+
},
187+
],
188+
});
189+
});
190+
191+
test("inset shadow via CSS variable", () => {
192+
registerCSS(`
193+
:root { --my-shadow: inset 0 2px 4px 0 #000; }
194+
.test { box-shadow: var(--my-shadow); }
195+
`);
196+
197+
render(<View testID={testID} className="test" />);
198+
const component = screen.getByTestId(testID);
199+
200+
expect(component.props.style).toStrictEqual({
201+
boxShadow: [
202+
{
203+
inset: true,
204+
offsetX: 0,
205+
offsetY: 2,
206+
blurRadius: 4,
207+
spreadDistance: 0,
208+
color: "#000",
209+
},
210+
],
211+
});
212+
});
213+
214+
test("inset shadow via CSS variable - blur and color without spread", () => {
215+
// CSS parser normalizes omitted spread to 0, so this exercises the
216+
// [inset, offsetX, offsetY, blurRadius, spreadDistance, color] pattern
217+
registerCSS(`
218+
:root { --my-shadow: inset 0 2px 4px #000; }
219+
.test { box-shadow: var(--my-shadow); }
220+
`);
221+
222+
render(<View testID={testID} className="test" />);
223+
const component = screen.getByTestId(testID);
224+
225+
expect(component.props.style).toStrictEqual({
226+
boxShadow: [
227+
{
228+
inset: true,
229+
offsetX: 0,
230+
offsetY: 2,
231+
blurRadius: 4,
232+
spreadDistance: 0,
233+
color: "#000",
234+
},
235+
],
236+
});
237+
});
238+
239+
test("shadow values from CSS variable are resolved", () => {
240+
registerCSS(`
241+
:root {
242+
--my-shadow: 0 0 0 0 #0000;
243+
}
244+
.test { box-shadow: var(--my-shadow); }
245+
`);
246+
247+
render(<View testID={testID} className="test" />);
248+
const component = screen.getByTestId(testID);
249+
250+
expect(component.props.style).toStrictEqual({
251+
boxShadow: [
252+
{
253+
offsetX: 0,
254+
offsetY: 0,
255+
blurRadius: 0,
256+
spreadDistance: 0,
257+
color: "#0000",
258+
},
259+
],
260+
});
261+
});

src/native/styles/shorthands/box-shadow.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,23 @@ const offsetX = ["offsetX", "number"] as const;
99
const offsetY = ["offsetY", "number"] as const;
1010
const blurRadius = ["blurRadius", "number"] as const;
1111
const spreadDistance = ["spreadDistance", "number"] as const;
12-
// const inset = ["inset", "string"] as const;
12+
// Match the literal string "inset" - the array type checks if value is in array
13+
const inset = ["inset", ["inset"]] as const;
1314

1415
const handler = shorthandHandler(
1516
[
17+
// Standard patterns (without inset)
1618
[offsetX, offsetY, blurRadius, spreadDistance],
1719
[offsetX, offsetY, blurRadius, spreadDistance, color],
1820
[color, offsetX, offsetY],
1921
[color, offsetX, offsetY, blurRadius, spreadDistance],
2022
[offsetX, offsetY, color],
2123
[offsetX, offsetY, blurRadius, color],
24+
// Inset patterns - "inset" keyword at the beginning
25+
[inset, offsetX, offsetY, blurRadius, spreadDistance],
26+
[inset, offsetX, offsetY, blurRadius, spreadDistance, color],
27+
[inset, offsetX, offsetY, blurRadius, color],
28+
[inset, color, offsetX, offsetY, blurRadius, spreadDistance],
2229
],
2330
[],
2431
"object",
@@ -41,8 +48,10 @@ export const boxShadow: StyleFunctionResolver = (
4148
if (shadows === undefined) {
4249
return;
4350
} else {
44-
return omitTransparentShadows(
45-
handler(resolveValue, shadows, get, options),
51+
return normalizeInsetValue(
52+
omitTransparentShadows(
53+
handler(resolveValue, shadows, get, options),
54+
),
4655
);
4756
}
4857
})
@@ -69,3 +78,17 @@ function omitTransparentShadows(style: unknown) {
6978

7079
return style;
7180
}
81+
82+
/**
83+
* Convert inset: "inset" to inset: true for React Native boxShadow.
84+
*
85+
* The shorthand handler matches the literal "inset" string and assigns it as the value.
86+
* React Native's boxShadow expects inset to be a boolean.
87+
*/
88+
function normalizeInsetValue(style: unknown) {
89+
if (typeof style === "object" && style && "inset" in style) {
90+
return { ...style, inset: true };
91+
}
92+
93+
return style;
94+
}

0 commit comments

Comments
 (0)