@ecohouse/ui 0.1.25 → 0.1.26

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": "@ecohouse/ui",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Cross-platform presentation-only UI components for EcoHouse (React Native + React Native Web)",
@@ -68,6 +68,40 @@ export const RangeInProgress: Story = {
68
68
  },
69
69
  };
70
70
 
71
+ const bookingRange = {
72
+ start: new Date(2026, 7, 15),
73
+ end: new Date(2026, 7, 15),
74
+ };
75
+
76
+ const formatBookingDate = (date: Date) =>
77
+ `${String(date.getDate()).padStart(2, "0")}/${String(date.getMonth() + 1).padStart(2, "0")}/${date.getFullYear()}`;
78
+
79
+ export const RangeHorizontal: Story = {
80
+ args: {
81
+ range: true,
82
+ variant: "range-horizontal",
83
+ startLabel: "ՄՈՒՏՔ",
84
+ endLabel: "ԵԼՔ",
85
+ startTime: "10:00",
86
+ endTime: "22:00",
87
+ defaultValue: bookingRange,
88
+ formatValue: formatBookingDate,
89
+ },
90
+ };
91
+
92
+ export const RangeVertical: Story = {
93
+ args: {
94
+ range: true,
95
+ variant: "range-vertical",
96
+ startLabel: "ՄՈՒՏՔ",
97
+ endLabel: "ԵԼՔ",
98
+ startTime: "10:00",
99
+ endTime: "22:00",
100
+ defaultValue: bookingRange,
101
+ formatValue: formatBookingDate,
102
+ },
103
+ };
104
+
71
105
  export const MinMaxDate: Story = {
72
106
  args: {
73
107
  defaultOpen: true,
@@ -17,7 +17,7 @@ import {
17
17
  type ViewProps,
18
18
  type ViewStyle,
19
19
  } from "react-native";
20
- import { CalendarOutlineIcon, ChevronLeftIcon } from "../../icons";
20
+ import { CalendarOutlineIcon, ChevronLeftIcon, ClockFilledIcon } from "../../icons";
21
21
  import { colors, datePickerTypography } from "../../theme";
22
22
  import {
23
23
  addMonths,
@@ -52,11 +52,23 @@ const DEFAULT_MONTH_NAMES = [
52
52
  const DEFAULT_WEEKDAY_LABELS = ["ԵՐ", "ԵՔ", "ՉՐ", "ՀՆ", "ՈՒՐ", "ՇԲ", "ԿՐ"];
53
53
 
54
54
  export type DateRange = { start: Date | null; end: Date | null };
55
+ export type DatePickerVariant = "default" | "range-horizontal" | "range-vertical";
55
56
 
56
57
  const EMPTY_RANGE: DateRange = { start: null, end: null };
57
58
 
58
59
  type DatePickerBaseProps = Omit<ViewProps, "style" | "children"> & {
59
60
  placeholder?: string;
61
+ /** Trigger presentation. Range variants display separate start/end fields. */
62
+ variant?: DatePickerVariant;
63
+ /** Labels shown above the start/end values in range trigger variants. */
64
+ startLabel?: string;
65
+ endLabel?: string;
66
+ /** Optional supplementary times shown beside the start/end dates. */
67
+ startTime?: string;
68
+ endTime?: string;
69
+ /** Per-field placeholders used by range trigger variants. */
70
+ startPlaceholder?: string;
71
+ endPlaceholder?: string;
60
72
  open?: boolean;
61
73
  defaultOpen?: boolean;
62
74
  onOpenChange?: (open: boolean) => void;
@@ -101,6 +113,7 @@ export type DatePickerRangeProps = DatePickerBaseProps & {
101
113
  export type DatePickerProps = DatePickerSingleProps | DatePickerRangeProps;
102
114
 
103
115
  const DEFAULT_WIDTH = 380;
116
+ const RANGE_TRIGGER_WIDTH = 382;
104
117
  const TRIGGER_HEIGHT = 44;
105
118
  const TRIGGER_RADIUS = 60;
106
119
  const MENU_RADIUS = 20;
@@ -109,10 +122,46 @@ const NAV_ICON_SIZE = 20;
109
122
  const DAY_CELL_HEIGHT = 44;
110
123
  const DAY_CIRCLE_SIZE = 44;
111
124
 
125
+ function RangeTriggerField({
126
+ label,
127
+ value,
128
+ time,
129
+ }: {
130
+ label: string;
131
+ value: string;
132
+ time?: string;
133
+ }) {
134
+ return (
135
+ <View style={styles.rangeField}>
136
+ <Text numberOfLines={1} style={styles.rangeLabel}>
137
+ {label}
138
+ </Text>
139
+ <View style={styles.rangeValueRow}>
140
+ <Text numberOfLines={1} style={styles.rangeDate}>
141
+ {value}
142
+ </Text>
143
+ {time ? (
144
+ <View style={styles.rangeTimeWrap}>
145
+ <ClockFilledIcon color={colors.lightGrey} size={16} />
146
+ <Text style={styles.rangeTime}>{time}</Text>
147
+ </View>
148
+ ) : null}
149
+ </View>
150
+ </View>
151
+ );
152
+ }
153
+
112
154
  export const DatePicker = forwardRef<ComponentRef<typeof View>, DatePickerProps>(
113
155
  function DatePicker(props, forwardedRef) {
114
156
  const {
115
157
  placeholder = "Select date",
158
+ variant = "default",
159
+ startLabel = "Start",
160
+ endLabel = "End",
161
+ startTime,
162
+ endTime,
163
+ startPlaceholder = placeholder,
164
+ endPlaceholder = placeholder,
116
165
  open: openProp,
117
166
  defaultOpen = false,
118
167
  onOpenChange,
@@ -298,8 +347,18 @@ export const DatePicker = forwardRef<ComponentRef<typeof View>, DatePickerProps>
298
347
  ? formatValue(singleValue)
299
348
  : placeholder;
300
349
 
301
- const triggerBorderColor = isOpen ? colors.primaryMuted : colors.grey700;
350
+ const usesRangeTrigger = isRange && variant !== "default";
351
+ const usesVerticalRangeTrigger = usesRangeTrigger && variant === "range-vertical";
352
+ const triggerBorderColor = usesRangeTrigger
353
+ ? isOpen
354
+ ? colors.primary
355
+ : colors.grey500
356
+ : isOpen
357
+ ? colors.primaryMuted
358
+ : colors.grey700;
302
359
  const triggerTextColor = hasValue ? colors.white : colors.grey100;
360
+ const startValue = rangeValue.start ? formatValue(rangeValue.start) : startPlaceholder;
361
+ const endValue = rangeValue.end ? formatValue(rangeValue.end) : endPlaceholder;
303
362
 
304
363
  const resolvedAccessibilityLabel = accessibilityLabel ?? placeholder;
305
364
 
@@ -307,7 +366,13 @@ export const DatePicker = forwardRef<ComponentRef<typeof View>, DatePickerProps>
307
366
  <View
308
367
  {...rest}
309
368
  ref={setRootRef}
310
- style={[styles.root, isOpen && styles.rootOpen, disabled && styles.disabled, style]}
369
+ style={[
370
+ styles.root,
371
+ usesRangeTrigger && styles.rangeRoot,
372
+ isOpen && styles.rootOpen,
373
+ disabled && styles.disabled,
374
+ style,
375
+ ]}
311
376
  accessibilityState={{ disabled, expanded: isOpen }}
312
377
  >
313
378
  <View style={[styles.triggerWrap, isOpen && styles.triggerWrapOpen]}>
@@ -320,19 +385,43 @@ export const DatePicker = forwardRef<ComponentRef<typeof View>, DatePickerProps>
320
385
  accessibilityState={{ disabled, expanded: isOpen }}
321
386
  style={[
322
387
  styles.trigger,
323
- { borderColor: triggerBorderColor, backgroundColor: colors.grey700 },
388
+ usesRangeTrigger && styles.rangeTrigger,
389
+ usesRangeTrigger &&
390
+ (usesVerticalRangeTrigger
391
+ ? styles.rangeTriggerVertical
392
+ : styles.rangeTriggerHorizontal),
393
+ {
394
+ borderColor: triggerBorderColor,
395
+ backgroundColor: usesRangeTrigger ? colors.grey800 : colors.grey700,
396
+ },
324
397
  ]}
325
398
  >
326
- <Text style={[styles.triggerText, { color: triggerTextColor }]} numberOfLines={1}>
327
- {triggerLabel}
328
- </Text>
329
- <View style={styles.iconSlot}>
330
- <CalendarOutlineIcon size={ICON_SIZE} color={colors.grey100} />
331
- </View>
399
+ {usesRangeTrigger ? (
400
+ <>
401
+ <RangeTriggerField label={startLabel} time={startTime} value={startValue} />
402
+ <View
403
+ style={
404
+ usesVerticalRangeTrigger
405
+ ? styles.rangeDividerVertical
406
+ : styles.rangeDividerHorizontal
407
+ }
408
+ />
409
+ <RangeTriggerField label={endLabel} time={endTime} value={endValue} />
410
+ </>
411
+ ) : (
412
+ <>
413
+ <Text style={[styles.triggerText, { color: triggerTextColor }]} numberOfLines={1}>
414
+ {triggerLabel}
415
+ </Text>
416
+ <View style={styles.iconSlot}>
417
+ <CalendarOutlineIcon size={ICON_SIZE} color={colors.grey100} />
418
+ </View>
419
+ </>
420
+ )}
332
421
  </Pressable>
333
422
 
334
423
  {isOpen ? (
335
- <View style={[styles.menu, calendarStyle]}>
424
+ <View style={[styles.menu, usesRangeTrigger && styles.rangeMenu, calendarStyle]}>
336
425
  <View style={styles.calendarHeader}>
337
426
  <Pressable
338
427
  onPress={handlePrevMonth}
@@ -502,6 +591,9 @@ const styles = StyleSheet.create({
502
591
  rootOpen: {
503
592
  zIndex: 30,
504
593
  },
594
+ rangeRoot: {
595
+ width: RANGE_TRIGGER_WIDTH,
596
+ },
505
597
  disabled: {
506
598
  opacity: 0.6,
507
599
  },
@@ -528,6 +620,65 @@ const styles = StyleSheet.create({
528
620
  flex: 1,
529
621
  minWidth: 0,
530
622
  },
623
+ rangeTrigger: {
624
+ paddingVertical: 0,
625
+ paddingHorizontal: 0,
626
+ borderRadius: 15,
627
+ gap: 0,
628
+ },
629
+ rangeTriggerHorizontal: {
630
+ height: 78,
631
+ flexDirection: "row",
632
+ },
633
+ rangeTriggerVertical: {
634
+ height: 156,
635
+ flexDirection: "column",
636
+ },
637
+ rangeField: {
638
+ flex: 1,
639
+ alignSelf: "stretch",
640
+ minWidth: 0,
641
+ justifyContent: "center",
642
+ gap: 8,
643
+ paddingVertical: 12,
644
+ paddingHorizontal: 16,
645
+ },
646
+ rangeLabel: {
647
+ ...datePickerTypography.rangeLabel,
648
+ color: colors.whiteAlpha60,
649
+ },
650
+ rangeValueRow: {
651
+ minWidth: 0,
652
+ flexDirection: "row",
653
+ alignItems: "center",
654
+ justifyContent: "space-between",
655
+ gap: 12,
656
+ },
657
+ rangeDate: {
658
+ ...datePickerTypography.rangeDate,
659
+ flexShrink: 1,
660
+ color: colors.white,
661
+ },
662
+ rangeTimeWrap: {
663
+ flexShrink: 0,
664
+ flexDirection: "row",
665
+ alignItems: "center",
666
+ gap: 3,
667
+ },
668
+ rangeTime: {
669
+ ...datePickerTypography.rangeTime,
670
+ color: colors.lightGrey,
671
+ },
672
+ rangeDividerHorizontal: {
673
+ width: 1,
674
+ alignSelf: "stretch",
675
+ backgroundColor: colors.grey500,
676
+ },
677
+ rangeDividerVertical: {
678
+ height: 1,
679
+ alignSelf: "stretch",
680
+ backgroundColor: colors.grey500,
681
+ },
531
682
  iconSlot: {
532
683
  width: ICON_SIZE,
533
684
  height: ICON_SIZE,
@@ -555,6 +706,9 @@ const styles = StyleSheet.create({
555
706
  },
556
707
  }),
557
708
  },
709
+ rangeMenu: {
710
+ width: RANGE_TRIGGER_WIDTH,
711
+ },
558
712
  calendarHeader: {
559
713
  height: 36,
560
714
  flexDirection: "row",
@@ -3,5 +3,6 @@ export type {
3
3
  DatePickerProps,
4
4
  DatePickerSingleProps,
5
5
  DatePickerRangeProps,
6
+ DatePickerVariant,
6
7
  DateRange,
7
8
  } from "./DatePicker";
@@ -27,6 +27,7 @@ export type {
27
27
  DatePickerProps,
28
28
  DatePickerSingleProps,
29
29
  DatePickerRangeProps,
30
+ DatePickerVariant,
30
31
  DateRange,
31
32
  } from "./DatePicker";
32
33
 
package/src/index.ts CHANGED
@@ -36,6 +36,7 @@ export type {
36
36
  DatePickerProps,
37
37
  DatePickerSingleProps,
38
38
  DatePickerRangeProps,
39
+ DatePickerVariant,
39
40
  DateRange,
40
41
  InputIcon,
41
42
  InputProps,
@@ -23,6 +23,7 @@ export const colors = {
23
23
  black: "#000000",
24
24
  white: "#ffffff",
25
25
  whiteAlpha86: "#FFFFFFDB",
26
+ whiteAlpha60: "#FFFFFF99",
26
27
  whiteAlpha40: "#FFFFFF66",
27
28
  whiteAlpha20: "#FFFFFF33",
28
29
  whiteAlpha6: "#FFFFFF0F",
@@ -196,6 +196,27 @@ export const datePickerTypography = {
196
196
  lineHeight: 12,
197
197
  letterSpacing: 0.36,
198
198
  },
199
+ rangeLabel: {
200
+ fontFamily: fonts.sans,
201
+ fontSize: 13,
202
+ fontWeight: "500" as const,
203
+ lineHeight: 13,
204
+ letterSpacing: 0,
205
+ },
206
+ rangeDate: {
207
+ fontFamily: fonts.sans,
208
+ fontSize: 16,
209
+ fontWeight: "500" as const,
210
+ lineHeight: 16,
211
+ letterSpacing: 0,
212
+ },
213
+ rangeTime: {
214
+ fontFamily: fonts.sans,
215
+ fontSize: 14,
216
+ fontWeight: "400" as const,
217
+ lineHeight: 14,
218
+ letterSpacing: 0,
219
+ },
199
220
  /** Calendar month/year heading — Bold, centered, White. */
200
221
  monthHeading: {
201
222
  fontFamily: fonts.sans,