@react-native-styled-system/core 0.0.5

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 (152) hide show
  1. package/bin/theme-gen.js +149 -0
  2. package/lib/commonjs/@types/SxProps.js +12 -0
  3. package/lib/commonjs/@types/SxProps.js.map +1 -0
  4. package/lib/commonjs/@types/ThemedDict.js +14 -0
  5. package/lib/commonjs/@types/ThemedDict.js.map +1 -0
  6. package/lib/commonjs/@types/ThemedTypings.js +6 -0
  7. package/lib/commonjs/@types/ThemedTypings.js.map +1 -0
  8. package/lib/commonjs/@types/Token.js +6 -0
  9. package/lib/commonjs/@types/Token.js.map +1 -0
  10. package/lib/commonjs/hook/useSx.js +82 -0
  11. package/lib/commonjs/hook/useSx.js.map +1 -0
  12. package/lib/commonjs/hook/useSxStyle.js +29 -0
  13. package/lib/commonjs/hook/useSxStyle.js.map +1 -0
  14. package/lib/commonjs/hook/useSxTokens.js +33 -0
  15. package/lib/commonjs/hook/useSxTokens.js.map +1 -0
  16. package/lib/commonjs/index.js +72 -0
  17. package/lib/commonjs/index.js.map +1 -0
  18. package/lib/commonjs/internal/TokenParser/ColorsParser.js +21 -0
  19. package/lib/commonjs/internal/TokenParser/ColorsParser.js.map +1 -0
  20. package/lib/commonjs/internal/TokenParser/RadiiParser.js +41 -0
  21. package/lib/commonjs/internal/TokenParser/RadiiParser.js.map +1 -0
  22. package/lib/commonjs/internal/TokenParser/SizesParser.js +41 -0
  23. package/lib/commonjs/internal/TokenParser/SizesParser.js.map +1 -0
  24. package/lib/commonjs/internal/TokenParser/SpaceParser.js +90 -0
  25. package/lib/commonjs/internal/TokenParser/SpaceParser.js.map +1 -0
  26. package/lib/commonjs/internal/TokenParser/TokenParser.js +23 -0
  27. package/lib/commonjs/internal/TokenParser/TokenParser.js.map +1 -0
  28. package/lib/commonjs/internal/TokenParser/TypographyParser.js +23 -0
  29. package/lib/commonjs/internal/TokenParser/TypographyParser.js.map +1 -0
  30. package/lib/commonjs/internal/useStableCallback.js +34 -0
  31. package/lib/commonjs/internal/useStableCallback.js.map +1 -0
  32. package/lib/commonjs/internal/util/fillNullishThemeKey.js +18 -0
  33. package/lib/commonjs/internal/util/fillNullishThemeKey.js.map +1 -0
  34. package/lib/commonjs/internal/util/fillStyleIfNotNullish.js +21 -0
  35. package/lib/commonjs/internal/util/fillStyleIfNotNullish.js.map +1 -0
  36. package/lib/commonjs/internal/util/parsePxSuffixNumber.js +16 -0
  37. package/lib/commonjs/internal/util/parsePxSuffixNumber.js.map +1 -0
  38. package/lib/commonjs/internal/util/printWarning.js +18 -0
  39. package/lib/commonjs/internal/util/printWarning.js.map +1 -0
  40. package/lib/commonjs/provider/StyledSystemProvider.js +25 -0
  41. package/lib/commonjs/provider/StyledSystemProvider.js.map +1 -0
  42. package/lib/commonjs/util/propsToThemedStyle.js +138 -0
  43. package/lib/commonjs/util/propsToThemedStyle.js.map +1 -0
  44. package/lib/module/@types/SxProps.js +6 -0
  45. package/lib/module/@types/SxProps.js.map +1 -0
  46. package/lib/module/@types/ThemedDict.js +8 -0
  47. package/lib/module/@types/ThemedDict.js.map +1 -0
  48. package/lib/module/@types/ThemedTypings.js +2 -0
  49. package/lib/module/@types/ThemedTypings.js.map +1 -0
  50. package/lib/module/@types/Token.js +2 -0
  51. package/lib/module/@types/Token.js.map +1 -0
  52. package/lib/module/hook/useSx.js +75 -0
  53. package/lib/module/hook/useSx.js.map +1 -0
  54. package/lib/module/hook/useSxStyle.js +22 -0
  55. package/lib/module/hook/useSxStyle.js.map +1 -0
  56. package/lib/module/hook/useSxTokens.js +26 -0
  57. package/lib/module/hook/useSxTokens.js.map +1 -0
  58. package/lib/module/index.js +7 -0
  59. package/lib/module/index.js.map +1 -0
  60. package/lib/module/internal/TokenParser/ColorsParser.js +14 -0
  61. package/lib/module/internal/TokenParser/ColorsParser.js.map +1 -0
  62. package/lib/module/internal/TokenParser/RadiiParser.js +34 -0
  63. package/lib/module/internal/TokenParser/RadiiParser.js.map +1 -0
  64. package/lib/module/internal/TokenParser/SizesParser.js +34 -0
  65. package/lib/module/internal/TokenParser/SizesParser.js.map +1 -0
  66. package/lib/module/internal/TokenParser/SpaceParser.js +82 -0
  67. package/lib/module/internal/TokenParser/SpaceParser.js.map +1 -0
  68. package/lib/module/internal/TokenParser/TokenParser.js +16 -0
  69. package/lib/module/internal/TokenParser/TokenParser.js.map +1 -0
  70. package/lib/module/internal/TokenParser/TypographyParser.js +15 -0
  71. package/lib/module/internal/TokenParser/TypographyParser.js.map +1 -0
  72. package/lib/module/internal/useStableCallback.js +27 -0
  73. package/lib/module/internal/useStableCallback.js.map +1 -0
  74. package/lib/module/internal/util/fillNullishThemeKey.js +11 -0
  75. package/lib/module/internal/util/fillNullishThemeKey.js.map +1 -0
  76. package/lib/module/internal/util/fillStyleIfNotNullish.js +14 -0
  77. package/lib/module/internal/util/fillStyleIfNotNullish.js.map +1 -0
  78. package/lib/module/internal/util/parsePxSuffixNumber.js +10 -0
  79. package/lib/module/internal/util/parsePxSuffixNumber.js.map +1 -0
  80. package/lib/module/internal/util/printWarning.js +11 -0
  81. package/lib/module/internal/util/printWarning.js.map +1 -0
  82. package/lib/module/provider/StyledSystemProvider.js +17 -0
  83. package/lib/module/provider/StyledSystemProvider.js.map +1 -0
  84. package/lib/module/util/propsToThemedStyle.js +131 -0
  85. package/lib/module/util/propsToThemedStyle.js.map +1 -0
  86. package/lib/typescript/@types/SxProps.d.ts +140 -0
  87. package/lib/typescript/@types/SxProps.d.ts.map +1 -0
  88. package/lib/typescript/@types/ThemedDict.d.ts +16 -0
  89. package/lib/typescript/@types/ThemedDict.d.ts.map +1 -0
  90. package/lib/typescript/@types/ThemedTypings.d.ts +9 -0
  91. package/lib/typescript/@types/ThemedTypings.d.ts.map +1 -0
  92. package/lib/typescript/@types/Token.d.ts +17 -0
  93. package/lib/typescript/@types/Token.d.ts.map +1 -0
  94. package/lib/typescript/hook/useSx.d.ts +307 -0
  95. package/lib/typescript/hook/useSx.d.ts.map +1 -0
  96. package/lib/typescript/hook/useSxStyle.d.ts +8 -0
  97. package/lib/typescript/hook/useSxStyle.d.ts.map +1 -0
  98. package/lib/typescript/hook/useSxTokens.d.ts +7 -0
  99. package/lib/typescript/hook/useSxTokens.d.ts.map +1 -0
  100. package/lib/typescript/index.d.ts +7 -0
  101. package/lib/typescript/index.d.ts.map +1 -0
  102. package/lib/typescript/internal/TokenParser/ColorsParser.d.ts +4 -0
  103. package/lib/typescript/internal/TokenParser/ColorsParser.d.ts.map +1 -0
  104. package/lib/typescript/internal/TokenParser/RadiiParser.d.ts +4 -0
  105. package/lib/typescript/internal/TokenParser/RadiiParser.d.ts.map +1 -0
  106. package/lib/typescript/internal/TokenParser/SizesParser.d.ts +4 -0
  107. package/lib/typescript/internal/TokenParser/SizesParser.d.ts.map +1 -0
  108. package/lib/typescript/internal/TokenParser/SpaceParser.d.ts +5 -0
  109. package/lib/typescript/internal/TokenParser/SpaceParser.d.ts.map +1 -0
  110. package/lib/typescript/internal/TokenParser/TokenParser.d.ts +10 -0
  111. package/lib/typescript/internal/TokenParser/TokenParser.d.ts.map +1 -0
  112. package/lib/typescript/internal/TokenParser/TypographyParser.d.ts +4 -0
  113. package/lib/typescript/internal/TokenParser/TypographyParser.d.ts.map +1 -0
  114. package/lib/typescript/internal/useStableCallback.d.ts +2 -0
  115. package/lib/typescript/internal/useStableCallback.d.ts.map +1 -0
  116. package/lib/typescript/internal/util/fillNullishThemeKey.d.ts +3 -0
  117. package/lib/typescript/internal/util/fillNullishThemeKey.d.ts.map +1 -0
  118. package/lib/typescript/internal/util/fillStyleIfNotNullish.d.ts +4 -0
  119. package/lib/typescript/internal/util/fillStyleIfNotNullish.d.ts.map +1 -0
  120. package/lib/typescript/internal/util/parsePxSuffixNumber.d.ts +2 -0
  121. package/lib/typescript/internal/util/parsePxSuffixNumber.d.ts.map +1 -0
  122. package/lib/typescript/internal/util/printWarning.d.ts +2 -0
  123. package/lib/typescript/internal/util/printWarning.d.ts.map +1 -0
  124. package/lib/typescript/provider/StyledSystemProvider.d.ts +13 -0
  125. package/lib/typescript/provider/StyledSystemProvider.d.ts.map +1 -0
  126. package/lib/typescript/util/propsToThemedStyle.d.ts +10 -0
  127. package/lib/typescript/util/propsToThemedStyle.d.ts.map +1 -0
  128. package/package.json +78 -0
  129. package/src/@types/SxProps.ts +255 -0
  130. package/src/@types/ThemedDict.ts +16 -0
  131. package/src/@types/ThemedTypings.ts +9 -0
  132. package/src/@types/Token.ts +21 -0
  133. package/src/hook/useSx.test.ts +348 -0
  134. package/src/hook/useSx.ts +94 -0
  135. package/src/hook/useSxStyle.test.ts +57 -0
  136. package/src/hook/useSxStyle.ts +32 -0
  137. package/src/hook/useSxTokens.test.ts +59 -0
  138. package/src/hook/useSxTokens.ts +42 -0
  139. package/src/index.ts +6 -0
  140. package/src/internal/TokenParser/ColorsParser.ts +18 -0
  141. package/src/internal/TokenParser/RadiiParser.ts +44 -0
  142. package/src/internal/TokenParser/SizesParser.ts +44 -0
  143. package/src/internal/TokenParser/SpaceParser.ts +95 -0
  144. package/src/internal/TokenParser/TokenParser.ts +18 -0
  145. package/src/internal/TokenParser/TypographyParser.ts +24 -0
  146. package/src/internal/useStableCallback.ts +29 -0
  147. package/src/internal/util/fillNullishThemeKey.ts +12 -0
  148. package/src/internal/util/fillStyleIfNotNullish.ts +26 -0
  149. package/src/internal/util/parsePxSuffixNumber.ts +10 -0
  150. package/src/internal/util/printWarning.ts +11 -0
  151. package/src/provider/StyledSystemProvider.tsx +23 -0
  152. package/src/util/propsToThemedStyle.ts +168 -0
@@ -0,0 +1,348 @@
1
+ import type { StyleProp } from 'react-native';
2
+ import { StyleSheet } from 'react-native';
3
+ import { is } from '@mj-studio/js-util';
4
+ import { renderHook } from '@testing-library/react-hooks';
5
+
6
+ import type { TextSxProps } from '../@types/SxProps';
7
+ import type { ThemedDict } from '../@types/ThemedDict';
8
+ import { emptyThemedDict } from '../@types/ThemedDict';
9
+ import type { ThemedStyleType } from '../util/propsToThemedStyle';
10
+
11
+ import type { StyleFallback, StyleTransform } from './useSx';
12
+ import { useSx } from './useSx';
13
+
14
+ export function expectResult(
15
+ theme: ThemedDict,
16
+ props: { style?: StyleProp<any> } & TextSxProps & Record<string, any>,
17
+ {
18
+ expectation,
19
+ filteredPropsExpectation,
20
+ styleType,
21
+ transform,
22
+ fallback,
23
+ }: {
24
+ expectation: object;
25
+ filteredPropsExpectation?: object;
26
+ styleType?: ThemedStyleType;
27
+ transform?: StyleTransform;
28
+ fallback?: StyleFallback;
29
+ },
30
+ ) {
31
+ const {
32
+ result: {
33
+ current: { getStyle, filteredProps },
34
+ },
35
+ } = renderHook(() => useSx(props, { theme, styleType, transform, fallback }));
36
+
37
+ if (expectation) {
38
+ expect(StyleSheet.flatten(getStyle())).toEqual(expectation);
39
+ }
40
+
41
+ if (filteredPropsExpectation) {
42
+ expect(filteredProps).toEqual(filteredPropsExpectation);
43
+ }
44
+ }
45
+
46
+ const emptyTheme = emptyThemedDict;
47
+
48
+ const baseTheme: ThemedDict = {
49
+ colors: {
50
+ red: 'red',
51
+ blue: 'blue',
52
+ green: 'green',
53
+ },
54
+ sizes: {
55
+ 1: 4,
56
+ 2: 8,
57
+ pagePadding: 20,
58
+ full: '100%',
59
+ },
60
+ space: { 1: 4, 2: 8, pagePadding: 20, full: '100%' },
61
+ radii: {
62
+ sm: 8,
63
+ md: 12,
64
+ lg: 20,
65
+ },
66
+ typography: {
67
+ title: {
68
+ fontFamily: 'Noto Sans',
69
+ fontSize: 14,
70
+ fontStyle: 'normal',
71
+ fontWeight: '400',
72
+ },
73
+ },
74
+ };
75
+
76
+ describe('simple usages', () => {
77
+ it('handle empty', () => {
78
+ expectResult(baseTheme, {}, { expectation: {} });
79
+ });
80
+
81
+ it('colors match', () => {
82
+ expectResult(baseTheme, { bg: 'red' }, { expectation: { backgroundColor: 'red' } });
83
+ expectResult(baseTheme, { bg: '#ffffff' }, { expectation: { backgroundColor: '#ffffff' } });
84
+ });
85
+
86
+ it('border widths should be match', () => {
87
+ expectResult(
88
+ emptyTheme,
89
+ { borderTopWidth: 1, borderWidth: 1 },
90
+ { expectation: { borderTopWidth: 1, borderWidth: 1 } },
91
+ );
92
+
93
+ expectResult(baseTheme, { borderLeftWidth: 1 }, { expectation: { borderLeftWidth: 1 } });
94
+ });
95
+ });
96
+
97
+ describe('edge case', () => {
98
+ it('invalid px suffix', () => {
99
+ expectResult(baseTheme, { w: 'undefinedpx' as any }, { expectation: {} });
100
+ expectResult(baseTheme, { w: 'nullpx' as any }, { expectation: {} });
101
+ expectResult(baseTheme, { w: '-px' as any }, { expectation: {} });
102
+ expectResult(baseTheme, { w: '-1px' as any }, { expectation: { width: -1 } });
103
+ });
104
+
105
+ it('if token is undefined, baseStyle is returned', () => {
106
+ expectResult(undefined as any, {}, { expectation: {} });
107
+ expectResult(undefined as any, { style: { width: 1 } }, { expectation: { width: 1 } });
108
+ });
109
+
110
+ it('if prop and getStyle sx parameter are nullish, return undefined', () => {
111
+ const {
112
+ result: {
113
+ current: { getStyle },
114
+ },
115
+ } = renderHook(() => useSx(null, { theme: baseTheme }));
116
+
117
+ return expect(getStyle()).toEqual(undefined);
118
+ });
119
+
120
+ it('if prop is nullish and fallback parameter is not nullish, return fallback style', () => {
121
+ const {
122
+ result: {
123
+ current: { getStyle },
124
+ },
125
+ } = renderHook(() => useSx(null, { theme: baseTheme, fallback: { w: 1 } }));
126
+
127
+ return expect(getStyle()).toEqual({ width: 4 });
128
+ });
129
+
130
+ it("gap doesn't accept not number value", () => {
131
+ expectResult(baseTheme, { gap: 'full' as any }, { expectation: {} });
132
+ });
133
+ });
134
+
135
+ describe('space parsing', () => {
136
+ it('if number token not found, return itself', () => {
137
+ expectResult(emptyTheme, { m: 1 }, { expectation: { margin: 1 } });
138
+ });
139
+
140
+ it('px suffix string, return parsed pixel number value', () => {
141
+ expectResult(emptyTheme, { m: '15px' }, { expectation: { margin: 15 } });
142
+ expectResult(emptyTheme, { m: '-1.5px' }, { expectation: { margin: -1.5 } });
143
+ expectResult(emptyTheme, { m: '-0px' }, { expectation: { margin: -0 } });
144
+ expectResult(emptyTheme, { m: '0px' }, { expectation: { margin: 0 } });
145
+ expectResult(emptyTheme, { m: '-1px' }, { expectation: { margin: -1 } });
146
+ });
147
+
148
+ it('percentage', () => {
149
+ expectResult(emptyTheme, { m: '100%' }, { expectation: { margin: '100%' } });
150
+ });
151
+
152
+ it('number, string parsing', () => {
153
+ expectResult(baseTheme, { m: 1 }, { expectation: { margin: 4 } });
154
+ expectResult(baseTheme, { m: '1' }, { expectation: { margin: 4 } });
155
+ });
156
+
157
+ it('negative works', () => {
158
+ expectResult(baseTheme, { m: -1 }, { expectation: { margin: -4 } });
159
+ });
160
+
161
+ it('negative number as key', () => {
162
+ expectResult(baseTheme, { m: -1 }, { expectation: { margin: -4 } });
163
+ });
164
+
165
+ it('minus prefixed string', () => {
166
+ expectResult(baseTheme, { m: '-pagePadding' as any }, { expectation: { margin: -20 } });
167
+ });
168
+
169
+ it('gap accepts number value', () => {
170
+ expectResult(baseTheme, { gap: 1 }, { expectation: { gap: 4 } });
171
+ });
172
+ });
173
+
174
+ describe('sizes parsing', () => {
175
+ it("negative doesn't work", () => {
176
+ expectResult(baseTheme, { w: -1 }, { expectation: { width: -1 } });
177
+ });
178
+ });
179
+
180
+ describe('shortcut priority', () => {
181
+ it('backgroundColor', () => {
182
+ expectResult(
183
+ baseTheme,
184
+ { bg: 'red', backgroundColor: 'blue' },
185
+ { expectation: { backgroundColor: 'blue' } },
186
+ );
187
+
188
+ expectResult(
189
+ baseTheme,
190
+ { bg: 'red', backgroundColor: '#ffffff' },
191
+ { expectation: { backgroundColor: '#ffffff' } },
192
+ );
193
+ });
194
+
195
+ it('width', () => {
196
+ expectResult(emptyTheme, { w: 1, width: 2 }, { expectation: { width: 2 } });
197
+ });
198
+
199
+ it('borderRadius', () => {
200
+ expectResult(emptyTheme, { radius: 1, borderRadius: 2 }, { expectation: { borderRadius: 2 } });
201
+ });
202
+
203
+ it('align', () => {
204
+ expectResult(
205
+ emptyTheme,
206
+ { textAlign: 'center', align: 'left' },
207
+ { expectation: { textAlign: 'center' }, styleType: 'TextStyle' },
208
+ );
209
+ });
210
+ });
211
+
212
+ describe('style parse priority', () => {
213
+ it('prop property > fallback property', () => {
214
+ expectResult(
215
+ emptyTheme,
216
+ { w: 1 },
217
+ {
218
+ fallback: {
219
+ w: 2,
220
+ },
221
+ expectation: { width: 1 },
222
+ },
223
+ );
224
+ });
225
+
226
+ it('sx prop property > prop property', () => {
227
+ expectResult(emptyTheme, { w: 1, sx: { w: 2 } }, { expectation: { width: 2 } });
228
+ });
229
+
230
+ it('style prop property > sx prop property', () => {
231
+ expectResult(emptyTheme, { style: { width: 1 }, sx: { w: 2 } }, { expectation: { width: 1 } });
232
+ });
233
+ });
234
+
235
+ describe('radii', () => {
236
+ it('simple case check', () => {
237
+ expectResult(baseTheme, { topLeftRadius: '1px' }, { expectation: { borderTopLeftRadius: 1 } });
238
+ expectResult(baseTheme, { topLeftRadius: '1' }, { expectation: { borderTopLeftRadius: 1 } });
239
+ expectResult(
240
+ baseTheme,
241
+ { topLeftRadius: 'sm' as any },
242
+ { expectation: { borderTopLeftRadius: 8 } },
243
+ );
244
+ });
245
+ });
246
+
247
+ describe('filteredProps', () => {
248
+ it("filteredProps shouldn't have not style props - ViewStyle", () => {
249
+ expectResult(
250
+ baseTheme,
251
+ { shouldBePersist: true, backgroundColor: 'red' },
252
+ {
253
+ expectation: { backgroundColor: 'red' },
254
+ filteredPropsExpectation: { shouldBePersist: true },
255
+ },
256
+ );
257
+ });
258
+
259
+ it("styleTheme includes only ViewStyle, then props in TextStyle shouldn't be filtered", () => {
260
+ expectResult(
261
+ baseTheme,
262
+ { color: 'red' },
263
+ {
264
+ expectation: {},
265
+ filteredPropsExpectation: { color: 'red' },
266
+ },
267
+ );
268
+ });
269
+
270
+ it("filteredProps shouldn't have not style props - TextStyle", () => {
271
+ expectResult(
272
+ baseTheme,
273
+ { color: 'red' },
274
+ {
275
+ expectation: { color: 'red' },
276
+ filteredPropsExpectation: {},
277
+ styleType: 'TextStyle',
278
+ },
279
+ );
280
+ });
281
+ });
282
+
283
+ describe('TextStyle', () => {
284
+ it('TextStyle props are parsed', () => {
285
+ expectResult(
286
+ baseTheme,
287
+ { textShadowColor: 'red' },
288
+ {
289
+ expectation: { textShadowColor: 'red' },
290
+ styleType: 'TextStyle',
291
+ },
292
+ );
293
+ });
294
+
295
+ it('Typography', () => {
296
+ expectResult(
297
+ baseTheme,
298
+ { t: 'title' },
299
+ {
300
+ expectation: {
301
+ fontFamily: 'Noto Sans',
302
+ fontSize: 14,
303
+ fontStyle: 'normal',
304
+ fontWeight: '400',
305
+ },
306
+ styleType: 'TextStyle',
307
+ },
308
+ );
309
+ });
310
+
311
+ it('Typography has lower priority than general props', () => {
312
+ expectResult(
313
+ baseTheme,
314
+ { t: 'title', fontFamily: 'Test' },
315
+ {
316
+ expectation: {
317
+ fontFamily: 'Test',
318
+ fontSize: 14,
319
+ fontStyle: 'normal',
320
+ fontWeight: '400',
321
+ },
322
+ styleType: 'TextStyle',
323
+ },
324
+ );
325
+ });
326
+ });
327
+
328
+ describe('transform', () => {
329
+ it('property should be transform', () => {
330
+ expectResult(
331
+ baseTheme,
332
+ { mt: 2, mb: 2 },
333
+ {
334
+ expectation: { marginTop: 8, marginBottom: 8, marginHorizontal: 16 },
335
+ transform: ({ marginTop, marginBottom }) => ({
336
+ mx: (is.number(marginTop) ? marginTop : 0) + (is.number(marginBottom) ? marginBottom : 0),
337
+ }),
338
+ },
339
+ );
340
+ });
341
+
342
+ it('transform output should be honored even all props are null', () => {
343
+ expectResult(baseTheme, undefined as any, {
344
+ expectation: { marginHorizontal: 4 },
345
+ transform: () => ({ mx: 1 }),
346
+ });
347
+ });
348
+ });
@@ -0,0 +1,94 @@
1
+ import { useContext, useMemo } from 'react';
2
+ import type { StyleProp, TextStyle, ViewStyle } from 'react-native';
3
+ import { StyleSheet } from 'react-native';
4
+ import { is } from '@mj-studio/js-util';
5
+
6
+ import type { SxPropsKeys, TextSxProps } from '../@types/SxProps';
7
+ import { _textStylePropList, _viewStylePropList } from '../@types/SxProps';
8
+ import type { ThemedDict } from '../@types/ThemedDict';
9
+ import { useStableCallback } from '../internal/useStableCallback';
10
+ import { printWarning } from '../internal/util/printWarning';
11
+ import { StyledSystemContext } from '../provider/StyledSystemProvider';
12
+ import type { ThemedStyleType } from '../util/propsToThemedStyle';
13
+ import { propsToThemedStyle } from '../util/propsToThemedStyle';
14
+
15
+ type Props = { style?: StyleProp<any> } & TextSxProps;
16
+
17
+ export type StyleTransform = (style: TextStyle) => TextSxProps;
18
+ export type StyleFallback = Omit<TextSxProps, 'sx'>;
19
+ export type UseSxOptions = {
20
+ theme?: ThemedDict;
21
+ styleType?: ThemedStyleType;
22
+ transform?: StyleTransform;
23
+ fallback?: StyleFallback;
24
+ };
25
+ const defaultUseSxOptions: UseSxOptions = { styleType: 'ViewStyle' };
26
+ export const useSx = <S extends ViewStyle = ViewStyle, P extends Props = Props>(
27
+ props?: P | null,
28
+ {
29
+ theme: optionTheme,
30
+ styleType = defaultUseSxOptions.styleType,
31
+ transform = defaultUseSxOptions.transform,
32
+ fallback,
33
+ }: UseSxOptions = defaultUseSxOptions,
34
+ ) => {
35
+ const styledSystemContext = useContext(StyledSystemContext);
36
+
37
+ const getStyle = useStableCallback((): StyleProp<S> | undefined => {
38
+ const skip = !props && !fallback;
39
+ const theme = optionTheme ?? styledSystemContext?.theme;
40
+
41
+ if (skip) {
42
+ if (is.function(transform)) {
43
+ return propsToThemedStyle({ theme, sx: transform({}) }) as S;
44
+ } else {
45
+ return;
46
+ }
47
+ }
48
+
49
+ if (!theme) {
50
+ printWarning('theme not found from useSx, undefined will be returned.');
51
+
52
+ return;
53
+ }
54
+
55
+ // caution: priority should be ordered correctly.
56
+ const mergedSx: TextSxProps = { ...fallback, ...props, ...props?.sx };
57
+
58
+ const mergedSxStyle = propsToThemedStyle({
59
+ theme,
60
+ sx: mergedSx,
61
+ styleType,
62
+ });
63
+
64
+ const composedStyle = !mergedSxStyle
65
+ ? props?.style
66
+ : props?.style
67
+ ? StyleSheet.compose(mergedSxStyle, props.style)
68
+ : mergedSxStyle;
69
+
70
+ if (is.function(transform)) {
71
+ const transformedSx = transform(StyleSheet.flatten(composedStyle));
72
+
73
+ return StyleSheet.compose(
74
+ composedStyle,
75
+ propsToThemedStyle({ theme, sx: transformedSx, styleType }) as S,
76
+ );
77
+ } else {
78
+ return composedStyle;
79
+ }
80
+ });
81
+
82
+ const filteredProps: Omit<P, SxPropsKeys | 'style'> = useMemo(() => {
83
+ const ret = { ...props } as Omit<P, SxPropsKeys | 'style'>;
84
+
85
+ _viewStylePropList.forEach((keyName) => delete ret[keyName]);
86
+ if (styleType === 'TextStyle') {
87
+ _textStylePropList.forEach((keyName) => delete ret[keyName]);
88
+ }
89
+
90
+ return ret;
91
+ }, [props, styleType]);
92
+
93
+ return { getStyle, filteredProps };
94
+ };
@@ -0,0 +1,57 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { renderHook } from '@testing-library/react-hooks';
3
+
4
+ import type { TextSxProps } from '../@types/SxProps';
5
+ import type { ThemedDict } from '../@types/ThemedDict';
6
+
7
+ import { useSxStyle } from './useSxStyle';
8
+
9
+ export function expectResult(
10
+ theme: ThemedDict,
11
+ sx: TextSxProps,
12
+ {
13
+ expectation,
14
+ }: {
15
+ expectation: object;
16
+ },
17
+ ) {
18
+ const {
19
+ result: { current },
20
+ } = renderHook(() => useSxStyle({ theme }));
21
+
22
+ expect(StyleSheet.flatten(current(sx))).toEqual(expectation);
23
+ }
24
+
25
+ const baseTheme: ThemedDict = {
26
+ colors: {
27
+ red: 'red',
28
+ blue: 'blue',
29
+ green: 'green',
30
+ },
31
+ sizes: {
32
+ 1: 4,
33
+ 2: 8,
34
+ pagePadding: 20,
35
+ full: '100%',
36
+ },
37
+ space: { 1: 4, 2: 8, pagePadding: 20, full: '100%' },
38
+ radii: {
39
+ sm: 8,
40
+ md: 12,
41
+ lg: 20,
42
+ },
43
+ typography: {
44
+ title: {
45
+ fontFamily: 'Noto Sans',
46
+ fontSize: 14,
47
+ fontStyle: 'normal',
48
+ fontWeight: '400',
49
+ },
50
+ },
51
+ };
52
+
53
+ describe('general case', () => {
54
+ it('', () => {
55
+ expectResult(baseTheme, { mt: 2 }, { expectation: { marginTop: 8 } });
56
+ });
57
+ });
@@ -0,0 +1,32 @@
1
+ import { useContext } from 'react';
2
+ import type { StyleProp, TextStyle } from 'react-native';
3
+
4
+ import type { TextSxProps } from '../@types/SxProps';
5
+ import type { ThemedDict } from '../@types/ThemedDict';
6
+ import { printWarning } from '../internal/util/printWarning';
7
+ import { StyledSystemContext } from '../provider/StyledSystemProvider';
8
+ import { propsToThemedStyle } from '../util/propsToThemedStyle';
9
+
10
+ export type UseSxStyleOptions = {
11
+ theme?: ThemedDict;
12
+ };
13
+ const defaultOptions: UseSxStyleOptions = {};
14
+
15
+ export const useSxStyle = ({ theme: optionTheme }: UseSxStyleOptions = defaultOptions) => {
16
+ const styledSystemContext = useContext(StyledSystemContext);
17
+
18
+ return (sx: TextSxProps): StyleProp<TextStyle> => {
19
+ const theme = optionTheme ?? styledSystemContext?.theme;
20
+
21
+ if (!theme) {
22
+ printWarning('theme not found from useSxStyle, empty style will be returned.');
23
+
24
+ return {};
25
+ }
26
+
27
+ return propsToThemedStyle({
28
+ theme,
29
+ sx,
30
+ });
31
+ };
32
+ };
@@ -0,0 +1,59 @@
1
+ import { renderHook } from '@testing-library/react-hooks';
2
+
3
+ import type { ThemedDict } from '../@types/ThemedDict';
4
+ import type { ThemedTypings } from '../@types/ThemedTypings';
5
+
6
+ import { useSxTokens } from './useSxTokens';
7
+
8
+ const theme: ThemedDict = {
9
+ colors: {
10
+ red: 'red',
11
+ blue: 'blue',
12
+ green: 'green',
13
+ },
14
+ sizes: {
15
+ 1: 4,
16
+ 2: 8,
17
+ pagePadding: 20,
18
+ },
19
+ space: { 1: 4, 2: 8, pagePadding: 20 },
20
+ radii: {
21
+ sm: 8,
22
+ md: 12,
23
+ lg: 20,
24
+ },
25
+ typography: {},
26
+ };
27
+
28
+ export function expectResult<T extends keyof ThemedTypings, V extends ThemedTypings[T]>(
29
+ theme: ThemedDict,
30
+ tokenGroup: T,
31
+ tokenValues: Array<Exclude<V, null | undefined>>,
32
+ expectation: any[],
33
+ ) {
34
+ const {
35
+ result: { current },
36
+ } = renderHook(() => useSxTokens(tokenGroup, tokenValues, { theme }));
37
+
38
+ return expect(current).toEqual(expectation);
39
+ }
40
+
41
+ describe('valid case', () => {
42
+ it('colors', () => {
43
+ expectResult(theme, 'colors', ['red'], ['red']);
44
+ });
45
+
46
+ it('radii', () => {
47
+ expectResult(theme, 'radii', ['sm' as any], [8]);
48
+ });
49
+ });
50
+
51
+ describe('edge case', () => {
52
+ it('cannot find value if tokenValue is not in tokenType', () => {
53
+ expectResult(theme, 'radii', ['' as any], [undefined]);
54
+ });
55
+
56
+ it('cannot find value if theme is undefined', () => {
57
+ expectResult(undefined as any, 'radii', ['' as any], [undefined]);
58
+ });
59
+ });
@@ -0,0 +1,42 @@
1
+ import { useContext } from 'react';
2
+ import { generateArray } from '@mj-studio/js-util';
3
+
4
+ import type { ThemedDict } from '../@types/ThemedDict';
5
+ import type { ThemedTypings } from '../@types/ThemedTypings';
6
+ import { printWarning } from '../internal/util/printWarning';
7
+ import { StyledSystemContext } from '../provider/StyledSystemProvider';
8
+
9
+ export type UseSxTokensOptions = {
10
+ theme?: ThemedDict;
11
+ };
12
+ export const useSxTokens = <T extends keyof ThemedTypings, V extends ThemedTypings[T]>(
13
+ tokenType: T,
14
+ tokenValues: Array<Exclude<V, null | undefined>>,
15
+ { theme: optionTheme }: UseSxTokensOptions = {},
16
+ ): Array<ThemedDict[T][keyof ThemedDict[T]]> => {
17
+ const styledSystemContext = useContext(StyledSystemContext);
18
+
19
+ const theme = optionTheme ?? styledSystemContext?.theme;
20
+
21
+ if (!theme) {
22
+ printWarning('theme not found from useSxTokens, undefineds will be returned.');
23
+
24
+ return generateArray(tokenValues.length).map(() => undefined) as any;
25
+ }
26
+
27
+ const ret: Array<ThemedDict[T][keyof ThemedDict[T]]> = [];
28
+
29
+ for (let i = 0; i < tokenValues.length; i++) {
30
+ const tokenValue = tokenValues[i];
31
+ if (!(tokenValue in theme[tokenType])) {
32
+ printWarning(
33
+ `tokenValue ${String(tokenValue)} at index ${i} doesn't exist in tokenType ${tokenType} from useSxTokens, undefined will be returned.`,
34
+ );
35
+ ret.push(undefined as any);
36
+ } else {
37
+ ret.push(theme[tokenType][tokenValue]);
38
+ }
39
+ }
40
+
41
+ return ret;
42
+ };
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './provider/StyledSystemProvider';
2
+ export * from './hook/useSx';
3
+ export * from './@types/ThemedDict';
4
+ export * from './@types/SxProps';
5
+ export * from './@types/ThemedTypings';
6
+ export * from './util/propsToThemedStyle';
@@ -0,0 +1,18 @@
1
+ import type { ThemedDict } from '../../@types/ThemedDict';
2
+ import type { ColorsValue, Token } from '../../@types/Token';
3
+
4
+ export const createColorsParser = (theme: ThemedDict) => {
5
+ return (token?: Token<'colors'>) => parseColors(theme, token);
6
+ };
7
+
8
+ const parseColors = (theme: ThemedDict, token?: Token<'colors'>): ColorsValue | undefined => {
9
+ if (!token) {
10
+ return;
11
+ }
12
+
13
+ if (token in theme.colors) {
14
+ return theme.colors[token];
15
+ } else {
16
+ return token;
17
+ }
18
+ };
@@ -0,0 +1,44 @@
1
+ import { is } from '@mj-studio/js-util';
2
+
3
+ import type { ThemedDict } from '../../@types/ThemedDict';
4
+ import type { RadiiValue, Token } from '../../@types/Token';
5
+ import { parsePxSuffixNumber } from '../util/parsePxSuffixNumber';
6
+
7
+ export const createRadiiParser = (theme: ThemedDict) => {
8
+ return (token?: Token<'radii'>) => parseRadii(theme, token);
9
+ };
10
+
11
+ const parseRadii = (theme: ThemedDict, token?: Token<'radii'>): RadiiValue | undefined => {
12
+ if (is.nullOrUndefined(token)) {
13
+ return;
14
+ }
15
+
16
+ const px = parsePxSuffixNumber(token);
17
+ if (is.number(px)) {
18
+ return px;
19
+ }
20
+
21
+ // end with px but not number parsed
22
+ if (is.string(token) && token.endsWith('px')) {
23
+ return;
24
+ }
25
+
26
+ const radii = theme.radii;
27
+
28
+ if ((is.string(token) || is.number(token)) && token in radii) {
29
+ return radii[token] as number;
30
+ }
31
+
32
+ if (is.number(token)) {
33
+ const stringKey = `${token}`;
34
+ if (stringKey in radii) {
35
+ return radii[stringKey] as number;
36
+ }
37
+ }
38
+
39
+ if (is.numberString(token)) {
40
+ return Number(token);
41
+ }
42
+
43
+ return token;
44
+ };