@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.
- package/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/components/AppBar/AppBar.d.ts +6 -0
- package/dist/components/AppBar/AppBar.js +55 -0
- package/dist/components/AppBar/AppBar.types.d.ts +11 -0
- package/dist/components/AppBar/AppBar.types.js +0 -0
- package/dist/components/AppBar/index.d.ts +3 -0
- package/dist/components/AppBar/index.js +4 -0
- package/dist/components/Avatar/Avatar.d.ts +7 -0
- package/dist/components/Avatar/Avatar.js +30 -0
- package/dist/components/Avatar/Avatar.types.d.ts +12 -0
- package/dist/components/Avatar/Avatar.types.js +0 -0
- package/dist/components/Avatar/index.d.ts +4 -0
- package/dist/components/Avatar/index.js +4 -0
- package/dist/components/Badge/Badge.d.ts +7 -0
- package/dist/components/Badge/Badge.js +75 -0
- package/dist/components/Badge/Badge.types.d.ts +12 -0
- package/dist/components/Badge/Badge.types.js +0 -0
- package/dist/components/Badge/index.d.ts +4 -0
- package/dist/components/Badge/index.js +4 -0
- package/dist/components/Banner/Banner.d.ts +7 -0
- package/dist/components/Banner/Banner.js +68 -0
- package/dist/components/Banner/Banner.types.d.ts +13 -0
- package/dist/components/Banner/Banner.types.js +0 -0
- package/dist/components/Banner/index.d.ts +4 -0
- package/dist/components/Banner/index.js +4 -0
- package/dist/components/BottomNav/BottomNav.d.ts +7 -0
- package/dist/components/BottomNav/BottomNav.js +9 -0
- package/dist/components/BottomNav/BottomNav.types.d.ts +10 -0
- package/dist/components/BottomNav/BottomNav.types.js +0 -0
- package/dist/components/BottomNav/index.d.ts +3 -0
- package/dist/components/BottomNav/index.js +4 -0
- package/dist/components/Button/Button.d.ts +7 -0
- package/dist/components/Button/Button.js +42 -0
- package/dist/components/Button/Button.types.d.ts +17 -0
- package/dist/components/Button/Button.types.js +0 -0
- package/dist/components/Button/index.d.ts +4 -0
- package/dist/components/Button/index.js +4 -0
- package/dist/components/Card/Card.d.ts +6 -0
- package/dist/components/Card/Card.js +26 -0
- package/dist/components/Card/Card.types.d.ts +10 -0
- package/dist/components/Card/Card.types.js +0 -0
- package/dist/components/Card/index.d.ts +3 -0
- package/dist/components/Card/index.js +4 -0
- package/dist/components/Checkbox/Checkbox.d.ts +7 -0
- package/dist/components/Checkbox/Checkbox.js +34 -0
- package/dist/components/Checkbox/Checkbox.types.d.ts +17 -0
- package/dist/components/Checkbox/Checkbox.types.js +0 -0
- package/dist/components/Checkbox/index.d.ts +4 -0
- package/dist/components/Checkbox/index.js +4 -0
- package/dist/components/Dialog/Dialog.d.ts +6 -0
- package/dist/components/Dialog/Dialog.js +51 -0
- package/dist/components/Dialog/Dialog.types.d.ts +12 -0
- package/dist/components/Dialog/Dialog.types.js +0 -0
- package/dist/components/Dialog/index.d.ts +3 -0
- package/dist/components/Dialog/index.js +4 -0
- package/dist/components/Divider/Divider.d.ts +6 -0
- package/dist/components/Divider/Divider.js +19 -0
- package/dist/components/Divider/Divider.types.d.ts +7 -0
- package/dist/components/Divider/Divider.types.js +0 -0
- package/dist/components/Divider/index.d.ts +3 -0
- package/dist/components/Divider/index.js +4 -0
- package/dist/components/Drawer/Drawer.d.ts +8 -0
- package/dist/components/Drawer/Drawer.js +9 -0
- package/dist/components/Drawer/Drawer.types.d.ts +13 -0
- package/dist/components/Drawer/Drawer.types.js +0 -0
- package/dist/components/Drawer/index.d.ts +4 -0
- package/dist/components/Drawer/index.js +4 -0
- package/dist/components/Fab/Fab.d.ts +7 -0
- package/dist/components/Fab/Fab.js +33 -0
- package/dist/components/Fab/Fab.types.d.ts +11 -0
- package/dist/components/Fab/Fab.types.js +0 -0
- package/dist/components/Fab/index.d.ts +4 -0
- package/dist/components/Fab/index.js +4 -0
- package/dist/components/GlassAppBar/GlassAppBar.d.ts +6 -0
- package/dist/components/GlassAppBar/GlassAppBar.js +57 -0
- package/dist/components/GlassAppBar/GlassAppBar.types.d.ts +12 -0
- package/dist/components/GlassAppBar/GlassAppBar.types.js +0 -0
- package/dist/components/GlassAppBar/index.d.ts +3 -0
- package/dist/components/GlassAppBar/index.js +4 -0
- package/dist/components/Icon/Icon.d.ts +8 -0
- package/dist/components/Icon/Icon.js +46 -0
- package/dist/components/Icon/Icon.types.d.ts +14 -0
- package/dist/components/Icon/Icon.types.js +0 -0
- package/dist/components/Icon/index.d.ts +5 -0
- package/dist/components/Icon/index.js +4 -0
- package/dist/components/Input/Input.d.ts +6 -0
- package/dist/components/Input/Input.js +43 -0
- package/dist/components/Input/Input.types.d.ts +13 -0
- package/dist/components/Input/Input.types.js +0 -0
- package/dist/components/Input/index.d.ts +3 -0
- package/dist/components/Input/index.js +4 -0
- package/dist/components/Select/Select.d.ts +6 -0
- package/dist/components/Select/Select.js +50 -0
- package/dist/components/Select/Select.types.d.ts +15 -0
- package/dist/components/Select/Select.types.js +0 -0
- package/dist/components/Select/index.d.ts +3 -0
- package/dist/components/Select/index.js +4 -0
- package/dist/components/Spinner/Spinner.d.ts +7 -0
- package/dist/components/Spinner/Spinner.js +35 -0
- package/dist/components/Spinner/Spinner.types.d.ts +9 -0
- package/dist/components/Spinner/Spinner.types.js +0 -0
- package/dist/components/Spinner/index.d.ts +4 -0
- package/dist/components/Spinner/index.js +4 -0
- package/dist/components/Surface/Surface.d.ts +6 -0
- package/dist/components/Surface/Surface.js +29 -0
- package/dist/components/Surface/Surface.types.d.ts +11 -0
- package/dist/components/Surface/Surface.types.js +0 -0
- package/dist/components/Surface/index.d.ts +3 -0
- package/dist/components/Surface/index.js +4 -0
- package/dist/components/Switch/Switch.d.ts +7 -0
- package/dist/components/Switch/Switch.js +34 -0
- package/dist/components/Switch/Switch.types.d.ts +17 -0
- package/dist/components/Switch/Switch.types.js +0 -0
- package/dist/components/Switch/index.d.ts +4 -0
- package/dist/components/Switch/index.js +4 -0
- package/dist/components/Text/Text.d.ts +7 -0
- package/dist/components/Text/Text.js +34 -0
- package/dist/components/Text/Text.types.d.ts +13 -0
- package/dist/components/Text/Text.types.js +0 -0
- package/dist/components/Text/index.d.ts +4 -0
- package/dist/components/Text/index.js +4 -0
- package/dist/components/Tooltip/Tooltip.d.ts +6 -0
- package/dist/components/Tooltip/Tooltip.js +8 -0
- package/dist/components/Tooltip/Tooltip.types.d.ts +11 -0
- package/dist/components/Tooltip/Tooltip.types.js +0 -0
- package/dist/components/Tooltip/index.d.ts +3 -0
- package/dist/components/Tooltip/index.js +4 -0
- package/dist/components/common.d.ts +11 -0
- package/dist/components/common.js +0 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +54 -0
- package/dist/notifications/NotificationContext.d.ts +33 -0
- package/dist/notifications/NotificationContext.js +34 -0
- package/dist/notifications/NotificationHost.d.ts +5 -0
- package/dist/notifications/NotificationHost.js +22 -0
- package/dist/notifications/useNotify.d.ts +13 -0
- package/dist/notifications/useNotify.js +19 -0
- package/dist/provider/SublimeProvider.d.ts +12 -0
- package/dist/provider/SublimeProvider.js +23 -0
- package/dist/provider/TokenContext.d.ts +14 -0
- package/dist/provider/TokenContext.js +5 -0
- package/dist/provider/resolveTokens.d.ts +7 -0
- package/dist/provider/resolveTokens.js +13 -0
- package/dist/provider/useTokens.d.ts +7 -0
- package/dist/provider/useTokens.js +12 -0
- package/dist/tokens/generateThemes.d.ts +10 -0
- package/dist/tokens/generateThemes.js +44 -0
- package/dist/tokens/tokens.d.ts +74 -0
- package/dist/tokens/tokens.js +78 -0
- package/package.json +53 -0
- package/src/components/AppBar/AppBar.native.tsx +28 -0
- package/src/components/AppBar/AppBar.tsx +51 -0
- package/src/components/AppBar/AppBar.types.ts +9 -0
- package/src/components/AppBar/index.ts +2 -0
- package/src/components/Avatar/Avatar.native.tsx +38 -0
- package/src/components/Avatar/Avatar.tsx +38 -0
- package/src/components/Avatar/Avatar.types.ts +10 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Badge/Badge.native.tsx +97 -0
- package/src/components/Badge/Badge.tsx +89 -0
- package/src/components/Badge/Badge.types.ts +11 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Banner/Banner.native.tsx +89 -0
- package/src/components/Banner/Banner.tsx +78 -0
- package/src/components/Banner/Banner.types.ts +11 -0
- package/src/components/Banner/index.ts +2 -0
- package/src/components/BottomNav/BottomNav.native.tsx +87 -0
- package/src/components/BottomNav/BottomNav.tsx +9 -0
- package/src/components/BottomNav/BottomNav.types.ts +8 -0
- package/src/components/BottomNav/index.ts +2 -0
- package/src/components/Button/Button.native.tsx +27 -0
- package/src/components/Button/Button.tsx +37 -0
- package/src/components/Button/Button.types.ts +17 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Card/Card.native.tsx +24 -0
- package/src/components/Card/Card.tsx +30 -0
- package/src/components/Card/Card.types.ts +8 -0
- package/src/components/Card/index.ts +2 -0
- package/src/components/Checkbox/Checkbox.native.tsx +58 -0
- package/src/components/Checkbox/Checkbox.tsx +35 -0
- package/src/components/Checkbox/Checkbox.types.ts +15 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/Dialog/Dialog.native.tsx +28 -0
- package/src/components/Dialog/Dialog.tsx +49 -0
- package/src/components/Dialog/Dialog.types.ts +10 -0
- package/src/components/Dialog/index.ts +2 -0
- package/src/components/Divider/Divider.native.tsx +30 -0
- package/src/components/Divider/Divider.tsx +16 -0
- package/src/components/Divider/Divider.types.ts +5 -0
- package/src/components/Divider/index.ts +2 -0
- package/src/components/Drawer/Drawer.native.tsx +113 -0
- package/src/components/Drawer/Drawer.tsx +9 -0
- package/src/components/Drawer/Drawer.types.ts +11 -0
- package/src/components/Drawer/index.ts +2 -0
- package/src/components/Fab/Fab.native.tsx +41 -0
- package/src/components/Fab/Fab.tsx +36 -0
- package/src/components/Fab/Fab.types.ts +9 -0
- package/src/components/Fab/index.ts +2 -0
- package/src/components/GlassAppBar/GlassAppBar.native.tsx +29 -0
- package/src/components/GlassAppBar/GlassAppBar.tsx +53 -0
- package/src/components/GlassAppBar/GlassAppBar.types.ts +10 -0
- package/src/components/GlassAppBar/index.ts +2 -0
- package/src/components/Icon/Icon.native.tsx +39 -0
- package/src/components/Icon/Icon.tsx +57 -0
- package/src/components/Icon/Icon.types.ts +13 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/Input/Input.native.tsx +34 -0
- package/src/components/Input/Input.tsx +33 -0
- package/src/components/Input/Input.types.ts +11 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Select/Select.native.tsx +63 -0
- package/src/components/Select/Select.tsx +48 -0
- package/src/components/Select/Select.types.ts +14 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/Spinner/Spinner.native.tsx +38 -0
- package/src/components/Spinner/Spinner.tsx +37 -0
- package/src/components/Spinner/Spinner.types.ts +7 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/Surface/Surface.native.tsx +32 -0
- package/src/components/Surface/Surface.tsx +31 -0
- package/src/components/Surface/Surface.types.ts +10 -0
- package/src/components/Surface/index.ts +2 -0
- package/src/components/Switch/Switch.native.tsx +58 -0
- package/src/components/Switch/Switch.tsx +35 -0
- package/src/components/Switch/Switch.types.ts +15 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Text/Text.native.tsx +31 -0
- package/src/components/Text/Text.tsx +34 -0
- package/src/components/Text/Text.types.ts +12 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Tooltip/Tooltip.native.tsx +6 -0
- package/src/components/Tooltip/Tooltip.tsx +10 -0
- package/src/components/Tooltip/Tooltip.types.ts +9 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/common.ts +10 -0
- package/src/index.ts +33 -0
- package/src/notifications/NotificationContext.tsx +57 -0
- package/src/notifications/NotificationHost.native.tsx +20 -0
- package/src/notifications/NotificationHost.tsx +27 -0
- package/src/notifications/useNotify.ts +18 -0
- package/src/provider/SublimeProvider.native.tsx +29 -0
- package/src/provider/SublimeProvider.tsx +30 -0
- package/src/provider/TokenContext.ts +13 -0
- package/src/provider/resolveTokens.ts +16 -0
- package/src/provider/useTokens.ts +10 -0
- package/src/test-utils/renderWeb.tsx +8 -0
- package/src/tokens/generateThemes.ts +49 -0
- 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,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,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,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,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,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
|
+
}
|