@umituz/react-native-onboarding 3.6.9 → 3.6.11

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-onboarding",
3
- "version": "3.6.9",
3
+ "version": "3.6.11",
4
4
  "description": "Advanced onboarding flow for React Native apps with personalization questions, theme-aware colors, animations, and customizable slides. SOLID, DRY, KISS principles applied.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -86,6 +86,22 @@ export interface OnboardingSlide {
86
86
  */
87
87
  backgroundImage?: any;
88
88
 
89
+ /**
90
+ * Optional multiple background images (URLs or require paths)
91
+ * Displayed in a collage/grid pattern behind content
92
+ * If provided, takes precedence over single backgroundImage
93
+ */
94
+ backgroundImages?: any[];
95
+
96
+ /**
97
+ * Layout pattern for multiple background images
98
+ * 'grid' - Equal sized grid
99
+ * 'masonry' - Pinterest-style masonry layout
100
+ * 'collage' - Random sizes and positions
101
+ * Default: 'grid'
102
+ */
103
+ backgroundImagesLayout?: 'grid' | 'masonry' | 'collage';
104
+
89
105
  /**
90
106
  * Optional background video (URL or require path)
91
107
  * Plays in loop behind content
package/src/index.ts CHANGED
@@ -68,7 +68,9 @@ export {
68
68
  export { OnboardingScreen, type OnboardingScreenProps } from "./presentation/screens/OnboardingScreen";
69
69
  export { OnboardingHeader, type OnboardingHeaderProps } from "./presentation/components/OnboardingHeader";
70
70
  export { OnboardingFooter, type OnboardingFooterProps } from "./presentation/components/OnboardingFooter";
71
- export { OnboardingThemeProvider, type OnboardingThemeProviderProps, useOnboardingTheme } from "./presentation/providers/OnboardingThemeProvider";
71
+ export { OnboardingProvider, type OnboardingProviderProps, useOnboardingProvider } from "./presentation/providers/OnboardingProvider";
72
+ export { BackgroundImageCollage } from "./presentation/components/BackgroundImageCollage";
73
+ export type { OnboardingTheme, OnboardingColors } from "./presentation/types/OnboardingTheme";
72
74
 
73
75
  // Export OnboardingSlide component
74
76
  // Note: TypeScript doesn't allow exporting both a type and a value with the same name
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Background Image Collage Component
3
+ * Displays multiple images in various layout patterns
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { View, StyleSheet, Dimensions } from "react-native";
8
+ import { Image } from "expo-image";
9
+
10
+ const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get("window");
11
+
12
+ interface BackgroundImageCollageProps {
13
+ images: any[];
14
+ layout?: 'grid' | 'masonry' | 'collage';
15
+ }
16
+
17
+ interface ImageLayout {
18
+ source: any;
19
+ style: {
20
+ position: 'absolute';
21
+ top: number;
22
+ left: number;
23
+ width: number;
24
+ height: number;
25
+ };
26
+ }
27
+
28
+ export const BackgroundImageCollage: React.FC<BackgroundImageCollageProps> = ({
29
+ images,
30
+ layout = 'grid',
31
+ }) => {
32
+ const imageLayouts = useMemo(() => {
33
+ if (images.length === 0) return [];
34
+
35
+ switch (layout) {
36
+ case 'grid':
37
+ return generateGridLayout(images);
38
+ case 'masonry':
39
+ return generateMasonryLayout(images);
40
+ case 'collage':
41
+ return generateCollageLayout(images);
42
+ default:
43
+ return generateGridLayout(images);
44
+ }
45
+ }, [images, layout]);
46
+
47
+ return (
48
+ <View style={StyleSheet.absoluteFill} pointerEvents="none">
49
+ {imageLayouts.map((item, index) => (
50
+ <Image
51
+ key={index}
52
+ source={item.source}
53
+ style={item.style}
54
+ contentFit="cover"
55
+ transition={300}
56
+ />
57
+ ))}
58
+ </View>
59
+ );
60
+ };
61
+
62
+ const generateGridLayout = (images: any[]): ImageLayout[] => {
63
+ const count = images.length;
64
+ const cols = Math.ceil(Math.sqrt(count));
65
+ const rows = Math.ceil(count / cols);
66
+ const cellWidth = SCREEN_WIDTH / cols;
67
+ const cellHeight = SCREEN_HEIGHT / rows;
68
+
69
+ return images.map((source, index) => {
70
+ const col = index % cols;
71
+ const row = Math.floor(index / cols);
72
+
73
+ return {
74
+ source,
75
+ style: {
76
+ position: 'absolute' as const,
77
+ left: col * cellWidth,
78
+ top: row * cellHeight,
79
+ width: cellWidth,
80
+ height: cellHeight,
81
+ },
82
+ };
83
+ });
84
+ };
85
+
86
+ const generateMasonryLayout = (images: any[]): ImageLayout[] => {
87
+ const cols = 3;
88
+ const colWidth = SCREEN_WIDTH / cols;
89
+ const columnHeights = new Array(cols).fill(0);
90
+
91
+ return images.map((source, _index) => {
92
+ const shortestCol = columnHeights.indexOf(Math.min(...columnHeights));
93
+ const height = colWidth * (0.7 + Math.random() * 0.6);
94
+
95
+ const layout: ImageLayout = {
96
+ source,
97
+ style: {
98
+ position: 'absolute' as const,
99
+ left: shortestCol * colWidth,
100
+ top: columnHeights[shortestCol],
101
+ width: colWidth,
102
+ height,
103
+ },
104
+ };
105
+
106
+ columnHeights[shortestCol] += height;
107
+ return layout;
108
+ });
109
+ };
110
+
111
+ const generateCollageLayout = (images: any[]): ImageLayout[] => {
112
+ const layouts: ImageLayout[] = [];
113
+ const minSize = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * 0.25;
114
+ const maxSize = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) * 0.45;
115
+
116
+ images.forEach((source, _index) => {
117
+ const size = minSize + Math.random() * (maxSize - minSize);
118
+ const maxX = SCREEN_WIDTH - size;
119
+ const maxY = SCREEN_HEIGHT - size;
120
+
121
+ let attempts = 0;
122
+ let overlaps = true;
123
+ let left = 0;
124
+ let top = 0;
125
+
126
+ while (overlaps && attempts < 50) {
127
+ left = Math.random() * maxX;
128
+ top = Math.random() * maxY;
129
+ overlaps = false;
130
+
131
+ for (const existing of layouts) {
132
+ if (
133
+ left < existing.style.left + existing.style.width &&
134
+ left + size > existing.style.left &&
135
+ top < existing.style.top + existing.style.height &&
136
+ top + size > existing.style.top
137
+ ) {
138
+ overlaps = true;
139
+ break;
140
+ }
141
+ }
142
+
143
+ attempts++;
144
+ }
145
+
146
+ layouts.push({
147
+ source,
148
+ style: {
149
+ position: 'absolute' as const,
150
+ left,
151
+ top,
152
+ width: size,
153
+ height: size,
154
+ },
155
+ });
156
+ });
157
+
158
+ return layouts;
159
+ };
@@ -9,6 +9,7 @@ import { LinearGradient } from "expo-linear-gradient";
9
9
  import { Image } from "expo-image";
10
10
  import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
11
11
  import { BackgroundVideo } from "./BackgroundVideo";
12
+ import { BackgroundImageCollage } from "./BackgroundImageCollage";
12
13
 
13
14
  interface OnboardingBackgroundProps {
14
15
  currentSlide: OnboardingSlide | undefined;
@@ -35,6 +36,15 @@ export const OnboardingBackground: React.FC<OnboardingBackgroundProps> = ({
35
36
  );
36
37
  }
37
38
 
39
+ if (currentSlide.backgroundImages && currentSlide.backgroundImages.length > 0) {
40
+ return (
41
+ <BackgroundImageCollage
42
+ images={currentSlide.backgroundImages}
43
+ layout={currentSlide.backgroundImagesLayout || 'grid'}
44
+ />
45
+ );
46
+ }
47
+
38
48
  if (currentSlide.backgroundImage) {
39
49
  return (
40
50
  <Image
@@ -3,7 +3,7 @@ import { View, StyleSheet, TouchableOpacity } from "react-native";
3
3
  import { useSafeAreaInsets } from "react-native-safe-area-context";
4
4
  import { useLocalization } from "@umituz/react-native-localization";
5
5
  import { AtomicText } from "@umituz/react-native-design-system";
6
- import { useOnboardingTheme } from "../providers/OnboardingThemeProvider";
6
+ import { useOnboardingProvider } from "../providers/OnboardingProvider";
7
7
 
8
8
  export interface OnboardingFooterProps {
9
9
  currentIndex: number;
@@ -32,7 +32,9 @@ export const OnboardingFooter = ({
32
32
  }: OnboardingFooterProps) => {
33
33
  const insets = useSafeAreaInsets();
34
34
  const { t } = useLocalization();
35
- const { colors } = useOnboardingTheme();
35
+ const {
36
+ theme: { colors },
37
+ } = useOnboardingProvider();
36
38
 
37
39
  const buttonText = isLastSlide
38
40
  ? getStartedButtonText || t("onboarding.getStarted")
@@ -2,7 +2,7 @@ import React from "react";
2
2
  import { View, TouchableOpacity, StyleSheet } from "react-native";
3
3
  import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
4
4
  import { useLocalization } from "@umituz/react-native-localization";
5
- import { useOnboardingTheme } from "../providers/OnboardingThemeProvider";
5
+ import { useOnboardingProvider } from "../providers/OnboardingProvider";
6
6
 
7
7
  export interface OnboardingHeaderProps {
8
8
  isFirstSlide: boolean;
@@ -22,7 +22,9 @@ export const OnboardingHeader = ({
22
22
  skipButtonText,
23
23
  }: OnboardingHeaderProps) => {
24
24
  const { t } = useLocalization();
25
- const { colors } = useOnboardingTheme();
25
+ const {
26
+ theme: { colors },
27
+ } = useOnboardingProvider();
26
28
 
27
29
  const skipText = skipButtonText || t("onboarding.skip");
28
30
 
@@ -53,7 +53,9 @@ export const OnboardingScreenContent = ({
53
53
  });
54
54
 
55
55
  const hasMedia =
56
- !!currentSlide?.backgroundImage || !!currentSlide?.backgroundVideo;
56
+ !!currentSlide?.backgroundImage ||
57
+ !!currentSlide?.backgroundVideo ||
58
+ (!!currentSlide?.backgroundImages && currentSlide.backgroundImages.length > 0);
57
59
  const overlayOpacity = currentSlide?.overlayOpacity ?? 0.5;
58
60
  const effectivelyUseGradient = useGradient || hasMedia;
59
61
 
@@ -8,7 +8,7 @@ import { View, StyleSheet } from "react-native";
8
8
  import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
9
9
  import type { OnboardingSlide as OnboardingSlideType } from "../../domain/entities/OnboardingSlide";
10
10
  import { BaseSlide } from "./BaseSlide";
11
- import { useOnboardingTheme } from "../providers/OnboardingThemeProvider";
11
+ import { useOnboardingProvider } from "../providers/OnboardingProvider";
12
12
 
13
13
  export interface OnboardingSlideProps {
14
14
  slide: OnboardingSlideType;
@@ -19,7 +19,9 @@ export const OnboardingSlide = ({
19
19
  slide,
20
20
  variant = "default",
21
21
  }: OnboardingSlideProps) => {
22
- const { colors } = useOnboardingTheme();
22
+ const {
23
+ theme: { colors },
24
+ } = useOnboardingProvider();
23
25
 
24
26
  const shouldShowIcon = slide.icon && !slide.hideIcon;
25
27
  const isEmoji = slide.iconType === 'emoji';
@@ -10,7 +10,7 @@ import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
10
10
  import { QuestionSlideHeader } from "./QuestionSlideHeader";
11
11
  import { QuestionRenderer } from "./QuestionRenderer";
12
12
  import { BaseSlide } from "./BaseSlide";
13
- import { useOnboardingTheme } from "../providers/OnboardingThemeProvider";
13
+ import { useOnboardingProvider } from "../providers/OnboardingProvider";
14
14
  import { useLocalization } from "@umituz/react-native-localization";
15
15
 
16
16
  export interface QuestionSlideProps {
@@ -26,7 +26,9 @@ export const QuestionSlide = ({
26
26
  onChange,
27
27
  variant: _variant = "default",
28
28
  }: QuestionSlideProps) => {
29
- const { colors } = useOnboardingTheme();
29
+ const {
30
+ theme: { colors },
31
+ } = useOnboardingProvider();
30
32
  const { t } = useLocalization();
31
33
  const { question } = slide;
32
34
 
@@ -2,16 +2,16 @@ import React from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
3
  import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
4
4
  import type { OnboardingSlide } from "../../domain/entities/OnboardingSlide";
5
- import { useOnboardingTheme } from "../providers/OnboardingThemeProvider";
5
+ import { useOnboardingProvider } from "../providers/OnboardingProvider";
6
6
 
7
7
  export interface QuestionSlideHeaderProps {
8
8
  slide: OnboardingSlide;
9
9
  }
10
10
 
11
- export const QuestionSlideHeader = ({
12
- slide,
13
- }: QuestionSlideHeaderProps) => {
14
- const { colors } = useOnboardingTheme();
11
+ export const QuestionSlideHeader = ({ slide }: QuestionSlideHeaderProps) => {
12
+ const {
13
+ theme: { colors },
14
+ } = useOnboardingProvider();
15
15
  const isEmoji = slide.iconType === 'emoji';
16
16
 
17
17
  return (
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { View, TouchableOpacity, StyleSheet } from "react-native";
3
3
  import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
4
- import { useOnboardingTheme } from "../../providers/OnboardingThemeProvider";
4
+ import { useOnboardingProvider } from "../../providers/OnboardingProvider";
5
5
  import type { OnboardingQuestion, QuestionOption } from "../../../domain/entities/OnboardingQuestion";
6
6
 
7
7
  export interface MultipleChoiceQuestionProps {
@@ -15,7 +15,9 @@ export const MultipleChoiceQuestion = ({
15
15
  value = [],
16
16
  onChange,
17
17
  }: MultipleChoiceQuestionProps) => {
18
- const { colors } = useOnboardingTheme();
18
+ const {
19
+ theme: { colors },
20
+ } = useOnboardingProvider();
19
21
 
20
22
  const handleToggle = (optionId: string) => {
21
23
  const newValue = value.includes(optionId)
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { View, TouchableOpacity, StyleSheet } from "react-native";
3
3
  import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
4
- import { useOnboardingTheme } from "../../providers/OnboardingThemeProvider";
4
+ import { useOnboardingProvider } from "../../providers/OnboardingProvider";
5
5
  import type { OnboardingQuestion } from "../../../domain/entities/OnboardingQuestion";
6
6
 
7
7
  export interface RatingQuestionProps {
@@ -15,7 +15,9 @@ export const RatingQuestion = ({
15
15
  value = 0,
16
16
  onChange,
17
17
  }: RatingQuestionProps) => {
18
- const { colors } = useOnboardingTheme();
18
+ const {
19
+ theme: { colors },
20
+ } = useOnboardingProvider();
19
21
  const max = question.validation?.max ?? 5;
20
22
 
21
23
  return (
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { View, TouchableOpacity, StyleSheet } from "react-native";
3
3
  import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
4
- import { useOnboardingTheme } from "../../providers/OnboardingThemeProvider";
4
+ import { useOnboardingProvider } from "../../providers/OnboardingProvider";
5
5
  import type { OnboardingQuestion, QuestionOption } from "../../../domain/entities/OnboardingQuestion";
6
6
 
7
7
  export interface SingleChoiceQuestionProps {
@@ -15,7 +15,9 @@ export const SingleChoiceQuestion = ({
15
15
  value,
16
16
  onChange,
17
17
  }: SingleChoiceQuestionProps) => {
18
- const { colors } = useOnboardingTheme();
18
+ const {
19
+ theme: { colors },
20
+ } = useOnboardingProvider();
19
21
 
20
22
  const renderOption = (option: QuestionOption) => {
21
23
  const isSelected = value === option.id;
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { View, TextInput, StyleSheet } from "react-native";
3
3
  import { AtomicText } from "@umituz/react-native-design-system";
4
- import { useOnboardingTheme } from "../../providers/OnboardingThemeProvider";
4
+ import { useOnboardingProvider } from "../../providers/OnboardingProvider";
5
5
  import type { OnboardingQuestion } from "../../../domain/entities/OnboardingQuestion";
6
6
 
7
7
  export interface TextInputQuestionProps {
@@ -15,7 +15,9 @@ export const TextInputQuestion = ({
15
15
  value = "",
16
16
  onChange,
17
17
  }: TextInputQuestionProps) => {
18
- const { colors } = useOnboardingTheme();
18
+ const {
19
+ theme: { colors },
20
+ } = useOnboardingProvider();
19
21
  const { validation } = question;
20
22
 
21
23
  return (
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Onboarding Provider
3
+ *
4
+ * Central manager for onboarding theme and configuration.
5
+ * All values are passed from the main application.
6
+ */
7
+
8
+ import React, { createContext, useContext, useMemo } from "react";
9
+ import type { OnboardingTheme, OnboardingColors } from "../types/OnboardingTheme";
10
+
11
+ interface OnboardingProviderValue {
12
+ theme: OnboardingTheme;
13
+ }
14
+
15
+ const OnboardingScope = createContext<OnboardingProviderValue | undefined>(undefined);
16
+
17
+ export interface OnboardingProviderProps {
18
+ children: React.ReactNode;
19
+ useGradient: boolean;
20
+ colors: OnboardingColors;
21
+ }
22
+
23
+ export const OnboardingProvider = ({
24
+ children,
25
+ useGradient,
26
+ colors,
27
+ }: OnboardingProviderProps) => {
28
+ const value = useMemo(
29
+ () => ({
30
+ theme: {
31
+ colors,
32
+ useGradient,
33
+ },
34
+ }),
35
+ [colors, useGradient]
36
+ );
37
+
38
+ return (
39
+ <OnboardingScope.Provider value={value}>
40
+ {children}
41
+ </OnboardingScope.Provider>
42
+ );
43
+ };
44
+
45
+ export const useOnboardingProvider = (): OnboardingProviderValue => {
46
+ const scope = useContext(OnboardingScope);
47
+ if (!scope) {
48
+ throw new Error("useOnboardingProvider must be used within OnboardingProvider");
49
+ }
50
+ return scope;
51
+ };
@@ -12,7 +12,8 @@ import { StyleSheet } from "react-native";
12
12
  import type { OnboardingOptions } from "../../domain/entities/OnboardingOptions";
13
13
  import { useOnboardingScreenState } from "../hooks/useOnboardingScreenState";
14
14
  import { OnboardingScreenContent } from "../components/OnboardingScreenContent";
15
- import { OnboardingThemeProvider } from "../providers/OnboardingThemeProvider";
15
+ import { OnboardingProvider } from "../providers/OnboardingProvider";
16
+ import type { OnboardingColors } from "../types/OnboardingTheme";
16
17
 
17
18
  export interface OnboardingScreenProps extends OnboardingOptions {
18
19
  /**
@@ -52,6 +53,11 @@ export interface OnboardingScreenProps extends OnboardingOptions {
52
53
  * When true, shows premium paywall before completing onboarding
53
54
  */
54
55
  showPaywallOnComplete?: boolean;
56
+
57
+ /**
58
+ * Theme colors for the onboarding (Required to avoid hardcoded values)
59
+ */
60
+ themeColors: OnboardingColors;
55
61
  }
56
62
 
57
63
  export const OnboardingScreen = ({
@@ -75,6 +81,7 @@ export const OnboardingScreen = ({
75
81
  showPaywallOnComplete = false,
76
82
  useGradient: globalUseGradient = false,
77
83
  themeVariant = "default",
84
+ themeColors,
78
85
  }: OnboardingScreenProps) => {
79
86
  if (__DEV__) {
80
87
  console.log("[OnboardingScreen] Rendering with slides:", slides?.length);
@@ -115,7 +122,7 @@ export const OnboardingScreen = ({
115
122
  }
116
123
 
117
124
  return (
118
- <OnboardingThemeProvider useGradient={useGradient}>
125
+ <OnboardingProvider useGradient={useGradient} colors={themeColors}>
119
126
  <OnboardingScreenContent
120
127
  containerStyle={[styles.container, containerStyle]}
121
128
  useGradient={useGradient}
@@ -145,7 +152,7 @@ export const OnboardingScreen = ({
145
152
  showPaywallOnComplete={showPaywallOnComplete}
146
153
  variant={themeVariant}
147
154
  />
148
- </OnboardingThemeProvider>
155
+ </OnboardingProvider>
149
156
  );
150
157
  };
151
158
 
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Onboarding Theme Types
3
+ */
4
+
5
+ export interface OnboardingColors {
6
+ iconColor: string;
7
+ textColor: string;
8
+ subTextColor: string;
9
+ buttonBg: string;
10
+ buttonTextColor: string;
11
+ progressBarBg: string;
12
+ progressFillColor: string;
13
+ dotColor: string;
14
+ activeDotColor: string;
15
+ progressTextColor: string;
16
+ headerButtonBg: string;
17
+ headerButtonBorder: string;
18
+ iconBg: string;
19
+ iconBorder: string;
20
+ errorColor: string;
21
+ featureItemBg: string;
22
+ }
23
+
24
+ export interface OnboardingTheme {
25
+ colors: OnboardingColors;
26
+ useGradient: boolean;
27
+ }
@@ -1,6 +0,0 @@
1
- /**
2
- * Onboarding Theme Variant
3
- *
4
- * Defines the visual style of the onboarding flow
5
- */
6
- export type OnboardingThemeVariant = "default" | "card" | "minimal" | "fullscreen";
@@ -1,103 +0,0 @@
1
- import React, { createContext, useContext, useMemo } from 'react';
2
- import { useAppDesignTokens } from '@umituz/react-native-design-system';
3
-
4
- interface OnboardingColors {
5
- iconColor: string;
6
- textColor: string;
7
- subTextColor: string;
8
- buttonBg: string;
9
- buttonTextColor: string;
10
- progressBarBg: string;
11
- progressFillColor: string;
12
- dotColor: string;
13
- activeDotColor: string;
14
- progressTextColor: string;
15
- headerButtonBg: string;
16
- headerButtonBorder: string;
17
- iconBg: string;
18
- iconBorder: string;
19
- errorColor: string;
20
- featureItemBg: string;
21
- }
22
-
23
- interface OnboardingThemeValue {
24
- colors: OnboardingColors;
25
- useGradient: boolean;
26
- }
27
-
28
- const OnboardingThemeInternal = createContext<OnboardingThemeValue | undefined>(undefined);
29
-
30
- export interface OnboardingThemeProviderProps {
31
- children: React.ReactNode;
32
- useGradient?: boolean;
33
- }
34
-
35
- export const OnboardingThemeProvider = ({
36
- children,
37
- useGradient = false,
38
- }: OnboardingThemeProviderProps) => {
39
- const tokens = useAppDesignTokens();
40
-
41
- const colors = useMemo<OnboardingColors>(() => {
42
- const primaryContent = tokens.colors.onPrimary || "#FFFFFF";
43
-
44
- if (useGradient) {
45
- return {
46
- iconColor: primaryContent,
47
- textColor: primaryContent,
48
- subTextColor: primaryContent + "CC",
49
- buttonBg: primaryContent,
50
- buttonTextColor: tokens.colors.primary,
51
- progressBarBg: primaryContent + "40",
52
- progressFillColor: primaryContent,
53
- dotColor: primaryContent + "66",
54
- activeDotColor: primaryContent,
55
- progressTextColor: primaryContent + "CC",
56
- headerButtonBg: primaryContent + "33",
57
- headerButtonBorder: primaryContent + "59",
58
- iconBg: primaryContent + "33",
59
- iconBorder: primaryContent + "59",
60
- errorColor: "#FFCDD2",
61
- featureItemBg: primaryContent + "1A",
62
- };
63
- }
64
-
65
- return {
66
- iconColor: tokens.colors.primary,
67
- textColor: tokens.colors.textPrimary,
68
- subTextColor: tokens.colors.textSecondary,
69
- buttonBg: tokens.colors.primary,
70
- buttonTextColor: tokens.colors.onPrimary,
71
- progressBarBg: tokens.colors.borderLight,
72
- progressFillColor: tokens.colors.primary,
73
- dotColor: tokens.colors.borderLight,
74
- activeDotColor: tokens.colors.primary,
75
- progressTextColor: tokens.colors.textSecondary,
76
- headerButtonBg: tokens.colors.surface,
77
- headerButtonBorder: tokens.colors.borderLight,
78
- iconBg: tokens.colors.primary + '15',
79
- iconBorder: tokens.colors.primary + '30',
80
- errorColor: tokens.colors.error,
81
- featureItemBg: tokens.colors.surfaceSecondary || tokens.colors.surfaceVariant || "rgba(0,0,0,0.05)",
82
- };
83
- }, [tokens, useGradient]);
84
-
85
- const value = useMemo(
86
- () => ({ colors, useGradient }),
87
- [colors, useGradient]
88
- );
89
-
90
- return (
91
- <OnboardingThemeInternal.Provider value={value}>
92
- {children}
93
- </OnboardingThemeInternal.Provider>
94
- );
95
- };
96
-
97
- export const useOnboardingTheme = (): OnboardingThemeValue => {
98
- const theme = useContext(OnboardingThemeInternal);
99
- if (!theme) {
100
- throw new Error('useOnboardingTheme must be used within OnboardingThemeProvider');
101
- }
102
- return theme;
103
- };