@astacinco/rn-primitives 0.1.0 → 0.3.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/README.md +195 -0
- package/__tests__/Button.test.tsx +3 -1
- package/__tests__/Card.test.tsx +18 -1
- package/__tests__/Container.test.tsx +18 -1
- package/__tests__/Input.test.tsx +3 -1
- package/__tests__/Stack.test.tsx +26 -4
- package/__tests__/Tabs.test.tsx +196 -0
- package/__tests__/Tag.test.tsx +125 -0
- package/__tests__/Text.test.tsx +3 -1
- package/__tests__/Timer.test.tsx +210 -0
- package/markdown/README.md +66 -0
- package/markdown/md.d.ts +16 -0
- package/markdown/metro-md-transformer.js +41 -0
- package/package.json +11 -7
- package/src/Accordion/Accordion.tsx +69 -0
- package/src/Accordion/AccordionItem.tsx +130 -0
- package/src/Accordion/index.ts +3 -0
- package/src/Accordion/types.ts +40 -0
- package/src/AppFooter/AppFooter.tsx +113 -0
- package/src/AppFooter/index.ts +2 -0
- package/src/AppFooter/types.ts +39 -0
- package/src/AppHeader/AppHeader.tsx +191 -0
- package/src/AppHeader/index.ts +2 -0
- package/src/AppHeader/types.ts +93 -0
- package/src/Avatar/Avatar.tsx +111 -0
- package/src/Avatar/index.ts +2 -0
- package/src/Avatar/types.ts +63 -0
- package/src/Badge/Badge.tsx +150 -0
- package/src/Badge/index.ts +2 -0
- package/src/Badge/types.ts +93 -0
- package/src/Button/Button.tsx +34 -20
- package/src/Button/types.ts +1 -1
- package/src/FloatingTierBadge/FloatingTierBadge.tsx +100 -0
- package/src/FloatingTierBadge/index.ts +2 -0
- package/src/FloatingTierBadge/types.ts +29 -0
- package/src/Input/Input.tsx +8 -23
- package/src/MarkdownViewer/MarkdownViewer.tsx +185 -0
- package/src/MarkdownViewer/index.ts +2 -0
- package/src/MarkdownViewer/types.ts +18 -0
- package/src/Modal/Modal.tsx +136 -0
- package/src/Modal/index.ts +2 -0
- package/src/Modal/types.ts +68 -0
- package/src/ProBadge/ProBadge.tsx +59 -0
- package/src/ProBadge/index.ts +2 -0
- package/src/ProBadge/types.ts +13 -0
- package/src/ProLockOverlay/ProLockOverlay.tsx +137 -0
- package/src/ProLockOverlay/index.ts +2 -0
- package/src/ProLockOverlay/types.ts +28 -0
- package/src/Switch/Switch.tsx +120 -0
- package/src/Switch/index.ts +2 -0
- package/src/Switch/types.ts +58 -0
- package/src/TabView/TabPanel.tsx +18 -0
- package/src/TabView/TabView.tsx +81 -0
- package/src/TabView/index.ts +3 -0
- package/src/TabView/types.ts +39 -0
- package/src/Tabs/Tabs.tsx +137 -0
- package/src/Tabs/index.ts +2 -0
- package/src/Tabs/types.ts +66 -0
- package/src/Tag/Tag.tsx +100 -0
- package/src/Tag/index.ts +2 -0
- package/src/Tag/types.ts +42 -0
- package/src/Timer/Timer.tsx +170 -0
- package/src/Timer/index.ts +2 -0
- package/src/Timer/types.ts +69 -0
- package/src/index.ts +60 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import { useTheme } from '@astacinco/rn-theming';
|
|
4
|
+
import { Text } from '../Text';
|
|
5
|
+
import type { BadgeProps, BadgeVariant, BadgePosition, BadgeSize } from './types';
|
|
6
|
+
|
|
7
|
+
const sizeConfig: Record<BadgeSize, { minWidth: number; height: number; fontSize: number; dotSize: number; padding: number }> = {
|
|
8
|
+
sm: { minWidth: 16, height: 16, fontSize: 10, dotSize: 8, padding: 4 },
|
|
9
|
+
md: { minWidth: 20, height: 20, fontSize: 12, dotSize: 10, padding: 6 },
|
|
10
|
+
lg: { minWidth: 24, height: 24, fontSize: 14, dotSize: 12, padding: 8 },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const getPositionStyle = (
|
|
14
|
+
position: BadgePosition,
|
|
15
|
+
offset: [number, number]
|
|
16
|
+
): { top?: number; bottom?: number; left?: number; right?: number } => {
|
|
17
|
+
const [offsetX, offsetY] = offset;
|
|
18
|
+
|
|
19
|
+
switch (position) {
|
|
20
|
+
case 'top-right':
|
|
21
|
+
return { top: offsetY, right: offsetX };
|
|
22
|
+
case 'top-left':
|
|
23
|
+
return { top: offsetY, left: offsetX };
|
|
24
|
+
case 'bottom-right':
|
|
25
|
+
return { bottom: offsetY, right: offsetX };
|
|
26
|
+
case 'bottom-left':
|
|
27
|
+
return { bottom: offsetY, left: offsetX };
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function Badge({
|
|
32
|
+
children,
|
|
33
|
+
count,
|
|
34
|
+
maxCount = 99,
|
|
35
|
+
dot = false,
|
|
36
|
+
variant = 'error',
|
|
37
|
+
position = 'top-right',
|
|
38
|
+
size = 'md',
|
|
39
|
+
showZero = false,
|
|
40
|
+
label,
|
|
41
|
+
offset = [0, 0],
|
|
42
|
+
hidden = false,
|
|
43
|
+
standalone = false,
|
|
44
|
+
style,
|
|
45
|
+
badgeStyle,
|
|
46
|
+
testID,
|
|
47
|
+
}: BadgeProps) {
|
|
48
|
+
const { colors } = useTheme();
|
|
49
|
+
const config = sizeConfig[size];
|
|
50
|
+
|
|
51
|
+
const getVariantColor = (v: BadgeVariant): string => {
|
|
52
|
+
switch (v) {
|
|
53
|
+
case 'primary':
|
|
54
|
+
return colors.primary;
|
|
55
|
+
case 'error':
|
|
56
|
+
return colors.error;
|
|
57
|
+
case 'success':
|
|
58
|
+
return colors.success;
|
|
59
|
+
case 'warning':
|
|
60
|
+
return colors.warning;
|
|
61
|
+
case 'default':
|
|
62
|
+
default:
|
|
63
|
+
return colors.textSecondary;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Determine if badge should be visible
|
|
68
|
+
const shouldShow = !hidden && (
|
|
69
|
+
dot ||
|
|
70
|
+
label ||
|
|
71
|
+
(count !== undefined && (count > 0 || showZero))
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Format count text
|
|
75
|
+
const getCountText = (): string => {
|
|
76
|
+
if (label) return label;
|
|
77
|
+
if (count === undefined) return '';
|
|
78
|
+
if (count > maxCount) return `${maxCount}+`;
|
|
79
|
+
return count.toString();
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const badgeColor = getVariantColor(variant);
|
|
83
|
+
const countText = getCountText();
|
|
84
|
+
|
|
85
|
+
const badgeElement = shouldShow ? (
|
|
86
|
+
<View
|
|
87
|
+
testID={testID}
|
|
88
|
+
style={[
|
|
89
|
+
styles.badge,
|
|
90
|
+
dot ? {
|
|
91
|
+
width: config.dotSize,
|
|
92
|
+
height: config.dotSize,
|
|
93
|
+
borderRadius: config.dotSize / 2,
|
|
94
|
+
backgroundColor: badgeColor,
|
|
95
|
+
} : {
|
|
96
|
+
minWidth: config.minWidth,
|
|
97
|
+
height: config.height,
|
|
98
|
+
borderRadius: config.height / 2,
|
|
99
|
+
backgroundColor: badgeColor,
|
|
100
|
+
paddingHorizontal: config.padding,
|
|
101
|
+
},
|
|
102
|
+
!standalone && getPositionStyle(position, offset),
|
|
103
|
+
!standalone && styles.positioned,
|
|
104
|
+
badgeStyle,
|
|
105
|
+
]}
|
|
106
|
+
>
|
|
107
|
+
{!dot && (
|
|
108
|
+
<Text
|
|
109
|
+
variant="caption"
|
|
110
|
+
style={{
|
|
111
|
+
color: '#FFFFFF',
|
|
112
|
+
fontSize: config.fontSize,
|
|
113
|
+
fontWeight: '600',
|
|
114
|
+
textAlign: 'center',
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{countText}
|
|
118
|
+
</Text>
|
|
119
|
+
)}
|
|
120
|
+
</View>
|
|
121
|
+
) : null;
|
|
122
|
+
|
|
123
|
+
// Standalone badge (no wrapper)
|
|
124
|
+
if (standalone || !children) {
|
|
125
|
+
return badgeElement;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Wrapper badge
|
|
129
|
+
return (
|
|
130
|
+
<View style={[styles.container, style]}>
|
|
131
|
+
{children}
|
|
132
|
+
{badgeElement}
|
|
133
|
+
</View>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const styles = StyleSheet.create({
|
|
138
|
+
container: {
|
|
139
|
+
position: 'relative',
|
|
140
|
+
alignSelf: 'flex-start',
|
|
141
|
+
},
|
|
142
|
+
badge: {
|
|
143
|
+
alignItems: 'center',
|
|
144
|
+
justifyContent: 'center',
|
|
145
|
+
},
|
|
146
|
+
positioned: {
|
|
147
|
+
position: 'absolute',
|
|
148
|
+
zIndex: 1,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ViewStyle, StyleProp } from 'react-native';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
export type BadgeVariant = 'default' | 'primary' | 'error' | 'success' | 'warning';
|
|
5
|
+
export type BadgePosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
6
|
+
export type BadgeSize = 'sm' | 'md' | 'lg';
|
|
7
|
+
|
|
8
|
+
export interface BadgeProps {
|
|
9
|
+
/**
|
|
10
|
+
* Content to wrap with badge
|
|
11
|
+
*/
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Badge count (displays number)
|
|
16
|
+
* If > maxCount, shows "maxCount+"
|
|
17
|
+
*/
|
|
18
|
+
count?: number;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Maximum count to display
|
|
22
|
+
* @default 99
|
|
23
|
+
*/
|
|
24
|
+
maxCount?: number;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Show as dot instead of count
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
dot?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Badge variant (color scheme)
|
|
34
|
+
* @default 'error'
|
|
35
|
+
*/
|
|
36
|
+
variant?: BadgeVariant;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Badge position relative to children
|
|
40
|
+
* @default 'top-right'
|
|
41
|
+
*/
|
|
42
|
+
position?: BadgePosition;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Badge size
|
|
46
|
+
* @default 'md'
|
|
47
|
+
*/
|
|
48
|
+
size?: BadgeSize;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Show badge even when count is 0
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
showZero?: boolean;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Custom badge label (overrides count)
|
|
58
|
+
*/
|
|
59
|
+
label?: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Offset from corner [x, y]
|
|
63
|
+
* @default [0, 0]
|
|
64
|
+
*/
|
|
65
|
+
offset?: [number, number];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Hide the badge
|
|
69
|
+
* @default false
|
|
70
|
+
*/
|
|
71
|
+
hidden?: boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Standalone badge (no children)
|
|
75
|
+
* @default false
|
|
76
|
+
*/
|
|
77
|
+
standalone?: boolean;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Custom container style
|
|
81
|
+
*/
|
|
82
|
+
style?: StyleProp<ViewStyle>;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Custom badge style
|
|
86
|
+
*/
|
|
87
|
+
badgeStyle?: StyleProp<ViewStyle>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Test ID for testing
|
|
91
|
+
*/
|
|
92
|
+
testID?: string;
|
|
93
|
+
}
|
package/src/Button/Button.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Pressable,
|
|
2
|
+
import { Pressable, StyleSheet, ActivityIndicator, ViewStyle, StyleProp } from 'react-native';
|
|
3
3
|
import { useTheme } from '@astacinco/rn-theming';
|
|
4
|
+
import { Text } from '../Text';
|
|
4
5
|
import type { ButtonProps, ButtonVariant, ButtonSize } from './types';
|
|
5
6
|
|
|
6
7
|
const sizeStyles: Record<ButtonSize, { paddingVertical: number; paddingHorizontal: number; fontSize: number }> = {
|
|
@@ -23,7 +24,7 @@ export function Button({
|
|
|
23
24
|
const { colors } = useTheme();
|
|
24
25
|
const sizeStyle = sizeStyles[size];
|
|
25
26
|
|
|
26
|
-
const getVariantStyles = (v: ButtonVariant, pressed: boolean) => {
|
|
27
|
+
const getVariantStyles = (v: ButtonVariant, pressed: boolean, hovered: boolean) => {
|
|
27
28
|
const opacity = pressed ? 0.8 : 1;
|
|
28
29
|
|
|
29
30
|
switch (v) {
|
|
@@ -33,6 +34,8 @@ export function Button({
|
|
|
33
34
|
borderWidth: 0,
|
|
34
35
|
textColor: colors.textInverse,
|
|
35
36
|
opacity,
|
|
37
|
+
// Web hover: slight scale up
|
|
38
|
+
transform: hovered && !pressed ? [{ scale: 1.02 }] : undefined,
|
|
36
39
|
};
|
|
37
40
|
case 'secondary':
|
|
38
41
|
return {
|
|
@@ -40,21 +43,32 @@ export function Button({
|
|
|
40
43
|
borderWidth: 0,
|
|
41
44
|
textColor: colors.textInverse,
|
|
42
45
|
opacity,
|
|
46
|
+
transform: hovered && !pressed ? [{ scale: 1.02 }] : undefined,
|
|
43
47
|
};
|
|
44
48
|
case 'outline':
|
|
45
49
|
return {
|
|
46
|
-
backgroundColor: 'transparent',
|
|
50
|
+
backgroundColor: hovered ? colors.primary + '10' : 'transparent',
|
|
47
51
|
borderWidth: 1,
|
|
48
52
|
borderColor: colors.primary,
|
|
49
53
|
textColor: colors.primary,
|
|
50
54
|
opacity,
|
|
55
|
+
transform: undefined,
|
|
51
56
|
};
|
|
52
57
|
case 'ghost':
|
|
53
58
|
return {
|
|
54
|
-
backgroundColor: pressed ? colors.surface : 'transparent',
|
|
59
|
+
backgroundColor: pressed ? colors.surface : hovered ? colors.surface : 'transparent',
|
|
55
60
|
borderWidth: 0,
|
|
56
61
|
textColor: colors.primary,
|
|
57
62
|
opacity: 1,
|
|
63
|
+
transform: undefined,
|
|
64
|
+
};
|
|
65
|
+
case 'danger':
|
|
66
|
+
return {
|
|
67
|
+
backgroundColor: colors.error,
|
|
68
|
+
borderWidth: 0,
|
|
69
|
+
textColor: colors.textInverse,
|
|
70
|
+
opacity,
|
|
71
|
+
transform: hovered && !pressed ? [{ scale: 1.02 }] : undefined,
|
|
58
72
|
};
|
|
59
73
|
}
|
|
60
74
|
};
|
|
@@ -66,8 +80,9 @@ export function Button({
|
|
|
66
80
|
testID={testID}
|
|
67
81
|
onPress={onPress}
|
|
68
82
|
disabled={isDisabled}
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
// @ts-expect-error - cursor is web-only, ignored on native
|
|
84
|
+
style={({ pressed, hovered }): StyleProp<ViewStyle> => {
|
|
85
|
+
const variantStyle = getVariantStyles(variant, pressed, hovered ?? false);
|
|
71
86
|
return [
|
|
72
87
|
styles.base,
|
|
73
88
|
{
|
|
@@ -77,25 +92,28 @@ export function Button({
|
|
|
77
92
|
paddingVertical: sizeStyle.paddingVertical,
|
|
78
93
|
paddingHorizontal: sizeStyle.paddingHorizontal,
|
|
79
94
|
opacity: isDisabled ? 0.5 : variantStyle.opacity,
|
|
80
|
-
|
|
95
|
+
transform: variantStyle.transform,
|
|
96
|
+
// Web-only: cursor style (ignored on native)
|
|
97
|
+
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
|
98
|
+
} as ViewStyle,
|
|
81
99
|
style as ViewStyle,
|
|
82
100
|
];
|
|
83
101
|
}}
|
|
84
102
|
{...props}
|
|
85
103
|
>
|
|
86
|
-
{({ pressed }) => {
|
|
87
|
-
const variantStyle = getVariantStyles(variant, pressed);
|
|
104
|
+
{({ pressed, hovered }) => {
|
|
105
|
+
const variantStyle = getVariantStyles(variant, pressed, hovered ?? false);
|
|
88
106
|
return loading ? (
|
|
89
107
|
<ActivityIndicator color={variantStyle.textColor} size="small" />
|
|
90
108
|
) : (
|
|
91
109
|
<Text
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
variant="body"
|
|
111
|
+
style={{
|
|
112
|
+
color: variantStyle.textColor,
|
|
113
|
+
fontSize: sizeStyle.fontSize,
|
|
114
|
+
fontWeight: '600',
|
|
115
|
+
textAlign: 'center',
|
|
116
|
+
}}
|
|
99
117
|
>
|
|
100
118
|
{label}
|
|
101
119
|
</Text>
|
|
@@ -112,8 +130,4 @@ const styles = StyleSheet.create({
|
|
|
112
130
|
justifyContent: 'center',
|
|
113
131
|
flexDirection: 'row',
|
|
114
132
|
},
|
|
115
|
-
label: {
|
|
116
|
-
fontWeight: '600',
|
|
117
|
-
textAlign: 'center',
|
|
118
|
-
},
|
|
119
133
|
});
|
package/src/Button/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PressableProps } from 'react-native';
|
|
2
2
|
|
|
3
|
-
export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
|
|
3
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
|
4
4
|
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
5
5
|
|
|
6
6
|
export interface ButtonProps extends Omit<PressableProps, 'children'> {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FloatingTierBadge component
|
|
3
|
+
*
|
|
4
|
+
* A floating badge that appears in the corner of the screen
|
|
5
|
+
* to indicate pro tier status. Uses themed colors with a
|
|
6
|
+
* subtle glow effect inspired by the SparkLabs design.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { StyleSheet, Pressable, View } from 'react-native';
|
|
11
|
+
import { useTheme } from '@astacinco/rn-theming';
|
|
12
|
+
import { Text } from '../Text';
|
|
13
|
+
import type { FloatingTierBadgeProps, TierBadgePosition } from './types';
|
|
14
|
+
|
|
15
|
+
const positionStyles: Record<TierBadgePosition, object> = {
|
|
16
|
+
'top-right': { top: 16, right: 16 },
|
|
17
|
+
'top-left': { top: 16, left: 16 },
|
|
18
|
+
'bottom-right': { bottom: 16, right: 16 },
|
|
19
|
+
'bottom-left': { bottom: 16, left: 16 },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function FloatingTierBadge({
|
|
23
|
+
visible,
|
|
24
|
+
position = 'bottom-right',
|
|
25
|
+
label = 'PRO',
|
|
26
|
+
onPress,
|
|
27
|
+
}: FloatingTierBadgeProps) {
|
|
28
|
+
const { colors, shadows } = useTheme();
|
|
29
|
+
|
|
30
|
+
if (!visible) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const content = (
|
|
35
|
+
<View
|
|
36
|
+
style={[
|
|
37
|
+
styles.badge,
|
|
38
|
+
positionStyles[position],
|
|
39
|
+
{
|
|
40
|
+
backgroundColor: colors.proGlow,
|
|
41
|
+
borderColor: colors.pro,
|
|
42
|
+
// Apply shadow for glow effect
|
|
43
|
+
shadowColor: colors.pro,
|
|
44
|
+
shadowOffset: { width: 0, height: 0 },
|
|
45
|
+
shadowOpacity: 0.6,
|
|
46
|
+
shadowRadius: 8,
|
|
47
|
+
},
|
|
48
|
+
]}
|
|
49
|
+
>
|
|
50
|
+
<Text
|
|
51
|
+
style={[
|
|
52
|
+
styles.text,
|
|
53
|
+
{ color: colors.pro },
|
|
54
|
+
]}
|
|
55
|
+
>
|
|
56
|
+
{label}
|
|
57
|
+
</Text>
|
|
58
|
+
</View>
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (onPress) {
|
|
62
|
+
return (
|
|
63
|
+
<Pressable
|
|
64
|
+
onPress={onPress}
|
|
65
|
+
style={({ pressed }) => [
|
|
66
|
+
styles.container,
|
|
67
|
+
positionStyles[position],
|
|
68
|
+
{ opacity: pressed ? 0.8 : 1 },
|
|
69
|
+
]}
|
|
70
|
+
>
|
|
71
|
+
{content}
|
|
72
|
+
</Pressable>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<View style={[styles.container, positionStyles[position]]}>
|
|
78
|
+
{content}
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const styles = StyleSheet.create({
|
|
84
|
+
container: {
|
|
85
|
+
position: 'absolute',
|
|
86
|
+
zIndex: 1000,
|
|
87
|
+
},
|
|
88
|
+
badge: {
|
|
89
|
+
paddingHorizontal: 10,
|
|
90
|
+
paddingVertical: 4,
|
|
91
|
+
borderRadius: 4,
|
|
92
|
+
borderWidth: 1,
|
|
93
|
+
},
|
|
94
|
+
text: {
|
|
95
|
+
fontSize: 10,
|
|
96
|
+
fontWeight: '700',
|
|
97
|
+
letterSpacing: 1.5,
|
|
98
|
+
textTransform: 'uppercase',
|
|
99
|
+
},
|
|
100
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FloatingTierBadge types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type TierBadgePosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
6
|
+
|
|
7
|
+
export interface FloatingTierBadgeProps {
|
|
8
|
+
/**
|
|
9
|
+
* Whether to show the badge (typically only for pro users)
|
|
10
|
+
*/
|
|
11
|
+
visible: boolean;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Position of the floating badge
|
|
15
|
+
* @default 'bottom-right'
|
|
16
|
+
*/
|
|
17
|
+
position?: TierBadgePosition;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Label to display
|
|
21
|
+
* @default 'PRO'
|
|
22
|
+
*/
|
|
23
|
+
label?: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Optional callback when badge is pressed
|
|
27
|
+
*/
|
|
28
|
+
onPress?: () => void;
|
|
29
|
+
}
|
package/src/Input/Input.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { View, TextInput,
|
|
2
|
+
import { View, TextInput, StyleSheet, NativeSyntheticEvent, TextInputFocusEventData } from 'react-native';
|
|
3
3
|
import { useTheme } from '@astacinco/rn-theming';
|
|
4
|
+
import { Text } from '../Text';
|
|
4
5
|
import type { InputProps } from './types';
|
|
5
6
|
|
|
6
7
|
export function Input({
|
|
@@ -36,14 +37,9 @@ export function Input({
|
|
|
36
37
|
<View style={styles.container}>
|
|
37
38
|
{label && (
|
|
38
39
|
<Text
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
color: colors.textSecondary,
|
|
43
|
-
fontSize: typography.fontSize.sm,
|
|
44
|
-
marginBottom: spacing.xs,
|
|
45
|
-
},
|
|
46
|
-
]}
|
|
40
|
+
variant="label"
|
|
41
|
+
color={colors.textSecondary}
|
|
42
|
+
style={{ marginBottom: spacing.xs }}
|
|
47
43
|
>
|
|
48
44
|
{label}
|
|
49
45
|
</Text>
|
|
@@ -70,14 +66,9 @@ export function Input({
|
|
|
70
66
|
/>
|
|
71
67
|
{error && (
|
|
72
68
|
<Text
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
color: colors.error,
|
|
77
|
-
fontSize: typography.fontSize.sm,
|
|
78
|
-
marginTop: spacing.xs,
|
|
79
|
-
},
|
|
80
|
-
]}
|
|
69
|
+
variant="caption"
|
|
70
|
+
color={colors.error}
|
|
71
|
+
style={{ marginTop: spacing.xs }}
|
|
81
72
|
>
|
|
82
73
|
{error}
|
|
83
74
|
</Text>
|
|
@@ -90,14 +81,8 @@ const styles = StyleSheet.create({
|
|
|
90
81
|
container: {
|
|
91
82
|
width: '100%',
|
|
92
83
|
},
|
|
93
|
-
label: {
|
|
94
|
-
fontWeight: '500',
|
|
95
|
-
},
|
|
96
84
|
input: {
|
|
97
85
|
borderWidth: 1,
|
|
98
86
|
borderRadius: 8,
|
|
99
87
|
},
|
|
100
|
-
error: {
|
|
101
|
-
// Error text styles
|
|
102
|
-
},
|
|
103
88
|
});
|