@sublime-ui/library 0.1.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 (249) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/dist/components/AppBar/AppBar.d.ts +6 -0
  4. package/dist/components/AppBar/AppBar.js +55 -0
  5. package/dist/components/AppBar/AppBar.types.d.ts +11 -0
  6. package/dist/components/AppBar/AppBar.types.js +0 -0
  7. package/dist/components/AppBar/index.d.ts +3 -0
  8. package/dist/components/AppBar/index.js +4 -0
  9. package/dist/components/Avatar/Avatar.d.ts +7 -0
  10. package/dist/components/Avatar/Avatar.js +30 -0
  11. package/dist/components/Avatar/Avatar.types.d.ts +12 -0
  12. package/dist/components/Avatar/Avatar.types.js +0 -0
  13. package/dist/components/Avatar/index.d.ts +4 -0
  14. package/dist/components/Avatar/index.js +4 -0
  15. package/dist/components/Badge/Badge.d.ts +7 -0
  16. package/dist/components/Badge/Badge.js +75 -0
  17. package/dist/components/Badge/Badge.types.d.ts +12 -0
  18. package/dist/components/Badge/Badge.types.js +0 -0
  19. package/dist/components/Badge/index.d.ts +4 -0
  20. package/dist/components/Badge/index.js +4 -0
  21. package/dist/components/Banner/Banner.d.ts +7 -0
  22. package/dist/components/Banner/Banner.js +68 -0
  23. package/dist/components/Banner/Banner.types.d.ts +13 -0
  24. package/dist/components/Banner/Banner.types.js +0 -0
  25. package/dist/components/Banner/index.d.ts +4 -0
  26. package/dist/components/Banner/index.js +4 -0
  27. package/dist/components/BottomNav/BottomNav.d.ts +7 -0
  28. package/dist/components/BottomNav/BottomNav.js +9 -0
  29. package/dist/components/BottomNav/BottomNav.types.d.ts +10 -0
  30. package/dist/components/BottomNav/BottomNav.types.js +0 -0
  31. package/dist/components/BottomNav/index.d.ts +3 -0
  32. package/dist/components/BottomNav/index.js +4 -0
  33. package/dist/components/Button/Button.d.ts +7 -0
  34. package/dist/components/Button/Button.js +42 -0
  35. package/dist/components/Button/Button.types.d.ts +17 -0
  36. package/dist/components/Button/Button.types.js +0 -0
  37. package/dist/components/Button/index.d.ts +4 -0
  38. package/dist/components/Button/index.js +4 -0
  39. package/dist/components/Card/Card.d.ts +6 -0
  40. package/dist/components/Card/Card.js +26 -0
  41. package/dist/components/Card/Card.types.d.ts +10 -0
  42. package/dist/components/Card/Card.types.js +0 -0
  43. package/dist/components/Card/index.d.ts +3 -0
  44. package/dist/components/Card/index.js +4 -0
  45. package/dist/components/Checkbox/Checkbox.d.ts +7 -0
  46. package/dist/components/Checkbox/Checkbox.js +34 -0
  47. package/dist/components/Checkbox/Checkbox.types.d.ts +17 -0
  48. package/dist/components/Checkbox/Checkbox.types.js +0 -0
  49. package/dist/components/Checkbox/index.d.ts +4 -0
  50. package/dist/components/Checkbox/index.js +4 -0
  51. package/dist/components/Dialog/Dialog.d.ts +6 -0
  52. package/dist/components/Dialog/Dialog.js +51 -0
  53. package/dist/components/Dialog/Dialog.types.d.ts +12 -0
  54. package/dist/components/Dialog/Dialog.types.js +0 -0
  55. package/dist/components/Dialog/index.d.ts +3 -0
  56. package/dist/components/Dialog/index.js +4 -0
  57. package/dist/components/Divider/Divider.d.ts +6 -0
  58. package/dist/components/Divider/Divider.js +19 -0
  59. package/dist/components/Divider/Divider.types.d.ts +7 -0
  60. package/dist/components/Divider/Divider.types.js +0 -0
  61. package/dist/components/Divider/index.d.ts +3 -0
  62. package/dist/components/Divider/index.js +4 -0
  63. package/dist/components/Drawer/Drawer.d.ts +8 -0
  64. package/dist/components/Drawer/Drawer.js +9 -0
  65. package/dist/components/Drawer/Drawer.types.d.ts +13 -0
  66. package/dist/components/Drawer/Drawer.types.js +0 -0
  67. package/dist/components/Drawer/index.d.ts +4 -0
  68. package/dist/components/Drawer/index.js +4 -0
  69. package/dist/components/Fab/Fab.d.ts +7 -0
  70. package/dist/components/Fab/Fab.js +33 -0
  71. package/dist/components/Fab/Fab.types.d.ts +11 -0
  72. package/dist/components/Fab/Fab.types.js +0 -0
  73. package/dist/components/Fab/index.d.ts +4 -0
  74. package/dist/components/Fab/index.js +4 -0
  75. package/dist/components/GlassAppBar/GlassAppBar.d.ts +6 -0
  76. package/dist/components/GlassAppBar/GlassAppBar.js +57 -0
  77. package/dist/components/GlassAppBar/GlassAppBar.types.d.ts +12 -0
  78. package/dist/components/GlassAppBar/GlassAppBar.types.js +0 -0
  79. package/dist/components/GlassAppBar/index.d.ts +3 -0
  80. package/dist/components/GlassAppBar/index.js +4 -0
  81. package/dist/components/Icon/Icon.d.ts +8 -0
  82. package/dist/components/Icon/Icon.js +46 -0
  83. package/dist/components/Icon/Icon.types.d.ts +14 -0
  84. package/dist/components/Icon/Icon.types.js +0 -0
  85. package/dist/components/Icon/index.d.ts +5 -0
  86. package/dist/components/Icon/index.js +4 -0
  87. package/dist/components/Input/Input.d.ts +6 -0
  88. package/dist/components/Input/Input.js +43 -0
  89. package/dist/components/Input/Input.types.d.ts +13 -0
  90. package/dist/components/Input/Input.types.js +0 -0
  91. package/dist/components/Input/index.d.ts +3 -0
  92. package/dist/components/Input/index.js +4 -0
  93. package/dist/components/Select/Select.d.ts +6 -0
  94. package/dist/components/Select/Select.js +50 -0
  95. package/dist/components/Select/Select.types.d.ts +15 -0
  96. package/dist/components/Select/Select.types.js +0 -0
  97. package/dist/components/Select/index.d.ts +3 -0
  98. package/dist/components/Select/index.js +4 -0
  99. package/dist/components/Spinner/Spinner.d.ts +7 -0
  100. package/dist/components/Spinner/Spinner.js +35 -0
  101. package/dist/components/Spinner/Spinner.types.d.ts +9 -0
  102. package/dist/components/Spinner/Spinner.types.js +0 -0
  103. package/dist/components/Spinner/index.d.ts +4 -0
  104. package/dist/components/Spinner/index.js +4 -0
  105. package/dist/components/Surface/Surface.d.ts +6 -0
  106. package/dist/components/Surface/Surface.js +29 -0
  107. package/dist/components/Surface/Surface.types.d.ts +11 -0
  108. package/dist/components/Surface/Surface.types.js +0 -0
  109. package/dist/components/Surface/index.d.ts +3 -0
  110. package/dist/components/Surface/index.js +4 -0
  111. package/dist/components/Switch/Switch.d.ts +7 -0
  112. package/dist/components/Switch/Switch.js +34 -0
  113. package/dist/components/Switch/Switch.types.d.ts +17 -0
  114. package/dist/components/Switch/Switch.types.js +0 -0
  115. package/dist/components/Switch/index.d.ts +4 -0
  116. package/dist/components/Switch/index.js +4 -0
  117. package/dist/components/Text/Text.d.ts +7 -0
  118. package/dist/components/Text/Text.js +34 -0
  119. package/dist/components/Text/Text.types.d.ts +13 -0
  120. package/dist/components/Text/Text.types.js +0 -0
  121. package/dist/components/Text/index.d.ts +4 -0
  122. package/dist/components/Text/index.js +4 -0
  123. package/dist/components/Tooltip/Tooltip.d.ts +6 -0
  124. package/dist/components/Tooltip/Tooltip.js +8 -0
  125. package/dist/components/Tooltip/Tooltip.types.d.ts +11 -0
  126. package/dist/components/Tooltip/Tooltip.types.js +0 -0
  127. package/dist/components/Tooltip/index.d.ts +3 -0
  128. package/dist/components/Tooltip/index.js +4 -0
  129. package/dist/components/common.d.ts +11 -0
  130. package/dist/components/common.js +0 -0
  131. package/dist/index.d.ts +53 -0
  132. package/dist/index.js +54 -0
  133. package/dist/notifications/NotificationContext.d.ts +33 -0
  134. package/dist/notifications/NotificationContext.js +34 -0
  135. package/dist/notifications/NotificationHost.d.ts +5 -0
  136. package/dist/notifications/NotificationHost.js +22 -0
  137. package/dist/notifications/useNotify.d.ts +13 -0
  138. package/dist/notifications/useNotify.js +19 -0
  139. package/dist/provider/SublimeProvider.d.ts +12 -0
  140. package/dist/provider/SublimeProvider.js +23 -0
  141. package/dist/provider/TokenContext.d.ts +14 -0
  142. package/dist/provider/TokenContext.js +5 -0
  143. package/dist/provider/resolveTokens.d.ts +7 -0
  144. package/dist/provider/resolveTokens.js +13 -0
  145. package/dist/provider/useTokens.d.ts +7 -0
  146. package/dist/provider/useTokens.js +12 -0
  147. package/dist/tokens/generateThemes.d.ts +10 -0
  148. package/dist/tokens/generateThemes.js +44 -0
  149. package/dist/tokens/tokens.d.ts +74 -0
  150. package/dist/tokens/tokens.js +78 -0
  151. package/package.json +53 -0
  152. package/src/components/AppBar/AppBar.native.tsx +28 -0
  153. package/src/components/AppBar/AppBar.tsx +51 -0
  154. package/src/components/AppBar/AppBar.types.ts +9 -0
  155. package/src/components/AppBar/index.ts +2 -0
  156. package/src/components/Avatar/Avatar.native.tsx +38 -0
  157. package/src/components/Avatar/Avatar.tsx +38 -0
  158. package/src/components/Avatar/Avatar.types.ts +10 -0
  159. package/src/components/Avatar/index.ts +2 -0
  160. package/src/components/Badge/Badge.native.tsx +97 -0
  161. package/src/components/Badge/Badge.tsx +89 -0
  162. package/src/components/Badge/Badge.types.ts +11 -0
  163. package/src/components/Badge/index.ts +2 -0
  164. package/src/components/Banner/Banner.native.tsx +89 -0
  165. package/src/components/Banner/Banner.tsx +78 -0
  166. package/src/components/Banner/Banner.types.ts +11 -0
  167. package/src/components/Banner/index.ts +2 -0
  168. package/src/components/BottomNav/BottomNav.native.tsx +87 -0
  169. package/src/components/BottomNav/BottomNav.tsx +9 -0
  170. package/src/components/BottomNav/BottomNav.types.ts +8 -0
  171. package/src/components/BottomNav/index.ts +2 -0
  172. package/src/components/Button/Button.native.tsx +27 -0
  173. package/src/components/Button/Button.tsx +37 -0
  174. package/src/components/Button/Button.types.ts +17 -0
  175. package/src/components/Button/index.ts +2 -0
  176. package/src/components/Card/Card.native.tsx +24 -0
  177. package/src/components/Card/Card.tsx +30 -0
  178. package/src/components/Card/Card.types.ts +8 -0
  179. package/src/components/Card/index.ts +2 -0
  180. package/src/components/Checkbox/Checkbox.native.tsx +58 -0
  181. package/src/components/Checkbox/Checkbox.tsx +35 -0
  182. package/src/components/Checkbox/Checkbox.types.ts +15 -0
  183. package/src/components/Checkbox/index.ts +2 -0
  184. package/src/components/Dialog/Dialog.native.tsx +28 -0
  185. package/src/components/Dialog/Dialog.tsx +49 -0
  186. package/src/components/Dialog/Dialog.types.ts +10 -0
  187. package/src/components/Dialog/index.ts +2 -0
  188. package/src/components/Divider/Divider.native.tsx +30 -0
  189. package/src/components/Divider/Divider.tsx +16 -0
  190. package/src/components/Divider/Divider.types.ts +5 -0
  191. package/src/components/Divider/index.ts +2 -0
  192. package/src/components/Drawer/Drawer.native.tsx +113 -0
  193. package/src/components/Drawer/Drawer.tsx +9 -0
  194. package/src/components/Drawer/Drawer.types.ts +11 -0
  195. package/src/components/Drawer/index.ts +2 -0
  196. package/src/components/Fab/Fab.native.tsx +41 -0
  197. package/src/components/Fab/Fab.tsx +36 -0
  198. package/src/components/Fab/Fab.types.ts +9 -0
  199. package/src/components/Fab/index.ts +2 -0
  200. package/src/components/GlassAppBar/GlassAppBar.native.tsx +29 -0
  201. package/src/components/GlassAppBar/GlassAppBar.tsx +53 -0
  202. package/src/components/GlassAppBar/GlassAppBar.types.ts +10 -0
  203. package/src/components/GlassAppBar/index.ts +2 -0
  204. package/src/components/Icon/Icon.native.tsx +39 -0
  205. package/src/components/Icon/Icon.tsx +57 -0
  206. package/src/components/Icon/Icon.types.ts +13 -0
  207. package/src/components/Icon/index.ts +2 -0
  208. package/src/components/Input/Input.native.tsx +34 -0
  209. package/src/components/Input/Input.tsx +33 -0
  210. package/src/components/Input/Input.types.ts +11 -0
  211. package/src/components/Input/index.ts +2 -0
  212. package/src/components/Select/Select.native.tsx +63 -0
  213. package/src/components/Select/Select.tsx +48 -0
  214. package/src/components/Select/Select.types.ts +14 -0
  215. package/src/components/Select/index.ts +2 -0
  216. package/src/components/Spinner/Spinner.native.tsx +38 -0
  217. package/src/components/Spinner/Spinner.tsx +37 -0
  218. package/src/components/Spinner/Spinner.types.ts +7 -0
  219. package/src/components/Spinner/index.ts +2 -0
  220. package/src/components/Surface/Surface.native.tsx +32 -0
  221. package/src/components/Surface/Surface.tsx +31 -0
  222. package/src/components/Surface/Surface.types.ts +10 -0
  223. package/src/components/Surface/index.ts +2 -0
  224. package/src/components/Switch/Switch.native.tsx +58 -0
  225. package/src/components/Switch/Switch.tsx +35 -0
  226. package/src/components/Switch/Switch.types.ts +15 -0
  227. package/src/components/Switch/index.ts +2 -0
  228. package/src/components/Text/Text.native.tsx +31 -0
  229. package/src/components/Text/Text.tsx +34 -0
  230. package/src/components/Text/Text.types.ts +12 -0
  231. package/src/components/Text/index.ts +2 -0
  232. package/src/components/Tooltip/Tooltip.native.tsx +6 -0
  233. package/src/components/Tooltip/Tooltip.tsx +10 -0
  234. package/src/components/Tooltip/Tooltip.types.ts +9 -0
  235. package/src/components/Tooltip/index.ts +2 -0
  236. package/src/components/common.ts +10 -0
  237. package/src/index.ts +33 -0
  238. package/src/notifications/NotificationContext.tsx +57 -0
  239. package/src/notifications/NotificationHost.native.tsx +20 -0
  240. package/src/notifications/NotificationHost.tsx +27 -0
  241. package/src/notifications/useNotify.ts +18 -0
  242. package/src/provider/SublimeProvider.native.tsx +29 -0
  243. package/src/provider/SublimeProvider.tsx +30 -0
  244. package/src/provider/TokenContext.ts +13 -0
  245. package/src/provider/resolveTokens.ts +16 -0
  246. package/src/provider/useTokens.ts +10 -0
  247. package/src/test-utils/renderWeb.tsx +8 -0
  248. package/src/tokens/generateThemes.ts +49 -0
  249. package/src/tokens/tokens.ts +71 -0
@@ -0,0 +1,89 @@
1
+ import { Chip } from '@mui/material';
2
+ import { useTokens } from '../../provider/useTokens.js';
3
+ import type { ResolvedTokens } from '../../provider/TokenContext.js';
4
+ import type { Tone } from '../common.js';
5
+ import type { BadgeProps, BadgeVariant } from './Badge.types.js';
6
+
7
+ interface ColorPair {
8
+ bg: string;
9
+ fg: string;
10
+ }
11
+
12
+ const solidPair = (c: ResolvedTokens['color'], tone: Tone): ColorPair => {
13
+ switch (tone) {
14
+ case 'primary':
15
+ return { bg: c.primary, fg: c.primaryFg };
16
+ case 'success':
17
+ return { bg: c.success, fg: c.primaryFg };
18
+ case 'danger':
19
+ return { bg: c.danger, fg: c.primaryFg };
20
+ case 'warning':
21
+ return { bg: c.warning, fg: c.primaryFg };
22
+ case 'info':
23
+ return { bg: c.info, fg: c.primaryFg };
24
+ case 'neutral':
25
+ default:
26
+ return { bg: c.secondary, fg: c.secondaryFg };
27
+ }
28
+ };
29
+
30
+ const softPair = (c: ResolvedTokens['color'], tone: Tone): ColorPair => {
31
+ switch (tone) {
32
+ case 'primary':
33
+ return { bg: c.primarySoftBg, fg: c.primarySoftFg };
34
+ case 'success':
35
+ return { bg: c.successSoftBg, fg: c.successSoftFg };
36
+ case 'danger':
37
+ return { bg: c.dangerSoftBg, fg: c.dangerSoftFg };
38
+ case 'warning':
39
+ return { bg: c.warningSoftBg, fg: c.warningSoftFg };
40
+ case 'info':
41
+ return { bg: c.infoSoftBg, fg: c.infoSoftFg };
42
+ case 'neutral':
43
+ default:
44
+ return { bg: c.surfaceHover, fg: c.mutedFg };
45
+ }
46
+ };
47
+
48
+ const mutedPair = (c: ResolvedTokens['color']): ColorPair => ({
49
+ bg: c.surfaceHover,
50
+ fg: c.mutedFg,
51
+ });
52
+
53
+ const pairFor = (
54
+ c: ResolvedTokens['color'],
55
+ tone: Tone,
56
+ variant: BadgeVariant,
57
+ ): ColorPair => {
58
+ if (variant === 'solid') return solidPair(c, tone);
59
+ if (variant === 'muted') return mutedPair(c);
60
+ return softPair(c, tone);
61
+ };
62
+
63
+ export function Badge({
64
+ label,
65
+ tone = 'neutral',
66
+ variant = 'soft',
67
+ icon,
68
+ testID,
69
+ }: BadgeProps) {
70
+ const tokens = useTokens();
71
+ const { bg, fg } = pairFor(tokens.color, tone, variant);
72
+ return (
73
+ <Chip
74
+ size="small"
75
+ label={label}
76
+ data-testid={testID}
77
+ sx={{
78
+ backgroundColor: bg,
79
+ color: fg,
80
+ borderRadius: `${tokens.radii.full}px`,
81
+ fontWeight: tokens.typography.weights.medium,
82
+ ...(icon ? { '& .MuiChip-icon': { color: fg } } : {}),
83
+ }}
84
+ {...(icon
85
+ ? { icon: <span className="material-icons" aria-hidden>{icon}</span> }
86
+ : {})}
87
+ />
88
+ );
89
+ }
@@ -0,0 +1,11 @@
1
+ import type { Tone } from '../common.js';
2
+
3
+ export type BadgeVariant = 'solid' | 'soft' | 'muted';
4
+
5
+ export interface BadgeProps {
6
+ label: string;
7
+ tone?: Tone;
8
+ variant?: BadgeVariant;
9
+ icon?: string;
10
+ testID?: string;
11
+ }
@@ -0,0 +1,2 @@
1
+ export { Badge } from './Badge.js';
2
+ export type { BadgeProps } from './Badge.types.js';
@@ -0,0 +1,89 @@
1
+ import { View } from 'react-native';
2
+ import { Text, IconButton } from 'react-native-paper';
3
+ import { useTokens } from '../../provider/useTokens.js';
4
+ import type { ResolvedTokens } from '../../provider/TokenContext.js';
5
+ import type { Tone } from '../common.js';
6
+ import type { BannerProps } from './Banner.types.js';
7
+
8
+ interface ColorPair {
9
+ bg: string;
10
+ fg: string;
11
+ }
12
+
13
+ const softPair = (c: ResolvedTokens['color'], tone: Tone): ColorPair => {
14
+ switch (tone) {
15
+ case 'success':
16
+ return { bg: c.successSoftBg, fg: c.successSoftFg };
17
+ case 'danger':
18
+ return { bg: c.dangerSoftBg, fg: c.dangerSoftFg };
19
+ case 'warning':
20
+ return { bg: c.warningSoftBg, fg: c.warningSoftFg };
21
+ case 'info':
22
+ return { bg: c.infoSoftBg, fg: c.infoSoftFg };
23
+ case 'primary':
24
+ case 'neutral':
25
+ default:
26
+ return { bg: c.primarySoftBg, fg: c.primarySoftFg };
27
+ }
28
+ };
29
+
30
+ export function Banner({
31
+ tone = 'info',
32
+ title,
33
+ children,
34
+ onClose,
35
+ action,
36
+ testID,
37
+ }: BannerProps) {
38
+ const tokens = useTokens();
39
+ const { bg, fg } = softPair(tokens.color, tone);
40
+ return (
41
+ <View
42
+ {...(testID ? { testID } : {})}
43
+ style={{
44
+ flexDirection: 'row',
45
+ alignItems: 'flex-start',
46
+ backgroundColor: bg,
47
+ borderColor: tokens.color.glassBorder,
48
+ borderWidth: 1,
49
+ borderRadius: tokens.radii.md,
50
+ paddingHorizontal: tokens.spacing.md,
51
+ paddingVertical: tokens.spacing.sm,
52
+ }}
53
+ >
54
+ <View style={{ flex: 1 }}>
55
+ {title ? (
56
+ <Text
57
+ style={{
58
+ color: fg,
59
+ fontSize: tokens.typography.sizes.md,
60
+ fontWeight: String(tokens.typography.weights.semibold) as '600',
61
+ marginBottom: tokens.spacing.xs,
62
+ }}
63
+ >
64
+ {title}
65
+ </Text>
66
+ ) : null}
67
+ {typeof children === 'string' ? (
68
+ <Text style={{ color: fg, fontSize: tokens.typography.sizes.sm }}>
69
+ {children}
70
+ </Text>
71
+ ) : (
72
+ children
73
+ )}
74
+ {action ? (
75
+ <View style={{ marginTop: tokens.spacing.sm }}>{action}</View>
76
+ ) : null}
77
+ </View>
78
+ {onClose ? (
79
+ <IconButton
80
+ icon="close"
81
+ size={tokens.typography.sizes.md}
82
+ iconColor={fg}
83
+ onPress={onClose}
84
+ accessibilityLabel="close"
85
+ />
86
+ ) : null}
87
+ </View>
88
+ );
89
+ }
@@ -0,0 +1,78 @@
1
+ import { Alert, AlertTitle } from '@mui/material';
2
+ import type { AlertColor } from '@mui/material';
3
+ import { useTokens } from '../../provider/useTokens.js';
4
+ import type { ResolvedTokens } from '../../provider/TokenContext.js';
5
+ import type { Tone } from '../common.js';
6
+ import type { BannerProps } from './Banner.types.js';
7
+
8
+ const severityFor = (tone: Tone): AlertColor => {
9
+ switch (tone) {
10
+ case 'success':
11
+ return 'success';
12
+ case 'danger':
13
+ return 'error';
14
+ case 'warning':
15
+ return 'warning';
16
+ case 'primary':
17
+ case 'info':
18
+ case 'neutral':
19
+ default:
20
+ return 'info';
21
+ }
22
+ };
23
+
24
+ interface ColorPair {
25
+ bg: string;
26
+ fg: string;
27
+ }
28
+
29
+ const softPair = (c: ResolvedTokens['color'], tone: Tone): ColorPair => {
30
+ switch (tone) {
31
+ case 'success':
32
+ return { bg: c.successSoftBg, fg: c.successSoftFg };
33
+ case 'danger':
34
+ return { bg: c.dangerSoftBg, fg: c.dangerSoftFg };
35
+ case 'warning':
36
+ return { bg: c.warningSoftBg, fg: c.warningSoftFg };
37
+ case 'info':
38
+ return { bg: c.infoSoftBg, fg: c.infoSoftFg };
39
+ case 'primary':
40
+ case 'neutral':
41
+ default:
42
+ return { bg: c.primarySoftBg, fg: c.primarySoftFg };
43
+ }
44
+ };
45
+
46
+ export function Banner({
47
+ tone = 'info',
48
+ title,
49
+ children,
50
+ onClose,
51
+ action,
52
+ testID,
53
+ }: BannerProps) {
54
+ const tokens = useTokens();
55
+ const { bg, fg } = softPair(tokens.color, tone);
56
+ return (
57
+ <Alert
58
+ severity={severityFor(tone)}
59
+ data-testid={testID}
60
+ {...(onClose ? { onClose } : {})}
61
+ {...(action ? { action } : {})}
62
+ sx={{
63
+ backgroundColor: bg,
64
+ color: fg,
65
+ borderRadius: `${tokens.radii.md}px`,
66
+ '& .MuiAlert-icon': { color: fg },
67
+ '& .MuiAlert-action': { color: fg },
68
+ }}
69
+ >
70
+ {title ? (
71
+ <AlertTitle sx={{ fontWeight: tokens.typography.weights.semibold }}>
72
+ {title}
73
+ </AlertTitle>
74
+ ) : null}
75
+ {children}
76
+ </Alert>
77
+ );
78
+ }
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Tone } from '../common.js';
3
+
4
+ export interface BannerProps {
5
+ tone?: Tone;
6
+ title?: string;
7
+ children: ReactNode;
8
+ onClose?: () => void;
9
+ action?: ReactNode;
10
+ testID?: string;
11
+ }
@@ -0,0 +1,2 @@
1
+ export { Banner } from './Banner.js';
2
+ export type { BannerProps } from './Banner.types.js';
@@ -0,0 +1,87 @@
1
+ import { View, Pressable } from 'react-native';
2
+ import { Surface, Text, Icon } from 'react-native-paper';
3
+ import { useTokens } from '../../provider/useTokens.js';
4
+ import type { NavItem } from '../common.js';
5
+ import type { BottomNavProps } from './BottomNav.types.js';
6
+
7
+ function NavBadge({ value, fg, bg }: { value: string | number; fg: string; bg: string }) {
8
+ return (
9
+ <View
10
+ style={{
11
+ position: 'absolute',
12
+ top: -4,
13
+ right: -10,
14
+ minWidth: 16,
15
+ height: 16,
16
+ paddingHorizontal: 4,
17
+ borderRadius: 8,
18
+ backgroundColor: bg,
19
+ alignItems: 'center',
20
+ justifyContent: 'center',
21
+ }}
22
+ >
23
+ <Text style={{ color: fg, fontSize: 10, fontWeight: '600' as '600' }}>{String(value)}</Text>
24
+ </View>
25
+ );
26
+ }
27
+
28
+ export function BottomNav({ items, activeKey, onSelect, testID }: BottomNavProps) {
29
+ const tokens = useTokens();
30
+ return (
31
+ <Surface
32
+ elevation={2}
33
+ {...(testID ? { testID } : {})}
34
+ style={{
35
+ flexDirection: 'row',
36
+ backgroundColor: tokens.color.glassBg,
37
+ borderTopColor: tokens.color.glassBorder,
38
+ borderTopWidth: 1,
39
+ paddingVertical: tokens.spacing.xs,
40
+ }}
41
+ >
42
+ {items.map((item: NavItem) => {
43
+ const active = item.key === activeKey;
44
+ const fg = active ? tokens.color.primarySoftFg : tokens.color.mutedFg;
45
+ return (
46
+ <Pressable
47
+ key={item.key}
48
+ onPress={() => onSelect(item.key)}
49
+ accessibilityRole="button"
50
+ accessibilityState={{ selected: active }}
51
+ testID={`${testID ?? 'bottomnav'}-${item.key}`}
52
+ style={{
53
+ flex: 1,
54
+ alignItems: 'center',
55
+ justifyContent: 'center',
56
+ paddingVertical: tokens.spacing.sm,
57
+ borderRadius: tokens.radii.md,
58
+ backgroundColor: active ? tokens.color.primarySoftBg : 'transparent',
59
+ }}
60
+ >
61
+ <View>
62
+ <Icon source={item.icon} size={tokens.typography.sizes.lg} color={fg} />
63
+ {item.badge != null ? (
64
+ <NavBadge
65
+ value={item.badge}
66
+ bg={tokens.color.danger}
67
+ fg={tokens.color.primaryFg}
68
+ />
69
+ ) : null}
70
+ </View>
71
+ <Text
72
+ style={{
73
+ color: fg,
74
+ fontSize: tokens.typography.sizes.xs,
75
+ fontWeight: String(
76
+ active ? tokens.typography.weights.semibold : tokens.typography.weights.medium,
77
+ ) as '600',
78
+ }}
79
+ >
80
+ {item.label}
81
+ </Text>
82
+ </Pressable>
83
+ );
84
+ })}
85
+ </Surface>
86
+ );
87
+ }
@@ -0,0 +1,9 @@
1
+ import type { BottomNavProps } from './BottomNav.types.js';
2
+
3
+ /** Web stub: BottomNav is a mobile-only component. */
4
+ export function BottomNav(_props: BottomNavProps): null {
5
+ if (process.env.NODE_ENV !== 'production') {
6
+ console.warn('BottomNav is mobile-only');
7
+ }
8
+ return null;
9
+ }
@@ -0,0 +1,8 @@
1
+ import type { NavItem } from '../common.js';
2
+
3
+ export interface BottomNavProps {
4
+ items: NavItem[];
5
+ activeKey: string;
6
+ onSelect: (key: string) => void;
7
+ testID?: string;
8
+ }
@@ -0,0 +1,2 @@
1
+ export { BottomNav } from './BottomNav.js';
2
+ export type { BottomNavProps } from './BottomNav.types.js';
@@ -0,0 +1,27 @@
1
+ import { Button as PaperButton } from 'react-native-paper';
2
+ import { useTokens } from '../../provider/useTokens.js';
3
+ import type { ButtonProps, Variant } from './Button.types.js';
4
+
5
+ const paperMode = (v: Variant): 'contained' | 'outlined' | 'text' | 'contained-tonal' =>
6
+ v === 'solid' ? 'contained' : v === 'soft' ? 'contained-tonal' : v === 'outline' ? 'outlined' : 'text';
7
+
8
+ export function Button({
9
+ children, onPress, variant = 'solid', size = 'md',
10
+ disabled, loading, icon, testID,
11
+ }: ButtonProps) {
12
+ const tokens = useTokens();
13
+ return (
14
+ <PaperButton
15
+ mode={paperMode(variant)}
16
+ onPress={onPress ?? (() => {})}
17
+ disabled={Boolean(disabled || loading)}
18
+ loading={Boolean(loading)}
19
+ compact={size === 'sm'}
20
+ style={{ borderRadius: tokens.radii.md }}
21
+ {...(icon !== undefined ? { icon } : {})}
22
+ {...(testID !== undefined ? { testID } : {})}
23
+ >
24
+ {children}
25
+ </PaperButton>
26
+ );
27
+ }
@@ -0,0 +1,37 @@
1
+ import { Button as MuiButton, CircularProgress } from '@mui/material';
2
+ import { useTokens } from '../../provider/useTokens.js';
3
+ import type { ButtonProps, Variant, Tone } from './Button.types.js';
4
+
5
+ const muiVariant = (v: Variant): 'contained' | 'outlined' | 'text' =>
6
+ v === 'solid' ? 'contained' : v === 'outline' ? 'outlined' : 'text';
7
+
8
+ const muiColor = (t: Tone): 'primary' | 'success' | 'error' | 'warning' | 'info' | 'inherit' =>
9
+ t === 'danger' ? 'error' : t === 'neutral' ? 'inherit' : t;
10
+
11
+ export function Button({
12
+ children, onPress, variant = 'solid', tone = 'primary', size = 'md',
13
+ disabled, loading, fullWidth, testID,
14
+ }: ButtonProps) {
15
+ const tokens = useTokens();
16
+ const soft = variant === 'soft';
17
+ return (
18
+ <MuiButton
19
+ variant={muiVariant(variant)}
20
+ color={muiColor(tone)}
21
+ size={size === 'md' ? 'medium' : size === 'sm' ? 'small' : 'large'}
22
+ disabled={Boolean(disabled || loading)}
23
+ fullWidth={Boolean(fullWidth)}
24
+ onClick={onPress}
25
+ data-testid={testID}
26
+ sx={{
27
+ borderRadius: `${tokens.radii.md}px`,
28
+ textTransform: 'none',
29
+ fontWeight: tokens.typography.weights.semibold,
30
+ ...(soft ? { backgroundColor: tokens.color.primarySoftBg, color: tokens.color.primarySoftFg, '&:hover': { backgroundColor: tokens.color.primarySoftBg } } : {}),
31
+ }}
32
+ startIcon={loading ? <CircularProgress size={16} color="inherit" /> : undefined}
33
+ >
34
+ {children}
35
+ </MuiButton>
36
+ );
37
+ }
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Variant, Tone, Size } from '../common.js';
3
+
4
+ export type { Variant, Tone, Size };
5
+
6
+ export interface ButtonProps {
7
+ children: ReactNode;
8
+ onPress?: () => void;
9
+ variant?: Variant;
10
+ tone?: Tone;
11
+ size?: Size;
12
+ disabled?: boolean;
13
+ loading?: boolean;
14
+ icon?: string;
15
+ fullWidth?: boolean;
16
+ testID?: string;
17
+ }
@@ -0,0 +1,2 @@
1
+ export { Button } from './Button.js';
2
+ export type { ButtonProps } from './Button.types.js';
@@ -0,0 +1,24 @@
1
+ import { View, Pressable } from 'react-native';
2
+ import { Surface } from 'react-native-paper';
3
+ import { useTokens } from '../../provider/useTokens.js';
4
+ import type { CardProps } from './Card.types.js';
5
+
6
+ export function Card({ children, onPress, padded = true, testID }: CardProps) {
7
+ const tokens = useTokens();
8
+ const surfaceStyle = {
9
+ backgroundColor: tokens.color.glassBg,
10
+ borderColor: tokens.color.glassBorder,
11
+ borderWidth: 1,
12
+ borderRadius: tokens.radii.lg,
13
+ padding: padded ? tokens.spacing.lg : 0,
14
+ };
15
+ return (
16
+ <Surface elevation={1} style={surfaceStyle} {...(testID ? { testID } : {})}>
17
+ {onPress ? (
18
+ <Pressable onPress={onPress}>{children}</Pressable>
19
+ ) : (
20
+ <View>{children}</View>
21
+ )}
22
+ </Surface>
23
+ );
24
+ }
@@ -0,0 +1,30 @@
1
+ import { Card as MuiCard, CardActionArea } from '@mui/material';
2
+ import { useTokens } from '../../provider/useTokens.js';
3
+ import type { CardProps } from './Card.types.js';
4
+
5
+ export function Card({ children, onPress, padded = true, testID }: CardProps) {
6
+ const tokens = useTokens();
7
+ const pad = padded ? `${tokens.spacing.lg}px` : 0;
8
+ const body = onPress ? (
9
+ <CardActionArea onClick={onPress} sx={{ p: pad }}>
10
+ {children}
11
+ </CardActionArea>
12
+ ) : (
13
+ children
14
+ );
15
+ return (
16
+ <MuiCard
17
+ elevation={0}
18
+ data-testid={testID}
19
+ sx={{
20
+ backgroundColor: tokens.color.glassBg,
21
+ border: `1px solid ${tokens.color.glassBorder}`,
22
+ borderRadius: `${tokens.radii.lg}px`,
23
+ boxShadow: tokens.shadows.sm,
24
+ ...(onPress ? {} : { p: pad }),
25
+ }}
26
+ >
27
+ {body}
28
+ </MuiCard>
29
+ );
30
+ }
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface CardProps {
4
+ children: ReactNode;
5
+ onPress?: () => void;
6
+ padded?: boolean;
7
+ testID?: string;
8
+ }
@@ -0,0 +1,2 @@
1
+ export { Card } from './Card.js';
2
+ export type { CardProps } from './Card.types.js';
@@ -0,0 +1,58 @@
1
+ import { Checkbox as PaperCheckbox } from 'react-native-paper';
2
+ import { useTokens } from '../../provider/useTokens.js';
3
+ import type { ResolvedTokens } from '../../provider/TokenContext.js';
4
+ import type { Tone } from '../common.js';
5
+ import type { CheckboxProps } from './Checkbox.types.js';
6
+
7
+ const toneColor = (c: ResolvedTokens['color'], tone: Tone): string => {
8
+ switch (tone) {
9
+ case 'success':
10
+ return c.success;
11
+ case 'danger':
12
+ return c.danger;
13
+ case 'warning':
14
+ return c.warning;
15
+ case 'info':
16
+ return c.info;
17
+ case 'neutral':
18
+ return c.secondary;
19
+ case 'primary':
20
+ default:
21
+ return c.primary;
22
+ }
23
+ };
24
+
25
+ export function Checkbox({
26
+ checked,
27
+ onChange,
28
+ label,
29
+ disabled,
30
+ tone = 'primary',
31
+ testID,
32
+ }: CheckboxProps) {
33
+ const tokens = useTokens();
34
+ const color = toneColor(tokens.color, tone);
35
+ const status = checked ? 'checked' : 'unchecked';
36
+ if (label === undefined) {
37
+ return (
38
+ <PaperCheckbox
39
+ status={status}
40
+ onPress={() => onChange(!checked)}
41
+ disabled={disabled ?? false}
42
+ color={color}
43
+ {...(testID === undefined ? {} : { testID })}
44
+ />
45
+ );
46
+ }
47
+ return (
48
+ <PaperCheckbox.Item
49
+ label={label}
50
+ status={status}
51
+ onPress={() => onChange(!checked)}
52
+ disabled={disabled ?? false}
53
+ color={color}
54
+ labelStyle={{ color: tokens.color.foreground }}
55
+ {...(testID === undefined ? {} : { testID })}
56
+ />
57
+ );
58
+ }
@@ -0,0 +1,35 @@
1
+ import { Checkbox as MuiCheckbox, FormControlLabel } from '@mui/material';
2
+ import type { Tone } from '../common.js';
3
+ import type { CheckboxProps } from './Checkbox.types.js';
4
+
5
+ const muiColor = (
6
+ t: Tone,
7
+ ): 'primary' | 'success' | 'error' | 'warning' | 'info' | 'default' =>
8
+ t === 'danger' ? 'error' : t === 'neutral' ? 'default' : t;
9
+
10
+ export function Checkbox({
11
+ checked,
12
+ onChange,
13
+ label,
14
+ disabled,
15
+ tone = 'primary',
16
+ testID,
17
+ }: CheckboxProps) {
18
+ const control = (
19
+ <MuiCheckbox
20
+ checked={checked}
21
+ onChange={(e) => onChange(e.target.checked)}
22
+ disabled={disabled}
23
+ color={muiColor(tone)}
24
+ data-testid={testID}
25
+ />
26
+ );
27
+ if (label === undefined) return control;
28
+ return (
29
+ <FormControlLabel
30
+ control={control}
31
+ label={label}
32
+ {...(disabled === undefined ? {} : { disabled })}
33
+ />
34
+ );
35
+ }
@@ -0,0 +1,15 @@
1
+ import type { Tone } from '../common.js';
2
+
3
+ export interface CheckboxProps {
4
+ /** Whether the checkbox is checked. */
5
+ checked: boolean;
6
+ /** Called with the new checked value when toggled. */
7
+ onChange: (checked: boolean) => void;
8
+ /** Optional label rendered beside the checkbox. */
9
+ label?: string;
10
+ /** Disables interaction. */
11
+ disabled?: boolean;
12
+ /** Color tone applied to the checked state. */
13
+ tone?: Tone;
14
+ testID?: string;
15
+ }
@@ -0,0 +1,2 @@
1
+ export { Checkbox } from './Checkbox.js';
2
+ export type { CheckboxProps } from './Checkbox.types.js';