@sudobility/components-rn 1.0.40 → 1.0.42

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 (36) hide show
  1. package/dist/index.cjs.js +217 -194
  2. package/dist/index.cjs.js.map +1 -1
  3. package/dist/index.esm.js +220 -197
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/ui/Alert/Alert.d.ts.map +1 -1
  6. package/dist/ui/Badge/Badge.d.ts.map +1 -1
  7. package/dist/ui/Banner/Banner.d.ts.map +1 -1
  8. package/dist/ui/Divider/Divider.d.ts.map +1 -1
  9. package/dist/ui/Heading/Heading.d.ts.map +1 -1
  10. package/dist/ui/InfoBox/InfoBox.d.ts.map +1 -1
  11. package/dist/ui/Input/Input.d.ts.map +1 -1
  12. package/dist/ui/Label/Label.d.ts.map +1 -1
  13. package/dist/ui/ProgressCircle/ProgressCircle.d.ts.map +1 -1
  14. package/dist/ui/QuickActions/QuickActions.d.ts.map +1 -1
  15. package/dist/ui/Spinner/Spinner.d.ts.map +1 -1
  16. package/dist/ui/Text/Text.d.ts.map +1 -1
  17. package/dist/ui/Toast/Toast.d.ts.map +1 -1
  18. package/package.json +5 -4
  19. package/src/__tests__/alert.test.tsx +20 -10
  20. package/src/__tests__/dialog.test.tsx +30 -1
  21. package/src/__tests__/sheet.test.tsx +42 -1
  22. package/src/__tests__/toast.test.tsx +32 -0
  23. package/src/ui/Alert/Alert.tsx +13 -5
  24. package/src/ui/Badge/Badge.tsx +61 -26
  25. package/src/ui/Banner/Banner.tsx +36 -25
  26. package/src/ui/Card/Card.tsx +2 -2
  27. package/src/ui/Divider/Divider.tsx +12 -17
  28. package/src/ui/Heading/Heading.tsx +15 -38
  29. package/src/ui/InfoBox/InfoBox.tsx +35 -17
  30. package/src/ui/Input/Input.tsx +10 -3
  31. package/src/ui/Label/Label.tsx +3 -1
  32. package/src/ui/ProgressCircle/ProgressCircle.tsx +7 -6
  33. package/src/ui/QuickActions/QuickActions.tsx +13 -9
  34. package/src/ui/Spinner/Spinner.tsx +7 -5
  35. package/src/ui/Text/Text.tsx +19 -52
  36. package/src/ui/Toast/Toast.tsx +49 -14
@@ -2,6 +2,26 @@ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
2
  import { View, Text, Pressable, Animated } from 'react-native';
3
3
  import { InfoType } from '@sudobility/types';
4
4
  import { cn } from '../../lib/utils';
5
+ import { colors } from '@sudobility/design';
6
+
7
+ const alert = colors.component.alert;
8
+
9
+ // Split DS alert color strings into container (bg+border) and text for RN.
10
+ // Views don't cascade text color to child Text elements.
11
+ function splitAlertClasses(base: string, dark: string) {
12
+ const all = `${base} ${dark}`.split(' ');
13
+ return {
14
+ container: all
15
+ .filter(c => c.includes('bg-') || c.includes('border-'))
16
+ .join(' '),
17
+ text: all.filter(c => c.includes('text-')).join(' '),
18
+ };
19
+ }
20
+
21
+ const dsInfo = splitAlertClasses(alert.info.base, alert.info.dark);
22
+ const dsSuccess = splitAlertClasses(alert.success.base, alert.success.dark);
23
+ const dsWarning = splitAlertClasses(alert.warning.base, alert.warning.dark);
24
+ const dsError = splitAlertClasses(alert.error.base, alert.error.dark);
5
25
 
6
26
  export interface BannerProps {
7
27
  /** Whether the banner is visible */
@@ -24,46 +44,39 @@ export interface BannerProps {
24
44
  closeAccessibilityLabel?: string;
25
45
  }
26
46
 
47
+ // Banner variant config derived from design system (colors.component.alert)
27
48
  const variantConfig: Record<
28
49
  InfoType,
29
50
  {
30
51
  icon: string;
31
52
  container: string;
32
53
  iconColor: string;
33
- titleColor: string;
34
- descriptionColor: string;
54
+ textColor: string;
35
55
  }
36
56
  > = {
37
57
  [InfoType.INFO]: {
38
58
  icon: '\u2139',
39
- container:
40
- 'bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800',
41
- iconColor: 'text-blue-600 dark:text-blue-400',
42
- titleColor: 'text-blue-900 dark:text-blue-100',
43
- descriptionColor: 'text-blue-700 dark:text-blue-300',
59
+ container: dsInfo.container,
60
+ iconColor: alert.info.icon,
61
+ textColor: dsInfo.text,
44
62
  },
45
63
  [InfoType.SUCCESS]: {
46
64
  icon: '\u2713',
47
- container:
48
- 'bg-green-50 dark:bg-green-950 border-green-200 dark:border-green-800',
49
- iconColor: 'text-green-600 dark:text-green-400',
50
- titleColor: 'text-green-900 dark:text-green-100',
51
- descriptionColor: 'text-green-700 dark:text-green-300',
65
+ container: dsSuccess.container,
66
+ iconColor: alert.success.icon,
67
+ textColor: dsSuccess.text,
52
68
  },
53
69
  [InfoType.WARNING]: {
54
70
  icon: '\u26A0',
55
- container:
56
- 'bg-yellow-50 dark:bg-amber-950 border-yellow-200 dark:border-amber-800',
57
- iconColor: 'text-yellow-600 dark:text-amber-400',
58
- titleColor: 'text-yellow-900 dark:text-amber-100',
59
- descriptionColor: 'text-yellow-700 dark:text-amber-300',
71
+ container: dsWarning.container,
72
+ iconColor: alert.warning.icon,
73
+ textColor: dsWarning.text,
60
74
  },
61
75
  [InfoType.ERROR]: {
62
76
  icon: '\u2717',
63
- container: 'bg-red-50 dark:bg-red-950 border-red-200 dark:border-red-800',
64
- iconColor: 'text-red-600 dark:text-red-400',
65
- titleColor: 'text-red-900 dark:text-red-100',
66
- descriptionColor: 'text-red-700 dark:text-red-300',
77
+ container: dsError.container,
78
+ iconColor: alert.error.icon,
79
+ textColor: dsError.text,
67
80
  },
68
81
  };
69
82
 
@@ -206,11 +219,9 @@ export const Banner: React.FC<BannerProps> = ({
206
219
 
207
220
  {/* Content */}
208
221
  <View className='flex-1 min-w-0'>
209
- <Text className={cn('font-semibold', config.titleColor)}>
210
- {title}
211
- </Text>
222
+ <Text className={cn('font-semibold', config.textColor)}>{title}</Text>
212
223
  {description ? (
213
- <Text className={cn('text-sm mt-1', config.descriptionColor)}>
224
+ <Text className={cn('text-sm mt-1', config.textColor)}>
214
225
  {description}
215
226
  </Text>
216
227
  ) : null}
@@ -29,8 +29,8 @@ export interface CardProps extends ViewProps {
29
29
  onClose?: () => void;
30
30
  }
31
31
 
32
- const calloutStyle =
33
- 'bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800';
32
+ // Callout uses the DS info card variant colors
33
+ const calloutStyle = getCardVariantColors('info');
34
34
 
35
35
  const paddingStyles = {
36
36
  none: '',
@@ -3,6 +3,13 @@ import { View, Text, ViewProps } from 'react-native';
3
3
  import { cn } from '../../lib/utils';
4
4
  import { textVariants } from '@sudobility/design';
5
5
 
6
+ // Divider line colors aligned with DS border tokens (colors.semantic.border)
7
+ const lineVariantClasses = {
8
+ light: 'bg-gray-200 dark:bg-gray-700',
9
+ medium: 'bg-gray-300 dark:bg-gray-600',
10
+ dark: 'bg-gray-400 dark:bg-gray-500',
11
+ } as const;
12
+
6
13
  export interface DividerProps extends ViewProps {
7
14
  /** Optional text label */
8
15
  label?: string;
@@ -81,20 +88,13 @@ export const Divider: React.FC<DividerProps> = ({
81
88
  thick: 4,
82
89
  };
83
90
 
84
- // Color variant configurations
85
- const variantClasses = {
86
- light: 'bg-gray-200 dark:bg-gray-700',
87
- medium: 'bg-gray-300 dark:bg-gray-600',
88
- dark: 'bg-gray-400 dark:bg-gray-500',
89
- };
90
-
91
91
  // Vertical divider
92
92
  if (orientation === 'vertical') {
93
93
  return (
94
94
  <View
95
95
  className={cn(
96
96
  'self-stretch',
97
- variantClasses[variant],
97
+ lineVariantClasses[variant],
98
98
  spacingClasses.vertical[spacing],
99
99
  className
100
100
  )}
@@ -111,7 +111,7 @@ export const Divider: React.FC<DividerProps> = ({
111
111
  <View
112
112
  className={cn(
113
113
  'w-full',
114
- variantClasses[variant],
114
+ lineVariantClasses[variant],
115
115
  spacingClasses.horizontal[spacing],
116
116
  lineClassName,
117
117
  className
@@ -143,21 +143,16 @@ export const Divider: React.FC<DividerProps> = ({
143
143
  >
144
144
  {labelPosition !== 'left' && (
145
145
  <View
146
- className={cn('flex-1', variantClasses[variant], lineClassName)}
146
+ className={cn('flex-1', lineVariantClasses[variant], lineClassName)}
147
147
  style={{ height: thicknessValues[thickness] }}
148
148
  />
149
149
  )}
150
- <Text
151
- className={cn(
152
- textVariants.body.sm(),
153
- 'px-3 text-gray-500 dark:text-gray-400'
154
- )}
155
- >
150
+ <Text className={cn(textVariants.caption.default(), 'px-3')}>
156
151
  {label}
157
152
  </Text>
158
153
  {labelPosition !== 'right' && (
159
154
  <View
160
- className={cn('flex-1', variantClasses[variant], lineClassName)}
155
+ className={cn('flex-1', lineVariantClasses[variant], lineClassName)}
161
156
  style={{ height: thicknessValues[thickness] }}
162
157
  />
163
158
  )}
@@ -1,6 +1,16 @@
1
1
  import * as React from 'react';
2
2
  import { Text } from 'react-native';
3
3
  import { cn } from '../../lib/utils';
4
+ import { designTokens } from '@sudobility/design';
5
+
6
+ const { typography } = designTokens;
7
+
8
+ // Heading colors aligned with the design system color architecture
9
+ const colorClasses = {
10
+ default: 'text-gray-900 dark:text-gray-100',
11
+ muted: 'text-gray-700 dark:text-gray-300',
12
+ primary: 'text-blue-600 dark:text-blue-400',
13
+ } as const;
4
14
 
5
15
  export interface HeadingProps {
6
16
  /** Heading content */
@@ -60,48 +70,15 @@ export const Heading: React.FC<HeadingProps> = ({
60
70
 
61
71
  const actualSize = size || defaultSizes[level];
62
72
 
63
- // Size configurations
64
- const sizeClasses = {
65
- '4xl': 'text-4xl',
66
- '3xl': 'text-3xl',
67
- '2xl': 'text-2xl',
68
- xl: 'text-xl',
69
- lg: 'text-lg',
70
- base: 'text-base',
71
- };
72
-
73
- // Weight configurations
74
- const weightClasses = {
75
- normal: 'font-normal',
76
- medium: 'font-medium',
77
- semibold: 'font-semibold',
78
- bold: 'font-bold',
79
- extrabold: 'font-extrabold',
80
- };
81
-
82
- // Color configurations
83
- const colorClasses = {
84
- default: 'text-gray-900 dark:text-gray-100',
85
- muted: 'text-gray-700 dark:text-gray-300',
86
- primary: 'text-blue-600 dark:text-blue-400',
87
- };
88
-
89
- // Alignment configurations
90
- const alignClasses = align
91
- ? {
92
- left: 'text-left',
93
- center: 'text-center',
94
- right: 'text-right',
95
- }[align]
96
- : '';
97
-
98
73
  return (
99
74
  <Text
100
75
  className={cn(
101
- sizeClasses[actualSize],
102
- weightClasses[weight],
76
+ typography.size[actualSize],
77
+ typography.weight[weight],
78
+ typography.leading.heading,
79
+ typography.tracking.heading,
103
80
  colorClasses[color],
104
- alignClasses,
81
+ align ? typography.align[align] : '',
105
82
  className
106
83
  )}
107
84
  accessibilityRole='header'
@@ -1,6 +1,24 @@
1
1
  import * as React from 'react';
2
2
  import { View, Text } from 'react-native';
3
3
  import { cn } from '../../lib/utils';
4
+ import { colors } from '@sudobility/design';
5
+
6
+ const alert = colors.component.alert;
7
+
8
+ // Split DS alert color strings into separate parts for RN
9
+ function splitAlertClasses(base: string, dark: string) {
10
+ const all = `${base} ${dark}`.split(' ');
11
+ return {
12
+ bg: all.filter(c => c.includes('bg-')).join(' '),
13
+ border: all.filter(c => c.includes('border-')).join(' '),
14
+ text: all.filter(c => c.includes('text-')).join(' '),
15
+ };
16
+ }
17
+
18
+ const dsInfo = splitAlertClasses(alert.info.base, alert.info.dark);
19
+ const dsSuccess = splitAlertClasses(alert.success.base, alert.success.dark);
20
+ const dsWarning = splitAlertClasses(alert.warning.base, alert.warning.dark);
21
+ const dsError = splitAlertClasses(alert.error.base, alert.error.dark);
4
22
 
5
23
  export interface InfoBoxProps {
6
24
  /** Content to display in the info box */
@@ -49,31 +67,31 @@ export const InfoBox: React.FC<InfoBoxProps> = ({
49
67
  bordered = true,
50
68
  className,
51
69
  }) => {
52
- // Color variant configurations
70
+ // Color variants derived from design system (colors.component.alert)
53
71
  const variantClasses = {
54
72
  info: {
55
- bg: 'bg-blue-50 dark:bg-blue-900/20',
56
- border: 'border-blue-200 dark:border-blue-800',
57
- title: 'text-blue-900 dark:text-blue-300',
58
- text: 'text-blue-800 dark:text-blue-400',
73
+ bg: dsInfo.bg,
74
+ border: dsInfo.border,
75
+ title: dsInfo.text,
76
+ text: dsInfo.text,
59
77
  },
60
78
  success: {
61
- bg: 'bg-green-50 dark:bg-green-900/20',
62
- border: 'border-green-200 dark:border-green-800',
63
- title: 'text-green-900 dark:text-green-300',
64
- text: 'text-green-800 dark:text-green-400',
79
+ bg: dsSuccess.bg,
80
+ border: dsSuccess.border,
81
+ title: dsSuccess.text,
82
+ text: dsSuccess.text,
65
83
  },
66
84
  warning: {
67
- bg: 'bg-yellow-50 dark:bg-yellow-900/20',
68
- border: 'border-yellow-200 dark:border-yellow-800',
69
- title: 'text-yellow-900 dark:text-yellow-300',
70
- text: 'text-yellow-800 dark:text-yellow-400',
85
+ bg: dsWarning.bg,
86
+ border: dsWarning.border,
87
+ title: dsWarning.text,
88
+ text: dsWarning.text,
71
89
  },
72
90
  danger: {
73
- bg: 'bg-red-50 dark:bg-red-900/20',
74
- border: 'border-red-200 dark:border-red-800',
75
- title: 'text-red-900 dark:text-red-300',
76
- text: 'text-red-800 dark:text-red-400',
91
+ bg: dsError.bg,
92
+ border: dsError.border,
93
+ title: dsError.text,
94
+ text: dsError.text,
77
95
  },
78
96
  neutral: {
79
97
  bg: 'bg-gray-50 dark:bg-gray-800',
@@ -8,6 +8,13 @@ import {
8
8
  import { cn } from '../../lib/utils';
9
9
  import { variants as v } from '@sudobility/design';
10
10
 
11
+ // RN input state classes aligned with design system (colors.component.input.default).
12
+ // CSS pseudo-class selectors (focus:, disabled:) don't apply in RN, so we
13
+ // apply these conditionally via component state instead.
14
+ const inputFocusClass = 'border-blue-500 dark:border-blue-400';
15
+ const inputErrorClass = 'border-red-500 dark:border-red-400';
16
+ const inputDisabledClass = 'opacity-50 bg-gray-100 dark:bg-gray-800';
17
+
11
18
  export interface InputProps extends Omit<TextInputProps, 'style'> {
12
19
  /** Additional class names for styling */
13
20
  className?: string;
@@ -54,9 +61,9 @@ export const Input = React.forwardRef<TextInput, InputProps>(
54
61
  ref={ref}
55
62
  className={cn(
56
63
  v.input.default(),
57
- isFocused && 'border-blue-500 dark:border-blue-400',
58
- error && 'border-red-500 dark:border-red-400',
59
- disabled && 'opacity-50 bg-gray-100 dark:bg-gray-800',
64
+ isFocused && inputFocusClass,
65
+ error && inputErrorClass,
66
+ disabled && inputDisabledClass,
60
67
  className
61
68
  )}
62
69
  editable={isEditable}
@@ -45,7 +45,9 @@ export const Label = React.forwardRef<Text, LabelProps>(
45
45
  {...props}
46
46
  >
47
47
  {children}
48
- {required && <Text className='text-red-500 ml-1'>*</Text>}
48
+ {required && (
49
+ <Text className='text-red-600 dark:text-red-400 ml-1'>*</Text>
50
+ )}
49
51
  </Text>
50
52
  );
51
53
  }
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { View, Text } from 'react-native';
3
3
  import { cn } from '../../lib/utils';
4
+ import { colors } from '@sudobility/design';
4
5
 
5
6
  export interface ProgressCircleProps {
6
7
  /** Progress value (0-100) */
@@ -48,18 +49,18 @@ export const ProgressCircle: React.FC<ProgressCircleProps> = ({
48
49
  label,
49
50
  variant = 'primary',
50
51
  color,
51
- trackColor = '#e5e7eb',
52
+ trackColor = colors.raw.neutral[200],
52
53
  className,
53
54
  }) => {
54
55
  // Clamp value between 0 and 100
55
56
  const progress = Math.min(100, Math.max(0, value));
56
57
 
57
- // Color variants
58
+ // Color variants from design system raw palette
58
59
  const variantColors = {
59
- primary: '#2563eb',
60
- success: '#16a34a',
61
- warning: '#ca8a04',
62
- danger: '#dc2626',
60
+ primary: colors.raw.blue[600],
61
+ success: colors.raw.green[600],
62
+ warning: colors.raw.amber[600],
63
+ danger: colors.raw.red[600],
63
64
  };
64
65
 
65
66
  const progressColor = color || variantColors[variant];
@@ -1,6 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import { View, Text, Pressable } from 'react-native';
3
3
  import { cn } from '../../lib/utils';
4
+ import { designTokens } from '@sudobility/design';
5
+
6
+ const { typography } = designTokens;
4
7
 
5
8
  export interface QuickAction {
6
9
  /** Unique identifier */
@@ -53,6 +56,7 @@ export const QuickActions: React.FC<QuickActionsProps> = ({
53
56
  columns = 3,
54
57
  className,
55
58
  }) => {
59
+ // Variant styles aligned with DS button colors (colors.component.button)
56
60
  const variantStyles = {
57
61
  default: {
58
62
  bg: 'bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700',
@@ -60,23 +64,23 @@ export const QuickActions: React.FC<QuickActionsProps> = ({
60
64
  text: 'text-gray-900 dark:text-white',
61
65
  },
62
66
  primary: {
63
- bg: 'bg-blue-500 border-blue-500',
64
- bgActive: 'active:bg-blue-600',
67
+ bg: 'bg-blue-600 border-blue-600',
68
+ bgActive: 'active:bg-blue-800',
65
69
  text: 'text-white',
66
70
  },
67
71
  success: {
68
- bg: 'bg-green-500 border-green-500',
69
- bgActive: 'active:bg-green-600',
72
+ bg: 'bg-green-600 border-green-600',
73
+ bgActive: 'active:bg-green-800',
70
74
  text: 'text-white',
71
75
  },
72
76
  warning: {
73
- bg: 'bg-yellow-500 border-yellow-500',
74
- bgActive: 'active:bg-yellow-600',
77
+ bg: 'bg-orange-600 border-orange-600',
78
+ bgActive: 'active:bg-orange-800',
75
79
  text: 'text-white',
76
80
  },
77
81
  danger: {
78
- bg: 'bg-red-500 border-red-500',
79
- bgActive: 'active:bg-red-600',
82
+ bg: 'bg-red-600 border-red-600',
83
+ bgActive: 'active:bg-red-800',
80
84
  text: 'text-white',
81
85
  },
82
86
  };
@@ -121,7 +125,7 @@ export const QuickActions: React.FC<QuickActionsProps> = ({
121
125
  accessibilityState={{ disabled: action.disabled }}
122
126
  >
123
127
  {action.icon && <View className='w-5 h-5'>{action.icon}</View>}
124
- <Text className={cn('font-medium', styles.text)}>
128
+ <Text className={cn(typography.weight.medium, styles.text)}>
125
129
  {action.label}
126
130
  </Text>
127
131
  </Pressable>
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { View, ActivityIndicator, Text, type ViewProps } from 'react-native';
3
3
  import { cn } from '../../lib/utils';
4
+ import { colors, textVariants } from '@sudobility/design';
4
5
 
5
6
  /**
6
7
  * Props for the Spinner loading indicator component.
@@ -25,12 +26,13 @@ const sizeMap = {
25
26
  extraLarge: 'large' as const,
26
27
  };
27
28
 
29
+ // Spinner colors from design system raw palette
28
30
  const colorMap = {
29
- default: '#2563eb', // blue-600
31
+ default: colors.raw.blue[600],
30
32
  white: '#ffffff',
31
- success: '#16a34a', // green-600
32
- warning: '#ea580c', // orange-600
33
- error: '#dc2626', // red-600
33
+ success: colors.raw.green[600],
34
+ warning: colors.raw.orange[600],
35
+ error: colors.raw.red[600],
34
36
  };
35
37
 
36
38
  /**
@@ -62,7 +64,7 @@ export const Spinner: React.FC<SpinnerProps> = ({
62
64
  >
63
65
  <ActivityIndicator size={activitySize} color={color} />
64
66
  {showText && (
65
- <Text className='mt-2 text-gray-600 dark:text-gray-400 text-sm'>
67
+ <Text className={cn(textVariants.body.sm(), 'mt-2')}>
66
68
  {loadingText}
67
69
  </Text>
68
70
  )}
@@ -1,6 +1,21 @@
1
1
  import * as React from 'react';
2
2
  import { Text as RNText } from 'react-native';
3
3
  import { cn } from '../../lib/utils';
4
+ import { designTokens } from '@sudobility/design';
5
+
6
+ const { typography } = designTokens;
7
+
8
+ // Semantic text colors aligned with the design system color architecture.
9
+ // The DS provides these as hex values via colors.semantic; here they are
10
+ // expressed as Tailwind classes for NativeWind consumption.
11
+ const colorClasses = {
12
+ default: 'text-gray-900 dark:text-gray-100',
13
+ muted: 'text-gray-600 dark:text-gray-400',
14
+ primary: 'text-blue-600 dark:text-blue-400',
15
+ success: 'text-green-600 dark:text-green-400',
16
+ warning: 'text-yellow-600 dark:text-yellow-400',
17
+ danger: 'text-red-600 dark:text-red-400',
18
+ } as const;
4
19
 
5
20
  export interface TextProps {
6
21
  /** Text content */
@@ -51,62 +66,14 @@ export const Text: React.FC<TextProps> = ({
51
66
  numberOfLines,
52
67
  className,
53
68
  }) => {
54
- // Size configurations
55
- const sizeClasses = {
56
- xs: 'text-xs',
57
- sm: 'text-sm',
58
- base: 'text-base',
59
- lg: 'text-lg',
60
- xl: 'text-xl',
61
- '2xl': 'text-2xl',
62
- '3xl': 'text-3xl',
63
- '4xl': 'text-4xl',
64
- };
65
-
66
- // Weight configurations
67
- const weightClasses = {
68
- light: 'font-light',
69
- normal: 'font-normal',
70
- medium: 'font-medium',
71
- semibold: 'font-semibold',
72
- bold: 'font-bold',
73
- };
74
-
75
- // Color configurations
76
- const colorClasses = {
77
- default: 'text-gray-900 dark:text-gray-100',
78
- muted: 'text-gray-600 dark:text-gray-400',
79
- primary: 'text-blue-600 dark:text-blue-400',
80
- success: 'text-green-600 dark:text-green-400',
81
- warning: 'text-yellow-600 dark:text-yellow-400',
82
- danger: 'text-red-600 dark:text-red-400',
83
- };
84
-
85
- // Alignment configurations
86
- const alignClasses = align
87
- ? {
88
- left: 'text-left',
89
- center: 'text-center',
90
- right: 'text-right',
91
- }[align]
92
- : '';
93
-
94
- // Transform configurations
95
- const transformClasses = {
96
- none: '',
97
- uppercase: 'uppercase',
98
- lowercase: 'lowercase',
99
- capitalize: 'capitalize',
100
- };
101
-
102
69
  return (
103
70
  <RNText
104
71
  className={cn(
105
- sizeClasses[size],
106
- weightClasses[weight],
72
+ typography.size[size],
73
+ typography.weight[weight],
107
74
  colorClasses[color],
108
- alignClasses,
109
- transformClasses[transform],
75
+ align ? typography.align[align] : '',
76
+ transform !== 'none' ? typography.transform[transform] : '',
110
77
  className
111
78
  )}
112
79
  numberOfLines={numberOfLines}
@@ -8,6 +8,35 @@ import React, {
8
8
  } from 'react';
9
9
  import { View, Text, Pressable, Animated, SafeAreaView } from 'react-native';
10
10
  import { cn } from '../../lib/utils';
11
+ import { colors, textVariants } from '@sudobility/design';
12
+
13
+ // Split DS alert colors for RN (Views don't cascade text color)
14
+ function splitAlertClasses(base: string, dark: string) {
15
+ const all = `${base} ${dark}`.split(' ');
16
+ return {
17
+ container: all
18
+ .filter(c => c.includes('bg-') || c.includes('border-'))
19
+ .join(' '),
20
+ icon: all.filter(c => c.includes('text-')).join(' '),
21
+ };
22
+ }
23
+
24
+ // Lazily derive alert colors so module-level access doesn't fail
25
+ // when Jest transforms ESM chunk imports.
26
+ let _alertColors: Record<string, ReturnType<typeof splitAlertClasses>> | null =
27
+ null;
28
+ function getAlertColors() {
29
+ if (!_alertColors) {
30
+ const alert = colors.component.alert;
31
+ _alertColors = {
32
+ success: splitAlertClasses(alert.success.base, alert.success.dark),
33
+ error: splitAlertClasses(alert.error.base, alert.error.dark),
34
+ warning: splitAlertClasses(alert.warning.base, alert.warning.dark),
35
+ info: splitAlertClasses(alert.info.base, alert.info.dark),
36
+ };
37
+ }
38
+ return _alertColors;
39
+ }
11
40
 
12
41
  /** Data structure representing a single toast notification. */
13
42
  export interface ToastMessage {
@@ -72,24 +101,25 @@ export const Toast: React.FC<ToastProps> = ({ toast, onRemove }) => {
72
101
  }).start();
73
102
  }, [slideAnim]);
74
103
 
75
- // Variant styles - background
104
+ const ac = getAlertColors();
105
+
106
+ // Variant background+border from design system (colors.component.alert)
76
107
  const variantBgClasses = {
77
108
  default: 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700',
78
- success:
79
- 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800',
80
- error: 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800',
81
- warning:
82
- 'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800',
83
- info: 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800',
109
+ success: ac.success.container,
110
+ error: ac.error.container,
111
+ warning: ac.warning.container,
112
+ info: ac.info.container,
84
113
  };
85
114
 
86
- // Variant styles - icon color
115
+ // Variant icon colors from design system
116
+ const alert = colors.component.alert;
87
117
  const iconColorClasses = {
88
118
  default: 'text-gray-600 dark:text-gray-400',
89
- success: 'text-green-600 dark:text-green-400',
90
- error: 'text-red-600 dark:text-red-400',
91
- warning: 'text-yellow-600 dark:text-yellow-400',
92
- info: 'text-blue-600 dark:text-blue-400',
119
+ success: alert.success.icon,
120
+ error: alert.error.icon,
121
+ warning: alert.warning.icon,
122
+ info: alert.info.icon,
93
123
  };
94
124
 
95
125
  // Icon symbols
@@ -116,12 +146,17 @@ export const Toast: React.FC<ToastProps> = ({ toast, onRemove }) => {
116
146
 
117
147
  <View className='flex-1 min-w-0'>
118
148
  {title && (
119
- <Text className='font-semibold text-gray-900 dark:text-white'>
149
+ <Text
150
+ className={cn(
151
+ textVariants.label.default(),
152
+ 'text-gray-900 dark:text-white'
153
+ )}
154
+ >
120
155
  {title}
121
156
  </Text>
122
157
  )}
123
158
  {description && (
124
- <Text className='text-sm text-gray-600 dark:text-gray-300 mt-1'>
159
+ <Text className={cn(textVariants.body.sm(), 'mt-1')}>
125
160
  {description}
126
161
  </Text>
127
162
  )}