@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.
- package/dist/index.cjs.js +217 -194
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +220 -197
- package/dist/index.esm.js.map +1 -1
- package/dist/ui/Alert/Alert.d.ts.map +1 -1
- package/dist/ui/Badge/Badge.d.ts.map +1 -1
- package/dist/ui/Banner/Banner.d.ts.map +1 -1
- package/dist/ui/Divider/Divider.d.ts.map +1 -1
- package/dist/ui/Heading/Heading.d.ts.map +1 -1
- package/dist/ui/InfoBox/InfoBox.d.ts.map +1 -1
- package/dist/ui/Input/Input.d.ts.map +1 -1
- package/dist/ui/Label/Label.d.ts.map +1 -1
- package/dist/ui/ProgressCircle/ProgressCircle.d.ts.map +1 -1
- package/dist/ui/QuickActions/QuickActions.d.ts.map +1 -1
- package/dist/ui/Spinner/Spinner.d.ts.map +1 -1
- package/dist/ui/Text/Text.d.ts.map +1 -1
- package/dist/ui/Toast/Toast.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/__tests__/alert.test.tsx +20 -10
- package/src/__tests__/dialog.test.tsx +30 -1
- package/src/__tests__/sheet.test.tsx +42 -1
- package/src/__tests__/toast.test.tsx +32 -0
- package/src/ui/Alert/Alert.tsx +13 -5
- package/src/ui/Badge/Badge.tsx +61 -26
- package/src/ui/Banner/Banner.tsx +36 -25
- package/src/ui/Card/Card.tsx +2 -2
- package/src/ui/Divider/Divider.tsx +12 -17
- package/src/ui/Heading/Heading.tsx +15 -38
- package/src/ui/InfoBox/InfoBox.tsx +35 -17
- package/src/ui/Input/Input.tsx +10 -3
- package/src/ui/Label/Label.tsx +3 -1
- package/src/ui/ProgressCircle/ProgressCircle.tsx +7 -6
- package/src/ui/QuickActions/QuickActions.tsx +13 -9
- package/src/ui/Spinner/Spinner.tsx +7 -5
- package/src/ui/Text/Text.tsx +19 -52
- package/src/ui/Toast/Toast.tsx +49 -14
package/src/ui/Banner/Banner.tsx
CHANGED
|
@@ -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
|
-
|
|
34
|
-
descriptionColor: string;
|
|
54
|
+
textColor: string;
|
|
35
55
|
}
|
|
36
56
|
> = {
|
|
37
57
|
[InfoType.INFO]: {
|
|
38
58
|
icon: '\u2139',
|
|
39
|
-
container:
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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:
|
|
64
|
-
iconColor:
|
|
65
|
-
|
|
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.
|
|
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.
|
|
224
|
+
<Text className={cn('text-sm mt-1', config.textColor)}>
|
|
214
225
|
{description}
|
|
215
226
|
</Text>
|
|
216
227
|
) : null}
|
package/src/ui/Card/Card.tsx
CHANGED
|
@@ -29,8 +29,8 @@ export interface CardProps extends ViewProps {
|
|
|
29
29
|
onClose?: () => void;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
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',
|
|
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
|
-
|
|
102
|
-
|
|
76
|
+
typography.size[actualSize],
|
|
77
|
+
typography.weight[weight],
|
|
78
|
+
typography.leading.heading,
|
|
79
|
+
typography.tracking.heading,
|
|
103
80
|
colorClasses[color],
|
|
104
|
-
|
|
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
|
|
70
|
+
// Color variants derived from design system (colors.component.alert)
|
|
53
71
|
const variantClasses = {
|
|
54
72
|
info: {
|
|
55
|
-
bg:
|
|
56
|
-
border:
|
|
57
|
-
title:
|
|
58
|
-
text:
|
|
73
|
+
bg: dsInfo.bg,
|
|
74
|
+
border: dsInfo.border,
|
|
75
|
+
title: dsInfo.text,
|
|
76
|
+
text: dsInfo.text,
|
|
59
77
|
},
|
|
60
78
|
success: {
|
|
61
|
-
bg:
|
|
62
|
-
border:
|
|
63
|
-
title:
|
|
64
|
-
text:
|
|
79
|
+
bg: dsSuccess.bg,
|
|
80
|
+
border: dsSuccess.border,
|
|
81
|
+
title: dsSuccess.text,
|
|
82
|
+
text: dsSuccess.text,
|
|
65
83
|
},
|
|
66
84
|
warning: {
|
|
67
|
-
bg:
|
|
68
|
-
border:
|
|
69
|
-
title:
|
|
70
|
-
text:
|
|
85
|
+
bg: dsWarning.bg,
|
|
86
|
+
border: dsWarning.border,
|
|
87
|
+
title: dsWarning.text,
|
|
88
|
+
text: dsWarning.text,
|
|
71
89
|
},
|
|
72
90
|
danger: {
|
|
73
|
-
bg:
|
|
74
|
-
border:
|
|
75
|
-
title:
|
|
76
|
-
text:
|
|
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',
|
package/src/ui/Input/Input.tsx
CHANGED
|
@@ -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 &&
|
|
58
|
-
error &&
|
|
59
|
-
disabled &&
|
|
64
|
+
isFocused && inputFocusClass,
|
|
65
|
+
error && inputErrorClass,
|
|
66
|
+
disabled && inputDisabledClass,
|
|
60
67
|
className
|
|
61
68
|
)}
|
|
62
69
|
editable={isEditable}
|
package/src/ui/Label/Label.tsx
CHANGED
|
@@ -45,7 +45,9 @@ export const Label = React.forwardRef<Text, LabelProps>(
|
|
|
45
45
|
{...props}
|
|
46
46
|
>
|
|
47
47
|
{children}
|
|
48
|
-
{required &&
|
|
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 =
|
|
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:
|
|
60
|
-
success:
|
|
61
|
-
warning:
|
|
62
|
-
danger:
|
|
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-
|
|
64
|
-
bgActive: 'active:bg-blue-
|
|
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-
|
|
69
|
-
bgActive: 'active:bg-green-
|
|
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-
|
|
74
|
-
bgActive: 'active:bg-
|
|
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-
|
|
79
|
-
bgActive: 'active:bg-red-
|
|
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(
|
|
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:
|
|
31
|
+
default: colors.raw.blue[600],
|
|
30
32
|
white: '#ffffff',
|
|
31
|
-
success:
|
|
32
|
-
warning:
|
|
33
|
-
error:
|
|
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
|
|
67
|
+
<Text className={cn(textVariants.body.sm(), 'mt-2')}>
|
|
66
68
|
{loadingText}
|
|
67
69
|
</Text>
|
|
68
70
|
)}
|
package/src/ui/Text/Text.tsx
CHANGED
|
@@ -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
|
-
|
|
106
|
-
|
|
72
|
+
typography.size[size],
|
|
73
|
+
typography.weight[weight],
|
|
107
74
|
colorClasses[color],
|
|
108
|
-
|
|
109
|
-
|
|
75
|
+
align ? typography.align[align] : '',
|
|
76
|
+
transform !== 'none' ? typography.transform[transform] : '',
|
|
110
77
|
className
|
|
111
78
|
)}
|
|
112
79
|
numberOfLines={numberOfLines}
|
package/src/ui/Toast/Toast.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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:
|
|
90
|
-
error:
|
|
91
|
-
warning:
|
|
92
|
-
info:
|
|
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
|
|
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=
|
|
159
|
+
<Text className={cn(textVariants.body.sm(), 'mt-1')}>
|
|
125
160
|
{description}
|
|
126
161
|
</Text>
|
|
127
162
|
)}
|