@compsych/mobile-ui 1.0.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.
@@ -0,0 +1,214 @@
1
+ import React from 'react';
2
+ import { StyleSheet, Text, View } from 'react-native';
3
+ import { sys } from './tokens';
4
+
5
+ export type TooltipVariant = 'filled' | 'elevated';
6
+ /**
7
+ * The direction the arrow points — i.e. where the tooltip's target is
8
+ * relative to the bubble.
9
+ *
10
+ * `bottom` → bubble sits above target, arrow points down
11
+ * `top` → bubble sits below target, arrow points up
12
+ * `left` → bubble sits right of target, arrow points left
13
+ * `right` → bubble sits left of target, arrow points right
14
+ * `none` → no arrow
15
+ */
16
+ export type TooltipDirection = 'none' | 'top' | 'bottom' | 'left' | 'right';
17
+
18
+ export interface TooltipProps {
19
+ text: string;
20
+ variant?: TooltipVariant;
21
+ direction?: TooltipDirection;
22
+ }
23
+
24
+ const { colorRoles: cr, dimensions: dim, typeScale: ts } = sys;
25
+
26
+ // ── Arrow dimensions ─────────────────────────────────────────────────────────
27
+ const ARROW_BASE = 16; // px — wide axis
28
+ const ARROW_HEIGHT = 6; // px — narrow axis
29
+
30
+ // ── Component ────────────────────────────────────────────────────────────────
31
+
32
+ export function Tooltip({
33
+ text,
34
+ variant = 'filled',
35
+ direction = 'none',
36
+ }: TooltipProps) {
37
+ const isFilled = variant === 'filled';
38
+
39
+ const bg = isFilled
40
+ ? cr.surface.inverse.sysInverseSurface
41
+ : cr.surface.surfaceContainer.sysSurfaceContainerLowest;
42
+
43
+ const textColor = isFilled
44
+ ? cr.surface.inverse.sysInverseOnSurface
45
+ : cr.surface.surface.sysOnSurface;
46
+
47
+ // ── Arrow (CSS triangle using zero-size View + borders) ───────────────────
48
+ // Half-base for side calculations
49
+ const half = ARROW_BASE / 2;
50
+
51
+ function renderArrow() {
52
+ if (direction === 'none') return null;
53
+
54
+ // Shared transparent sides
55
+ const transparent = 'transparent';
56
+
57
+ let arrowStyle: object;
58
+
59
+ switch (direction) {
60
+ case 'bottom':
61
+ // Triangle pointing down — sits below bubble
62
+ arrowStyle = {
63
+ width: 0,
64
+ height: 0,
65
+ borderLeftWidth: half,
66
+ borderRightWidth: half,
67
+ borderTopWidth: ARROW_HEIGHT,
68
+ borderLeftColor: transparent,
69
+ borderRightColor: transparent,
70
+ borderTopColor: bg,
71
+ };
72
+ break;
73
+ case 'top':
74
+ // Triangle pointing up — sits above bubble
75
+ arrowStyle = {
76
+ width: 0,
77
+ height: 0,
78
+ borderLeftWidth: half,
79
+ borderRightWidth: half,
80
+ borderBottomWidth: ARROW_HEIGHT,
81
+ borderLeftColor: transparent,
82
+ borderRightColor: transparent,
83
+ borderBottomColor: bg,
84
+ };
85
+ break;
86
+ case 'left':
87
+ // Triangle pointing left — sits to the left of bubble
88
+ arrowStyle = {
89
+ width: 0,
90
+ height: 0,
91
+ borderTopWidth: half,
92
+ borderBottomWidth: half,
93
+ borderRightWidth: ARROW_HEIGHT,
94
+ borderTopColor: transparent,
95
+ borderBottomColor: transparent,
96
+ borderRightColor: bg,
97
+ };
98
+ break;
99
+ case 'right':
100
+ // Triangle pointing right — sits to the right of bubble
101
+ arrowStyle = {
102
+ width: 0,
103
+ height: 0,
104
+ borderTopWidth: half,
105
+ borderBottomWidth: half,
106
+ borderLeftWidth: ARROW_HEIGHT,
107
+ borderTopColor: transparent,
108
+ borderBottomColor: transparent,
109
+ borderLeftColor: bg,
110
+ };
111
+ break;
112
+ }
113
+
114
+ return <View accessible={false} style={arrowStyle!} />;
115
+ }
116
+
117
+ // ── Bubble ────────────────────────────────────────────────────────────────
118
+ const bubble = (
119
+ <View
120
+ accessible
121
+ accessibilityRole="text"
122
+ accessibilityLabel={text}
123
+ style={[
124
+ styles.bubble,
125
+ {
126
+ backgroundColor: bg,
127
+ paddingHorizontal: dim.spacing.padding.sysPadding12,
128
+ paddingVertical: dim.spacing.padding.sysPadding8,
129
+ borderRadius: dim.borderRadius.sysRadiusSm,
130
+ },
131
+ !isFilled && styles.elevated,
132
+ !isFilled && {
133
+ borderWidth: dim.borderWidth.sysStrokeThin,
134
+ borderColor: cr.outline.sysOutline,
135
+ },
136
+ ]}
137
+ >
138
+ <Text
139
+ style={{
140
+ color: textColor,
141
+ fontSize: ts.bodySmall.sysFontSize,
142
+ lineHeight: ts.bodySmall.sysLineHeight,
143
+ fontWeight: '400',
144
+ textAlign: 'center',
145
+ includeFontPadding: false,
146
+ }}
147
+ >
148
+ {text}
149
+ </Text>
150
+ </View>
151
+ );
152
+
153
+ // ── Layout — arrow position determines stacking order ────────────────────
154
+ if (direction === 'none') {
155
+ return bubble;
156
+ }
157
+
158
+ if (direction === 'bottom') {
159
+ return (
160
+ <View style={styles.colCenter}>
161
+ {bubble}
162
+ {renderArrow()}
163
+ </View>
164
+ );
165
+ }
166
+
167
+ if (direction === 'top') {
168
+ return (
169
+ <View style={styles.colCenter}>
170
+ {renderArrow()}
171
+ {bubble}
172
+ </View>
173
+ );
174
+ }
175
+
176
+ if (direction === 'right') {
177
+ return (
178
+ <View style={styles.rowCenter}>
179
+ {bubble}
180
+ {renderArrow()}
181
+ </View>
182
+ );
183
+ }
184
+
185
+ // left
186
+ return (
187
+ <View style={styles.rowCenter}>
188
+ {renderArrow()}
189
+ {bubble}
190
+ </View>
191
+ );
192
+ }
193
+
194
+ const styles = StyleSheet.create({
195
+ bubble: {
196
+ alignSelf: 'flex-start',
197
+ },
198
+ elevated: {
199
+ shadowColor: '#000',
200
+ shadowOffset: { width: 0, height: 2 },
201
+ shadowOpacity: 0.06,
202
+ shadowRadius: 4,
203
+ elevation: 1,
204
+ },
205
+ colCenter: {
206
+ alignItems: 'center',
207
+ alignSelf: 'flex-start',
208
+ },
209
+ rowCenter: {
210
+ flexDirection: 'row',
211
+ alignItems: 'center',
212
+ alignSelf: 'flex-start',
213
+ },
214
+ });
package/src/index.ts ADDED
@@ -0,0 +1,62 @@
1
+ // ComPsych Mobile UI — React Native Component Library
2
+ // Version 1.0.0
3
+
4
+ export { Alert } from './Alert';
5
+ export type { AlertProps, AlertVariant, AlertSize } from './Alert';
6
+
7
+ export { Avatar } from './Avatar';
8
+ export type { AvatarProps, AvatarSize, AvatarVariant } from './Avatar';
9
+
10
+ export { Badge } from './Badge';
11
+ export type { BadgeProps, BadgeSize, BadgeStyle } from './Badge';
12
+
13
+ export { Breadcrumb } from './Breadcrumb';
14
+ export type { BreadcrumbProps, BreadcrumbItem, BreadcrumbSize } from './Breadcrumb';
15
+
16
+ export { Button } from './Button';
17
+ export type { ButtonProps, ButtonVariant, ButtonSize } from './Button';
18
+
19
+ export { Card } from './Card';
20
+ export type { CardProps, CardVariant, CardSize } from './Card';
21
+
22
+ export { Checkbox } from './Checkbox';
23
+ export type { CheckboxProps, CheckboxSize, CheckboxCheckedState } from './Checkbox';
24
+
25
+ export { Chip } from './Chip';
26
+ export type { ChipProps, ChipSize, ChipUsage } from './Chip';
27
+
28
+ export { Divider } from './Divider';
29
+ export type { DividerProps, DividerVariant, DividerWeight } from './Divider';
30
+
31
+ export { EmptyState } from './EmptyState';
32
+ export type { EmptyStateProps, EmptyStateStyle, EmptyStateViewport } from './EmptyState';
33
+
34
+ export { Input } from './Input';
35
+ export type { InputProps, InputSize } from './Input';
36
+
37
+ export { Pagination } from './Pagination';
38
+ export type { PaginationProps, PaginationSize } from './Pagination';
39
+
40
+ export { ProgressTracker, ProgressBar } from './ProgressTracker';
41
+ export type { ProgressTrackerProps, ProgressTrackerSize, ProgressBarProps, TrackerStep, StepState } from './ProgressTracker';
42
+
43
+ export { RadioButton } from './RadioButton';
44
+ export type { RadioButtonProps, RadioButtonSize } from './RadioButton';
45
+
46
+ export { Slider } from './Slider';
47
+ export type { SliderProps } from './Slider';
48
+
49
+ export { Switch } from './Switch';
50
+ export type { SwitchProps } from './Switch';
51
+
52
+ export { Tooltip } from './Tooltip';
53
+ export type { TooltipProps, TooltipVariant, TooltipDirection } from './Tooltip';
54
+
55
+ export { BodyText } from './BodyText';
56
+ export type { BodyTextProps, BodyVariant } from './BodyText';
57
+
58
+ export { HeaderText } from './HeaderText';
59
+ export type { HeaderTextProps, HeaderVariant } from './HeaderText';
60
+
61
+ // Re-export tokens for consumers who need direct access
62
+ export { sys } from './tokens';
package/src/tokens.ts ADDED
@@ -0,0 +1,121 @@
1
+ export const sys = {
2
+ colorRoles: {
3
+ accent: {
4
+ primary: {
5
+ sysPrimary: '#075cba',
6
+ sysOnPrimary: '#ffffff',
7
+ sysPrimaryContainer: '#070f36',
8
+ },
9
+ },
10
+ addOn: {
11
+ primaryFixed: {
12
+ sysOnPrimaryFixed: '#075cba',
13
+ sysOnPrimaryFixedVariant: '#4285f4',
14
+ sysPrimaryFixedDim: '#e3f2fd',
15
+ },
16
+ },
17
+ custom: {
18
+ info: {
19
+ sysInfo: '#226cee',
20
+ sysInfoContainer: '#daecff',
21
+ sysOnInfo: '#ffffff',
22
+ sysOnInfoContainer: '#162755',
23
+ },
24
+ success: {
25
+ sysSuccess: '#278647',
26
+ sysSuccessContainer: '#c4e9d0',
27
+ sysOnSuccess: '#ffffff',
28
+ sysOnSuccessContainer: '#030c09',
29
+ },
30
+ warning: {
31
+ sysWarning: '#d67d00',
32
+ sysWarningContainer: '#fdeed9',
33
+ sysOnWarning: '#ffffff',
34
+ sysOnWarningContainer: '#3a2304',
35
+ },
36
+ },
37
+ error: {
38
+ sysError: '#d82727',
39
+ sysErrorContainer: '#f7d4d4',
40
+ sysOnError: '#ffffff',
41
+ sysOnErrorContainer: '#570f0f',
42
+ },
43
+ outline: {
44
+ sysOutline: '#ebecf0',
45
+ sysOutlineFixed: '#8a96a6',
46
+ sysOutlineVariant: '#d7dbe0',
47
+ },
48
+ surface: {
49
+ surface: {
50
+ sysSurface: '#f3f4f6',
51
+ sysOnSurface: '#1b1d22',
52
+ sysOnSurfaceVariant: '#565f6c',
53
+ },
54
+ surfaceContainer: {
55
+ sysSurfaceContainerLowest: '#ffffff',
56
+ sysSurfaceContainerLow: '#f9fafb',
57
+ sysSurfaceContainer: '#f3f4f6',
58
+ sysSurfaceContainerHighest: '#e6e8ea',
59
+ },
60
+ inverse: {
61
+ sysInverseSurface: '#565f6c',
62
+ sysInverseOnSurface: '#ffffff',
63
+ },
64
+ },
65
+ transparent: {
66
+ neutral: {
67
+ sysBlack10: '#0000001a',
68
+ sysWhite20: '#ffffff33',
69
+ },
70
+ primary: {
71
+ sysPrimary08: '#075cba14',
72
+ },
73
+ },
74
+ },
75
+ dimensions: {
76
+ borderRadius: {
77
+ sysRadiusXs: 4,
78
+ sysRadiusSm: 8,
79
+ sysRadiusMd: 12,
80
+ sysRadiusLg: 16,
81
+ sysRadiusXl: 24,
82
+ sysRadiusFull: 999,
83
+ },
84
+ borderWidth: {
85
+ sysStrokeThin: 1,
86
+ sysStrokeMedium: 1.5,
87
+ sysStrokeThick: 2,
88
+ sysStrokeBold: 4,
89
+ },
90
+ spacing: {
91
+ padding: {
92
+ sysPadding2: 2,
93
+ sysPadding4: 4,
94
+ sysPadding8: 8,
95
+ sysPadding12: 12,
96
+ sysPadding16: 16,
97
+ sysPadding20: 20,
98
+ sysPadding24: 24,
99
+ sysPadding32: 32,
100
+ sysPadding48: 48,
101
+ },
102
+ },
103
+ },
104
+ typeScale: {
105
+ displayLarge: { sysFontSize: 80, sysLineHeight: 80, sysTracking: -2.4, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
106
+ displayMedium: { sysFontSize: 72, sysLineHeight: 79.2, sysTracking: -1.44, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
107
+ displaySmall: { sysFontSize: 64, sysLineHeight: 76.8, sysTracking: -0.64, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
108
+ headlineLarge: { sysFontSize: 48, sysLineHeight: 57.6, sysTracking: -0.48, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
109
+ headlineMedium: { sysFontSize: 40, sysLineHeight: 48, sysTracking: 0, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
110
+ headlineSmall: { sysFontSize: 32, sysLineHeight: 38.4, sysTracking: 0, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
111
+ titleLarge: { sysFontSize: 28, sysLineHeight: 36.4, sysTracking: 0, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
112
+ titleMedium: { sysFontSize: 24, sysLineHeight: 31.2, sysTracking: 0, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
113
+ titleSmall: { sysFontSize: 20, sysLineHeight: 28, sysTracking: 0, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
114
+ bodyLarge: { sysFontSize: 20, sysLineHeight: 30, sysTracking: 0, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
115
+ bodyMedium: { sysFontSize: 16, sysLineHeight: 24, sysTracking: 0, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
116
+ bodySmall: { sysFontSize: 14, sysLineHeight: 20, sysTracking: 0, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
117
+ labelLarge: { sysFontSize: 16, sysLineHeight: 22.4, sysTracking: 0, sysFontWeight: 'regular', sysFontWeightEmphasized: 'medium' },
118
+ labelMedium: { sysFontSize: 14, sysLineHeight: 20, sysTracking: 0.07, sysFontWeight: 'medium', sysFontWeightEmphasized: 'semibold' },
119
+ labelSmall: { sysFontSize: 12, sysLineHeight: 18, sysTracking: 0.12, sysFontWeight: 'medium', sysFontWeightEmphasized: 'semibold' },
120
+ },
121
+ } as const;