@qumra/fanar-native 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/README.md +83 -0
- package/lib/art.d.ts +8 -0
- package/lib/art.js +29 -0
- package/lib/assets/art/avatar.png +0 -0
- package/lib/assets/art/empty.png +0 -0
- package/lib/assets/art/image-placeholder.png +0 -0
- package/lib/assets/art/plan-update.png +0 -0
- package/lib/assets/brand/app-store.png +0 -0
- package/lib/assets/brand/etisalat.png +0 -0
- package/lib/assets/brand/facebook.png +0 -0
- package/lib/assets/brand/google-play.png +0 -0
- package/lib/assets/brand/instagram.png +0 -0
- package/lib/assets/brand/orange.png +0 -0
- package/lib/assets/brand/snapchat.png +0 -0
- package/lib/assets/brand/tiktok.png +0 -0
- package/lib/assets/brand/vodafone.png +0 -0
- package/lib/assets/brand/we.png +0 -0
- package/lib/assets/brand/whatsapp.png +0 -0
- package/lib/assets/brand/x.png +0 -0
- package/lib/assets/brand/youtube.png +0 -0
- package/lib/components/Button.d.ts +28 -0
- package/lib/components/Button.js +75 -0
- package/lib/components/Disclosure.d.ts +41 -0
- package/lib/components/Disclosure.js +98 -0
- package/lib/components/Display.d.ts +88 -0
- package/lib/components/Display.js +157 -0
- package/lib/components/Drawer.d.ts +27 -0
- package/lib/components/Drawer.js +58 -0
- package/lib/components/Feedback.d.ts +43 -0
- package/lib/components/Feedback.js +136 -0
- package/lib/components/Form.d.ts +92 -0
- package/lib/components/Form.js +196 -0
- package/lib/components/Money.d.ts +29 -0
- package/lib/components/Money.js +65 -0
- package/lib/components/Nav.d.ts +29 -0
- package/lib/components/Nav.js +65 -0
- package/lib/components/NumberStepper.d.ts +16 -0
- package/lib/components/NumberStepper.js +79 -0
- package/lib/components/Overlay.d.ts +54 -0
- package/lib/components/Overlay.js +147 -0
- package/lib/components/Rating.d.ts +18 -0
- package/lib/components/Rating.js +32 -0
- package/lib/components/Shell.d.ts +54 -0
- package/lib/components/Shell.js +99 -0
- package/lib/components/Surfaces.d.ts +64 -0
- package/lib/components/Surfaces.js +122 -0
- package/lib/components/Text.d.ts +14 -0
- package/lib/components/Text.js +31 -0
- package/lib/components/currency.d.ts +251 -0
- package/lib/components/currency.js +48 -0
- package/lib/glyphs.d.ts +2 -0
- package/lib/glyphs.js +7 -0
- package/lib/i18n/content.d.ts +1 -0
- package/lib/i18n/content.js +28 -0
- package/lib/i18n/en.d.ts +1 -0
- package/lib/i18n/en.js +124 -0
- package/lib/i18n/index.d.ts +64 -0
- package/lib/i18n/index.js +91 -0
- package/lib/i18n/screens.d.ts +1 -0
- package/lib/i18n/screens.js +2025 -0
- package/lib/icons.d.ts +18 -0
- package/lib/icons.js +400 -0
- package/lib/index.d.ts +25 -0
- package/lib/index.js +25 -0
- package/lib/lib/a11y.d.ts +4 -0
- package/lib/lib/a11y.js +8 -0
- package/lib/lib/digits.d.ts +3 -0
- package/lib/lib/digits.js +22 -0
- package/lib/lib/motion.d.ts +14 -0
- package/lib/lib/motion.js +47 -0
- package/lib/lib/tone.d.ts +9 -0
- package/lib/lib/tone.js +16 -0
- package/lib/qumra/NavIcon.d.ts +14 -0
- package/lib/qumra/NavIcon.js +17 -0
- package/lib/qumra/QumraIcon.d.ts +17 -0
- package/lib/qumra/QumraIcon.js +91 -0
- package/lib/qumra/data.generated.d.ts +3 -0
- package/lib/qumra/data.generated.js +146 -0
- package/lib/qumra/types.d.ts +5 -0
- package/lib/qumra/types.js +1 -0
- package/lib/theme/context.d.ts +31 -0
- package/lib/theme/context.js +46 -0
- package/lib/theme/fonts.d.ts +9 -0
- package/lib/theme/fonts.js +36 -0
- package/lib/theme/index.d.ts +4 -0
- package/lib/theme/index.js +4 -0
- package/lib/theme/shadows.d.ts +2 -0
- package/lib/theme/shadows.js +30 -0
- package/lib/theme/theme.d.ts +52 -0
- package/lib/theme/theme.js +78 -0
- package/lib/tokens/generated.d.ts +180 -0
- package/lib/tokens/generated.js +173 -0
- package/lib/tokens/index.d.ts +1 -0
- package/lib/tokens/index.js +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { Animated, Modal as RNModal, PanResponder, Pressable, ScrollView, StyleSheet, useWindowDimensions, View, } from 'react-native';
|
|
4
|
+
import { Text } from './Text';
|
|
5
|
+
import { Button, IconButton } from './Button';
|
|
6
|
+
import { useFanar } from '../theme/context';
|
|
7
|
+
import { EASE_OUT } from '../lib/motion';
|
|
8
|
+
import { DURATION } from '../theme/theme';
|
|
9
|
+
import { TONE_SOFT } from '../lib/tone';
|
|
10
|
+
import { ChevronLeft, ChevronRight, X } from '../icons';
|
|
11
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
12
|
+
export function Sheet({ open, title, onClose, onBack, action, children, maxHeightRatio = 0.82, scroll = true, style, }) {
|
|
13
|
+
const { theme, p, dir } = useFanar();
|
|
14
|
+
const { height } = useWindowDimensions();
|
|
15
|
+
const { bottom } = useSafeAreaInsets();
|
|
16
|
+
const slide = useRef(new Animated.Value(0)).current;
|
|
17
|
+
const drag = useRef(new Animated.Value(0)).current;
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
Animated.timing(slide, {
|
|
20
|
+
toValue: open ? 1 : 0,
|
|
21
|
+
duration: DURATION.sheet,
|
|
22
|
+
easing: EASE_OUT,
|
|
23
|
+
useNativeDriver: true,
|
|
24
|
+
}).start();
|
|
25
|
+
if (open)
|
|
26
|
+
drag.setValue(0);
|
|
27
|
+
}, [open, slide, drag]);
|
|
28
|
+
const pan = useRef(PanResponder.create({
|
|
29
|
+
onMoveShouldSetPanResponder: (_, g) => g.dy > 6 && Math.abs(g.dy) > Math.abs(g.dx),
|
|
30
|
+
onPanResponderMove: (_, g) => {
|
|
31
|
+
if (g.dy > 0)
|
|
32
|
+
drag.setValue(g.dy);
|
|
33
|
+
},
|
|
34
|
+
onPanResponderRelease: (_, g) => {
|
|
35
|
+
if (g.dy > 90 || g.vy > 0.5)
|
|
36
|
+
onClose();
|
|
37
|
+
else
|
|
38
|
+
Animated.timing(drag, { toValue: 0, duration: 140, useNativeDriver: true }).start();
|
|
39
|
+
},
|
|
40
|
+
})).current;
|
|
41
|
+
const translateY = Animated.add(slide.interpolate({ inputRange: [0, 1], outputRange: [height, 0] }), drag);
|
|
42
|
+
const Body = scroll ? ScrollView : View;
|
|
43
|
+
return (_jsx(RNModal, { visible: open, transparent: true, animationType: "none", onRequestClose: onClose, statusBarTranslucent: true, children: _jsxs(View, { style: [styles.sheetRoot, { direction: dir }], children: [_jsx(Animated.View, { style: [StyleSheet.absoluteFill, { opacity: slide }], children: _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: p('close'), onPress: onClose, style: [StyleSheet.absoluteFill, styles.scrim] }) }), _jsxs(Animated.View, { style: [
|
|
44
|
+
styles.sheet,
|
|
45
|
+
theme.shadow.sheet,
|
|
46
|
+
{
|
|
47
|
+
backgroundColor: theme.color.surface,
|
|
48
|
+
borderTopLeftRadius: 20,
|
|
49
|
+
borderTopRightRadius: 20,
|
|
50
|
+
maxHeight: height * maxHeightRatio,
|
|
51
|
+
paddingBottom: bottom,
|
|
52
|
+
transform: [{ translateY }],
|
|
53
|
+
borderTopWidth: 1,
|
|
54
|
+
borderTopColor: theme.dark ? 'rgba(255,255,255,0.08)' : theme.color.line,
|
|
55
|
+
},
|
|
56
|
+
style,
|
|
57
|
+
], children: [_jsx(View, { ...pan.panHandlers, style: styles.grabArea, children: _jsx(View, { style: [styles.grabber, { backgroundColor: theme.color.border }] }) }), (title || onBack || action) && (_jsxs(View, { style: styles.sheetHead, children: [_jsxs(View, { style: styles.sheetHeadStart, children: [onBack && (_jsx(IconButton, { icon: dir === 'rtl' ? ChevronRight : ChevronLeft, size: 32, iconSize: 18, color: "muted", onPress: onBack, accessibilityLabel: p('back') })), title ? (_jsx(Text, { size: "h4", weight: "black", align: "start", lines: 1, style: styles.flex1, children: title })) : null] }), _jsxs(View, { style: styles.sheetHeadEnd, children: [action, _jsx(IconButton, { icon: X, size: 36, iconSize: 17, color: "muted", background: "bg", onPress: onClose, accessibilityLabel: p('close') })] })] })), _jsx(Body, { style: styles.flex1, ...(scroll
|
|
58
|
+
? {
|
|
59
|
+
contentContainerStyle: styles.sheetBody,
|
|
60
|
+
showsVerticalScrollIndicator: false,
|
|
61
|
+
keyboardShouldPersistTaps: 'handled',
|
|
62
|
+
}
|
|
63
|
+
: {}), children: children })] })] }) }));
|
|
64
|
+
}
|
|
65
|
+
export function SheetRow({ icon: Icon, label, meta, danger, onPress, style }) {
|
|
66
|
+
const { theme } = useFanar();
|
|
67
|
+
const fg = danger ? theme.color.red : theme.color.ink2;
|
|
68
|
+
return (_jsxs(Pressable, { accessibilityRole: "menuitem", onPress: onPress, style: ({ pressed }) => [
|
|
69
|
+
styles.sheetRow,
|
|
70
|
+
{
|
|
71
|
+
borderRadius: theme.radius.field,
|
|
72
|
+
backgroundColor: pressed ? (danger ? theme.color['red-bg'] : theme.color.hover) : 'transparent',
|
|
73
|
+
},
|
|
74
|
+
style,
|
|
75
|
+
], children: [Icon && _jsx(Icon, { size: 17, strokeWidth: 2, color: fg }), _jsx(Text, { size: "body", weight: "semiBold", color: danger ? 'red' : 'ink2', align: "start", style: styles.flex1, lines: 1, children: label }), meta ? (_jsx(Text, { size: "caption", color: "muted2", children: meta })) : null] }));
|
|
76
|
+
}
|
|
77
|
+
const MODAL_WIDTH = { sm: 380, md: 520, lg: 720 };
|
|
78
|
+
export function Modal({ open, title, description, onClose, size = 'md', footer, children }) {
|
|
79
|
+
const { theme, p, dir } = useFanar();
|
|
80
|
+
const { width } = useWindowDimensions();
|
|
81
|
+
const fade = useRef(new Animated.Value(0)).current;
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
Animated.timing(fade, {
|
|
84
|
+
toValue: open ? 1 : 0,
|
|
85
|
+
duration: DURATION.fade,
|
|
86
|
+
easing: EASE_OUT,
|
|
87
|
+
useNativeDriver: true,
|
|
88
|
+
}).start();
|
|
89
|
+
}, [open, fade]);
|
|
90
|
+
return (_jsx(RNModal, { visible: open, transparent: true, animationType: "none", onRequestClose: onClose, statusBarTranslucent: true, children: _jsxs(Animated.View, { style: [styles.modalRoot, { opacity: fade, direction: dir }], children: [_jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: p('close'), onPress: onClose, style: [StyleSheet.absoluteFill, styles.scrim] }), _jsxs(View, { accessibilityViewIsModal: true, style: [
|
|
91
|
+
styles.modal,
|
|
92
|
+
theme.shadow.modal,
|
|
93
|
+
{
|
|
94
|
+
backgroundColor: theme.color.surface,
|
|
95
|
+
borderRadius: theme.radius.section,
|
|
96
|
+
width: Math.min(MODAL_WIDTH[size], width - 32),
|
|
97
|
+
},
|
|
98
|
+
], children: [(title || description) && (_jsxs(View, { style: styles.modalHead, children: [_jsxs(View, { style: styles.flex1, children: [title ? (_jsx(Text, { size: "h4", weight: "black", align: "start", children: title })) : null, description ? (_jsx(Text, { size: "ui", color: "muted", align: "start", style: styles.modalDesc, children: description })) : null] }), _jsx(IconButton, { icon: X, size: 36, iconSize: 17, color: "muted", background: "bg", onPress: onClose, accessibilityLabel: p('close') })] })), children ? _jsx(View, { style: styles.modalBody, children: children }) : null, footer ? _jsx(View, { style: styles.modalFoot, children: footer }) : null] })] }) }));
|
|
99
|
+
}
|
|
100
|
+
export function Alert({ tone = 'amber', title, children, icon: Icon, action, style }) {
|
|
101
|
+
const { theme } = useFanar();
|
|
102
|
+
const c = TONE_SOFT[tone];
|
|
103
|
+
return (_jsxs(View, { accessibilityRole: "alert", style: [
|
|
104
|
+
styles.alert,
|
|
105
|
+
{
|
|
106
|
+
backgroundColor: theme.colorOf(c.bg, theme.color.hover),
|
|
107
|
+
borderColor: theme.colorOf(c.line, theme.color.line),
|
|
108
|
+
borderRadius: theme.radius.field,
|
|
109
|
+
},
|
|
110
|
+
style,
|
|
111
|
+
], children: [Icon && _jsx(Icon, { size: 16, strokeWidth: 2, color: theme.colorOf(c.fg, theme.color.ink) }), _jsxs(View, { style: styles.flex1, children: [title ? (_jsx(Text, { size: "ui", weight: "extraBold", color: c.fg, align: "start", children: title })) : null, typeof children === 'string' ? (_jsx(Text, { size: "caption", color: c.fg, align: "start", leading: "relaxed", children: children })) : (children)] }), action] }));
|
|
112
|
+
}
|
|
113
|
+
export function EmptyState({ icon: Icon, title, description, actionLabel, onAction, style }) {
|
|
114
|
+
const { theme } = useFanar();
|
|
115
|
+
return (_jsxs(View, { style: [styles.empty, style], children: [Icon && (_jsx(View, { style: [styles.emptyIcon, { backgroundColor: theme.color.soft, borderRadius: theme.radius.section }], children: _jsx(Icon, { size: 26, strokeWidth: 1.8, color: theme.color.brand }) })), _jsx(Text, { size: "lead", weight: "extraBold", align: "center", children: title }), description ? (_jsx(Text, { size: "ui", color: "muted", align: "center", leading: "relaxed", style: styles.emptyDesc, children: description })) : null, actionLabel && onAction ? (_jsx(Button, { size: "sm", variant: "soft", onPress: onAction, style: styles.emptyAction, children: actionLabel })) : null] }));
|
|
116
|
+
}
|
|
117
|
+
const styles = StyleSheet.create({
|
|
118
|
+
sheetRoot: { flex: 1, justifyContent: 'flex-end' },
|
|
119
|
+
scrim: { backgroundColor: 'rgba(0,0,0,0.5)' },
|
|
120
|
+
sheet: { overflow: 'hidden' },
|
|
121
|
+
grabArea: { paddingTop: 12, paddingBottom: 6, alignItems: 'center' },
|
|
122
|
+
grabber: { width: 44, height: 6, borderRadius: 3 },
|
|
123
|
+
sheetHead: {
|
|
124
|
+
flexDirection: 'row',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
justifyContent: 'space-between',
|
|
127
|
+
gap: 12,
|
|
128
|
+
paddingHorizontal: 20,
|
|
129
|
+
paddingBottom: 12,
|
|
130
|
+
},
|
|
131
|
+
sheetHeadStart: { flexDirection: 'row', alignItems: 'center', gap: 8, flex: 1, minWidth: 0 },
|
|
132
|
+
sheetHeadEnd: { flexDirection: 'row', alignItems: 'center', gap: 12 },
|
|
133
|
+
sheetBody: { paddingHorizontal: 12, paddingBottom: 16 },
|
|
134
|
+
sheetRow: { flexDirection: 'row', alignItems: 'center', gap: 12, height: 48, paddingHorizontal: 12 },
|
|
135
|
+
flex1: { flex: 1 },
|
|
136
|
+
modalRoot: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 16 },
|
|
137
|
+
modal: { overflow: 'hidden' },
|
|
138
|
+
modalHead: { flexDirection: 'row', alignItems: 'flex-start', gap: 12, padding: 20, paddingBottom: 12 },
|
|
139
|
+
modalDesc: { marginTop: 4 },
|
|
140
|
+
modalBody: { paddingHorizontal: 20, paddingBottom: 20 },
|
|
141
|
+
modalFoot: { flexDirection: 'row', justifyContent: 'flex-end', gap: 10, padding: 16 },
|
|
142
|
+
alert: { flexDirection: 'row', alignItems: 'flex-start', gap: 10, padding: 12, borderWidth: 1 },
|
|
143
|
+
empty: { alignItems: 'center', gap: 8, paddingVertical: 40, paddingHorizontal: 24 },
|
|
144
|
+
emptyIcon: { width: 56, height: 56, alignItems: 'center', justifyContent: 'center', marginBottom: 4 },
|
|
145
|
+
emptyDesc: { maxWidth: 340 },
|
|
146
|
+
emptyAction: { marginTop: 8 },
|
|
147
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
2
|
+
export declare const MAX = 5;
|
|
3
|
+
export interface StarsProps {
|
|
4
|
+
value: number;
|
|
5
|
+
size?: number;
|
|
6
|
+
showValue?: boolean;
|
|
7
|
+
count?: number;
|
|
8
|
+
style?: StyleProp<ViewStyle>;
|
|
9
|
+
}
|
|
10
|
+
export declare function Stars({ value, size, showValue, count, style }: StarsProps): import("react").JSX.Element;
|
|
11
|
+
export interface RatingInputProps {
|
|
12
|
+
value: number;
|
|
13
|
+
onChange: (next: number) => void;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
label?: string;
|
|
16
|
+
style?: StyleProp<ViewStyle>;
|
|
17
|
+
}
|
|
18
|
+
export declare function RatingInput({ value, onChange, disabled, label, style }: RatingInputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Pressable, StyleSheet, View } from 'react-native';
|
|
3
|
+
import { Text } from './Text';
|
|
4
|
+
import { useFanar } from '../theme/context';
|
|
5
|
+
import { Star } from '../icons';
|
|
6
|
+
export const MAX = 5;
|
|
7
|
+
export function Stars({ value, size = 14, showValue, count, style }) {
|
|
8
|
+
const { theme } = useFanar();
|
|
9
|
+
const filled = Math.round(value);
|
|
10
|
+
return (_jsxs(View, { accessible: true, accessibilityRole: "image", accessibilityLabel: `${value} / ${MAX}`, style: [styles.row, style], children: [Array.from({ length: MAX }, (_, i) => (_jsx(Star, { size: size, strokeWidth: 2, color: i < filled ? theme.color.amber : theme.color.muted2, fill: i < filled ? theme.color.amber : 'transparent' }, i))), showValue && (_jsx(Text, { size: "caption", weight: "bold", color: "muted", numeric: true, style: styles.value, children: value.toFixed(1) })), count !== undefined && (_jsxs(Text, { size: "caption", color: "muted2", numeric: true, children: ["(", count, ")"] }))] }));
|
|
11
|
+
}
|
|
12
|
+
export function RatingInput({ value, onChange, disabled, label, style }) {
|
|
13
|
+
const { theme } = useFanar();
|
|
14
|
+
return (_jsx(View, { accessibilityRole: "radiogroup", accessibilityLabel: label, style: [styles.inputRow, style], children: Array.from({ length: MAX }, (_, i) => {
|
|
15
|
+
const n = i + 1;
|
|
16
|
+
const on = n <= value;
|
|
17
|
+
return (_jsx(Pressable, { accessibilityRole: "radio", accessibilityState: { checked: n === value, disabled: !!disabled }, accessibilityLabel: `${n} / ${MAX}`, disabled: disabled, onPress: () => onChange(n), style: ({ pressed }) => [
|
|
18
|
+
styles.starBtn,
|
|
19
|
+
{
|
|
20
|
+
borderRadius: theme.radius.sm,
|
|
21
|
+
backgroundColor: pressed && !disabled ? theme.color.hover : 'transparent',
|
|
22
|
+
opacity: disabled ? 0.6 : 1,
|
|
23
|
+
},
|
|
24
|
+
], children: _jsx(Star, { size: 22, strokeWidth: 2, color: on ? theme.color.amber : theme.color.muted2, fill: on ? theme.color.amber : 'transparent' }) }, n));
|
|
25
|
+
}) }));
|
|
26
|
+
}
|
|
27
|
+
const styles = StyleSheet.create({
|
|
28
|
+
row: { flexDirection: 'row', alignItems: 'center', gap: 2 },
|
|
29
|
+
value: { marginStart: 8 },
|
|
30
|
+
inputRow: { flexDirection: 'row', alignItems: 'center', gap: 4 },
|
|
31
|
+
starBtn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' },
|
|
32
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type ImageSourcePropType, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import type { LucideIcon } from '../icons';
|
|
4
|
+
export interface ScreenProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
scroll?: boolean;
|
|
7
|
+
padded?: boolean;
|
|
8
|
+
gap?: number;
|
|
9
|
+
tabBarSpace?: boolean;
|
|
10
|
+
refreshing?: boolean;
|
|
11
|
+
onRefresh?: () => void;
|
|
12
|
+
background?: 'bg' | 'surface';
|
|
13
|
+
header?: ReactNode;
|
|
14
|
+
footer?: ReactNode;
|
|
15
|
+
style?: StyleProp<ViewStyle>;
|
|
16
|
+
contentStyle?: StyleProp<ViewStyle>;
|
|
17
|
+
}
|
|
18
|
+
export declare function Screen({ children, scroll, padded, gap, tabBarSpace, refreshing, onRefresh, background, header, footer, style, contentStyle, }: ScreenProps): import("react").JSX.Element;
|
|
19
|
+
export interface HeaderAction {
|
|
20
|
+
key: string;
|
|
21
|
+
icon: LucideIcon;
|
|
22
|
+
label: string;
|
|
23
|
+
onPress: () => void;
|
|
24
|
+
active?: boolean;
|
|
25
|
+
badge?: number;
|
|
26
|
+
}
|
|
27
|
+
export type HeaderMark = ImageSourcePropType | string | LucideIcon;
|
|
28
|
+
export interface AppHeaderProps {
|
|
29
|
+
logo?: HeaderMark;
|
|
30
|
+
onPressLogo?: () => void;
|
|
31
|
+
logoActive?: boolean;
|
|
32
|
+
avatar?: HeaderMark;
|
|
33
|
+
onPressAvatar?: () => void;
|
|
34
|
+
avatarActive?: boolean;
|
|
35
|
+
actions?: HeaderAction[];
|
|
36
|
+
title?: string;
|
|
37
|
+
leading?: ReactNode;
|
|
38
|
+
safeTop?: boolean;
|
|
39
|
+
style?: StyleProp<ViewStyle>;
|
|
40
|
+
}
|
|
41
|
+
export declare function AppHeader({ logo, onPressLogo, logoActive, avatar, onPressAvatar, avatarActive, actions, title, leading, safeTop, style, }: AppHeaderProps): import("react").JSX.Element;
|
|
42
|
+
export interface TabBarItem {
|
|
43
|
+
key: string;
|
|
44
|
+
label: string;
|
|
45
|
+
icon: LucideIcon | string;
|
|
46
|
+
badge?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface BottomTabBarProps {
|
|
49
|
+
items: TabBarItem[];
|
|
50
|
+
value: string;
|
|
51
|
+
onChange: (key: string) => void;
|
|
52
|
+
style?: StyleProp<ViewStyle>;
|
|
53
|
+
}
|
|
54
|
+
export declare function BottomTabBar({ items, value, onChange, style }: BottomTabBarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Image, Pressable, RefreshControl, ScrollView, StatusBar, StyleSheet, View, } from 'react-native';
|
|
3
|
+
import { Text } from './Text';
|
|
4
|
+
import { Counter } from './Display';
|
|
5
|
+
import { useFanar } from '../theme/context';
|
|
6
|
+
import { NavIcon } from '../qumra/NavIcon';
|
|
7
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
8
|
+
export function Screen({ children, scroll = true, padded = true, gap = 16, tabBarSpace = false, refreshing, onRefresh, background = 'bg', header, footer, style, contentStyle, }) {
|
|
9
|
+
const { theme } = useFanar();
|
|
10
|
+
const insets = useSafeAreaInsets();
|
|
11
|
+
const content = [
|
|
12
|
+
{
|
|
13
|
+
paddingHorizontal: padded ? 16 : 0,
|
|
14
|
+
paddingTop: 16,
|
|
15
|
+
paddingBottom: (tabBarSpace ? 76 : 16) + insets.bottom,
|
|
16
|
+
gap,
|
|
17
|
+
},
|
|
18
|
+
contentStyle,
|
|
19
|
+
];
|
|
20
|
+
return (_jsxs(View, { style: [styles.flex1, { backgroundColor: theme.color[background] }, style], children: [_jsx(StatusBar, { barStyle: theme.dark ? 'light-content' : 'dark-content', backgroundColor: theme.color.surface }), header, scroll ? (_jsx(ScrollView, { style: styles.flex1, contentContainerStyle: content, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", refreshControl: onRefresh ? (_jsx(RefreshControl, { refreshing: !!refreshing, onRefresh: onRefresh, tintColor: theme.color.brand, colors: [theme.color.brand], progressBackgroundColor: theme.color.surface })) : undefined, children: children })) : (_jsx(View, { style: [styles.flex1, content], children: children })), footer] }));
|
|
21
|
+
}
|
|
22
|
+
function isIconMark(v) {
|
|
23
|
+
return (typeof v === 'function' ||
|
|
24
|
+
(typeof v === 'object' && v !== null && '$$typeof' in v));
|
|
25
|
+
}
|
|
26
|
+
function Mark({ src, size, color, style, }) {
|
|
27
|
+
if (isIconMark(src)) {
|
|
28
|
+
const Glyph = src;
|
|
29
|
+
return _jsx(Glyph, { size: size, color: color });
|
|
30
|
+
}
|
|
31
|
+
return (_jsx(Image, { accessibilityIgnoresInvertColors: true, source: typeof src === 'string' ? { uri: src } : src, style: style, resizeMode: "cover" }));
|
|
32
|
+
}
|
|
33
|
+
export function AppHeader({ logo, onPressLogo, logoActive, avatar, onPressAvatar, avatarActive, actions = [], title, leading, safeTop = true, style, }) {
|
|
34
|
+
const { theme, p } = useFanar();
|
|
35
|
+
const insets = useSafeAreaInsets();
|
|
36
|
+
return (_jsx(View, { style: [
|
|
37
|
+
styles.header,
|
|
38
|
+
{
|
|
39
|
+
paddingTop: safeTop ? 12 : 0,
|
|
40
|
+
backgroundColor: theme.color.surface,
|
|
41
|
+
borderBottomWidth: 1,
|
|
42
|
+
borderBottomColor: theme.color.line,
|
|
43
|
+
},
|
|
44
|
+
style,
|
|
45
|
+
], children: _jsxs(View, { style: styles.headerRow, children: [logo ? (_jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: p('switchStore'), accessibilityState: { expanded: logoActive }, onPress: onPressLogo, style: [
|
|
46
|
+
styles.logo,
|
|
47
|
+
{
|
|
48
|
+
borderRadius: theme.radius.field,
|
|
49
|
+
borderColor: logoActive ? theme.color.brand : theme.color.line,
|
|
50
|
+
backgroundColor: theme.color.bg,
|
|
51
|
+
},
|
|
52
|
+
], children: _jsx(Mark, { src: logo, size: 20, color: theme.color.ink2, style: styles.logoImg }) })) : null, leading ? _jsx(View, { style: styles.leading, children: leading }) : null, title ? (_jsx(Text, { size: "h4", weight: "black", align: "start", lines: 1, style: styles.flex1, children: title })) : (_jsx(View, { style: [styles.actions, !!leading && styles.actionsCompact], children: actions.map((a) => (_jsxs(Pressable, { accessibilityRole: "button", accessibilityLabel: a.label, accessibilityState: { selected: a.active }, onPress: a.onPress, style: ({ pressed }) => [
|
|
53
|
+
styles.iconBtn,
|
|
54
|
+
{
|
|
55
|
+
borderRadius: theme.radius.full,
|
|
56
|
+
backgroundColor: a.active
|
|
57
|
+
? theme.color.soft
|
|
58
|
+
: pressed
|
|
59
|
+
? theme.color.hover
|
|
60
|
+
: 'transparent',
|
|
61
|
+
},
|
|
62
|
+
], children: [_jsx(a.icon, { size: 25, strokeWidth: 2, color: a.active ? theme.color.brand : theme.color.ink2 }), !!a.badge && _jsx(Counter, { value: a.badge, style: styles.badge })] }, a.key))) })), avatar ? (_jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: p('profile'), onPress: onPressAvatar, style: [
|
|
63
|
+
styles.avatar,
|
|
64
|
+
{ borderColor: avatarActive ? theme.color.brand : 'transparent' },
|
|
65
|
+
], children: _jsx(Mark, { src: avatar, size: 20, color: theme.color.ink2, style: styles.avatarImg }) })) : null] }) }));
|
|
66
|
+
}
|
|
67
|
+
export function BottomTabBar({ items, value, onChange, style }) {
|
|
68
|
+
const { theme } = useFanar();
|
|
69
|
+
return (_jsx(View, { accessibilityRole: "tablist", style: [
|
|
70
|
+
styles.tabBar,
|
|
71
|
+
{
|
|
72
|
+
backgroundColor: theme.color.surface,
|
|
73
|
+
borderTopWidth: 1,
|
|
74
|
+
borderTopColor: theme.color.line,
|
|
75
|
+
},
|
|
76
|
+
style,
|
|
77
|
+
], children: items.map((it) => {
|
|
78
|
+
const on = it.key === value;
|
|
79
|
+
return (_jsxs(Pressable, { accessibilityRole: "tab", accessibilityState: { selected: on }, accessibilityLabel: it.label, onPress: () => onChange(it.key), style: styles.tab, children: [_jsxs(View, { style: styles.tabIcon, children: [_jsx(NavIcon, { icon: it.icon, size: 22, active: on, strokeWidth: on ? 2.4 : 2, color: theme.color.muted2, activeColor: theme.color.brand }), !!it.badge && _jsx(Counter, { value: it.badge, style: styles.tabBadge })] }), _jsx(Text, { size: "micro", weight: on ? 'bold' : 'medium', color: on ? 'brand' : 'muted2', leading: "none", children: it.label })] }, it.key));
|
|
80
|
+
}) }));
|
|
81
|
+
}
|
|
82
|
+
const styles = StyleSheet.create({
|
|
83
|
+
flex1: { flex: 1 },
|
|
84
|
+
header: { paddingBottom: 8 },
|
|
85
|
+
headerRow: { height: 36, flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 14 },
|
|
86
|
+
logo: { width: 36, height: 36, borderWidth: 1, overflow: 'hidden', alignItems: 'center', justifyContent: 'center' },
|
|
87
|
+
logoImg: { width: '100%', height: '100%' },
|
|
88
|
+
leading: { flex: 1, flexShrink: 1, minWidth: 0 },
|
|
89
|
+
actions: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-around' },
|
|
90
|
+
actionsCompact: { flex: 0, flexShrink: 0, justifyContent: 'flex-end' },
|
|
91
|
+
iconBtn: { height: 36, minWidth: 36, paddingHorizontal: 8, alignItems: 'center', justifyContent: 'center' },
|
|
92
|
+
badge: { position: 'absolute', top: 0, insetInlineEnd: 6, },
|
|
93
|
+
avatar: { width: 36, height: 36, borderRadius: 18, borderWidth: 1, overflow: 'hidden', alignItems: 'center', justifyContent: 'center' },
|
|
94
|
+
avatarImg: { width: '100%', height: '100%' },
|
|
95
|
+
tabBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-around', paddingTop: 6, paddingBottom: 6 },
|
|
96
|
+
tab: { flex: 1, alignItems: 'center', gap: 4, paddingVertical: 3 },
|
|
97
|
+
tabIcon: { height: 26, alignItems: 'center', justifyContent: 'center' },
|
|
98
|
+
tabBadge: { position: 'absolute', top: -2, insetInlineEnd: 4 },
|
|
99
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { type LucideIcon } from '../icons';
|
|
4
|
+
export declare function RuleGrid({ spacing, opacity, count }: {
|
|
5
|
+
spacing?: number;
|
|
6
|
+
opacity?: number;
|
|
7
|
+
count?: number;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
export declare function Glow({ size, color, top, bottom, start, end, }: {
|
|
10
|
+
size?: number;
|
|
11
|
+
color?: string;
|
|
12
|
+
top?: number;
|
|
13
|
+
bottom?: number;
|
|
14
|
+
start?: number;
|
|
15
|
+
end?: number;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
17
|
+
export interface HeroProps {
|
|
18
|
+
tag?: string;
|
|
19
|
+
tagIcon?: LucideIcon;
|
|
20
|
+
title: string;
|
|
21
|
+
body?: string;
|
|
22
|
+
metric?: string;
|
|
23
|
+
metricNote?: string;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
style?: StyleProp<ViewStyle>;
|
|
26
|
+
}
|
|
27
|
+
export declare function Hero({ tag, tagIcon: TagIcon, title, body, metric, metricNote, children, style }: HeroProps): import("react").JSX.Element;
|
|
28
|
+
export interface PromptCardProps {
|
|
29
|
+
icon?: LucideIcon;
|
|
30
|
+
title: string;
|
|
31
|
+
body?: string;
|
|
32
|
+
action?: ReactNode;
|
|
33
|
+
prompts?: {
|
|
34
|
+
key: string;
|
|
35
|
+
text: string;
|
|
36
|
+
icon?: LucideIcon;
|
|
37
|
+
}[];
|
|
38
|
+
onPickPrompt?: (key: string) => void;
|
|
39
|
+
style?: StyleProp<ViewStyle>;
|
|
40
|
+
}
|
|
41
|
+
export declare function PromptCard({ icon: Icon, title, body, action, prompts, onPickPrompt, style }: PromptCardProps): import("react").JSX.Element;
|
|
42
|
+
export interface ListRowProps {
|
|
43
|
+
icon?: LucideIcon | string;
|
|
44
|
+
leading?: ReactNode;
|
|
45
|
+
title: string;
|
|
46
|
+
subtitle?: string;
|
|
47
|
+
meta?: ReactNode;
|
|
48
|
+
trailing?: ReactNode;
|
|
49
|
+
onPress?: () => void;
|
|
50
|
+
chevron?: boolean;
|
|
51
|
+
danger?: boolean;
|
|
52
|
+
style?: StyleProp<ViewStyle>;
|
|
53
|
+
}
|
|
54
|
+
export declare function ListRow({ icon: Icon, leading, title, subtitle, meta, trailing, onPress, chevron, danger, style, }: ListRowProps): import("react").JSX.Element;
|
|
55
|
+
export interface StepRowProps {
|
|
56
|
+
icon?: LucideIcon | string;
|
|
57
|
+
title: string;
|
|
58
|
+
why?: string;
|
|
59
|
+
done?: boolean;
|
|
60
|
+
action?: ReactNode;
|
|
61
|
+
onPress?: () => void;
|
|
62
|
+
style?: StyleProp<ViewStyle>;
|
|
63
|
+
}
|
|
64
|
+
export declare function StepRow({ icon, title, why, done, action, onPress, style }: StepRowProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Pressable, StyleSheet, View } from 'react-native';
|
|
3
|
+
import { Text } from './Text';
|
|
4
|
+
import { useFanar } from '../theme/context';
|
|
5
|
+
import { Check, ChevronLeft, ChevronRight, qumraIconFor } from '../icons';
|
|
6
|
+
import { NavIcon } from '../qumra/NavIcon';
|
|
7
|
+
import { QUMRA_ICONS, QumraIcon } from '../qumra/QumraIcon';
|
|
8
|
+
const brandIconName = (name) => qumraIconFor(name) ?? (name in QUMRA_ICONS ? name : undefined);
|
|
9
|
+
export function RuleGrid({ spacing = 72, opacity = 0.045, count = 8 }) {
|
|
10
|
+
return (_jsx(View, { pointerEvents: "none", style: StyleSheet.absoluteFill, children: Array.from({ length: count }, (_, i) => (_jsx(View, { style: {
|
|
11
|
+
position: 'absolute',
|
|
12
|
+
top: 0,
|
|
13
|
+
bottom: 0,
|
|
14
|
+
insetInlineStart: i * spacing,
|
|
15
|
+
width: 1,
|
|
16
|
+
backgroundColor: `rgba(255,255,255,${opacity})`,
|
|
17
|
+
} }, i))) }));
|
|
18
|
+
}
|
|
19
|
+
export function Glow({ size = 230, color = 'rgba(125,211,192,0.09)', top, bottom, start, end, }) {
|
|
20
|
+
return (_jsx(View, { pointerEvents: "none", style: {
|
|
21
|
+
position: 'absolute',
|
|
22
|
+
width: size,
|
|
23
|
+
height: size,
|
|
24
|
+
borderRadius: size / 2,
|
|
25
|
+
backgroundColor: color,
|
|
26
|
+
top,
|
|
27
|
+
bottom,
|
|
28
|
+
insetInlineStart: start,
|
|
29
|
+
insetInlineEnd: end,
|
|
30
|
+
} }));
|
|
31
|
+
}
|
|
32
|
+
export function Hero({ tag, tagIcon: TagIcon, title, body, metric, metricNote, children, style }) {
|
|
33
|
+
const { theme } = useFanar();
|
|
34
|
+
return (_jsxs(View, { style: [
|
|
35
|
+
styles.hero,
|
|
36
|
+
{ backgroundColor: theme.color['brand-deep'], borderRadius: theme.radius.lg },
|
|
37
|
+
style,
|
|
38
|
+
], children: [_jsx(RuleGrid, {}), _jsx(Glow, { top: -70, end: -50 }), _jsxs(View, { style: styles.heroTop, children: [_jsxs(View, { style: styles.heroText, children: [tag ? (_jsxs(View, { style: [styles.heroTag, { borderRadius: theme.radius.full }], children: [TagIcon && _jsx(TagIcon, { size: 13, strokeWidth: 2, color: theme.color.amber }), _jsx(Text, { style: { textAlign: 'center' }, size: 11.5, weight: "extraBold", color: "amber", leading: "none", children: tag })] })) : null, _jsx(Text, { size: 23, style: { textAlign: 'left' }, weight: "black", color: "white", align: "start", leading: 1.42, children: title }), body ? (_jsx(Text, { size: 13.5, color: "hero-body", align: "start", leading: 1.75, style: styles.heroBody, children: body })) : null] }), metric ? (_jsxs(View, { style: styles.heroMetric, children: [_jsx(Text, { size: 34, weight: "black", color: "mint", leading: "none", numeric: true, children: metric }), metricNote ? (_jsx(Text, { size: 11.5, color: "hero-meta", numeric: true, children: metricNote })) : null] })) : null] }), children ? _jsx(View, { style: styles.heroFoot, children: children }) : null] }));
|
|
39
|
+
}
|
|
40
|
+
export function PromptCard({ icon: Icon, title, body, action, prompts = [], onPickPrompt, style }) {
|
|
41
|
+
const { theme, dir } = useFanar();
|
|
42
|
+
const Chevron = dir === 'rtl' ? ChevronLeft : ChevronRight;
|
|
43
|
+
return (_jsxs(View, { style: [styles.prompt, { backgroundColor: theme.color.night, borderRadius: theme.radius.lg }, style], children: [_jsx(Glow, { size: 190, color: "rgba(125,211,192,0.07)", top: -60, start: -40 }), _jsxs(View, { style: styles.promptHead, children: [Icon && (_jsx(View, { style: [styles.promptIcon, { borderRadius: theme.radius.field }], children: _jsx(Icon, { size: 19, strokeWidth: 2, color: theme.color.mint }) })), _jsxs(View, { style: styles.flex1, children: [_jsx(Text, { size: 15, weight: "extraBold", color: "white", align: "start", children: title }), body ? (_jsx(Text, { size: 12.5, color: "hero-meta", align: "start", children: body })) : null] }), action] }), prompts.length > 0 && (_jsx(View, { style: styles.promptList, children: prompts.map((pr) => (_jsxs(Pressable, { accessibilityRole: "button", onPress: () => onPickPrompt?.(pr.key), style: ({ pressed }) => [
|
|
44
|
+
styles.promptRow,
|
|
45
|
+
{
|
|
46
|
+
borderRadius: theme.radius.field,
|
|
47
|
+
backgroundColor: pressed ? 'rgba(255,255,255,0.09)' : 'rgba(255,255,255,0.05)',
|
|
48
|
+
borderColor: 'rgba(255,255,255,0.08)',
|
|
49
|
+
},
|
|
50
|
+
], children: [pr.icon && _jsx(pr.icon, { size: 14, strokeWidth: 2, color: theme.color.mint }), _jsx(Text, { size: 12.5, color: "hero-text", align: "start", lines: 1, style: styles.flex1, children: pr.text }), _jsx(Chevron, { size: 13, strokeWidth: 2, color: theme.color['hero-dim'] })] }, pr.key))) }))] }));
|
|
51
|
+
}
|
|
52
|
+
export function ListRow({ icon: Icon, leading, title, subtitle, meta, trailing, onPress, chevron, danger, style, }) {
|
|
53
|
+
const { theme, dir } = useFanar();
|
|
54
|
+
const Chevron = dir === 'rtl' ? ChevronLeft : ChevronRight;
|
|
55
|
+
const showChevron = chevron ?? !!onPress;
|
|
56
|
+
const body = (_jsxs(_Fragment, { children: [leading ??
|
|
57
|
+
(Icon ? (_jsx(View, { style: [styles.rowIcon, { backgroundColor: theme.color.soft, borderRadius: theme.radius.sm }], children: _jsx(NavIcon, { icon: Icon, size: 19, active: true, strokeWidth: 2, color: danger ? theme.color.red : theme.color['brand-ink'], activeColor: danger ? theme.color.red : theme.color['brand-ink'] }) })) : null), _jsxs(View, { style: styles.rowText, children: [_jsx(Text, { size: "body", weight: "bold", color: danger ? 'red' : 'ink', align: "start", lines: 1, children: title }), subtitle ? (_jsx(Text, { size: "caption", color: "muted", align: "start", lines: 2, children: subtitle })) : null] }), typeof meta === 'string' ? (_jsx(Text, { size: "caption", color: "muted2", children: meta })) : (meta), trailing, showChevron && !trailing && _jsx(Chevron, { size: 16, strokeWidth: 2.2, color: theme.color.muted2 })] }));
|
|
58
|
+
if (!onPress)
|
|
59
|
+
return _jsx(View, { style: [styles.row, style], children: body });
|
|
60
|
+
return (_jsx(Pressable, { accessibilityRole: "button", onPress: onPress, style: ({ pressed }) => [
|
|
61
|
+
styles.row,
|
|
62
|
+
pressed && { backgroundColor: danger ? theme.color['red-bg'] : theme.color.hover },
|
|
63
|
+
style,
|
|
64
|
+
], children: body }));
|
|
65
|
+
}
|
|
66
|
+
export function StepRow({ icon, title, why, done, action, onPress, style }) {
|
|
67
|
+
const { theme } = useFanar();
|
|
68
|
+
const brand = typeof icon === 'string' ? brandIconName(icon) : undefined;
|
|
69
|
+
const Lucide = typeof icon === 'string' ? undefined : icon;
|
|
70
|
+
const inner = (_jsxs(_Fragment, { children: [done ? (_jsx(View, { style: [
|
|
71
|
+
styles.stepIcon,
|
|
72
|
+
{ borderRadius: theme.radius.field, backgroundColor: theme.color.brand },
|
|
73
|
+
], children: _jsx(Check, { size: 17, strokeWidth: 3, color: "#ffffff" }) })) : brand ? (_jsx(QumraIcon, { name: brand, size: 34 })) : (_jsx(View, { style: [
|
|
74
|
+
styles.stepIcon,
|
|
75
|
+
{ borderRadius: theme.radius.field, backgroundColor: theme.color.soft },
|
|
76
|
+
], children: Lucide ? _jsx(Lucide, { size: 18, strokeWidth: 2, color: theme.color.brand }) : null })), _jsxs(View, { style: styles.rowText, children: [_jsx(Text, { size: 14.5, weight: "extraBold", color: done ? 'muted' : 'ink', align: "start", style: done ? styles.struck : { textAlign: 'left' }, children: title }), why ? (_jsx(Text, { size: 12.5, style: { textAlign: 'left' }, color: "muted", align: "start", leading: "relaxed", lines: 1, children: why })) : null] }), action] }));
|
|
77
|
+
if (!onPress)
|
|
78
|
+
return _jsx(View, { style: [styles.stepRow, style], children: inner });
|
|
79
|
+
return (_jsx(Pressable, { accessibilityRole: "button", accessibilityState: { checked: !!done }, onPress: onPress, style: ({ pressed }) => [styles.stepRow, pressed && { backgroundColor: theme.color.hover }, style], children: inner }));
|
|
80
|
+
}
|
|
81
|
+
const styles = StyleSheet.create({
|
|
82
|
+
flex1: { flex: 1 },
|
|
83
|
+
hero: { overflow: 'hidden', paddingVertical: 24, paddingHorizontal: 22, gap: 18 },
|
|
84
|
+
heroTop: { flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', gap: 20 },
|
|
85
|
+
heroText: { flex: 1, minWidth: 0, gap: 8 },
|
|
86
|
+
heroTag: {
|
|
87
|
+
alignSelf: 'flex-start',
|
|
88
|
+
flexDirection: 'row',
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
gap: 7,
|
|
91
|
+
paddingVertical: 5,
|
|
92
|
+
paddingHorizontal: 12,
|
|
93
|
+
backgroundColor: 'rgba(248,172,51,0.18)',
|
|
94
|
+
},
|
|
95
|
+
heroBody: { maxWidth: 520, textAlign: 'left' },
|
|
96
|
+
heroMetric: { alignItems: 'center', gap: 2 },
|
|
97
|
+
heroFoot: { flexDirection: 'row', alignItems: 'center', gap: 14, flexWrap: 'wrap' },
|
|
98
|
+
prompt: { overflow: 'hidden', padding: 20, gap: 14 },
|
|
99
|
+
promptHead: { flexDirection: 'row', alignItems: 'center', gap: 12 },
|
|
100
|
+
promptIcon: {
|
|
101
|
+
width: 40,
|
|
102
|
+
height: 40,
|
|
103
|
+
alignItems: 'center',
|
|
104
|
+
justifyContent: 'center',
|
|
105
|
+
backgroundColor: 'rgba(125,211,192,0.14)',
|
|
106
|
+
},
|
|
107
|
+
promptList: { gap: 7 },
|
|
108
|
+
promptRow: {
|
|
109
|
+
flexDirection: 'row',
|
|
110
|
+
alignItems: 'center',
|
|
111
|
+
gap: 10,
|
|
112
|
+
paddingVertical: 11,
|
|
113
|
+
paddingHorizontal: 13,
|
|
114
|
+
borderWidth: 1,
|
|
115
|
+
},
|
|
116
|
+
row: { flexDirection: 'row', alignItems: 'center', gap: 12, minHeight: 56, paddingHorizontal: 16, paddingVertical: 10 },
|
|
117
|
+
rowIcon: { width: 34, height: 34, alignItems: 'center', justifyContent: 'center' },
|
|
118
|
+
rowText: { flex: 1, minWidth: 0, gap: 2 },
|
|
119
|
+
stepRow: { flexDirection: 'row', alignItems: 'center', gap: 15, paddingVertical: 15, paddingHorizontal: 20 },
|
|
120
|
+
stepIcon: { width: 38, height: 38, alignItems: 'center', justifyContent: 'center' },
|
|
121
|
+
struck: { textDecorationLine: 'line-through', textAlign: 'left' },
|
|
122
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Text as RNText, type TextProps as RNTextProps } from 'react-native';
|
|
2
|
+
import { type Leading } from '../theme/theme';
|
|
3
|
+
import type { Weight } from '../theme/fonts';
|
|
4
|
+
import type { ColorToken, TextSize } from '../tokens';
|
|
5
|
+
export interface TextProps extends RNTextProps {
|
|
6
|
+
size?: TextSize | number;
|
|
7
|
+
weight?: Weight;
|
|
8
|
+
color?: ColorToken | 'white' | (string & {});
|
|
9
|
+
align?: 'start' | 'end' | 'center' | 'auto';
|
|
10
|
+
leading?: Leading | number;
|
|
11
|
+
numeric?: boolean;
|
|
12
|
+
lines?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const Text: import("react").ForwardRefExoticComponent<TextProps & import("react").RefAttributes<RNText>>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { Text as RNText } from 'react-native';
|
|
4
|
+
import { useFanar } from '../theme/context';
|
|
5
|
+
import { LEADING } from '../theme/theme';
|
|
6
|
+
export const Text = forwardRef(function Text({ size = 'body', weight = 'medium', color = 'ink', align, leading, numeric, lines, style, numberOfLines, ...rest }, ref) {
|
|
7
|
+
const { theme } = useFanar();
|
|
8
|
+
const fontSize = typeof size === 'number' ? size : theme.text[size];
|
|
9
|
+
const asked = typeof leading === 'number' ? leading : LEADING[leading ?? 'normal'];
|
|
10
|
+
const ratio = Math.max(asked, theme.minLeading);
|
|
11
|
+
const resolved = {
|
|
12
|
+
...theme.font(weight),
|
|
13
|
+
fontSize,
|
|
14
|
+
lineHeight: Math.round(fontSize * ratio),
|
|
15
|
+
color: theme.colorOf(color, color === 'white' ? '#ffffff' : theme.color.ink),
|
|
16
|
+
};
|
|
17
|
+
const resolvedAlign = align ?? 'start';
|
|
18
|
+
resolved.textAlign =
|
|
19
|
+
resolvedAlign === 'auto'
|
|
20
|
+
? 'auto'
|
|
21
|
+
: resolvedAlign === 'center'
|
|
22
|
+
? 'center'
|
|
23
|
+
: resolvedAlign === 'start'
|
|
24
|
+
? 'left'
|
|
25
|
+
: 'right';
|
|
26
|
+
if (numeric) {
|
|
27
|
+
resolved.fontVariant = ['tabular-nums'];
|
|
28
|
+
resolved.writingDirection = 'ltr';
|
|
29
|
+
}
|
|
30
|
+
return (_jsx(RNText, { ref: ref, numberOfLines: numberOfLines ?? lines, style: [resolved, style], ...rest }));
|
|
31
|
+
});
|