@mgcrea/react-native-tailwind 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  [![npm version](https://img.shields.io/npm/v/@mgcrea/react-native-tailwind.svg?style=for-the-badge)](https://www.npmjs.com/package/@mgcrea/react-native-tailwind)
6
6
  [![npm downloads](https://img.shields.io/npm/dt/@mgcrea/react-native-tailwind.svg?style=for-the-badge)](https://www.npmjs.com/package/@mgcrea/react-native-tailwind)
7
7
  [![license](https://img.shields.io/github/license/mgcrea/react-native-tailwind.svg?style=for-the-badge)](https://github.com/mgcrea/react-native-tailwind/blob/main/LICENSE)
8
+ [![build status](https://img.shields.io/github/actions/workflow/status/mgcrea/react-native-tailwind/main.yaml?style=for-the-badge&branch=master)](https://github.com/mgcrea/react-native-tailwind/actions/workflows/main.yaml)
8
9
 
9
10
  </div>
10
11
 
@@ -121,292 +122,6 @@ const _twStyles = StyleSheet.create({
121
122
  });
122
123
  ```
123
124
 
124
- ## API Reference
125
-
126
- ### Spacing
127
-
128
- **Margin & Padding:**
129
-
130
- - `m-{size}`, `p-{size}` — All sides
131
- - `mx-{size}`, `my-{size}`, `px-{size}`, `py-{size}` — Horizontal/vertical
132
- - `mt-{size}`, `mr-{size}`, `mb-{size}`, `ml-{size}` — Directional margin
133
- - `pt-{size}`, `pr-{size}`, `pb-{size}`, `pl-{size}` — Directional padding
134
- - `gap-{size}` — Gap between flex items
135
-
136
- **Available sizes:** `0`, `0.5`, `1`, `1.5`, `2`, `2.5`, `3`, `3.5`, `4`, `5`, `6`, `7`, `8`, `9`, `10`, `11`, `12`, `14`, `16`, `20`, `24`, `28`, `32`, `36`, `40`, `44`, `48`, `52`, `56`, `60`, `64`, `72`, `80`, `96`
137
-
138
- **Arbitrary values:** `m-[16px]`, `p-[20]`, `mx-[24px]`, `gap-[12px]` — Custom spacing values (px only)
139
-
140
- ### Layout
141
-
142
- **Flexbox:**
143
-
144
- - `flex`, `flex-1`, `flex-auto`, `flex-none` — Flex sizing
145
- - `flex-row`, `flex-row-reverse`, `flex-col`, `flex-col-reverse` — Direction
146
- - `flex-wrap`, `flex-wrap-reverse`, `flex-nowrap` — Wrapping
147
- - `items-start`, `items-end`, `items-center`, `items-baseline`, `items-stretch` — Align items
148
- - `justify-start`, `justify-end`, `justify-center`, `justify-between`, `justify-around`, `justify-evenly` — Justify content
149
- - `self-auto`, `self-start`, `self-end`, `self-center`, `self-stretch`, `self-baseline` — Align self
150
- - `grow`, `grow-0`, `shrink`, `shrink-0` — Flex grow/shrink
151
-
152
- **Other:**
153
-
154
- - `absolute`, `relative` — Position
155
- - `overflow-hidden`, `overflow-visible`, `overflow-scroll` — Overflow
156
- - `flex`, `hidden` — Display
157
-
158
- ### Colors
159
-
160
- - `bg-{color}-{shade}` — Background color
161
- - `text-{color}-{shade}` — Text color
162
- - `border-{color}-{shade}` — Border color
163
-
164
- **Available colors:** `gray`, `red`, `blue`, `green`, `yellow`, `purple`, `pink`, `orange`, `indigo`, `white`, `black`, `transparent`
165
-
166
- **Available shades:** `50`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`
167
-
168
- > **Note:** You can extend the color palette with custom colors via `tailwind.config.*` — see [Custom Colors](#custom-colors)
169
-
170
- **Opacity Modifiers:**
171
-
172
- Apply transparency to any color using the `/` operator with a percentage value (0-100):
173
-
174
- ```tsx
175
- <View className="bg-black/50 p-4"> {/* 50% opacity black background */}
176
- <Text className="text-gray-900/80"> {/* 80% opacity gray text */}
177
- Semi-transparent content
178
- </Text>
179
- <View className="border-2 border-red-500/30" /> {/* 30% opacity red border */}
180
- </View>
181
- ```
182
-
183
- - Works with all color types: `bg-*`, `text-*`, `border-*`
184
- - Supports preset colors: `bg-blue-500/75`, `text-red-600/50`
185
- - Supports arbitrary colors: `bg-[#ff0000]/40`, `text-[#3B82F6]/90`
186
- - Supports custom colors: `bg-primary/60`, `text-brand/80`
187
- - Uses React Native's 8-digit hex format: `#RRGGBBAA`
188
-
189
- **Examples:**
190
-
191
- ```tsx
192
- // Background opacity
193
- <View className="bg-white/90" /> // #FFFFFFE6
194
- <View className="bg-blue-500/50" /> // #3B82F680
195
-
196
- // Text opacity
197
- <Text className="text-black/70" /> // #000000B3
198
- <Text className="text-gray-900/60" /> // #11182799
199
-
200
- // Border opacity
201
- <View className="border-2 border-purple-500/40" /> // #A855F766
202
-
203
- // Arbitrary colors with opacity
204
- <View className="bg-[#ff6b6b]/25" /> // #FF6B6B40
205
-
206
- // Edge cases
207
- <View className="bg-black/0" /> // Fully transparent
208
- <View className="bg-blue-500/100" /> // Fully opaque
209
- <View className="bg-transparent/50" /> // Remains transparent
210
- ```
211
-
212
- ### Typography
213
-
214
- **Font Size:**
215
-
216
- `text-xs`, `text-sm`, `text-base`, `text-lg`, `text-xl`, `text-2xl`, `text-3xl`, `text-4xl`, `text-5xl`, `text-6xl`, `text-7xl`, `text-8xl`, `text-9xl`
217
-
218
- **Font Weight:**
219
-
220
- `font-thin`, `font-extralight`, `font-light`, `font-normal`, `font-medium`, `font-semibold`, `font-bold`, `font-extrabold`, `font-black`
221
-
222
- **Other:**
223
-
224
- - `italic`, `not-italic` — Font style
225
- - `text-left`, `text-center`, `text-right`, `text-justify` — Text alignment
226
- - `underline`, `line-through`, `no-underline` — Text decoration
227
- - `uppercase`, `lowercase`, `capitalize`, `normal-case` — Text transform
228
- - `leading-none`, `leading-tight`, `leading-snug`, `leading-normal`, `leading-relaxed`, `leading-loose` — Line height
229
-
230
- ### Borders
231
-
232
- **Width:**
233
-
234
- - `border`, `border-0`, `border-2`, `border-4`, `border-8` — All sides
235
- - `border-t`, `border-r`, `border-b`, `border-l` (with variants `-0`, `-2`, `-4`, `-8`) — Directional
236
- - `border-[8px]`, `border-t-[12px]` — Arbitrary values
237
-
238
- **Radius:**
239
-
240
- - `rounded-none`, `rounded-sm`, `rounded`, `rounded-md`, `rounded-lg`, `rounded-xl`, `rounded-2xl`, `rounded-3xl`, `rounded-full` — All corners
241
- - `rounded-t`, `rounded-r`, `rounded-b`, `rounded-l` (with size variants) — Directional
242
- - `rounded-tl`, `rounded-tr`, `rounded-bl`, `rounded-br` (with size variants) — Individual corners
243
- - `rounded-[12px]`, `rounded-t-[8px]`, `rounded-tl-[16px]` — Arbitrary values
244
-
245
- **Style:**
246
-
247
- `border-solid`, `border-dotted`, `border-dashed`
248
-
249
- ### Shadows & Elevation
250
-
251
- Apply platform-specific shadows and elevation to create depth and visual hierarchy. Automatically uses iOS shadow properties or Android elevation based on the platform:
252
-
253
- **Available shadow sizes:**
254
-
255
- - `shadow-sm` — Subtle shadow
256
- - `shadow` — Default shadow
257
- - `shadow-md` — Medium shadow
258
- - `shadow-lg` — Large shadow
259
- - `shadow-xl` — Extra large shadow
260
- - `shadow-2xl` — Extra extra large shadow
261
- - `shadow-none` — Remove shadow
262
-
263
- **Platform Differences:**
264
-
265
- | Platform | Properties Used | Example Output |
266
- |----------|----------------|----------------|
267
- | **iOS** | `shadowColor`, `shadowOffset`, `shadowOpacity`, `shadowRadius` | Native iOS shadow rendering |
268
- | **Android** | `elevation` | Native Android elevation (Material Design) |
269
-
270
- **Examples:**
271
-
272
- ```tsx
273
- // Card with shadow
274
- <View className="bg-white rounded-lg shadow-lg p-6 m-4">
275
- <Text className="text-xl font-bold">Card Title</Text>
276
- <Text className="text-gray-600">Card with large shadow</Text>
277
- </View>
278
-
279
- // Button with subtle shadow
280
- <Pressable className="bg-blue-500 shadow-sm rounded-lg px-6 py-3">
281
- <Text className="text-white">Press Me</Text>
282
- </Pressable>
283
-
284
- // Different shadow sizes
285
- <View className="shadow-sm p-4">Subtle</View>
286
- <View className="shadow p-4">Default</View>
287
- <View className="shadow-md p-4">Medium</View>
288
- <View className="shadow-lg p-4">Large</View>
289
- <View className="shadow-xl p-4">Extra Large</View>
290
- <View className="shadow-2xl p-4">2X Large</View>
291
-
292
- // Remove shadow
293
- <View className="shadow-lg md:shadow-none p-4">
294
- Conditional shadow removal
295
- </View>
296
- ```
297
-
298
- **iOS Shadow Values:**
299
-
300
- | Class | shadowOpacity | shadowRadius | shadowOffset |
301
- |-------|---------------|--------------|--------------|
302
- | `shadow-sm` | 0.05 | 1 | { width: 0, height: 1 } |
303
- | `shadow` | 0.1 | 2 | { width: 0, height: 1 } |
304
- | `shadow-md` | 0.15 | 4 | { width: 0, height: 3 } |
305
- | `shadow-lg` | 0.2 | 8 | { width: 0, height: 6 } |
306
- | `shadow-xl` | 0.25 | 12 | { width: 0, height: 10 } |
307
- | `shadow-2xl` | 0.3 | 24 | { width: 0, height: 20 } |
308
-
309
- **Android Elevation Values:**
310
-
311
- | Class | elevation |
312
- |-------|-----------|
313
- | `shadow-sm` | 1 |
314
- | `shadow` | 2 |
315
- | `shadow-md` | 4 |
316
- | `shadow-lg` | 8 |
317
- | `shadow-xl` | 12 |
318
- | `shadow-2xl` | 16 |
319
-
320
- > **Note:** All shadow parsing happens at compile-time with zero runtime overhead. The platform detection uses React Native's `Platform.select()` API.
321
-
322
- ### Aspect Ratio
323
-
324
- Control the aspect ratio of views using preset or arbitrary values. Requires React Native 0.71+:
325
-
326
- **Preset values:**
327
-
328
- - `aspect-auto` — Remove aspect ratio constraint
329
- - `aspect-square` — 1:1 aspect ratio
330
- - `aspect-video` — 16:9 aspect ratio
331
-
332
- **Arbitrary values:**
333
-
334
- Use `aspect-[width/height]` for custom ratios:
335
-
336
- ```tsx
337
- <View className="aspect-[4/3]" /> // 4:3 ratio (1.333...)
338
- <View className="aspect-[16/9]" /> // 16:9 ratio (1.778...)
339
- <View className="aspect-[21/9]" /> // 21:9 ultrawide
340
- <View className="aspect-[9/16]" /> // 9:16 portrait
341
- <View className="aspect-[3/2]" /> // 3:2 ratio (1.5)
342
- ```
343
-
344
- **Examples:**
345
-
346
- ```tsx
347
- // Square image container
348
- <View className="w-full aspect-square bg-gray-200">
349
- <Image source={avatar} className="w-full h-full" />
350
- </View>
351
-
352
- // Video player container (16:9)
353
- <View className="w-full aspect-video bg-black">
354
- <VideoPlayer />
355
- </View>
356
-
357
- // Instagram-style square grid
358
- <View className="flex-row flex-wrap gap-2">
359
- {photos.map((photo) => (
360
- <View key={photo.id} className="w-[32%] aspect-square">
361
- <Image source={photo.uri} className="w-full h-full rounded" />
362
- </View>
363
- ))}
364
- </View>
365
-
366
- // Custom aspect ratio for wide images
367
- <View className="w-full aspect-[21/9] rounded-lg overflow-hidden">
368
- <Image source={banner} className="w-full h-full" resizeMode="cover" />
369
- </View>
370
-
371
- // Portrait orientation
372
- <View className="h-full aspect-[9/16]">
373
- <Story />
374
- </View>
375
-
376
- // Remove aspect ratio constraint
377
- <View className="aspect-square md:aspect-auto">
378
- Responsive aspect ratio
379
- </View>
380
- ```
381
-
382
- **Common Aspect Ratios:**
383
-
384
- | Ratio | Class | Decimal | Use Case |
385
- |-------|-------|---------|----------|
386
- | 1:1 | `aspect-square` | 1.0 | Profile pictures, thumbnails |
387
- | 16:9 | `aspect-video` | 1.778 | Videos, landscape photos |
388
- | 4:3 | `aspect-[4/3]` | 1.333 | Standard photos |
389
- | 3:2 | `aspect-[3/2]` | 1.5 | Classic photography |
390
- | 21:9 | `aspect-[21/9]` | 2.333 | Ultrawide/cinematic |
391
- | 9:16 | `aspect-[9/16]` | 0.5625 | Stories, vertical video |
392
-
393
- > **Note:** The aspect ratio is calculated as `width / height`. When combined with `w-full`, the height will be automatically calculated to maintain the ratio.
394
-
395
- ### Sizing
396
-
397
- - `w-{size}`, `h-{size}` — Width/height
398
- - `min-w-{size}`, `min-h-{size}` — Min width/height
399
- - `max-w-{size}`, `max-h-{size}` — Max width/height
400
-
401
- **Available sizes:**
402
-
403
- - **Numeric:** `0`-`96` (same as spacing scale)
404
- - **Fractional:** `1/2`, `1/3`, `2/3`, `1/4`, `3/4`, `1/5`, `2/5`, `3/5`, `4/5`, `1/6`, `2/6`, `3/6`, `4/6`, `5/6`
405
- - **Special:** `full` (100%), `auto`
406
- - **Arbitrary:** `w-[123px]`, `h-[50%]`, `min-w-[200px]`, `max-h-[80%]`
407
-
408
- > **Note:** Arbitrary sizing supports pixel values (`[123px]` or `[123]`) and percentages (`[50%]`). Other units (`rem`, `em`, `vh`, `vw`) are not supported in React Native.
409
-
410
125
  ## Usage Examples
411
126
 
412
127
  ### Basic Example
@@ -662,10 +377,10 @@ The package exports an enhanced `TextInput` component that:
662
377
 
663
378
  **Supported Modifiers by Component:**
664
379
 
665
- | Component | Supported Modifiers | Notes |
666
- | ---------------------- | -------------------------------------------- | ------------------------------------------ |
667
- | `Pressable` (enhanced) | `active:`, `hover:`, `focus:`, `disabled:` | Use `@mgcrea/react-native-tailwind` export |
668
- | `TextInput` (enhanced) | `focus:`, `disabled:` | Use `@mgcrea/react-native-tailwind` export |
380
+ | Component | Supported Modifiers | Notes |
381
+ | ---------------------- | ------------------------------------------ | ------------------------------------------ |
382
+ | `Pressable` (enhanced) | `active:`, `hover:`, `focus:`, `disabled:` | Use `@mgcrea/react-native-tailwind` export |
383
+ | `TextInput` (enhanced) | `focus:`, `disabled:` | Use `@mgcrea/react-native-tailwind` export |
669
384
 
670
385
  #### Disabled Modifier (Pressable & TextInput)
671
386
 
@@ -682,9 +397,7 @@ export function SubmitButton({ isLoading }) {
682
397
  disabled={isLoading}
683
398
  className="bg-blue-500 active:bg-blue-700 disabled:bg-gray-400 p-4 rounded-lg"
684
399
  >
685
- <Text className="text-white font-semibold">
686
- {isLoading ? "Loading..." : "Submit"}
687
- </Text>
400
+ <Text className="text-white font-semibold">{isLoading ? "Loading..." : "Submit"}</Text>
688
401
  </Pressable>
689
402
  );
690
403
  }
@@ -870,47 +583,295 @@ export function Button({ title, onPress, style, containerStyle }: ButtonProps) {
870
583
 
871
584
  This pattern allows you to build component libraries with optimized default styling while still supporting full customization.
872
585
 
873
- ## Architecture
586
+ ## API Reference
587
+
588
+ ### Spacing
874
589
 
875
- ### Compile-Time Transformation
590
+ **Margin & Padding:**
876
591
 
877
- The Babel plugin performs all transformations during build time, ensuring zero runtime overhead:
592
+ - `m-{size}`, `p-{size}` All sides
593
+ - `mx-{size}`, `my-{size}`, `px-{size}`, `py-{size}` — Horizontal/vertical
594
+ - `mt-{size}`, `mr-{size}`, `mb-{size}`, `ml-{size}` — Directional margin
595
+ - `pt-{size}`, `pr-{size}`, `pb-{size}`, `pl-{size}` — Directional padding
596
+ - `gap-{size}` — Gap between flex items
878
597
 
879
- 1. **AST Transformation** Visits JSX elements and finds `className` attributes
880
- 2. **Static Analysis** — Only processes string literals (dynamic values produce warnings)
881
- 3. **Style Registry** — Collects all className → style mappings per file
882
- 4. **Code Generation** — Injects `StyleSheet.create()` at end of file
883
- 5. **Import Management** — Adds `StyleSheet` import if needed
598
+ **Available sizes:** `0`, `0.5`, `1`, `1.5`, `2`, `2.5`, `3`, `3.5`, `4`, `5`, `6`, `7`, `8`, `9`, `10`, `11`, `12`, `14`, `16`, `20`, `24`, `28`, `32`, `36`, `40`, `44`, `48`, `52`, `56`, `60`, `64`, `72`, `80`, `96`
884
599
 
885
- ### Performance Characteristics
600
+ **Arbitrary values:** `m-[16px]`, `p-[20]`, `mx-[24px]`, `gap-[12px]` — Custom spacing values (px only)
886
601
 
887
- | Metric | Value |
888
- | ---------------- | ------------------ |
889
- | Runtime Overhead | 0ms (compile-time) |
890
- | Bundle Size | ~4KB typical |
891
- | Build Time | +50-200ms |
602
+ ### Layout
892
603
 
893
- **Why it's fast:**
604
+ **Flexbox:**
894
605
 
895
- - All className parsing happens at build time
896
- - 🎯 Uses React Native's optimized `StyleSheet.create` API
897
- - 📦 Tree shakingonly includes styles actually used
898
- - 🔄 Deduplication identical styles reused across components
606
+ - `flex`, `flex-1`, `flex-auto`, `flex-none` Flex sizing
607
+ - `flex-row`, `flex-row-reverse`, `flex-col`, `flex-col-reverse` — Direction
608
+ - `flex-wrap`, `flex-wrap-reverse`, `flex-nowrap`Wrapping
609
+ - `items-start`, `items-end`, `items-center`, `items-baseline`, `items-stretch` Align items
610
+ - `justify-start`, `justify-end`, `justify-center`, `justify-between`, `justify-around`, `justify-evenly` — Justify content
611
+ - `self-auto`, `self-start`, `self-end`, `self-center`, `self-stretch`, `self-baseline` — Align self
612
+ - `grow`, `grow-0`, `shrink`, `shrink-0` — Flex grow/shrink
899
613
 
900
- ## Limitations
614
+ **Other:**
901
615
 
902
- **Dynamic class names are not supported** The Babel plugin can only transform static string literals:
616
+ - `absolute`, `relative`Position
617
+ - `overflow-hidden`, `overflow-visible`, `overflow-scroll` — Overflow
618
+ - `flex`, `hidden` — Display
619
+
620
+ ### Colors
621
+
622
+ - `bg-{color}-{shade}` — Background color
623
+ - `text-{color}-{shade}` — Text color
624
+ - `border-{color}-{shade}` — Border color
625
+
626
+ **Available colors:** `gray`, `red`, `blue`, `green`, `yellow`, `purple`, `pink`, `orange`, `indigo`, `white`, `black`, `transparent`
627
+
628
+ **Available shades:** `50`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`
629
+
630
+ > **Note:** You can extend the color palette with custom colors via `tailwind.config.*` — see [Custom Colors](#custom-colors)
631
+
632
+ **Opacity Modifiers:**
633
+
634
+ Apply transparency to any color using the `/` operator with a percentage value (0-100):
903
635
 
904
636
  ```tsx
905
- // This will NOT work
906
- const spacing = 4;
907
- <View className={`m-${spacing} p-2`} />
637
+ <View className="bg-black/50 p-4">
638
+ {" "}
639
+ {/* 50% opacity black background */}
640
+ <Text className="text-gray-900/80">
641
+ {" "}
642
+ {/* 80% opacity gray text */}
643
+ Semi-transparent content
644
+ </Text>
645
+ <View className="border-2 border-red-500/30" /> {/* 30% opacity red border */}
646
+ </View>
647
+ ```
908
648
 
909
- // Use inline styles for dynamic values
910
- <View className="p-2" style={{ margin: spacing * 4 }} />
649
+ - Works with all color types: `bg-*`, `text-*`, `border-*`
650
+ - Supports preset colors: `bg-blue-500/75`, `text-red-600/50`
651
+ - Supports arbitrary colors: `bg-[#ff0000]/40`, `text-[#3B82F6]/90`
652
+ - Supports custom colors: `bg-primary/60`, `text-brand/80`
653
+ - Uses React Native's 8-digit hex format: `#RRGGBBAA`
654
+
655
+ **Examples:**
656
+
657
+ ```tsx
658
+ // Background opacity
659
+ <View className="bg-white/90" /> // #FFFFFFE6
660
+ <View className="bg-blue-500/50" /> // #3B82F680
661
+
662
+ // Text opacity
663
+ <Text className="text-black/70" /> // #000000B3
664
+ <Text className="text-gray-900/60" /> // #11182799
665
+
666
+ // Border opacity
667
+ <View className="border-2 border-purple-500/40" /> // #A855F766
668
+
669
+ // Arbitrary colors with opacity
670
+ <View className="bg-[#ff6b6b]/25" /> // #FF6B6B40
671
+
672
+ // Edge cases
673
+ <View className="bg-black/0" /> // Fully transparent
674
+ <View className="bg-blue-500/100" /> // Fully opaque
675
+ <View className="bg-transparent/50" /> // Remains transparent
911
676
  ```
912
677
 
913
- For dynamic styling, use the `style` prop alongside `className`.
678
+ ### Typography
679
+
680
+ **Font Size:**
681
+
682
+ `text-xs`, `text-sm`, `text-base`, `text-lg`, `text-xl`, `text-2xl`, `text-3xl`, `text-4xl`, `text-5xl`, `text-6xl`, `text-7xl`, `text-8xl`, `text-9xl`
683
+
684
+ **Font Weight:**
685
+
686
+ `font-thin`, `font-extralight`, `font-light`, `font-normal`, `font-medium`, `font-semibold`, `font-bold`, `font-extrabold`, `font-black`
687
+
688
+ **Other:**
689
+
690
+ - `italic`, `not-italic` — Font style
691
+ - `text-left`, `text-center`, `text-right`, `text-justify` — Text alignment
692
+ - `underline`, `line-through`, `no-underline` — Text decoration
693
+ - `uppercase`, `lowercase`, `capitalize`, `normal-case` — Text transform
694
+ - `leading-none`, `leading-tight`, `leading-snug`, `leading-normal`, `leading-relaxed`, `leading-loose` — Line height
695
+
696
+ ### Borders
697
+
698
+ **Width:**
699
+
700
+ - `border`, `border-0`, `border-2`, `border-4`, `border-8` — All sides
701
+ - `border-t`, `border-r`, `border-b`, `border-l` (with variants `-0`, `-2`, `-4`, `-8`) — Directional
702
+ - `border-[8px]`, `border-t-[12px]` — Arbitrary values
703
+
704
+ **Radius:**
705
+
706
+ - `rounded-none`, `rounded-sm`, `rounded`, `rounded-md`, `rounded-lg`, `rounded-xl`, `rounded-2xl`, `rounded-3xl`, `rounded-full` — All corners
707
+ - `rounded-t`, `rounded-r`, `rounded-b`, `rounded-l` (with size variants) — Directional
708
+ - `rounded-tl`, `rounded-tr`, `rounded-bl`, `rounded-br` (with size variants) — Individual corners
709
+ - `rounded-[12px]`, `rounded-t-[8px]`, `rounded-tl-[16px]` — Arbitrary values
710
+
711
+ **Style:**
712
+
713
+ `border-solid`, `border-dotted`, `border-dashed`
714
+
715
+ ### Shadows & Elevation
716
+
717
+ Apply platform-specific shadows and elevation to create depth and visual hierarchy. Automatically uses iOS shadow properties or Android elevation based on the platform:
718
+
719
+ **Available shadow sizes:**
720
+
721
+ - `shadow-sm` — Subtle shadow
722
+ - `shadow` — Default shadow
723
+ - `shadow-md` — Medium shadow
724
+ - `shadow-lg` — Large shadow
725
+ - `shadow-xl` — Extra large shadow
726
+ - `shadow-2xl` — Extra extra large shadow
727
+ - `shadow-none` — Remove shadow
728
+
729
+ **Platform Differences:**
730
+
731
+ | Platform | Properties Used | Example Output |
732
+ | ----------- | -------------------------------------------------------------- | ------------------------------------------ |
733
+ | **iOS** | `shadowColor`, `shadowOffset`, `shadowOpacity`, `shadowRadius` | Native iOS shadow rendering |
734
+ | **Android** | `elevation` | Native Android elevation (Material Design) |
735
+
736
+ **Examples:**
737
+
738
+ ```tsx
739
+ // Card with shadow
740
+ <View className="bg-white rounded-lg shadow-lg p-6 m-4">
741
+ <Text className="text-xl font-bold">Card Title</Text>
742
+ <Text className="text-gray-600">Card with large shadow</Text>
743
+ </View>
744
+
745
+ // Button with subtle shadow
746
+ <Pressable className="bg-blue-500 shadow-sm rounded-lg px-6 py-3">
747
+ <Text className="text-white">Press Me</Text>
748
+ </Pressable>
749
+
750
+ // Different shadow sizes
751
+ <View className="shadow-sm p-4">Subtle</View>
752
+ <View className="shadow p-4">Default</View>
753
+ <View className="shadow-md p-4">Medium</View>
754
+ <View className="shadow-lg p-4">Large</View>
755
+ <View className="shadow-xl p-4">Extra Large</View>
756
+ <View className="shadow-2xl p-4">2X Large</View>
757
+
758
+ // Remove shadow
759
+ <View className="shadow-lg md:shadow-none p-4">
760
+ Conditional shadow removal
761
+ </View>
762
+ ```
763
+
764
+ **iOS Shadow Values:**
765
+
766
+ | Class | shadowOpacity | shadowRadius | shadowOffset |
767
+ | ------------ | ------------- | ------------ | ------------------------ |
768
+ | `shadow-sm` | 0.05 | 1 | { width: 0, height: 1 } |
769
+ | `shadow` | 0.1 | 2 | { width: 0, height: 1 } |
770
+ | `shadow-md` | 0.15 | 4 | { width: 0, height: 3 } |
771
+ | `shadow-lg` | 0.2 | 8 | { width: 0, height: 6 } |
772
+ | `shadow-xl` | 0.25 | 12 | { width: 0, height: 10 } |
773
+ | `shadow-2xl` | 0.3 | 24 | { width: 0, height: 20 } |
774
+
775
+ **Android Elevation Values:**
776
+
777
+ | Class | elevation |
778
+ | ------------ | --------- |
779
+ | `shadow-sm` | 1 |
780
+ | `shadow` | 2 |
781
+ | `shadow-md` | 4 |
782
+ | `shadow-lg` | 8 |
783
+ | `shadow-xl` | 12 |
784
+ | `shadow-2xl` | 16 |
785
+
786
+ > **Note:** All shadow parsing happens at compile-time with zero runtime overhead. The platform detection uses React Native's `Platform.select()` API.
787
+
788
+ ### Aspect Ratio
789
+
790
+ Control the aspect ratio of views using preset or arbitrary values. Requires React Native 0.71+:
791
+
792
+ **Preset values:**
793
+
794
+ - `aspect-auto` — Remove aspect ratio constraint
795
+ - `aspect-square` — 1:1 aspect ratio
796
+ - `aspect-video` — 16:9 aspect ratio
797
+
798
+ **Arbitrary values:**
799
+
800
+ Use `aspect-[width/height]` for custom ratios:
801
+
802
+ ```tsx
803
+ <View className="aspect-[4/3]" /> // 4:3 ratio (1.333...)
804
+ <View className="aspect-[16/9]" /> // 16:9 ratio (1.778...)
805
+ <View className="aspect-[21/9]" /> // 21:9 ultrawide
806
+ <View className="aspect-[9/16]" /> // 9:16 portrait
807
+ <View className="aspect-[3/2]" /> // 3:2 ratio (1.5)
808
+ ```
809
+
810
+ **Examples:**
811
+
812
+ ```tsx
813
+ // Square image container
814
+ <View className="w-full aspect-square bg-gray-200">
815
+ <Image source={avatar} className="w-full h-full" />
816
+ </View>
817
+
818
+ // Video player container (16:9)
819
+ <View className="w-full aspect-video bg-black">
820
+ <VideoPlayer />
821
+ </View>
822
+
823
+ // Instagram-style square grid
824
+ <View className="flex-row flex-wrap gap-2">
825
+ {photos.map((photo) => (
826
+ <View key={photo.id} className="w-[32%] aspect-square">
827
+ <Image source={photo.uri} className="w-full h-full rounded" />
828
+ </View>
829
+ ))}
830
+ </View>
831
+
832
+ // Custom aspect ratio for wide images
833
+ <View className="w-full aspect-[21/9] rounded-lg overflow-hidden">
834
+ <Image source={banner} className="w-full h-full" resizeMode="cover" />
835
+ </View>
836
+
837
+ // Portrait orientation
838
+ <View className="h-full aspect-[9/16]">
839
+ <Story />
840
+ </View>
841
+
842
+ // Remove aspect ratio constraint
843
+ <View className="aspect-square md:aspect-auto">
844
+ Responsive aspect ratio
845
+ </View>
846
+ ```
847
+
848
+ **Common Aspect Ratios:**
849
+
850
+ | Ratio | Class | Decimal | Use Case |
851
+ | ----- | --------------- | ------- | ---------------------------- |
852
+ | 1:1 | `aspect-square` | 1.0 | Profile pictures, thumbnails |
853
+ | 16:9 | `aspect-video` | 1.778 | Videos, landscape photos |
854
+ | 4:3 | `aspect-[4/3]` | 1.333 | Standard photos |
855
+ | 3:2 | `aspect-[3/2]` | 1.5 | Classic photography |
856
+ | 21:9 | `aspect-[21/9]` | 2.333 | Ultrawide/cinematic |
857
+ | 9:16 | `aspect-[9/16]` | 0.5625 | Stories, vertical video |
858
+
859
+ > **Note:** The aspect ratio is calculated as `width / height`. When combined with `w-full`, the height will be automatically calculated to maintain the ratio.
860
+
861
+ ### Sizing
862
+
863
+ - `w-{size}`, `h-{size}` — Width/height
864
+ - `min-w-{size}`, `min-h-{size}` — Min width/height
865
+ - `max-w-{size}`, `max-h-{size}` — Max width/height
866
+
867
+ **Available sizes:**
868
+
869
+ - **Numeric:** `0`-`96` (same as spacing scale)
870
+ - **Fractional:** `1/2`, `1/3`, `2/3`, `1/4`, `3/4`, `1/5`, `2/5`, `3/5`, `4/5`, `1/6`, `2/6`, `3/6`, `4/6`, `5/6`
871
+ - **Special:** `full` (100%), `auto`
872
+ - **Arbitrary:** `w-[123px]`, `h-[50%]`, `min-w-[200px]`, `max-h-[80%]`
873
+
874
+ > **Note:** Arbitrary sizing supports pixel values (`[123px]` or `[123]`) and percentages (`[50%]`). Other units (`rem`, `em`, `vh`, `vw`) are not supported in React Native.
914
875
 
915
876
  ## Advanced
916
877
 
@@ -620,94 +620,57 @@ function parseLayout(cls) {
620
620
  }
621
621
 
622
622
  // src/parser/shadows.ts
623
- var import_react_native = require("react-native");
624
- var SHADOW_DEFINITIONS = {
623
+ var SHADOW_SCALE = {
625
624
  "shadow-sm": {
626
- ios: {
627
- shadowColor: "#000000",
628
- shadowOffset: { width: 0, height: 1 },
629
- shadowOpacity: 0.05,
630
- shadowRadius: 1
631
- },
632
- android: {
633
- elevation: 1
634
- }
625
+ shadowColor: "#000000",
626
+ shadowOffset: { width: 0, height: 1 },
627
+ shadowOpacity: 0.05,
628
+ shadowRadius: 1,
629
+ elevation: 1
635
630
  },
636
631
  shadow: {
637
- ios: {
638
- shadowColor: "#000000",
639
- shadowOffset: { width: 0, height: 1 },
640
- shadowOpacity: 0.1,
641
- shadowRadius: 2
642
- },
643
- android: {
644
- elevation: 2
645
- }
632
+ shadowColor: "#000000",
633
+ shadowOffset: { width: 0, height: 1 },
634
+ shadowOpacity: 0.1,
635
+ shadowRadius: 2,
636
+ elevation: 2
646
637
  },
647
638
  "shadow-md": {
648
- ios: {
649
- shadowColor: "#000000",
650
- shadowOffset: { width: 0, height: 3 },
651
- shadowOpacity: 0.15,
652
- shadowRadius: 4
653
- },
654
- android: {
655
- elevation: 4
656
- }
639
+ shadowColor: "#000000",
640
+ shadowOffset: { width: 0, height: 3 },
641
+ shadowOpacity: 0.15,
642
+ shadowRadius: 4,
643
+ elevation: 4
657
644
  },
658
645
  "shadow-lg": {
659
- ios: {
660
- shadowColor: "#000000",
661
- shadowOffset: { width: 0, height: 6 },
662
- shadowOpacity: 0.2,
663
- shadowRadius: 8
664
- },
665
- android: {
666
- elevation: 8
667
- }
646
+ shadowColor: "#000000",
647
+ shadowOffset: { width: 0, height: 6 },
648
+ shadowOpacity: 0.2,
649
+ shadowRadius: 8,
650
+ elevation: 8
668
651
  },
669
652
  "shadow-xl": {
670
- ios: {
671
- shadowColor: "#000000",
672
- shadowOffset: { width: 0, height: 10 },
673
- shadowOpacity: 0.25,
674
- shadowRadius: 12
675
- },
676
- android: {
677
- elevation: 12
678
- }
653
+ shadowColor: "#000000",
654
+ shadowOffset: { width: 0, height: 10 },
655
+ shadowOpacity: 0.25,
656
+ shadowRadius: 12,
657
+ elevation: 12
679
658
  },
680
659
  "shadow-2xl": {
681
- ios: {
682
- shadowColor: "#000000",
683
- shadowOffset: { width: 0, height: 20 },
684
- shadowOpacity: 0.3,
685
- shadowRadius: 24
686
- },
687
- android: {
688
- elevation: 16
689
- }
660
+ shadowColor: "#000000",
661
+ shadowOffset: { width: 0, height: 20 },
662
+ shadowOpacity: 0.3,
663
+ shadowRadius: 24,
664
+ elevation: 16
690
665
  },
691
666
  "shadow-none": {
692
- ios: {
693
- shadowColor: "transparent",
694
- shadowOffset: { width: 0, height: 0 },
695
- shadowOpacity: 0,
696
- shadowRadius: 0
697
- },
698
- android: {
699
- elevation: 0
700
- }
667
+ shadowColor: "transparent",
668
+ shadowOffset: { width: 0, height: 0 },
669
+ shadowOpacity: 0,
670
+ shadowRadius: 0,
671
+ elevation: 0
701
672
  }
702
673
  };
703
- function buildShadowScale() {
704
- const scale = {};
705
- for (const [key, value] of Object.entries(SHADOW_DEFINITIONS)) {
706
- scale[key] = import_react_native.Platform.select(value);
707
- }
708
- return scale;
709
- }
710
- var SHADOW_SCALE = buildShadowScale();
711
674
  function parseShadow(cls) {
712
675
  if (cls in SHADOW_SCALE) {
713
676
  return SHADOW_SCALE[cls];
@@ -4,23 +4,19 @@
4
4
  */
5
5
  import type { StyleObject } from "../types";
6
6
  /**
7
- * Helper function to build the shadow scale using Platform.select()
8
- * This allows tests to rebuild the scale after changing the platform mock
7
+ * Shadow scale definitions combining iOS and Android properties
8
+ * Based on Tailwind CSS shadow scale, adapted for React Native
9
+ *
10
+ * Note: We include BOTH iOS shadow properties AND Android elevation in each style.
11
+ * React Native will automatically use the appropriate properties for each platform:
12
+ * - iOS uses shadowColor, shadowOffset, shadowOpacity, shadowRadius
13
+ * - Android uses elevation
9
14
  */
10
- declare function buildShadowScale(): Record<string, StyleObject>;
11
- /**
12
- * Computed shadow scale using Platform.select()
13
- * This is evaluated at module load time for production use
14
- */
15
- declare let SHADOW_SCALE: Record<string, StyleObject>;
16
- /**
17
- * Rebuild the shadow scale (useful for testing with platform mocks)
18
- */
19
- export declare function rebuildShadowScale(): void;
15
+ declare const SHADOW_SCALE: Record<string, StyleObject>;
20
16
  /**
21
17
  * Parse shadow classes
22
18
  * @param cls - Class name to parse
23
19
  * @returns Style object or null if not a shadow class
24
20
  */
25
21
  export declare function parseShadow(cls: string): StyleObject | null;
26
- export { buildShadowScale, SHADOW_SCALE };
22
+ export { SHADOW_SCALE };
@@ -1 +1 @@
1
- var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.SHADOW_SCALE=void 0;exports.buildShadowScale=buildShadowScale;exports.parseShadow=parseShadow;exports.rebuildShadowScale=rebuildShadowScale;var _slicedToArray2=_interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));var _reactNative=require("react-native");var SHADOW_DEFINITIONS={"shadow-sm":{ios:{shadowColor:"#000000",shadowOffset:{width:0,height:1},shadowOpacity:0.05,shadowRadius:1},android:{elevation:1}},shadow:{ios:{shadowColor:"#000000",shadowOffset:{width:0,height:1},shadowOpacity:0.1,shadowRadius:2},android:{elevation:2}},"shadow-md":{ios:{shadowColor:"#000000",shadowOffset:{width:0,height:3},shadowOpacity:0.15,shadowRadius:4},android:{elevation:4}},"shadow-lg":{ios:{shadowColor:"#000000",shadowOffset:{width:0,height:6},shadowOpacity:0.2,shadowRadius:8},android:{elevation:8}},"shadow-xl":{ios:{shadowColor:"#000000",shadowOffset:{width:0,height:10},shadowOpacity:0.25,shadowRadius:12},android:{elevation:12}},"shadow-2xl":{ios:{shadowColor:"#000000",shadowOffset:{width:0,height:20},shadowOpacity:0.3,shadowRadius:24},android:{elevation:16}},"shadow-none":{ios:{shadowColor:"transparent",shadowOffset:{width:0,height:0},shadowOpacity:0,shadowRadius:0},android:{elevation:0}}};function buildShadowScale(){var scale={};for(var _ref of Object.entries(SHADOW_DEFINITIONS)){var _ref2=(0,_slicedToArray2.default)(_ref,2);var key=_ref2[0];var value=_ref2[1];scale[key]=_reactNative.Platform.select(value);}return scale;}var SHADOW_SCALE=exports.SHADOW_SCALE=buildShadowScale();function rebuildShadowScale(){exports.SHADOW_SCALE=SHADOW_SCALE=buildShadowScale();}function parseShadow(cls){if(cls in SHADOW_SCALE){return SHADOW_SCALE[cls];}return null;}
1
+ Object.defineProperty(exports,"__esModule",{value:true});exports.SHADOW_SCALE=void 0;exports.parseShadow=parseShadow;var SHADOW_SCALE=exports.SHADOW_SCALE={"shadow-sm":{shadowColor:"#000000",shadowOffset:{width:0,height:1},shadowOpacity:0.05,shadowRadius:1,elevation:1},shadow:{shadowColor:"#000000",shadowOffset:{width:0,height:1},shadowOpacity:0.1,shadowRadius:2,elevation:2},"shadow-md":{shadowColor:"#000000",shadowOffset:{width:0,height:3},shadowOpacity:0.15,shadowRadius:4,elevation:4},"shadow-lg":{shadowColor:"#000000",shadowOffset:{width:0,height:6},shadowOpacity:0.2,shadowRadius:8,elevation:8},"shadow-xl":{shadowColor:"#000000",shadowOffset:{width:0,height:10},shadowOpacity:0.25,shadowRadius:12,elevation:12},"shadow-2xl":{shadowColor:"#000000",shadowOffset:{width:0,height:20},shadowOpacity:0.3,shadowRadius:24,elevation:16},"shadow-none":{shadowColor:"transparent",shadowOffset:{width:0,height:0},shadowOpacity:0,shadowRadius:0,elevation:0}};function parseShadow(cls){if(cls in SHADOW_SCALE){return SHADOW_SCALE[cls];}return null;}
@@ -1 +1 @@
1
- var _reactNative=require("test/mocks/react-native");var _vitest=require("vitest");var _shadows=require("./shadows");(0,_vitest.beforeEach)(function(){(0,_reactNative.setPlatform)("ios");(0,_shadows.rebuildShadowScale)();});(0,_vitest.describe)("SHADOW_SCALE",function(){(0,_vitest.it)("should export complete shadow scale",function(){(0,_vitest.expect)(_shadows.SHADOW_SCALE).toMatchSnapshot();});(0,_vitest.it)("should have all shadow variants",function(){(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-sm");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-md");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-lg");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-xl");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-2xl");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-none");});});(0,_vitest.describe)("parseShadow - basic shadows",function(){(0,_vitest.it)("should parse shadow-sm",function(){var result=(0,_shadows.parseShadow)("shadow-sm");(0,_vitest.expect)(result).toBeTruthy();(0,_vitest.expect)(result).toHaveProperty("shadowColor");(0,_vitest.expect)(result).toHaveProperty("shadowOffset");(0,_vitest.expect)(result).toHaveProperty("shadowOpacity");(0,_vitest.expect)(result).toHaveProperty("shadowRadius");});(0,_vitest.it)("should parse default shadow",function(){var result=(0,_shadows.parseShadow)("shadow");(0,_vitest.expect)(result).toBeTruthy();});(0,_vitest.it)("should parse shadow-md",function(){var result=(0,_shadows.parseShadow)("shadow-md");(0,_vitest.expect)(result).toBeTruthy();});(0,_vitest.it)("should parse shadow-lg",function(){var result=(0,_shadows.parseShadow)("shadow-lg");(0,_vitest.expect)(result).toBeTruthy();});(0,_vitest.it)("should parse shadow-xl",function(){var result=(0,_shadows.parseShadow)("shadow-xl");(0,_vitest.expect)(result).toBeTruthy();});(0,_vitest.it)("should parse shadow-2xl",function(){var result=(0,_shadows.parseShadow)("shadow-2xl");(0,_vitest.expect)(result).toBeTruthy();});(0,_vitest.it)("should parse shadow-none",function(){var result=(0,_shadows.parseShadow)("shadow-none");(0,_vitest.expect)(result).toBeTruthy();(0,_vitest.expect)(result).toMatchObject({shadowColor:"transparent",shadowOpacity:0,shadowRadius:0});});});(0,_vitest.describe)("parseShadow - shadow properties (iOS)",function(){(0,_vitest.beforeEach)(function(){(0,_reactNative.setPlatform)("ios");});(0,_vitest.it)("should have increasing shadow values for larger shadows",function(){var sm=(0,_shadows.parseShadow)("shadow-sm");var md=(0,_shadows.parseShadow)("shadow-md");var lg=(0,_shadows.parseShadow)("shadow-lg");var xl=(0,_shadows.parseShadow)("shadow-xl");var xxl=(0,_shadows.parseShadow)("shadow-2xl");(0,_vitest.expect)(md==null?void 0:md.shadowOpacity).toBeGreaterThan(sm==null?void 0:sm.shadowOpacity);(0,_vitest.expect)(lg==null?void 0:lg.shadowOpacity).toBeGreaterThan(md==null?void 0:md.shadowOpacity);(0,_vitest.expect)(md==null?void 0:md.shadowRadius).toBeGreaterThan(sm==null?void 0:sm.shadowRadius);(0,_vitest.expect)(lg==null?void 0:lg.shadowRadius).toBeGreaterThan(md==null?void 0:md.shadowRadius);(0,_vitest.expect)(xl==null?void 0:xl.shadowRadius).toBeGreaterThan(lg==null?void 0:lg.shadowRadius);(0,_vitest.expect)(xxl==null?void 0:xxl.shadowRadius).toBeGreaterThan(xl==null?void 0:xl.shadowRadius);});(0,_vitest.it)("should use consistent shadow color",function(){var shadows=["shadow-sm","shadow","shadow-md","shadow-lg","shadow-xl","shadow-2xl"];shadows.forEach(function(shadow){var result=(0,_shadows.parseShadow)(shadow);(0,_vitest.expect)(result==null?void 0:result.shadowColor).toBe("#000000");});});(0,_vitest.it)("should have proper shadowOffset structure",function(){var _result$shadowOffset,_result$shadowOffset2;var result=(0,_shadows.parseShadow)("shadow");(0,_vitest.expect)(result==null?void 0:result.shadowOffset).toHaveProperty("width");(0,_vitest.expect)(result==null?void 0:result.shadowOffset).toHaveProperty("height");(0,_vitest.expect)(typeof(result==null||(_result$shadowOffset=result.shadowOffset)==null?void 0:_result$shadowOffset.width)).toBe("number");(0,_vitest.expect)(typeof(result==null||(_result$shadowOffset2=result.shadowOffset)==null?void 0:_result$shadowOffset2.height)).toBe("number");});(0,_vitest.it)("should not have elevation property on iOS",function(){var result=(0,_shadows.parseShadow)("shadow");(0,_vitest.expect)(result).not.toHaveProperty("elevation");});});(0,_vitest.describe)("parseShadow - shadow properties (Android)",function(){(0,_vitest.beforeEach)(function(){(0,_reactNative.setPlatform)("android");(0,_shadows.rebuildShadowScale)();});(0,_vitest.it)("should have increasing elevation values for larger shadows",function(){var sm=(0,_shadows.parseShadow)("shadow-sm");var md=(0,_shadows.parseShadow)("shadow-md");var lg=(0,_shadows.parseShadow)("shadow-lg");var xl=(0,_shadows.parseShadow)("shadow-xl");var xxl=(0,_shadows.parseShadow)("shadow-2xl");(0,_vitest.expect)(md==null?void 0:md.elevation).toBeGreaterThan(sm==null?void 0:sm.elevation);(0,_vitest.expect)(lg==null?void 0:lg.elevation).toBeGreaterThan(md==null?void 0:md.elevation);(0,_vitest.expect)(xl==null?void 0:xl.elevation).toBeGreaterThan(lg==null?void 0:lg.elevation);(0,_vitest.expect)(xxl==null?void 0:xxl.elevation).toBeGreaterThan(xl==null?void 0:xl.elevation);});(0,_vitest.it)("should not have shadow properties on Android",function(){var result=(0,_shadows.parseShadow)("shadow");(0,_vitest.expect)(result).not.toHaveProperty("shadowColor");(0,_vitest.expect)(result).not.toHaveProperty("shadowOffset");(0,_vitest.expect)(result).not.toHaveProperty("shadowOpacity");(0,_vitest.expect)(result).not.toHaveProperty("shadowRadius");});});(0,_vitest.describe)("parseShadow - invalid classes",function(){(0,_vitest.it)("should return null for invalid shadow classes",function(){(0,_vitest.expect)((0,_shadows.parseShadow)("shadow-invalid")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadows")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadow-")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadow-small")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadow-3xl")).toBeNull();});(0,_vitest.it)("should return null for non-shadow classes",function(){(0,_vitest.expect)((0,_shadows.parseShadow)("bg-blue-500")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("p-4")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("text-white")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("border-2")).toBeNull();});(0,_vitest.it)("should return null for empty or invalid input",function(){(0,_vitest.expect)((0,_shadows.parseShadow)("")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadow123")).toBeNull();});});(0,_vitest.describe)("parseShadow - comprehensive coverage",function(){(0,_vitest.it)("should parse all shadow variants without errors",function(){var variants=["shadow-sm","shadow","shadow-md","shadow-lg","shadow-xl","shadow-2xl","shadow-none"];variants.forEach(function(variant){var result=(0,_shadows.parseShadow)(variant);(0,_vitest.expect)(result).toBeTruthy();(0,_vitest.expect)(typeof result).toBe("object");});});(0,_vitest.it)("should return consistent results for same input",function(){var result1=(0,_shadows.parseShadow)("shadow-md");var result2=(0,_shadows.parseShadow)("shadow-md");(0,_vitest.expect)(result1).toEqual(result2);});(0,_vitest.it)("should handle case-sensitive class names",function(){(0,_vitest.expect)((0,_shadows.parseShadow)("SHADOW")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("Shadow-md")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadow-MD")).toBeNull();});});
1
+ var _vitest=require("vitest");var _shadows=require("./shadows");(0,_vitest.describe)("SHADOW_SCALE",function(){(0,_vitest.it)("should export complete shadow scale",function(){(0,_vitest.expect)(_shadows.SHADOW_SCALE).toMatchSnapshot();});(0,_vitest.it)("should have all shadow variants",function(){(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-sm");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-md");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-lg");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-xl");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-2xl");(0,_vitest.expect)(_shadows.SHADOW_SCALE).toHaveProperty("shadow-none");});});(0,_vitest.describe)("parseShadow - basic shadows",function(){(0,_vitest.it)("should parse shadow-sm",function(){var result=(0,_shadows.parseShadow)("shadow-sm");(0,_vitest.expect)(result).toBeTruthy();(0,_vitest.expect)(result).toHaveProperty("shadowColor");(0,_vitest.expect)(result).toHaveProperty("shadowOffset");(0,_vitest.expect)(result).toHaveProperty("shadowOpacity");(0,_vitest.expect)(result).toHaveProperty("shadowRadius");});(0,_vitest.it)("should parse default shadow",function(){var result=(0,_shadows.parseShadow)("shadow");(0,_vitest.expect)(result).toBeTruthy();});(0,_vitest.it)("should parse shadow-md",function(){var result=(0,_shadows.parseShadow)("shadow-md");(0,_vitest.expect)(result).toBeTruthy();});(0,_vitest.it)("should parse shadow-lg",function(){var result=(0,_shadows.parseShadow)("shadow-lg");(0,_vitest.expect)(result).toBeTruthy();});(0,_vitest.it)("should parse shadow-xl",function(){var result=(0,_shadows.parseShadow)("shadow-xl");(0,_vitest.expect)(result).toBeTruthy();});(0,_vitest.it)("should parse shadow-2xl",function(){var result=(0,_shadows.parseShadow)("shadow-2xl");(0,_vitest.expect)(result).toBeTruthy();});(0,_vitest.it)("should parse shadow-none",function(){var result=(0,_shadows.parseShadow)("shadow-none");(0,_vitest.expect)(result).toBeTruthy();(0,_vitest.expect)(result).toMatchObject({shadowColor:"transparent",shadowOpacity:0,shadowRadius:0});});});(0,_vitest.describe)("parseShadow - shadow properties (iOS)",function(){(0,_vitest.it)("should have increasing shadow values for larger shadows",function(){var sm=(0,_shadows.parseShadow)("shadow-sm");var md=(0,_shadows.parseShadow)("shadow-md");var lg=(0,_shadows.parseShadow)("shadow-lg");var xl=(0,_shadows.parseShadow)("shadow-xl");var xxl=(0,_shadows.parseShadow)("shadow-2xl");(0,_vitest.expect)(md==null?void 0:md.shadowOpacity).toBeGreaterThan(sm==null?void 0:sm.shadowOpacity);(0,_vitest.expect)(lg==null?void 0:lg.shadowOpacity).toBeGreaterThan(md==null?void 0:md.shadowOpacity);(0,_vitest.expect)(md==null?void 0:md.shadowRadius).toBeGreaterThan(sm==null?void 0:sm.shadowRadius);(0,_vitest.expect)(lg==null?void 0:lg.shadowRadius).toBeGreaterThan(md==null?void 0:md.shadowRadius);(0,_vitest.expect)(xl==null?void 0:xl.shadowRadius).toBeGreaterThan(lg==null?void 0:lg.shadowRadius);(0,_vitest.expect)(xxl==null?void 0:xxl.shadowRadius).toBeGreaterThan(xl==null?void 0:xl.shadowRadius);});(0,_vitest.it)("should use consistent shadow color",function(){var shadows=["shadow-sm","shadow","shadow-md","shadow-lg","shadow-xl","shadow-2xl"];shadows.forEach(function(shadow){var result=(0,_shadows.parseShadow)(shadow);(0,_vitest.expect)(result==null?void 0:result.shadowColor).toBe("#000000");});});(0,_vitest.it)("should have proper shadowOffset structure",function(){var result=(0,_shadows.parseShadow)("shadow");var shadowOffset=result==null?void 0:result.shadowOffset;(0,_vitest.expect)(shadowOffset).toHaveProperty("width");(0,_vitest.expect)(shadowOffset).toHaveProperty("height");if(typeof shadowOffset==="object"&&shadowOffset!==null){(0,_vitest.expect)(typeof shadowOffset.width).toBe("number");(0,_vitest.expect)(typeof shadowOffset.height).toBe("number");}});(0,_vitest.it)("should include both iOS shadow and Android elevation properties",function(){var result=(0,_shadows.parseShadow)("shadow");(0,_vitest.expect)(result).toHaveProperty("shadowColor");(0,_vitest.expect)(result).toHaveProperty("shadowOffset");(0,_vitest.expect)(result).toHaveProperty("shadowOpacity");(0,_vitest.expect)(result).toHaveProperty("shadowRadius");(0,_vitest.expect)(result).toHaveProperty("elevation");});});(0,_vitest.describe)("parseShadow - shadow properties (Android)",function(){(0,_vitest.it)("should have increasing elevation values for larger shadows",function(){var sm=(0,_shadows.parseShadow)("shadow-sm");var md=(0,_shadows.parseShadow)("shadow-md");var lg=(0,_shadows.parseShadow)("shadow-lg");var xl=(0,_shadows.parseShadow)("shadow-xl");var xxl=(0,_shadows.parseShadow)("shadow-2xl");(0,_vitest.expect)(md==null?void 0:md.elevation).toBeGreaterThan(sm==null?void 0:sm.elevation);(0,_vitest.expect)(lg==null?void 0:lg.elevation).toBeGreaterThan(md==null?void 0:md.elevation);(0,_vitest.expect)(xl==null?void 0:xl.elevation).toBeGreaterThan(lg==null?void 0:lg.elevation);(0,_vitest.expect)(xxl==null?void 0:xxl.elevation).toBeGreaterThan(xl==null?void 0:xl.elevation);});(0,_vitest.it)("should include elevation property for Android",function(){var result=(0,_shadows.parseShadow)("shadow");(0,_vitest.expect)(result).toHaveProperty("elevation");(0,_vitest.expect)(typeof(result==null?void 0:result.elevation)).toBe("number");});});(0,_vitest.describe)("parseShadow - invalid classes",function(){(0,_vitest.it)("should return null for invalid shadow classes",function(){(0,_vitest.expect)((0,_shadows.parseShadow)("shadow-invalid")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadows")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadow-")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadow-small")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadow-3xl")).toBeNull();});(0,_vitest.it)("should return null for non-shadow classes",function(){(0,_vitest.expect)((0,_shadows.parseShadow)("bg-blue-500")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("p-4")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("text-white")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("border-2")).toBeNull();});(0,_vitest.it)("should return null for empty or invalid input",function(){(0,_vitest.expect)((0,_shadows.parseShadow)("")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadow123")).toBeNull();});});(0,_vitest.describe)("parseShadow - comprehensive coverage",function(){(0,_vitest.it)("should parse all shadow variants without errors",function(){var variants=["shadow-sm","shadow","shadow-md","shadow-lg","shadow-xl","shadow-2xl","shadow-none"];variants.forEach(function(variant){var result=(0,_shadows.parseShadow)(variant);(0,_vitest.expect)(result).toBeTruthy();(0,_vitest.expect)(typeof result).toBe("object");});});(0,_vitest.it)("should return consistent results for same input",function(){var result1=(0,_shadows.parseShadow)("shadow-md");var result2=(0,_shadows.parseShadow)("shadow-md");(0,_vitest.expect)(result1).toEqual(result2);});(0,_vitest.it)("should handle case-sensitive class names",function(){(0,_vitest.expect)((0,_shadows.parseShadow)("SHADOW")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("Shadow-md")).toBeNull();(0,_vitest.expect)((0,_shadows.parseShadow)("shadow-MD")).toBeNull();});});
package/dist/types.d.ts CHANGED
@@ -3,12 +3,10 @@
3
3
  */
4
4
  import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
5
5
  export type RNStyle = ViewStyle | TextStyle | ImageStyle;
6
- export type StyleObject = Record<Exclude<string, "shadowOffset">, string | number | undefined> & {
7
- shadowOffset?: {
8
- width: number;
9
- height: number;
10
- };
11
- };
6
+ export type StyleObject = Record<string, string | number | {
7
+ width: number;
8
+ height: number;
9
+ } | undefined>;
12
10
  export type SpacingValue = number;
13
11
  export type ColorValue = string;
14
12
  export type Parser = (className: string) => StyleObject | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgcrea/react-native-tailwind",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Compile-time Tailwind CSS for React Native with zero runtime overhead",
5
5
  "author": "Olivier Louvignes <olivier@mgcrea.io> (https://github.com/mgcrea)",
6
6
  "homepage": "https://github.com/mgcrea/react-native-tailwind#readme",
@@ -1,12 +1,5 @@
1
- import { setPlatform } from "test/mocks/react-native";
2
- import { beforeEach, describe, expect, it } from "vitest";
3
- import { SHADOW_SCALE, parseShadow, rebuildShadowScale } from "./shadows";
4
-
5
- // Reset to iOS before each test
6
- beforeEach(() => {
7
- setPlatform("ios");
8
- rebuildShadowScale();
9
- });
1
+ import { describe, expect, it } from "vitest";
2
+ import { SHADOW_SCALE, parseShadow } from "./shadows";
10
3
 
11
4
  describe("SHADOW_SCALE", () => {
12
5
  it("should export complete shadow scale", () => {
@@ -71,10 +64,6 @@ describe("parseShadow - basic shadows", () => {
71
64
  });
72
65
 
73
66
  describe("parseShadow - shadow properties (iOS)", () => {
74
- beforeEach(() => {
75
- setPlatform("ios");
76
- });
77
-
78
67
  it("should have increasing shadow values for larger shadows", () => {
79
68
  const sm = parseShadow("shadow-sm");
80
69
  const md = parseShadow("shadow-md");
@@ -104,24 +93,28 @@ describe("parseShadow - shadow properties (iOS)", () => {
104
93
 
105
94
  it("should have proper shadowOffset structure", () => {
106
95
  const result = parseShadow("shadow");
107
- expect(result?.shadowOffset).toHaveProperty("width");
108
- expect(result?.shadowOffset).toHaveProperty("height");
109
- expect(typeof result?.shadowOffset?.width).toBe("number");
110
- expect(typeof result?.shadowOffset?.height).toBe("number");
96
+ const shadowOffset = result?.shadowOffset;
97
+ expect(shadowOffset).toHaveProperty("width");
98
+ expect(shadowOffset).toHaveProperty("height");
99
+ if (typeof shadowOffset === "object" && shadowOffset !== null) {
100
+ expect(typeof shadowOffset.width).toBe("number");
101
+ expect(typeof shadowOffset.height).toBe("number");
102
+ }
111
103
  });
112
104
 
113
- it("should not have elevation property on iOS", () => {
105
+ it("should include both iOS shadow and Android elevation properties", () => {
114
106
  const result = parseShadow("shadow");
115
- expect(result).not.toHaveProperty("elevation");
107
+ // iOS properties
108
+ expect(result).toHaveProperty("shadowColor");
109
+ expect(result).toHaveProperty("shadowOffset");
110
+ expect(result).toHaveProperty("shadowOpacity");
111
+ expect(result).toHaveProperty("shadowRadius");
112
+ // Android property
113
+ expect(result).toHaveProperty("elevation");
116
114
  });
117
115
  });
118
116
 
119
117
  describe("parseShadow - shadow properties (Android)", () => {
120
- beforeEach(() => {
121
- setPlatform("android");
122
- rebuildShadowScale();
123
- });
124
-
125
118
  it("should have increasing elevation values for larger shadows", () => {
126
119
  const sm = parseShadow("shadow-sm");
127
120
  const md = parseShadow("shadow-md");
@@ -136,12 +129,10 @@ describe("parseShadow - shadow properties (Android)", () => {
136
129
  expect(xxl?.elevation).toBeGreaterThan(xl?.elevation as number);
137
130
  });
138
131
 
139
- it("should not have shadow properties on Android", () => {
132
+ it("should include elevation property for Android", () => {
140
133
  const result = parseShadow("shadow");
141
- expect(result).not.toHaveProperty("shadowColor");
142
- expect(result).not.toHaveProperty("shadowOffset");
143
- expect(result).not.toHaveProperty("shadowOpacity");
144
- expect(result).not.toHaveProperty("shadowRadius");
134
+ expect(result).toHaveProperty("elevation");
135
+ expect(typeof result?.elevation).toBe("number");
145
136
  });
146
137
  });
147
138
 
@@ -3,117 +3,68 @@
3
3
  * iOS uses shadow* properties, Android uses elevation
4
4
  */
5
5
 
6
- import { Platform } from "react-native";
7
6
  import type { StyleObject } from "../types";
8
7
 
9
8
  /**
10
- * Shadow scale definitions (raw values, not platform-specific)
9
+ * Shadow scale definitions combining iOS and Android properties
11
10
  * Based on Tailwind CSS shadow scale, adapted for React Native
11
+ *
12
+ * Note: We include BOTH iOS shadow properties AND Android elevation in each style.
13
+ * React Native will automatically use the appropriate properties for each platform:
14
+ * - iOS uses shadowColor, shadowOffset, shadowOpacity, shadowRadius
15
+ * - Android uses elevation
12
16
  */
13
- const SHADOW_DEFINITIONS = {
17
+ const SHADOW_SCALE: Record<string, StyleObject> = {
14
18
  "shadow-sm": {
15
- ios: {
16
- shadowColor: "#000000",
17
- shadowOffset: { width: 0, height: 1 },
18
- shadowOpacity: 0.05,
19
- shadowRadius: 1,
20
- },
21
- android: {
22
- elevation: 1,
23
- },
19
+ shadowColor: "#000000",
20
+ shadowOffset: { width: 0, height: 1 },
21
+ shadowOpacity: 0.05,
22
+ shadowRadius: 1,
23
+ elevation: 1,
24
24
  },
25
25
  shadow: {
26
- ios: {
27
- shadowColor: "#000000",
28
- shadowOffset: { width: 0, height: 1 },
29
- shadowOpacity: 0.1,
30
- shadowRadius: 2,
31
- },
32
- android: {
33
- elevation: 2,
34
- },
26
+ shadowColor: "#000000",
27
+ shadowOffset: { width: 0, height: 1 },
28
+ shadowOpacity: 0.1,
29
+ shadowRadius: 2,
30
+ elevation: 2,
35
31
  },
36
32
  "shadow-md": {
37
- ios: {
38
- shadowColor: "#000000",
39
- shadowOffset: { width: 0, height: 3 },
40
- shadowOpacity: 0.15,
41
- shadowRadius: 4,
42
- },
43
- android: {
44
- elevation: 4,
45
- },
33
+ shadowColor: "#000000",
34
+ shadowOffset: { width: 0, height: 3 },
35
+ shadowOpacity: 0.15,
36
+ shadowRadius: 4,
37
+ elevation: 4,
46
38
  },
47
39
  "shadow-lg": {
48
- ios: {
49
- shadowColor: "#000000",
50
- shadowOffset: { width: 0, height: 6 },
51
- shadowOpacity: 0.2,
52
- shadowRadius: 8,
53
- },
54
- android: {
55
- elevation: 8,
56
- },
40
+ shadowColor: "#000000",
41
+ shadowOffset: { width: 0, height: 6 },
42
+ shadowOpacity: 0.2,
43
+ shadowRadius: 8,
44
+ elevation: 8,
57
45
  },
58
46
  "shadow-xl": {
59
- ios: {
60
- shadowColor: "#000000",
61
- shadowOffset: { width: 0, height: 10 },
62
- shadowOpacity: 0.25,
63
- shadowRadius: 12,
64
- },
65
- android: {
66
- elevation: 12,
67
- },
47
+ shadowColor: "#000000",
48
+ shadowOffset: { width: 0, height: 10 },
49
+ shadowOpacity: 0.25,
50
+ shadowRadius: 12,
51
+ elevation: 12,
68
52
  },
69
53
  "shadow-2xl": {
70
- ios: {
71
- shadowColor: "#000000",
72
- shadowOffset: { width: 0, height: 20 },
73
- shadowOpacity: 0.3,
74
- shadowRadius: 24,
75
- },
76
- android: {
77
- elevation: 16,
78
- },
54
+ shadowColor: "#000000",
55
+ shadowOffset: { width: 0, height: 20 },
56
+ shadowOpacity: 0.3,
57
+ shadowRadius: 24,
58
+ elevation: 16,
79
59
  },
80
60
  "shadow-none": {
81
- ios: {
82
- shadowColor: "transparent",
83
- shadowOffset: { width: 0, height: 0 },
84
- shadowOpacity: 0,
85
- shadowRadius: 0,
86
- },
87
- android: {
88
- elevation: 0,
89
- },
61
+ shadowColor: "transparent",
62
+ shadowOffset: { width: 0, height: 0 },
63
+ shadowOpacity: 0,
64
+ shadowRadius: 0,
65
+ elevation: 0,
90
66
  },
91
- } as const;
92
-
93
- /**
94
- * Helper function to build the shadow scale using Platform.select()
95
- * This allows tests to rebuild the scale after changing the platform mock
96
- */
97
- function buildShadowScale(): Record<string, StyleObject> {
98
- const scale: Record<string, StyleObject> = {};
99
- for (const [key, value] of Object.entries(SHADOW_DEFINITIONS)) {
100
- scale[key] = Platform.select<StyleObject>(value as never);
101
- }
102
- return scale;
103
- }
104
-
105
- /**
106
- * Computed shadow scale using Platform.select()
107
- * This is evaluated at module load time for production use
108
- */
109
- let SHADOW_SCALE = buildShadowScale();
110
-
111
- /**
112
- * Rebuild the shadow scale (useful for testing with platform mocks)
113
- */
114
- export function rebuildShadowScale() {
115
- SHADOW_SCALE = buildShadowScale();
116
- }
67
+ };
117
68
 
118
69
  /**
119
70
  * Parse shadow classes
@@ -129,5 +80,5 @@ export function parseShadow(cls: string): StyleObject | null {
129
80
  return null;
130
81
  }
131
82
 
132
- // Export shadow scale and builder for testing/advanced usage
133
- export { buildShadowScale, SHADOW_SCALE };
83
+ // Export shadow scale for testing/advanced usage
84
+ export { SHADOW_SCALE };
package/src/types.ts CHANGED
@@ -6,9 +6,7 @@ import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
6
6
 
7
7
  export type RNStyle = ViewStyle | TextStyle | ImageStyle;
8
8
 
9
- export type StyleObject = Record<Exclude<string, "shadowOffset">, string | number | undefined> & {
10
- shadowOffset?: { width: number; height: number };
11
- };
9
+ export type StyleObject = Record<string, string | number | { width: number; height: number } | undefined>;
12
10
 
13
11
  export type SpacingValue = number;
14
12
  export type ColorValue = string;