@mgcrea/react-native-tailwind 0.7.0 → 0.8.0

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.
Files changed (81) hide show
  1. package/README.md +2 -1
  2. package/dist/babel/index.cjs +333 -195
  3. package/dist/babel/index.d.ts +4 -40
  4. package/dist/babel/index.test.ts +214 -1
  5. package/dist/babel/index.ts +4 -1169
  6. package/dist/babel/plugin.d.ts +42 -0
  7. package/{src/babel/index.test.ts → dist/babel/plugin.test.ts} +216 -2
  8. package/dist/babel/plugin.ts +491 -0
  9. package/dist/babel/utils/attributeMatchers.d.ts +23 -0
  10. package/dist/babel/utils/attributeMatchers.ts +71 -0
  11. package/dist/babel/utils/componentSupport.d.ts +18 -0
  12. package/dist/babel/utils/componentSupport.ts +68 -0
  13. package/dist/babel/utils/dynamicProcessing.d.ts +32 -0
  14. package/dist/babel/utils/dynamicProcessing.ts +223 -0
  15. package/dist/babel/utils/modifierProcessing.d.ts +26 -0
  16. package/dist/babel/utils/modifierProcessing.ts +118 -0
  17. package/dist/babel/utils/styleInjection.d.ts +15 -0
  18. package/dist/babel/utils/styleInjection.ts +80 -0
  19. package/dist/babel/utils/styleTransforms.d.ts +39 -0
  20. package/dist/babel/utils/styleTransforms.test.ts +349 -0
  21. package/dist/babel/utils/styleTransforms.ts +258 -0
  22. package/dist/babel/utils/twProcessing.d.ts +28 -0
  23. package/dist/babel/utils/twProcessing.ts +124 -0
  24. package/dist/components/TextInput.d.ts +171 -14
  25. package/dist/config/tailwind.d.ts +302 -0
  26. package/dist/config/tailwind.js +1 -0
  27. package/dist/index.d.ts +5 -4
  28. package/dist/index.js +1 -1
  29. package/dist/parser/colors.js +1 -1
  30. package/dist/parser/index.d.ts +1 -0
  31. package/dist/parser/index.js +1 -1
  32. package/dist/parser/modifiers.d.ts +2 -2
  33. package/dist/parser/modifiers.js +1 -1
  34. package/dist/parser/placeholder.d.ts +36 -0
  35. package/dist/parser/placeholder.js +1 -0
  36. package/dist/parser/placeholder.test.js +1 -0
  37. package/dist/parser/typography.d.ts +1 -0
  38. package/dist/parser/typography.js +1 -1
  39. package/dist/parser/typography.test.js +1 -1
  40. package/dist/runtime.cjs +1 -1
  41. package/dist/runtime.cjs.map +4 -4
  42. package/dist/runtime.d.ts +1 -14
  43. package/dist/runtime.js +1 -1
  44. package/dist/runtime.js.map +4 -4
  45. package/dist/stubs/tw.d.ts +1 -14
  46. package/dist/types/core.d.ts +40 -0
  47. package/dist/types/core.js +0 -0
  48. package/dist/types/index.d.ts +2 -0
  49. package/dist/types/index.js +1 -0
  50. package/dist/types/runtime.d.ts +15 -0
  51. package/dist/types/runtime.js +1 -0
  52. package/dist/types/util.d.ts +3 -0
  53. package/dist/types/util.js +0 -0
  54. package/package.json +1 -1
  55. package/src/babel/index.ts +4 -1169
  56. package/src/babel/plugin.test.ts +482 -0
  57. package/src/babel/plugin.ts +491 -0
  58. package/src/babel/utils/attributeMatchers.ts +71 -0
  59. package/src/babel/utils/componentSupport.ts +68 -0
  60. package/src/babel/utils/dynamicProcessing.ts +223 -0
  61. package/src/babel/utils/modifierProcessing.ts +118 -0
  62. package/src/babel/utils/styleInjection.ts +80 -0
  63. package/src/babel/utils/styleTransforms.test.ts +349 -0
  64. package/src/babel/utils/styleTransforms.ts +258 -0
  65. package/src/babel/utils/twProcessing.ts +124 -0
  66. package/src/components/TextInput.tsx +17 -14
  67. package/src/config/{palettes.ts → tailwind.ts} +2 -2
  68. package/src/index.ts +6 -3
  69. package/src/parser/colors.ts +2 -2
  70. package/src/parser/index.ts +1 -0
  71. package/src/parser/modifiers.ts +10 -4
  72. package/src/parser/placeholder.test.ts +105 -0
  73. package/src/parser/placeholder.ts +78 -0
  74. package/src/parser/typography.test.ts +11 -0
  75. package/src/parser/typography.ts +20 -2
  76. package/src/runtime.ts +1 -16
  77. package/src/stubs/tw.ts +1 -16
  78. package/src/{types.ts → types/core.ts} +0 -4
  79. package/src/types/index.ts +2 -0
  80. package/src/types/runtime.ts +17 -0
  81. package/src/types/util.ts +1 -0
@@ -1,42 +1,6 @@
1
1
  /**
2
- * Babel plugin for react-native-tailwind
3
- * Transforms className props to style props at compile time
2
+ * Main entry point for the Babel plugin
3
+ * Re-exports the plugin from plugin.ts
4
4
  */
5
- import type { PluginObj, PluginPass } from "@babel/core";
6
- import * as BabelTypes from "@babel/types";
7
- import { StyleObject } from "src/types.js";
8
- /**
9
- * Plugin options
10
- */
11
- export type PluginOptions = {
12
- /**
13
- * List of JSX attribute names to transform (in addition to or instead of 'className')
14
- * Supports exact matches and glob patterns:
15
- * - Exact: 'className', 'containerClassName'
16
- * - Glob: '*ClassName' (matches any attribute ending in 'ClassName')
17
- *
18
- * @default ['className', 'contentContainerClassName', 'columnWrapperClassName', 'ListHeaderComponentClassName', 'ListFooterComponentClassName']
19
- */
20
- attributes?: string[];
21
- /**
22
- * Custom identifier name for the generated StyleSheet constant
23
- *
24
- * @default '_twStyles'
25
- */
26
- stylesIdentifier?: string;
27
- };
28
- type PluginState = PluginPass & {
29
- styleRegistry: Map<string, StyleObject>;
30
- hasClassNames: boolean;
31
- hasStyleSheetImport: boolean;
32
- customColors: Record<string, string>;
33
- supportedAttributes: Set<string>;
34
- attributePatterns: RegExp[];
35
- stylesIdentifier: string;
36
- twImportNames: Set<string>;
37
- hasTwImport: boolean;
38
- };
39
- export default function reactNativeTailwindBabelPlugin({ types: t }: {
40
- types: typeof BabelTypes;
41
- }, options?: PluginOptions): PluginObj<PluginState>;
42
- export {};
5
+ export { default } from "./plugin.js";
6
+ export type { PluginOptions } from "./plugin.js";
@@ -1,5 +1,5 @@
1
1
  import { transformSync } from "@babel/core";
2
- import { describe, expect, it } from "vitest";
2
+ import { describe, expect, it, vi } from "vitest";
3
3
  import babelPlugin, { type PluginOptions } from "./index.js";
4
4
 
5
5
  /**
@@ -265,4 +265,217 @@ describe("Babel plugin - className transformation (existing behavior)", () => {
265
265
  expect(output).toContain("_bg_red_500");
266
266
  expect(output).toContain("_m_4_p_2");
267
267
  });
268
+
269
+ it("should merge className with function-based style prop", () => {
270
+ const input = `
271
+ import { TextInput } from 'react-native';
272
+ export function Component() {
273
+ return (
274
+ <TextInput
275
+ className="border border-gray-300 bg-gray-100"
276
+ style={({ focused, disabled }) => [
277
+ baseStyles,
278
+ focused && focusedStyles,
279
+ ]}
280
+ />
281
+ );
282
+ }
283
+ `;
284
+
285
+ const output = transform(input, undefined, true); // Enable JSX
286
+
287
+ // Should have StyleSheet with className styles
288
+ expect(output).toContain("StyleSheet.create");
289
+ // Style keys are sorted alphabetically: bg-gray-100 comes before border
290
+ expect(output).toContain("_bg_gray_100_border_border_gray_300");
291
+
292
+ // Should create a wrapper function that merges both
293
+ // The wrapper should call the original function and merge results
294
+ expect(output).toContain("_state");
295
+ expect(output).toContain("_twStyles._bg_gray_100_border_border_gray_300");
296
+
297
+ // Should not have className in output
298
+ expect(output).not.toContain("className");
299
+
300
+ // Should have a function that accepts state and returns an array
301
+ expect(output).toMatch(/_state\s*=>/);
302
+ });
303
+
304
+ it("should merge dynamic className with function-based style prop", () => {
305
+ const input = `
306
+ import { TextInput } from 'react-native';
307
+ export function Component({ isError }) {
308
+ return (
309
+ <TextInput
310
+ className={\`border \${isError ? 'border-red-500' : 'border-gray-300'}\`}
311
+ style={({ focused }) => [
312
+ baseStyles,
313
+ focused && focusedStyles,
314
+ ]}
315
+ />
316
+ );
317
+ }
318
+ `;
319
+
320
+ const output = transform(input, undefined, true); // Enable JSX
321
+
322
+ // Should have StyleSheet with both className styles
323
+ expect(output).toContain("StyleSheet.create");
324
+ expect(output).toContain("_border");
325
+ expect(output).toContain("_border_red_500");
326
+ expect(output).toContain("_border_gray_300");
327
+
328
+ // Should create a wrapper function that merges dynamic styles with function result
329
+ expect(output).toContain("_state");
330
+
331
+ // Should not have className in output
332
+ expect(output).not.toContain("className");
333
+ });
334
+ });
335
+
336
+ describe("Babel plugin - placeholder: modifier transformation", () => {
337
+ it("should transform placeholder:text-{color} to placeholderTextColor prop", () => {
338
+ const input = `
339
+ import { TextInput } from 'react-native';
340
+ export function Component() {
341
+ return (
342
+ <TextInput
343
+ className="border-2 placeholder:text-gray-400"
344
+ placeholder="Email"
345
+ />
346
+ );
347
+ }
348
+ `;
349
+
350
+ const output = transform(input, undefined, true);
351
+
352
+ // Should have placeholderTextColor prop with correct hex value (from custom palette)
353
+ expect(output).toContain('placeholderTextColor: "#99a1af"');
354
+
355
+ // Should still have style for border-2
356
+ expect(output).toContain("StyleSheet.create");
357
+ expect(output).toContain("_border_2");
358
+
359
+ // Should not have className in output
360
+ expect(output).not.toContain("className");
361
+ });
362
+
363
+ it("should support placeholder colors with opacity", () => {
364
+ const input = `
365
+ import { TextInput } from 'react-native';
366
+ export function Component() {
367
+ return <TextInput className="placeholder:text-red-500/50" />;
368
+ }
369
+ `;
370
+
371
+ const output = transform(input, undefined, true);
372
+
373
+ // Should have 8-digit hex with alpha channel (custom palette red-500, uppercased)
374
+ expect(output).toContain('placeholderTextColor: "#FB2C3680"');
375
+ });
376
+
377
+ it("should support arbitrary placeholder colors", () => {
378
+ const input = `
379
+ import { TextInput } from 'react-native';
380
+ export function Component() {
381
+ return <TextInput className="placeholder:text-[#ff0000]" />;
382
+ }
383
+ `;
384
+
385
+ const output = transform(input, undefined, true);
386
+
387
+ expect(output).toContain('placeholderTextColor: "#ff0000"');
388
+ });
389
+
390
+ it("should combine placeholder: with other modifiers", () => {
391
+ const input = `
392
+ import { TextInput } from 'react-native';
393
+ export function Component() {
394
+ return (
395
+ <TextInput
396
+ className="border-2 focus:border-blue-500 placeholder:text-gray-400"
397
+ placeholder="Email"
398
+ />
399
+ );
400
+ }
401
+ `;
402
+
403
+ const output = transform(input, undefined, true);
404
+
405
+ // Should have placeholderTextColor prop (custom palette gray-400)
406
+ expect(output).toContain('placeholderTextColor: "#99a1af"');
407
+
408
+ // Should have focus: modifier handling (style function)
409
+ expect(output).toContain("focused");
410
+ expect(output).toMatch(/style[\s\S]*=>/); // Style function
411
+
412
+ // Should not have className
413
+ expect(output).not.toContain("className");
414
+ });
415
+
416
+ it("should handle multiple placeholder: classes (last wins)", () => {
417
+ const input = `
418
+ import { TextInput } from 'react-native';
419
+ export function Component() {
420
+ return (
421
+ <TextInput className="placeholder:text-red-500 placeholder:text-blue-500" />
422
+ );
423
+ }
424
+ `;
425
+
426
+ const output = transform(input, undefined, true);
427
+
428
+ // Blue should win (last color, custom palette blue-500)
429
+ expect(output).toContain('placeholderTextColor: "#2b7fff"');
430
+ });
431
+
432
+ it("should ignore non-text utilities in placeholder: modifier", () => {
433
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
434
+
435
+ const input = `
436
+ import { TextInput } from 'react-native';
437
+ export function Component() {
438
+ return (
439
+ <TextInput className="placeholder:font-bold placeholder:text-gray-400" />
440
+ );
441
+ }
442
+ `;
443
+
444
+ const output = transform(input, undefined, true);
445
+
446
+ // Should still have the valid text color (custom palette gray-400)
447
+ expect(output).toContain('placeholderTextColor: "#99a1af"');
448
+
449
+ // Should not have font-bold anywhere
450
+ expect(output).not.toContain("fontWeight");
451
+
452
+ consoleSpy.mockRestore();
453
+ });
454
+
455
+ it("should work with custom colors", () => {
456
+ // Note: This test would require setting up a tailwind.config file
457
+ // For now, we'll skip custom color testing in Babel tests
458
+ // Custom colors are tested in the parser tests
459
+ });
460
+
461
+ it("should not transform placeholder: on non-TextInput elements", () => {
462
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
463
+
464
+ const input = `
465
+ import { View } from 'react-native';
466
+ export function Component() {
467
+ return <View className="placeholder:text-gray-400" />;
468
+ }
469
+ `;
470
+
471
+ const output = transform(input, undefined, true);
472
+
473
+ // Should not have placeholderTextColor prop (View doesn't support it)
474
+ expect(output).not.toContain("placeholderTextColor");
475
+
476
+ // Should warn about unsupported modifier
477
+ // (The warning happens because View doesn't support any modifiers)
478
+
479
+ consoleSpy.mockRestore();
480
+ });
268
481
  });