@ecohouse/ui 0.1.28 → 0.1.30
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 +17 -13
- package/dist/index.cjs +782 -191
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -2
- package/dist/index.d.ts +89 -2
- package/dist/index.js +782 -194
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/AvatarSimple/AvatarSimple.stories.tsx +62 -0
- package/src/components/AvatarSimple/AvatarSimple.tsx +178 -0
- package/src/components/AvatarSimple/index.ts +2 -0
- package/src/components/SegmentedToggle/SegmentedToggle.stories.tsx +22 -0
- package/src/components/SegmentedToggle/SegmentedToggle.tsx +51 -18
- package/src/components/Sidebar/Sidebar.stories.tsx +119 -0
- package/src/components/Sidebar/Sidebar.tsx +305 -0
- package/src/components/Sidebar/index.ts +2 -0
- package/src/components/VerificationCodeInput/VerificationCodeInput.stories.tsx +112 -0
- package/src/components/VerificationCodeInput/VerificationCodeInput.test.ts +42 -0
- package/src/components/VerificationCodeInput/VerificationCodeInput.tsx +255 -0
- package/src/components/VerificationCodeInput/VerificationCodeInput.utils.ts +34 -0
- package/src/components/VerificationCodeInput/index.ts +5 -0
- package/src/components/index.ts +12 -0
- package/src/icons/LogOut/logOutPath.ts +1 -1
- package/src/index.ts +9 -0
package/package.json
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { fn } from "storybook/test";
|
|
3
|
+
import { StyleSheet, View } from "react-native";
|
|
4
|
+
import { AvatarSimple } from "./AvatarSimple";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Components/AvatarSimple",
|
|
8
|
+
component: AvatarSimple,
|
|
9
|
+
args: {
|
|
10
|
+
name: "Աննա Հակոբյան",
|
|
11
|
+
email: "anna.hakobyan@example.com",
|
|
12
|
+
imageUrl: "https://i.pravatar.cc/150?img=47",
|
|
13
|
+
onLogOut: fn(),
|
|
14
|
+
logOutAccessibilityLabel: "Log out",
|
|
15
|
+
compact: false,
|
|
16
|
+
},
|
|
17
|
+
argTypes: {
|
|
18
|
+
compact: { control: "boolean" },
|
|
19
|
+
onLogOut: { action: "log out" },
|
|
20
|
+
},
|
|
21
|
+
parameters: {
|
|
22
|
+
backgrounds: { default: "black" },
|
|
23
|
+
},
|
|
24
|
+
} satisfies Meta<typeof AvatarSimple>;
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
type Story = StoryObj<typeof meta>;
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {};
|
|
30
|
+
|
|
31
|
+
export const WithoutImage: Story = {
|
|
32
|
+
args: { imageUrl: undefined },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Compact: Story = {
|
|
36
|
+
args: { compact: true },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const LongEmail: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
email: "anna.hakobyan.very.long.email@example.com",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const InSidebarFooter: Story = {
|
|
46
|
+
render: (args) => (
|
|
47
|
+
<View style={storyStyles.footer}>
|
|
48
|
+
<AvatarSimple {...args} />
|
|
49
|
+
</View>
|
|
50
|
+
),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const storyStyles = StyleSheet.create({
|
|
54
|
+
footer: {
|
|
55
|
+
width: 300,
|
|
56
|
+
height: 80,
|
|
57
|
+
paddingVertical: 12,
|
|
58
|
+
paddingHorizontal: 16,
|
|
59
|
+
backgroundColor: "#090909",
|
|
60
|
+
justifyContent: "center",
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { forwardRef, useMemo, useRef, useState, type ComponentRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Image,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
View,
|
|
8
|
+
type StyleProp,
|
|
9
|
+
type ViewProps,
|
|
10
|
+
type ViewStyle,
|
|
11
|
+
} from "react-native";
|
|
12
|
+
import { LogOutIcon } from "../../icons";
|
|
13
|
+
import { colors, fonts } from "../../theme";
|
|
14
|
+
import { mergeRefs } from "../../utils/mergeRefs";
|
|
15
|
+
import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
|
|
16
|
+
|
|
17
|
+
export interface AvatarSimpleProps extends Omit<ViewProps, "style" | "children"> {
|
|
18
|
+
/** Logged-in user's display name. */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Shown under the name, e.g. the user's email. */
|
|
21
|
+
email: string;
|
|
22
|
+
/** Photo URL; falls back to a solid placeholder when missing or when it fails to load. */
|
|
23
|
+
imageUrl?: string | null;
|
|
24
|
+
/** Called when the trailing logout control is pressed. */
|
|
25
|
+
onLogOut?: () => void;
|
|
26
|
+
/** Accessible label for the logout control. Defaults to `"Log out"`. */
|
|
27
|
+
logOutAccessibilityLabel?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Compact mode — avatar image only (no card chrome, name, email, or logout).
|
|
30
|
+
* Used by collapsed account sidebars.
|
|
31
|
+
*/
|
|
32
|
+
compact?: boolean;
|
|
33
|
+
style?: StyleProp<ViewStyle>;
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const IMAGE_SIZE = 32;
|
|
38
|
+
const CARD_WIDTH = 268;
|
|
39
|
+
const CARD_HEIGHT = 56;
|
|
40
|
+
const LOG_OUT_SIZE = 16;
|
|
41
|
+
|
|
42
|
+
export const AvatarSimple = forwardRef<ComponentRef<typeof View>, AvatarSimpleProps>(
|
|
43
|
+
function AvatarSimple(
|
|
44
|
+
{
|
|
45
|
+
name,
|
|
46
|
+
email,
|
|
47
|
+
imageUrl,
|
|
48
|
+
onLogOut,
|
|
49
|
+
logOutAccessibilityLabel = "Log out",
|
|
50
|
+
compact = false,
|
|
51
|
+
style,
|
|
52
|
+
className,
|
|
53
|
+
...rest
|
|
54
|
+
},
|
|
55
|
+
forwardedRef,
|
|
56
|
+
) {
|
|
57
|
+
const containerRef = useRef<ComponentRef<typeof View>>(null);
|
|
58
|
+
const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
|
|
59
|
+
const [imageFailed, setImageFailed] = useState(false);
|
|
60
|
+
const showImage = Boolean(imageUrl) && !imageFailed;
|
|
61
|
+
|
|
62
|
+
useApplyWebClassName(containerRef, className);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<View
|
|
66
|
+
{...rest}
|
|
67
|
+
ref={setContainerRef}
|
|
68
|
+
style={[styles.root, compact ? styles.rootCompact : styles.rootCard, style]}
|
|
69
|
+
accessibilityRole="summary"
|
|
70
|
+
accessibilityLabel={`${name}, ${email}`}
|
|
71
|
+
>
|
|
72
|
+
<View style={styles.avatar}>
|
|
73
|
+
{showImage ? (
|
|
74
|
+
<Image
|
|
75
|
+
source={{ uri: imageUrl ?? undefined }}
|
|
76
|
+
style={styles.avatarImage}
|
|
77
|
+
onError={() => setImageFailed(true)}
|
|
78
|
+
accessibilityIgnoresInvertColors
|
|
79
|
+
/>
|
|
80
|
+
) : null}
|
|
81
|
+
</View>
|
|
82
|
+
|
|
83
|
+
{!compact ? (
|
|
84
|
+
<View style={styles.details}>
|
|
85
|
+
<View style={styles.textBlock}>
|
|
86
|
+
<Text style={styles.name} numberOfLines={1}>
|
|
87
|
+
{name}
|
|
88
|
+
</Text>
|
|
89
|
+
<Text style={styles.email} numberOfLines={1}>
|
|
90
|
+
{email}
|
|
91
|
+
</Text>
|
|
92
|
+
</View>
|
|
93
|
+
|
|
94
|
+
<Pressable
|
|
95
|
+
onPress={onLogOut}
|
|
96
|
+
accessibilityRole="button"
|
|
97
|
+
accessibilityLabel={logOutAccessibilityLabel}
|
|
98
|
+
hitSlop={8}
|
|
99
|
+
style={styles.logOutButton}
|
|
100
|
+
>
|
|
101
|
+
<LogOutIcon color={colors.primary} size={LOG_OUT_SIZE} strokeWidth={2} />
|
|
102
|
+
</Pressable>
|
|
103
|
+
</View>
|
|
104
|
+
) : null}
|
|
105
|
+
</View>
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const styles = StyleSheet.create({
|
|
111
|
+
root: {
|
|
112
|
+
flexDirection: "row",
|
|
113
|
+
alignItems: "center",
|
|
114
|
+
overflow: "hidden",
|
|
115
|
+
},
|
|
116
|
+
rootCard: {
|
|
117
|
+
width: CARD_WIDTH,
|
|
118
|
+
height: CARD_HEIGHT,
|
|
119
|
+
gap: 12,
|
|
120
|
+
borderRadius: 12,
|
|
121
|
+
paddingVertical: 4,
|
|
122
|
+
paddingHorizontal: 12,
|
|
123
|
+
backgroundColor: colors.grey700,
|
|
124
|
+
},
|
|
125
|
+
rootCompact: {
|
|
126
|
+
width: IMAGE_SIZE,
|
|
127
|
+
height: IMAGE_SIZE,
|
|
128
|
+
borderRadius: IMAGE_SIZE,
|
|
129
|
+
backgroundColor: "transparent",
|
|
130
|
+
},
|
|
131
|
+
avatar: {
|
|
132
|
+
width: IMAGE_SIZE,
|
|
133
|
+
height: IMAGE_SIZE,
|
|
134
|
+
borderRadius: 56,
|
|
135
|
+
backgroundColor: colors.grey500,
|
|
136
|
+
overflow: "hidden",
|
|
137
|
+
flexShrink: 0,
|
|
138
|
+
},
|
|
139
|
+
avatarImage: {
|
|
140
|
+
width: IMAGE_SIZE,
|
|
141
|
+
height: IMAGE_SIZE,
|
|
142
|
+
},
|
|
143
|
+
details: {
|
|
144
|
+
flex: 1,
|
|
145
|
+
minWidth: 0,
|
|
146
|
+
flexDirection: "row",
|
|
147
|
+
alignItems: "center",
|
|
148
|
+
gap: 12,
|
|
149
|
+
},
|
|
150
|
+
textBlock: {
|
|
151
|
+
flex: 1,
|
|
152
|
+
minWidth: 0,
|
|
153
|
+
},
|
|
154
|
+
name: {
|
|
155
|
+
fontFamily: fonts.sans,
|
|
156
|
+
fontSize: 12,
|
|
157
|
+
fontWeight: "600",
|
|
158
|
+
lineHeight: 16,
|
|
159
|
+
letterSpacing: 0,
|
|
160
|
+
color: colors.white,
|
|
161
|
+
},
|
|
162
|
+
email: {
|
|
163
|
+
marginTop: 2,
|
|
164
|
+
fontFamily: fonts.sans,
|
|
165
|
+
fontSize: 12,
|
|
166
|
+
fontWeight: "400",
|
|
167
|
+
lineHeight: 16,
|
|
168
|
+
letterSpacing: 0,
|
|
169
|
+
color: colors.grey0,
|
|
170
|
+
},
|
|
171
|
+
logOutButton: {
|
|
172
|
+
width: LOG_OUT_SIZE,
|
|
173
|
+
height: LOG_OUT_SIZE,
|
|
174
|
+
alignItems: "center",
|
|
175
|
+
justifyContent: "center",
|
|
176
|
+
flexShrink: 0,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
@@ -14,6 +14,12 @@ const optionsWithoutIcons = [
|
|
|
14
14
|
{ value: "second", label: "Label" },
|
|
15
15
|
] as const;
|
|
16
16
|
|
|
17
|
+
const threeOptions = [
|
|
18
|
+
{ value: "active", label: "Ակտիվ" },
|
|
19
|
+
{ value: "completed", label: "Ավարտված" },
|
|
20
|
+
{ value: "cancelled", label: "Չեղարկված" },
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
17
23
|
const multilingualOptions = [
|
|
18
24
|
{ value: "daily", label: "Օրավարձ" },
|
|
19
25
|
{ value: "overnight", label: "Գիշերակաց երկար տարբերակ" },
|
|
@@ -50,6 +56,22 @@ export const WithoutIcons: Story = {
|
|
|
50
56
|
args: { options: optionsWithoutIcons, defaultValue: "first" },
|
|
51
57
|
};
|
|
52
58
|
|
|
59
|
+
export const ThreeOptions: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
options: threeOptions,
|
|
62
|
+
defaultValue: "completed",
|
|
63
|
+
fullWidth: false,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const ThreeOptionsFullWidth: Story = {
|
|
68
|
+
render: () => (
|
|
69
|
+
<View style={storyStyles.container}>
|
|
70
|
+
<SegmentedToggle options={threeOptions} defaultValue="active" />
|
|
71
|
+
</View>
|
|
72
|
+
),
|
|
73
|
+
};
|
|
74
|
+
|
|
53
75
|
export const ContentWidth: Story = {
|
|
54
76
|
args: { fullWidth: false },
|
|
55
77
|
};
|
|
@@ -23,7 +23,13 @@ export interface SegmentedToggleOption {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface SegmentedToggleProps extends Omit<ViewProps, "style" | "children"> {
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Segment options. Supports 2 or 3 options (e.g. Active / Completed / Cancelled).
|
|
28
|
+
* Passing fewer than 2 options is unsupported.
|
|
29
|
+
*/
|
|
30
|
+
options:
|
|
31
|
+
| readonly [SegmentedToggleOption, SegmentedToggleOption]
|
|
32
|
+
| readonly [SegmentedToggleOption, SegmentedToggleOption, SegmentedToggleOption];
|
|
27
33
|
value?: string;
|
|
28
34
|
defaultValue?: string;
|
|
29
35
|
onValueChange?: (value: string) => void;
|
|
@@ -40,6 +46,18 @@ const OPTION_HORIZONTAL_PADDING = 16;
|
|
|
40
46
|
const OPTION_CONTENT_GAP = 12;
|
|
41
47
|
const OPTION_MIN_WIDTH = 97;
|
|
42
48
|
const OPTION_MEASUREMENT_TOLERANCE = 0.5;
|
|
49
|
+
const TRACK_PADDING = 4;
|
|
50
|
+
const OPTION_GAP = 4;
|
|
51
|
+
|
|
52
|
+
function buildIndicatorOutputRange(optionWidths: number[]): number[] {
|
|
53
|
+
const outputRange: number[] = [];
|
|
54
|
+
let offset = 0;
|
|
55
|
+
for (let index = 0; index < optionWidths.length; index += 1) {
|
|
56
|
+
outputRange.push(offset);
|
|
57
|
+
offset += optionWidths[index] + OPTION_GAP;
|
|
58
|
+
}
|
|
59
|
+
return outputRange;
|
|
60
|
+
}
|
|
43
61
|
|
|
44
62
|
export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedToggleProps>(
|
|
45
63
|
function SegmentedToggle(
|
|
@@ -58,6 +76,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
58
76
|
},
|
|
59
77
|
forwardedRef,
|
|
60
78
|
) {
|
|
79
|
+
const optionCount = options.length;
|
|
61
80
|
const containerRef = useRef<ComponentRef<typeof View>>(null);
|
|
62
81
|
const isControlled = value !== undefined;
|
|
63
82
|
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
@@ -69,9 +88,14 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
69
88
|
const resolvedClassName = className?.trim() || undefined;
|
|
70
89
|
const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
|
|
71
90
|
const progress = useRef(new Animated.Value(selectedIndex)).current;
|
|
72
|
-
const [contentWidths, setContentWidths] = useState<[
|
|
91
|
+
const [contentWidths, setContentWidths] = useState<number[]>(() =>
|
|
92
|
+
Array.from({ length: optionCount }, () => 0),
|
|
93
|
+
);
|
|
73
94
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
74
|
-
|
|
95
|
+
|
|
96
|
+
// Track chrome: left+right padding + gaps between options.
|
|
97
|
+
const trackChrome = TRACK_PADDING * 2 + OPTION_GAP * (optionCount - 1);
|
|
98
|
+
const availableOptionWidth = Math.max(0, (containerWidth - trackChrome) / optionCount);
|
|
75
99
|
const resolveContentOptionWidth = (contentWidth: number) =>
|
|
76
100
|
contentWidth > 0
|
|
77
101
|
? Math.max(
|
|
@@ -79,17 +103,28 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
79
103
|
contentWidth + OPTION_HORIZONTAL_PADDING * 2 + OPTION_MEASUREMENT_TOLERANCE,
|
|
80
104
|
)
|
|
81
105
|
: 0;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
:
|
|
85
|
-
|
|
106
|
+
|
|
107
|
+
const optionWidths = fullWidth
|
|
108
|
+
? Array.from({ length: optionCount }, () => availableOptionWidth)
|
|
109
|
+
: contentWidths.map((width) => resolveContentOptionWidth(width));
|
|
110
|
+
|
|
111
|
+
const selectedOptionWidth = optionWidths[selectedIndex] ?? 0;
|
|
112
|
+
const indicatorInputRange = options.map((_, index) => index);
|
|
113
|
+
const indicatorOutputRange = buildIndicatorOutputRange(optionWidths);
|
|
86
114
|
const indicatorTranslateX = progress.interpolate({
|
|
87
|
-
inputRange:
|
|
88
|
-
outputRange:
|
|
115
|
+
inputRange: indicatorInputRange,
|
|
116
|
+
outputRange: indicatorOutputRange,
|
|
89
117
|
});
|
|
90
118
|
|
|
91
119
|
useApplyWebClassName(containerRef, resolvedClassName);
|
|
92
120
|
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
setContentWidths((current) => {
|
|
123
|
+
if (current.length === optionCount) return current;
|
|
124
|
+
return Array.from({ length: optionCount }, (_, index) => current[index] ?? 0);
|
|
125
|
+
});
|
|
126
|
+
}, [optionCount]);
|
|
127
|
+
|
|
93
128
|
useEffect(() => {
|
|
94
129
|
const animation = Animated.timing(progress, {
|
|
95
130
|
toValue: selectedIndex,
|
|
@@ -139,7 +174,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
139
174
|
const nextWidth = event.nativeEvent.layout.width;
|
|
140
175
|
setContentWidths((currentWidths) => {
|
|
141
176
|
if (currentWidths[index] === nextWidth) return currentWidths;
|
|
142
|
-
const nextWidths
|
|
177
|
+
const nextWidths = [...currentWidths];
|
|
143
178
|
nextWidths[index] = nextWidth;
|
|
144
179
|
return nextWidths;
|
|
145
180
|
});
|
|
@@ -164,6 +199,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
164
199
|
const selected = optionValue === selectedValue;
|
|
165
200
|
const iconColor = selected ? colors.primary : colors.whiteAlpha20;
|
|
166
201
|
const labelColor = selected ? colors.whiteAlpha86 : colors.whiteAlpha40;
|
|
202
|
+
const optionWidth = optionWidths[index] ?? 0;
|
|
167
203
|
|
|
168
204
|
return (
|
|
169
205
|
<Pressable
|
|
@@ -178,10 +214,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
|
|
|
178
214
|
styles.option,
|
|
179
215
|
fullWidth
|
|
180
216
|
? styles.optionFullWidth
|
|
181
|
-
: [
|
|
182
|
-
styles.optionContentWidth,
|
|
183
|
-
optionWidths[index] > 0 && { width: optionWidths[index] },
|
|
184
|
-
],
|
|
217
|
+
: [styles.optionContentWidth, optionWidth > 0 && { width: optionWidth }],
|
|
185
218
|
]}
|
|
186
219
|
>
|
|
187
220
|
<View style={styles.optionContent}>
|
|
@@ -209,8 +242,8 @@ const styles = StyleSheet.create({
|
|
|
209
242
|
base: {
|
|
210
243
|
height: 44,
|
|
211
244
|
flexDirection: "row",
|
|
212
|
-
padding:
|
|
213
|
-
gap:
|
|
245
|
+
padding: TRACK_PADDING,
|
|
246
|
+
gap: OPTION_GAP,
|
|
214
247
|
borderRadius: 60,
|
|
215
248
|
backgroundColor: colors.grey700,
|
|
216
249
|
overflow: "hidden",
|
|
@@ -255,8 +288,8 @@ const styles = StyleSheet.create({
|
|
|
255
288
|
},
|
|
256
289
|
activeIndicator: {
|
|
257
290
|
position: "absolute",
|
|
258
|
-
top:
|
|
259
|
-
left:
|
|
291
|
+
top: TRACK_PADDING,
|
|
292
|
+
left: TRACK_PADDING,
|
|
260
293
|
height: 36,
|
|
261
294
|
backgroundColor: colors.grey600,
|
|
262
295
|
borderRadius: 60,
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
3
|
+
import { fn } from "storybook/test";
|
|
4
|
+
import { StyleSheet, View } from "react-native";
|
|
5
|
+
import {
|
|
6
|
+
CalendarIcon,
|
|
7
|
+
HeartIcon,
|
|
8
|
+
HelpCircleIcon,
|
|
9
|
+
LayoutGridIcon,
|
|
10
|
+
SettingsIcon,
|
|
11
|
+
VideoIcon,
|
|
12
|
+
} from "../../icons";
|
|
13
|
+
import { Sidebar } from "./Sidebar";
|
|
14
|
+
|
|
15
|
+
const defaultItems = [
|
|
16
|
+
{ key: "bookings", label: "Ամրագրումներ", icon: CalendarIcon, active: true, onPress: fn() },
|
|
17
|
+
{ key: "digitalKeys", label: "Թվային բանալիներ", icon: LayoutGridIcon, onPress: fn() },
|
|
18
|
+
{ key: "cameras", label: "Տեսախցիկներ", icon: VideoIcon, onPress: fn() },
|
|
19
|
+
{ key: "favorites", label: "Նախընտրածներ", icon: HeartIcon, onPress: fn() },
|
|
20
|
+
{ key: "support", label: "Աջակցություն", icon: HelpCircleIcon, onPress: fn() },
|
|
21
|
+
{ key: "settings", label: "Կարգավորումներ", icon: SettingsIcon, onPress: fn() },
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
const meta = {
|
|
25
|
+
title: "Components/Sidebar",
|
|
26
|
+
component: Sidebar,
|
|
27
|
+
args: {
|
|
28
|
+
items: [...defaultItems],
|
|
29
|
+
user: {
|
|
30
|
+
name: "Աննա Հակոբյան",
|
|
31
|
+
email: "anna.hakobyan@example.com",
|
|
32
|
+
imageUrl: "https://i.pravatar.cc/150?img=47",
|
|
33
|
+
},
|
|
34
|
+
onLogOut: fn(),
|
|
35
|
+
logoAccessibilityLabel: "Cortel Booking",
|
|
36
|
+
navigationAccessibilityLabel: "Հաշվի նավիգացիա",
|
|
37
|
+
collapseAccessibilityLabel: "Թաքցնել կողագոտին",
|
|
38
|
+
expandAccessibilityLabel: "Բացել կողագոտին",
|
|
39
|
+
logOutAccessibilityLabel: "Ելք",
|
|
40
|
+
defaultCollapsed: false,
|
|
41
|
+
},
|
|
42
|
+
argTypes: {
|
|
43
|
+
collapsed: { control: "boolean" },
|
|
44
|
+
defaultCollapsed: { control: "boolean" },
|
|
45
|
+
onLogOut: { action: "log out" },
|
|
46
|
+
onCollapsedChange: { action: "collapsed change" },
|
|
47
|
+
},
|
|
48
|
+
parameters: {
|
|
49
|
+
backgrounds: { default: "black" },
|
|
50
|
+
layout: "fullscreen",
|
|
51
|
+
},
|
|
52
|
+
decorators: [
|
|
53
|
+
(Story) => (
|
|
54
|
+
<View style={storyStyles.frame}>
|
|
55
|
+
<Story />
|
|
56
|
+
<View style={storyStyles.content} />
|
|
57
|
+
</View>
|
|
58
|
+
),
|
|
59
|
+
],
|
|
60
|
+
} satisfies Meta<typeof Sidebar>;
|
|
61
|
+
|
|
62
|
+
export default meta;
|
|
63
|
+
type Story = StoryObj<typeof meta>;
|
|
64
|
+
|
|
65
|
+
export const Default: Story = {};
|
|
66
|
+
|
|
67
|
+
export const Collapsed: Story = {
|
|
68
|
+
args: { defaultCollapsed: true },
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const WithoutImage: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
user: {
|
|
74
|
+
name: "Աննա Հակոբյան",
|
|
75
|
+
email: "anna.hakobyan@example.com",
|
|
76
|
+
imageUrl: null,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function ControlledSidebar() {
|
|
82
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
83
|
+
const [activeKey, setActiveKey] = useState("bookings");
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Sidebar
|
|
87
|
+
collapsed={collapsed}
|
|
88
|
+
onCollapsedChange={setCollapsed}
|
|
89
|
+
onLogOut={fn()}
|
|
90
|
+
user={{
|
|
91
|
+
name: "Աննա Հակոբյան",
|
|
92
|
+
email: "anna.hakobyan@example.com",
|
|
93
|
+
imageUrl: "https://i.pravatar.cc/150?img=47",
|
|
94
|
+
}}
|
|
95
|
+
items={defaultItems.map((item) => ({
|
|
96
|
+
...item,
|
|
97
|
+
active: item.key === activeKey,
|
|
98
|
+
onPress: () => setActiveKey(item.key),
|
|
99
|
+
}))}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const Controlled: Story = {
|
|
105
|
+
render: () => <ControlledSidebar />,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const storyStyles = StyleSheet.create({
|
|
109
|
+
frame: {
|
|
110
|
+
flexDirection: "row",
|
|
111
|
+
height: 720,
|
|
112
|
+
width: "100%",
|
|
113
|
+
backgroundColor: "#000000",
|
|
114
|
+
},
|
|
115
|
+
content: {
|
|
116
|
+
flex: 1,
|
|
117
|
+
backgroundColor: "#090909",
|
|
118
|
+
},
|
|
119
|
+
});
|