@hero-design/rn-work-uikit 1.1.0-alpha.0 → 1.2.0-alpha.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 (56) hide show
  1. package/.cursorrules +57 -0
  2. package/CHANGELOG.md +8 -3
  3. package/DEVELOPMENT.md +118 -0
  4. package/THEME_OVERRIDE.md +52 -0
  5. package/eslint.config.js +20 -0
  6. package/lib/index.js +1000 -4
  7. package/locales/en_AU.js +10 -0
  8. package/locales/en_AU.mjs +8 -0
  9. package/locales/en_CA.js +10 -0
  10. package/locales/en_CA.mjs +8 -0
  11. package/locales/index.js +11 -0
  12. package/locales/index.mjs +9 -0
  13. package/locales/types.js +2 -0
  14. package/locales/types.mjs +1 -0
  15. package/package.json +8 -4
  16. package/rollup.config.mjs +18 -2
  17. package/src/__tests__/__snapshots__/index.spec.tsx.snap +91 -116
  18. package/src/__tests__/index.spec.tsx +15 -0
  19. package/src/__tests__/theme-export-override.spec.ts +96 -0
  20. package/src/components/TextInput/ErrorOrHelpText.tsx +58 -0
  21. package/src/components/TextInput/FloatingLabel.tsx +120 -0
  22. package/src/components/TextInput/InputComponent.tsx +61 -0
  23. package/src/components/TextInput/InputRow.tsx +103 -0
  24. package/src/components/TextInput/MaxLengthMessage.tsx +66 -0
  25. package/src/components/TextInput/PrefixComponent.tsx +77 -0
  26. package/src/components/TextInput/StyledTextInput.tsx +134 -0
  27. package/src/components/TextInput/SuffixComponent.tsx +73 -0
  28. package/src/components/TextInput/__tests__/ErrorOrHelpText.spec.tsx +20 -0
  29. package/src/components/TextInput/__tests__/FloatingLabel.spec.tsx +203 -0
  30. package/src/components/TextInput/__tests__/InputComponent.spec.tsx +39 -0
  31. package/src/components/TextInput/__tests__/InputRow.spec.tsx +275 -0
  32. package/src/components/TextInput/__tests__/MaxLengthMessage.spec.tsx +17 -0
  33. package/src/components/TextInput/__tests__/PrefixComponent.spec.tsx +14 -0
  34. package/src/components/TextInput/__tests__/StyledTextInput.spec.tsx +114 -0
  35. package/src/components/TextInput/__tests__/SuffixComponent.spec.tsx +20 -0
  36. package/src/components/TextInput/__tests__/__snapshots__/StyledTextInput.spec.tsx.snap +571 -0
  37. package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +5671 -0
  38. package/src/components/TextInput/__tests__/getState.spec.tsx +89 -0
  39. package/src/components/TextInput/__tests__/index.spec.tsx +699 -0
  40. package/src/components/TextInput/constants.ts +1 -0
  41. package/src/components/TextInput/index.tsx +327 -0
  42. package/src/components/TextInput/types.ts +95 -0
  43. package/src/emotion.d.ts +15 -0
  44. package/src/index.ts +16 -1
  45. package/src/jest.d.ts +24 -0
  46. package/src/theme/ThemeProvider.ts +20 -0
  47. package/src/theme/ThemeSwitcher.tsx +76 -0
  48. package/src/theme/__tests__/ThemeProvider.spec.tsx +32 -0
  49. package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +1851 -0
  50. package/src/theme/__tests__/index.spec.ts +7 -0
  51. package/src/theme/components/textInput.ts +92 -0
  52. package/src/theme/getTheme.ts +32 -0
  53. package/src/theme/index.ts +17 -0
  54. package/src/utils/__tests__/helpers.spec.ts +92 -0
  55. package/src/utils/helpers.ts +113 -0
  56. package/testUtils/renderWithTheme.tsx +6 -3
@@ -0,0 +1,327 @@
1
+ import React, { forwardRef, useCallback } from 'react';
2
+ import { StyleSheet, TextInput as RNTextInput } from 'react-native';
3
+ import type {
4
+ TextInputProps as NativeTextInputProps,
5
+ StyleProp,
6
+ ViewStyle,
7
+ NativeSyntheticEvent,
8
+ TextInputFocusEventData,
9
+ } from 'react-native';
10
+ import {
11
+ StyledInputWrapper,
12
+ StyledContainer,
13
+ StyledBorder,
14
+ StyledBottomContainer,
15
+ StyledSuffixContainer,
16
+ } from './StyledTextInput';
17
+ import { useTheme } from '../../theme';
18
+ import type { State } from './StyledTextInput';
19
+ import ErrorOrHelpText from './ErrorOrHelpText';
20
+ import SuffixComponent from './SuffixComponent';
21
+ import MaxLengthMessage from './MaxLengthMessage';
22
+ import FloatingLabel from './FloatingLabel';
23
+ import InputRow from './InputRow';
24
+ import type { TextInputHandles, TextInputProps } from './types';
25
+
26
+ export type {
27
+ TextInputHandles,
28
+ TextInputVariant,
29
+ TextInputProps,
30
+ } from './types';
31
+
32
+ export const getState = ({
33
+ disabled,
34
+ error,
35
+ editable,
36
+ loading,
37
+ isEmptyValue,
38
+ }: {
39
+ disabled?: boolean;
40
+ error?: string;
41
+ editable?: boolean;
42
+ loading: boolean;
43
+ isEmptyValue?: boolean;
44
+ }): State => {
45
+ if (disabled) {
46
+ return 'disabled';
47
+ }
48
+ if (error) {
49
+ return 'error';
50
+ }
51
+ if (!editable || loading) {
52
+ return 'readonly';
53
+ }
54
+ if (!isEmptyValue) {
55
+ return 'filled';
56
+ }
57
+
58
+ return 'default';
59
+ };
60
+
61
+ // Fix issue: Placeholder is not shown on iOS when multiline is true
62
+ // https://github.com/callstack/react-native-paper/pull/3331
63
+ const EMPTY_PLACEHOLDER_VALUE = ' ';
64
+
65
+ // Simplified style extraction functions
66
+ const extractBackgroundColor = (style: StyleProp<ViewStyle>) => {
67
+ const flattened = StyleSheet.flatten(style);
68
+ return flattened?.backgroundColor;
69
+ };
70
+
71
+ const extractBorderStyles = (style: StyleProp<ViewStyle>) => {
72
+ const flattened = StyleSheet.flatten(style);
73
+ if (!flattened) return {};
74
+
75
+ const borderKeys = Object.keys(flattened).filter((key) =>
76
+ key.startsWith('border')
77
+ );
78
+ const borderStyles: Record<string, unknown> = {};
79
+
80
+ borderKeys.forEach((key) => {
81
+ borderStyles[key] = flattened[key as keyof typeof flattened];
82
+ });
83
+
84
+ return borderStyles;
85
+ };
86
+
87
+ /**
88
+ * TextInput Layout Structure:
89
+ *
90
+ * ┌─────────────────────────────────────────────────────────────────────────┐
91
+ * │ StyledContainer (with StyledBorder overlay) │
92
+ * │ ┌─────────────────────────────────────────────────────────┐ │
93
+ * │ │ StyledInputWrapper │ │
94
+ * │ │ ┌─────────────────────────────────────────────────┐ │ │
95
+ * │ │ │ FloatingLabel (Optional) │ │ │
96
+ * │ │ │ "Label" or animated position │ │ ┌──────┐ │
97
+ * │ │ └─────────────────────────────────────────────────┘ │ │Suffix│ │
98
+ * │ │ │ │ Icon │ │
99
+ * │ │ ┌─────────────────────────────────────────────────┐ │ │ or │ │
100
+ * │ │ │ InputRow Component │ │ │Custom│ │
101
+ * │ │ │ ┌──────────┐ ┌─────────────────────────────┐ │ │ │(V- │ │
102
+ * │ │ │ │ Prefix │ │ Input Field │ │ │ │Center│ │
103
+ * │ │ │ │(Animated)│ │ (Animated TextInput) │ │ │ │ ed) │ │
104
+ * │ │ │ └──────────┘ └─────────────────────────────┘ │ │ └──────┘ │
105
+ * │ │ └─────────────────────────────────────────────────┘ │ │
106
+ * │ │ │ │
107
+ * │ │ ┌─────────────────────────────────────────────────┐ │ │
108
+ * │ │ │ StyledBottomContainer │ │ │
109
+ * │ │ │ ┌─────────────────┐ ┌─────────────────────┐ │ │ │
110
+ * │ │ │ │ Error/Help Text │ │ Character Count │ │ │ │
111
+ * │ │ │ │ (flex: 4) │ │ (flex: 1) │ │ │ │
112
+ * │ │ │ └─────────────────┘ └─────────────────────┘ │ │ │
113
+ * │ │ └─────────────────────────────────────────────────┘ │ │
114
+ * │ └─────────────────────────────────────────────────────────┘ │
115
+ * └─────────────────────────────────────────────────────────────────────────┘
116
+ *
117
+ * Note: StyledBorder uses StyleSheet.absoluteFillObject to overlay the entire
118
+ * StyledContainer, providing the border and background styling.
119
+ */
120
+ const TextInput = forwardRef<TextInputHandles, TextInputProps>(
121
+ (
122
+ {
123
+ label,
124
+ prefix,
125
+ suffix,
126
+ style,
127
+ textStyle,
128
+ testID,
129
+ accessibilityLabelledBy,
130
+ error,
131
+ required,
132
+ editable = true,
133
+ disabled = false,
134
+ loading = false,
135
+ maxLength,
136
+ hideCharacterCount = false,
137
+ helpText,
138
+ value,
139
+ defaultValue,
140
+ renderInputValue,
141
+ allowFontScaling = false,
142
+ variant = 'text',
143
+ ...nativeProps
144
+ }: TextInputProps,
145
+ ref?: React.Ref<TextInputHandles>
146
+ ) => {
147
+ // Inline the simple getDisplayText function
148
+ const displayText = (value !== undefined ? value : defaultValue) ?? '';
149
+ const isEmptyValue = displayText.length === 0;
150
+
151
+ const [isFocused, setIsFocused] = React.useState(false);
152
+
153
+ const state = getState({
154
+ disabled,
155
+ error,
156
+ editable,
157
+ loading,
158
+ isEmptyValue,
159
+ });
160
+
161
+ const theme = useTheme();
162
+
163
+ const innerTextInput = React.useRef<RNTextInput | null>(null);
164
+ React.useImperativeHandle(
165
+ ref,
166
+ () => ({
167
+ // we don't expose this method, it's for testing https://medium.com/developer-rants/how-to-test-useref-without-mocking-useref-699165f4994e
168
+ getNativeTextInputRef: () => innerTextInput.current,
169
+ focus: () => {
170
+ innerTextInput.current?.focus();
171
+ },
172
+ clear: () => innerTextInput.current?.clear(),
173
+ setNativeProps: (args: NativeTextInputProps) =>
174
+ innerTextInput.current?.setNativeProps(args),
175
+ isFocused: () => innerTextInput.current?.isFocused() || false,
176
+ blur: () => innerTextInput.current?.blur(),
177
+ }),
178
+ [innerTextInput]
179
+ );
180
+
181
+ // Simplified style extraction
182
+ const borderStyles = extractBorderStyles(textStyle);
183
+ const customBackgroundColor = extractBackgroundColor(style);
184
+ const backgroundColor =
185
+ customBackgroundColor ??
186
+ theme.__hd__.textInput.colors.containerBackground;
187
+
188
+ // Simplified callback functions (removed unnecessary memoization for simple cases)
189
+ const handleFocus = useCallback(
190
+ (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
191
+ setIsFocused(true);
192
+ nativeProps.onFocus?.(event);
193
+ },
194
+ [nativeProps]
195
+ );
196
+
197
+ const handleBlur = useCallback(
198
+ (event: NativeSyntheticEvent<TextInputFocusEventData>) => {
199
+ setIsFocused(false);
200
+ nativeProps.onBlur?.(event);
201
+ },
202
+ [nativeProps]
203
+ );
204
+
205
+ const handleChangeText = useCallback(
206
+ (text: string) => {
207
+ nativeProps.onChangeText?.(text);
208
+ },
209
+ [nativeProps]
210
+ );
211
+
212
+ // Simplified callbacks - these don't need memoization as they're stable
213
+ const handleContainerPress = () => {
214
+ innerTextInput.current?.focus();
215
+ };
216
+
217
+ // Create text input style without border properties
218
+ const textInputStyle = textStyle
219
+ ? { ...StyleSheet.flatten(textStyle) }
220
+ : {};
221
+ Object.keys(borderStyles).forEach((key) => {
222
+ delete textInputStyle[key as keyof typeof textInputStyle];
223
+ });
224
+
225
+ const nativeInputProps: NativeTextInputProps = {
226
+ style: StyleSheet.flatten([
227
+ {
228
+ backgroundColor,
229
+ color: theme.__hd__.textInput.colors.text,
230
+ },
231
+ textInputStyle,
232
+ ]),
233
+ testID: 'text-input',
234
+ accessibilityState: {
235
+ disabled: state === 'disabled' || state === 'readonly',
236
+ },
237
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
238
+ // @ts-ignore
239
+ accessibilityLabelledBy,
240
+ allowFontScaling,
241
+ ...nativeProps,
242
+ onFocus: handleFocus,
243
+ onBlur: handleBlur,
244
+ onChangeText: handleChangeText,
245
+ editable,
246
+ maxLength,
247
+ value,
248
+ defaultValue,
249
+ placeholder:
250
+ isFocused || label === undefined
251
+ ? nativeProps.placeholder
252
+ : EMPTY_PLACEHOLDER_VALUE,
253
+ };
254
+
255
+ // Create container style without background color
256
+ const containerStyle = style ? { ...StyleSheet.flatten(style) } : {};
257
+ if (customBackgroundColor) {
258
+ delete containerStyle.backgroundColor;
259
+ }
260
+
261
+ const isDisabledOrReadonly = state === 'disabled' || state === 'readonly';
262
+
263
+ return (
264
+ <StyledContainer
265
+ style={containerStyle}
266
+ onPress={handleContainerPress}
267
+ disabled={isDisabledOrReadonly}
268
+ accessibilityState={{
269
+ disabled: isDisabledOrReadonly,
270
+ }}
271
+ testID={testID}
272
+ >
273
+ {/*
274
+ StyledBorder: Absolute positioned overlay covering entire container
275
+ - Uses StyleSheet.absoluteFillObject to cover full StyledContainer
276
+ - Provides border styling and background color
277
+ - pointerEvents="none" to allow interaction with underlying components
278
+ */}
279
+ <StyledBorder
280
+ themeFocused={isFocused}
281
+ themeState={state}
282
+ testID="text-input-border"
283
+ pointerEvents="none"
284
+ style={[{ backgroundColor }, borderStyles]}
285
+ />
286
+ <StyledInputWrapper>
287
+ {!!label && (
288
+ <FloatingLabel
289
+ label={label}
290
+ variant={variant}
291
+ state={state}
292
+ isFocused={isFocused}
293
+ required={required}
294
+ accessibilityLabelledBy={accessibilityLabelledBy}
295
+ isEmptyValue={isEmptyValue}
296
+ />
297
+ )}
298
+ <InputRow
299
+ state={state}
300
+ isFocused={isFocused}
301
+ prefix={prefix}
302
+ variant={variant}
303
+ nativeInputProps={nativeInputProps}
304
+ renderInputValue={renderInputValue}
305
+ ref={innerTextInput}
306
+ isEmptyValue={isEmptyValue}
307
+ />
308
+
309
+ <StyledBottomContainer>
310
+ <ErrorOrHelpText error={error} helpText={helpText} />
311
+ <MaxLengthMessage
312
+ state={state}
313
+ currentLength={displayText.length}
314
+ maxLength={maxLength}
315
+ hideCharacterCount={hideCharacterCount}
316
+ />
317
+ </StyledBottomContainer>
318
+ </StyledInputWrapper>
319
+ <StyledSuffixContainer>
320
+ <SuffixComponent state={state} loading={loading} suffix={suffix} />
321
+ </StyledSuffixContainer>
322
+ </StyledContainer>
323
+ );
324
+ }
325
+ );
326
+
327
+ export default TextInput;
@@ -0,0 +1,95 @@
1
+ import type {
2
+ TextInputProps as NativeTextInputProps,
3
+ StyleProp,
4
+ ViewStyle,
5
+ TextStyle,
6
+ TextInput as RNTextInput,
7
+ } from 'react-native';
8
+ import type { IconName } from '@hero-design/rn';
9
+
10
+ export type TextInputHandles = Pick<
11
+ RNTextInput,
12
+ 'focus' | 'clear' | 'blur' | 'isFocused' | 'setNativeProps'
13
+ >;
14
+
15
+ export type TextInputVariant = 'text' | 'textarea';
16
+
17
+ export interface TextInputProps extends NativeTextInputProps {
18
+ /**
19
+ * Field label.
20
+ */
21
+ label?: string;
22
+ /**
23
+ * Name of Icon or ReactElement to render on the left side of the input, before the user's cursor.
24
+ */
25
+ prefix?: IconName | React.ReactElement;
26
+ /**
27
+ * Name of Icon or ReactElement to render on the right side of the input.
28
+ */
29
+ suffix?: IconName | React.ReactElement;
30
+ /**
31
+ * Additional wrapper style.
32
+ */
33
+ style?: StyleProp<ViewStyle>;
34
+ /**
35
+ * Input text style.
36
+ */
37
+ textStyle?: StyleProp<TextStyle>;
38
+ /**
39
+ * Testing id of the component.
40
+ */
41
+ testID?: string;
42
+ /**
43
+ * Accessibility label for the input (Android).
44
+ */
45
+ accessibilityLabelledBy?: string;
46
+ /**
47
+ * Error message to display.
48
+ */
49
+ error?: string;
50
+ /**
51
+ * Whether the input is required. When false, "(Optional)" will be appended to the label.
52
+ */
53
+ required?: boolean;
54
+ /**
55
+ * Placeholder text to display.
56
+ * */
57
+ placeholder?: string;
58
+ /**
59
+ * Whether the input is editable.
60
+ * */
61
+ editable?: boolean;
62
+ /**
63
+ * Whether the input is disabled.
64
+ */
65
+ disabled?: boolean;
66
+ /**
67
+ * Whether the input is loading.
68
+ */
69
+ loading?: boolean;
70
+ /**
71
+ * The max length of the input.
72
+ * If the max length is set, the input will display the current length and the max length.
73
+ * */
74
+ maxLength?: number;
75
+ /**
76
+ * Whether to hide the character count.
77
+ * */
78
+ hideCharacterCount?: boolean;
79
+ /**
80
+ * The helper text to display.
81
+ */
82
+ helpText?: string;
83
+ /**
84
+ * Customise input value renderer
85
+ */
86
+ renderInputValue?: (inputProps: NativeTextInputProps) => React.ReactNode;
87
+ /**
88
+ * Component ref.
89
+ */
90
+ ref?: React.Ref<TextInputHandles>;
91
+ /**
92
+ * Component variant.
93
+ */
94
+ variant?: TextInputVariant;
95
+ }
@@ -0,0 +1,15 @@
1
+ import '@emotion/react';
2
+ import '@emotion/native';
3
+ import type { Theme as WorkTheme } from './theme';
4
+
5
+ // Augment the emotion theme for both @emotion/react and @emotion/native
6
+ declare module '@emotion/react' {
7
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
8
+ export interface Theme extends WorkTheme {}
9
+ }
10
+
11
+ // This should make styled components automatically get the Theme type
12
+ declare module '@emotion/native' {
13
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
14
+ export interface Theme extends WorkTheme {}
15
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,17 @@
1
- // Re-export all components from @hero-design/rn
1
+ // Re-export everything from @hero-design/rn except theme exports we want to override
2
+ import TextInput from './components/TextInput';
3
+
2
4
  export * from '@hero-design/rn';
5
+
6
+ // Override theme exports with work-specific versions
7
+ export {
8
+ getTheme,
9
+ type Theme,
10
+ ThemeProvider,
11
+ ThemeSwitcher,
12
+ useTheme,
13
+ withTheme,
14
+ } from './theme';
15
+
16
+ export { default as theme } from './theme';
17
+ export { TextInput };
package/src/jest.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ declare global {
2
+ namespace jest {
3
+ interface Matchers<R> {
4
+ toHaveProp(prop: string, value?: unknown): R;
5
+ toHaveStyle(style: Record<string, unknown>): R;
6
+ toBeDisabled(): R;
7
+ toBeEnabled(): R;
8
+ toBeVisible(): R;
9
+ toBeEmptyElement(): R;
10
+ toHaveTextContent(text: string | RegExp): R;
11
+ toHaveDisplayValue(value: string | RegExp): R;
12
+ toBeSelected(): R;
13
+ toBePartiallyChecked(): R;
14
+ toBeChecked(): R;
15
+ toHaveA11yLabel(label: string | RegExp): R;
16
+ toHaveA11yHint(hint: string | RegExp): R;
17
+ toHaveA11yRole(role: string): R;
18
+ toHaveA11yState(state: Record<string, boolean | string>): R;
19
+ toHaveA11yValue(value: Record<string, unknown>): R;
20
+ }
21
+ }
22
+ }
23
+
24
+ export {};
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { ThemeProvider as BaseThemeProvider, useTheme } from '@hero-design/rn';
3
+ import type { Theme } from './getTheme';
4
+
5
+ export interface ThemeProviderProps {
6
+ theme: Partial<Theme> | ((outerTheme: Theme) => Theme);
7
+ children: React.ReactNode;
8
+ }
9
+
10
+ interface ThemeProviderType {
11
+ (props: ThemeProviderProps): React.ReactElement;
12
+ }
13
+
14
+ const WorkThemeProvider = BaseThemeProvider as ThemeProviderType;
15
+
16
+ const useWorkTheme = useTheme as () => Theme;
17
+
18
+ export { WorkThemeProvider, useWorkTheme };
19
+
20
+ export default WorkThemeProvider;
@@ -0,0 +1,76 @@
1
+ import React, { PropsWithChildren, useMemo } from 'react';
2
+
3
+ import {
4
+ eBensSystemPalette,
5
+ ehWorkDarkSystemPalette,
6
+ jobsSystemPalette,
7
+ swagDarkSystemPalette,
8
+ swagLightJobsSystemPalette,
9
+ swagLightSystemPalette,
10
+ swagSystemPalette,
11
+ walletSystemPalette,
12
+ workSystemPalette,
13
+ } from '@hero-design/rn';
14
+ import getWorkTheme from './getTheme';
15
+ import { WorkThemeProvider } from './ThemeProvider';
16
+
17
+ type ThemeName =
18
+ | 'swag'
19
+ | 'swagDark'
20
+ | 'swagLight'
21
+ | 'work'
22
+ | 'jobs'
23
+ | 'wallet'
24
+ | 'eBens'
25
+ | 'ehWorkDark'
26
+ | 'ehWork'
27
+ | 'ehJobs';
28
+
29
+ const WorkThemeSwitcher = ({
30
+ name = 'work',
31
+ children,
32
+ }: {
33
+ name?: ThemeName;
34
+ children: React.ReactNode;
35
+ }) => {
36
+ const theme = useMemo(() => {
37
+ switch (name) {
38
+ case 'swag':
39
+ return getWorkTheme(undefined, swagSystemPalette);
40
+ case 'work':
41
+ return getWorkTheme(undefined, workSystemPalette);
42
+ case 'jobs':
43
+ return getWorkTheme(undefined, jobsSystemPalette);
44
+ case 'wallet':
45
+ return getWorkTheme(undefined, walletSystemPalette);
46
+ case 'eBens':
47
+ return getWorkTheme(undefined, eBensSystemPalette);
48
+ case 'swagDark':
49
+ return getWorkTheme(undefined, swagDarkSystemPalette);
50
+ case 'swagLight':
51
+ case 'ehWork':
52
+ return getWorkTheme(undefined, swagLightSystemPalette);
53
+ case 'ehWorkDark':
54
+ return getWorkTheme(undefined, ehWorkDarkSystemPalette);
55
+ case 'ehJobs':
56
+ return getWorkTheme(undefined, swagLightJobsSystemPalette);
57
+ }
58
+ }, [name]);
59
+
60
+ return <WorkThemeProvider theme={theme}>{children}</WorkThemeProvider>;
61
+ };
62
+
63
+ export const withWorkTheme =
64
+ <P extends Record<string, unknown>>(
65
+ C: React.ComponentType<P>,
66
+ themeName: ThemeName
67
+ ) =>
68
+ (props: PropsWithChildren<P>) => {
69
+ return (
70
+ <WorkThemeSwitcher name={themeName}>
71
+ <C {...props} />
72
+ </WorkThemeSwitcher>
73
+ );
74
+ };
75
+
76
+ export default WorkThemeSwitcher;
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react-native';
3
+ import { Text } from 'react-native';
4
+ import { WorkThemeProvider, useWorkTheme } from '../ThemeProvider';
5
+ import workTheme from '..';
6
+
7
+ const TestComponent = () => {
8
+ const theme = useWorkTheme();
9
+ return <Text testID="test-text">Theme loaded: {theme ? 'Yes' : 'No'}</Text>;
10
+ };
11
+
12
+ describe('WorkThemeProvider', () => {
13
+ it('should provide theme to children', () => {
14
+ const { getByTestId } = render(
15
+ <WorkThemeProvider theme={workTheme}>
16
+ <TestComponent />
17
+ </WorkThemeProvider>
18
+ );
19
+
20
+ expect(getByTestId('test-text')).toBeTruthy();
21
+ });
22
+
23
+ it('should allow useWorkTheme hook to access theme', () => {
24
+ const { getByText } = render(
25
+ <WorkThemeProvider theme={workTheme}>
26
+ <TestComponent />
27
+ </WorkThemeProvider>
28
+ );
29
+
30
+ expect(getByText('Theme loaded: Yes')).toBeTruthy();
31
+ });
32
+ });