@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
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Babel plugin for react-native-tailwind
3
+ * Transforms className props to style props at compile time
4
+ */
5
+ import type { PluginObj, PluginPass } from "@babel/core";
6
+ import * as BabelTypes from "@babel/types";
7
+ import type { StyleObject } from "../types/core.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 {};
@@ -1,6 +1,7 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-function */
1
2
  import { transformSync } from "@babel/core";
2
- import { describe, expect, it } from "vitest";
3
- import babelPlugin, { type PluginOptions } from "./index.js";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import babelPlugin, { type PluginOptions } from "./plugin.js";
4
5
 
5
6
  /**
6
7
  * Helper to transform code with the Babel plugin
@@ -265,4 +266,217 @@ describe("Babel plugin - className transformation (existing behavior)", () => {
265
266
  expect(output).toContain("_bg_red_500");
266
267
  expect(output).toContain("_m_4_p_2");
267
268
  });
269
+
270
+ it("should merge className with function-based style prop", () => {
271
+ const input = `
272
+ import { TextInput } from 'react-native';
273
+ export function Component() {
274
+ return (
275
+ <TextInput
276
+ className="border border-gray-300 bg-gray-100"
277
+ style={({ focused, disabled }) => [
278
+ baseStyles,
279
+ focused && focusedStyles,
280
+ ]}
281
+ />
282
+ );
283
+ }
284
+ `;
285
+
286
+ const output = transform(input, undefined, true); // Enable JSX
287
+
288
+ // Should have StyleSheet with className styles
289
+ expect(output).toContain("StyleSheet.create");
290
+ // Style keys are sorted alphabetically: bg-gray-100 comes before border
291
+ expect(output).toContain("_bg_gray_100_border_border_gray_300");
292
+
293
+ // Should create a wrapper function that merges both
294
+ // The wrapper should call the original function and merge results
295
+ expect(output).toContain("_state");
296
+ expect(output).toContain("_twStyles._bg_gray_100_border_border_gray_300");
297
+
298
+ // Should not have className in output
299
+ expect(output).not.toContain("className");
300
+
301
+ // Should have a function that accepts state and returns an array
302
+ expect(output).toMatch(/_state\s*=>/);
303
+ });
304
+
305
+ it("should merge dynamic className with function-based style prop", () => {
306
+ const input = `
307
+ import { TextInput } from 'react-native';
308
+ export function Component({ isError }) {
309
+ return (
310
+ <TextInput
311
+ className={\`border \${isError ? 'border-red-500' : 'border-gray-300'}\`}
312
+ style={({ focused }) => [
313
+ baseStyles,
314
+ focused && focusedStyles,
315
+ ]}
316
+ />
317
+ );
318
+ }
319
+ `;
320
+
321
+ const output = transform(input, undefined, true); // Enable JSX
322
+
323
+ // Should have StyleSheet with both className styles
324
+ expect(output).toContain("StyleSheet.create");
325
+ expect(output).toContain("_border");
326
+ expect(output).toContain("_border_red_500");
327
+ expect(output).toContain("_border_gray_300");
328
+
329
+ // Should create a wrapper function that merges dynamic styles with function result
330
+ expect(output).toContain("_state");
331
+
332
+ // Should not have className in output
333
+ expect(output).not.toContain("className");
334
+ });
335
+ });
336
+
337
+ describe("Babel plugin - placeholder: modifier transformation", () => {
338
+ it("should transform placeholder:text-{color} to placeholderTextColor prop", () => {
339
+ const input = `
340
+ import { TextInput } from 'react-native';
341
+ export function Component() {
342
+ return (
343
+ <TextInput
344
+ className="border-2 placeholder:text-gray-400"
345
+ placeholder="Email"
346
+ />
347
+ );
348
+ }
349
+ `;
350
+
351
+ const output = transform(input, undefined, true);
352
+
353
+ // Should have placeholderTextColor prop with correct hex value (from custom palette)
354
+ expect(output).toContain('placeholderTextColor: "#99a1af"');
355
+
356
+ // Should still have style for border-2
357
+ expect(output).toContain("StyleSheet.create");
358
+ expect(output).toContain("_border_2");
359
+
360
+ // Should not have className in output
361
+ expect(output).not.toContain("className");
362
+ });
363
+
364
+ it("should support placeholder colors with opacity", () => {
365
+ const input = `
366
+ import { TextInput } from 'react-native';
367
+ export function Component() {
368
+ return <TextInput className="placeholder:text-red-500/50" />;
369
+ }
370
+ `;
371
+
372
+ const output = transform(input, undefined, true);
373
+
374
+ // Should have 8-digit hex with alpha channel (custom palette red-500, uppercased)
375
+ expect(output).toContain('placeholderTextColor: "#FB2C3680"');
376
+ });
377
+
378
+ it("should support arbitrary placeholder colors", () => {
379
+ const input = `
380
+ import { TextInput } from 'react-native';
381
+ export function Component() {
382
+ return <TextInput className="placeholder:text-[#ff0000]" />;
383
+ }
384
+ `;
385
+
386
+ const output = transform(input, undefined, true);
387
+
388
+ expect(output).toContain('placeholderTextColor: "#ff0000"');
389
+ });
390
+
391
+ it("should combine placeholder: with other modifiers", () => {
392
+ const input = `
393
+ import { TextInput } from 'react-native';
394
+ export function Component() {
395
+ return (
396
+ <TextInput
397
+ className="border-2 focus:border-blue-500 placeholder:text-gray-400"
398
+ placeholder="Email"
399
+ />
400
+ );
401
+ }
402
+ `;
403
+
404
+ const output = transform(input, undefined, true);
405
+
406
+ // Should have placeholderTextColor prop (custom palette gray-400)
407
+ expect(output).toContain('placeholderTextColor: "#99a1af"');
408
+
409
+ // Should have focus: modifier handling (style function)
410
+ expect(output).toContain("focused");
411
+ expect(output).toMatch(/style[\s\S]*=>/); // Style function
412
+
413
+ // Should not have className
414
+ expect(output).not.toContain("className");
415
+ });
416
+
417
+ it("should handle multiple placeholder: classes (last wins)", () => {
418
+ const input = `
419
+ import { TextInput } from 'react-native';
420
+ export function Component() {
421
+ return (
422
+ <TextInput className="placeholder:text-red-500 placeholder:text-blue-500" />
423
+ );
424
+ }
425
+ `;
426
+
427
+ const output = transform(input, undefined, true);
428
+
429
+ // Blue should win (last color, custom palette blue-500)
430
+ expect(output).toContain('placeholderTextColor: "#2b7fff"');
431
+ });
432
+
433
+ it("should ignore non-text utilities in placeholder: modifier", () => {
434
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
435
+
436
+ const input = `
437
+ import { TextInput } from 'react-native';
438
+ export function Component() {
439
+ return (
440
+ <TextInput className="placeholder:font-bold placeholder:text-gray-400" />
441
+ );
442
+ }
443
+ `;
444
+
445
+ const output = transform(input, undefined, true);
446
+
447
+ // Should still have the valid text color (custom palette gray-400)
448
+ expect(output).toContain('placeholderTextColor: "#99a1af"');
449
+
450
+ // Should not have font-bold anywhere
451
+ expect(output).not.toContain("fontWeight");
452
+
453
+ consoleSpy.mockRestore();
454
+ });
455
+
456
+ it.skip("should work with custom colors", () => {
457
+ // Note: This test would require setting up a tailwind.config file
458
+ // For now, we'll skip custom color testing in Babel tests
459
+ // Custom colors are tested in the parser tests
460
+ });
461
+
462
+ it("should not transform placeholder: on non-TextInput elements", () => {
463
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
464
+
465
+ const input = `
466
+ import { View } from 'react-native';
467
+ export function Component() {
468
+ return <View className="placeholder:text-gray-400" />;
469
+ }
470
+ `;
471
+
472
+ const output = transform(input, undefined, true);
473
+
474
+ // Should not have placeholderTextColor prop (View doesn't support it)
475
+ expect(output).not.toContain("placeholderTextColor");
476
+
477
+ // Should warn about unsupported modifier
478
+ // (The warning happens because View doesn't support any modifiers)
479
+
480
+ consoleSpy.mockRestore();
481
+ });
268
482
  });