@boxcustodia/library 2.0.0-alpha.11 → 2.0.0-alpha.12
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.css +1 -1
- package/package.json +4 -3
- package/src/__doc__/Changelog.mdx +6 -0
- package/src/__doc__/Components.mdx +73 -0
- package/src/__doc__/Examples.tsx +69 -0
- package/src/__doc__/Icons.mdx +41 -0
- package/src/__doc__/Intro.mdx +138 -0
- package/src/__doc__/MCP.mdx +71 -0
- package/src/__doc__/Migration.mdx +475 -0
- package/src/__doc__/Theme.mdx +132 -0
- package/src/__doc__/Types.mdx +252 -0
- package/src/components/alert/alert.stories.tsx +142 -0
- package/src/components/alert/alert.tsx +109 -0
- package/src/components/alert/index.ts +7 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +173 -0
- package/src/components/alert-dialog/alert-dialog.test.tsx +49 -0
- package/src/components/alert-dialog/alert-dialog.tsx +265 -0
- package/src/components/alert-dialog/index.ts +1 -0
- package/src/components/auto-complete/auto-complete-primitives.tsx +155 -0
- package/src/components/auto-complete/auto-complete.stories.tsx +241 -0
- package/src/components/auto-complete/auto-complete.tsx +82 -0
- package/src/components/auto-complete/index.ts +2 -0
- package/src/components/avatar/avatar.stories.tsx +84 -0
- package/src/components/avatar/avatar.test.tsx +61 -0
- package/src/components/avatar/avatar.tsx +104 -0
- package/src/components/avatar/index.ts +1 -0
- package/src/components/background-image/background-image.stories.tsx +21 -0
- package/src/components/background-image/background-image.test.tsx +29 -0
- package/src/components/background-image/background-image.tsx +23 -0
- package/src/components/background-image/index.ts +1 -0
- package/src/components/button/button.stories.tsx +396 -0
- package/src/components/button/button.test.tsx +58 -0
- package/src/components/button/button.tsx +31 -0
- package/src/components/button/button.variants.ts +44 -0
- package/src/components/button/components/base-button.tsx +86 -0
- package/src/components/button/components/loader-overlay.tsx +21 -0
- package/src/components/button/components/loading-icon.tsx +47 -0
- package/src/components/button/index.ts +3 -0
- package/src/components/calendar/calendar.model.ts +86 -0
- package/src/components/calendar/calendar.stories.tsx +155 -0
- package/src/components/calendar/calendar.test.tsx +12 -0
- package/src/components/calendar/calendar.tsx +185 -0
- package/src/components/calendar/components/calendar-navigation.tsx +141 -0
- package/src/components/calendar/components/day.tsx +61 -0
- package/src/components/calendar/components/decade-view.tsx +45 -0
- package/src/components/calendar/components/index.ts +6 -0
- package/src/components/calendar/components/month-view.tsx +58 -0
- package/src/components/calendar/components/week-days.tsx +27 -0
- package/src/components/calendar/components/year-view.tsx +29 -0
- package/src/components/calendar/hooks/index.ts +4 -0
- package/src/components/calendar/hooks/use-calendar-navigation.ts +79 -0
- package/src/components/calendar/hooks/use-calendar.ts +90 -0
- package/src/components/calendar/hooks/use-multiple-calendar.ts +34 -0
- package/src/components/calendar/hooks/use-range-calendar.ts +91 -0
- package/src/components/calendar/hooks/use-single-calendar.ts +18 -0
- package/src/components/calendar/index.ts +1 -0
- package/src/components/calendar/utils/typeguards.ts +7 -0
- package/src/components/card/card.stories.tsx +116 -0
- package/src/components/card/card.tsx +74 -0
- package/src/components/card/index.ts +1 -0
- package/src/components/center/center.stories.tsx +81 -0
- package/src/components/center/center.tsx +24 -0
- package/src/components/center/index.ts +1 -0
- package/src/components/checkbox/checkbox.stories.tsx +307 -0
- package/src/components/checkbox/checkbox.tsx +273 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/checkbox-group/checkbox-group.stories.tsx +104 -0
- package/src/components/checkbox-group/checkbox-group.tsx +16 -0
- package/src/components/checkbox-group/index.ts +1 -0
- package/src/components/combobox/combobox.stories.tsx +339 -0
- package/src/components/combobox/combobox.tsx +892 -0
- package/src/components/combobox/index.ts +1 -0
- package/src/components/date-picker/date-input.stories.tsx +158 -0
- package/src/components/date-picker/date-input.tsx +163 -0
- package/src/components/date-picker/date-picker.model.ts +90 -0
- package/src/components/date-picker/date-picker.stories.tsx +200 -0
- package/src/components/date-picker/date-picker.test.tsx +23 -0
- package/src/components/date-picker/date-picker.tsx +298 -0
- package/src/components/date-picker/date-picker.utils.ts +260 -0
- package/src/components/date-picker/index.ts +3 -0
- package/src/components/date-picker/use-date-input-popover.ts +48 -0
- package/src/components/date-picker/use-date-input.ts +125 -0
- package/src/components/dialog/dialog.stories.tsx +171 -0
- package/src/components/dialog/dialog.test.tsx +68 -0
- package/src/components/dialog/dialog.tsx +277 -0
- package/src/components/dialog/index.ts +1 -0
- package/src/components/divider/divider.stories.tsx +70 -0
- package/src/components/divider/divider.test.tsx +22 -0
- package/src/components/divider/divider.tsx +23 -0
- package/src/components/divider/index.ts +1 -0
- package/src/components/dropzone/dropzone.stories.tsx +210 -0
- package/src/components/dropzone/dropzone.tsx +154 -0
- package/src/components/dropzone/file-types.ts +64 -0
- package/src/components/dropzone/index.ts +3 -0
- package/src/components/dropzone/upload-primitives.tsx +310 -0
- package/src/components/dropzone/use-dropzone.ts +122 -0
- package/src/components/empty-state/empty-state.stories.tsx +56 -0
- package/src/components/empty-state/empty-state.tsx +39 -0
- package/src/components/empty-state/index.ts +1 -0
- package/src/components/field/field.stories.tsx +223 -0
- package/src/components/field/field.tsx +229 -0
- package/src/components/field/index.ts +1 -0
- package/src/components/form/form.stories.tsx +594 -0
- package/src/components/form/form.tsx +30 -0
- package/src/components/form/index.ts +1 -0
- package/src/components/heading/heading.stories.tsx +74 -0
- package/src/components/heading/heading.tsx +28 -0
- package/src/components/heading/heading.variants.ts +27 -0
- package/src/components/heading/index.ts +1 -0
- package/src/components/index.ts +46 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input/input.stories.tsx +104 -0
- package/src/components/input/input.tsx +75 -0
- package/src/components/kbd/index.ts +1 -0
- package/src/components/kbd/kbd.stories.tsx +40 -0
- package/src/components/kbd/kbd.tsx +31 -0
- package/src/components/kbd/kbd.variants.ts +26 -0
- package/src/components/label/index.ts +1 -0
- package/src/components/label/label.stories.tsx +68 -0
- package/src/components/label/label.test.tsx +61 -0
- package/src/components/label/label.tsx +62 -0
- package/src/components/loader/index.ts +1 -0
- package/src/components/loader/loader.stories.tsx +60 -0
- package/src/components/loader/loader.test.tsx +26 -0
- package/src/components/loader/loader.tsx +60 -0
- package/src/components/menu/index.ts +2 -0
- package/src/components/menu/menu-primitives.tsx +248 -0
- package/src/components/menu/menu.stories.tsx +203 -0
- package/src/components/menu/menu.tsx +100 -0
- package/src/components/menu/util/render-menu-item.tsx +54 -0
- package/src/components/multi-select/hooks/use-multi-select.ts +66 -0
- package/src/components/multi-select/index.ts +1 -0
- package/src/components/multi-select/multi-select.stories.tsx +294 -0
- package/src/components/multi-select/multi-select.tsx +300 -0
- package/src/components/multi-select/multi-select.variants.ts +22 -0
- package/src/components/number-input/index.ts +1 -0
- package/src/components/number-input/number-input.stories.tsx +209 -0
- package/src/components/number-input/number-input.test.tsx +87 -0
- package/src/components/number-input/number-input.tsx +230 -0
- package/src/components/pagination/components/pagination-option.tsx +27 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.stories.tsx +80 -0
- package/src/components/pagination/pagination.test.tsx +76 -0
- package/src/components/pagination/pagination.tsx +102 -0
- package/src/components/password/index.ts +1 -0
- package/src/components/password/password.stories.tsx +104 -0
- package/src/components/password/password.tsx +71 -0
- package/src/components/popover/index.ts +1 -0
- package/src/components/popover/popover.stories.tsx +213 -0
- package/src/components/popover/popover.tsx +203 -0
- package/src/components/progress/index.ts +1 -0
- package/src/components/progress/progress.stories.tsx +124 -0
- package/src/components/progress/progress.test.tsx +25 -0
- package/src/components/progress/progress.tsx +124 -0
- package/src/components/scroll-area/index.ts +1 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +166 -0
- package/src/components/scroll-area/scroll-area.tsx +64 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.stories.tsx +253 -0
- package/src/components/select/select.tsx +430 -0
- package/src/components/show/index.ts +1 -0
- package/src/components/show/show.stories.tsx +197 -0
- package/src/components/show/show.test.tsx +41 -0
- package/src/components/show/show.tsx +16 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.stories.tsx +36 -0
- package/src/components/skeleton/skeleton.test.tsx +14 -0
- package/src/components/skeleton/skeleton.tsx +15 -0
- package/src/components/stack/index.ts +1 -0
- package/src/components/stack/stack.stories.tsx +194 -0
- package/src/components/stack/stack.tsx +52 -0
- package/src/components/stepper/Stepper.tsx +190 -0
- package/src/components/stepper/context/stepper-context.tsx +11 -0
- package/src/components/stepper/index.ts +1 -0
- package/src/components/stepper/stepper.stories.tsx +130 -0
- package/src/components/stepper/stepper.test.tsx +91 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.stories.tsx +122 -0
- package/src/components/switch/switch.test.tsx +30 -0
- package/src/components/switch/switch.tsx +86 -0
- package/src/components/table/index.ts +3 -0
- package/src/components/table/table-primitives.tsx +122 -0
- package/src/components/table/table.model.ts +20 -0
- package/src/components/table/table.stories.tsx +169 -0
- package/src/components/table/table.test.tsx +91 -0
- package/src/components/table/table.tsx +109 -0
- package/src/components/table-pagination/index.ts +2 -0
- package/src/components/table-pagination/table-pagination.model.ts +2 -0
- package/src/components/table-pagination/table-pagination.stories.tsx +23 -0
- package/src/components/table-pagination/table-pagination.test.tsx +32 -0
- package/src/components/table-pagination/table-pagination.tsx +108 -0
- package/src/components/tabs/context/tabs-context.tsx +14 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.stories.tsx +182 -0
- package/src/components/tabs/tabs.test.tsx +61 -0
- package/src/components/tabs/tabs.tsx +175 -0
- package/src/components/tag/index.ts +2 -0
- package/src/components/tag/tag.stories.tsx +170 -0
- package/src/components/tag/tag.test.tsx +18 -0
- package/src/components/tag/tag.tsx +99 -0
- package/src/components/tag/tag.variants.ts +31 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/textarea.stories.tsx +73 -0
- package/src/components/textarea/textarea.tsx +105 -0
- package/src/components/timeline/index.ts +1 -0
- package/src/components/timeline/timeline-status.ts +5 -0
- package/src/components/timeline/timeline.stories.tsx +84 -0
- package/src/components/timeline/timeline.tsx +147 -0
- package/src/components/toast/index.ts +1 -0
- package/src/components/toast/toast.stories.tsx +392 -0
- package/src/components/toast/toast.test.tsx +50 -0
- package/src/components/toast/toast.tsx +411 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip/tooltip.stories.tsx +226 -0
- package/src/components/tooltip/tooltip.test.tsx +46 -0
- package/src/components/tooltip/tooltip.tsx +171 -0
- package/src/components/tree/hooks/use-controllable-tree-state.ts +80 -0
- package/src/components/tree/index.ts +2 -0
- package/src/components/tree/tree-primitives.tsx +126 -0
- package/src/components/tree/tree.stories.tsx +468 -0
- package/src/components/tree/tree.tsx +42 -0
- package/src/hooks/index.ts +26 -0
- package/src/hooks/useArray/__doc__/useArray.stories.tsx +100 -0
- package/src/hooks/useArray/__test__/useArray.test.tsx +88 -0
- package/src/hooks/useArray/index.ts +1 -0
- package/src/hooks/useArray/useArray.ts +76 -0
- package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +149 -0
- package/src/hooks/useAsync/__test__/useAsync.test.tsx +68 -0
- package/src/hooks/useAsync/index.ts +1 -0
- package/src/hooks/useAsync/useAsync.ts +58 -0
- package/src/hooks/useClickOutside/__doc__/useClickOutside.stories.tsx +40 -0
- package/src/hooks/useClickOutside/__test__/useClickOutside.test.tsx +33 -0
- package/src/hooks/useClickOutside/index.ts +1 -0
- package/src/hooks/useClickOutside/useClickOutside.ts +26 -0
- package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +45 -0
- package/src/hooks/useClipboard/__test__/useClipboard.test.tsx +19 -0
- package/src/hooks/useClipboard/index.ts +1 -0
- package/src/hooks/useClipboard/useClipboard.tsx +28 -0
- package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +84 -0
- package/src/hooks/useDebounceCallback/index.ts +1 -0
- package/src/hooks/useDebounceCallback/useDebouncedCallback.ts +23 -0
- package/src/hooks/useDebounceValue/__doc__/useDebouncedValue.stories.tsx +75 -0
- package/src/hooks/useDebounceValue/index.ts +1 -0
- package/src/hooks/useDebounceValue/useDebouncedValue.ts +17 -0
- package/src/hooks/useDisclosure/__doc__/useDisclosure.stories.tsx +39 -0
- package/src/hooks/useDisclosure/__test__/useDisclosure.test.ts +43 -0
- package/src/hooks/useDisclosure/index.ts +1 -0
- package/src/hooks/useDisclosure/useDisclosure.ts +37 -0
- package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +26 -0
- package/src/hooks/useDocumentTitle/index.ts +1 -0
- package/src/hooks/useDocumentTitle/useDocumentTitle.tsx +11 -0
- package/src/hooks/useEventListener/__doc__/useEventListener.stories.tsx +28 -0
- package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +26 -0
- package/src/hooks/useEventListener/index.ts +1 -0
- package/src/hooks/useEventListener/useEventListener.ts +25 -0
- package/src/hooks/useFocusTrap/__doc__/useFocusTrap.stories.tsx +37 -0
- package/src/hooks/useFocusTrap/index.ts +1 -0
- package/src/hooks/useFocusTrap/scopeTab.ts +38 -0
- package/src/hooks/useFocusTrap/tabbable.ts +70 -0
- package/src/hooks/useFocusTrap/useFocusTrap.ts +78 -0
- package/src/hooks/useHotkey/__docs__/useHotkey.stories.tsx +116 -0
- package/src/hooks/useHotkey/__test__/useHotkey.test.tsx +105 -0
- package/src/hooks/useHotkey/__utils__/create-hotkey-listener.ts +25 -0
- package/src/hooks/useHotkey/__utils__/index.ts +3 -0
- package/src/hooks/useHotkey/__utils__/is-input-field.ts +14 -0
- package/src/hooks/useHotkey/__utils__/match-key-modifiers.ts +25 -0
- package/src/hooks/useHotkey/index.ts +1 -0
- package/src/hooks/useHotkey/useHotkey.ts +34 -0
- package/src/hooks/useHover/__doc__/useHover.stories.tsx +41 -0
- package/src/hooks/useHover/__test__/useHover.test.tsx +45 -0
- package/src/hooks/useHover/index.ts +1 -0
- package/src/hooks/useHover/useHover.tsx +40 -0
- package/src/hooks/useIsVisible/__doc__/useIsVisible.stories.tsx +60 -0
- package/src/hooks/useIsVisible/index.ts +1 -0
- package/src/hooks/useIsVisible/useIsVisible.tsx +50 -0
- package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +86 -0
- package/src/hooks/useLocalStorage/__test__/useLocalStorage.test.ts +85 -0
- package/src/hooks/useLocalStorage/index.ts +1 -0
- package/src/hooks/useLocalStorage/useLocalStorage.ts +57 -0
- package/src/hooks/useMediaQuery/__doc__/useMediaQuery.stories.tsx +39 -0
- package/src/hooks/useMediaQuery/index.ts +1 -0
- package/src/hooks/useMediaQuery/useMediaQuery.ts +22 -0
- package/src/hooks/useMemoizedFn/index.ts +1 -0
- package/src/hooks/useMemoizedFn/useMemoizedFn.ts +32 -0
- package/src/hooks/useMutation/__doc__/useMutation.stories.tsx +111 -0
- package/src/hooks/useMutation/__test__/useMutation.test.tsx +83 -0
- package/src/hooks/useMutation/index.ts +1 -0
- package/src/hooks/useMutation/useMutation.tsx +60 -0
- package/src/hooks/useObject/__doc__/useObject.stories.tsx +119 -0
- package/src/hooks/useObject/__test__/useObject.test.tsx +87 -0
- package/src/hooks/useObject/index.ts +1 -0
- package/src/hooks/useObject/useObject.tsx +48 -0
- package/src/hooks/usePagination/__doc__/usePagination.stories.tsx +72 -0
- package/src/hooks/usePagination/__test__/usePagination.test.tsx +98 -0
- package/src/hooks/usePagination/index.ts +2 -0
- package/src/hooks/usePagination/usePagination.tsx +74 -0
- package/src/hooks/usePortal/__doc__/usePortal.stories.tsx +19 -0
- package/src/hooks/usePortal/__test__/usePortal.test.tsx +20 -0
- package/src/hooks/usePortal/index.ts +1 -0
- package/src/hooks/usePortal/usePortal.ts +40 -0
- package/src/hooks/usePreventCloseWindow/__doc__/usePreventCloseWindow.stories.tsx +32 -0
- package/src/hooks/usePreventCloseWindow/index.ts +1 -0
- package/src/hooks/usePreventCloseWindow/usePreventCloseWindow.ts +33 -0
- package/src/hooks/useRangePagination/__test__/useRangePagination.test.tsx +63 -0
- package/src/hooks/useRangePagination/index.ts +2 -0
- package/src/hooks/useRangePagination/useRangePagination.tsx +72 -0
- package/src/hooks/useSelection/__doc__/useSelection.stories.tsx +140 -0
- package/src/hooks/useSelection/__test__/useSelection.test.tsx +57 -0
- package/src/hooks/useSelection/index.ts +1 -0
- package/src/hooks/useSelection/useSelection.ts +121 -0
- package/src/hooks/useStep/__doc__/useStep.stories.tsx +98 -0
- package/src/hooks/useStep/__test__/useStep.test.ts +51 -0
- package/src/hooks/useStep/index.ts +1 -0
- package/src/hooks/useStep/useStep.ts +57 -0
- package/src/hooks/useToggle/__doc__/useToggle.stories.tsx +25 -0
- package/src/hooks/useToggle/__test__/useToggle.test.tsx +43 -0
- package/src/hooks/useToggle/index.ts +1 -0
- package/src/hooks/useToggle/useToggle.ts +16 -0
- package/src/index.ts +6 -0
- package/src/lib/cn.ts +8 -0
- package/src/lib/index.ts +1 -0
- package/src/models/Generic.model.ts +67 -0
- package/src/models/index.ts +1 -0
- package/src/providers/index.ts +2 -0
- package/src/providers/library-provider.tsx +44 -0
- package/src/providers/theme/ThemeProvider.tsx +25 -0
- package/src/providers/theme/index.ts +3 -0
- package/src/providers/theme/types.ts +11 -0
- package/src/providers/theme/useThemeProps.ts +25 -0
- package/src/stores/theme.store.ts +31 -0
- package/src/styles/components.css +4 -0
- package/src/styles/index.css +2 -0
- package/src/styles/library.css +2 -0
- package/src/styles/theme.css +232 -0
- package/src/utils/dates/parseDateRange.utility.ts +39 -0
- package/src/utils/form.tsx +91 -0
- package/src/utils/functions/createSafeContext.ts +17 -0
- package/src/utils/functions/ensureReactElement.tsx +30 -0
- package/src/utils/functions/getFormData.ts +19 -0
- package/src/utils/functions/index.ts +4 -0
- package/src/utils/functions/mergeRefs.ts +18 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/strings/extractInitials.utility.ts +10 -0
- package/src/utils/strings/index.ts +1 -0
- package/src/utils/tests/click.ts +3 -0
- package/src/utils/tests/index.ts +2 -0
- package/src/utils/tests/keyboard.ts +21 -0
- package/src/utils/tests/type.ts +6 -0
- package/dist/components.css +0 -2
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { cva, VariantProps } from "class-variance-authority";
|
|
3
|
+
import { Flame, SearchIcon } from "lucide-react";
|
|
4
|
+
import { ComponentProps } from "react";
|
|
5
|
+
import { BaseButton, Button, Heading } from "../../components";
|
|
6
|
+
import { cn } from "../../lib";
|
|
7
|
+
import { ThemeProvider } from "../../providers";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Componente principal para ejecutar acciones.
|
|
11
|
+
*
|
|
12
|
+
* Extendiende al componente `BaseButton` agregándole variantes
|
|
13
|
+
*/
|
|
14
|
+
const meta: Meta<typeof Button> = {
|
|
15
|
+
title: "Data entry/Button",
|
|
16
|
+
component: Button,
|
|
17
|
+
args: {
|
|
18
|
+
children: "Click me!",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof Button>;
|
|
24
|
+
|
|
25
|
+
export const Default: Story = {};
|
|
26
|
+
|
|
27
|
+
export const Outline: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
variant: "outline",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const Secondary: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
variant: "secondary",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Ghost: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
variant: "ghost",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Link: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
variant: "link",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Error: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
variant: "error",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const Success: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
variant: "success",
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Se puede agregar un ícono al botón con la prop `icon`.
|
|
65
|
+
*/
|
|
66
|
+
export const Icon: Story = {
|
|
67
|
+
args: {
|
|
68
|
+
icon: <SearchIcon />,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Se puede cambiar la posición del ícono con la prop `iconPosition`.
|
|
74
|
+
*
|
|
75
|
+
* Los valores posibles son `start` y `end`.
|
|
76
|
+
*/
|
|
77
|
+
export const IconPosition: Story = {
|
|
78
|
+
args: {
|
|
79
|
+
icon: <SearchIcon />,
|
|
80
|
+
iconPosition: "end",
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* El button recibe la propieda `loading` que permite controlar el estado de carga del botón.
|
|
86
|
+
* Se puede cambiar la forma en la que se muestra el loader con la prop `loaderReplace`.
|
|
87
|
+
* Si `loaderReplace` es `true`, el loader reemplaza todo el contenido manteniendo el ancho original del botón.
|
|
88
|
+
* Por defecto, el loader se agrega al contenido existente o reemplaza al ícono en caso de que exista.
|
|
89
|
+
*/
|
|
90
|
+
export const Loading: Story = {
|
|
91
|
+
render: () => {
|
|
92
|
+
const sleep = (): Promise<void> => {
|
|
93
|
+
return new Promise((resolve) => setTimeout(resolve, 2000));
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="space-y-8">
|
|
98
|
+
{/* Modo Append (default) */}
|
|
99
|
+
<section>
|
|
100
|
+
<Heading as="h2" className="mb-4">
|
|
101
|
+
Modo Append (default)
|
|
102
|
+
</Heading>
|
|
103
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
104
|
+
En este modo, el loader se agrega al contenido existente. Si hay un
|
|
105
|
+
ícono, el loader lo reemplaza.
|
|
106
|
+
</p>
|
|
107
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
108
|
+
<Button onClick={sleep}>Por izquierda</Button>
|
|
109
|
+
<Button onClick={sleep} iconPosition="end">
|
|
110
|
+
Por derecha
|
|
111
|
+
</Button>
|
|
112
|
+
<Button onClick={sleep} icon={<SearchIcon />}>
|
|
113
|
+
Ícono inicio
|
|
114
|
+
</Button>
|
|
115
|
+
<Button onClick={sleep} icon={<SearchIcon />} iconPosition="end">
|
|
116
|
+
Ícono final
|
|
117
|
+
</Button>
|
|
118
|
+
<Button onClick={sleep} icon={<SearchIcon />} size="icon" />
|
|
119
|
+
</div>
|
|
120
|
+
</section>
|
|
121
|
+
|
|
122
|
+
{/* Modo Replace */}
|
|
123
|
+
<section>
|
|
124
|
+
<Heading as="h2" className="mb-4">
|
|
125
|
+
Modo Replace (loaderReplace = true)
|
|
126
|
+
</Heading>
|
|
127
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
128
|
+
En este modo, el loader reemplaza todo el contenido manteniendo el
|
|
129
|
+
ancho original del botón.
|
|
130
|
+
</p>
|
|
131
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
132
|
+
<Button onClick={sleep} loaderReplace>
|
|
133
|
+
Solo texto
|
|
134
|
+
</Button>
|
|
135
|
+
<Button onClick={sleep} loaderReplace icon={<SearchIcon />}>
|
|
136
|
+
Ícono inicio
|
|
137
|
+
</Button>
|
|
138
|
+
<Button
|
|
139
|
+
onClick={sleep}
|
|
140
|
+
loaderReplace
|
|
141
|
+
icon={<SearchIcon />}
|
|
142
|
+
iconPosition="end"
|
|
143
|
+
>
|
|
144
|
+
Ícono final
|
|
145
|
+
</Button>
|
|
146
|
+
<Button
|
|
147
|
+
onClick={sleep}
|
|
148
|
+
loaderReplace
|
|
149
|
+
icon={<SearchIcon />}
|
|
150
|
+
size="icon"
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
</section>
|
|
154
|
+
|
|
155
|
+
{/* Variantes */}
|
|
156
|
+
<section>
|
|
157
|
+
<Heading as="h2" className="mb-4">
|
|
158
|
+
Variantes
|
|
159
|
+
</Heading>
|
|
160
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
161
|
+
El loader funciona con todas las variantes de botón.
|
|
162
|
+
</p>
|
|
163
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
164
|
+
<Button onClick={sleep} variant="outline" icon={<SearchIcon />}>
|
|
165
|
+
Outline
|
|
166
|
+
</Button>
|
|
167
|
+
<Button onClick={sleep} variant="secondary" icon={<SearchIcon />}>
|
|
168
|
+
Secondary
|
|
169
|
+
</Button>
|
|
170
|
+
<Button onClick={sleep} variant="ghost" icon={<SearchIcon />}>
|
|
171
|
+
Ghost
|
|
172
|
+
</Button>
|
|
173
|
+
<Button onClick={sleep} variant="link" icon={<SearchIcon />}>
|
|
174
|
+
Link
|
|
175
|
+
</Button>
|
|
176
|
+
<Button onClick={sleep} variant="error" icon={<SearchIcon />}>
|
|
177
|
+
Error
|
|
178
|
+
</Button>
|
|
179
|
+
<Button onClick={sleep} variant="success" icon={<SearchIcon />}>
|
|
180
|
+
Success
|
|
181
|
+
</Button>
|
|
182
|
+
</div>
|
|
183
|
+
</section>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Al setear `asChild` a `true`, puedes renderizar cualquier elemento con los estilos, props y referencias del `Button`.
|
|
191
|
+
*
|
|
192
|
+
* En este caso, se usa un elemento `a` que hereda los estilos, props y referencias del `Button`.
|
|
193
|
+
* Esto es útil para aplicar los mismos estilos y garantizar consistencia en la interfaz de usuario de su aplicación.
|
|
194
|
+
* Se puede verificar abriendo la consola del navegador.
|
|
195
|
+
*/
|
|
196
|
+
export const AsChild: Story = {
|
|
197
|
+
args: {
|
|
198
|
+
children: <a href="#">Click me</a>,
|
|
199
|
+
variant: "error",
|
|
200
|
+
asChild: true,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* El componente recibe todas las propiedades de `button`, por lo que se puede customizar los estilos, accesibilidad, referencias, eventos, etc
|
|
206
|
+
*/
|
|
207
|
+
export const Custom: Story = {
|
|
208
|
+
args: {
|
|
209
|
+
className: "bg-amber-500 hover:bg-amber-600",
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Estos son los tamanos disponibles para el `Button`.
|
|
215
|
+
*/
|
|
216
|
+
export const Sizes: Story = {
|
|
217
|
+
render: () => {
|
|
218
|
+
const sizes = ["default", "sm", "lg", "icon"] as const;
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<>
|
|
222
|
+
{sizes.map((size) => (
|
|
223
|
+
<div key={size} className="mb-1">
|
|
224
|
+
<p className="font-semibold">{`size: ${size}`}</p>
|
|
225
|
+
<Button size={size} key={size}>
|
|
226
|
+
{size === "icon" ? <Flame /> : "Click me"}
|
|
227
|
+
</Button>
|
|
228
|
+
</div>
|
|
229
|
+
))}
|
|
230
|
+
</>
|
|
231
|
+
);
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
export const Shapes: Story = {
|
|
236
|
+
render: () => {
|
|
237
|
+
const shapes = ["rounded", "square", "circle"] as const;
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
241
|
+
{shapes.map((shape) => (
|
|
242
|
+
<div key={shape} className="flex flex-col gap-2">
|
|
243
|
+
<p className="font-semibold capitalize">{shape}</p>
|
|
244
|
+
<Button shape={shape} key={shape} size="icon" icon={<Flame />} />
|
|
245
|
+
<Button shape={shape} key={shape}>
|
|
246
|
+
{shape}
|
|
247
|
+
</Button>
|
|
248
|
+
</div>
|
|
249
|
+
))}
|
|
250
|
+
</div>
|
|
251
|
+
);
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const buttonVariants = cva(
|
|
256
|
+
[
|
|
257
|
+
"flex items-center justify-center gap-2 px-4 py-2",
|
|
258
|
+
"text-sm font-medium transition-all",
|
|
259
|
+
"rounded ring-offset-background",
|
|
260
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
261
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
262
|
+
],
|
|
263
|
+
{
|
|
264
|
+
variants: {
|
|
265
|
+
variant: {
|
|
266
|
+
white: "bg-white text-primary border",
|
|
267
|
+
black: "bg-black text-white",
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
defaultVariants: {
|
|
271
|
+
variant: "white",
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
type BaseButtonProps = ComponentProps<typeof BaseButton>;
|
|
277
|
+
interface Props extends BaseButtonProps, VariantProps<typeof buttonVariants> {}
|
|
278
|
+
|
|
279
|
+
const CustomButton = ({ variant, ...props }: Props) => {
|
|
280
|
+
return <BaseButton className={cn(buttonVariants({ variant }))} {...props} />;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* #### Crea tus propias variantes
|
|
285
|
+
* En caso de que necesites customizar demasiado a tu componente, lo mejor es crear tus propias variantes.
|
|
286
|
+
* Para esto se puede importar el componente `BaseButton` el cuál no incluye estilos por defecto y se puede extender para crear variantes personalizadas.
|
|
287
|
+
*
|
|
288
|
+
* A continuación, se definen las variantes de botón que puedes extender o modificar a tus necesidades.
|
|
289
|
+
*
|
|
290
|
+
* ```tsx
|
|
291
|
+
* import { ComponentProps } from 'react';
|
|
292
|
+
* import { cva, type VariantProps } from 'class-variance-authority';
|
|
293
|
+
* import { BaseButton, cn } from '@boxcustodia/library';
|
|
294
|
+
*
|
|
295
|
+
* export const buttonVariants = cva(
|
|
296
|
+
* [
|
|
297
|
+
* 'flex items-center justify-center gap-2',
|
|
298
|
+
* 'text-sm font-medium transition-all',
|
|
299
|
+
* 'rounded-md ring-offset-background',
|
|
300
|
+
* 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
301
|
+
* 'disabled:pointer-events-none disabled:opacity-50',
|
|
302
|
+
* 'hover:brightness-105',
|
|
303
|
+
* ],
|
|
304
|
+
* {
|
|
305
|
+
* variants: {
|
|
306
|
+
* variant: {
|
|
307
|
+
* default: "bg-primary text-primary-foreground",
|
|
308
|
+
* error: "bg-error text-error-foreground",
|
|
309
|
+
* success: "bg-success text-success-foreground",
|
|
310
|
+
* outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
311
|
+
* secondary: "bg-secondary text-secondary-foreground hover:brightness-100 hover:bg-secondary/80",
|
|
312
|
+
* ghost: "hover:bg-accent hover:brightness-100 hover:text-accent-foreground",
|
|
313
|
+
* link: "text-primary underline-offset-4 hover:underline",
|
|
314
|
+
* white: 'bg-white text-primary', <-- Nueva variante
|
|
315
|
+
* black: 'bg-black text-white', <-- Nueva variante
|
|
316
|
+
* },
|
|
317
|
+
* size: {
|
|
318
|
+
* default: "h-10 px-4 py-2",
|
|
319
|
+
* sm: "h-9 rounded-md px-3",
|
|
320
|
+
* lg: "h-11 rounded-md px-8",
|
|
321
|
+
* icon: "w-10 aspect-square",
|
|
322
|
+
* },
|
|
323
|
+
* },
|
|
324
|
+
* defaultVariants: {
|
|
325
|
+
* variant: 'default',
|
|
326
|
+
* size: 'default',
|
|
327
|
+
* },
|
|
328
|
+
* },
|
|
329
|
+
* );
|
|
330
|
+
*
|
|
331
|
+
* type BaseButtonProps = ComponentProps<typeof BaseButton>;
|
|
332
|
+
* typer Props = BaseButtonProps, VariantProps<typeof buttonVariants>
|
|
333
|
+
*
|
|
334
|
+
* export const Button = ({ className, variant, size, ...props }: Props) => {
|
|
335
|
+
* return (
|
|
336
|
+
* <BaseButton
|
|
337
|
+
* {...props}
|
|
338
|
+
* className={cn(buttonVariants({ variant, size }), className)}
|
|
339
|
+
* />
|
|
340
|
+
* );
|
|
341
|
+
* };
|
|
342
|
+
*
|
|
343
|
+
* ```
|
|
344
|
+
*
|
|
345
|
+
*/
|
|
346
|
+
|
|
347
|
+
export const CustomVariants: Story = {
|
|
348
|
+
render: () => (
|
|
349
|
+
<div className="space-y-4">
|
|
350
|
+
<CustomButton variant="black">Default Button</CustomButton>
|
|
351
|
+
<CustomButton variant="white">Error Button</CustomButton>
|
|
352
|
+
</div>
|
|
353
|
+
),
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* ThemeProvider permite customizar de manera global las propiedades del componente
|
|
358
|
+
*
|
|
359
|
+
* Ejemplo:
|
|
360
|
+
* ```tsx
|
|
361
|
+
* <ThemeProvider
|
|
362
|
+
* theme={{
|
|
363
|
+
* Button: {
|
|
364
|
+
* className: "bg-red-500",
|
|
365
|
+
* icon: <SearchIcon />,
|
|
366
|
+
* iconPosition: "end",
|
|
367
|
+
* loaderReplace: true,
|
|
368
|
+
* },
|
|
369
|
+
* }}
|
|
370
|
+
* >
|
|
371
|
+
* <Button>Default Button</Button>
|
|
372
|
+
* </ThemeProvider>
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
export const Theme: Story = {
|
|
376
|
+
render: () => {
|
|
377
|
+
const sleep = (): Promise<void> => {
|
|
378
|
+
return new Promise((resolve) => setTimeout(resolve, 2000));
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<ThemeProvider
|
|
383
|
+
theme={{
|
|
384
|
+
Button: {
|
|
385
|
+
className: "bg-red-500",
|
|
386
|
+
icon: <SearchIcon />,
|
|
387
|
+
iconPosition: "end",
|
|
388
|
+
loaderReplace: true,
|
|
389
|
+
},
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
<Button onClick={sleep}>Default Button</Button>
|
|
393
|
+
</ThemeProvider>
|
|
394
|
+
);
|
|
395
|
+
},
|
|
396
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { createRef } from "react";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { Button } from "../../components";
|
|
5
|
+
|
|
6
|
+
describe("Button component", () => {
|
|
7
|
+
it("should render correctly", () => {
|
|
8
|
+
render(<Button />);
|
|
9
|
+
const button = screen.getByRole("button");
|
|
10
|
+
|
|
11
|
+
expect(button).toBeInTheDocument();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should render children correctly", () => {
|
|
15
|
+
render(<Button>Click me</Button>);
|
|
16
|
+
const button = screen.getByRole("button");
|
|
17
|
+
|
|
18
|
+
expect(button).toHaveTextContent("Click me");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should render as child correctly", () => {
|
|
22
|
+
render(
|
|
23
|
+
<Button asChild>
|
|
24
|
+
<a href="#">Click me</a>
|
|
25
|
+
</Button>,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// No debe renderizarse como un button
|
|
29
|
+
const button = screen.queryByRole("button");
|
|
30
|
+
expect(button).not.toBeInTheDocument();
|
|
31
|
+
|
|
32
|
+
// Debe renderizarse como un link
|
|
33
|
+
const link = screen.getByRole("link");
|
|
34
|
+
expect(link).toBeInTheDocument();
|
|
35
|
+
expect(link).toHaveTextContent("Click me");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should accept className prop", () => {
|
|
39
|
+
const TEST_CLASS = "lorem";
|
|
40
|
+
render(<Button className={TEST_CLASS} />);
|
|
41
|
+
const button = screen.getByRole("button");
|
|
42
|
+
|
|
43
|
+
expect(button).toHaveClass(TEST_CLASS);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should accept ref prop", () => {
|
|
47
|
+
const ref = createRef<HTMLButtonElement>();
|
|
48
|
+
render(<Button ref={ref} />);
|
|
49
|
+
|
|
50
|
+
expect(ref.current).not.toBeNull();
|
|
51
|
+
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should show loader", () => {
|
|
55
|
+
render(<Button loading />);
|
|
56
|
+
expect(screen.getByTestId("btn-loader")).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type VariantProps } from "class-variance-authority";
|
|
2
|
+
import { ComponentProps } from "react";
|
|
3
|
+
import { cn } from "../../lib";
|
|
4
|
+
import { useThemeProps } from "../../providers/theme/useThemeProps";
|
|
5
|
+
import { buttonVariants } from "./button.variants";
|
|
6
|
+
import { BaseButton } from "./components/base-button";
|
|
7
|
+
|
|
8
|
+
type BaseButtonProps = ComponentProps<typeof BaseButton>;
|
|
9
|
+
type Props = BaseButtonProps & VariantProps<typeof buttonVariants>;
|
|
10
|
+
|
|
11
|
+
export const Button = (props: Props) => {
|
|
12
|
+
const { variant, size, shape, className, ...rest } = useThemeProps(
|
|
13
|
+
"Button",
|
|
14
|
+
props,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<BaseButton
|
|
19
|
+
{...rest}
|
|
20
|
+
className={cn(
|
|
21
|
+
buttonVariants({
|
|
22
|
+
variant,
|
|
23
|
+
size,
|
|
24
|
+
shape,
|
|
25
|
+
}),
|
|
26
|
+
className,
|
|
27
|
+
)}
|
|
28
|
+
data-variant={variant || "default"}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Slot, Slottable } from "@radix-ui/react-slot";
|
|
2
|
+
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
3
|
+
import { ComponentProps, ReactNode } from "react";
|
|
4
|
+
import { ClickEvent } from "@/models";
|
|
5
|
+
import { LoaderOverlay } from "./loader-overlay";
|
|
6
|
+
import { LoadingIcon } from "./loading-icon";
|
|
7
|
+
|
|
8
|
+
interface Props extends ComponentProps<"button"> {
|
|
9
|
+
asChild?: boolean;
|
|
10
|
+
loading?: boolean;
|
|
11
|
+
showLoader?: boolean;
|
|
12
|
+
icon?: ReactNode;
|
|
13
|
+
iconPosition?: "start" | "end";
|
|
14
|
+
loaderReplace?: true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const baseStyles = {
|
|
18
|
+
display: "inline-flex",
|
|
19
|
+
alignItems: "center",
|
|
20
|
+
gap: "0.5rem",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const BaseButton = ({
|
|
24
|
+
asChild = false,
|
|
25
|
+
onClick,
|
|
26
|
+
loading: prop,
|
|
27
|
+
showLoader = true,
|
|
28
|
+
icon,
|
|
29
|
+
iconPosition = "start",
|
|
30
|
+
loaderReplace,
|
|
31
|
+
style,
|
|
32
|
+
...props
|
|
33
|
+
}: Props) => {
|
|
34
|
+
const Comp = asChild ? Slot : "button";
|
|
35
|
+
const [loading = false, setLoading] = useControllableState<boolean>({
|
|
36
|
+
prop,
|
|
37
|
+
defaultProp: false,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const handleClick = async (e: ClickEvent) => {
|
|
41
|
+
if (!onClick || loading) return;
|
|
42
|
+
|
|
43
|
+
const onClickResult = onClick(e) as unknown;
|
|
44
|
+
|
|
45
|
+
if (onClickResult instanceof Promise) {
|
|
46
|
+
showLoader && setLoading(true);
|
|
47
|
+
await onClickResult.finally(() => setLoading(false));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// TODO: Refactorizar lógica de renderización de iconos
|
|
52
|
+
const renderIconWithLoader = (position: "start" | "end") => {
|
|
53
|
+
if (position !== iconPosition) return null;
|
|
54
|
+
const isLoading = loaderReplace ? false : loading;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<LoadingIcon loading={isLoading && !icon} animate={true} />
|
|
59
|
+
{icon && <LoadingIcon loading={isLoading} animate={false} />}
|
|
60
|
+
{icon && !isLoading && icon}
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const isLoading = loading && showLoader;
|
|
66
|
+
const isReplaceMode = loaderReplace && isLoading;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Comp
|
|
70
|
+
type="button"
|
|
71
|
+
data-slot="button"
|
|
72
|
+
{...props}
|
|
73
|
+
onClick={handleClick}
|
|
74
|
+
style={{
|
|
75
|
+
...baseStyles,
|
|
76
|
+
position: isReplaceMode ? "relative" : undefined,
|
|
77
|
+
...style,
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{renderIconWithLoader("start")}
|
|
81
|
+
<Slottable>{props.children}</Slottable>
|
|
82
|
+
{renderIconWithLoader("end")}
|
|
83
|
+
{isReplaceMode && <LoaderOverlay loading={true} />}
|
|
84
|
+
</Comp>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
);
|
|
@@ -0,0 +1,47 @@
|
|
|
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 };
|