@ecohouse/ui 0.1.31 → 0.1.32
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 +5 -0
- package/dist/index.cjs +1122 -338
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +147 -5
- package/dist/index.d.ts +147 -5
- package/dist/index.js +1049 -285
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/AccountHeader/AccountHeader.stories.tsx +70 -0
- package/src/components/AccountHeader/AccountHeader.tsx +224 -0
- package/src/components/AccountHeader/index.ts +2 -0
- package/src/components/AvatarSimple/AvatarSimple.tsx +2 -2
- package/src/components/Button/Button.tsx +4 -1
- package/src/components/DatePicker/DatePicker.tsx +14 -9
- package/src/components/FloatingActionButton/FloatingActionButton.stories.tsx +42 -0
- package/src/components/FloatingActionButton/FloatingActionButton.tsx +77 -0
- package/src/components/FloatingActionButton/index.ts +2 -0
- package/src/components/InfoTile/InfoTile.stories.tsx +80 -0
- package/src/components/InfoTile/InfoTile.tsx +118 -0
- package/src/components/InfoTile/index.ts +2 -0
- package/src/components/PaginationDots/PaginationDots.stories.tsx +48 -0
- package/src/components/PaginationDots/PaginationDots.tsx +111 -0
- package/src/components/PaginationDots/index.ts +2 -0
- package/src/components/StatusBadge/StatusBadge.stories.tsx +54 -0
- package/src/components/StatusBadge/StatusBadge.tsx +108 -0
- package/src/components/StatusBadge/index.ts +2 -0
- package/src/components/StepList/StepList.stories.tsx +37 -0
- package/src/components/StepList/StepList.tsx +152 -0
- package/src/components/StepList/index.ts +2 -0
- package/src/components/index.ts +18 -0
- package/src/icons/AlertTriangle/AlertTriangleIcon.tsx +19 -0
- package/src/icons/AlertTriangle/AlertTriangleIcon.web.tsx +18 -0
- package/src/icons/AlertTriangle/alertTrianglePath.ts +3 -0
- package/src/icons/AlertTriangle/index.ts +1 -0
- package/src/icons/Bell/BellIcon.tsx +2 -2
- package/src/icons/Bell/BellIcon.web.tsx +2 -2
- package/src/icons/Bell/bellPath.ts +1 -1
- package/src/icons/CottageDetail/CottageDetailIcons.tsx +15 -0
- package/src/icons/CottageDetail/CottageDetailIcons.web.tsx +15 -0
- package/src/icons/CottageDetail/cottageDetailPaths.ts +3 -0
- package/src/icons/CottageDetail/index.ts +1 -0
- package/src/icons/Edit/EditIcon.tsx +20 -0
- package/src/icons/Edit/EditIcon.web.tsx +19 -0
- package/src/icons/Edit/editPath.ts +3 -0
- package/src/icons/Edit/index.ts +1 -0
- package/src/icons/HouseRules/HouseRulesIcons.tsx +62 -0
- package/src/icons/HouseRules/HouseRulesIcons.web.tsx +61 -0
- package/src/icons/HouseRules/houseRulesPaths.ts +24 -0
- package/src/icons/HouseRules/index.ts +2 -0
- package/src/icons/LocationUser/LocationUserIcon.tsx +15 -0
- package/src/icons/LocationUser/LocationUserIcon.web.tsx +14 -0
- package/src/icons/LocationUser/index.ts +1 -0
- package/src/icons/LocationUser/locationUserPath.ts +2 -0
- package/src/icons/Lock/LockIcon.tsx +21 -0
- package/src/icons/Lock/LockIcon.web.tsx +26 -0
- package/src/icons/Lock/index.ts +1 -0
- package/src/icons/Lock/lockPath.ts +3 -0
- package/src/icons/Refresh/RefreshIcon.tsx +20 -0
- package/src/icons/Refresh/RefreshIcon.web.tsx +19 -0
- package/src/icons/Refresh/index.ts +1 -0
- package/src/icons/Refresh/refreshPath.ts +3 -0
- package/src/icons/RefundStatus/RefundStatusIcon.tsx +24 -0
- package/src/icons/RefundStatus/RefundStatusIcon.web.tsx +23 -0
- package/src/icons/RefundStatus/index.ts +1 -0
- package/src/icons/RefundStatus/refundStatusPath.ts +3 -0
- package/src/icons/index.ts +8 -0
- package/src/icons/registry.ts +30 -0
- package/src/index.ts +26 -0
- package/src/theme/colors.ts +4 -0
- package/src/theme/typography.ts +1 -1
- package/src/utils/dateUtils.ts +25 -0
package/package.json
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { fn } from "storybook/test";
|
|
3
|
+
import { StyleSheet, View } from "react-native";
|
|
4
|
+
import { StatusBadge } from "../StatusBadge";
|
|
5
|
+
import { AccountHeader } from "./AccountHeader";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Components/AccountHeader",
|
|
9
|
+
component: AccountHeader,
|
|
10
|
+
args: {
|
|
11
|
+
title: "Իմ ամրագրումները",
|
|
12
|
+
onNotificationsPress: fn(),
|
|
13
|
+
notificationsAccessibilityLabel: "Ծանուցումներ",
|
|
14
|
+
menuAccessibilityLabel: "Բացել ընտրացանկը",
|
|
15
|
+
},
|
|
16
|
+
argTypes: {
|
|
17
|
+
onMenuPress: { action: "menu press" },
|
|
18
|
+
onNotificationsPress: { action: "notifications press" },
|
|
19
|
+
},
|
|
20
|
+
parameters: {
|
|
21
|
+
backgrounds: { default: "black" },
|
|
22
|
+
layout: "fullscreen",
|
|
23
|
+
},
|
|
24
|
+
} satisfies Meta<typeof AccountHeader>;
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
type Story = StoryObj<typeof meta>;
|
|
28
|
+
|
|
29
|
+
export const Desktop: Story = {
|
|
30
|
+
render: (args) => (
|
|
31
|
+
<View style={styles.desktopFrame}>
|
|
32
|
+
<AccountHeader {...args} />
|
|
33
|
+
</View>
|
|
34
|
+
),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const Mobile: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
onMenuPress: fn(),
|
|
40
|
+
},
|
|
41
|
+
render: (args) => (
|
|
42
|
+
<View style={styles.mobileFrame}>
|
|
43
|
+
<AccountHeader {...args} />
|
|
44
|
+
</View>
|
|
45
|
+
),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const WithBackAndStatus: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
title: "Ամրագրում #BK-9087",
|
|
51
|
+
onBackPress: fn(),
|
|
52
|
+
},
|
|
53
|
+
render: (args) => (
|
|
54
|
+
<View style={styles.desktopFrame}>
|
|
55
|
+
<AccountHeader {...args} badge={<StatusBadge label="Ակտիվ" tone="active" />} />
|
|
56
|
+
</View>
|
|
57
|
+
),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const styles = StyleSheet.create({
|
|
61
|
+
desktopFrame: {
|
|
62
|
+
width: "100%",
|
|
63
|
+
maxWidth: 960,
|
|
64
|
+
backgroundColor: "#090909",
|
|
65
|
+
},
|
|
66
|
+
mobileFrame: {
|
|
67
|
+
width: 390,
|
|
68
|
+
backgroundColor: "#090909",
|
|
69
|
+
},
|
|
70
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { forwardRef, useMemo, useRef, type ComponentRef, type ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Text,
|
|
6
|
+
View,
|
|
7
|
+
type StyleProp,
|
|
8
|
+
type ViewProps,
|
|
9
|
+
type ViewStyle,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import { BellIcon, ChevronLeftIcon, MenuIcon } from "../../icons";
|
|
12
|
+
import { colors, fonts } from "../../theme";
|
|
13
|
+
import { mergeRefs } from "../../utils/mergeRefs";
|
|
14
|
+
import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
|
|
15
|
+
|
|
16
|
+
export interface AccountHeaderProps extends Omit<ViewProps, "style" | "children"> {
|
|
17
|
+
title: string;
|
|
18
|
+
/**
|
|
19
|
+
* When set, shows a 32×32 back control (takes priority over `onMenuPress`).
|
|
20
|
+
* Used on nested account pages such as booking details.
|
|
21
|
+
*/
|
|
22
|
+
onBackPress?: () => void;
|
|
23
|
+
/** When set, shows the 28×28 menu control (mobile account shell). Ignored if `onBackPress` is set. */
|
|
24
|
+
onMenuPress?: () => void;
|
|
25
|
+
onNotificationsPress?: () => void;
|
|
26
|
+
/** Optional status chip (or other accessory) rendered after the title with a 12px gap. */
|
|
27
|
+
badge?: ReactNode;
|
|
28
|
+
/** Accessible name for the back control. Defaults to `"Back"`. */
|
|
29
|
+
backAccessibilityLabel?: string;
|
|
30
|
+
/** Accessible name for the menu control. Defaults to `"Open menu"`. */
|
|
31
|
+
menuAccessibilityLabel?: string;
|
|
32
|
+
/** Accessible name for notifications. Defaults to `"Notifications"`. */
|
|
33
|
+
notificationsAccessibilityLabel?: string;
|
|
34
|
+
style?: StyleProp<ViewStyle>;
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const HEADER_HEIGHT = 64;
|
|
39
|
+
const MENU_ICON_SIZE = 28;
|
|
40
|
+
const ICON_BUTTON_SIZE = 32;
|
|
41
|
+
const ICON_BUTTON_ICON_SIZE = 16;
|
|
42
|
+
const BACK_TITLE_GAP = 12;
|
|
43
|
+
const TITLE_BADGE_GAP = 12;
|
|
44
|
+
const MENU_TITLE_GAP = 16;
|
|
45
|
+
const RIGHT_GAP = 16;
|
|
46
|
+
|
|
47
|
+
export const AccountHeader = forwardRef<ComponentRef<typeof View>, AccountHeaderProps>(
|
|
48
|
+
function AccountHeader(
|
|
49
|
+
{
|
|
50
|
+
title,
|
|
51
|
+
onBackPress,
|
|
52
|
+
onMenuPress,
|
|
53
|
+
onNotificationsPress,
|
|
54
|
+
badge,
|
|
55
|
+
backAccessibilityLabel = "Back",
|
|
56
|
+
menuAccessibilityLabel = "Open menu",
|
|
57
|
+
notificationsAccessibilityLabel = "Notifications",
|
|
58
|
+
style,
|
|
59
|
+
className,
|
|
60
|
+
...rest
|
|
61
|
+
},
|
|
62
|
+
forwardedRef,
|
|
63
|
+
) {
|
|
64
|
+
const containerRef = useRef<ComponentRef<typeof View>>(null);
|
|
65
|
+
const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
|
|
66
|
+
const resolvedClassName = className?.trim() || undefined;
|
|
67
|
+
const showBack = onBackPress != null;
|
|
68
|
+
const showMenu = !showBack && onMenuPress != null;
|
|
69
|
+
const isCompact = showMenu;
|
|
70
|
+
|
|
71
|
+
useApplyWebClassName(containerRef, resolvedClassName);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<View
|
|
75
|
+
{...rest}
|
|
76
|
+
ref={setContainerRef}
|
|
77
|
+
accessibilityRole="header"
|
|
78
|
+
style={[styles.base, isCompact ? styles.compact : styles.desktop, style]}
|
|
79
|
+
>
|
|
80
|
+
<View
|
|
81
|
+
style={[
|
|
82
|
+
styles.start,
|
|
83
|
+
showBack ? styles.startWithBack : null,
|
|
84
|
+
showMenu ? styles.startWithMenu : null,
|
|
85
|
+
]}
|
|
86
|
+
>
|
|
87
|
+
{showBack ? (
|
|
88
|
+
<Pressable
|
|
89
|
+
accessibilityRole="button"
|
|
90
|
+
accessibilityLabel={backAccessibilityLabel}
|
|
91
|
+
hitSlop={8}
|
|
92
|
+
onPress={onBackPress}
|
|
93
|
+
style={styles.iconButton}
|
|
94
|
+
>
|
|
95
|
+
<ChevronLeftIcon
|
|
96
|
+
color={colors.white}
|
|
97
|
+
size={ICON_BUTTON_ICON_SIZE}
|
|
98
|
+
strokeWidth={1.5}
|
|
99
|
+
/>
|
|
100
|
+
</Pressable>
|
|
101
|
+
) : null}
|
|
102
|
+
|
|
103
|
+
{showMenu ? (
|
|
104
|
+
<Pressable
|
|
105
|
+
accessibilityRole="button"
|
|
106
|
+
accessibilityLabel={menuAccessibilityLabel}
|
|
107
|
+
hitSlop={8}
|
|
108
|
+
onPress={onMenuPress}
|
|
109
|
+
style={styles.menuButton}
|
|
110
|
+
>
|
|
111
|
+
<MenuIcon color={colors.white} size={MENU_ICON_SIZE} strokeWidth={1.5} />
|
|
112
|
+
</Pressable>
|
|
113
|
+
) : null}
|
|
114
|
+
|
|
115
|
+
<View style={styles.titleRow}>
|
|
116
|
+
<Text
|
|
117
|
+
numberOfLines={1}
|
|
118
|
+
style={[
|
|
119
|
+
styles.title,
|
|
120
|
+
showBack || !isCompact ? styles.titleDesktop : styles.titleCompact,
|
|
121
|
+
]}
|
|
122
|
+
>
|
|
123
|
+
{title}
|
|
124
|
+
</Text>
|
|
125
|
+
{badge != null ? <View style={styles.badgeSlot}>{badge}</View> : null}
|
|
126
|
+
</View>
|
|
127
|
+
</View>
|
|
128
|
+
|
|
129
|
+
<View style={styles.end}>
|
|
130
|
+
<Pressable
|
|
131
|
+
accessibilityRole="button"
|
|
132
|
+
accessibilityLabel={notificationsAccessibilityLabel}
|
|
133
|
+
onPress={onNotificationsPress}
|
|
134
|
+
style={styles.iconButton}
|
|
135
|
+
>
|
|
136
|
+
<BellIcon color={colors.white} size={ICON_BUTTON_ICON_SIZE} strokeWidth={1.5} />
|
|
137
|
+
</Pressable>
|
|
138
|
+
</View>
|
|
139
|
+
</View>
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const styles = StyleSheet.create({
|
|
145
|
+
base: {
|
|
146
|
+
height: HEADER_HEIGHT,
|
|
147
|
+
width: "100%",
|
|
148
|
+
flexDirection: "row",
|
|
149
|
+
alignItems: "center",
|
|
150
|
+
justifyContent: "space-between",
|
|
151
|
+
backgroundColor: colors.dark,
|
|
152
|
+
borderBottomWidth: 1,
|
|
153
|
+
borderBottomColor: colors.grey700,
|
|
154
|
+
},
|
|
155
|
+
desktop: {
|
|
156
|
+
paddingTop: 16,
|
|
157
|
+
paddingBottom: 16,
|
|
158
|
+
paddingLeft: 24,
|
|
159
|
+
paddingRight: 24,
|
|
160
|
+
},
|
|
161
|
+
compact: {
|
|
162
|
+
padding: 16,
|
|
163
|
+
},
|
|
164
|
+
start: {
|
|
165
|
+
flex: 1,
|
|
166
|
+
minWidth: 0,
|
|
167
|
+
flexDirection: "row",
|
|
168
|
+
alignItems: "center",
|
|
169
|
+
},
|
|
170
|
+
startWithBack: {
|
|
171
|
+
gap: BACK_TITLE_GAP,
|
|
172
|
+
},
|
|
173
|
+
startWithMenu: {
|
|
174
|
+
gap: MENU_TITLE_GAP,
|
|
175
|
+
},
|
|
176
|
+
titleRow: {
|
|
177
|
+
flex: 1,
|
|
178
|
+
minWidth: 0,
|
|
179
|
+
flexDirection: "row",
|
|
180
|
+
alignItems: "center",
|
|
181
|
+
gap: TITLE_BADGE_GAP,
|
|
182
|
+
},
|
|
183
|
+
end: {
|
|
184
|
+
flexShrink: 0,
|
|
185
|
+
marginLeft: RIGHT_GAP,
|
|
186
|
+
},
|
|
187
|
+
iconButton: {
|
|
188
|
+
width: ICON_BUTTON_SIZE,
|
|
189
|
+
height: ICON_BUTTON_SIZE,
|
|
190
|
+
padding: 8,
|
|
191
|
+
alignItems: "center",
|
|
192
|
+
justifyContent: "center",
|
|
193
|
+
borderRadius: 5,
|
|
194
|
+
backgroundColor: colors.grey800,
|
|
195
|
+
borderWidth: 1,
|
|
196
|
+
borderColor: colors.grey700,
|
|
197
|
+
flexShrink: 0,
|
|
198
|
+
},
|
|
199
|
+
menuButton: {
|
|
200
|
+
width: MENU_ICON_SIZE,
|
|
201
|
+
height: MENU_ICON_SIZE,
|
|
202
|
+
alignItems: "center",
|
|
203
|
+
justifyContent: "center",
|
|
204
|
+
flexShrink: 0,
|
|
205
|
+
},
|
|
206
|
+
title: {
|
|
207
|
+
flexShrink: 1,
|
|
208
|
+
fontFamily: fonts.sans,
|
|
209
|
+
fontWeight: "600",
|
|
210
|
+
letterSpacing: 0,
|
|
211
|
+
color: colors.white,
|
|
212
|
+
},
|
|
213
|
+
titleDesktop: {
|
|
214
|
+
fontSize: 16,
|
|
215
|
+
lineHeight: 22,
|
|
216
|
+
},
|
|
217
|
+
titleCompact: {
|
|
218
|
+
fontSize: 14,
|
|
219
|
+
lineHeight: 20,
|
|
220
|
+
},
|
|
221
|
+
badgeSlot: {
|
|
222
|
+
flexShrink: 0,
|
|
223
|
+
},
|
|
224
|
+
});
|
|
@@ -83,10 +83,10 @@ export const AvatarSimple = forwardRef<ComponentRef<typeof View>, AvatarSimplePr
|
|
|
83
83
|
{!compact ? (
|
|
84
84
|
<View style={styles.details}>
|
|
85
85
|
<View style={styles.textBlock}>
|
|
86
|
-
<Text style={styles.name} numberOfLines={1}>
|
|
86
|
+
<Text style={styles.name} numberOfLines={1} ellipsizeMode="tail">
|
|
87
87
|
{name}
|
|
88
88
|
</Text>
|
|
89
|
-
<Text style={styles.email} numberOfLines={1}>
|
|
89
|
+
<Text style={styles.email} numberOfLines={1} ellipsizeMode="tail">
|
|
90
90
|
{email}
|
|
91
91
|
</Text>
|
|
92
92
|
</View>
|
|
@@ -31,6 +31,8 @@ export interface ButtonProps extends Omit<
|
|
|
31
31
|
size?: ButtonSize;
|
|
32
32
|
showIcon?: boolean;
|
|
33
33
|
icon?: ReactNode;
|
|
34
|
+
/** Overrides the trailing icon badge fill (defaults to the variant preset). */
|
|
35
|
+
iconBadgeBackgroundColor?: string;
|
|
34
36
|
style?: StyleProp<ViewStyle>;
|
|
35
37
|
textStyle?: StyleProp<TextStyle>;
|
|
36
38
|
className?: string;
|
|
@@ -97,6 +99,7 @@ export const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonPr
|
|
|
97
99
|
size = "lg",
|
|
98
100
|
showIcon = false,
|
|
99
101
|
icon,
|
|
102
|
+
iconBadgeBackgroundColor,
|
|
100
103
|
style,
|
|
101
104
|
textStyle,
|
|
102
105
|
className,
|
|
@@ -182,7 +185,7 @@ export const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonPr
|
|
|
182
185
|
width: metrics.iconContainerSize,
|
|
183
186
|
height: metrics.iconContainerSize,
|
|
184
187
|
borderRadius: metrics.iconContainerSize / 2,
|
|
185
|
-
backgroundColor: preset.iconBadgeBackgroundColor,
|
|
188
|
+
backgroundColor: iconBadgeBackgroundColor ?? preset.iconBadgeBackgroundColor,
|
|
186
189
|
},
|
|
187
190
|
]}
|
|
188
191
|
>
|
|
@@ -22,6 +22,7 @@ import { colors, datePickerTypography } from "../../theme";
|
|
|
22
22
|
import {
|
|
23
23
|
addMonths,
|
|
24
24
|
formatDate,
|
|
25
|
+
formatDateDayShortMonth,
|
|
25
26
|
getMonthMatrix,
|
|
26
27
|
isBefore,
|
|
27
28
|
isDateDisabled,
|
|
@@ -137,9 +138,7 @@ function RangeTriggerField({
|
|
|
137
138
|
{label}
|
|
138
139
|
</Text>
|
|
139
140
|
<View style={styles.rangeValueRow}>
|
|
140
|
-
<Text
|
|
141
|
-
{value}
|
|
142
|
-
</Text>
|
|
141
|
+
<Text style={styles.rangeDate}>{value}</Text>
|
|
143
142
|
{time ? (
|
|
144
143
|
<View style={styles.rangeTimeWrap}>
|
|
145
144
|
<ClockFilledIcon color={colors.lightGrey} size={16} />
|
|
@@ -172,7 +171,7 @@ export const DatePicker = forwardRef<ComponentRef<typeof View>, DatePickerProps>
|
|
|
172
171
|
defaultVisibleMonth,
|
|
173
172
|
monthNames = DEFAULT_MONTH_NAMES,
|
|
174
173
|
weekdayLabels = DEFAULT_WEEKDAY_LABELS,
|
|
175
|
-
formatValue
|
|
174
|
+
formatValue: formatValueProp,
|
|
176
175
|
weekStartsOn = 1,
|
|
177
176
|
calendarStyle,
|
|
178
177
|
style,
|
|
@@ -188,6 +187,10 @@ export const DatePicker = forwardRef<ComponentRef<typeof View>, DatePickerProps>
|
|
|
188
187
|
} = props;
|
|
189
188
|
|
|
190
189
|
const isRange = props.range === true;
|
|
190
|
+
const usesRangeTrigger = isRange && variant !== "default";
|
|
191
|
+
const formatValue =
|
|
192
|
+
formatValueProp ??
|
|
193
|
+
(usesRangeTrigger ? (date: Date) => formatDateDayShortMonth(date, monthNames) : formatDate);
|
|
191
194
|
const valueProp = props.value;
|
|
192
195
|
const rootRef = useRef<ComponentRef<typeof View>>(null);
|
|
193
196
|
const triggerRef = useRef<ComponentRef<typeof Pressable>>(null);
|
|
@@ -347,7 +350,6 @@ export const DatePicker = forwardRef<ComponentRef<typeof View>, DatePickerProps>
|
|
|
347
350
|
? formatValue(singleValue)
|
|
348
351
|
: placeholder;
|
|
349
352
|
|
|
350
|
-
const usesRangeTrigger = isRange && variant !== "default";
|
|
351
353
|
const usesVerticalRangeTrigger = usesRangeTrigger && variant === "range-vertical";
|
|
352
354
|
const triggerBorderColor = usesRangeTrigger
|
|
353
355
|
? isOpen
|
|
@@ -641,7 +643,7 @@ const styles = StyleSheet.create({
|
|
|
641
643
|
justifyContent: "center",
|
|
642
644
|
gap: 8,
|
|
643
645
|
paddingVertical: 12,
|
|
644
|
-
paddingHorizontal:
|
|
646
|
+
paddingHorizontal: 12,
|
|
645
647
|
},
|
|
646
648
|
rangeLabel: {
|
|
647
649
|
...datePickerTypography.rangeLabel,
|
|
@@ -651,13 +653,16 @@ const styles = StyleSheet.create({
|
|
|
651
653
|
minWidth: 0,
|
|
652
654
|
flexDirection: "row",
|
|
653
655
|
alignItems: "center",
|
|
654
|
-
justifyContent: "
|
|
655
|
-
|
|
656
|
+
justifyContent: "flex-start",
|
|
657
|
+
flexWrap: "nowrap",
|
|
658
|
+
gap: 10,
|
|
656
659
|
},
|
|
657
660
|
rangeDate: {
|
|
658
661
|
...datePickerTypography.rangeDate,
|
|
659
|
-
flexShrink:
|
|
662
|
+
flexShrink: 0,
|
|
660
663
|
color: colors.white,
|
|
664
|
+
// @ts-expect-error RN Web — keep the full date on one line
|
|
665
|
+
whiteSpace: "nowrap",
|
|
661
666
|
},
|
|
662
667
|
rangeTimeWrap: {
|
|
663
668
|
flexShrink: 0,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { fn } from "storybook/test";
|
|
3
|
+
import { StyleSheet, View } from "react-native";
|
|
4
|
+
import { FloatingActionButton } from "./FloatingActionButton";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Components/FloatingActionButton",
|
|
8
|
+
component: FloatingActionButton,
|
|
9
|
+
args: {
|
|
10
|
+
onPress: fn(),
|
|
11
|
+
accessibilityLabel: "Նոր ամրագրում",
|
|
12
|
+
},
|
|
13
|
+
parameters: {
|
|
14
|
+
backgrounds: { default: "black" },
|
|
15
|
+
layout: "fullscreen",
|
|
16
|
+
},
|
|
17
|
+
} satisfies Meta<typeof FloatingActionButton>;
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
type Story = StoryObj<typeof meta>;
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
render: (args) => (
|
|
24
|
+
<View style={styles.frame}>
|
|
25
|
+
<FloatingActionButton {...args} style={styles.fab} />
|
|
26
|
+
</View>
|
|
27
|
+
),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const styles = StyleSheet.create({
|
|
31
|
+
frame: {
|
|
32
|
+
position: "relative",
|
|
33
|
+
width: 390,
|
|
34
|
+
height: 640,
|
|
35
|
+
backgroundColor: "#090909",
|
|
36
|
+
},
|
|
37
|
+
fab: {
|
|
38
|
+
position: "absolute",
|
|
39
|
+
right: 12,
|
|
40
|
+
bottom: 20,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { forwardRef, useMemo, useRef, type ComponentRef, type ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
TouchableOpacity,
|
|
5
|
+
type GestureResponderEvent,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type TouchableOpacityProps,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from "react-native";
|
|
10
|
+
import { PlusIcon } from "../../icons";
|
|
11
|
+
import { colors } from "../../theme";
|
|
12
|
+
import { mergeRefs } from "../../utils/mergeRefs";
|
|
13
|
+
import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
|
|
14
|
+
|
|
15
|
+
export interface FloatingActionButtonProps extends Omit<
|
|
16
|
+
TouchableOpacityProps,
|
|
17
|
+
"onPress" | "disabled" | "style" | "children"
|
|
18
|
+
> {
|
|
19
|
+
onPress: (event: GestureResponderEvent) => void;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/** Defaults to a 24×24 white plus icon. */
|
|
22
|
+
icon?: ReactNode;
|
|
23
|
+
accessibilityLabel?: string;
|
|
24
|
+
style?: StyleProp<ViewStyle>;
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const SIZE = 48;
|
|
29
|
+
const PADDING = 12;
|
|
30
|
+
const ICON_SIZE = 24;
|
|
31
|
+
const RADIUS = 64;
|
|
32
|
+
|
|
33
|
+
export const FloatingActionButton = forwardRef<
|
|
34
|
+
ComponentRef<typeof TouchableOpacity>,
|
|
35
|
+
FloatingActionButtonProps
|
|
36
|
+
>(function FloatingActionButton(
|
|
37
|
+
{ onPress, disabled = false, icon, accessibilityLabel = "Add", style, className, ...rest },
|
|
38
|
+
forwardedRef,
|
|
39
|
+
) {
|
|
40
|
+
const containerRef = useRef<ComponentRef<typeof TouchableOpacity>>(null);
|
|
41
|
+
const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
|
|
42
|
+
const resolvedClassName = className?.trim() || undefined;
|
|
43
|
+
|
|
44
|
+
useApplyWebClassName(containerRef, resolvedClassName);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<TouchableOpacity
|
|
48
|
+
{...rest}
|
|
49
|
+
ref={setContainerRef}
|
|
50
|
+
onPress={onPress}
|
|
51
|
+
disabled={disabled}
|
|
52
|
+
activeOpacity={0.7}
|
|
53
|
+
accessibilityRole="button"
|
|
54
|
+
accessibilityLabel={accessibilityLabel}
|
|
55
|
+
accessibilityState={{ disabled }}
|
|
56
|
+
style={[styles.base, disabled && styles.disabled, style]}
|
|
57
|
+
>
|
|
58
|
+
{icon ?? <PlusIcon color={colors.white} size={ICON_SIZE} strokeWidth={2} />}
|
|
59
|
+
</TouchableOpacity>
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const styles = StyleSheet.create({
|
|
64
|
+
base: {
|
|
65
|
+
width: SIZE,
|
|
66
|
+
height: SIZE,
|
|
67
|
+
padding: PADDING,
|
|
68
|
+
borderRadius: RADIUS,
|
|
69
|
+
backgroundColor: colors.primary,
|
|
70
|
+
alignItems: "center",
|
|
71
|
+
justifyContent: "center",
|
|
72
|
+
gap: 10,
|
|
73
|
+
},
|
|
74
|
+
disabled: {
|
|
75
|
+
opacity: 0.6,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { StyleSheet, View } from "react-native";
|
|
3
|
+
import { NoSmokingIcon, PawIcon, QuietHoursIcon, TrashIcon } from "../../icons";
|
|
4
|
+
import { InfoTile } from "./InfoTile";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Components/InfoTile",
|
|
8
|
+
component: InfoTile,
|
|
9
|
+
args: {
|
|
10
|
+
title: "Լռության ժամեր",
|
|
11
|
+
description: "23:00 - 08:00",
|
|
12
|
+
icon: QuietHoursIcon,
|
|
13
|
+
},
|
|
14
|
+
parameters: {
|
|
15
|
+
backgrounds: { default: "black" },
|
|
16
|
+
},
|
|
17
|
+
} satisfies Meta<typeof InfoTile>;
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
type Story = StoryObj<typeof meta>;
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
render: (args) => (
|
|
24
|
+
<View style={styles.frame}>
|
|
25
|
+
<InfoTile {...args} />
|
|
26
|
+
</View>
|
|
27
|
+
),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const HouseRulesGrid: Story = {
|
|
31
|
+
render: () => (
|
|
32
|
+
<View style={styles.grid}>
|
|
33
|
+
<InfoTile
|
|
34
|
+
title="Լռության ժամեր"
|
|
35
|
+
description="23:00 - 08:00"
|
|
36
|
+
icon={QuietHoursIcon}
|
|
37
|
+
style={styles.tile}
|
|
38
|
+
/>
|
|
39
|
+
<InfoTile
|
|
40
|
+
title="Ծխելը արգելված է"
|
|
41
|
+
description="Տան ներսում խստիվ արգելվում է"
|
|
42
|
+
icon={NoSmokingIcon}
|
|
43
|
+
style={styles.tile}
|
|
44
|
+
/>
|
|
45
|
+
<InfoTile
|
|
46
|
+
title="Ընտանի կենդանիներ"
|
|
47
|
+
description="Թույլատրված չեն"
|
|
48
|
+
icon={PawIcon}
|
|
49
|
+
style={styles.tile}
|
|
50
|
+
/>
|
|
51
|
+
<InfoTile
|
|
52
|
+
title="Աղբի հեռացում"
|
|
53
|
+
description="Խնդրում ենք աղբը թողնել դրսի աղբամանում"
|
|
54
|
+
icon={TrashIcon}
|
|
55
|
+
style={styles.tile}
|
|
56
|
+
/>
|
|
57
|
+
</View>
|
|
58
|
+
),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const styles = StyleSheet.create({
|
|
62
|
+
frame: {
|
|
63
|
+
padding: 24,
|
|
64
|
+
backgroundColor: "#090909",
|
|
65
|
+
maxWidth: 420,
|
|
66
|
+
},
|
|
67
|
+
grid: {
|
|
68
|
+
padding: 24,
|
|
69
|
+
backgroundColor: "#090909",
|
|
70
|
+
flexDirection: "row",
|
|
71
|
+
flexWrap: "wrap",
|
|
72
|
+
gap: 12,
|
|
73
|
+
maxWidth: 720,
|
|
74
|
+
},
|
|
75
|
+
tile: {
|
|
76
|
+
flexGrow: 1,
|
|
77
|
+
flexBasis: "45%",
|
|
78
|
+
minWidth: 280,
|
|
79
|
+
},
|
|
80
|
+
});
|