@lets-events/react 7.0.1 → 7.1.0

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.
@@ -1,7 +1,7 @@
1
- import React, { ComponentProps, useEffect, useState } from "react";
1
+ import React, { ComponentProps, useRef, useEffect, useState } from "react";
2
+ import { useOnClickOutside } from "../../hooks/useOnClickOutside";
2
3
  import { DayPicker } from "react-day-picker";
3
4
  import { ptBR } from "date-fns/locale";
4
- import { Dialog as Calendaradix } from "@radix-ui/themes";
5
5
  import { parse, isValid, format, addYears } from "date-fns";
6
6
  import { Button } from "../Button";
7
7
  import { Box } from "../Box";
@@ -12,16 +12,16 @@ import {
12
12
  CalendarFooterStyled,
13
13
  CalendarStyled,
14
14
  DayPickerWrapperStyled,
15
- CalendarTitleStyled,
15
+ CalendarButtonStyled,
16
16
  } from "./styledComponents";
17
17
 
18
18
  export type CalendarProps = ComponentProps<typeof CalendarStyled> & {
19
19
  calendarLayout?: "label" | "dropdown" | "dropdown-months" | "dropdown-years";
20
20
  selected: Date | undefined;
21
21
  setSelected: React.Dispatch<React.SetStateAction<Date | undefined>>;
22
+ position?: "top" | "bottom";
22
23
  action?: boolean;
23
24
  actionText?: string;
24
- onAction?: () => void;
25
25
  };
26
26
 
27
27
  function formatToDateMask(value: string): string {
@@ -38,16 +38,22 @@ export function Calendar({
38
38
  calendarLayout,
39
39
  selected,
40
40
  setSelected,
41
- onAction,
41
+ position = "bottom",
42
42
  ...props
43
43
  }: CalendarProps) {
44
44
  const [inputValue, setInputValue] = useState("");
45
+ const [showContainer, setShowCalendar] = useState(false);
45
46
 
47
+ const dropdownRef = useRef<HTMLDivElement>(null);
48
+
49
+ useOnClickOutside(dropdownRef, () => {
50
+ console.warn("Clicked outside");
51
+ setShowCalendar(false);
52
+ });
46
53
  const today = new Date();
47
54
  const maxDate = addYears(today, 20);
48
55
 
49
56
  useEffect(() => {
50
- console.log("selected", selected);
51
57
  if (selected) {
52
58
  setInputValue(format(selected, "dd/MM/yyyy"));
53
59
  } else {
@@ -68,57 +74,60 @@ export function Calendar({
68
74
  };
69
75
 
70
76
  return (
71
- <Calendaradix.Root>
72
- <CalendarStyled {...props}>
73
- <Calendaradix.Trigger>
77
+ <div>
78
+ <CalendarStyled {...props} ref={dropdownRef}>
79
+ <CalendarButtonStyled onClick={() => setShowCalendar((prev) => !prev)}>
74
80
  <TextField
75
81
  placeholder="00/00/0000"
76
82
  type="text"
77
83
  value={inputValue}
78
84
  onChange={handleInputChange}
79
85
  inputMode="numeric"
86
+ textAlign={"right"}
80
87
  >
81
88
  <TextFieldSlot>
82
89
  <Icon name="calendar" size={"xl"} />
83
90
  </TextFieldSlot>
84
91
  </TextField>
85
- </Calendaradix.Trigger>
86
-
87
- <CalendarContentStyled>
88
- <CalendarTitleStyled>Calendário</CalendarTitleStyled>
89
- <Box>
90
- <DayPickerWrapperStyled>
91
- <DayPicker
92
- mode="single"
93
- captionLayout={calendarLayout}
94
- selected={selected}
95
- onSelect={setSelected}
96
- required
97
- locale={ptBR}
98
- disabled={{ before: today }}
99
- startMonth={today}
100
- endMonth={maxDate}
101
- />
102
- </DayPickerWrapperStyled>
103
- </Box>
92
+ </CalendarButtonStyled>
93
+ {showContainer && (
94
+ <CalendarContentStyled
95
+ style={position === "top" ? { bottom: "110%" } : { top: "110%" }}
96
+ >
97
+ <Box>
98
+ <DayPickerWrapperStyled>
99
+ <DayPicker
100
+ mode="single"
101
+ captionLayout={calendarLayout}
102
+ selected={selected}
103
+ onSelect={setSelected}
104
+ required
105
+ locale={ptBR}
106
+ disabled={{ before: today }}
107
+ startMonth={today}
108
+ endMonth={maxDate}
109
+ />
110
+ </DayPickerWrapperStyled>
111
+ </Box>
104
112
 
105
- {action && onAction && (
106
- <CalendarFooterStyled>
107
- <Calendaradix.Close>
113
+ {action && (
114
+ <CalendarFooterStyled>
108
115
  <Button
109
116
  variant="text"
110
117
  color="brand"
111
- onClick={onAction}
118
+ onClick={() => {
119
+ setShowCalendar(false);
120
+ }}
112
121
  typography="buttonMedium"
113
122
  fontWeight="medium"
114
123
  >
115
124
  {actionText ?? "Aplicar"}
116
125
  </Button>
117
- </Calendaradix.Close>
118
- </CalendarFooterStyled>
119
- )}
120
- </CalendarContentStyled>
126
+ </CalendarFooterStyled>
127
+ )}
128
+ </CalendarContentStyled>
129
+ )}
121
130
  </CalendarStyled>
122
- </Calendaradix.Root>
131
+ </div>
123
132
  );
124
133
  }
@@ -1,18 +1,25 @@
1
- import { Dialog as Calendaradix } from "@radix-ui/themes";
2
-
3
1
  import { styled } from "../../styles"
4
2
 
5
3
  export const CalendarStyled = styled('div', {
6
4
  fontFamily: '$default',
7
5
  lineHeight: '$base',
8
6
  fontSize: '$14',
9
- maxWidth: '200px',
10
7
  borderRadius: '$sm',
8
+ position: 'relative',
11
9
  })
12
- export const CalendarTitleStyled = styled(Calendaradix.Title, {
13
- display: 'none',
10
+ export const CalendarButtonStyled = styled('button', {
11
+ backgroundColor: 'transparent',
12
+ border: 'none',
13
+ maxWidth: '200px',
14
+ padding: '0',
15
+ '> div > div': {
16
+ paddingLeft: '1rem',
17
+ 'input': {
18
+ textAlign: 'right',
19
+ }
20
+ },
14
21
  })
15
- export const CalendarContentStyled = styled(Calendaradix.Content, {
22
+ export const CalendarContentStyled = styled('div', {
16
23
  fontFamily: '$default',
17
24
  lineHeight: '$base',
18
25
  fontSize: '$14',
@@ -21,6 +28,10 @@ export const CalendarContentStyled = styled(Calendaradix.Content, {
21
28
  border: '1px solid $neutral300',
22
29
  borderRadius: '$sm',
23
30
  boxShadow: '0px 2px 8px 0px $shadow50',
31
+ position: 'absolute',
32
+ left: '0',
33
+ backgroundColor: '$neutral50',
34
+ zIndex: '999999',
24
35
  })
25
36
 
26
37
  export const CalendarFooterStyled = styled('div', {
@@ -157,7 +157,7 @@ export const TextFieldSlotStyled = styled(TextFieldRadix.Slot, {
157
157
  })
158
158
 
159
159
  export type TextFieldProps = ComponentProps<typeof TextFieldStyled> & {
160
- addon?:string
160
+ addon?: string
161
161
  placeholder?: string
162
162
  children?: React.ReactNode
163
163
  isValid?: boolean
@@ -182,14 +182,17 @@ export type TextFieldSlotProps = Omit<ComponentProps<typeof TextFieldStyled>, 'c
182
182
  const InputAddon = styled(TextStyle, {
183
183
  boxSizing: 'border-box',
184
184
  border: '1px solid $dark300',
185
- height: '$40',
186
- padding: '0',
185
+ height: '$40',
186
+ padding: '0 $12',
187
187
  color: '$dark600',
188
188
  borderRadius: '$sm 0px 0px $sm',
189
189
  borderRightWidth: '0px',
190
190
  margin: 'auto 0',
191
191
  display: 'flex',
192
- alignItems: 'center'
192
+ flexWrap: 'nowrap',
193
+ alignItems: 'center',
194
+ whiteSpace: 'nowrap',
195
+ lineHeight: 1,
193
196
  })
194
197
 
195
198
  export function TextField({
@@ -204,7 +207,7 @@ export function TextField({
204
207
  ...props
205
208
  }: TextFieldProps) {
206
209
  return (
207
- <Flex gap={'0'} css={{width: '100%'}}>
210
+ <Flex gap={'0'} css={{ width: '100%' }}>
208
211
  {!!addon && (
209
212
  <InputAddon typography="labelSmall">{addon}</InputAddon>
210
213
  )}
@@ -215,18 +218,18 @@ export function TextField({
215
218
  typography={typography}
216
219
  fontWeight={fontWeight}
217
220
  textAlign={textAlign}
218
- style={!!addon ? {borderTopLeftRadius:'0px', borderBottomLeftRadius: '0px'}: undefined}
221
+ style={!!addon ? { borderTopLeftRadius: '0px', borderBottomLeftRadius: '0px' } : undefined}
219
222
  {...props}
220
- >
223
+ >
221
224
  {children}
222
225
  {isValid && (
223
226
  <TextFieldSlot
224
- position='flex-end'
225
- name={name}
226
- color={color as TextFieldSlotProps['color']}
227
- typography={typography}
228
- fontWeight={fontWeight}
229
- textAlign={textAlign}
227
+ position='flex-end'
228
+ name={name}
229
+ color={color as TextFieldSlotProps['color']}
230
+ typography={typography}
231
+ fontWeight={fontWeight}
232
+ textAlign={textAlign}
230
233
  >
231
234
  <Icon name='check' />
232
235
  </TextFieldSlot>
@@ -1,29 +1,39 @@
1
- import React, { useCallback, useState } from "react";
2
- import { Dialog as TimePickerRadix } from "@radix-ui/themes";
1
+ import React, { useCallback, useRef, useState } from "react";
3
2
  import { Box } from "./Box";
4
3
  import { Button } from "./Button";
5
4
  import { TextField, TextFieldSlot } from "./TextField";
6
5
  import { Text } from "./Text";
7
6
  import Icon from "./Icon";
8
7
  import { styled } from "../styles";
8
+ import { useOnClickOutside } from "../hooks/useOnClickOutside";
9
9
 
10
10
  export const TimePickerStyled = styled("div", {
11
+ position: "relative",
11
12
  fontFamily: "$default",
12
13
  lineHeight: "$base",
13
14
  fontSize: "$14",
14
15
  maxWidth: "200px",
15
16
  borderRadius: "$sm",
17
+ "> div > div": {
18
+ paddingLeft: "1rem",
19
+ input: {
20
+ textAlign: "right",
21
+ },
22
+ },
16
23
  });
17
- export const TimePickerTitleStyled = styled(TimePickerRadix.Title, {
18
- display: "none",
19
- });
20
- export const TimePickerDialogStyled = styled(TimePickerRadix.Content, {
24
+
25
+ export const TimePickerDropdownStyled = styled("div", {
26
+ position: "absolute",
27
+ left: 0,
28
+ zIndex: 10,
21
29
  width: "100%",
22
30
  maxWidth: "8.875rem",
31
+ backgroundColor: "$neutral50",
23
32
  border: "1px solid $neutral300",
24
33
  borderRadius: "$sm",
25
34
  boxShadow: "0px 2px 8px 0px $shadow50",
26
35
  });
36
+
27
37
  export const TimePickerFooterStyled = styled("div", {
28
38
  borderTop: "2px solid $neutral100",
29
39
  padding: "$4 $16",
@@ -32,6 +42,7 @@ export const TimePickerFooterStyled = styled("div", {
32
42
  alignItems: "center",
33
43
  height: "3rem",
34
44
  });
45
+
35
46
  export const TimerPickerContentStyled = styled("div", {
36
47
  display: "flex",
37
48
  gap: "$16",
@@ -40,21 +51,36 @@ export const TimerPickerContentStyled = styled("div", {
40
51
  "& > div:nth-child(2)": {
41
52
  order: 2,
42
53
  },
54
+ input: {
55
+ padding: "0",
56
+ textAlign: "center!important",
57
+ },
43
58
  });
59
+
44
60
  export type TimePickerProps = {
45
61
  selected: string | undefined;
46
62
  setSelected: React.Dispatch<React.SetStateAction<string | undefined>>;
63
+ position?: "bottom" | "top";
47
64
  };
48
65
 
49
- export function TimePicker({ selected, setSelected }: TimePickerProps) {
66
+ export function TimePicker({
67
+ selected,
68
+ setSelected,
69
+ position = "bottom",
70
+ }: TimePickerProps) {
50
71
  const [hours, setHours] = useState("00");
51
72
  const [minutes, setMinutes] = useState("00");
73
+ const [isOpen, setIsOpen] = useState(false);
74
+ const dropdownRef = useRef(null);
75
+
76
+ useOnClickOutside(dropdownRef, () => setIsOpen(false));
52
77
 
53
78
  const pad = (num: number) => String(num).padStart(2, "0");
54
79
 
55
80
  const handleInputValue = useCallback(
56
81
  (time: string) => {
57
82
  setSelected(time);
83
+ setIsOpen(false);
58
84
  },
59
85
  [setSelected]
60
86
  );
@@ -71,7 +97,6 @@ export function TimePicker({ selected, setSelected }: TimePickerProps) {
71
97
  },
72
98
  [hours, minutes]
73
99
  );
74
-
75
100
  const handleDecrement = useCallback(
76
101
  (type: "hours" | "minutes") => {
77
102
  if (type === "hours") {
@@ -86,24 +111,25 @@ export function TimePicker({ selected, setSelected }: TimePickerProps) {
86
111
  );
87
112
 
88
113
  return (
89
- <TimePickerRadix.Root>
90
- <TimePickerStyled>
91
- <TimePickerRadix.Trigger>
92
- <TextField
93
- value={selected}
94
- readOnly
95
- type="text"
96
- placeholder="00:00"
97
- typography="labelSmall"
98
- fontWeight="regular"
99
- >
100
- <TextFieldSlot>
101
- <Icon name="clock" size="xl" />
102
- </TextFieldSlot>
103
- </TextField>
104
- </TimePickerRadix.Trigger>
105
- <TimePickerDialogStyled>
106
- <TimePickerTitleStyled>Horário</TimePickerTitleStyled>
114
+ <TimePickerStyled ref={dropdownRef}>
115
+ <TextField
116
+ value={selected}
117
+ readOnly
118
+ type="text"
119
+ placeholder="00:00"
120
+ typography="labelSmall"
121
+ fontWeight="regular"
122
+ onClick={() => setIsOpen((prev) => !prev)}
123
+ >
124
+ <TextFieldSlot>
125
+ <Icon name="clock" size="xl" />
126
+ </TextFieldSlot>
127
+ </TextField>
128
+
129
+ {isOpen && (
130
+ <TimePickerDropdownStyled
131
+ style={position === "top" ? { bottom: "110%" } : { top: "110%" }}
132
+ >
107
133
  <TimerPickerContentStyled>
108
134
  {["hours", "minutes"].map((unit) => (
109
135
  <Box
@@ -143,9 +169,7 @@ export function TimePicker({ selected, setSelected }: TimePickerProps) {
143
169
  typography="labelSmall"
144
170
  fontWeight="regular"
145
171
  textAlign="center"
146
- style={{ padding: "4px" }}
147
172
  />
148
-
149
173
  <Button
150
174
  variant="text"
151
175
  onClick={() => handleDecrement(unit as "hours" | "minutes")}
@@ -172,20 +196,18 @@ export function TimePicker({ selected, setSelected }: TimePickerProps) {
172
196
  <Text>:</Text>
173
197
  </TimerPickerContentStyled>
174
198
  <TimePickerFooterStyled>
175
- <TimePickerRadix.Close>
176
- <Button
177
- variant="text"
178
- color="brand"
179
- onClick={() => handleInputValue(`${hours}:${minutes}`)}
180
- typography="buttonMedium"
181
- fontWeight="medium"
182
- >
183
- Aplicar
184
- </Button>
185
- </TimePickerRadix.Close>
199
+ <Button
200
+ variant="text"
201
+ color="brand"
202
+ onClick={() => handleInputValue(`${hours}:${minutes}`)}
203
+ typography="buttonMedium"
204
+ fontWeight="medium"
205
+ >
206
+ Aplicar
207
+ </Button>
186
208
  </TimePickerFooterStyled>
187
- </TimePickerDialogStyled>
188
- </TimePickerStyled>
189
- </TimePickerRadix.Root>
209
+ </TimePickerDropdownStyled>
210
+ )}
211
+ </TimePickerStyled>
190
212
  );
191
213
  }
@@ -0,0 +1,20 @@
1
+ import { useEffect } from "react";
2
+
3
+ export function useOnClickOutside(ref: any, handler: () => void) {
4
+ useEffect(() => {
5
+ const listener = (event: MouseEvent | TouchEvent) => {
6
+ if (!ref.current || ref.current.contains(event.target)) {
7
+ return;
8
+ }
9
+ handler();
10
+ };
11
+
12
+ document.addEventListener("mousedown", listener);
13
+ document.addEventListener("touchstart", listener);
14
+
15
+ return () => {
16
+ document.removeEventListener("mousedown", listener);
17
+ document.removeEventListener("touchstart", listener);
18
+ };
19
+ }, [ref, handler]);
20
+ }