Skip to content

Commit 0cb0e65

Browse files
feat: add inset shadow parsing support
1 parent 930095f commit 0cb0e65

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)