@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,411 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ToastManagerAddOptions as BaseToastManagerAddOptions,
|
|
3
|
+
Toast,
|
|
4
|
+
type ToastObject,
|
|
5
|
+
} from "@base-ui/react/toast";
|
|
6
|
+
import { CircleCheck, CircleX, Info, TriangleAlert, X } from "lucide-react";
|
|
7
|
+
import {
|
|
8
|
+
type ComponentProps,
|
|
9
|
+
type ComponentType,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
useEffect,
|
|
12
|
+
useMemo,
|
|
13
|
+
} from "react";
|
|
14
|
+
import { cn } from "../../lib";
|
|
15
|
+
import { Button } from "../button";
|
|
16
|
+
|
|
17
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
export type ToastVariant = "default" | "success" | "error" | "warning" | "info";
|
|
20
|
+
|
|
21
|
+
type ButtonProps = ComponentProps<typeof Button>;
|
|
22
|
+
|
|
23
|
+
// ─── Variant configuration ────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
type VariantConfig = {
|
|
26
|
+
icon: ComponentType<{ className?: string }> | null;
|
|
27
|
+
ringClass: string;
|
|
28
|
+
titleClass: string;
|
|
29
|
+
bgClass: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const TOAST_VARIANT_CONFIG: Record<ToastVariant, VariantConfig> = {
|
|
33
|
+
default: {
|
|
34
|
+
icon: null,
|
|
35
|
+
ringClass: "ring-input",
|
|
36
|
+
titleClass: "",
|
|
37
|
+
bgClass: "",
|
|
38
|
+
},
|
|
39
|
+
success: {
|
|
40
|
+
icon: CircleCheck,
|
|
41
|
+
ringClass: "ring-success",
|
|
42
|
+
titleClass: "text-success",
|
|
43
|
+
bgClass: "bg-success/5",
|
|
44
|
+
},
|
|
45
|
+
error: {
|
|
46
|
+
icon: CircleX,
|
|
47
|
+
ringClass: "ring-error",
|
|
48
|
+
titleClass: "text-error",
|
|
49
|
+
bgClass: "bg-error/5",
|
|
50
|
+
},
|
|
51
|
+
warning: {
|
|
52
|
+
icon: TriangleAlert,
|
|
53
|
+
ringClass: "ring-warning",
|
|
54
|
+
titleClass: "text-warning",
|
|
55
|
+
bgClass: "bg-warning/5",
|
|
56
|
+
},
|
|
57
|
+
info: {
|
|
58
|
+
icon: Info,
|
|
59
|
+
ringClass: "ring-info",
|
|
60
|
+
titleClass: "text-info",
|
|
61
|
+
bgClass: "bg-info/5",
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function toastVariants({
|
|
66
|
+
variant = "default",
|
|
67
|
+
}: {
|
|
68
|
+
variant?: ToastVariant;
|
|
69
|
+
} = {}) {
|
|
70
|
+
return cn(
|
|
71
|
+
"rounded-xl ring-1 bg-clip-padding p-4 shadow-lg bg-background",
|
|
72
|
+
TOAST_VARIANT_CONFIG[variant].ringClass,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─── Option types ─────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
type ToastOptionsBase = {
|
|
79
|
+
variant?: ToastVariant;
|
|
80
|
+
content?: ReactNode;
|
|
81
|
+
actions?: ButtonProps[];
|
|
82
|
+
bump?: boolean;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type ToastOptions<Data extends object = object> = ToastObject<Data> &
|
|
86
|
+
ToastOptionsBase;
|
|
87
|
+
export type ToastManagerAddOptions<Data extends object = object> =
|
|
88
|
+
BaseToastManagerAddOptions<Data> & ToastOptionsBase;
|
|
89
|
+
|
|
90
|
+
// ─── Manager wrapping ─────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
function wrapManagerMethods<
|
|
93
|
+
T extends {
|
|
94
|
+
add: Function;
|
|
95
|
+
update: Function;
|
|
96
|
+
promise: Function;
|
|
97
|
+
close: Function;
|
|
98
|
+
},
|
|
99
|
+
>(manager: T) {
|
|
100
|
+
return {
|
|
101
|
+
...manager,
|
|
102
|
+
|
|
103
|
+
close: (id?: string) => manager.close(id),
|
|
104
|
+
|
|
105
|
+
add: (options: ToastManagerAddOptions<any>) => {
|
|
106
|
+
if (options.id) {
|
|
107
|
+
const toasts = (manager as any).toasts as
|
|
108
|
+
| Array<ToastObject<any>>
|
|
109
|
+
| undefined;
|
|
110
|
+
|
|
111
|
+
if (toasts) {
|
|
112
|
+
const existing = toasts.find((t) => t.id === options.id);
|
|
113
|
+
|
|
114
|
+
if (existing && existing.transitionStatus !== "ending") {
|
|
115
|
+
manager.update(options.id, { bump: false });
|
|
116
|
+
requestAnimationFrame(() => {
|
|
117
|
+
manager.update(options.id, {
|
|
118
|
+
bump: true,
|
|
119
|
+
...(options.timeout !== undefined && {
|
|
120
|
+
timeout: options.timeout,
|
|
121
|
+
}),
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
return options.id;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (existing && existing.transitionStatus === "ending") {
|
|
128
|
+
return options.id;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return manager.add({ ...options });
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
update: (id: string, options: Partial<ToastManagerAddOptions<any>>) => {
|
|
137
|
+
return manager.update(id, { ...options });
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
promise: <T,>(
|
|
141
|
+
promise: Promise<T>,
|
|
142
|
+
options: {
|
|
143
|
+
loading: ToastManagerAddOptions<any>;
|
|
144
|
+
success:
|
|
145
|
+
| ToastManagerAddOptions<any>
|
|
146
|
+
| ((data: T) => ToastManagerAddOptions<any>);
|
|
147
|
+
error:
|
|
148
|
+
| ToastManagerAddOptions<any>
|
|
149
|
+
| ((error: Error) => ToastManagerAddOptions<any>);
|
|
150
|
+
},
|
|
151
|
+
) => {
|
|
152
|
+
return manager.promise(promise, {
|
|
153
|
+
loading: { ...options.loading },
|
|
154
|
+
success:
|
|
155
|
+
typeof options.success === "function"
|
|
156
|
+
? (data: T) => ({
|
|
157
|
+
...(options.success as (d: T) => ToastManagerAddOptions<any>)(
|
|
158
|
+
data,
|
|
159
|
+
),
|
|
160
|
+
})
|
|
161
|
+
: { ...options.success },
|
|
162
|
+
error:
|
|
163
|
+
typeof options.error === "function"
|
|
164
|
+
? (err: Error) => ({
|
|
165
|
+
...(options.error as (e: Error) => ToastManagerAddOptions<any>)(
|
|
166
|
+
err,
|
|
167
|
+
),
|
|
168
|
+
})
|
|
169
|
+
: { ...options.error },
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function useToastManager() {
|
|
176
|
+
const manager = Toast.useToastManager();
|
|
177
|
+
return {
|
|
178
|
+
...wrapManagerMethods(manager),
|
|
179
|
+
toasts: manager.toasts as ToastOptions<any>[],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function createToastManager() {
|
|
184
|
+
return wrapManagerMethods(Toast.createToastManager());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ─── Singleton + convenience API ─────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
// Captured by ToastManagerBridge on provider mount — points to the internal
|
|
190
|
+
// Base UI manager so that toast.* calls update React state synchronously,
|
|
191
|
+
// avoiding the extra async tick of the subscription-based external manager.
|
|
192
|
+
let _activeManager: ReturnType<
|
|
193
|
+
typeof wrapManagerMethods<ReturnType<typeof Toast.useToastManager>>
|
|
194
|
+
> | null = null;
|
|
195
|
+
|
|
196
|
+
type ToastArg = string | Omit<ToastManagerAddOptions<any>, "variant">;
|
|
197
|
+
|
|
198
|
+
function resolveArgs(
|
|
199
|
+
arg: ToastArg,
|
|
200
|
+
): Omit<ToastManagerAddOptions<any>, "variant"> {
|
|
201
|
+
return typeof arg === "string" ? { title: arg } : arg;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Imperative toast API. Works anywhere — no hook needed.
|
|
206
|
+
*
|
|
207
|
+
* Requires `<ToastProvider>` somewhere in the tree (no extra props needed).
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* toast("Notificación"); // default variant
|
|
212
|
+
* toast.success("Guardado correctamente");
|
|
213
|
+
* toast.error({ title: "Error", description: "Algo salió mal" });
|
|
214
|
+
* toast.promise(saveData(), {
|
|
215
|
+
* loading: { description: "Guardando..." },
|
|
216
|
+
* success: () => ({ variant: "success", description: "Guardado!" }),
|
|
217
|
+
* error: (e) => ({ variant: "error", description: e.message }),
|
|
218
|
+
* });
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
export const toast = Object.assign(
|
|
222
|
+
(arg: ToastArg) => _activeManager?.add({ ...resolveArgs(arg) }),
|
|
223
|
+
{
|
|
224
|
+
success: (arg: ToastArg) =>
|
|
225
|
+
_activeManager?.add({ variant: "success", ...resolveArgs(arg) }),
|
|
226
|
+
error: (arg: ToastArg) =>
|
|
227
|
+
_activeManager?.add({ variant: "error", ...resolveArgs(arg) }),
|
|
228
|
+
warning: (arg: ToastArg) =>
|
|
229
|
+
_activeManager?.add({ variant: "warning", ...resolveArgs(arg) }),
|
|
230
|
+
info: (arg: ToastArg) =>
|
|
231
|
+
_activeManager?.add({ variant: "info", ...resolveArgs(arg) }),
|
|
232
|
+
promise: <T,>(
|
|
233
|
+
promise: Promise<T>,
|
|
234
|
+
options: {
|
|
235
|
+
loading: ToastManagerAddOptions<any>;
|
|
236
|
+
success:
|
|
237
|
+
| ToastManagerAddOptions<any>
|
|
238
|
+
| ((data: T) => ToastManagerAddOptions<any>);
|
|
239
|
+
error:
|
|
240
|
+
| ToastManagerAddOptions<any>
|
|
241
|
+
| ((error: Error) => ToastManagerAddOptions<any>);
|
|
242
|
+
},
|
|
243
|
+
) => _activeManager?.promise(promise, options),
|
|
244
|
+
},
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// ─── Provider ─────────────────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
export interface ToastProviderProps {
|
|
250
|
+
children: ReactNode;
|
|
251
|
+
/**
|
|
252
|
+
* External toast manager created with `createToastManager()`.
|
|
253
|
+
* Only needed for isolated instances (tests, microfrontends).
|
|
254
|
+
* The default `<ToastProvider>` (no props) is all you need for `toast.*` to work.
|
|
255
|
+
*/
|
|
256
|
+
toastManager?: ReturnType<typeof Toast.createToastManager>;
|
|
257
|
+
/**
|
|
258
|
+
* Container element for the portal.
|
|
259
|
+
* @default document.body
|
|
260
|
+
*/
|
|
261
|
+
container?: HTMLElement | null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Captures the internal Base UI manager so that `toast.*` calls update
|
|
266
|
+
* React state synchronously, avoiding async subscription lag.
|
|
267
|
+
*/
|
|
268
|
+
function ToastManagerBridge() {
|
|
269
|
+
const manager = Toast.useToastManager();
|
|
270
|
+
const wrapped = useMemo(() => wrapManagerMethods(manager), [manager]);
|
|
271
|
+
|
|
272
|
+
// Set during render so it's available immediately on first interaction.
|
|
273
|
+
// Safe for a module-level singleton — does not trigger re-renders.
|
|
274
|
+
_activeManager = wrapped;
|
|
275
|
+
|
|
276
|
+
useEffect(
|
|
277
|
+
() => () => {
|
|
278
|
+
_activeManager = null;
|
|
279
|
+
},
|
|
280
|
+
[],
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* ToastProvider — wrap your app once to enable toast notifications.
|
|
288
|
+
*
|
|
289
|
+
* Toasts stack in the bottom-right corner with swipe-to-dismiss and expand-on-hover.
|
|
290
|
+
* Once mounted, call `toast.success()` / `toast.error()` etc. from anywhere.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```tsx
|
|
294
|
+
* // app.tsx — one-time setup, no extra props needed
|
|
295
|
+
* <ToastProvider>
|
|
296
|
+
* <App />
|
|
297
|
+
* </ToastProvider>
|
|
298
|
+
*
|
|
299
|
+
* // Anywhere in the codebase
|
|
300
|
+
* import { toast } from "@boxcustodia/library";
|
|
301
|
+
* toast.success("Cambios guardados");
|
|
302
|
+
* toast.error({ title: "Error", description: "Algo salió mal" });
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
export function ToastProvider({
|
|
306
|
+
children,
|
|
307
|
+
toastManager,
|
|
308
|
+
container,
|
|
309
|
+
}: ToastProviderProps) {
|
|
310
|
+
return (
|
|
311
|
+
<Toast.Provider toastManager={toastManager}>
|
|
312
|
+
<ToastManagerBridge />
|
|
313
|
+
{children}
|
|
314
|
+
<Toast.Portal container={container}>
|
|
315
|
+
<Toast.Viewport className="fixed top-auto right-4 bottom-4 mx-auto flex w-[calc(100%-2rem)] sm:right-8 sm:bottom-8 sm:w-[340px]">
|
|
316
|
+
<ToastList />
|
|
317
|
+
</Toast.Viewport>
|
|
318
|
+
</Toast.Portal>
|
|
319
|
+
</Toast.Provider>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ─── Internal render helpers ──────────────────────────────────────────────────
|
|
324
|
+
|
|
325
|
+
function ToastList() {
|
|
326
|
+
const { toasts } = useToastManager();
|
|
327
|
+
return toasts.map((toast) => (
|
|
328
|
+
<Toast.Root
|
|
329
|
+
key={toast.id}
|
|
330
|
+
toast={toast}
|
|
331
|
+
data-slot="toast"
|
|
332
|
+
className={cn(
|
|
333
|
+
"absolute right-0 bottom-0 left-auto z-[calc(var(--z-toast)-var(--toast-index))] mr-0 h-[var(--height)] w-full origin-bottom select-none",
|
|
334
|
+
toastVariants({ variant: toast.variant }),
|
|
335
|
+
"[--gap:0.75rem] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))]",
|
|
336
|
+
"[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))] [transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]",
|
|
337
|
+
"after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']",
|
|
338
|
+
"data-[ending-style]:opacity-0 data-[expanded]:h-[var(--toast-height)] data-[expanded]:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--offset-y)))] data-[limited]:opacity-0 data-[starting-style]:[transform:translateY(150%)]",
|
|
339
|
+
"data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-[expanded]:data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]",
|
|
340
|
+
"data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-[expanded]:data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]",
|
|
341
|
+
"data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-[expanded]:data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]",
|
|
342
|
+
"data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] data-[expanded]:data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]",
|
|
343
|
+
"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]",
|
|
344
|
+
toast.bump && "animate-toast-bump",
|
|
345
|
+
)}
|
|
346
|
+
>
|
|
347
|
+
<ToastBackground variant={toast.variant} />
|
|
348
|
+
<Toast.Content className="isolate flex flex-col gap-1 transition-opacity [transition-duration:250ms] data-[behind]:pointer-events-none data-[behind]:opacity-0 data-[expanded]:pointer-events-auto data-[expanded]:opacity-100">
|
|
349
|
+
{toast.content ?? (
|
|
350
|
+
<div className="flex items-start gap-2">
|
|
351
|
+
<ToastIcon variant={toast.variant} />
|
|
352
|
+
<div className="flex flex-col gap-1 overflow-hidden">
|
|
353
|
+
<Toast.Title
|
|
354
|
+
data-slot="toast-title"
|
|
355
|
+
className={cn(
|
|
356
|
+
"text-[0.975rem] leading-5 font-medium text-foreground",
|
|
357
|
+
TOAST_VARIANT_CONFIG[toast.variant ?? "default"].titleClass,
|
|
358
|
+
)}
|
|
359
|
+
/>
|
|
360
|
+
<Toast.Description
|
|
361
|
+
data-slot="toast-description"
|
|
362
|
+
className="text-[0.925rem] leading-5 text-muted-foreground"
|
|
363
|
+
/>
|
|
364
|
+
{!!toast.actions?.length && (
|
|
365
|
+
<div className="mt-2 flex min-w-0 flex-nowrap gap-2 overflow-x-auto p-px">
|
|
366
|
+
{toast.actions.map((actionProps, idx) => (
|
|
367
|
+
<Button key={idx} {...actionProps} />
|
|
368
|
+
))}
|
|
369
|
+
</div>
|
|
370
|
+
)}
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
<Toast.Close
|
|
375
|
+
className="absolute top-2 right-2 flex h-4 w-4 items-center justify-center rounded border-none bg-transparent text-current/50 hover:bg-foreground/10 hover:text-current"
|
|
376
|
+
aria-label="Close"
|
|
377
|
+
>
|
|
378
|
+
<X className="h-3 w-3" />
|
|
379
|
+
</Toast.Close>
|
|
380
|
+
</Toast.Content>
|
|
381
|
+
</Toast.Root>
|
|
382
|
+
));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function ToastBackground({ variant }: { variant?: ToastVariant }) {
|
|
386
|
+
const bgClass = variant ? TOAST_VARIANT_CONFIG[variant].bgClass : "";
|
|
387
|
+
return (
|
|
388
|
+
<div
|
|
389
|
+
className={cn(
|
|
390
|
+
"absolute inset-0 rounded-[11px] bg-background/90",
|
|
391
|
+
bgClass,
|
|
392
|
+
)}
|
|
393
|
+
/>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function ToastIcon({ variant }: { variant?: ToastVariant }) {
|
|
398
|
+
if (!variant || variant === "default") return null;
|
|
399
|
+
const { icon: Icon, titleClass } = TOAST_VARIANT_CONFIG[variant];
|
|
400
|
+
if (!Icon) return null;
|
|
401
|
+
return (
|
|
402
|
+
<Icon
|
|
403
|
+
data-slot="toast-icon"
|
|
404
|
+
className={cn("mt-0.5 h-4 w-4 shrink-0", titleClass)}
|
|
405
|
+
/>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ─── Primitive escape hatch ───────────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
export { Toast as ToastPrimitive };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./tooltip";
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { Info } from "lucide-react";
|
|
3
|
+
import {
|
|
4
|
+
AlertDialogClose,
|
|
5
|
+
AlertDialogPopup,
|
|
6
|
+
AlertDialogRoot,
|
|
7
|
+
AlertDialogTrigger,
|
|
8
|
+
Button,
|
|
9
|
+
Tooltip,
|
|
10
|
+
TooltipArrow,
|
|
11
|
+
TooltipPopup,
|
|
12
|
+
TooltipPrimitive,
|
|
13
|
+
TooltipRoot,
|
|
14
|
+
TooltipTrigger,
|
|
15
|
+
} from "../../components";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Accessible tooltip built on Base UI. Shows supplementary information on hover or focus.
|
|
19
|
+
* Children become the trigger via Base UI's `render` prop — no wrapper element is added.
|
|
20
|
+
* Accepts `open`/`defaultOpen`/`onOpenChange` for controlled and uncontrolled usage.
|
|
21
|
+
*
|
|
22
|
+
* **Requires `<LibraryProvider>` at the app root.** It wires up `TooltipProvider` globally,
|
|
23
|
+
* enabling shared hover intent: once one tooltip opens, subsequent ones skip `delay` until
|
|
24
|
+
* the cursor leaves all triggers.
|
|
25
|
+
*/
|
|
26
|
+
const meta: Meta<typeof Tooltip> = {
|
|
27
|
+
title: "Components/Tooltip",
|
|
28
|
+
component: Tooltip,
|
|
29
|
+
parameters: {
|
|
30
|
+
layout: "centered",
|
|
31
|
+
},
|
|
32
|
+
args: {
|
|
33
|
+
content: "Tooltip content",
|
|
34
|
+
children: (
|
|
35
|
+
<Button variant="outline">
|
|
36
|
+
<Info />
|
|
37
|
+
Hover me
|
|
38
|
+
</Button>
|
|
39
|
+
),
|
|
40
|
+
},
|
|
41
|
+
argTypes: {
|
|
42
|
+
children: { control: false },
|
|
43
|
+
content: { control: "text" },
|
|
44
|
+
side: {
|
|
45
|
+
control: "select",
|
|
46
|
+
options: ["top", "bottom", "left", "right"],
|
|
47
|
+
},
|
|
48
|
+
align: {
|
|
49
|
+
control: "select",
|
|
50
|
+
options: ["start", "center", "end"],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default meta;
|
|
56
|
+
type Story = StoryObj<typeof Tooltip>;
|
|
57
|
+
|
|
58
|
+
export const Default: Story = {};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* All four `side` values side by side. The arrow and popup reposition automatically.
|
|
62
|
+
*/
|
|
63
|
+
export const Sides: Story = {
|
|
64
|
+
render: () => (
|
|
65
|
+
<div className="grid grid-cols-2 gap-x-12 gap-y-10 p-12">
|
|
66
|
+
{(["top", "right", "bottom", "left"] as const).map((side) => (
|
|
67
|
+
<Tooltip key={side} content={`side="${side}"`} side={side} defaultOpen>
|
|
68
|
+
<Button variant="outline" className="w-24">
|
|
69
|
+
{side}
|
|
70
|
+
</Button>
|
|
71
|
+
</Tooltip>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* All three `align` values with `side="right"` for clarity.
|
|
79
|
+
* `align` controls placement along the axis perpendicular to `side`.
|
|
80
|
+
*/
|
|
81
|
+
export const Align: Story = {
|
|
82
|
+
render: () => (
|
|
83
|
+
<div className="flex flex-col gap-2 py-8">
|
|
84
|
+
{(["start", "center", "end"] as const).map((align) => (
|
|
85
|
+
<Tooltip
|
|
86
|
+
key={align}
|
|
87
|
+
content={`align="${align}"`}
|
|
88
|
+
side="right"
|
|
89
|
+
align={align}
|
|
90
|
+
defaultOpen
|
|
91
|
+
>
|
|
92
|
+
<Button variant="outline" className="w-28 h-28">
|
|
93
|
+
{align}
|
|
94
|
+
</Button>
|
|
95
|
+
</Tooltip>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* `offset` controls the gap in pixels between the tooltip and the trigger. Default is `10`.
|
|
103
|
+
*/
|
|
104
|
+
export const Offset: Story = {
|
|
105
|
+
args: {
|
|
106
|
+
offset: 24,
|
|
107
|
+
defaultOpen: true,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* `delay` sets how long (ms) the user must hover before the tooltip opens.
|
|
113
|
+
* `closeDelay` sets how long it stays visible after the cursor leaves.
|
|
114
|
+
* Both default to Base UI's built-in values (600ms open, 0ms close).
|
|
115
|
+
*/
|
|
116
|
+
export const Delay: Story = {
|
|
117
|
+
render: () => (
|
|
118
|
+
<div className="flex items-center gap-6">
|
|
119
|
+
<Tooltip content="Opens instantly" delay={0}>
|
|
120
|
+
<Button variant="outline">delay=0</Button>
|
|
121
|
+
</Tooltip>
|
|
122
|
+
<Tooltip content="Opens after 1s" delay={1000}>
|
|
123
|
+
<Button variant="outline">delay=1000</Button>
|
|
124
|
+
</Tooltip>
|
|
125
|
+
<Tooltip content="Stays open 1s after blur" closeDelay={1000}>
|
|
126
|
+
<Button variant="outline">closeDelay=1000</Button>
|
|
127
|
+
</Tooltip>
|
|
128
|
+
</div>
|
|
129
|
+
),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `className` overrides the popup styles. `arrowClassName` accepts a `text-*` class
|
|
134
|
+
* to recolor the arrow — it uses `currentColor`, so `text-primary` turns the arrow primary.
|
|
135
|
+
*/
|
|
136
|
+
export const CustomColor: Story = {
|
|
137
|
+
args: {
|
|
138
|
+
content: "Custom color",
|
|
139
|
+
className: "bg-primary text-primary-foreground",
|
|
140
|
+
arrowClassName: "text-primary",
|
|
141
|
+
defaultOpen: true,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* `defaultOpen` renders the tooltip open without external state.
|
|
147
|
+
* Use `open` + `onOpenChange` for fully controlled behavior.
|
|
148
|
+
*/
|
|
149
|
+
export const Controlled: Story = {
|
|
150
|
+
args: {
|
|
151
|
+
open: true,
|
|
152
|
+
content: "Always visible",
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Direct composition with primitives for full structural control.
|
|
158
|
+
* `TooltipPrimitive` exposes every Base UI subcomponent not wrapped by the library.
|
|
159
|
+
*
|
|
160
|
+
* ```tsx
|
|
161
|
+
* <TooltipRoot>
|
|
162
|
+
* <TooltipTrigger render={<Button />} />
|
|
163
|
+
* <TooltipPrimitive.Portal>
|
|
164
|
+
* <TooltipPrimitive.Positioner side="top" sideOffset={10}>
|
|
165
|
+
* <TooltipPopup>
|
|
166
|
+
* <TooltipArrow />
|
|
167
|
+
* Custom content
|
|
168
|
+
* </TooltipPopup>
|
|
169
|
+
* </TooltipPrimitive.Positioner>
|
|
170
|
+
* </TooltipPrimitive.Portal>
|
|
171
|
+
* </TooltipRoot>
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export const Primitive: Story = {
|
|
175
|
+
render: () => (
|
|
176
|
+
<TooltipRoot>
|
|
177
|
+
<TooltipTrigger
|
|
178
|
+
render={
|
|
179
|
+
<Button variant="outline">
|
|
180
|
+
<Info />
|
|
181
|
+
Primitives
|
|
182
|
+
</Button>
|
|
183
|
+
}
|
|
184
|
+
/>
|
|
185
|
+
<TooltipPrimitive.Portal>
|
|
186
|
+
<TooltipPrimitive.Positioner side="top" sideOffset={10}>
|
|
187
|
+
<TooltipPopup>
|
|
188
|
+
<TooltipArrow />
|
|
189
|
+
Built with primitives
|
|
190
|
+
</TooltipPopup>
|
|
191
|
+
</TooltipPrimitive.Positioner>
|
|
192
|
+
</TooltipPrimitive.Portal>
|
|
193
|
+
</TooltipRoot>
|
|
194
|
+
),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Tooltip + AlertDialog composed on the same trigger.
|
|
199
|
+
* Base UI's `render` prop chains both triggers into a single `<Button>` —
|
|
200
|
+
* hover shows the tooltip, click opens the dialog.
|
|
201
|
+
*/
|
|
202
|
+
export const WithAlertDialog: Story = {
|
|
203
|
+
render: () => (
|
|
204
|
+
<AlertDialogRoot>
|
|
205
|
+
<Tooltip content="Esta acción no se puede deshacer">
|
|
206
|
+
<AlertDialogTrigger
|
|
207
|
+
render={<Button variant="error">Eliminar</Button>}
|
|
208
|
+
/>
|
|
209
|
+
</Tooltip>
|
|
210
|
+
<AlertDialogPopup>
|
|
211
|
+
<p className="font-semibold">¿Estás seguro?</p>
|
|
212
|
+
<p className="text-sm text-muted-foreground">
|
|
213
|
+
Esta acción no se puede deshacer.
|
|
214
|
+
</p>
|
|
215
|
+
<div className="flex justify-end gap-2">
|
|
216
|
+
<AlertDialogClose
|
|
217
|
+
render={<Button variant="secondary">Cancelar</Button>}
|
|
218
|
+
/>
|
|
219
|
+
<AlertDialogClose
|
|
220
|
+
render={<Button variant="error">Eliminar</Button>}
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
</AlertDialogPopup>
|
|
224
|
+
</AlertDialogRoot>
|
|
225
|
+
),
|
|
226
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { Button } from "../../components/button";
|
|
4
|
+
import { Tooltip } from ".";
|
|
5
|
+
|
|
6
|
+
describe("Tooltip component", () => {
|
|
7
|
+
const content = "Tooltip content";
|
|
8
|
+
|
|
9
|
+
it("should toggle the tooltip onFocus", async () => {
|
|
10
|
+
render(
|
|
11
|
+
<Tooltip content={content}>
|
|
12
|
+
<Button>Focus me</Button>
|
|
13
|
+
</Tooltip>,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const button = screen.getByText("Focus me");
|
|
17
|
+
fireEvent.focus(button);
|
|
18
|
+
|
|
19
|
+
const toooltip = screen.getByText(content);
|
|
20
|
+
expect(toooltip).toBeInTheDocument();
|
|
21
|
+
|
|
22
|
+
fireEvent.blur(button);
|
|
23
|
+
waitFor(() => {
|
|
24
|
+
expect(toooltip).not.toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should toggle the tooltip onHover", async () => {
|
|
29
|
+
render(
|
|
30
|
+
<Tooltip content={content}>
|
|
31
|
+
<Button>Hover me</Button>
|
|
32
|
+
</Tooltip>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const button = screen.getByText("Hover me");
|
|
36
|
+
fireEvent.mouseEnter(button);
|
|
37
|
+
|
|
38
|
+
const toooltip = screen.getByText(content);
|
|
39
|
+
expect(toooltip).toBeInTheDocument();
|
|
40
|
+
|
|
41
|
+
fireEvent.mouseLeave(button);
|
|
42
|
+
waitFor(() => {
|
|
43
|
+
expect(toooltip).not.toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|