@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
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @qumra/fanar-native
|
|
2
|
+
|
|
3
|
+
مكتبة مكوّنات قمرة لـReact Native — النظير النيتف لـ`@qumra/fanar`.
|
|
4
|
+
|
|
5
|
+
نفس التوكنز، ونفس أسماء المكوّنات، ونفس أسماء الخصائص — بتنفيذ مكتوب
|
|
6
|
+
لـReact Native لا مترجَم عنه. بلا تبعيّات وقت تشغيل.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import { FanarProvider, Screen, Card, Button, Text } from '@qumra/fanar-native'
|
|
10
|
+
|
|
11
|
+
<FanarProvider mode="system" lang="ar">
|
|
12
|
+
<Screen>
|
|
13
|
+
<Card padded>
|
|
14
|
+
<Text size="h3" weight="black">مرحباً</Text>
|
|
15
|
+
<Button variant="primary" onPress={save}>احفظ</Button>
|
|
16
|
+
</Card>
|
|
17
|
+
</Screen>
|
|
18
|
+
</FanarProvider>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## التركيب
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm i @qumra/fanar-native
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
نظائر مطلوبة: `react` · `react-native` · `lucide-react-native` ·
|
|
28
|
+
`react-native-safe-area-context` · `react-native-svg`.
|
|
29
|
+
|
|
30
|
+
ثمّ:
|
|
31
|
+
|
|
32
|
+
1. ركّب `<SafeAreaProvider>` عند جذر التطبيق.
|
|
33
|
+
2. ركّب `<FanarProvider>` تحته.
|
|
34
|
+
3. ضع ملفّات الأسكندرية الثمانية في `src/assets/fonts` وشغّل
|
|
35
|
+
`npx react-native-asset`. (أو مرّر `fonts={SYSTEM_FONT}` وخذ خطّ
|
|
36
|
+
النظام.)
|
|
37
|
+
|
|
38
|
+
`DrawerMenu` **محتوى** لا مُلاحِظ: يرسم الأقسام ويصرف الاختيار، ولا
|
|
39
|
+
يعرف شيئاً عن `@react-navigation/drawer`. مرّره إلى `drawerContent`.
|
|
40
|
+
|
|
41
|
+
## الاتجاه
|
|
42
|
+
|
|
43
|
+
`FanarProvider` يضع `direction` على View جذرٍ **`flex: 1`**، فكلّ ما تحته
|
|
44
|
+
— الخصائص المنطقية، و`flexDirection: 'row'`، ومحاذاة النصّ، والأسهم
|
|
45
|
+
المعكوسة — يتبع `lang` فوراً، بلا إعادة تشغيل. لذلك:
|
|
46
|
+
|
|
47
|
+
- ضعه **فوق** `NavigationContainer`، لتقع الشاشات كلّها تحته.
|
|
48
|
+
- لا تستدعِ `I18nManager.forceRTL(true)` لتطبيقٍ يبدّل لغته.
|
|
49
|
+
- هيدر `react-navigation` وإيماءة الرجوع يقرآن `I18nManager` مباشرةً:
|
|
50
|
+
مرّر `<NavigationContainer direction={dir}>` (React Navigation 7)،
|
|
51
|
+
و`dir` من `useLang()`.
|
|
52
|
+
|
|
53
|
+
## ما في الحزمة
|
|
54
|
+
|
|
55
|
+
**الأساس** — `FanarProvider` · `useFanar` · `useTheme` · `useLang` ·
|
|
56
|
+
`buildTheme` · `TOKENS` · `DARK_TOKENS` · `icon()`
|
|
57
|
+
|
|
58
|
+
**النصّ والأزرار** — `Text` · `Button` · `IconButton`
|
|
59
|
+
|
|
60
|
+
**العرض** — `Badge` · `Counter` · `Avatar` · `Card` · `CardHeader` ·
|
|
61
|
+
`PageHeader` · `StatCard` · `Progress` · `ProgressSteps` · `Skeleton` ·
|
|
62
|
+
`Divider`
|
|
63
|
+
|
|
64
|
+
**الحقول** — `Field` · `Input` · `SearchInput` · `Textarea` · `Select` ·
|
|
65
|
+
`Checkbox` · `Radio` · `RadioGroup` · `Switch` · `NumberStepper`
|
|
66
|
+
|
|
67
|
+
**التنقّل** — `Tabs` · `FilterChips` · `Accordion` · `Timeline` ·
|
|
68
|
+
`Stepper`
|
|
69
|
+
|
|
70
|
+
**الطبقات** — `Sheet` · `SheetRow` · `Modal` · `Alert` · `EmptyState` ·
|
|
71
|
+
`toast` · `ToastHost` · `Tooltip`
|
|
72
|
+
|
|
73
|
+
**القشرة** — `Screen` · `AppHeader` · `BottomTabBar` · `DrawerMenu`
|
|
74
|
+
|
|
75
|
+
**أسطح قمرة** — `Hero` · `PromptCard` · `ListRow` · `StepRow` ·
|
|
76
|
+
`RuleGrid` · `Glow`
|
|
77
|
+
|
|
78
|
+
**المال والتقييم** — `Money` · `MoneyProvider` · `useCurrency` ·
|
|
79
|
+
`Stars` · `RatingInput`
|
|
80
|
+
|
|
81
|
+
**اللغة** — `translate` · `phrase` · `DICT`
|
|
82
|
+
|
|
83
|
+
`@qumra/fanar-native/tokens` — القيم وحدها، بلا مكوّنات.
|
package/lib/art.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ImageSourcePropType } from 'react-native';
|
|
2
|
+
declare const BRAND: Record<string, ImageSourcePropType>;
|
|
3
|
+
declare const ART: Record<string, ImageSourcePropType>;
|
|
4
|
+
export type BrandName = keyof typeof BRAND;
|
|
5
|
+
export type ArtName = keyof typeof ART;
|
|
6
|
+
export declare function art(name: string): ImageSourcePropType;
|
|
7
|
+
export declare function hasArt(name: string): boolean;
|
|
8
|
+
export { BRAND, ART };
|
package/lib/art.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const BRAND = {
|
|
2
|
+
facebook: require('./assets/brand/facebook.png'),
|
|
3
|
+
instagram: require('./assets/brand/instagram.png'),
|
|
4
|
+
tiktok: require('./assets/brand/tiktok.png'),
|
|
5
|
+
x: require('./assets/brand/x.png'),
|
|
6
|
+
twitter: require('./assets/brand/x.png'),
|
|
7
|
+
youtube: require('./assets/brand/youtube.png'),
|
|
8
|
+
snapchat: require('./assets/brand/snapchat.png'),
|
|
9
|
+
whatsapp: require('./assets/brand/whatsapp.png'),
|
|
10
|
+
vodafone: require('./assets/brand/vodafone.png'),
|
|
11
|
+
orange: require('./assets/brand/orange.png'),
|
|
12
|
+
etisalat: require('./assets/brand/etisalat.png'),
|
|
13
|
+
we: require('./assets/brand/we.png'),
|
|
14
|
+
'app-store': require('./assets/brand/app-store.png'),
|
|
15
|
+
'google-play': require('./assets/brand/google-play.png'),
|
|
16
|
+
};
|
|
17
|
+
const ART = {
|
|
18
|
+
avatar: require('./assets/art/avatar.png'),
|
|
19
|
+
empty: require('./assets/art/empty.png'),
|
|
20
|
+
'image-placeholder': require('./assets/art/image-placeholder.png'),
|
|
21
|
+
'plan-update': require('./assets/art/plan-update.png'),
|
|
22
|
+
};
|
|
23
|
+
export function art(name) {
|
|
24
|
+
return BRAND[name] ?? ART[name] ?? ART.avatar;
|
|
25
|
+
}
|
|
26
|
+
export function hasArt(name) {
|
|
27
|
+
return name in BRAND || name in ART;
|
|
28
|
+
}
|
|
29
|
+
export { BRAND, ART };
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type PressableProps, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import type { ColorToken } from '../tokens';
|
|
4
|
+
import type { LucideIcon } from '../icons';
|
|
5
|
+
export type ButtonVariant = 'primary' | 'accent' | 'soft' | 'outline' | 'ghost' | 'danger' | 'link';
|
|
6
|
+
export type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
7
|
+
export interface ButtonProps extends Omit<PressableProps, 'style' | 'children'> {
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
variant?: ButtonVariant;
|
|
10
|
+
size?: ButtonSize;
|
|
11
|
+
icon?: LucideIcon;
|
|
12
|
+
iconEnd?: LucideIcon;
|
|
13
|
+
loading?: boolean;
|
|
14
|
+
block?: boolean;
|
|
15
|
+
style?: StyleProp<ViewStyle>;
|
|
16
|
+
}
|
|
17
|
+
export declare function Button({ children, variant, size, icon: Icon, iconEnd: IconEnd, loading, block, disabled, style, accessibilityLabel, ...rest }: ButtonProps): import("react").JSX.Element;
|
|
18
|
+
export interface IconButtonProps extends Omit<PressableProps, 'style' | 'children'> {
|
|
19
|
+
icon: LucideIcon;
|
|
20
|
+
size?: number;
|
|
21
|
+
iconSize?: number;
|
|
22
|
+
color?: ColorToken | 'white';
|
|
23
|
+
background?: ColorToken | 'transparent';
|
|
24
|
+
active?: boolean;
|
|
25
|
+
style?: StyleProp<ViewStyle>;
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
}
|
|
28
|
+
export declare function IconButton({ icon: Icon, size, iconSize, color, background, active, disabled, style, children, ...rest }: IconButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ActivityIndicator, Animated, Pressable, StyleSheet, View, } from 'react-native';
|
|
3
|
+
import { Text } from './Text';
|
|
4
|
+
import { useFanar } from '../theme/context';
|
|
5
|
+
import { usePressScale } from '../lib/motion';
|
|
6
|
+
const SIZES = {
|
|
7
|
+
sm: { height: 36, padding: 14, gap: 6, text: 13, radius: 7, weight: 'bold', spinner: 14, square: 36 },
|
|
8
|
+
md: { height: 44, padding: 16, gap: 8, text: 14, radius: 9, weight: 'bold', spinner: 15, square: 40 },
|
|
9
|
+
lg: { height: 48, padding: 20, gap: 8, text: 15, radius: 12, weight: 'bold', spinner: 17, square: 48 },
|
|
10
|
+
xl: { height: 56, padding: 22, gap: 16, text: 20, radius: 9, weight: 'extraBold', spinner: 20, square: 56 },
|
|
11
|
+
};
|
|
12
|
+
const VARIANTS = {
|
|
13
|
+
primary: { bg: 'brand', border: 'brand', fg: 'white', pressed: 'brand-hover' },
|
|
14
|
+
accent: { bg: 'amber', border: 'amber', fg: 'amber-900', pressed: 'amber-hover' },
|
|
15
|
+
soft: { bg: 'soft', border: 'soft2', fg: 'brand-ink', pressed: 'soft2' },
|
|
16
|
+
outline: { bg: 'surface', border: 'border', fg: 'ink2', pressed: 'hover' },
|
|
17
|
+
ghost: { bg: 'transparent', border: 'transparent', fg: 'ink2', pressed: 'hover' },
|
|
18
|
+
danger: { bg: 'danger', border: 'danger', fg: 'white', pressed: 'danger-hover' },
|
|
19
|
+
link: { bg: 'transparent', border: 'transparent', fg: 'brand-ink', pressed: 'transparent', underline: true, bare: true },
|
|
20
|
+
};
|
|
21
|
+
export function Button({ children, variant = 'primary', size = 'md', icon: Icon, iconEnd: IconEnd, loading = false, block = false, disabled, style, accessibilityLabel, ...rest }) {
|
|
22
|
+
const { theme } = useFanar();
|
|
23
|
+
const s = SIZES[size];
|
|
24
|
+
const v = VARIANTS[variant];
|
|
25
|
+
const off = disabled || loading;
|
|
26
|
+
const press = usePressScale(!off);
|
|
27
|
+
const fg = theme.colorOf(v.fg, v.fg === 'white' ? '#ffffff' : theme.color.ink2);
|
|
28
|
+
const iconOnly = !children && (!!Icon || !!IconEnd);
|
|
29
|
+
const base = {
|
|
30
|
+
flexDirection: 'row',
|
|
31
|
+
alignItems: 'center',
|
|
32
|
+
justifyContent: 'center',
|
|
33
|
+
gap: s.gap,
|
|
34
|
+
height: v.bare ? undefined : s.height,
|
|
35
|
+
paddingHorizontal: v.bare || iconOnly ? 0 : s.padding,
|
|
36
|
+
width: iconOnly ? s.square : block ? '100%' : undefined,
|
|
37
|
+
borderRadius: v.bare ? 0 : s.radius,
|
|
38
|
+
borderWidth: v.bare ? 0 : 1,
|
|
39
|
+
opacity: off ? 0.5 : 1,
|
|
40
|
+
};
|
|
41
|
+
const iconSize = Math.round(s.text * 1.05);
|
|
42
|
+
return (_jsx(Animated.View, { style: [press.style, block && styles.block, style], children: _jsx(Pressable, { accessibilityRole: "button", accessibilityState: { disabled: !!off, busy: loading }, accessibilityLabel: accessibilityLabel, disabled: off, onPressIn: press.onPressIn, onPressOut: press.onPressOut, style: ({ pressed }) => [
|
|
43
|
+
base,
|
|
44
|
+
{
|
|
45
|
+
backgroundColor: theme.colorOf(pressed && !off ? v.pressed : v.bg, 'transparent'),
|
|
46
|
+
borderColor: theme.colorOf(v.border, 'transparent'),
|
|
47
|
+
},
|
|
48
|
+
], ...rest, children: loading ? (_jsx(ActivityIndicator, { size: "small", color: fg })) : (_jsxs(_Fragment, { children: [Icon && _jsx(Icon, { size: iconSize, strokeWidth: 2.2, color: fg }), children != null &&
|
|
49
|
+
(typeof children === 'string' || typeof children === 'number' ? (_jsx(Text, { size: s.text, weight: s.weight, color: v.fg, lines: 1, leading: "tight", style: v.underline ? styles.underline : undefined, children: children })) : (_jsx(View, { style: styles.slot, children: children }))), IconEnd && _jsx(IconEnd, { size: iconSize, strokeWidth: 2.2, color: fg })] })) }) }));
|
|
50
|
+
}
|
|
51
|
+
const styles = StyleSheet.create({
|
|
52
|
+
block: { width: '100%' },
|
|
53
|
+
slot: { flexDirection: 'row', alignItems: 'center' },
|
|
54
|
+
underline: { textDecorationLine: 'underline' },
|
|
55
|
+
});
|
|
56
|
+
export function IconButton({ icon: Icon, size = 36, iconSize, color = 'ink2', background = 'transparent', active = false, disabled, style, children, ...rest }) {
|
|
57
|
+
const { theme } = useFanar();
|
|
58
|
+
const press = usePressScale(!disabled);
|
|
59
|
+
const fg = active ? theme.color.brand : theme.colorOf(color, theme.color.ink2);
|
|
60
|
+
return (_jsx(Animated.View, { style: [press.style, style], children: _jsxs(Pressable, { accessibilityRole: "button", accessibilityState: { disabled: !!disabled, selected: active }, disabled: disabled, onPressIn: press.onPressIn, onPressOut: press.onPressOut, hitSlop: size < 44 ? Math.ceil((44 - size) / 2) : undefined, style: ({ pressed }) => [
|
|
61
|
+
{
|
|
62
|
+
width: size,
|
|
63
|
+
height: size,
|
|
64
|
+
borderRadius: size / 2,
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
justifyContent: 'center',
|
|
67
|
+
opacity: disabled ? 0.5 : 1,
|
|
68
|
+
backgroundColor: active
|
|
69
|
+
? theme.color.soft
|
|
70
|
+
: pressed
|
|
71
|
+
? theme.color.hover
|
|
72
|
+
: theme.colorOf(background, 'transparent'),
|
|
73
|
+
},
|
|
74
|
+
], ...rest, children: [_jsx(Icon, { size: iconSize ?? Math.round(size * 0.55), strokeWidth: 2.2, color: fg }), children] }) }));
|
|
75
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { type LucideIcon } from '../icons';
|
|
4
|
+
export interface AccordionItem {
|
|
5
|
+
key: string;
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
icon?: LucideIcon;
|
|
9
|
+
content: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
export interface AccordionProps {
|
|
12
|
+
items: AccordionItem[];
|
|
13
|
+
defaultOpen?: string | null;
|
|
14
|
+
single?: boolean;
|
|
15
|
+
style?: StyleProp<ViewStyle>;
|
|
16
|
+
}
|
|
17
|
+
export declare function Accordion({ items, defaultOpen, single, style }: AccordionProps): import("react").JSX.Element;
|
|
18
|
+
export interface TimelineStep {
|
|
19
|
+
key: string;
|
|
20
|
+
title: string;
|
|
21
|
+
time?: string;
|
|
22
|
+
note?: string;
|
|
23
|
+
done?: boolean;
|
|
24
|
+
active?: boolean;
|
|
25
|
+
icon?: LucideIcon;
|
|
26
|
+
}
|
|
27
|
+
export declare function Timeline({ steps, nowLabel, style }: {
|
|
28
|
+
steps: TimelineStep[];
|
|
29
|
+
nowLabel?: string;
|
|
30
|
+
style?: StyleProp<ViewStyle>;
|
|
31
|
+
}): import("react").JSX.Element;
|
|
32
|
+
export interface StepperStep {
|
|
33
|
+
key: string;
|
|
34
|
+
label: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function Stepper({ steps, current, onPick, style, }: {
|
|
37
|
+
steps: StepperStep[];
|
|
38
|
+
current: number;
|
|
39
|
+
onPick?: (index: number) => void;
|
|
40
|
+
style?: StyleProp<ViewStyle>;
|
|
41
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { LayoutAnimation, Platform, Pressable, StyleSheet, UIManager, View } from 'react-native';
|
|
4
|
+
import { Text } from './Text';
|
|
5
|
+
import { useFanar } from '../theme/context';
|
|
6
|
+
import { Check, ChevronDown } from '../icons';
|
|
7
|
+
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental)
|
|
8
|
+
UIManager.setLayoutAnimationEnabledExperimental(true);
|
|
9
|
+
export function Accordion({ items, defaultOpen = null, single = true, style }) {
|
|
10
|
+
const { theme } = useFanar();
|
|
11
|
+
const [open, setOpen] = useState(defaultOpen ? [defaultOpen] : []);
|
|
12
|
+
const toggle = (key) => {
|
|
13
|
+
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
|
14
|
+
setOpen((prev) => prev.includes(key) ? prev.filter((k) => k !== key) : single ? [key] : [...prev, key]);
|
|
15
|
+
};
|
|
16
|
+
return (_jsx(View, { style: [
|
|
17
|
+
{
|
|
18
|
+
backgroundColor: theme.color.surface,
|
|
19
|
+
borderWidth: 1,
|
|
20
|
+
borderColor: theme.color.line,
|
|
21
|
+
borderRadius: theme.radius.card,
|
|
22
|
+
overflow: 'hidden',
|
|
23
|
+
},
|
|
24
|
+
style,
|
|
25
|
+
], children: items.map((it, i) => {
|
|
26
|
+
const on = open.includes(it.key);
|
|
27
|
+
const Icon = it.icon;
|
|
28
|
+
return (_jsxs(View, { style: i < items.length - 1
|
|
29
|
+
? { borderBottomWidth: 1, borderBottomColor: theme.color.line }
|
|
30
|
+
: undefined, children: [_jsxs(Pressable, { accessibilityRole: "button", accessibilityState: { expanded: on }, onPress: () => toggle(it.key), style: ({ pressed }) => [styles.accHead, pressed && { backgroundColor: theme.color.hover }], children: [Icon && (_jsx(View, { style: [styles.accIcon, { backgroundColor: theme.color.soft, borderRadius: theme.radius.sm }], children: _jsx(Icon, { size: 16, strokeWidth: 2.2, color: theme.color['brand-ink'] }) })), _jsxs(View, { style: styles.accText, children: [_jsx(Text, { size: "body", weight: "extraBold", align: "start", children: it.title }), it.description ? (_jsx(Text, { size: "caption", color: "muted", align: "start", children: it.description })) : null] }), _jsx(ChevronDown, { size: 17, strokeWidth: 2.2, color: theme.color.muted2, style: on ? styles.flipped : undefined })] }), on && (_jsx(View, { style: styles.accBody, children: typeof it.content === 'string' ? (_jsx(Text, { size: "ui", color: "ink2", align: "start", leading: 1.85, children: it.content })) : (it.content) }))] }, it.key));
|
|
31
|
+
}) }));
|
|
32
|
+
}
|
|
33
|
+
export function Timeline({ steps, nowLabel, style }) {
|
|
34
|
+
const { theme } = useFanar();
|
|
35
|
+
return (_jsx(View, { style: style, children: steps.map((s, i) => {
|
|
36
|
+
const last = i === steps.length - 1;
|
|
37
|
+
const on = s.done || s.active;
|
|
38
|
+
const Icon = s.icon;
|
|
39
|
+
return (_jsxs(View, { style: styles.tlRow, children: [_jsxs(View, { style: styles.tlRail, children: [_jsx(View, { style: [
|
|
40
|
+
styles.tlDot,
|
|
41
|
+
{
|
|
42
|
+
borderRadius: theme.radius.full,
|
|
43
|
+
backgroundColor: s.done ? theme.color.brand : theme.color.surface,
|
|
44
|
+
borderColor: on ? theme.color.brand : theme.color.border,
|
|
45
|
+
},
|
|
46
|
+
], children: s.done ? (_jsx(Check, { size: 14, strokeWidth: 3, color: "#ffffff" })) : Icon ? (_jsx(Icon, { size: 13, strokeWidth: 2.2, color: s.active ? theme.color['brand-ink'] : theme.color.muted2 })) : (_jsx(View, { style: {
|
|
47
|
+
width: 8,
|
|
48
|
+
height: 8,
|
|
49
|
+
borderRadius: 4,
|
|
50
|
+
backgroundColor: s.active ? theme.color.brand : theme.color.border,
|
|
51
|
+
} })) }), !last && (_jsx(View, { style: [
|
|
52
|
+
styles.tlLine,
|
|
53
|
+
{ backgroundColor: s.done ? theme.color.brand : theme.color.line },
|
|
54
|
+
] }))] }), _jsxs(View, { style: [styles.tlBody, last && styles.tlBodyLast], children: [_jsxs(View, { style: styles.tlHead, children: [_jsx(Text, { size: "body", weight: "extraBold", color: on ? 'ink' : 'muted2', align: "start", children: s.title }), s.active && nowLabel ? (_jsx(View, { style: [styles.tlNow, { backgroundColor: theme.color.soft, borderRadius: theme.radius.full }], children: _jsx(Text, { size: "micro", weight: "extraBold", color: "brand-ink", children: nowLabel }) })) : null, s.time ? (_jsx(Text, { size: "micro", color: "muted2", numeric: true, children: s.time })) : null] }), s.note ? (_jsx(Text, { size: "caption", color: "muted", align: "start", leading: "relaxed", children: s.note })) : null] })] }, s.key));
|
|
55
|
+
}) }));
|
|
56
|
+
}
|
|
57
|
+
export function Stepper({ steps, current, onPick, style, }) {
|
|
58
|
+
const { theme } = useFanar();
|
|
59
|
+
return (_jsx(View, { style: [styles.stList, style], children: steps.map((s, i) => {
|
|
60
|
+
const done = i < current;
|
|
61
|
+
const on = i <= current;
|
|
62
|
+
const pickable = !!onPick && done;
|
|
63
|
+
return (_jsxs(View, { style: styles.stItem, children: [_jsxs(View, { style: styles.stRow, children: [_jsx(View, { style: [
|
|
64
|
+
styles.stLine,
|
|
65
|
+
{ backgroundColor: i === 0 ? 'transparent' : done || i === current ? theme.color.brand : theme.color.line },
|
|
66
|
+
] }), _jsx(Pressable, { accessibilityRole: pickable ? 'button' : 'text', accessibilityState: { selected: i === current, disabled: !pickable }, disabled: !pickable, onPress: () => onPick?.(i), style: [
|
|
67
|
+
styles.stDot,
|
|
68
|
+
{
|
|
69
|
+
borderRadius: theme.radius.full,
|
|
70
|
+
backgroundColor: on ? theme.color.brand : theme.color.surface,
|
|
71
|
+
borderColor: on ? theme.color.brand : theme.color.border,
|
|
72
|
+
},
|
|
73
|
+
], children: done ? (_jsx(Check, { size: 15, strokeWidth: 3, color: "#ffffff" })) : (_jsx(Text, { size: "caption", weight: "extraBold", color: on ? 'white' : 'muted', numeric: true, children: i + 1 })) }), _jsx(View, { style: [
|
|
74
|
+
styles.stLine,
|
|
75
|
+
{ backgroundColor: i === steps.length - 1 ? 'transparent' : done ? theme.color.brand : theme.color.line },
|
|
76
|
+
] })] }), _jsx(Text, { size: "caption", weight: "bold", color: on ? 'ink' : 'muted', align: "center", leading: "tight", lines: 2, children: s.label })] }, s.key));
|
|
77
|
+
}) }));
|
|
78
|
+
}
|
|
79
|
+
const styles = StyleSheet.create({
|
|
80
|
+
accHead: { flexDirection: 'row', alignItems: 'center', gap: 12, paddingHorizontal: 16, paddingVertical: 14 },
|
|
81
|
+
accIcon: { width: 32, height: 32, alignItems: 'center', justifyContent: 'center' },
|
|
82
|
+
accText: { flex: 1, minWidth: 0, gap: 2 },
|
|
83
|
+
accBody: { paddingHorizontal: 16, paddingBottom: 16, paddingTop: 2 },
|
|
84
|
+
flipped: { transform: [{ rotate: '180deg' }] },
|
|
85
|
+
tlRow: { flexDirection: 'row', gap: 12 },
|
|
86
|
+
tlRail: { alignItems: 'center' },
|
|
87
|
+
tlDot: { width: 28, height: 28, borderWidth: 2, alignItems: 'center', justifyContent: 'center' },
|
|
88
|
+
tlLine: { width: 2, flex: 1, minHeight: 32 },
|
|
89
|
+
tlBody: { flex: 1, minWidth: 0, gap: 2, paddingBottom: 20 },
|
|
90
|
+
tlBodyLast: { paddingBottom: 0 },
|
|
91
|
+
tlHead: { flexDirection: 'row', alignItems: 'center', gap: 10, flexWrap: 'wrap' },
|
|
92
|
+
tlNow: { paddingHorizontal: 8, paddingVertical: 2 },
|
|
93
|
+
stList: { flexDirection: 'row', alignItems: 'flex-start', gap: 4 },
|
|
94
|
+
stItem: { flex: 1, alignItems: 'center', gap: 8, minWidth: 0 },
|
|
95
|
+
stRow: { flexDirection: 'row', alignItems: 'center', gap: 4, width: '100%' },
|
|
96
|
+
stLine: { height: 2, flex: 1, borderRadius: 1 },
|
|
97
|
+
stDot: { width: 32, height: 32, borderWidth: 2, alignItems: 'center', justifyContent: 'center' },
|
|
98
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type ImageSourcePropType, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { type Tone } from '../lib/tone';
|
|
4
|
+
import type { ColorToken } from '../tokens';
|
|
5
|
+
import type { LucideIcon } from '../icons';
|
|
6
|
+
export interface BadgeProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
tone?: Tone;
|
|
9
|
+
solid?: boolean;
|
|
10
|
+
size?: 'sm' | 'md';
|
|
11
|
+
dot?: boolean;
|
|
12
|
+
icon?: LucideIcon;
|
|
13
|
+
style?: StyleProp<ViewStyle>;
|
|
14
|
+
}
|
|
15
|
+
export declare function Badge({ children, tone, solid, size, dot, icon: Icon, style }: BadgeProps): import("react").JSX.Element;
|
|
16
|
+
export declare function Counter({ value, style }: {
|
|
17
|
+
value: number | string;
|
|
18
|
+
style?: StyleProp<ViewStyle>;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
21
|
+
export declare function initials(name: string): string;
|
|
22
|
+
export interface AvatarProps {
|
|
23
|
+
name?: string;
|
|
24
|
+
source?: ImageSourcePropType | string;
|
|
25
|
+
size?: AvatarSize;
|
|
26
|
+
square?: boolean;
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
}
|
|
29
|
+
export declare function Avatar({ name, source, size, square, style }: AvatarProps): import("react").JSX.Element;
|
|
30
|
+
export interface CardProps {
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
padded?: boolean;
|
|
33
|
+
flat?: boolean;
|
|
34
|
+
onPress?: () => void;
|
|
35
|
+
style?: StyleProp<ViewStyle>;
|
|
36
|
+
}
|
|
37
|
+
export declare function Card({ children, padded, flat, onPress, style }: CardProps): import("react").JSX.Element;
|
|
38
|
+
export interface CardHeaderProps {
|
|
39
|
+
title: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
actions?: ReactNode;
|
|
42
|
+
style?: StyleProp<ViewStyle>;
|
|
43
|
+
}
|
|
44
|
+
export declare function CardHeader({ title, description, actions, style }: CardHeaderProps): import("react").JSX.Element;
|
|
45
|
+
export interface PageHeaderProps {
|
|
46
|
+
title: string;
|
|
47
|
+
hint?: string;
|
|
48
|
+
actions?: ReactNode;
|
|
49
|
+
style?: StyleProp<ViewStyle>;
|
|
50
|
+
}
|
|
51
|
+
export declare function PageHeader({ title, hint, actions, style }: PageHeaderProps): import("react").JSX.Element;
|
|
52
|
+
export interface StatCardProps {
|
|
53
|
+
label: string;
|
|
54
|
+
value: string | number;
|
|
55
|
+
hint?: string;
|
|
56
|
+
icon?: LucideIcon;
|
|
57
|
+
tone?: Tone;
|
|
58
|
+
onPress?: () => void;
|
|
59
|
+
style?: StyleProp<ViewStyle>;
|
|
60
|
+
}
|
|
61
|
+
export declare function StatCard({ label, value, hint, icon: Icon, tone, onPress, style }: StatCardProps): import("react").JSX.Element;
|
|
62
|
+
export interface ProgressProps {
|
|
63
|
+
value: number;
|
|
64
|
+
max?: number;
|
|
65
|
+
label?: string;
|
|
66
|
+
meta?: string;
|
|
67
|
+
tone?: Tone;
|
|
68
|
+
style?: StyleProp<ViewStyle>;
|
|
69
|
+
}
|
|
70
|
+
export declare function Progress({ value, max, label, meta, tone, style }: ProgressProps): import("react").JSX.Element;
|
|
71
|
+
export declare function ProgressSteps({ total, done, fill, track, style, }: {
|
|
72
|
+
total: number;
|
|
73
|
+
done: number;
|
|
74
|
+
fill?: ColorToken;
|
|
75
|
+
track?: string;
|
|
76
|
+
style?: StyleProp<ViewStyle>;
|
|
77
|
+
}): import("react").JSX.Element;
|
|
78
|
+
export interface SkeletonProps {
|
|
79
|
+
variant?: 'text' | 'block' | 'circle';
|
|
80
|
+
width?: number | `${number}%`;
|
|
81
|
+
height?: number;
|
|
82
|
+
style?: StyleProp<ViewStyle>;
|
|
83
|
+
}
|
|
84
|
+
export declare function Skeleton({ variant, width, height, style }: SkeletonProps): import("react").JSX.Element;
|
|
85
|
+
export declare function Divider({ inset, style }: {
|
|
86
|
+
inset?: number;
|
|
87
|
+
style?: StyleProp<ViewStyle>;
|
|
88
|
+
}): import("react").JSX.Element;
|