@ecohouse/ui 0.1.31 → 0.1.33

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.
Files changed (94) hide show
  1. package/README.md +5 -0
  2. package/dist/index.cjs +1594 -523
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +218 -10
  5. package/dist/index.d.ts +218 -10
  6. package/dist/index.js +1516 -471
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/components/AccountHeader/AccountHeader.stories.tsx +70 -0
  10. package/src/components/AccountHeader/AccountHeader.tsx +224 -0
  11. package/src/components/AccountHeader/index.ts +2 -0
  12. package/src/components/AvatarSimple/AvatarSimple.tsx +2 -2
  13. package/src/components/Button/Button.tsx +4 -1
  14. package/src/components/DatePicker/DatePicker.tsx +129 -54
  15. package/src/components/FloatingActionButton/FloatingActionButton.stories.tsx +42 -0
  16. package/src/components/FloatingActionButton/FloatingActionButton.tsx +77 -0
  17. package/src/components/FloatingActionButton/index.ts +2 -0
  18. package/src/components/InfoTile/InfoTile.stories.tsx +80 -0
  19. package/src/components/InfoTile/InfoTile.tsx +118 -0
  20. package/src/components/InfoTile/index.ts +2 -0
  21. package/src/components/PaginationDots/PaginationDots.stories.tsx +48 -0
  22. package/src/components/PaginationDots/PaginationDots.tsx +111 -0
  23. package/src/components/PaginationDots/index.ts +2 -0
  24. package/src/components/Select/Select.tsx +5 -2
  25. package/src/components/Sidebar/Sidebar.tsx +25 -11
  26. package/src/components/StatusBadge/StatusBadge.stories.tsx +54 -0
  27. package/src/components/StatusBadge/StatusBadge.tsx +108 -0
  28. package/src/components/StatusBadge/index.ts +2 -0
  29. package/src/components/StepList/StepList.stories.tsx +37 -0
  30. package/src/components/StepList/StepList.tsx +152 -0
  31. package/src/components/StepList/index.ts +2 -0
  32. package/src/components/StepPager/StepPager.tsx +150 -0
  33. package/src/components/StepPager/index.ts +2 -0
  34. package/src/components/index.ts +21 -0
  35. package/src/icons/AlertTriangle/AlertTriangleIcon.tsx +19 -0
  36. package/src/icons/AlertTriangle/AlertTriangleIcon.web.tsx +18 -0
  37. package/src/icons/AlertTriangle/alertTrianglePath.ts +3 -0
  38. package/src/icons/AlertTriangle/index.ts +1 -0
  39. package/src/icons/Ban/BanIcon.tsx +15 -0
  40. package/src/icons/Ban/banPath.ts +2 -0
  41. package/src/icons/Ban/index.ts +1 -0
  42. package/src/icons/Bell/BellIcon.tsx +2 -2
  43. package/src/icons/Bell/BellIcon.web.tsx +2 -2
  44. package/src/icons/Bell/bellPath.ts +1 -1
  45. package/src/icons/CottageDetail/CottageDetailIcons.tsx +15 -0
  46. package/src/icons/CottageDetail/CottageDetailIcons.web.tsx +15 -0
  47. package/src/icons/CottageDetail/cottageDetailPaths.ts +3 -0
  48. package/src/icons/CottageDetail/index.ts +1 -0
  49. package/src/icons/Edit/EditIcon.tsx +20 -0
  50. package/src/icons/Edit/EditIcon.web.tsx +19 -0
  51. package/src/icons/Edit/editPath.ts +3 -0
  52. package/src/icons/Edit/index.ts +1 -0
  53. package/src/icons/Fullscreen/FullscreenIcon.tsx +21 -0
  54. package/src/icons/Fullscreen/fullscreenPath.ts +2 -0
  55. package/src/icons/Fullscreen/index.ts +1 -0
  56. package/src/icons/Home/homePath.ts +1 -1
  57. package/src/icons/HouseRules/HouseRulesIcons.tsx +62 -0
  58. package/src/icons/HouseRules/HouseRulesIcons.web.tsx +61 -0
  59. package/src/icons/HouseRules/houseRulesPaths.ts +24 -0
  60. package/src/icons/HouseRules/index.ts +2 -0
  61. package/src/icons/LocationUser/LocationUserIcon.tsx +15 -0
  62. package/src/icons/LocationUser/LocationUserIcon.web.tsx +14 -0
  63. package/src/icons/LocationUser/index.ts +1 -0
  64. package/src/icons/LocationUser/locationUserPath.ts +2 -0
  65. package/src/icons/Lock/LockIcon.tsx +21 -0
  66. package/src/icons/Lock/LockIcon.web.tsx +26 -0
  67. package/src/icons/Lock/index.ts +1 -0
  68. package/src/icons/Lock/lockPath.ts +3 -0
  69. package/src/icons/Refresh/RefreshIcon.tsx +20 -0
  70. package/src/icons/Refresh/RefreshIcon.web.tsx +19 -0
  71. package/src/icons/Refresh/index.ts +1 -0
  72. package/src/icons/Refresh/refreshPath.ts +3 -0
  73. package/src/icons/RefundStatus/RefundStatusIcon.tsx +24 -0
  74. package/src/icons/RefundStatus/RefundStatusIcon.web.tsx +23 -0
  75. package/src/icons/RefundStatus/index.ts +1 -0
  76. package/src/icons/RefundStatus/refundStatusPath.ts +3 -0
  77. package/src/icons/Sort/SortIcon.tsx +3 -3
  78. package/src/icons/Sort/SortIcon.web.tsx +2 -2
  79. package/src/icons/Sort/sortPath.ts +3 -2
  80. package/src/icons/VideoOff/VideoOffIcon.tsx +21 -0
  81. package/src/icons/VideoOff/VideoOffIcon.web.tsx +27 -0
  82. package/src/icons/VideoOff/index.ts +1 -0
  83. package/src/icons/VideoOff/videoOffPath.ts +2 -0
  84. package/src/icons/ZoomOut/ZoomOutIcon.tsx +21 -0
  85. package/src/icons/ZoomOut/index.ts +1 -0
  86. package/src/icons/ZoomOut/zoomOutPath.ts +2 -0
  87. package/src/icons/index.ts +12 -0
  88. package/src/icons/registry.ts +42 -0
  89. package/src/index.ts +34 -0
  90. package/src/theme/colors.test.ts +1 -1
  91. package/src/theme/colors.ts +4 -0
  92. package/src/theme/typography.ts +5 -5
  93. package/src/utils/dateUtils.ts +25 -0
  94. package/src/utils/popoverAlign.ts +85 -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,2 @@
1
+ export { FloatingActionButton } from "./FloatingActionButton";
2
+ export type { FloatingActionButtonProps } from "./FloatingActionButton";
@@ -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
+ });
@@ -0,0 +1,118 @@
1
+ import { forwardRef, useMemo, useRef, type ComponentRef } from "react";
2
+ import {
3
+ StyleSheet,
4
+ Text,
5
+ View,
6
+ type StyleProp,
7
+ type ViewProps,
8
+ type ViewStyle,
9
+ } from "react-native";
10
+ import type { IconComponent, IconProps } from "../../icons";
11
+ import { colors, fonts } from "../../theme";
12
+ import { mergeRefs } from "../../utils/mergeRefs";
13
+ import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
14
+
15
+ export interface InfoTileProps extends Omit<ViewProps, "style" | "children"> {
16
+ title: string;
17
+ /** Optional supporting line under the title. */
18
+ description?: string;
19
+ icon: IconComponent;
20
+ iconProps?: Omit<IconProps, "size">;
21
+ iconSize?: number;
22
+ /** Icon slot fill. Defaults to `grey600`. */
23
+ iconSlotBackgroundColor?: string;
24
+ style?: StyleProp<ViewStyle>;
25
+ className?: string;
26
+ }
27
+
28
+ const ICON_SLOT = 36;
29
+ const ICON_SIZE = 20;
30
+
31
+ /**
32
+ * Compact info tile — icon slot + title (+ optional description).
33
+ * Padding 12, radius 16, icon container 36×36.
34
+ */
35
+ export const InfoTile = forwardRef<ComponentRef<typeof View>, InfoTileProps>(function InfoTile(
36
+ {
37
+ title,
38
+ description,
39
+ icon: Icon,
40
+ iconProps,
41
+ iconSize = ICON_SIZE,
42
+ iconSlotBackgroundColor = colors.grey600,
43
+ style,
44
+ className,
45
+ accessibilityLabel,
46
+ ...rest
47
+ },
48
+ forwardedRef,
49
+ ) {
50
+ const containerRef = useRef<ComponentRef<typeof View>>(null);
51
+ const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
52
+ const hasDescription = Boolean(description?.trim());
53
+
54
+ useApplyWebClassName(containerRef, className);
55
+
56
+ return (
57
+ <View
58
+ {...rest}
59
+ ref={setContainerRef}
60
+ accessibilityRole="text"
61
+ accessibilityLabel={
62
+ accessibilityLabel ?? (hasDescription ? `${title}. ${description}` : title)
63
+ }
64
+ style={[styles.root, style]}
65
+ >
66
+ <View style={[styles.iconSlot, { backgroundColor: iconSlotBackgroundColor }]}>
67
+ <Icon {...iconProps} size={iconSize} color={iconProps?.color ?? colors.white} />
68
+ </View>
69
+ <View style={styles.textBlock}>
70
+ <Text style={styles.title}>{title}</Text>
71
+ {hasDescription ? <Text style={styles.description}>{description}</Text> : null}
72
+ </View>
73
+ </View>
74
+ );
75
+ });
76
+
77
+ const styles = StyleSheet.create({
78
+ root: {
79
+ flexDirection: "row",
80
+ alignItems: "center",
81
+ alignSelf: "flex-start",
82
+ gap: 16,
83
+ padding: 12,
84
+ borderRadius: 16,
85
+ backgroundColor: colors.grey700,
86
+ },
87
+ iconSlot: {
88
+ width: ICON_SLOT,
89
+ height: ICON_SLOT,
90
+ padding: 8,
91
+ borderRadius: 8,
92
+ alignItems: "center",
93
+ justifyContent: "center",
94
+ flexShrink: 0,
95
+ },
96
+ textBlock: {
97
+ flexShrink: 1,
98
+ minWidth: 0,
99
+ flexDirection: "column",
100
+ gap: 6,
101
+ },
102
+ title: {
103
+ fontFamily: fonts.sans,
104
+ fontSize: 14,
105
+ fontWeight: "500",
106
+ lineHeight: 14,
107
+ letterSpacing: 0,
108
+ color: colors.white,
109
+ },
110
+ description: {
111
+ fontFamily: fonts.sans,
112
+ fontSize: 12,
113
+ fontWeight: "400",
114
+ lineHeight: 12,
115
+ letterSpacing: 0,
116
+ color: colors.lightGrey,
117
+ },
118
+ });
@@ -0,0 +1,2 @@
1
+ export { InfoTile } from "./InfoTile";
2
+ export type { InfoTileProps } from "./InfoTile";
@@ -0,0 +1,48 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { useState } from "react";
3
+ import { StyleSheet, View } from "react-native";
4
+ import { PaginationDots } from "./PaginationDots";
5
+
6
+ const meta = {
7
+ title: "Components/PaginationDots",
8
+ component: PaginationDots,
9
+ args: {
10
+ count: 4,
11
+ activeIndex: 0,
12
+ },
13
+ parameters: {
14
+ backgrounds: { default: "black" },
15
+ },
16
+ } satisfies Meta<typeof PaginationDots>;
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof meta>;
20
+
21
+ export const Default: Story = {
22
+ render: (args) => (
23
+ <View style={styles.frame}>
24
+ <PaginationDots {...args} />
25
+ </View>
26
+ ),
27
+ };
28
+
29
+ function InteractivePaginationDots() {
30
+ const [activeIndex, setActiveIndex] = useState(0);
31
+ return (
32
+ <View style={styles.frame}>
33
+ <PaginationDots count={4} activeIndex={activeIndex} onDotPress={setActiveIndex} />
34
+ </View>
35
+ );
36
+ }
37
+
38
+ export const Interactive: Story = {
39
+ render: () => <InteractivePaginationDots />,
40
+ };
41
+
42
+ const styles = StyleSheet.create({
43
+ frame: {
44
+ padding: 24,
45
+ backgroundColor: "#090909",
46
+ alignItems: "center",
47
+ },
48
+ });
@@ -0,0 +1,111 @@
1
+ import { forwardRef, useMemo, useRef, type ComponentRef } from "react";
2
+ import {
3
+ Pressable,
4
+ StyleSheet,
5
+ View,
6
+ type StyleProp,
7
+ type ViewProps,
8
+ type ViewStyle,
9
+ } from "react-native";
10
+ import { colors } from "../../theme";
11
+ import { mergeRefs } from "../../utils/mergeRefs";
12
+ import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
13
+
14
+ export interface PaginationDotsProps extends Omit<ViewProps, "style" | "children"> {
15
+ /** Total number of steps / slides. */
16
+ count: number;
17
+ /** Zero-based active index. */
18
+ activeIndex: number;
19
+ /** Called when a dot is pressed. */
20
+ onDotPress?: (index: number) => void;
21
+ /** Accessible name for the control group. */
22
+ accessibilityLabel?: string;
23
+ style?: StyleProp<ViewStyle>;
24
+ className?: string;
25
+ }
26
+
27
+ /** Total width of the active pill + inactive dots + gaps. */
28
+ const TRACK_WIDTH = 48;
29
+ const DOT_HEIGHT = 6;
30
+ const INACTIVE_SIZE = 6;
31
+ const GAP = 4;
32
+
33
+ function activePillWidth(count: number) {
34
+ if (count <= 1) return TRACK_WIDTH;
35
+ const inactiveCount = count - 1;
36
+ return TRACK_WIDTH - inactiveCount * INACTIVE_SIZE - inactiveCount * GAP;
37
+ }
38
+
39
+ /**
40
+ * Carousel / stepper pagination.
41
+ * Whole track is `48` wide; height `6`; gap `4`; inactive dots `6×6`;
42
+ * active pill takes the remaining width.
43
+ */
44
+ export const PaginationDots = forwardRef<ComponentRef<typeof View>, PaginationDotsProps>(
45
+ function PaginationDots(
46
+ { count, activeIndex, onDotPress, accessibilityLabel, style, className, ...rest },
47
+ forwardedRef,
48
+ ) {
49
+ const containerRef = useRef<ComponentRef<typeof View>>(null);
50
+ const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
51
+ const safeCount = Math.max(0, count);
52
+ const safeActive = safeCount === 0 ? 0 : Math.min(Math.max(activeIndex, 0), safeCount - 1);
53
+ const pillWidth = Math.max(INACTIVE_SIZE, activePillWidth(safeCount));
54
+
55
+ useApplyWebClassName(containerRef, className);
56
+
57
+ if (safeCount <= 0) {
58
+ return null;
59
+ }
60
+
61
+ return (
62
+ <View
63
+ {...rest}
64
+ ref={setContainerRef}
65
+ accessibilityRole="adjustable"
66
+ accessibilityLabel={accessibilityLabel ?? `Slide ${safeActive + 1} of ${safeCount}`}
67
+ style={[styles.root, style]}
68
+ >
69
+ {Array.from({ length: safeCount }, (_, index) => {
70
+ const active = index === safeActive;
71
+ return (
72
+ <Pressable
73
+ key={index}
74
+ accessibilityRole="button"
75
+ accessibilityLabel={`Go to slide ${index + 1}`}
76
+ accessibilityState={{ selected: active }}
77
+ disabled={onDotPress == null}
78
+ hitSlop={8}
79
+ onPress={() => onDotPress?.(index)}
80
+ style={[
81
+ styles.dot,
82
+ active ? [styles.dotActive, { width: pillWidth }] : styles.dotInactive,
83
+ ]}
84
+ />
85
+ );
86
+ })}
87
+ </View>
88
+ );
89
+ },
90
+ );
91
+
92
+ const styles = StyleSheet.create({
93
+ root: {
94
+ width: TRACK_WIDTH,
95
+ flexDirection: "row",
96
+ alignItems: "center",
97
+ justifyContent: "center",
98
+ gap: GAP,
99
+ },
100
+ dot: {
101
+ height: DOT_HEIGHT,
102
+ borderRadius: DOT_HEIGHT,
103
+ },
104
+ dotActive: {
105
+ backgroundColor: colors.white,
106
+ },
107
+ dotInactive: {
108
+ width: INACTIVE_SIZE,
109
+ backgroundColor: colors.whiteAlpha40,
110
+ },
111
+ });
@@ -0,0 +1,2 @@
1
+ export { PaginationDots } from "./PaginationDots";
2
+ export type { PaginationDotsProps } from "./PaginationDots";
@@ -61,6 +61,8 @@ type SelectBaseProps = Omit<ViewProps, "style" | "children"> & {
61
61
  loading?: boolean;
62
62
  /** Text shown when no options match. */
63
63
  emptyText?: string;
64
+ /** Override color for the selected value text in the trigger. */
65
+ valueColor?: string;
64
66
  style?: StyleProp<ViewStyle>;
65
67
  className?: string;
66
68
  };
@@ -264,6 +266,7 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
264
266
  onSearchChange,
265
267
  loading = false,
266
268
  emptyText = "No results",
269
+ valueColor,
267
270
  style,
268
271
  className,
269
272
  accessibilityLabel,
@@ -508,7 +511,7 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
508
511
 
509
512
  const leftIconNode = renderStatefulIcon(leftIcon, UserIcon, {
510
513
  size: ICON_SIZE,
511
- color: error ? colors.red : isOpen ? colors.primary : colors.grey100,
514
+ color: error ? colors.red : colors.primary,
512
515
  });
513
516
  const hasLeftIcon = showLeftIcon || leftIcon != null;
514
517
 
@@ -520,7 +523,7 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
520
523
  const triggerTextColor = hasValue
521
524
  ? error
522
525
  ? colors.red
523
- : colors.white
526
+ : (valueColor ?? colors.white)
524
527
  : error
525
528
  ? colors.red
526
529
  : colors.grey100;
@@ -59,6 +59,8 @@ export interface SidebarProps extends Omit<ViewProps, "style" | "children"> {
59
59
  collapsed?: boolean;
60
60
  defaultCollapsed?: boolean;
61
61
  onCollapsedChange?: (collapsed: boolean) => void;
62
+ /** When false, hides the expand/collapse control. Defaults to `true`. */
63
+ showCollapseToggle?: boolean;
62
64
  style?: StyleProp<ViewStyle>;
63
65
  className?: string;
64
66
  }
@@ -86,6 +88,7 @@ export const Sidebar = forwardRef<ComponentRef<typeof View>, SidebarProps>(funct
86
88
  collapsed: collapsedProp,
87
89
  defaultCollapsed = false,
88
90
  onCollapsedChange,
91
+ showCollapseToggle = true,
89
92
  style,
90
93
  className,
91
94
  ...rest
@@ -136,7 +139,13 @@ export const Sidebar = forwardRef<ComponentRef<typeof View>, SidebarProps>(funct
136
139
  style={[styles.root, { width: widthAnim }, style]}
137
140
  accessibilityRole="menu"
138
141
  >
139
- <View style={[styles.header, collapsed ? styles.headerCollapsed : styles.headerExpanded]}>
142
+ <View
143
+ style={[
144
+ styles.header,
145
+ collapsed ? styles.headerCollapsed : styles.headerExpanded,
146
+ !showCollapseToggle ? styles.headerNoToggle : null,
147
+ ]}
148
+ >
140
149
  <Animated.View
141
150
  pointerEvents={collapsed ? "none" : "auto"}
142
151
  style={[
@@ -157,16 +166,18 @@ export const Sidebar = forwardRef<ComponentRef<typeof View>, SidebarProps>(funct
157
166
  />
158
167
  </Animated.View>
159
168
 
160
- <Pressable
161
- accessibilityRole="button"
162
- accessibilityLabel={collapsed ? expandAccessibilityLabel : collapseAccessibilityLabel}
163
- accessibilityState={{ expanded: !collapsed }}
164
- hitSlop={8}
165
- onPress={() => setCollapsed(!collapsed)}
166
- style={styles.toggleButton}
167
- >
168
- <SidebarIcon color={colors.grey100} size={TOGGLE_SIZE} strokeWidth={2} />
169
- </Pressable>
169
+ {showCollapseToggle ? (
170
+ <Pressable
171
+ accessibilityRole="button"
172
+ accessibilityLabel={collapsed ? expandAccessibilityLabel : collapseAccessibilityLabel}
173
+ accessibilityState={{ expanded: !collapsed }}
174
+ hitSlop={8}
175
+ onPress={() => setCollapsed(!collapsed)}
176
+ style={styles.toggleButton}
177
+ >
178
+ <SidebarIcon color={colors.grey100} size={TOGGLE_SIZE} strokeWidth={2} />
179
+ </Pressable>
180
+ ) : null}
170
181
  </View>
171
182
 
172
183
  <View
@@ -242,6 +253,9 @@ const styles = StyleSheet.create({
242
253
  justifyContent: "space-between",
243
254
  paddingHorizontal: 24,
244
255
  },
256
+ headerNoToggle: {
257
+ justifyContent: "flex-start",
258
+ },
245
259
  headerCollapsed: {
246
260
  justifyContent: "center",
247
261
  paddingHorizontal: 0,
@@ -0,0 +1,54 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { StyleSheet, View } from "react-native";
3
+ import { StatusBadge } from "./StatusBadge";
4
+
5
+ const meta = {
6
+ title: "Components/StatusBadge",
7
+ component: StatusBadge,
8
+ args: {
9
+ label: "Ակտիվ",
10
+ tone: "active",
11
+ },
12
+ parameters: {
13
+ backgrounds: { default: "black" },
14
+ },
15
+ } satisfies Meta<typeof StatusBadge>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Active: Story = {};
21
+
22
+ export const Completed: Story = {
23
+ args: {
24
+ label: "Ավարտված",
25
+ tone: "completed",
26
+ },
27
+ };
28
+
29
+ export const Cancelled: Story = {
30
+ args: {
31
+ label: "Չեղարկված",
32
+ tone: "cancelled",
33
+ },
34
+ };
35
+
36
+ export const AllTones: Story = {
37
+ render: () => (
38
+ <View style={styles.row}>
39
+ <StatusBadge label="Ակտիվ" tone="active" />
40
+ <StatusBadge label="Ավարտված" tone="completed" />
41
+ <StatusBadge label="Չեղարկված" tone="cancelled" />
42
+ </View>
43
+ ),
44
+ };
45
+
46
+ const styles = StyleSheet.create({
47
+ row: {
48
+ flexDirection: "row",
49
+ flexWrap: "wrap",
50
+ gap: 12,
51
+ padding: 24,
52
+ backgroundColor: "#090909",
53
+ },
54
+ });