@boxcustodia/library 2.0.0-alpha.10 → 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.cjs.js +70 -70
- package/dist/index.css +2 -0
- package/dist/index.d.ts +420 -272
- package/dist/index.es.js +34448 -27816
- package/dist/theme.css +1 -1
- package/package.json +11 -6
- 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
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Field } from "../field";
|
|
4
|
+
import { Label } from "../label";
|
|
5
|
+
import {
|
|
6
|
+
NumberInput,
|
|
7
|
+
NumberInputCursorIcon,
|
|
8
|
+
NumberInputDecrement,
|
|
9
|
+
NumberInputGroup,
|
|
10
|
+
NumberInputIncrement,
|
|
11
|
+
NumberInputInput,
|
|
12
|
+
NumberInputRoot,
|
|
13
|
+
NumberInputScrubArea,
|
|
14
|
+
NumberInputScrubAreaCursor,
|
|
15
|
+
} from "./number-input";
|
|
16
|
+
|
|
17
|
+
// TODO: Mostrar borde rojo si es invalid
|
|
18
|
+
/**
|
|
19
|
+
* Input numérico construido sobre Base UI NumberField.
|
|
20
|
+
* Soporta incremento/decremento por botones, teclado y scrub (drag horizontal).
|
|
21
|
+
* Usá `hideControls` para renderizar sólo el input sin botones.
|
|
22
|
+
* Para composición avanzada, usá los primitivos `NumberInputRoot`, `NumberInputGroup`, etc.
|
|
23
|
+
*/
|
|
24
|
+
const meta: Meta<typeof NumberInput> = {
|
|
25
|
+
title: "Components/NumberInput",
|
|
26
|
+
component: NumberInput,
|
|
27
|
+
parameters: { layout: "centered" },
|
|
28
|
+
args: {
|
|
29
|
+
name: "quantity",
|
|
30
|
+
defaultValue: 0,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
|
|
36
|
+
type Story = StoryObj<typeof NumberInput>;
|
|
37
|
+
|
|
38
|
+
export const Default: Story = {};
|
|
39
|
+
|
|
40
|
+
export const Disabled: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
disabled: true,
|
|
43
|
+
defaultValue: 42,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `hideControls` oculta los botones +/- y renderiza sólo el input.
|
|
49
|
+
* Útil cuando el espacio es limitado o los controles resultan innecesarios.
|
|
50
|
+
*/
|
|
51
|
+
export const HideControls: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
hideControls: true,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* El `ScrubArea` permite cambiar el valor arrastrando horizontalmente sobre la etiqueta.
|
|
59
|
+
* Requiere composición manual con los primitivos exportados.
|
|
60
|
+
*/
|
|
61
|
+
export const Scrub: Story = {
|
|
62
|
+
render: () => {
|
|
63
|
+
const [value, setValue] = useState(50);
|
|
64
|
+
return (
|
|
65
|
+
<NumberInputRoot
|
|
66
|
+
value={value}
|
|
67
|
+
onValueChange={(v) => v !== null && setValue(v)}
|
|
68
|
+
name="opacity"
|
|
69
|
+
allowWheelScrub
|
|
70
|
+
>
|
|
71
|
+
<div className="flex flex-col gap-2">
|
|
72
|
+
<NumberInputScrubArea>
|
|
73
|
+
<Label className="cursor-ew-resize" htmlFor="scrub-input">
|
|
74
|
+
Opacity
|
|
75
|
+
</Label>
|
|
76
|
+
<NumberInputScrubAreaCursor>
|
|
77
|
+
<NumberInputCursorIcon />
|
|
78
|
+
</NumberInputScrubAreaCursor>
|
|
79
|
+
</NumberInputScrubArea>
|
|
80
|
+
<NumberInputGroup>
|
|
81
|
+
<NumberInputDecrement />
|
|
82
|
+
<NumberInputInput id="scrub-input" />
|
|
83
|
+
<NumberInputIncrement />
|
|
84
|
+
</NumberInputGroup>
|
|
85
|
+
</div>
|
|
86
|
+
</NumberInputRoot>
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Usá `value` + `onChange` para controlar el valor externamente.
|
|
93
|
+
* `onChange` recibe `number | null` — `null` cuando el campo está vacío o contiene un valor inválido.
|
|
94
|
+
*/
|
|
95
|
+
export const Controlled: Story = {
|
|
96
|
+
render: () => {
|
|
97
|
+
const [value, setValue] = useState<number | null>(0);
|
|
98
|
+
return (
|
|
99
|
+
<div className="flex w-48 flex-col gap-2">
|
|
100
|
+
<NumberInput name="quantity" value={value} onChange={setValue} />
|
|
101
|
+
<p className="text-muted-foreground text-xs">
|
|
102
|
+
Valor: {value ?? "vacío"}
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Usá `min` y `max` para restringir el rango de valores.
|
|
111
|
+
* Los botones se deshabilitan automáticamente al alcanzar los límites.
|
|
112
|
+
*/
|
|
113
|
+
export const Range: Story = {
|
|
114
|
+
render: () => {
|
|
115
|
+
const [value, setValue] = useState(50);
|
|
116
|
+
return (
|
|
117
|
+
<div className="flex w-64 flex-col gap-2">
|
|
118
|
+
<NumberInput
|
|
119
|
+
name="progress"
|
|
120
|
+
value={value}
|
|
121
|
+
onChange={(v) => v !== null && setValue(v)}
|
|
122
|
+
min={0}
|
|
123
|
+
max={100}
|
|
124
|
+
step={10}
|
|
125
|
+
/>
|
|
126
|
+
<p className="text-muted-foreground text-xs">
|
|
127
|
+
Valor: {value} (0 – 100, paso 10)
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Pasá un objeto `format` compatible con `Intl.NumberFormat` para formatear el valor mostrado.
|
|
136
|
+
* El formato se aplica al mostrar y no interfiere con la edición ni con el valor interno.
|
|
137
|
+
*
|
|
138
|
+
* Para `style: "percent"`, el valor interno es decimal (0–1) y se muestra multiplicado por 100.
|
|
139
|
+
* Al tipear manualmente, incluí el símbolo "%" (ej: "80%") para que el parser lo interprete correctamente.
|
|
140
|
+
*
|
|
141
|
+
* Para campos tipo DNI, omitir `min`/`max` evita que el campo auto-corrija valores parciales mientras
|
|
142
|
+
* se escribe. La validación del rango se realiza en submit vía `Field` con el prop `error`.
|
|
143
|
+
*
|
|
144
|
+
* ```tsx
|
|
145
|
+
* <NumberInput
|
|
146
|
+
* name="dni"
|
|
147
|
+
* placeholder="Ej: 33.456.789"
|
|
148
|
+
* format={{ useGrouping: true, maximumFractionDigits: 0 }}
|
|
149
|
+
* hideControls
|
|
150
|
+
* />
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export const FormattedValue: Story = {
|
|
154
|
+
render: () => (
|
|
155
|
+
<div className="flex w-64 flex-col gap-4">
|
|
156
|
+
<NumberInput
|
|
157
|
+
name="price"
|
|
158
|
+
defaultValue={1299}
|
|
159
|
+
locale="es-AR"
|
|
160
|
+
format={{ style: "currency", currency: "ARS" }}
|
|
161
|
+
/>
|
|
162
|
+
<NumberInput
|
|
163
|
+
name="ratio"
|
|
164
|
+
defaultValue={0.75}
|
|
165
|
+
step={0.05}
|
|
166
|
+
min={0}
|
|
167
|
+
max={1}
|
|
168
|
+
format={{ style: "percent" }}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Controlá la precisión y el incremento mínimo con `step`.
|
|
176
|
+
* `largeStep` (Page Up / Page Down) multiplica el paso por defecto.
|
|
177
|
+
*/
|
|
178
|
+
export const Step: Story = {
|
|
179
|
+
args: {
|
|
180
|
+
name: "rating",
|
|
181
|
+
step: 0.5,
|
|
182
|
+
largeStep: 2,
|
|
183
|
+
min: 0,
|
|
184
|
+
max: 10,
|
|
185
|
+
defaultValue: 2.5,
|
|
186
|
+
format: { minimumFractionDigits: 1, maximumFractionDigits: 1 },
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Integrá el `NumberInput` con el componente `Field` para obtener label, descripción y errores.
|
|
192
|
+
* Cuando `Field` tiene un `error`, el borde rojo se aplica automáticamente vía el contexto de Base UI.
|
|
193
|
+
* Para usarlo sin `Field`, pasá `invalid` directamente al componente.
|
|
194
|
+
*/
|
|
195
|
+
export const FormIntegration: Story = {
|
|
196
|
+
render: () => {
|
|
197
|
+
return (
|
|
198
|
+
<div className="w-64">
|
|
199
|
+
<Field
|
|
200
|
+
label="Edad"
|
|
201
|
+
description="Ingresá tu edad en años."
|
|
202
|
+
error="El valor debe estar entre 18 y 99."
|
|
203
|
+
>
|
|
204
|
+
<NumberInput name="age" />
|
|
205
|
+
</Field>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
},
|
|
209
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { createRef } from "react";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { NumberInput } from "../../components";
|
|
5
|
+
|
|
6
|
+
describe("NumberInput component", () => {
|
|
7
|
+
it("se renderiza correctamente", () => {
|
|
8
|
+
render(<NumberInput name="cantidad" />);
|
|
9
|
+
expect(screen.getByRole("textbox")).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("acepta y muestra el valor inicial correctamente", () => {
|
|
13
|
+
render(<NumberInput name="cantidad" value={10} />);
|
|
14
|
+
const input = screen.getByRole("textbox") as HTMLInputElement;
|
|
15
|
+
expect(input.value).toBe("10");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("deshabilita el input cuando la prop disabled es true", () => {
|
|
19
|
+
render(<NumberInput name="cantidad" disabled />);
|
|
20
|
+
expect(screen.getByRole("textbox")).toBeDisabled();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("acepta la prop className en el contenedor visual", () => {
|
|
24
|
+
const TEST_CLASS = "mi-clase-personalizada";
|
|
25
|
+
render(<NumberInput name="cantidad" className={TEST_CLASS} />);
|
|
26
|
+
expect(screen.getByRole("group")).toHaveClass(TEST_CLASS);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("acepta la prop ref al input visible via inputProps", () => {
|
|
30
|
+
const ref = createRef<HTMLInputElement>();
|
|
31
|
+
render(<NumberInput name="cantidad" inputProps={{ ref }} />);
|
|
32
|
+
expect(ref.current).not.toBeNull();
|
|
33
|
+
expect(ref.current).toBeInstanceOf(HTMLInputElement);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("llama a onChange cuando el valor cambia", () => {
|
|
37
|
+
const handleChange = vi.fn();
|
|
38
|
+
render(<NumberInput name="cantidad" onChange={handleChange} />);
|
|
39
|
+
const input = screen.getByRole("textbox");
|
|
40
|
+
|
|
41
|
+
fireEvent.input(input, { target: { value: "5" } });
|
|
42
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("respeta el valor mínimo", () => {
|
|
46
|
+
render(<NumberInput name="cantidad" min={0} />);
|
|
47
|
+
const input = screen.getByRole("textbox") as HTMLInputElement;
|
|
48
|
+
|
|
49
|
+
fireEvent.input(input, { target: { value: "-5" } });
|
|
50
|
+
fireEvent.blur(input);
|
|
51
|
+
expect(input.value).toBe("0");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("respeta el valor máximo", () => {
|
|
55
|
+
render(<NumberInput name="cantidad" max={100} />);
|
|
56
|
+
const input = screen.getByRole("textbox") as HTMLInputElement;
|
|
57
|
+
|
|
58
|
+
fireEvent.input(input, { target: { value: "150" } });
|
|
59
|
+
fireEvent.blur(input);
|
|
60
|
+
expect(input.value).toBe("100");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("incrementa el valor correctamente con los botones", () => {
|
|
64
|
+
render(<NumberInput name="cantidad" defaultValue={5} />);
|
|
65
|
+
const input = screen.getByRole("textbox") as HTMLInputElement;
|
|
66
|
+
const incrementButton = screen.getByTestId("increment-trigger");
|
|
67
|
+
|
|
68
|
+
fireEvent.click(incrementButton);
|
|
69
|
+
expect(input.value).toBe("6");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("decrementa el valor correctamente con los botones", () => {
|
|
73
|
+
render(<NumberInput name="cantidad" defaultValue={5} />);
|
|
74
|
+
const input = screen.getByRole("textbox") as HTMLInputElement;
|
|
75
|
+
const decrementButton = screen.getByTestId("decrement-trigger");
|
|
76
|
+
|
|
77
|
+
fireEvent.click(decrementButton);
|
|
78
|
+
expect(input.value).toBe("4");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("oculta los botones cuando hideControls es true", () => {
|
|
82
|
+
render(<NumberInput name="cantidad" hideControls />);
|
|
83
|
+
expect(screen.queryByTestId("increment-trigger")).not.toBeInTheDocument();
|
|
84
|
+
expect(screen.queryByTestId("decrement-trigger")).not.toBeInTheDocument();
|
|
85
|
+
expect(screen.getByRole("textbox")).toBeInTheDocument();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { NumberField as NumberFieldPrimitive } from "@base-ui/react/number-field";
|
|
4
|
+
import {
|
|
5
|
+
ChevronDownIcon,
|
|
6
|
+
ChevronUpIcon,
|
|
7
|
+
MinusIcon,
|
|
8
|
+
PlusIcon,
|
|
9
|
+
} from "lucide-react";
|
|
10
|
+
import { type ComponentProps } from "react";
|
|
11
|
+
import { cn } from "../../lib";
|
|
12
|
+
|
|
13
|
+
// ── Primitives ────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export function NumberInputRoot({
|
|
16
|
+
className,
|
|
17
|
+
...props
|
|
18
|
+
}: NumberFieldPrimitive.Root.Props) {
|
|
19
|
+
return (
|
|
20
|
+
<NumberFieldPrimitive.Root
|
|
21
|
+
className={cn("w-full", className)}
|
|
22
|
+
data-slot="number-input"
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function NumberInputGroup({
|
|
29
|
+
className,
|
|
30
|
+
...props
|
|
31
|
+
}: NumberFieldPrimitive.Group.Props) {
|
|
32
|
+
return (
|
|
33
|
+
<NumberFieldPrimitive.Group
|
|
34
|
+
className={cn(
|
|
35
|
+
"flex w-full items-center overflow-hidden rounded-md border border-input bg-background text-sm transition-shadow",
|
|
36
|
+
"focus-within:border-ring",
|
|
37
|
+
"aria-invalid:border-error focus-within:aria-invalid:ring-error/20",
|
|
38
|
+
"has-aria-invalid:border-error focus-within:has-aria-invalid:ring-error/20",
|
|
39
|
+
"data-[invalid]:border-error focus-within:data-[invalid]:ring-error/20",
|
|
40
|
+
"data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:pointer-events-none",
|
|
41
|
+
"[&_svg]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
data-slot="number-input-group"
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function NumberInputInput({
|
|
51
|
+
className,
|
|
52
|
+
...props
|
|
53
|
+
}: NumberFieldPrimitive.Input.Props) {
|
|
54
|
+
return (
|
|
55
|
+
<NumberFieldPrimitive.Input
|
|
56
|
+
className={cn(
|
|
57
|
+
"min-w-0 flex-1 bg-transparent px-3 py-2 text-left tabular-nums outline-none",
|
|
58
|
+
"placeholder:text-muted-foreground",
|
|
59
|
+
className,
|
|
60
|
+
)}
|
|
61
|
+
data-slot="number-input-input"
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function NumberInputDecrement({
|
|
68
|
+
className,
|
|
69
|
+
children,
|
|
70
|
+
...props
|
|
71
|
+
}: NumberFieldPrimitive.Decrement.Props) {
|
|
72
|
+
return (
|
|
73
|
+
<NumberFieldPrimitive.Decrement
|
|
74
|
+
className={cn(
|
|
75
|
+
"flex shrink-0 cursor-pointer items-center justify-center border-r border-input px-2 py-2 text-muted-foreground transition-colors",
|
|
76
|
+
"hover:bg-accent hover:text-foreground",
|
|
77
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
78
|
+
className,
|
|
79
|
+
)}
|
|
80
|
+
data-slot="number-input-decrement"
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
83
|
+
{children ?? <MinusIcon />}
|
|
84
|
+
</NumberFieldPrimitive.Decrement>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function NumberInputIncrement({
|
|
89
|
+
className,
|
|
90
|
+
children,
|
|
91
|
+
...props
|
|
92
|
+
}: NumberFieldPrimitive.Increment.Props) {
|
|
93
|
+
return (
|
|
94
|
+
<NumberFieldPrimitive.Increment
|
|
95
|
+
className={cn(
|
|
96
|
+
"flex shrink-0 cursor-pointer items-center justify-center border-l border-input px-2 py-2 text-muted-foreground transition-colors",
|
|
97
|
+
"hover:bg-accent hover:text-foreground",
|
|
98
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
data-slot="number-input-increment"
|
|
102
|
+
{...props}
|
|
103
|
+
>
|
|
104
|
+
{children ?? <PlusIcon />}
|
|
105
|
+
</NumberFieldPrimitive.Increment>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function NumberInputScrubArea({
|
|
110
|
+
className,
|
|
111
|
+
...props
|
|
112
|
+
}: NumberFieldPrimitive.ScrubArea.Props) {
|
|
113
|
+
return (
|
|
114
|
+
<NumberFieldPrimitive.ScrubArea
|
|
115
|
+
className={cn("inline-flex cursor-ew-resize items-center", className)}
|
|
116
|
+
data-slot="number-input-scrub-area"
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function NumberInputScrubAreaCursor({
|
|
123
|
+
className,
|
|
124
|
+
...props
|
|
125
|
+
}: NumberFieldPrimitive.ScrubAreaCursor.Props) {
|
|
126
|
+
return (
|
|
127
|
+
<NumberFieldPrimitive.ScrubAreaCursor
|
|
128
|
+
className={cn("drop-shadow-[0_1px_1px_#0008] filter", className)}
|
|
129
|
+
{...props}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function NumberInputCursorIcon(props: ComponentProps<"svg">) {
|
|
135
|
+
return (
|
|
136
|
+
<svg
|
|
137
|
+
aria-hidden="true"
|
|
138
|
+
fill="black"
|
|
139
|
+
height="14"
|
|
140
|
+
stroke="white"
|
|
141
|
+
viewBox="0 0 24 14"
|
|
142
|
+
width="26"
|
|
143
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
144
|
+
{...props}
|
|
145
|
+
>
|
|
146
|
+
<path d="M19.5 5.5L6.49737 5.51844V2L1 6.9999L6.5 12L6.49737 8.5L19.5 8.5V12L25 6.9999L19.5 2V5.5Z" />
|
|
147
|
+
</svg>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Composite ─────────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
export interface NumberInputProps extends NumberFieldPrimitive.Root.Props {
|
|
154
|
+
/** Hides the increment/decrement buttons. */
|
|
155
|
+
hideControls?: boolean;
|
|
156
|
+
/** Shorthand for `onValueChange` — receives only the value, without event details. */
|
|
157
|
+
onChange?: (value: number | null) => void;
|
|
158
|
+
/** Marks the field as invalid, adding the error border to the group. */
|
|
159
|
+
invalid?: boolean;
|
|
160
|
+
/** Forwarded to the underlying input element. */
|
|
161
|
+
placeholder?: string;
|
|
162
|
+
groupProps?: NumberFieldPrimitive.Group.Props;
|
|
163
|
+
inputProps?: NumberFieldPrimitive.Input.Props;
|
|
164
|
+
decrementProps?: NumberFieldPrimitive.Decrement.Props;
|
|
165
|
+
incrementProps?: NumberFieldPrimitive.Increment.Props;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function NumberInput({
|
|
169
|
+
className,
|
|
170
|
+
hideControls = false,
|
|
171
|
+
onChange,
|
|
172
|
+
onValueChange,
|
|
173
|
+
invalid,
|
|
174
|
+
placeholder,
|
|
175
|
+
groupProps,
|
|
176
|
+
inputProps,
|
|
177
|
+
decrementProps,
|
|
178
|
+
incrementProps,
|
|
179
|
+
...props
|
|
180
|
+
}: NumberInputProps) {
|
|
181
|
+
return (
|
|
182
|
+
<NumberInputRoot
|
|
183
|
+
onValueChange={(v, d) => {
|
|
184
|
+
onChange?.(v);
|
|
185
|
+
onValueChange?.(v, d);
|
|
186
|
+
}}
|
|
187
|
+
{...props}
|
|
188
|
+
>
|
|
189
|
+
<NumberInputGroup
|
|
190
|
+
aria-invalid={invalid || undefined}
|
|
191
|
+
{...groupProps}
|
|
192
|
+
className={cn(className, groupProps?.className)}
|
|
193
|
+
>
|
|
194
|
+
<NumberInputInput
|
|
195
|
+
{...(invalid ? { "aria-invalid": true } : {})}
|
|
196
|
+
placeholder={placeholder}
|
|
197
|
+
{...inputProps}
|
|
198
|
+
/>
|
|
199
|
+
{!hideControls && (
|
|
200
|
+
<div className="flex flex-col self-stretch divide-y divide-input border-l border-input">
|
|
201
|
+
<NumberInputIncrement
|
|
202
|
+
data-testid="increment-trigger"
|
|
203
|
+
{...incrementProps}
|
|
204
|
+
className={cn(
|
|
205
|
+
"flex-1 border-l-0 py-0 [&_svg]:size-3",
|
|
206
|
+
incrementProps?.className,
|
|
207
|
+
)}
|
|
208
|
+
>
|
|
209
|
+
{incrementProps?.children ?? <ChevronUpIcon />}
|
|
210
|
+
</NumberInputIncrement>
|
|
211
|
+
<NumberInputDecrement
|
|
212
|
+
data-testid="decrement-trigger"
|
|
213
|
+
{...decrementProps}
|
|
214
|
+
className={cn(
|
|
215
|
+
"flex-1 border-r-0 py-0 [&_svg]:size-3",
|
|
216
|
+
decrementProps?.className,
|
|
217
|
+
)}
|
|
218
|
+
>
|
|
219
|
+
{decrementProps?.children ?? <ChevronDownIcon />}
|
|
220
|
+
</NumberInputDecrement>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
</NumberInputGroup>
|
|
224
|
+
</NumberInputRoot>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Primitive escape hatch ────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
export { NumberFieldPrimitive as NumberInputPrimitive };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ButtonHTMLAttributes } from "react";
|
|
2
|
+
import { DOTS } from "../../../hooks";
|
|
3
|
+
import { cn } from "../../../lib";
|
|
4
|
+
|
|
5
|
+
interface PaginationOptionProps
|
|
6
|
+
extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
7
|
+
isActive?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const PaginationOption = ({
|
|
11
|
+
isActive = false,
|
|
12
|
+
...props
|
|
13
|
+
}: PaginationOptionProps) => (
|
|
14
|
+
<button
|
|
15
|
+
{...props}
|
|
16
|
+
data-slot="pagination-option"
|
|
17
|
+
data-active={isActive}
|
|
18
|
+
data-dots={props.children === DOTS}
|
|
19
|
+
className={cn(
|
|
20
|
+
"transition-colors border py-2 px-4 w-[50px] h-full data-[active=false]:hover:bg-accent",
|
|
21
|
+
"data-[active=true]:bg-primary data-[active=true]:text-primary-foreground data-[active=true]:border-transparent data-[active=true]:hover:bg-primary/90",
|
|
22
|
+
props.className,
|
|
23
|
+
)}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export { PaginationOption, type PaginationOptionProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./pagination";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { ComponentProps } from "react";
|
|
3
|
+
import { action } from "storybook/actions";
|
|
4
|
+
import { Pagination } from "../../components";
|
|
5
|
+
import { cn } from "../../lib";
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Pagination> = {
|
|
8
|
+
title: "Data display/Pagination",
|
|
9
|
+
component: Pagination,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
args: {
|
|
12
|
+
siblingCount: 1,
|
|
13
|
+
totalItems: 100,
|
|
14
|
+
pageSize: 10,
|
|
15
|
+
initialCurrentPage: 1,
|
|
16
|
+
onChange: action("onChange"),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
export const Default: Story = {};
|
|
24
|
+
|
|
25
|
+
export const DotsVariants: Story = {
|
|
26
|
+
argTypes: {
|
|
27
|
+
totalItems: { control: false },
|
|
28
|
+
pageSize: { control: false },
|
|
29
|
+
initialCurrentPage: { control: false },
|
|
30
|
+
},
|
|
31
|
+
render: (args) => {
|
|
32
|
+
return (
|
|
33
|
+
<div className="space-y-2">
|
|
34
|
+
<h2>No hay suficientes elementos para mostrar puntos</h2>
|
|
35
|
+
<Pagination
|
|
36
|
+
{...(args as ComponentProps<typeof Pagination>)}
|
|
37
|
+
pageSize={20}
|
|
38
|
+
/>
|
|
39
|
+
<h2>Puntos a la derecha</h2>
|
|
40
|
+
<Pagination
|
|
41
|
+
{...(args as ComponentProps<typeof Pagination>)}
|
|
42
|
+
initialCurrentPage={1}
|
|
43
|
+
/>
|
|
44
|
+
<h2>Puntos de ambos lados</h2>
|
|
45
|
+
<Pagination
|
|
46
|
+
{...(args as ComponentProps<typeof Pagination>)}
|
|
47
|
+
initialCurrentPage={5}
|
|
48
|
+
/>
|
|
49
|
+
<h2>Puntos a la izquierda</h2>
|
|
50
|
+
<Pagination
|
|
51
|
+
{...(args as ComponentProps<typeof Pagination>)}
|
|
52
|
+
initialCurrentPage={10}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Se exponen los siguientes atributos:
|
|
61
|
+
*
|
|
62
|
+
* | Atributo | Descripción |
|
|
63
|
+
* | --- | --- |
|
|
64
|
+
* | `data-active` | si el elemento es el activo |
|
|
65
|
+
* | `data-dots` | si el elemento son puntos|
|
|
66
|
+
**/
|
|
67
|
+
export const Custom: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
className: "gap-x-4",
|
|
70
|
+
optionProps: {
|
|
71
|
+
className: cn(
|
|
72
|
+
"p-0 w-9 h-9 rounded-full flex justify-center items-center",
|
|
73
|
+
"data-[active=true]:bg-error data-[active=true]:text-error-foreground data-[active=true]:hover:bg-error/90",
|
|
74
|
+
"data-[dots=true]:border-none data-[dots=true]:bg-transparent",
|
|
75
|
+
"[&:nth-child(-n+2)]:border-none [&:nth-child(-n+2)]:bg-background",
|
|
76
|
+
"[&:nth-last-child(-n+2)]:border-none [&:nth-last-child(-n+2)]:bg-background",
|
|
77
|
+
),
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|