rn-vs-lb 1.0.62 → 1.0.64

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.
@@ -0,0 +1,138 @@
1
+ import React, { memo, useMemo } from "react";
2
+ import { ActivityIndicator, Image, Pressable, StyleSheet, Text, View } from "react-native";
3
+ import { useTheme, ThemeType, SizesType, TypographytType } from "../../theme";
4
+ import { GalleryModal } from "../Modals";
5
+
6
+ export type AiAgentGalleryViewProps = {
7
+ isLoading: boolean;
8
+ photos: string[];
9
+ galleryColumns: number;
10
+ galleryItemSize: number;
11
+
12
+ visible: boolean;
13
+ initialIndex: number;
14
+ onOpenAt: (index: number) => void;
15
+ onClose: () => void;
16
+
17
+ emptyTest: string;
18
+ };
19
+
20
+ export const AiAgentGalleryView = memo(
21
+ ({
22
+ isLoading,
23
+ photos,
24
+ galleryColumns,
25
+ galleryItemSize,
26
+ visible,
27
+ initialIndex,
28
+ onOpenAt,
29
+ onClose,
30
+ emptyTest,
31
+ }: AiAgentGalleryViewProps) => {
32
+ const { theme, sizes, typography } = useTheme();
33
+ const isDark = (theme as any)?.isDark ?? false;
34
+
35
+ const styles = useMemo(() => getStyles(theme, sizes, typography, isDark), [
36
+ theme,
37
+ sizes,
38
+ typography,
39
+ isDark,
40
+ ]);
41
+
42
+ if (isLoading) {
43
+ return (
44
+ <View style={styles.galleryWrapper}>
45
+ <ActivityIndicator />
46
+ </View>
47
+ );
48
+ }
49
+
50
+ if (!photos.length) {
51
+ return (
52
+ <View style={styles.galleryWrapper}>
53
+ <View style={styles.emptyState}>
54
+ <Text style={styles.emptyText}>{emptyTest}</Text>
55
+ </View>
56
+ </View>
57
+ );
58
+ }
59
+
60
+ return (
61
+ <View style={styles.galleryWrapper}>
62
+ <View style={styles.galleryGrid}>
63
+ {photos.map((photo, i) => {
64
+ const isLastInRow = (i + 1) % galleryColumns === 0;
65
+ return (
66
+ <Pressable
67
+ key={`${photo}-${i}`}
68
+ onPress={() => onOpenAt(i)}
69
+ android_ripple={{ color: "#00000022" }}
70
+ style={[
71
+ styles.galleryImage,
72
+ isLastInRow && styles.galleryImageLast,
73
+ { width: galleryItemSize, height: galleryItemSize },
74
+ ]}
75
+ >
76
+ <Image
77
+ source={{ uri: photo }}
78
+ style={{
79
+ width: "100%",
80
+ height: "100%",
81
+ borderRadius: styles.galleryImage.borderRadius,
82
+ }}
83
+ />
84
+ </Pressable>
85
+ );
86
+ })}
87
+ </View>
88
+
89
+ <GalleryModal
90
+ visible={visible}
91
+ images={photos}
92
+ initialIndex={initialIndex}
93
+ onRequestClose={onClose}
94
+ />
95
+ </View>
96
+ );
97
+ }
98
+ );
99
+
100
+ AiAgentGalleryView.displayName = "AiAgentGalleryView";
101
+
102
+ const getStyles = (
103
+ theme: ThemeType,
104
+ sizes: SizesType,
105
+ typography: TypographytType,
106
+ isDark: boolean
107
+ ) =>
108
+ StyleSheet.create({
109
+ galleryWrapper: {
110
+ paddingHorizontal: sizes.md as number,
111
+ paddingVertical: sizes.xl as number,
112
+ },
113
+ galleryGrid: {
114
+ flexDirection: "row",
115
+ flexWrap: "wrap",
116
+ },
117
+ galleryImage: {
118
+ borderRadius: 18,
119
+ backgroundColor: theme.backgroundSecond,
120
+ marginRight: sizes.sm as number,
121
+ marginBottom: sizes.sm as number,
122
+ },
123
+ galleryImageLast: {
124
+ marginRight: 0,
125
+ },
126
+ emptyState: {
127
+ alignItems: "center",
128
+ justifyContent: "center",
129
+ paddingVertical: sizes.lg as number,
130
+ },
131
+ emptyText: {
132
+ ...(typography.bodySm as object),
133
+ color: theme.greyText,
134
+ textAlign: "center",
135
+ },
136
+ });
137
+
138
+ export default AiAgentGalleryView;
@@ -0,0 +1,75 @@
1
+ import React, { useState } from 'react';
2
+ import { Meta, StoryFn } from '@storybook/react';
3
+ import { View } from 'react-native';
4
+ import { ThemeProvider } from '../../theme';
5
+ import { AiAgentGalleryView } from './AiAgentGallery.pure';
6
+
7
+ type Props = React.ComponentProps<typeof AiAgentGalleryView>;
8
+
9
+ const meta: Meta<Props> = {
10
+ title: 'Gallery/AiAgentGallery',
11
+ component: AiAgentGalleryView,
12
+ decorators: [
13
+ (Story) => (
14
+ <ThemeProvider>
15
+ <View style={{ paddingVertical: 24 }}>
16
+ <Story />
17
+ </View>
18
+ </ThemeProvider>
19
+ ),
20
+ ],
21
+ args: {
22
+ galleryColumns: 3,
23
+ galleryItemSize: 108,
24
+ emptyTest: 'No generated images yet. Try creating one with your AI agent!',
25
+ },
26
+ };
27
+
28
+ export default meta;
29
+
30
+ const Template: StoryFn<Props> = ({
31
+ onOpenAt: _onOpenAt,
32
+ onClose: _onClose,
33
+ visible: _visible,
34
+ initialIndex: _initialIndex,
35
+ ...rest
36
+ }) => {
37
+ const [visible, setVisible] = useState(false);
38
+ const [initialIndex, setInitialIndex] = useState(0);
39
+
40
+ return (
41
+ <AiAgentGalleryView
42
+ {...rest}
43
+ visible={visible}
44
+ initialIndex={initialIndex}
45
+ onOpenAt={(index) => {
46
+ setInitialIndex(index);
47
+ setVisible(true);
48
+ }}
49
+ onClose={() => setVisible(false)}
50
+ />
51
+ );
52
+ };
53
+
54
+ export const Default = Template.bind({});
55
+ Default.args = {
56
+ isLoading: false,
57
+ photos: [
58
+ 'https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=600&q=60',
59
+ 'https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=600&q=60',
60
+ 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?auto=format&fit=crop&w=600&q=60',
61
+ 'https://images.unsplash.com/photo-1545239351-1141bd82e8a6?auto=format&fit=crop&w=600&q=60',
62
+ ],
63
+ };
64
+
65
+ export const Loading = Template.bind({});
66
+ Loading.args = {
67
+ isLoading: true,
68
+ photos: [],
69
+ };
70
+
71
+ export const Empty = Template.bind({});
72
+ Empty.args = {
73
+ isLoading: false,
74
+ photos: [],
75
+ };
@@ -0,0 +1 @@
1
+ export * from "./AiAgentGallery.pure"
@@ -0,0 +1,61 @@
1
+ import React, { useState } from 'react';
2
+ import { Meta, StoryFn } from '@storybook/react';
3
+ import { View, Alert } from 'react-native';
4
+ import { ThemeProvider } from '../../theme';
5
+ import { StepProgress } from './StepProgress';
6
+
7
+ type Props = React.ComponentProps<typeof StepProgress>;
8
+
9
+ const meta: Meta<Props> = {
10
+ title: 'UI/StepProgress',
11
+ component: StepProgress,
12
+ decorators: [
13
+ (Story) => (
14
+ <ThemeProvider>
15
+ <View style={{ padding: 24 }}>
16
+ <Story />
17
+ </View>
18
+ </ThemeProvider>
19
+ ),
20
+ ],
21
+ args: {
22
+ steps: [
23
+ { title: 'Briefing', description: 'Share context and goals for your agent.' },
24
+ { title: 'Generation', description: 'The agent drafts a tailored solution.' },
25
+ { title: 'Review', description: 'Assess the output and give feedback.' },
26
+ { title: 'Launch', description: 'Publish or deploy your agent deliverable.' },
27
+ ],
28
+ },
29
+ };
30
+
31
+ export default meta;
32
+
33
+ const Template: StoryFn<Props> = (args) => <StepProgress {...args} />;
34
+
35
+ export const Default = Template.bind({});
36
+ Default.args = {
37
+ activeStep: 1,
38
+ };
39
+
40
+ export const FirstStep = Template.bind({});
41
+ FirstStep.args = {
42
+ activeStep: 0,
43
+ };
44
+
45
+ export const WithClickableSteps: StoryFn<Props> = (args) => {
46
+ const [activeStep, setActiveStep] = useState(args.activeStep ?? 2);
47
+
48
+ return (
49
+ <StepProgress
50
+ {...args}
51
+ activeStep={activeStep}
52
+ onStepPress={(index) => {
53
+ Alert.alert('Step selected', args.steps?.[index]?.title ?? `Step ${index + 1}`);
54
+ setActiveStep(index);
55
+ }}
56
+ />
57
+ );
58
+ };
59
+ WithClickableSteps.args = {
60
+ activeStep: 2,
61
+ };
@@ -0,0 +1,153 @@
1
+ import React, { useMemo } from "react";
2
+ import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
3
+ import { ThemeType, TypographytType, SizesType, useTheme } from "../../theme";
4
+
5
+ interface StepProgressProps {
6
+ steps: { title: string; description?: string }[];
7
+ activeStep: number;
8
+ onStepPress?: (index: number) => void;
9
+ }
10
+
11
+ export const StepProgress: React.FC<StepProgressProps> = ({ steps, activeStep, onStepPress }) => {
12
+ const { theme, typography, sizes } = useTheme();
13
+
14
+ const styles = useMemo(
15
+ () => createStyles({ theme, typography, sizes }),
16
+ [theme, typography, sizes],
17
+ );
18
+
19
+ const currentDescription = steps[activeStep]?.description ?? "";
20
+
21
+ return (
22
+ <View style={styles.container}>
23
+ <View style={styles.stepsRow}>
24
+ {steps.map((step, index) => {
25
+ const isActive = index === activeStep;
26
+ const isCompleted = index < activeStep;
27
+ const isPressable = typeof onStepPress === "function" && index < activeStep;
28
+ return (
29
+ <TouchableOpacity
30
+ key={step.title ?? index}
31
+ style={[styles.stepItem, isPressable && styles.stepItemPressable]}
32
+ activeOpacity={0.7}
33
+ onPress={isPressable ? () => onStepPress(index) : undefined}
34
+ disabled={!isPressable}
35
+ >
36
+ <View style={styles.stepHeader}>
37
+ <View
38
+ style={[
39
+ styles.circle,
40
+ (isActive || isCompleted) && styles.circleActive,
41
+ ]}
42
+ >
43
+ <Text
44
+ style={[
45
+ styles.circleText,
46
+ (isActive || isCompleted) && styles.circleTextActive,
47
+ ]}
48
+ >
49
+ {index + 1}
50
+ </Text>
51
+ </View>
52
+ {index < steps.length - 1 ? (
53
+ <View
54
+ style={[
55
+ styles.connector,
56
+ (isCompleted || (isActive && activeStep === index)) && styles.connectorActive,
57
+ ]}
58
+ />
59
+ ) : null}
60
+ </View>
61
+ <Text
62
+ numberOfLines={2}
63
+ style={[styles.stepTitle, isActive && styles.stepTitleActive]}
64
+ >
65
+ {step.title}
66
+ </Text>
67
+ </TouchableOpacity>
68
+ );
69
+ })}
70
+ </View>
71
+ {currentDescription ? (
72
+ <Text style={styles.description}>{currentDescription}</Text>
73
+ ) : null}
74
+ </View>
75
+ );
76
+ };
77
+
78
+ const createStyles = ({
79
+ theme,
80
+ typography,
81
+ sizes,
82
+ }: {
83
+ theme: ThemeType;
84
+ typography: TypographytType;
85
+ sizes: SizesType;
86
+ }) =>
87
+ StyleSheet.create({
88
+ container: {
89
+ marginBottom: sizes.xl as number,
90
+ },
91
+ stepsRow: {
92
+ flexDirection: "row",
93
+ alignItems: "center",
94
+ justifyContent: "space-between",
95
+ },
96
+ stepItem: {
97
+ flex: 1,
98
+ marginRight: sizes.sm as number,
99
+ },
100
+ stepItemPressable: {
101
+ opacity: 0.9,
102
+ },
103
+ stepHeader: {
104
+ flexDirection: "row",
105
+ alignItems: "center",
106
+ marginBottom: sizes.xs as number,
107
+ },
108
+ circle: {
109
+ width: 36,
110
+ height: 36,
111
+ borderRadius: 18,
112
+ borderWidth: 2,
113
+ borderColor: theme.border,
114
+ alignItems: "center",
115
+ justifyContent: "center",
116
+ backgroundColor: theme.white,
117
+ },
118
+ circleActive: {
119
+ borderColor: theme.primary,
120
+ backgroundColor: theme.primary,
121
+ },
122
+ circleText: {
123
+ ...typography.bodyXs,
124
+ color: theme.text,
125
+ fontWeight: "600",
126
+ },
127
+ circleTextActive: {
128
+ color: theme.white,
129
+ },
130
+ connector: {
131
+ flex: 1,
132
+ height: 2,
133
+ marginLeft: sizes.xs as number,
134
+ backgroundColor: theme.border,
135
+ },
136
+ connectorActive: {
137
+ backgroundColor: theme.primary,
138
+ },
139
+ stepTitle: {
140
+ ...typography.bodyXs,
141
+ color: theme.greyText,
142
+ },
143
+ stepTitleActive: {
144
+ color: theme.text,
145
+ fontWeight: "600",
146
+ },
147
+ description: {
148
+ marginTop: sizes.sm as number,
149
+ ...typography.bodySm,
150
+ color: theme.greyText,
151
+ },
152
+ });
153
+
@@ -28,3 +28,4 @@ export { ThreeDotsMenu } from './ThreeDotsMenu';
28
28
  export { default as TripleSwitch } from './TripleSwitch';
29
29
  export type { TripleSwitchValue } from './TripleSwitch';
30
30
  export { UpdateRequiredView } from './UpdateRequiredView';
31
+ export * from "./StepProgress";
@@ -10,3 +10,4 @@ export * from './Specialist';
10
10
  export * from './Tooltip';
11
11
  export * from './UI';
12
12
  export * from './UserCards';
13
+ export * from './Gallery';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-vs-lb",
3
- "version": "1.0.62",
3
+ "version": "1.0.64",
4
4
  "description": "Expo Router + Storybook template ready for npm distribution.",
5
5
  "keywords": [
6
6
  "expo",