@granto-umbrella/umbrella-components 2.2.8 → 2.3.1
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 +1 -1
- package/src/components/molecules/Calendar/Calendar.tsx +19 -51
- package/src/components/molecules/Popover/Popover.tsx +3 -9
- package/src/components/molecules/TabToggle/TabToggle.styles.ts +54 -0
- package/src/components/molecules/TabToggle/TabToggle.tsx +35 -0
- package/src/components/organisms/CalendarPopover/CalendarPopover.tsx +57 -0
- package/src/components/organisms/Form/Form.tsx +42 -59
- package/src/styles/tokens/radius.ts +2 -0
package/package.json
CHANGED
|
@@ -1,63 +1,31 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
2
|
import * as React from "react";
|
|
2
3
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
3
|
-
import { DayPicker } from "react-day-picker";
|
|
4
|
+
import { DayPicker as RDPDayPicker } from "react-day-picker";
|
|
5
|
+
import { StyledDayPicker } from "./Calendar.styles";
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
export type CalendarProps = React.ComponentProps<typeof RDPDayPicker>;
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function Calendar({
|
|
10
|
-
className,
|
|
11
|
-
classNames,
|
|
12
|
-
showOutsideDays = true,
|
|
13
|
-
...props
|
|
14
|
-
}: CalendarProps) {
|
|
9
|
+
function Calendar({ className, ...props }: CalendarProps) {
|
|
15
10
|
return (
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
nav_button_next: "absolute right-1",
|
|
30
|
-
table: "w-full border-collapse space-y-1",
|
|
31
|
-
head_row: "flex",
|
|
32
|
-
head_cell:
|
|
33
|
-
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
|
34
|
-
row: "flex w-full mt-2",
|
|
35
|
-
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
|
36
|
-
day: cn("h-9 w-9 p-0 font-normal aria-selected:opacity-100"),
|
|
37
|
-
day_range_end: "day-range-end",
|
|
38
|
-
day_selected:
|
|
39
|
-
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
|
40
|
-
day_today: "bg-accent text-accent-foreground",
|
|
41
|
-
day_outside:
|
|
42
|
-
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
|
|
43
|
-
day_disabled: "text-muted-foreground opacity-50",
|
|
44
|
-
day_range_middle:
|
|
45
|
-
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
|
46
|
-
day_hidden: "invisible",
|
|
47
|
-
...classNames,
|
|
48
|
-
}}
|
|
49
|
-
components={{
|
|
50
|
-
IconLeft: ({ className, ...props }) => (
|
|
51
|
-
<ChevronLeft className={cn("h-4 w-4", className)} {...props} />
|
|
52
|
-
),
|
|
53
|
-
IconRight: ({ className, ...props }) => (
|
|
54
|
-
<ChevronRight className={cn("h-4 w-4", className)} {...props} />
|
|
55
|
-
),
|
|
56
|
-
}}
|
|
11
|
+
<StyledDayPicker
|
|
12
|
+
className={className}
|
|
13
|
+
showOutsideDays={true}
|
|
14
|
+
components={
|
|
15
|
+
{
|
|
16
|
+
IconPrevious: ({ className, ...props }: { className?: string }) => (
|
|
17
|
+
<ChevronLeft className={className} {...props} />
|
|
18
|
+
),
|
|
19
|
+
IconNext: ({ className, ...props }: { className?: string }) => (
|
|
20
|
+
<ChevronRight className={className} {...props} />
|
|
21
|
+
),
|
|
22
|
+
} as unknown as any
|
|
23
|
+
}
|
|
57
24
|
{...props}
|
|
58
25
|
/>
|
|
59
26
|
);
|
|
60
27
|
}
|
|
28
|
+
|
|
61
29
|
Calendar.displayName = "Calendar";
|
|
62
30
|
|
|
63
31
|
export { Calendar };
|
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
3
|
-
|
|
4
|
-
import { cn } from "../../../lib/utils";
|
|
3
|
+
import { StyledPopoverContent } from "./Popover.styles";
|
|
5
4
|
|
|
6
5
|
const Popover = PopoverPrimitive.Root;
|
|
7
|
-
|
|
8
6
|
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
9
7
|
|
|
10
8
|
const PopoverContent = React.forwardRef<
|
|
11
9
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
12
10
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
|
13
|
-
>(({
|
|
11
|
+
>(({ align = "center", sideOffset = 4, ...props }, ref) => (
|
|
14
12
|
<PopoverPrimitive.Portal>
|
|
15
|
-
<
|
|
13
|
+
<StyledPopoverContent
|
|
16
14
|
ref={ref}
|
|
17
15
|
align={align}
|
|
18
16
|
sideOffset={sideOffset}
|
|
19
|
-
className={cn(
|
|
20
|
-
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
21
|
-
className
|
|
22
|
-
)}
|
|
23
17
|
{...props}
|
|
24
18
|
/>
|
|
25
19
|
</PopoverPrimitive.Portal>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
semanticColors,
|
|
3
|
+
semanticRadius,
|
|
4
|
+
semanticSizes,
|
|
5
|
+
typographyTokens,
|
|
6
|
+
} from "../../../styles/tokens";
|
|
7
|
+
import styled from "styled-components";
|
|
8
|
+
|
|
9
|
+
export const ToggleContainer = styled.div`
|
|
10
|
+
position: relative;
|
|
11
|
+
display: inline-flex;
|
|
12
|
+
background: transparent;
|
|
13
|
+
border-radius: ${semanticRadius.global.radius.md2};
|
|
14
|
+
padding: ${semanticSizes.global.padding.xs};
|
|
15
|
+
white-space: nowrap;
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export const Slider = styled.div<{
|
|
19
|
+
activeIndex: number;
|
|
20
|
+
count: number;
|
|
21
|
+
}>`
|
|
22
|
+
position: absolute;
|
|
23
|
+
top: ${semanticSizes.global.padding.xs};
|
|
24
|
+
left: ${({ activeIndex, count }) => (activeIndex * 100) / count}%;
|
|
25
|
+
width: ${({ count }) => 100 / count}%;
|
|
26
|
+
height: calc(100% - ${semanticSizes.global.padding.xs} * 2);
|
|
27
|
+
background: ${semanticColors.base.background};
|
|
28
|
+
border-radius: ${semanticRadius.global.radius.md2};
|
|
29
|
+
transition: left 200ms ease-in-out;
|
|
30
|
+
z-index: 0;
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export const TabButton = styled.button<{ active: boolean }>`
|
|
34
|
+
position: relative;
|
|
35
|
+
z-index: 1;
|
|
36
|
+
display: flex;
|
|
37
|
+
width: 230px;
|
|
38
|
+
justify-content: center;
|
|
39
|
+
align-items: center;
|
|
40
|
+
padding: ${semanticSizes.global.padding.sm} ${semanticSizes.global.gap["3xl"]};
|
|
41
|
+
font-size: ${typographyTokens.fontSizes.bodyS};
|
|
42
|
+
color: ${({ active }) =>
|
|
43
|
+
active ? semanticColors.base.text : semanticColors.neutral[400]};
|
|
44
|
+
background: none;
|
|
45
|
+
border: none;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
transition: color 200ms;
|
|
48
|
+
|
|
49
|
+
white-space: nowrap;
|
|
50
|
+
|
|
51
|
+
&:focus {
|
|
52
|
+
outline: none;
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { ToggleContainer, Slider, TabButton } from "./TabToggle.styles";
|
|
3
|
+
|
|
4
|
+
export type TabItem = {
|
|
5
|
+
title: string;
|
|
6
|
+
quantity: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type TabToggleProps = {
|
|
10
|
+
items: TabItem[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const TabToggle: React.FC<TabToggleProps> = ({ items }) => {
|
|
14
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
15
|
+
const count = items.length;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<ToggleContainer>
|
|
19
|
+
{/* Slider agora sabe quantas abas existem */}
|
|
20
|
+
<Slider activeIndex={activeIndex} count={count} />
|
|
21
|
+
|
|
22
|
+
{items.map(({ title, quantity }, i) => (
|
|
23
|
+
<TabButton
|
|
24
|
+
key={title}
|
|
25
|
+
active={i === activeIndex}
|
|
26
|
+
onClick={() => setActiveIndex(i)}
|
|
27
|
+
>
|
|
28
|
+
{title} ({quantity})
|
|
29
|
+
</TabButton>
|
|
30
|
+
))}
|
|
31
|
+
</ToggleContainer>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default TabToggle;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { format } from "date-fns";
|
|
3
|
+
import { Calendar, CalendarIcon } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../../lib/utils";
|
|
6
|
+
import { FormControl } from "../Form/Form";
|
|
7
|
+
import {
|
|
8
|
+
Popover,
|
|
9
|
+
PopoverContent,
|
|
10
|
+
PopoverTrigger,
|
|
11
|
+
} from "../../molecules/Popover/Popover";
|
|
12
|
+
import Button from "../../atoms/Button/Button";
|
|
13
|
+
|
|
14
|
+
export type DatePickerFieldProps = {
|
|
15
|
+
formField: {
|
|
16
|
+
value: Date | null;
|
|
17
|
+
onChange: (date: Date | null) => void;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const DatePickerField: React.FC<DatePickerFieldProps> = ({ formField }) => {
|
|
22
|
+
return (
|
|
23
|
+
<Popover>
|
|
24
|
+
<PopoverTrigger asChild>
|
|
25
|
+
<FormControl>
|
|
26
|
+
<Button
|
|
27
|
+
variant="outline"
|
|
28
|
+
className={cn(
|
|
29
|
+
"w-[240px] pl-3 text-left font-normal",
|
|
30
|
+
!formField.value && "text-muted-foreground"
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
{formField.value ? (
|
|
34
|
+
format(formField.value, "PPP")
|
|
35
|
+
) : (
|
|
36
|
+
<span>CALENDÁRIO</span>
|
|
37
|
+
)}
|
|
38
|
+
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
|
39
|
+
</Button>
|
|
40
|
+
</FormControl>
|
|
41
|
+
</PopoverTrigger>
|
|
42
|
+
<PopoverContent className="w-auto p-0" align="start">
|
|
43
|
+
<Calendar
|
|
44
|
+
mode="single"
|
|
45
|
+
selected={formField.value}
|
|
46
|
+
onSelect={formField.onChange}
|
|
47
|
+
disabled={(date) =>
|
|
48
|
+
date > new Date() || date < new Date("1900-01-01")
|
|
49
|
+
}
|
|
50
|
+
initialFocus
|
|
51
|
+
/>
|
|
52
|
+
</PopoverContent>
|
|
53
|
+
</Popover>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default DatePickerField;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
/* eslint-disable react-refresh/only-export-components */
|
|
2
|
+
// Form.tsx
|
|
1
3
|
import * as React from "react";
|
|
2
|
-
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
3
4
|
import { Slot } from "@radix-ui/react-slot";
|
|
4
5
|
import {
|
|
5
6
|
Controller,
|
|
@@ -9,10 +10,14 @@ import {
|
|
|
9
10
|
FormProvider,
|
|
10
11
|
useFormContext,
|
|
11
12
|
} from "react-hook-form";
|
|
13
|
+
import {
|
|
14
|
+
StyledFormItem,
|
|
15
|
+
StyledFormLabel,
|
|
16
|
+
StyledFormDescription,
|
|
17
|
+
StyledFormMessage,
|
|
18
|
+
} from "./Form.styles";
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
import { Label } from "../../atoms/Label/Label";
|
|
15
|
-
|
|
20
|
+
/* Reexporta o FormProvider como Form para facilitar o uso */
|
|
16
21
|
const Form = FormProvider;
|
|
17
22
|
|
|
18
23
|
type FormFieldContextValue<
|
|
@@ -39,6 +44,25 @@ const FormField = <
|
|
|
39
44
|
);
|
|
40
45
|
};
|
|
41
46
|
|
|
47
|
+
type FormItemContextValue = { id: string };
|
|
48
|
+
|
|
49
|
+
const FormItemContext = React.createContext<FormItemContextValue>(
|
|
50
|
+
{} as FormItemContextValue
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const FormItem = React.forwardRef<
|
|
54
|
+
HTMLDivElement,
|
|
55
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
56
|
+
>(({ ...props }, ref) => {
|
|
57
|
+
const id = React.useId();
|
|
58
|
+
return (
|
|
59
|
+
<FormItemContext.Provider value={{ id }}>
|
|
60
|
+
<StyledFormItem ref={ref} {...props} />
|
|
61
|
+
</FormItemContext.Provider>
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
FormItem.displayName = "FormItem";
|
|
65
|
+
|
|
42
66
|
const useFormField = () => {
|
|
43
67
|
const fieldContext = React.useContext(FormFieldContext);
|
|
44
68
|
const itemContext = React.useContext(FormItemContext);
|
|
@@ -51,7 +75,6 @@ const useFormField = () => {
|
|
|
51
75
|
}
|
|
52
76
|
|
|
53
77
|
const { id } = itemContext;
|
|
54
|
-
|
|
55
78
|
return {
|
|
56
79
|
id,
|
|
57
80
|
name: fieldContext.name,
|
|
@@ -62,41 +85,15 @@ const useFormField = () => {
|
|
|
62
85
|
};
|
|
63
86
|
};
|
|
64
87
|
|
|
65
|
-
type FormItemContextValue = {
|
|
66
|
-
id: string;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const FormItemContext = React.createContext<FormItemContextValue>(
|
|
70
|
-
{} as FormItemContextValue
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const FormItem = React.forwardRef<
|
|
74
|
-
HTMLDivElement,
|
|
75
|
-
React.HTMLAttributes<HTMLDivElement>
|
|
76
|
-
>(({ className, ...props }, ref) => {
|
|
77
|
-
const id = React.useId();
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<FormItemContext.Provider value={{ id }}>
|
|
81
|
-
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
|
82
|
-
</FormItemContext.Provider>
|
|
83
|
-
);
|
|
84
|
-
});
|
|
85
|
-
FormItem.displayName = "FormItem";
|
|
86
|
-
|
|
87
88
|
const FormLabel = React.forwardRef<
|
|
88
|
-
|
|
89
|
-
React.ComponentPropsWithoutRef<
|
|
90
|
-
>(({
|
|
89
|
+
HTMLLabelElement,
|
|
90
|
+
React.ComponentPropsWithoutRef<"label">
|
|
91
|
+
>(({ children, ...props }, ref) => {
|
|
91
92
|
const { error, formItemId } = useFormField();
|
|
92
|
-
|
|
93
93
|
return (
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
htmlFor={formItemId}
|
|
98
|
-
{...props}
|
|
99
|
-
/>
|
|
94
|
+
<StyledFormLabel ref={ref} htmlFor={formItemId} error={!!error} {...props}>
|
|
95
|
+
{children}
|
|
96
|
+
</StyledFormLabel>
|
|
100
97
|
);
|
|
101
98
|
});
|
|
102
99
|
FormLabel.displayName = "FormLabel";
|
|
@@ -107,7 +104,6 @@ const FormControl = React.forwardRef<
|
|
|
107
104
|
>(({ ...props }, ref) => {
|
|
108
105
|
const { error, formItemId, formDescriptionId, formMessageId } =
|
|
109
106
|
useFormField();
|
|
110
|
-
|
|
111
107
|
return (
|
|
112
108
|
<Slot
|
|
113
109
|
ref={ref}
|
|
@@ -127,16 +123,12 @@ FormControl.displayName = "FormControl";
|
|
|
127
123
|
const FormDescription = React.forwardRef<
|
|
128
124
|
HTMLParagraphElement,
|
|
129
125
|
React.HTMLAttributes<HTMLParagraphElement>
|
|
130
|
-
>(({
|
|
126
|
+
>(({ children, ...props }, ref) => {
|
|
131
127
|
const { formDescriptionId } = useFormField();
|
|
132
|
-
|
|
133
128
|
return (
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
className={cn("text-sm text-muted-foreground", className)}
|
|
138
|
-
{...props}
|
|
139
|
-
/>
|
|
129
|
+
<StyledFormDescription ref={ref} id={formDescriptionId} {...props}>
|
|
130
|
+
{children}
|
|
131
|
+
</StyledFormDescription>
|
|
140
132
|
);
|
|
141
133
|
});
|
|
142
134
|
FormDescription.displayName = "FormDescription";
|
|
@@ -144,23 +136,14 @@ FormDescription.displayName = "FormDescription";
|
|
|
144
136
|
const FormMessage = React.forwardRef<
|
|
145
137
|
HTMLParagraphElement,
|
|
146
138
|
React.HTMLAttributes<HTMLParagraphElement>
|
|
147
|
-
>(({
|
|
139
|
+
>(({ children, ...props }, ref) => {
|
|
148
140
|
const { error, formMessageId } = useFormField();
|
|
149
141
|
const body = error ? String(error?.message) : children;
|
|
150
|
-
|
|
151
|
-
if (!body) {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
|
|
142
|
+
if (!body) return null;
|
|
155
143
|
return (
|
|
156
|
-
<
|
|
157
|
-
ref={ref}
|
|
158
|
-
id={formMessageId}
|
|
159
|
-
className={cn("text-sm font-medium text-destructive", className)}
|
|
160
|
-
{...props}
|
|
161
|
-
>
|
|
144
|
+
<StyledFormMessage ref={ref} id={formMessageId} {...props}>
|
|
162
145
|
{body}
|
|
163
|
-
</
|
|
146
|
+
</StyledFormMessage>
|
|
164
147
|
);
|
|
165
148
|
});
|
|
166
149
|
FormMessage.displayName = "FormMessage";
|
|
@@ -3,6 +3,7 @@ export const primitiveRadius = {
|
|
|
3
3
|
half: "0.0625rem", // 1px
|
|
4
4
|
x1: "0.125rem", // 2px
|
|
5
5
|
x2: "0.25rem", // 4px
|
|
6
|
+
x3: "0.5rem", // 8px
|
|
6
7
|
x4: "1rem", // 16px
|
|
7
8
|
x1000: "62.4375rem", // 9999px (Full radius, para bordas arredondadas completas)
|
|
8
9
|
},
|
|
@@ -13,6 +14,7 @@ export const semanticRadius = {
|
|
|
13
14
|
radius: {
|
|
14
15
|
sm: primitiveRadius.radius.x1, // 2px
|
|
15
16
|
md: primitiveRadius.radius.x2, // 4px
|
|
17
|
+
md2: primitiveRadius.radius.x3, // 8px
|
|
16
18
|
lg: primitiveRadius.radius.x4, // 16px
|
|
17
19
|
full: primitiveRadius.radius.x1000, // 9999px para bordas completamente arredondadas
|
|
18
20
|
},
|