syzygy-ui-rn 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/package.json +56 -0
  4. package/src/__tests__/components.test.tsx +368 -0
  5. package/src/__tests__/tokens.test.ts +47 -0
  6. package/src/components/badges/Badge.tsx +74 -0
  7. package/src/components/buttons/DestructiveButton.tsx +82 -0
  8. package/src/components/buttons/GhostButton.tsx +77 -0
  9. package/src/components/buttons/IconButton.tsx +61 -0
  10. package/src/components/buttons/PrimaryButton.tsx +82 -0
  11. package/src/components/buttons/SecondaryButton.tsx +84 -0
  12. package/src/components/cards/CardView.tsx +45 -0
  13. package/src/components/display/Avatar.tsx +53 -0
  14. package/src/components/display/Chip.tsx +64 -0
  15. package/src/components/display/CountBadge.tsx +58 -0
  16. package/src/components/display/DividerLine.tsx +29 -0
  17. package/src/components/display/LazyImageView.tsx +57 -0
  18. package/src/components/display/ListRow.tsx +76 -0
  19. package/src/components/display/SectionHeader.tsx +55 -0
  20. package/src/components/display/StarRatingView.tsx +70 -0
  21. package/src/components/feedback/EmptyStateView.tsx +93 -0
  22. package/src/components/feedback/ErrorStateView.tsx +75 -0
  23. package/src/components/feedback/LoadingView.tsx +57 -0
  24. package/src/components/feedback/ProgressBar.tsx +42 -0
  25. package/src/components/feedback/PullToRefresh.tsx +38 -0
  26. package/src/components/feedback/ShimmerView.tsx +47 -0
  27. package/src/components/feedback/ToastView.tsx +71 -0
  28. package/src/components/inputs/CheckboxInput.tsx +73 -0
  29. package/src/components/inputs/Dropdown.tsx +110 -0
  30. package/src/components/inputs/QuantityStepper.tsx +81 -0
  31. package/src/components/inputs/RadioButtonInput.tsx +73 -0
  32. package/src/components/inputs/SearchInput.tsx +107 -0
  33. package/src/components/inputs/SecureInput.tsx +105 -0
  34. package/src/components/inputs/SegmentedControl.tsx +70 -0
  35. package/src/components/inputs/SliderInput.tsx +131 -0
  36. package/src/components/inputs/TextInput.tsx +90 -0
  37. package/src/components/inputs/ToggleSwitch.tsx +50 -0
  38. package/src/components/layout/KeyboardAvoidingScrollView.tsx +40 -0
  39. package/src/components/navigation/AppBar.tsx +53 -0
  40. package/src/components/navigation/BackButton.tsx +63 -0
  41. package/src/components/navigation/BottomNavigationBar.tsx +65 -0
  42. package/src/components/navigation/PagerView.tsx +40 -0
  43. package/src/components/navigation/TabBar.tsx +68 -0
  44. package/src/components/navigation/TabBarItem.ts +8 -0
  45. package/src/components/overlay/BottomSheet.tsx +64 -0
  46. package/src/components/overlay/CollapsibleView.tsx +82 -0
  47. package/src/components/overlay/ModalDialog.tsx +47 -0
  48. package/src/index.ts +54 -0
  49. package/src/tokens/colors.ts +84 -0
  50. package/src/tokens/index.ts +4 -0
  51. package/src/tokens/radius.ts +8 -0
  52. package/src/tokens/spacing.ts +10 -0
  53. package/src/tokens/typography.ts +27 -0
  54. package/src/transitions/navigationTransitions.ts +66 -0
@@ -0,0 +1,82 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ LayoutAnimation,
4
+ Platform,
5
+ StyleProp,
6
+ StyleSheet,
7
+ Text,
8
+ TouchableOpacity,
9
+ UIManager,
10
+ View,
11
+ ViewStyle,
12
+ useColorScheme,
13
+ } from 'react-native';
14
+
15
+ import { getColors } from '../../tokens/colors';
16
+ import { spacing } from '../../tokens/spacing';
17
+ import { fontSizes, fontWeights } from '../../tokens/typography';
18
+
19
+ if (
20
+ Platform.OS === 'android' &&
21
+ UIManager.setLayoutAnimationEnabledExperimental
22
+ ) {
23
+ UIManager.setLayoutAnimationEnabledExperimental(true);
24
+ }
25
+
26
+ export interface CollapsibleViewProps {
27
+ title: string;
28
+ initiallyExpanded?: boolean;
29
+ children: React.ReactNode;
30
+ style?: StyleProp<ViewStyle>;
31
+ }
32
+
33
+ /** An expandable/collapsible section (accordion) with a tappable header. */
34
+ export const CollapsibleView: React.FC<CollapsibleViewProps> = ({
35
+ title,
36
+ initiallyExpanded = false,
37
+ children,
38
+ style,
39
+ }) => {
40
+ const scheme = useColorScheme();
41
+ const colors = getColors(scheme);
42
+ const [expanded, setExpanded] = useState(initiallyExpanded);
43
+
44
+ const toggle = () => {
45
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
46
+ setExpanded((prev) => !prev);
47
+ };
48
+
49
+ return (
50
+ <View style={[styles.container, { backgroundColor: colors.surface }, style]}>
51
+ <TouchableOpacity
52
+ onPress={toggle}
53
+ accessibilityRole="button"
54
+ accessibilityLabel={`${title}, ${expanded ? 'expanded' : 'collapsed'}`}
55
+ style={styles.header}
56
+ >
57
+ <Text style={{ color: colors.textPrimary, fontSize: fontSizes.lg, fontWeight: fontWeights.semibold }}>
58
+ {title}
59
+ </Text>
60
+ <Text style={{ color: colors.textSecondary }}>{expanded ? '⌃' : '⌄'}</Text>
61
+ </TouchableOpacity>
62
+ {expanded ? <View style={styles.content}>{children}</View> : null}
63
+ </View>
64
+ );
65
+ };
66
+
67
+ const styles = StyleSheet.create({
68
+ container: {
69
+ overflow: 'hidden',
70
+ },
71
+ header: {
72
+ minHeight: 44,
73
+ flexDirection: 'row',
74
+ alignItems: 'center',
75
+ justifyContent: 'space-between',
76
+ paddingHorizontal: spacing.md,
77
+ },
78
+ content: {
79
+ paddingHorizontal: spacing.md,
80
+ paddingBottom: spacing.md,
81
+ },
82
+ });
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { Modal, Pressable, StyleSheet, useColorScheme } from 'react-native';
3
+
4
+ import { getColors } from '../../tokens/colors';
5
+ import { radius } from '../../tokens/radius';
6
+ import { spacing } from '../../tokens/spacing';
7
+
8
+ export interface ModalDialogProps {
9
+ visible: boolean;
10
+ onDismiss: () => void;
11
+ children: React.ReactNode;
12
+ }
13
+
14
+ /**
15
+ * A centered dialog card over a dimmed scrim, wrapping React Native's core
16
+ * `Modal`. Named `ModalDialog` (not `Modal`) to avoid ambiguity with the
17
+ * `Modal` import from `react-native`.
18
+ */
19
+ export const ModalDialog: React.FC<ModalDialogProps> = ({ visible, onDismiss, children }) => {
20
+ const scheme = useColorScheme();
21
+ const colors = getColors(scheme);
22
+
23
+ return (
24
+ <Modal visible={visible} transparent animationType="fade" onRequestClose={onDismiss}>
25
+ <Pressable style={[styles.backdrop, { backgroundColor: colors.overlay }]} onPress={onDismiss}>
26
+ <Pressable style={[styles.card, { backgroundColor: colors.surface }]}>
27
+ {children}
28
+ </Pressable>
29
+ </Pressable>
30
+ </Modal>
31
+ );
32
+ };
33
+
34
+ const styles = StyleSheet.create({
35
+ backdrop: {
36
+ flex: 1,
37
+ alignItems: 'center',
38
+ justifyContent: 'center',
39
+ padding: spacing.lg,
40
+ },
41
+ card: {
42
+ borderRadius: radius.lg,
43
+ padding: spacing.lg,
44
+ width: '100%',
45
+ maxWidth: 400,
46
+ },
47
+ });
package/src/index.ts ADDED
@@ -0,0 +1,54 @@
1
+ export * from './tokens';
2
+
3
+ export * from './components/buttons/PrimaryButton';
4
+ export * from './components/buttons/SecondaryButton';
5
+ export * from './components/buttons/DestructiveButton';
6
+ export * from './components/buttons/GhostButton';
7
+ export * from './components/buttons/IconButton';
8
+
9
+ export * from './components/inputs/TextInput';
10
+ export * from './components/inputs/SecureInput';
11
+ export * from './components/inputs/SearchInput';
12
+ export * from './components/inputs/ToggleSwitch';
13
+ export * from './components/inputs/CheckboxInput';
14
+ export * from './components/inputs/RadioButtonInput';
15
+ export * from './components/inputs/SliderInput';
16
+ export * from './components/inputs/Dropdown';
17
+ export * from './components/inputs/SegmentedControl';
18
+ export * from './components/inputs/QuantityStepper';
19
+
20
+ export * from './components/feedback/LoadingView';
21
+ export * from './components/feedback/EmptyStateView';
22
+ export * from './components/feedback/ToastView';
23
+ export * from './components/feedback/ShimmerView';
24
+ export * from './components/feedback/ProgressBar';
25
+ export * from './components/feedback/PullToRefresh';
26
+ export * from './components/feedback/ErrorStateView';
27
+
28
+ export * from './components/cards/CardView';
29
+
30
+ export * from './components/badges/Badge';
31
+
32
+ export * from './components/display/Avatar';
33
+ export * from './components/display/DividerLine';
34
+ export * from './components/display/Chip';
35
+ export * from './components/display/ListRow';
36
+ export * from './components/display/SectionHeader';
37
+ export * from './components/display/LazyImageView';
38
+ export * from './components/display/StarRatingView';
39
+ export * from './components/display/CountBadge';
40
+
41
+ export * from './components/overlay/ModalDialog';
42
+ export * from './components/overlay/BottomSheet';
43
+ export * from './components/overlay/CollapsibleView';
44
+
45
+ export * from './components/navigation/BackButton';
46
+ export * from './components/navigation/TabBarItem';
47
+ export * from './components/navigation/TabBar';
48
+ export * from './components/navigation/BottomNavigationBar';
49
+ export * from './components/navigation/AppBar';
50
+ export * from './components/navigation/PagerView';
51
+
52
+ export * from './components/layout/KeyboardAvoidingScrollView';
53
+
54
+ export * from './transitions/navigationTransitions';
@@ -0,0 +1,84 @@
1
+ export type ColorScheme = 'light' | 'dark';
2
+
3
+ export interface ColorPalette {
4
+ background: string;
5
+ surface: string;
6
+ surfaceAlt: string;
7
+ border: string;
8
+ textPrimary: string;
9
+ textSecondary: string;
10
+ textInverse: string;
11
+ primary: string;
12
+ primaryText: string;
13
+ secondary: string;
14
+ secondaryText: string;
15
+ destructive: string;
16
+ destructiveText: string;
17
+ success: string;
18
+ successText: string;
19
+ warning: string;
20
+ warningText: string;
21
+ error: string;
22
+ errorText: string;
23
+ disabled: string;
24
+ disabledText: string;
25
+ overlay: string;
26
+ }
27
+
28
+ export const lightColors: ColorPalette = {
29
+ background: '#FFFFFF',
30
+ surface: '#F5F5F7',
31
+ surfaceAlt: '#ECECEE',
32
+ border: '#D8D8DC',
33
+ textPrimary: '#1A1A1E',
34
+ textSecondary: '#6B6B72',
35
+ textInverse: '#FFFFFF',
36
+ primary: '#2F6FED',
37
+ primaryText: '#FFFFFF',
38
+ secondary: '#ECECEE',
39
+ secondaryText: '#1A1A1E',
40
+ destructive: '#E5484D',
41
+ destructiveText: '#FFFFFF',
42
+ success: '#1F9254',
43
+ successText: '#FFFFFF',
44
+ warning: '#B7791F',
45
+ warningText: '#FFFFFF',
46
+ error: '#E5484D',
47
+ errorText: '#FFFFFF',
48
+ disabled: '#D8D8DC',
49
+ disabledText: '#9A9AA1',
50
+ overlay: 'rgba(0,0,0,0.4)',
51
+ };
52
+
53
+ export const darkColors: ColorPalette = {
54
+ background: '#101012',
55
+ surface: '#1C1C1F',
56
+ surfaceAlt: '#27272B',
57
+ border: '#3A3A3F',
58
+ textPrimary: '#F5F5F7',
59
+ textSecondary: '#A5A5AC',
60
+ textInverse: '#101012',
61
+ primary: '#5C8DF6',
62
+ primaryText: '#101012',
63
+ secondary: '#27272B',
64
+ secondaryText: '#F5F5F7',
65
+ destructive: '#F2555A',
66
+ destructiveText: '#101012',
67
+ success: '#3DD68C',
68
+ successText: '#101012',
69
+ warning: '#E3A008',
70
+ warningText: '#101012',
71
+ error: '#F2555A',
72
+ errorText: '#101012',
73
+ disabled: '#3A3A3F',
74
+ disabledText: '#6B6B72',
75
+ overlay: 'rgba(0,0,0,0.6)',
76
+ };
77
+
78
+ export const colors: Record<ColorScheme, ColorPalette> = {
79
+ light: lightColors,
80
+ dark: darkColors,
81
+ };
82
+
83
+ export const getColors = (scheme: ColorScheme | 'unspecified' | null | undefined): ColorPalette =>
84
+ colors[scheme === 'dark' ? 'dark' : 'light'];
@@ -0,0 +1,4 @@
1
+ export * from './colors';
2
+ export * from './typography';
3
+ export * from './spacing';
4
+ export * from './radius';
@@ -0,0 +1,8 @@
1
+ export const radius = {
2
+ sm: 4,
3
+ md: 8,
4
+ lg: 16,
5
+ full: 9999,
6
+ } as const;
7
+
8
+ export type RadiusToken = keyof typeof radius;
@@ -0,0 +1,10 @@
1
+ export const spacing = {
2
+ xs: 4,
3
+ sm: 8,
4
+ md: 16,
5
+ lg: 24,
6
+ xl: 32,
7
+ xxl: 48,
8
+ } as const;
9
+
10
+ export type SpacingToken = keyof typeof spacing;
@@ -0,0 +1,27 @@
1
+ export const fontSizes = {
2
+ xs: 12,
3
+ sm: 14,
4
+ md: 16,
5
+ lg: 18,
6
+ xl: 22,
7
+ xxl: 28,
8
+ } as const;
9
+
10
+ export const fontWeights = {
11
+ regular: '400' as const,
12
+ medium: '500' as const,
13
+ semibold: '600' as const,
14
+ bold: '700' as const,
15
+ };
16
+
17
+ export const lineHeights = {
18
+ xs: 16,
19
+ sm: 20,
20
+ md: 22,
21
+ lg: 24,
22
+ xl: 28,
23
+ xxl: 34,
24
+ } as const;
25
+
26
+ export type FontSizeToken = keyof typeof fontSizes;
27
+ export type LineHeightToken = keyof typeof lineHeights;
@@ -0,0 +1,66 @@
1
+ import { Animated } from 'react-native';
2
+
3
+ /**
4
+ * Reusable `Animated`-driven transition helpers for common
5
+ * navigation/presentation motion. Each returns an interpolated style object
6
+ * to spread onto an `Animated.View`, driven by an `Animated.Value` that the
7
+ * caller animates from 0 to 1 with `Animated.timing`.
8
+ */
9
+
10
+ export type HorizontalDirection = 'leftToRight' | 'rightToLeft';
11
+
12
+ /** Slides content in/out horizontally as `progress` animates 0 -> 1. */
13
+ export const slideTransition = (progress: Animated.Value, direction: HorizontalDirection, width: number) => {
14
+ const sign = direction === 'leftToRight' ? 1 : -1;
15
+ return {
16
+ opacity: progress,
17
+ transform: [
18
+ {
19
+ translateX: progress.interpolate({
20
+ inputRange: [0, 1],
21
+ outputRange: [sign * width, 0],
22
+ }),
23
+ },
24
+ ],
25
+ };
26
+ };
27
+
28
+ /** A plain cross-fade as `progress` animates 0 -> 1. */
29
+ export const crossFadeTransition = (progress: Animated.Value) => ({
30
+ opacity: progress,
31
+ });
32
+
33
+ export type VerticalDirection = 'topToBottom' | 'bottomToTop';
34
+
35
+ /** Slides content in/out vertically as `progress` animates 0 -> 1. */
36
+ export const slideVerticalTransition = (
37
+ progress: Animated.Value,
38
+ direction: VerticalDirection,
39
+ height: number
40
+ ) => {
41
+ const sign = direction === 'topToBottom' ? -1 : 1;
42
+ return {
43
+ opacity: progress,
44
+ transform: [
45
+ {
46
+ translateY: progress.interpolate({
47
+ inputRange: [0, 1],
48
+ outputRange: [sign * height, 0],
49
+ }),
50
+ },
51
+ ],
52
+ };
53
+ };
54
+
55
+ /** A slide-up-and-fade transition suited to modal/sheet presentation. */
56
+ export const modalPresentationTransition = (progress: Animated.Value, height: number) => ({
57
+ opacity: progress,
58
+ transform: [
59
+ {
60
+ translateY: progress.interpolate({
61
+ inputRange: [0, 1],
62
+ outputRange: [height, 0],
63
+ }),
64
+ },
65
+ ],
66
+ });