@boxcustodia/library 2.0.0-alpha.13 → 2.0.0-alpha.15
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/dist/index.cjs.js +1 -138
- package/dist/index.d.ts +1083 -717
- package/dist/index.es.js +7059 -56179
- package/dist/theme.css +1 -1
- package/package.json +34 -26
- package/src/__doc__/Changelog.mdx +6 -6
- package/src/__doc__/Examples.tsx +1 -1
- package/src/__doc__/Intro.mdx +3 -3
- package/src/__doc__/Tabs.mdx +112 -0
- package/src/__doc__/V2.mdx +1245 -0
- package/src/components/accordion/accordion.stories.tsx +143 -0
- package/src/components/accordion/accordion.tsx +135 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/alert/alert.stories.tsx +24 -4
- package/src/components/alert/alert.tsx +17 -9
- package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
- package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
- package/src/components/alert-dialog/alert-dialog.tsx +58 -10
- package/src/components/auto-complete/auto-complete.stories.tsx +615 -200
- package/src/components/auto-complete/auto-complete.tsx +420 -68
- package/src/components/auto-complete/index.ts +0 -1
- package/src/components/avatar/avatar.stories.tsx +162 -21
- package/src/components/avatar/avatar.tsx +79 -20
- package/src/components/button/button.stories.tsx +236 -294
- package/src/components/button/button.test.tsx +10 -17
- package/src/components/button/button.tsx +53 -18
- package/src/components/button/components/base-button.tsx +25 -53
- package/src/components/button/index.ts +0 -1
- package/src/components/calendar/calendar.stories.tsx +1 -1
- package/src/components/calendar/calendar.tsx +4 -4
- package/src/components/card/card.stories.tsx +140 -69
- package/src/components/card/card.tsx +155 -54
- package/src/components/center/center.stories.tsx +22 -39
- package/src/components/checkbox/checkbox.stories.tsx +25 -5
- package/src/components/checkbox/checkbox.tsx +76 -15
- package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
- package/src/components/checkbox-group/checkbox-group.tsx +84 -3
- package/src/components/combobox/combobox.stories.tsx +33 -23
- package/src/components/combobox/combobox.tsx +120 -104
- package/src/components/date-picker/date-input.stories.tsx +14 -6
- package/src/components/date-picker/date-input.tsx +3 -3
- package/src/components/date-picker/date-picker.model.ts +13 -4
- package/src/components/date-picker/date-picker.stories.tsx +38 -12
- package/src/components/date-picker/date-picker.tsx +29 -15
- package/src/components/dialog/dialog.stories.tsx +18 -0
- package/src/components/dialog/dialog.test.tsx +1 -1
- package/src/components/dialog/dialog.tsx +51 -20
- package/src/components/divider/divider.stories.tsx +6 -0
- package/src/components/dropzone/dropzone.stories.tsx +70 -90
- package/src/components/dropzone/dropzone.tsx +383 -105
- package/src/components/dropzone/index.ts +0 -1
- package/src/components/empty/empty.stories.tsx +164 -0
- package/src/components/empty/empty.tsx +156 -0
- package/src/components/empty/index.ts +1 -0
- package/src/components/field/field.stories.tsx +226 -3
- package/src/components/field/field.tsx +77 -42
- package/src/components/form/form.stories.tsx +320 -197
- package/src/components/form/form.tsx +3 -23
- package/src/components/index.ts +2 -6
- package/src/components/input/input.stories.tsx +5 -5
- package/src/components/input/input.tsx +5 -5
- package/src/components/kbd/kbd.stories.tsx +1 -0
- package/src/components/label/label.stories.tsx +16 -0
- package/src/components/label/label.tsx +13 -2
- package/src/components/loader/loader.stories.tsx +7 -5
- package/src/components/loader/loader.tsx +8 -3
- package/src/components/menu/menu-primitives.tsx +207 -196
- package/src/components/menu/menu.stories.tsx +275 -146
- package/src/components/menu/menu.tsx +146 -54
- package/src/components/number-input/number-input.stories.tsx +27 -4
- package/src/components/number-input/number-input.test.tsx +2 -2
- package/src/components/number-input/number-input.tsx +29 -33
- package/src/components/otp/index.ts +1 -0
- package/src/components/otp/otp.stories.tsx +209 -0
- package/src/components/otp/otp.tsx +100 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.model.ts +2 -0
- package/src/components/pagination/pagination.stories.tsx +153 -59
- package/src/components/pagination/pagination.test.tsx +122 -57
- package/src/components/pagination/pagination.tsx +575 -77
- package/src/components/password/password.stories.tsx +18 -3
- package/src/components/password/password.tsx +26 -10
- package/src/components/popover/popover.stories.tsx +26 -5
- package/src/components/popover/popover.tsx +15 -23
- package/src/components/progress/progress.stories.tsx +1 -0
- package/src/components/radio-group/index.ts +1 -0
- package/src/components/radio-group/radio-group.stories.tsx +251 -0
- package/src/components/radio-group/radio-group.tsx +212 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
- package/src/components/select/select.stories.tsx +118 -19
- package/src/components/select/select.tsx +67 -62
- package/src/components/skeleton/skeleton.stories.tsx +1 -0
- package/src/components/stack/stack.stories.tsx +179 -89
- package/src/components/stack/stack.tsx +2 -2
- package/src/components/stepper/index.ts +1 -1
- package/src/components/stepper/stepper.stories.tsx +766 -83
- package/src/components/stepper/stepper.test.tsx +18 -18
- package/src/components/stepper/stepper.tsx +554 -0
- package/src/components/switch/switch.stories.tsx +15 -1
- package/src/components/switch/switch.tsx +17 -4
- package/src/components/table/index.ts +0 -2
- package/src/components/table/table.stories.tsx +131 -18
- package/src/components/table/table.test.tsx +1 -1
- package/src/components/table/table.tsx +183 -77
- package/src/components/tabs/tabs.stories.tsx +372 -155
- package/src/components/tabs/tabs.test.tsx +12 -12
- package/src/components/tabs/tabs.tsx +72 -149
- package/src/components/tag/index.ts +0 -1
- package/src/components/tag/tag.stories.tsx +147 -120
- package/src/components/tag/tag.tsx +47 -95
- package/src/components/textarea/textarea.stories.tsx +8 -22
- package/src/components/textarea/textarea.tsx +17 -79
- package/src/components/timeline/timeline.stories.tsx +322 -42
- package/src/components/timeline/timeline.tsx +359 -132
- package/src/components/toast/toast.stories.tsx +1 -0
- package/src/components/tooltip/tooltip.tsx +11 -9
- package/src/components/tree/index.ts +0 -1
- package/src/components/tree/tree.stories.tsx +364 -408
- package/src/components/tree/tree.test.tsx +163 -0
- package/src/components/tree/tree.tsx +212 -36
- package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
- package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
- package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
- package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
- package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
- package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
- package/src/hooks/usePagination/usePagination.tsx +36 -24
- package/src/styles/theme.css +1 -1
- package/src/utils/form.tsx +69 -37
- package/src/utils/index.ts +1 -1
- package/src/__doc__/Migration.mdx +0 -451
- package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
- package/src/components/background-image/background-image.stories.tsx +0 -21
- package/src/components/background-image/background-image.test.tsx +0 -29
- package/src/components/background-image/background-image.tsx +0 -23
- package/src/components/background-image/index.ts +0 -1
- package/src/components/button/button.variants.ts +0 -44
- package/src/components/button/components/loader-overlay.tsx +0 -21
- package/src/components/button/components/loading-icon.tsx +0 -47
- package/src/components/dropzone/upload-primitives.tsx +0 -310
- package/src/components/dropzone/use-dropzone.ts +0 -122
- package/src/components/empty-state/empty-state.stories.tsx +0 -56
- package/src/components/empty-state/empty-state.tsx +0 -39
- package/src/components/empty-state/index.ts +0 -1
- package/src/components/heading/heading.stories.tsx +0 -74
- package/src/components/heading/heading.tsx +0 -28
- package/src/components/heading/heading.variants.ts +0 -27
- package/src/components/heading/index.ts +0 -1
- package/src/components/kbd/kbd.variants.ts +0 -26
- package/src/components/menu/util/render-menu-item.tsx +0 -54
- package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
- package/src/components/multi-select/index.ts +0 -1
- package/src/components/multi-select/multi-select.stories.tsx +0 -294
- package/src/components/multi-select/multi-select.tsx +0 -300
- package/src/components/multi-select/multi-select.variants.ts +0 -22
- package/src/components/pagination/components/pagination-option.tsx +0 -27
- package/src/components/show/index.ts +0 -1
- package/src/components/show/show.stories.tsx +0 -197
- package/src/components/show/show.test.tsx +0 -41
- package/src/components/show/show.tsx +0 -16
- package/src/components/stepper/Stepper.tsx +0 -190
- package/src/components/stepper/context/stepper-context.tsx +0 -11
- package/src/components/table/table-primitives.tsx +0 -122
- package/src/components/table/table.model.ts +0 -20
- package/src/components/table-pagination/index.ts +0 -2
- package/src/components/table-pagination/table-pagination.model.ts +0 -2
- package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
- package/src/components/table-pagination/table-pagination.test.tsx +0 -32
- package/src/components/table-pagination/table-pagination.tsx +0 -108
- package/src/components/tabs/context/tabs-context.tsx +0 -14
- package/src/components/tag/tag.variants.ts +0 -31
- package/src/components/timeline/timeline-status.ts +0 -5
- package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
- package/src/components/tree/tree-primitives.tsx +0 -126
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { type DialogProps } from "@radix-ui/react-dialog";
|
|
2
|
-
import { Command } from "cmdk";
|
|
3
|
-
import { Search } from "lucide-react";
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
import { cn } from "../../lib";
|
|
6
|
-
import { DialogPopup, DialogRoot } from "../dialog";
|
|
7
|
-
|
|
8
|
-
const AutoCompleteRoot = ({
|
|
9
|
-
className,
|
|
10
|
-
...props
|
|
11
|
-
}: React.ComponentProps<typeof Command>) => (
|
|
12
|
-
<Command
|
|
13
|
-
data-slot="auto-complete"
|
|
14
|
-
className={cn(
|
|
15
|
-
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
|
16
|
-
className,
|
|
17
|
-
)}
|
|
18
|
-
{...props}
|
|
19
|
-
/>
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
interface AutoCompleteDialogProps extends DialogProps {}
|
|
23
|
-
|
|
24
|
-
const AutoCompleteDialog = ({
|
|
25
|
-
children,
|
|
26
|
-
...props
|
|
27
|
-
}: AutoCompleteDialogProps) => {
|
|
28
|
-
return (
|
|
29
|
-
<DialogRoot {...props} data-slot="auto-complete-dialog">
|
|
30
|
-
<DialogPopup
|
|
31
|
-
className="p-0 overflow-hidden shadow-lg"
|
|
32
|
-
data-slot="auto-complete-dialog-content"
|
|
33
|
-
>
|
|
34
|
-
<AutoCompleteRoot className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
|
35
|
-
{children}
|
|
36
|
-
</AutoCompleteRoot>
|
|
37
|
-
</DialogPopup>
|
|
38
|
-
</DialogRoot>
|
|
39
|
-
);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const AutoCompleteInput = ({
|
|
43
|
-
className,
|
|
44
|
-
...props
|
|
45
|
-
}: React.ComponentProps<typeof Command.Input>) => (
|
|
46
|
-
<div
|
|
47
|
-
className="flex items-center px-3 border-b"
|
|
48
|
-
data-slot="auto-complete-input-container"
|
|
49
|
-
>
|
|
50
|
-
<Search
|
|
51
|
-
className="w-4 h-4 mr-2 opacity-50 shrink-0"
|
|
52
|
-
data-slot="auto-complete-input-icon"
|
|
53
|
-
/>
|
|
54
|
-
<Command.Input
|
|
55
|
-
data-slot="auto-complete-input"
|
|
56
|
-
className={cn(
|
|
57
|
-
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
58
|
-
className,
|
|
59
|
-
)}
|
|
60
|
-
{...props}
|
|
61
|
-
/>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
const AutoCompleteList = ({
|
|
66
|
-
className,
|
|
67
|
-
...props
|
|
68
|
-
}: React.ComponentProps<typeof Command.List>) => (
|
|
69
|
-
<Command.List
|
|
70
|
-
data-slot="auto-complete-list"
|
|
71
|
-
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
|
72
|
-
{...props}
|
|
73
|
-
/>
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
const AutoCompleteEmpty = (
|
|
77
|
-
props: React.ComponentProps<typeof Command.Empty>,
|
|
78
|
-
) => (
|
|
79
|
-
<Command.Empty
|
|
80
|
-
className="py-6 text-sm text-center"
|
|
81
|
-
data-slot="auto-complete-empty"
|
|
82
|
-
{...props}
|
|
83
|
-
/>
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const AutoCompleteGroup = ({
|
|
87
|
-
className,
|
|
88
|
-
...props
|
|
89
|
-
}: React.ComponentProps<typeof Command.Group>) => (
|
|
90
|
-
<Command.Group
|
|
91
|
-
data-slot="auto-complete-group"
|
|
92
|
-
className={cn(
|
|
93
|
-
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
|
94
|
-
className,
|
|
95
|
-
)}
|
|
96
|
-
{...props}
|
|
97
|
-
/>
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
const AutoCompleteSeparator = ({
|
|
101
|
-
className,
|
|
102
|
-
...props
|
|
103
|
-
}: React.ComponentProps<typeof Command.Separator>) => (
|
|
104
|
-
<Command.Separator
|
|
105
|
-
data-slot="auto-complete-separator"
|
|
106
|
-
className={cn("-mx-1 h-px bg-input", className)}
|
|
107
|
-
{...props}
|
|
108
|
-
/>
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
const AutoCompleteItem = ({
|
|
112
|
-
className,
|
|
113
|
-
...props
|
|
114
|
-
}: React.ComponentProps<typeof Command.Item>) => {
|
|
115
|
-
return (
|
|
116
|
-
<Command.Item
|
|
117
|
-
data-slot="auto-complete-item"
|
|
118
|
-
className={cn(
|
|
119
|
-
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
|
120
|
-
"aria-disabled:pointer-events-none aria-disabled:opacity-50",
|
|
121
|
-
"relative flex gap-2 cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
|
|
122
|
-
className,
|
|
123
|
-
)}
|
|
124
|
-
{...props}
|
|
125
|
-
/>
|
|
126
|
-
);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const AutoCompleteShortcut = ({
|
|
130
|
-
className,
|
|
131
|
-
...props
|
|
132
|
-
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
133
|
-
return (
|
|
134
|
-
<span
|
|
135
|
-
data-slot="auto-complete-shortcut"
|
|
136
|
-
className={cn(
|
|
137
|
-
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
138
|
-
className,
|
|
139
|
-
)}
|
|
140
|
-
{...props}
|
|
141
|
-
/>
|
|
142
|
-
);
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
export {
|
|
146
|
-
AutoCompleteDialog,
|
|
147
|
-
AutoCompleteEmpty,
|
|
148
|
-
AutoCompleteGroup,
|
|
149
|
-
AutoCompleteInput,
|
|
150
|
-
AutoCompleteItem,
|
|
151
|
-
AutoCompleteList,
|
|
152
|
-
AutoCompleteRoot,
|
|
153
|
-
AutoCompleteSeparator,
|
|
154
|
-
AutoCompleteShortcut,
|
|
155
|
-
};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import { BackgroundImage } from "../../components";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Componente que facilita el manejo de imágenes de fondo.
|
|
6
|
-
*/
|
|
7
|
-
const meta: Meta<typeof BackgroundImage> = {
|
|
8
|
-
title: "Others/BackgroundImage",
|
|
9
|
-
component: BackgroundImage,
|
|
10
|
-
args: {
|
|
11
|
-
src: "https://m.media-amazon.com/images/I/81PgpygKwdL._AC_UF894,1000_QL80_.jpg",
|
|
12
|
-
children:
|
|
13
|
-
"Lorem Ipsum es simplemente el texto de relleno de las imprentas y archivos de texto. Lorem Ipsum ha sido el texto de relleno estándar de las industrias desde el año 1500, cuando un impresor (N. del T. persona que se dedica a la imprenta)",
|
|
14
|
-
className: "text-white w-[400px] h-[200px] rounded p-4",
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export default meta;
|
|
19
|
-
type Story = StoryObj<typeof meta>;
|
|
20
|
-
|
|
21
|
-
export const Default: Story = {};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { BackgroundImage } from ".";
|
|
4
|
-
|
|
5
|
-
describe("BackgroundImage component", () => {
|
|
6
|
-
const imageSrc = "https://source.unsplash.com/random";
|
|
7
|
-
|
|
8
|
-
it("should render the background image", () => {
|
|
9
|
-
render(<BackgroundImage src={imageSrc} data-testid="container" />);
|
|
10
|
-
const container = screen.getByTestId("container");
|
|
11
|
-
|
|
12
|
-
expect(container).toBeInTheDocument();
|
|
13
|
-
expect(container).toHaveStyle({
|
|
14
|
-
backgroundImage: `url(${imageSrc})`,
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("should render the children", () => {
|
|
19
|
-
const text = "Lorem Ipsum";
|
|
20
|
-
render(
|
|
21
|
-
<BackgroundImage src={imageSrc}>
|
|
22
|
-
<p>{text}</p>
|
|
23
|
-
</BackgroundImage>,
|
|
24
|
-
);
|
|
25
|
-
const paragraph = screen.getByText(text);
|
|
26
|
-
|
|
27
|
-
expect(paragraph).toBeInTheDocument();
|
|
28
|
-
});
|
|
29
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { HTMLProps } from "react";
|
|
2
|
-
import { cn } from "../../lib";
|
|
3
|
-
|
|
4
|
-
interface Props extends HTMLProps<HTMLDivElement> {
|
|
5
|
-
/**
|
|
6
|
-
* La URL de la imagen
|
|
7
|
-
*/
|
|
8
|
-
src: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const BackgroundImage = ({ src, ...props }: Props) => {
|
|
12
|
-
return (
|
|
13
|
-
<div
|
|
14
|
-
{...props}
|
|
15
|
-
data-slot="background-image"
|
|
16
|
-
className={cn("w-full block bg-cover bg-center", props.className)}
|
|
17
|
-
style={{
|
|
18
|
-
...props.style,
|
|
19
|
-
backgroundImage: `url(${src})`,
|
|
20
|
-
}}
|
|
21
|
-
/>
|
|
22
|
-
);
|
|
23
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./background-image";
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { cva } from "class-variance-authority";
|
|
2
|
-
|
|
3
|
-
export const buttonVariants = cva(
|
|
4
|
-
[
|
|
5
|
-
"inline-flex items-center justify-center gap-2",
|
|
6
|
-
"text-sm font-medium transition-all",
|
|
7
|
-
"ring-offset-background",
|
|
8
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
9
|
-
"disabled:pointer-events-none disabled:opacity-50",
|
|
10
|
-
"hover:brightness-105",
|
|
11
|
-
],
|
|
12
|
-
{
|
|
13
|
-
variants: {
|
|
14
|
-
variant: {
|
|
15
|
-
default: "bg-primary text-primary-foreground",
|
|
16
|
-
error: "bg-error text-error-foreground",
|
|
17
|
-
success: "bg-success text-success-foreground",
|
|
18
|
-
outline:
|
|
19
|
-
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
20
|
-
secondary:
|
|
21
|
-
"bg-secondary text-secondary-foreground hover:brightness-100 hover:bg-secondary/80",
|
|
22
|
-
ghost:
|
|
23
|
-
"hover:bg-accent hover:brightness-100 hover:text-accent-foreground",
|
|
24
|
-
link: "text-primary underline-offset-4 hover:underline",
|
|
25
|
-
},
|
|
26
|
-
size: {
|
|
27
|
-
default: "h-10 px-4 py-2",
|
|
28
|
-
sm: "h-9 px-3",
|
|
29
|
-
lg: "h-11 px-8",
|
|
30
|
-
icon: "w-10 aspect-square",
|
|
31
|
-
},
|
|
32
|
-
shape: {
|
|
33
|
-
rounded: "rounded-md",
|
|
34
|
-
square: "rounded-none",
|
|
35
|
-
circle: "rounded-full",
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
defaultVariants: {
|
|
39
|
-
variant: "default",
|
|
40
|
-
size: "default",
|
|
41
|
-
shape: "rounded",
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
);
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { LoadingIcon } from "./loading-icon";
|
|
2
|
-
|
|
3
|
-
interface LoaderOverlayProps {
|
|
4
|
-
loading: boolean;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export const LoaderOverlay = ({ loading }: LoaderOverlayProps) => (
|
|
8
|
-
<span
|
|
9
|
-
style={{
|
|
10
|
-
position: "absolute",
|
|
11
|
-
inset: 0,
|
|
12
|
-
display: "flex",
|
|
13
|
-
alignItems: "center",
|
|
14
|
-
justifyContent: "center",
|
|
15
|
-
background: "inherit",
|
|
16
|
-
borderRadius: "inherit",
|
|
17
|
-
}}
|
|
18
|
-
>
|
|
19
|
-
<LoadingIcon loading={loading} animate={false} />
|
|
20
|
-
</span>
|
|
21
|
-
);
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { LoaderCircle, LucideProps } from "lucide-react";
|
|
2
|
-
import { useRef } from "react";
|
|
3
|
-
import { CSSTransition } from "react-transition-group";
|
|
4
|
-
import { cn } from "../../../lib";
|
|
5
|
-
|
|
6
|
-
interface LoadingIconProps extends LucideProps {
|
|
7
|
-
loading?: boolean;
|
|
8
|
-
animate?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const LoadingIcon = ({
|
|
12
|
-
loading,
|
|
13
|
-
animate = true,
|
|
14
|
-
...props
|
|
15
|
-
}: LoadingIconProps) => {
|
|
16
|
-
const nodeRef = useRef(null);
|
|
17
|
-
|
|
18
|
-
if (!animate) {
|
|
19
|
-
return loading ? (
|
|
20
|
-
<LoaderCircle
|
|
21
|
-
{...props}
|
|
22
|
-
data-testid="btn-loader"
|
|
23
|
-
className={cn("animate-spin", props.className)}
|
|
24
|
-
/>
|
|
25
|
-
) : null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<CSSTransition
|
|
30
|
-
unmountOnExit
|
|
31
|
-
mountOnEnter
|
|
32
|
-
timeout={200}
|
|
33
|
-
in={loading}
|
|
34
|
-
classNames="btn-loader"
|
|
35
|
-
nodeRef={nodeRef}
|
|
36
|
-
>
|
|
37
|
-
<LoaderCircle
|
|
38
|
-
{...props}
|
|
39
|
-
ref={nodeRef}
|
|
40
|
-
data-testid="btn-loader"
|
|
41
|
-
className={cn("animate-spin", props.className)}
|
|
42
|
-
/>
|
|
43
|
-
</CSSTransition>
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export { LoadingIcon, type LoadingIconProps };
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
import { Slot } from "@radix-ui/react-slot";
|
|
2
|
-
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
3
|
-
import { X } from "lucide-react";
|
|
4
|
-
import React, {
|
|
5
|
-
ComponentPropsWithoutRef,
|
|
6
|
-
createContext,
|
|
7
|
-
ReactNode,
|
|
8
|
-
useContext,
|
|
9
|
-
useRef,
|
|
10
|
-
useState,
|
|
11
|
-
} from "react";
|
|
12
|
-
import { cn } from "../../lib";
|
|
13
|
-
import { Button } from "../button";
|
|
14
|
-
import { FileError, useDropzone } from "./use-dropzone";
|
|
15
|
-
|
|
16
|
-
type UploadContextType = {
|
|
17
|
-
files: File[];
|
|
18
|
-
rejectedFiles: File[];
|
|
19
|
-
handleFileAdd: (files: File[]) => void;
|
|
20
|
-
handleFileRemove: (index: number, type: "accepted" | "rejected") => void;
|
|
21
|
-
inputProps: React.ComponentPropsWithoutRef<"input">;
|
|
22
|
-
isDragActive: boolean;
|
|
23
|
-
dropzoneProps: {
|
|
24
|
-
onDragOver: React.DragEventHandler<HTMLDivElement>;
|
|
25
|
-
onDragEnter: React.DragEventHandler<HTMLDivElement>;
|
|
26
|
-
onDragLeave: React.DragEventHandler<HTMLDivElement>;
|
|
27
|
-
onDrop: React.DragEventHandler<HTMLDivElement>;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const UploadContext = createContext<UploadContextType | null>(null);
|
|
32
|
-
|
|
33
|
-
const useUploadContext = () => {
|
|
34
|
-
const context = useContext(UploadContext);
|
|
35
|
-
if (!context) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
"useUploadContext must be used within a UploadContextProvider",
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
return context;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
type UploadRootProps = {
|
|
44
|
-
onFileAdd?: (files: File[]) => void;
|
|
45
|
-
onFileRemove?: (index: number, type: "accepted" | "rejected") => void;
|
|
46
|
-
onChangeFiles?: (files: File[]) => void;
|
|
47
|
-
files?: File[];
|
|
48
|
-
maxFiles?: number;
|
|
49
|
-
allowedExtensions?: string[];
|
|
50
|
-
maxFileSize?: number;
|
|
51
|
-
multiple?: true;
|
|
52
|
-
className?: string;
|
|
53
|
-
children?: ReactNode;
|
|
54
|
-
onError?: (fileErrors: FileError[]) => void;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const UploadRoot = ({
|
|
58
|
-
onFileAdd,
|
|
59
|
-
onFileRemove,
|
|
60
|
-
onChangeFiles,
|
|
61
|
-
files: prop,
|
|
62
|
-
maxFiles = 1,
|
|
63
|
-
allowedExtensions = [],
|
|
64
|
-
maxFileSize = 5 * 1024 * 1024, // 5MB
|
|
65
|
-
multiple,
|
|
66
|
-
children,
|
|
67
|
-
onError,
|
|
68
|
-
...props
|
|
69
|
-
}: UploadRootProps) => {
|
|
70
|
-
const [files = [], setFiles] = useControllableState<File[]>({
|
|
71
|
-
defaultProp: [],
|
|
72
|
-
prop,
|
|
73
|
-
onChange: onChangeFiles,
|
|
74
|
-
});
|
|
75
|
-
const [rejectedFiles, setRejectedFiles] = useState<File[]>([]);
|
|
76
|
-
const { isDragActive, validateFiles, dropzoneProps } = useDropzone({
|
|
77
|
-
onDrop: (acceptedFiles, rejectedFiles) => {
|
|
78
|
-
setFiles([...files, ...acceptedFiles]);
|
|
79
|
-
setRejectedFiles((prevRejectedFiles) => [
|
|
80
|
-
...prevRejectedFiles,
|
|
81
|
-
...rejectedFiles,
|
|
82
|
-
]);
|
|
83
|
-
onFileAdd?.(acceptedFiles);
|
|
84
|
-
},
|
|
85
|
-
allowedExtensions,
|
|
86
|
-
maxFileSize,
|
|
87
|
-
maxFiles,
|
|
88
|
-
onError,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const handleFileAdd = (newFiles: File[]) => {
|
|
92
|
-
const [rejected = [], accepted = []] = validateFiles(newFiles);
|
|
93
|
-
|
|
94
|
-
// Caso 1: Single file upload (maxFiles === 1 o !multiple)
|
|
95
|
-
if (maxFiles === 1 || !multiple) {
|
|
96
|
-
setFiles(accepted.slice(0, 1));
|
|
97
|
-
setRejectedFiles([...rejected, ...accepted.slice(1)]);
|
|
98
|
-
onFileAdd?.(accepted.slice(0, 1));
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Caso 2: Multiple files upload
|
|
103
|
-
const availableSlots = maxFiles - files.length;
|
|
104
|
-
if (availableSlots <= 0) {
|
|
105
|
-
// No hay slots disponibles, todos van a rejected
|
|
106
|
-
setRejectedFiles((prevRejected) => [...prevRejected, ...accepted]);
|
|
107
|
-
onFileAdd?.([]);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Dividir los archivos aceptados entre los que caben y los que no
|
|
112
|
-
const acceptedFiles = accepted.slice(0, availableSlots);
|
|
113
|
-
const overflowFiles = accepted.slice(availableSlots);
|
|
114
|
-
|
|
115
|
-
// Actualizar estados
|
|
116
|
-
setFiles([...files, ...acceptedFiles]);
|
|
117
|
-
setRejectedFiles((prevRejected) => [
|
|
118
|
-
...prevRejected,
|
|
119
|
-
...rejected,
|
|
120
|
-
...overflowFiles,
|
|
121
|
-
]);
|
|
122
|
-
|
|
123
|
-
onFileAdd?.(acceptedFiles);
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const handleFileRemove = (index: number, type: "accepted" | "rejected") => {
|
|
127
|
-
if (type === "accepted") {
|
|
128
|
-
setFiles(files.filter((_, i) => i !== index));
|
|
129
|
-
} else {
|
|
130
|
-
setRejectedFiles((prevRejectedFiles) =>
|
|
131
|
-
prevRejectedFiles.filter((_, i) => i !== index),
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
onFileRemove?.(index, type);
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<UploadContext.Provider
|
|
139
|
-
value={{
|
|
140
|
-
files,
|
|
141
|
-
rejectedFiles,
|
|
142
|
-
handleFileAdd,
|
|
143
|
-
handleFileRemove,
|
|
144
|
-
inputProps: {
|
|
145
|
-
accept: allowedExtensions.join(","),
|
|
146
|
-
multiple: multiple,
|
|
147
|
-
},
|
|
148
|
-
isDragActive,
|
|
149
|
-
dropzoneProps,
|
|
150
|
-
}}
|
|
151
|
-
>
|
|
152
|
-
<div {...props} data-slot="upload">
|
|
153
|
-
{children}
|
|
154
|
-
</div>
|
|
155
|
-
</UploadContext.Provider>
|
|
156
|
-
);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
type UploadTriggerProps = {
|
|
160
|
-
asChild?: boolean;
|
|
161
|
-
} & ComponentPropsWithoutRef<"button">;
|
|
162
|
-
|
|
163
|
-
const UploadTrigger = ({ asChild, ...props }: UploadTriggerProps) => {
|
|
164
|
-
const { handleFileAdd, inputProps, dropzoneProps, isDragActive } =
|
|
165
|
-
useUploadContext();
|
|
166
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
167
|
-
const Comp = asChild ? Slot : "button";
|
|
168
|
-
|
|
169
|
-
return (
|
|
170
|
-
<>
|
|
171
|
-
<Comp
|
|
172
|
-
data-slot="upload-trigger"
|
|
173
|
-
{...props}
|
|
174
|
-
{...(dropzoneProps as any)}
|
|
175
|
-
data-drag-active={isDragActive || false}
|
|
176
|
-
onClick={(e) => {
|
|
177
|
-
props.onClick?.(e);
|
|
178
|
-
inputRef.current?.click();
|
|
179
|
-
}}
|
|
180
|
-
/>
|
|
181
|
-
<input
|
|
182
|
-
type="file"
|
|
183
|
-
className="hidden"
|
|
184
|
-
data-slot="upload-input"
|
|
185
|
-
{...inputProps}
|
|
186
|
-
ref={inputRef}
|
|
187
|
-
onChange={(e) => handleFileAdd(Array.from(e.target.files || []))}
|
|
188
|
-
/>
|
|
189
|
-
</>
|
|
190
|
-
);
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
type UploadContentProps = ComponentPropsWithoutRef<"div"> & {
|
|
194
|
-
renderRejectedFile?: (file: File) => ReactNode;
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
const UploadContent = ({ className, ...props }: UploadContentProps) => {
|
|
198
|
-
return (
|
|
199
|
-
<div
|
|
200
|
-
{...props}
|
|
201
|
-
className={cn("mt-2 space-y-2", className)}
|
|
202
|
-
data-slot="upload-content"
|
|
203
|
-
/>
|
|
204
|
-
);
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
type UploadFilesListProps = {
|
|
208
|
-
files: File[];
|
|
209
|
-
handleFileRemove: (index: number, type: "accepted" | "rejected") => void;
|
|
210
|
-
fileType: "accepted" | "rejected";
|
|
211
|
-
children?: ReactNode;
|
|
212
|
-
renderFile?: (file: File, removeItem: () => void) => ReactNode;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const UploadFilesList = ({
|
|
216
|
-
files,
|
|
217
|
-
handleFileRemove,
|
|
218
|
-
fileType,
|
|
219
|
-
children,
|
|
220
|
-
renderFile,
|
|
221
|
-
...props
|
|
222
|
-
}: UploadFilesListProps) => {
|
|
223
|
-
if (files.length === 0) return null;
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<div className="mt-2 space-y-2" {...props} data-slot="upload-files-list">
|
|
227
|
-
{children}
|
|
228
|
-
{files.map((file, index) => {
|
|
229
|
-
if (renderFile) {
|
|
230
|
-
return renderFile(file, () => handleFileRemove(index, fileType));
|
|
231
|
-
}
|
|
232
|
-
return (
|
|
233
|
-
<UploadItem
|
|
234
|
-
key={file.name}
|
|
235
|
-
file={file}
|
|
236
|
-
onRemove={() => handleFileRemove(index, fileType)}
|
|
237
|
-
/>
|
|
238
|
-
);
|
|
239
|
-
})}
|
|
240
|
-
</div>
|
|
241
|
-
);
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
type UploadFilesProps = Omit<
|
|
245
|
-
UploadFilesListProps,
|
|
246
|
-
"fileType" | "handleFileRemove" | "files"
|
|
247
|
-
>;
|
|
248
|
-
|
|
249
|
-
export const UploadAcceptedFiles = (props: UploadFilesProps) => {
|
|
250
|
-
const { files, handleFileRemove } = useUploadContext();
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<UploadFilesList
|
|
254
|
-
data-slot="upload-accepted-files"
|
|
255
|
-
files={files}
|
|
256
|
-
handleFileRemove={handleFileRemove}
|
|
257
|
-
fileType="accepted"
|
|
258
|
-
{...props}
|
|
259
|
-
/>
|
|
260
|
-
);
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
export const UploadRejectedFiles = (props: UploadFilesProps) => {
|
|
264
|
-
const { rejectedFiles, handleFileRemove } = useUploadContext();
|
|
265
|
-
|
|
266
|
-
return (
|
|
267
|
-
<UploadFilesList
|
|
268
|
-
data-slot="upload-rejected-files"
|
|
269
|
-
files={rejectedFiles}
|
|
270
|
-
handleFileRemove={handleFileRemove}
|
|
271
|
-
fileType="rejected"
|
|
272
|
-
{...props}
|
|
273
|
-
/>
|
|
274
|
-
);
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
type UploadItemProps = ComponentPropsWithoutRef<"div"> & {
|
|
278
|
-
file: File;
|
|
279
|
-
onRemove: () => void;
|
|
280
|
-
renderIcon?: (file: File) => ReactNode;
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
const UploadItem = ({
|
|
284
|
-
file,
|
|
285
|
-
onRemove,
|
|
286
|
-
className,
|
|
287
|
-
renderIcon,
|
|
288
|
-
...props
|
|
289
|
-
}: UploadItemProps) => (
|
|
290
|
-
<div
|
|
291
|
-
className={cn("flex items-center gap-2 mb-2", className)}
|
|
292
|
-
{...props}
|
|
293
|
-
data-slot="upload-item"
|
|
294
|
-
>
|
|
295
|
-
{renderIcon?.(file)}
|
|
296
|
-
{file.name}
|
|
297
|
-
<Button
|
|
298
|
-
variant="ghost"
|
|
299
|
-
size="icon"
|
|
300
|
-
type="button"
|
|
301
|
-
className="h-6 w-6"
|
|
302
|
-
onClick={onRemove}
|
|
303
|
-
data-slot="upload-item-remove"
|
|
304
|
-
>
|
|
305
|
-
<X data-slot="upload-item-remove-icon" />
|
|
306
|
-
</Button>
|
|
307
|
-
</div>
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
export { UploadContent, UploadItem, UploadRoot, UploadTrigger };
|