@umituz/react-native-design-system 4.24.0 → 4.25.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "4.24.0",
3
+ "version": "4.25.1",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,147 @@
1
+ import React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+ import { useAppDesignTokens } from "../theme";
4
+ import { AtomicText } from "../atoms/AtomicText";
5
+ import { AtomicIcon } from "../atoms/icon/AtomicIcon";
6
+ import { Carousel } from "./Carousel";
7
+ import type { CarouselItem as CarouselItemType } from "./types";
8
+
9
+ export interface BannerItem {
10
+ id: string;
11
+ title: string;
12
+ subtitle: string;
13
+ backgroundColor: string;
14
+ action: () => void;
15
+ }
16
+
17
+ interface BannerCarouselProps {
18
+ items: BannerItem[];
19
+ }
20
+
21
+ export const BannerCarousel: React.FC<BannerCarouselProps> = ({ items }) => {
22
+ const tokens = useAppDesignTokens();
23
+
24
+ const renderBannerItem = (
25
+ item: CarouselItemType<BannerItem>,
26
+ _index: number,
27
+ ) => {
28
+ const bannerData = item.data;
29
+ const spacing = tokens.spacing;
30
+
31
+ return (
32
+ <View
33
+ style={[
34
+ styles.banner,
35
+ {
36
+ backgroundColor: bannerData.backgroundColor,
37
+ padding: spacing.xl,
38
+ minHeight: 240,
39
+ borderRadius: 28,
40
+ },
41
+ ]}
42
+ >
43
+ <View style={styles.bannerContent}>
44
+ <View
45
+ style={[styles.bannerTextContainer, { marginRight: spacing.lg }]}
46
+ >
47
+ <AtomicText
48
+ type="headlineLarge"
49
+ style={[
50
+ styles.bannerTitle,
51
+ {
52
+ color: tokens.colors.textInverse,
53
+ marginBottom: spacing.sm,
54
+ fontSize: 32,
55
+ lineHeight: 40,
56
+ letterSpacing: -0.5,
57
+ },
58
+ ]}
59
+ >
60
+ {bannerData.title}
61
+ </AtomicText>
62
+ <AtomicText
63
+ type="bodyLarge"
64
+ style={[
65
+ styles.bannerSubtitle,
66
+ {
67
+ color: tokens.colors.textInverse,
68
+ opacity: 0.95,
69
+ fontSize: 16,
70
+ lineHeight: 24,
71
+ },
72
+ ]}
73
+ >
74
+ {bannerData.subtitle}
75
+ </AtomicText>
76
+ </View>
77
+ <View style={styles.bannerIconContainer}>
78
+ <View
79
+ style={[
80
+ styles.bannerIconCircle,
81
+ {
82
+ width: 56,
83
+ height: 56,
84
+ borderRadius: 28,
85
+ backgroundColor: "rgba(255, 255, 255, 0.3)",
86
+ borderWidth: 1,
87
+ borderColor: "rgba(255, 255, 255, 0.4)",
88
+ },
89
+ ]}
90
+ >
91
+ <AtomicIcon
92
+ name="arrow-forward-outline"
93
+ size="xl"
94
+ color="onSurface"
95
+ />
96
+ </View>
97
+ </View>
98
+ </View>
99
+ </View>
100
+ );
101
+ };
102
+
103
+ const carouselItems: CarouselItemType<BannerItem>[] = items.map((item) => ({
104
+ id: item.id,
105
+ data: item,
106
+ onPress: item.action,
107
+ }));
108
+
109
+ return (
110
+ <Carousel
111
+ items={carouselItems}
112
+ renderItem={renderBannerItem}
113
+ spacing={16}
114
+ showDots={true}
115
+ pagingEnabled={true}
116
+ />
117
+ );
118
+ };
119
+
120
+ const styles = StyleSheet.create({
121
+ banner: {
122
+ justifyContent: "center",
123
+ overflow: "hidden",
124
+ },
125
+ bannerContent: {
126
+ flexDirection: "row",
127
+ alignItems: "center",
128
+ justifyContent: "space-between",
129
+ },
130
+ bannerTextContainer: {
131
+ flex: 1,
132
+ },
133
+ bannerTitle: {
134
+ fontWeight: "800",
135
+ },
136
+ bannerSubtitle: {
137
+ fontWeight: "500",
138
+ },
139
+ bannerIconContainer: {
140
+ alignItems: "center",
141
+ justifyContent: "center",
142
+ },
143
+ bannerIconCircle: {
144
+ alignItems: "center",
145
+ justifyContent: "center",
146
+ },
147
+ });
@@ -0,0 +1,73 @@
1
+ import React from "react";
2
+ import { View, StyleSheet, ViewStyle } from "react-native";
3
+ import { useAppDesignTokens } from "../theme";
4
+ import { CarouselScrollView } from "./CarouselScrollView";
5
+ import { CarouselDots } from "./CarouselDots";
6
+ import { CarouselItem } from "./CarouselItem";
7
+ import { useCarouselScroll } from "./useCarouselScroll";
8
+ import { calculateItemWidth } from "./carouselCalculations";
9
+ import type { CarouselProps, CarouselItem as CarouselItemType } from "./types";
10
+
11
+ export const Carousel = <T,>({
12
+ items,
13
+ renderItem,
14
+ itemWidth,
15
+ spacing = 16,
16
+ onIndexChange,
17
+ showDots = true,
18
+ pagingEnabled = true,
19
+ style,
20
+ }: CarouselProps<T> & { style?: ViewStyle }) => {
21
+ const tokens = useAppDesignTokens();
22
+ const calculatedItemWidth = itemWidth || calculateItemWidth(spacing);
23
+
24
+ const pageWidth = calculatedItemWidth + spacing;
25
+
26
+ const { currentIndex, handleScroll } = useCarouselScroll({
27
+ itemWidth: pageWidth,
28
+ onIndexChange,
29
+ });
30
+
31
+ if (items.length === 0) {
32
+ return null;
33
+ }
34
+
35
+ return (
36
+ <View style={[styles.container, style]}>
37
+ <CarouselScrollView
38
+ onScroll={handleScroll}
39
+ pagingEnabled={pagingEnabled}
40
+ spacing={spacing}
41
+ itemWidth={calculatedItemWidth}
42
+ >
43
+ {items.map((item, index) => (
44
+ <CarouselItem
45
+ key={item.id}
46
+ item={item}
47
+ itemWidth={calculatedItemWidth}
48
+ renderContent={(itemData) => renderItem(itemData, index)}
49
+ style={
50
+ index < items.length - 1 ? { marginRight: spacing } : undefined
51
+ }
52
+ />
53
+ ))}
54
+ </CarouselScrollView>
55
+
56
+ {showDots && items.length > 1 && (
57
+ <CarouselDots
58
+ count={items.length}
59
+ currentIndex={currentIndex}
60
+ activeColor={tokens.colors.primary}
61
+ inactiveColor={tokens.colors.border}
62
+ />
63
+ )}
64
+ </View>
65
+ );
66
+ };
67
+
68
+ const styles = StyleSheet.create({
69
+ container: {
70
+ marginTop: 8,
71
+ marginBottom: 8,
72
+ },
73
+ });
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+ import { useAppDesignTokens } from "../theme";
4
+ import type { CarouselDotsProps } from "./types";
5
+
6
+ export const CarouselDots: React.FC<CarouselDotsProps> = ({
7
+ count,
8
+ currentIndex,
9
+ activeColor,
10
+ inactiveColor,
11
+ }) => {
12
+ const tokens = useAppDesignTokens();
13
+
14
+ if (count <= 1) {
15
+ return null;
16
+ }
17
+
18
+ const activeDotColor = activeColor || tokens.colors.primary;
19
+ const inactiveDotColor = inactiveColor || tokens.colors.border;
20
+
21
+ return (
22
+ <View style={styles.container}>
23
+ {Array.from({ length: count }).map((_, index) => (
24
+ <View
25
+ key={index}
26
+ style={[
27
+ styles.dot,
28
+ {
29
+ backgroundColor:
30
+ index === currentIndex ? activeDotColor : inactiveDotColor,
31
+ },
32
+ ]}
33
+ />
34
+ ))}
35
+ </View>
36
+ );
37
+ };
38
+
39
+ const styles = StyleSheet.create({
40
+ container: {
41
+ flexDirection: "row",
42
+ justifyContent: "center",
43
+ alignItems: "center",
44
+ marginTop: 12,
45
+ gap: 8,
46
+ },
47
+ dot: {
48
+ width: 8,
49
+ height: 8,
50
+ borderRadius: 4,
51
+ },
52
+ });
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import { TouchableOpacity, StyleSheet, ViewStyle } from "react-native";
3
+ import type { CarouselItem as CarouselItemType } from "./types";
4
+
5
+ interface CarouselItemProps<T = unknown> {
6
+ item: CarouselItemType<T>;
7
+ itemWidth: number;
8
+ renderContent: (item: CarouselItemType<T>) => React.ReactNode;
9
+ style?: ViewStyle;
10
+ activeOpacity?: number;
11
+ }
12
+
13
+ export const CarouselItem = <T,>({
14
+ item,
15
+ itemWidth,
16
+ renderContent,
17
+ style,
18
+ activeOpacity = 0.9,
19
+ }: CarouselItemProps<T>) => {
20
+ return (
21
+ <TouchableOpacity
22
+ activeOpacity={activeOpacity}
23
+ onPress={item.onPress}
24
+ style={[styles.container, { width: itemWidth }, style]}
25
+ >
26
+ {renderContent(item)}
27
+ </TouchableOpacity>
28
+ );
29
+ };
30
+
31
+ const styles = StyleSheet.create({
32
+ container: {
33
+ // Container styles handled by width prop
34
+ },
35
+ });
@@ -0,0 +1,56 @@
1
+ import React from "react";
2
+ import {
3
+ ScrollView,
4
+ StyleSheet,
5
+ ViewStyle,
6
+ NativeScrollEvent,
7
+ NativeSyntheticEvent,
8
+ } from "react-native";
9
+
10
+ interface CarouselScrollViewProps {
11
+ itemWidth: number;
12
+ onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
13
+ children: React.ReactNode;
14
+ pagingEnabled?: boolean;
15
+ style?: ViewStyle;
16
+ contentContainerStyle?: ViewStyle;
17
+ spacing?: number;
18
+ }
19
+
20
+ export const CarouselScrollView: React.FC<CarouselScrollViewProps> = ({
21
+ onScroll,
22
+ children,
23
+ pagingEnabled = true,
24
+ style,
25
+ contentContainerStyle,
26
+ spacing = 0,
27
+ }) => {
28
+ return (
29
+ <ScrollView
30
+ horizontal
31
+ pagingEnabled={pagingEnabled}
32
+ showsHorizontalScrollIndicator={false}
33
+ onScroll={onScroll}
34
+ scrollEventThrottle={16}
35
+ style={[styles.scrollView, style]}
36
+ contentContainerStyle={[
37
+ styles.scrollContent,
38
+ pagingEnabled
39
+ ? { paddingHorizontal: spacing }
40
+ : { paddingLeft: spacing, paddingRight: spacing },
41
+ contentContainerStyle,
42
+ ]}
43
+ >
44
+ {children}
45
+ </ScrollView>
46
+ );
47
+ };
48
+
49
+ const styles = StyleSheet.create({
50
+ scrollView: {
51
+ marginHorizontal: 0,
52
+ },
53
+ scrollContent: {
54
+ paddingHorizontal: 0,
55
+ },
56
+ });
@@ -0,0 +1,18 @@
1
+ import { Dimensions } from "react-native";
2
+
3
+ const { width: SCREEN_WIDTH } = Dimensions.get("window");
4
+
5
+ export const calculateItemWidth = (padding: number = 16): number => {
6
+ return SCREEN_WIDTH - padding * 2;
7
+ };
8
+
9
+ export const calculateIndexFromScroll = (
10
+ scrollPosition: number,
11
+ itemWidth: number,
12
+ ): number => {
13
+ return Math.round(scrollPosition / itemWidth);
14
+ };
15
+
16
+ export const getScreenWidth = (): number => {
17
+ return SCREEN_WIDTH;
18
+ };
@@ -0,0 +1,21 @@
1
+ export type {
2
+ CarouselItem,
3
+ CarouselProps,
4
+ CarouselScrollProps,
5
+ CarouselDotsProps,
6
+ } from "./types";
7
+
8
+ export { useCarouselScroll } from "./useCarouselScroll";
9
+
10
+ export {
11
+ calculateItemWidth,
12
+ calculateIndexFromScroll,
13
+ getScreenWidth,
14
+ } from "./carouselCalculations";
15
+
16
+ export { CarouselDots } from "./CarouselDots";
17
+ export { CarouselItem as CarouselItemComponent } from "./CarouselItem";
18
+ export { CarouselScrollView } from "./CarouselScrollView";
19
+ export { Carousel } from "./Carousel";
20
+ export { BannerCarousel } from "./BannerCarousel";
21
+ export type { BannerItem } from "./BannerCarousel";
@@ -0,0 +1,31 @@
1
+ import type { NativeSyntheticEvent, NativeScrollEvent } from "react-native";
2
+
3
+ export interface CarouselItem<T = unknown> {
4
+ id: string;
5
+ data: T;
6
+ onPress?: () => void;
7
+ }
8
+
9
+ export interface CarouselProps<T = unknown> {
10
+ items: CarouselItem<T>[];
11
+ renderItem: (item: CarouselItem<T>, index: number) => React.ReactNode;
12
+ itemWidth?: number;
13
+ spacing?: number;
14
+ onIndexChange?: (index: number) => void;
15
+ showDots?: boolean;
16
+ pagingEnabled?: boolean;
17
+ }
18
+
19
+ export interface CarouselScrollProps {
20
+ itemWidth: number;
21
+ onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
22
+ children: React.ReactNode;
23
+ pagingEnabled?: boolean;
24
+ }
25
+
26
+ export interface CarouselDotsProps {
27
+ count: number;
28
+ currentIndex: number;
29
+ activeColor?: string;
30
+ inactiveColor?: string;
31
+ }
@@ -0,0 +1,33 @@
1
+ import { useState, useCallback } from "react";
2
+ import { NativeScrollEvent, NativeSyntheticEvent } from "react-native";
3
+ import { calculateIndexFromScroll } from "./carouselCalculations";
4
+
5
+ interface UseCarouselScrollOptions {
6
+ itemWidth: number;
7
+ onIndexChange?: (index: number) => void;
8
+ }
9
+
10
+ export const useCarouselScroll = ({
11
+ itemWidth,
12
+ onIndexChange,
13
+ }: UseCarouselScrollOptions) => {
14
+ const [currentIndex, setCurrentIndex] = useState(0);
15
+
16
+ const handleScroll = useCallback(
17
+ (event: NativeSyntheticEvent<NativeScrollEvent>) => {
18
+ const scrollPosition = event.nativeEvent.contentOffset.x;
19
+ const newIndex = calculateIndexFromScroll(scrollPosition, itemWidth);
20
+
21
+ if (newIndex !== currentIndex) {
22
+ setCurrentIndex(newIndex);
23
+ onIndexChange?.(newIndex);
24
+ }
25
+ },
26
+ [itemWidth, currentIndex, onIndexChange],
27
+ );
28
+
29
+ return {
30
+ currentIndex,
31
+ handleScroll,
32
+ };
33
+ };
package/src/index.ts CHANGED
@@ -128,11 +128,6 @@ export * from "./onboarding";
128
128
  // =============================================================================
129
129
  export * from "./filesystem";
130
130
 
131
- // =============================================================================
132
- // MEDIA EXPORTS
133
- // =============================================================================
134
- export * from "./media";
135
-
136
131
  // =============================================================================
137
132
  // TANSTACK EXPORTS
138
133
  // =============================================================================
@@ -152,3 +147,8 @@ export * from "./init";
152
147
  // GALLERY EXPORTS
153
148
  // =============================================================================
154
149
  export * from "./gallery";
150
+
151
+ // =============================================================================
152
+ // CAROUSEL EXPORTS
153
+ // =============================================================================
154
+ export * from "./carousel";
@@ -64,7 +64,6 @@ export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
64
64
  title={action.label}
65
65
  variant={action.style === 'destructive' ? 'danger' : action.style === 'secondary' ? 'secondary' : 'primary'}
66
66
  onPress={async () => {
67
- // BUG FIX: Execute action BEFORE closing (was backwards)
68
67
  await action.onPress();
69
68
  if (action.closeOnPress ?? true) {
70
69
  handleClose();
@@ -14,7 +14,6 @@ export function useAlertAutoDismiss(
14
14
  onDismiss: () => void
15
15
  ) {
16
16
  useEffect(() => {
17
- // BUG FIX: Check dismissible flag before auto-dismissing
18
17
  if (!alert.dismissible) return;
19
18
 
20
19
  const duration = alert.duration ?? DEFAULT_ALERT_DURATION;