@boxcustodia/library 2.0.0-alpha.11 → 2.0.0-alpha.13
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 +38 -38
- package/dist/index.css +1 -1
- package/dist/index.d.ts +28 -29
- package/dist/index.es.js +6804 -6792
- 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 +451 -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 +898 -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 +139 -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 +232 -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 +75 -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,175 @@
|
|
|
1
|
+
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
2
|
+
import { HTMLProps, ReactNode, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { cn } from "../../lib";
|
|
4
|
+
import { TabsContextProvider, useTabsContext } from "./context/tabs-context";
|
|
5
|
+
|
|
6
|
+
interface TabsExtendedProps extends TabsProps {
|
|
7
|
+
listClassName: TabListProps["className"];
|
|
8
|
+
listProps: TabListProps;
|
|
9
|
+
triggerClassName: TabTriggerProps["className"];
|
|
10
|
+
triggerProps: TabTriggerProps;
|
|
11
|
+
contentClassName: TabContentProps["className"];
|
|
12
|
+
contentProps: TabContentProps;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface TabsProps
|
|
16
|
+
extends Omit<
|
|
17
|
+
HTMLProps<HTMLDivElement>,
|
|
18
|
+
"value" | "defaultValue" | "onChange"
|
|
19
|
+
> {
|
|
20
|
+
value?: string;
|
|
21
|
+
onChange?: (tab: string) => void;
|
|
22
|
+
defaultValue?: string;
|
|
23
|
+
variant?: "default" | "background";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Tabs = ({
|
|
27
|
+
children,
|
|
28
|
+
value: prop,
|
|
29
|
+
onChange,
|
|
30
|
+
defaultValue,
|
|
31
|
+
variant = "default",
|
|
32
|
+
...props
|
|
33
|
+
}: TabsProps) => {
|
|
34
|
+
const [activeTab = "", setActiveTab] = useControllableState<string>({
|
|
35
|
+
prop,
|
|
36
|
+
onChange,
|
|
37
|
+
defaultProp: defaultValue as string,
|
|
38
|
+
});
|
|
39
|
+
const [targetRef, setTargetRef] = useState<HTMLButtonElement | null>(null);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<TabsContextProvider
|
|
43
|
+
value={{ activeTab, setActiveTab, setTargetRef, targetRef, variant }}
|
|
44
|
+
>
|
|
45
|
+
<div data-slot="tabs" {...props}>
|
|
46
|
+
{children}
|
|
47
|
+
</div>
|
|
48
|
+
</TabsContextProvider>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
interface TabListProps {
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
className?: string;
|
|
55
|
+
indicatorClassName?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const TabList = ({ children, className, indicatorClassName }: TabListProps) => {
|
|
59
|
+
const { targetRef } = useTabsContext();
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div data-slot="tabs-list" className={cn("relative mb-4", className)}>
|
|
63
|
+
<div data-slot="tabs-content" className="flex">
|
|
64
|
+
{children}
|
|
65
|
+
</div>
|
|
66
|
+
<TabsIndicator
|
|
67
|
+
targetRef={targetRef}
|
|
68
|
+
indicatorClassName={indicatorClassName}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
interface TabTriggerProps
|
|
75
|
+
extends Omit<HTMLProps<HTMLButtonElement>, "onClick" | "type"> {
|
|
76
|
+
value: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const TabTrigger = ({ value, className, ...props }: TabTriggerProps) => {
|
|
80
|
+
const { activeTab, setActiveTab, setTargetRef, variant } = useTabsContext();
|
|
81
|
+
const isActive = activeTab === value;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<button
|
|
85
|
+
data-slot="tabs-trigger"
|
|
86
|
+
{...props}
|
|
87
|
+
type="button"
|
|
88
|
+
ref={(element) => {
|
|
89
|
+
if (isActive) setTargetRef(element);
|
|
90
|
+
}}
|
|
91
|
+
onClick={() => setActiveTab(value)}
|
|
92
|
+
data-tab={value}
|
|
93
|
+
data-selected={isActive}
|
|
94
|
+
aria-selected={isActive}
|
|
95
|
+
className={cn(
|
|
96
|
+
"px-2 py-2 cursor-pointer w-full min-w-[100px] text-center rounded-t font-semibold select-none",
|
|
97
|
+
variant === "background"
|
|
98
|
+
? "hover:bg-transparent aria-selected:text-primary-foreground rounded"
|
|
99
|
+
: "hover:bg-accent hover:text-accent-foreground",
|
|
100
|
+
className,
|
|
101
|
+
)}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
interface TabContentProps extends HTMLProps<HTMLDivElement> {
|
|
107
|
+
value: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const TabContent = ({ value, ...props }: TabContentProps) => {
|
|
111
|
+
const { activeTab } = useTabsContext();
|
|
112
|
+
const isActiveTab = activeTab === value;
|
|
113
|
+
if (!isActiveTab) return null;
|
|
114
|
+
return <div data-slot="tabs-content" {...props} />;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
interface TabsIndicatorProps {
|
|
118
|
+
targetRef: HTMLButtonElement | null;
|
|
119
|
+
indicatorClassName?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const TabsIndicator = ({
|
|
123
|
+
targetRef,
|
|
124
|
+
indicatorClassName,
|
|
125
|
+
}: TabsIndicatorProps) => {
|
|
126
|
+
const indicatorRef = useRef<HTMLDivElement>(null);
|
|
127
|
+
const indicator = indicatorRef?.current;
|
|
128
|
+
const targetResizeObserver = useRef<ResizeObserver>(null);
|
|
129
|
+
const { variant } = useTabsContext();
|
|
130
|
+
|
|
131
|
+
const updatePosition = (): void => {
|
|
132
|
+
if (!targetRef || !indicator) return;
|
|
133
|
+
|
|
134
|
+
const leftDistance = targetRef.offsetLeft;
|
|
135
|
+
const width = targetRef.offsetWidth;
|
|
136
|
+
|
|
137
|
+
indicator.style.width = width + "px";
|
|
138
|
+
indicator.style.left = leftDistance + "px";
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (targetRef) {
|
|
143
|
+
updatePosition();
|
|
144
|
+
targetResizeObserver.current = new ResizeObserver(updatePosition);
|
|
145
|
+
targetResizeObserver.current.observe(targetRef);
|
|
146
|
+
|
|
147
|
+
return () => {
|
|
148
|
+
targetResizeObserver.current?.disconnect();
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}, [targetRef]);
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div
|
|
155
|
+
data-slot="tabs-indicator"
|
|
156
|
+
ref={indicatorRef}
|
|
157
|
+
className={cn(
|
|
158
|
+
"transition-all duration-300 absolute rounded bg-primary",
|
|
159
|
+
variant === "background"
|
|
160
|
+
? "inset-0 -z-10 h-full top-0"
|
|
161
|
+
: "top-full h-[3px]",
|
|
162
|
+
indicatorClassName,
|
|
163
|
+
)}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export { TabContent, TabList, Tabs, TabTrigger };
|
|
169
|
+
export type {
|
|
170
|
+
TabContentProps,
|
|
171
|
+
TabListProps,
|
|
172
|
+
TabsExtendedProps,
|
|
173
|
+
TabsProps,
|
|
174
|
+
TabTriggerProps,
|
|
175
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Tag } from "../../components";
|
|
2
|
+
import { createToastManager, ToastProvider } from "../toast";
|
|
3
|
+
|
|
4
|
+
const toastManager = createToastManager();
|
|
5
|
+
|
|
6
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
7
|
+
import { Settings, StarIcon, User } from "lucide-react";
|
|
8
|
+
import { action } from "storybook/actions";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* El componente Tag es una herramienta versátil para etiquetar, categorizar y organizar elementos con etiquetas visuales.
|
|
12
|
+
*/
|
|
13
|
+
const meta: Meta<typeof Tag> = {
|
|
14
|
+
title: "Data display/Tag",
|
|
15
|
+
component: Tag,
|
|
16
|
+
args: {
|
|
17
|
+
children: "Lorem ipsum",
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<typeof Tag>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Variante principal del Tag.
|
|
26
|
+
* Ideal para destacar información importante o etiquetas primarias.
|
|
27
|
+
*/
|
|
28
|
+
export const Primary: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
children: "Tag básico",
|
|
31
|
+
variant: "primary",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Variante secundaria con un énfasis visual más sutil.
|
|
37
|
+
* Perfecta para información complementaria o categorías secundarias.
|
|
38
|
+
*/
|
|
39
|
+
export const Secondary: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
variant: "secondary",
|
|
42
|
+
children: "Tag secundario",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Variante de error para resaltar estados problemáticos o alertas.
|
|
48
|
+
* Usar con moderación y solo cuando sea necesario indicar un error o advertencia.
|
|
49
|
+
*/
|
|
50
|
+
export const ErrorVariant: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
variant: "error",
|
|
53
|
+
children: "Tag de error",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Variante outline con borde y fondo transparente.
|
|
59
|
+
* Útil para interfaces minimalistas o cuando se necesita un contraste menor.
|
|
60
|
+
*/
|
|
61
|
+
export const Outline: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
variant: "outline",
|
|
64
|
+
children: "Tag outline",
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Tipo redondo o cuadrado .
|
|
70
|
+
*/
|
|
71
|
+
export const Square: Story = {
|
|
72
|
+
render: () => (
|
|
73
|
+
<div className="flex w-full gap-2">
|
|
74
|
+
<Tag rounded={"default"} color="blue">
|
|
75
|
+
Tag rounded default
|
|
76
|
+
</Tag>
|
|
77
|
+
|
|
78
|
+
<Tag rounded={"full"} color="magenta">
|
|
79
|
+
Tag rounded full
|
|
80
|
+
</Tag>
|
|
81
|
+
<Tag rounded={"square"} color="blue">
|
|
82
|
+
Tag rounded square
|
|
83
|
+
</Tag>
|
|
84
|
+
</div>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Los estilos del Tag.
|
|
90
|
+
*/
|
|
91
|
+
export const Styles: Story = {
|
|
92
|
+
render: () => (
|
|
93
|
+
<div className="flex flex-col items-start justify-start gap-2">
|
|
94
|
+
<Tag variant={"outline"} color="var(--color-primary)">
|
|
95
|
+
Bordeado default
|
|
96
|
+
</Tag>
|
|
97
|
+
<Tag rounded={"full"} variant={"outline"} color="red">
|
|
98
|
+
Bordeado redondo
|
|
99
|
+
</Tag>
|
|
100
|
+
<Tag rounded={"square"}>Solido cuadrado</Tag>
|
|
101
|
+
<Tag rounded={"full"} variant={"error"}>
|
|
102
|
+
Solido redondo
|
|
103
|
+
</Tag>
|
|
104
|
+
<Tag rounded={"full"} color="blue" icon={<User />}>
|
|
105
|
+
Solido redondo con icono bordeado
|
|
106
|
+
</Tag>
|
|
107
|
+
<Tag
|
|
108
|
+
rounded={"full"}
|
|
109
|
+
color="red"
|
|
110
|
+
variant={"borderless"}
|
|
111
|
+
icon={<Settings />}
|
|
112
|
+
>
|
|
113
|
+
Coloreado sin borde redondo con icono
|
|
114
|
+
</Tag>
|
|
115
|
+
<Tag
|
|
116
|
+
color="magenta"
|
|
117
|
+
variant={"borderless"}
|
|
118
|
+
closable
|
|
119
|
+
onClose={() => {
|
|
120
|
+
action("onClose")();
|
|
121
|
+
toastManager.add({ variant: "success", description: "Tag cerrado" });
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
Coloreado sin borde redondo y cerrable
|
|
125
|
+
</Tag>
|
|
126
|
+
<Tag color="magenta" variant={"borderless"}>
|
|
127
|
+
Coloreado sin borde
|
|
128
|
+
</Tag>
|
|
129
|
+
</div>
|
|
130
|
+
),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Tags con iconos para mejorar la comprensión visual.
|
|
135
|
+
* Los iconos deben ser relevantes y mejorar la comunicación del tag.
|
|
136
|
+
*/
|
|
137
|
+
export const WithIcon: Story = {
|
|
138
|
+
args: {
|
|
139
|
+
icon: <StarIcon className="w-4 h-4" />,
|
|
140
|
+
children: "Tag con icono",
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Variante cerrable que permite al usuario eliminar el tag.
|
|
146
|
+
* Incluye callback personalizable y feedback visual mediante toast.
|
|
147
|
+
*
|
|
148
|
+
* Al pasar la propiedad `closable` a true, se debe pasar la propiedad `onClose` con una función que se ejecutará al cerrar el tag.
|
|
149
|
+
*
|
|
150
|
+
* ```tsx
|
|
151
|
+
* <Tag closable onClose={() => {}}>Tag cerrable</Tag>
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export const Closable: Story = {
|
|
155
|
+
decorators: [
|
|
156
|
+
(Story) => (
|
|
157
|
+
<ToastProvider toastManager={toastManager}>
|
|
158
|
+
<Story />
|
|
159
|
+
</ToastProvider>
|
|
160
|
+
),
|
|
161
|
+
],
|
|
162
|
+
args: {
|
|
163
|
+
children: "Tag cerrable",
|
|
164
|
+
closable: true,
|
|
165
|
+
onClose: () => {
|
|
166
|
+
action("onClose")();
|
|
167
|
+
toastManager.add({ variant: "success", description: "Tag cerrado" });
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { Tag } from "../../components";
|
|
4
|
+
|
|
5
|
+
describe("Badge component", () => {
|
|
6
|
+
it("should render children", () => {
|
|
7
|
+
render(<Tag>Lorem</Tag>);
|
|
8
|
+
const component = screen.getByText("Lorem");
|
|
9
|
+
|
|
10
|
+
expect(component).toBeInTheDocument();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should accept custom styles", () => {
|
|
14
|
+
render(<Tag className="custom-class">Lorem</Tag>);
|
|
15
|
+
const component = screen.getByText("Lorem");
|
|
16
|
+
expect(component).toHaveClass("custom-class");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { X } from "lucide-react";
|
|
2
|
+
import { cn } from "../../lib";
|
|
3
|
+
import { TagVariant, tagVariants } from "./tag.variants";
|
|
4
|
+
|
|
5
|
+
type VariantProps = {
|
|
6
|
+
color?: never;
|
|
7
|
+
variant?: TagVariant["variant"];
|
|
8
|
+
};
|
|
9
|
+
type ColorProps = {
|
|
10
|
+
color: string;
|
|
11
|
+
variant?: "outline" | "borderless";
|
|
12
|
+
};
|
|
13
|
+
type StyleProps = ColorProps | VariantProps;
|
|
14
|
+
|
|
15
|
+
type BaseProps = {
|
|
16
|
+
icon?: React.ReactNode;
|
|
17
|
+
rounded?: TagVariant["rounded"];
|
|
18
|
+
} & StyleProps &
|
|
19
|
+
React.HTMLAttributes<HTMLDivElement>;
|
|
20
|
+
|
|
21
|
+
type ClosableProps = BaseProps & {
|
|
22
|
+
closable: true;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type NonClosableProps = BaseProps & {
|
|
27
|
+
closable?: false;
|
|
28
|
+
onClose?: never;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type Props = ClosableProps | NonClosableProps;
|
|
32
|
+
|
|
33
|
+
export const Tag = ({
|
|
34
|
+
className,
|
|
35
|
+
color,
|
|
36
|
+
variant,
|
|
37
|
+
rounded,
|
|
38
|
+
icon,
|
|
39
|
+
closable,
|
|
40
|
+
onClose,
|
|
41
|
+
children,
|
|
42
|
+
...props
|
|
43
|
+
}: Props) => {
|
|
44
|
+
const withTextColored =
|
|
45
|
+
!!color && (variant === "outline" || variant === "borderless");
|
|
46
|
+
const withBgColored = !!color && !variant;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
data-slot="tag"
|
|
51
|
+
className={cn(
|
|
52
|
+
tagVariants({ variant, rounded }),
|
|
53
|
+
withTextColored && `bg-white`,
|
|
54
|
+
withBgColored && ` text-white`,
|
|
55
|
+
!!icon && "pl-1.5",
|
|
56
|
+
!!closable && "pr-1.5",
|
|
57
|
+
className,
|
|
58
|
+
)}
|
|
59
|
+
style={{
|
|
60
|
+
...(withTextColored && {
|
|
61
|
+
color: color,
|
|
62
|
+
borderColor: color,
|
|
63
|
+
}),
|
|
64
|
+
...(withBgColored && {
|
|
65
|
+
borderColor: color,
|
|
66
|
+
backgroundColor: color,
|
|
67
|
+
}),
|
|
68
|
+
}}
|
|
69
|
+
{...props}
|
|
70
|
+
>
|
|
71
|
+
{icon}
|
|
72
|
+
{children}
|
|
73
|
+
{closable && (
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
data-slot="tag-close"
|
|
77
|
+
className="z-10 h-4 w-4 cursor-pointer"
|
|
78
|
+
onClick={(e) => {
|
|
79
|
+
e.stopPropagation();
|
|
80
|
+
onClose();
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<X data-slot="tag-close-icon" />
|
|
84
|
+
</button>
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
<span
|
|
88
|
+
data-slot="tag-overlay"
|
|
89
|
+
className="w-full h-full absolute top-0 left-0"
|
|
90
|
+
style={{
|
|
91
|
+
...(withTextColored && {
|
|
92
|
+
opacity: 0.13,
|
|
93
|
+
backgroundColor: color,
|
|
94
|
+
}),
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cva, VariantProps } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
export const tagVariants = cva(
|
|
4
|
+
"inline-flex items-center relative overflow-hidden bg-gray-100 gap-1 px-2.5 py-0.5 text-xs font-normal transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
rounded: {
|
|
8
|
+
default: "rounded-(--radius-tag)",
|
|
9
|
+
full: "rounded-full",
|
|
10
|
+
square: "rounded-sm",
|
|
11
|
+
},
|
|
12
|
+
variant: {
|
|
13
|
+
primary:
|
|
14
|
+
"bg-primary border-primary text-primary-foreground hover:bg-primary/80",
|
|
15
|
+
secondary:
|
|
16
|
+
"bg-secondary border-secondary !text-secondary-foreground hover:bg-secondary/80",
|
|
17
|
+
error: "bg-error border-error text-error-foreground hover:bg-error/80",
|
|
18
|
+
success:
|
|
19
|
+
"bg-success border-success text-success-foreground hover:bg-success/80",
|
|
20
|
+
outline: "border border-foreground text-foreground",
|
|
21
|
+
borderless: "text-foreground",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
rounded: "default",
|
|
26
|
+
variant: "primary",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
export type TagVariant = VariantProps<typeof tagVariants>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./textarea";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Textarea, TextareaControl, TextareaField } from "../../components";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Multi-line text input built on Base UI's Field.Control.
|
|
7
|
+
* Shares the same border, focus, error, and disabled states as `Input`.
|
|
8
|
+
* Error state is driven by `aria-invalid` — pass it directly or wire via a `Field` context.
|
|
9
|
+
* Use `controlProps` to pass extra props to the outer wrapper `span`.
|
|
10
|
+
*
|
|
11
|
+
* `onChange` receives `(value: string, event: ChangeEvent<HTMLTextAreaElement>)` — not the
|
|
12
|
+
* native event directly. This matches the `Input` component API.
|
|
13
|
+
*/
|
|
14
|
+
const meta: Meta<typeof Textarea> = {
|
|
15
|
+
title: "Components/Textarea",
|
|
16
|
+
component: Textarea,
|
|
17
|
+
args: {
|
|
18
|
+
placeholder: "Type something...",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof Textarea>;
|
|
24
|
+
|
|
25
|
+
export const Default: Story = {};
|
|
26
|
+
|
|
27
|
+
export const Disabled: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
disabled: true,
|
|
30
|
+
defaultValue: "This field is disabled",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const ReadOnly: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
readOnly: true,
|
|
37
|
+
defaultValue: "This field is read-only",
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Error state is triggered by `aria-invalid`. In a full form, the `Field` component
|
|
43
|
+
* sets this automatically based on validation. You can also pass it directly.
|
|
44
|
+
*/
|
|
45
|
+
export const Error: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
"aria-invalid": true,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `onChange` fires with `(value: string, event)` — the extracted string value comes first,
|
|
53
|
+
* matching the `Input` component signature. Use `value` + `onChange` for controlled usage;
|
|
54
|
+
* `defaultValue` for uncontrolled.
|
|
55
|
+
*/
|
|
56
|
+
export const Controlled: Story = {
|
|
57
|
+
render: () => {
|
|
58
|
+
const [value, setValue] = useState<string>("");
|
|
59
|
+
return <Textarea value={value} onChange={setValue} />;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Use `TextareaControl` and `TextareaField` directly when you need to inject
|
|
65
|
+
* elements inside the wrapper (e.g., a character counter overlay).
|
|
66
|
+
*/
|
|
67
|
+
export const Primitive: Story = {
|
|
68
|
+
render: (args) => (
|
|
69
|
+
<TextareaControl>
|
|
70
|
+
<TextareaField {...args} />
|
|
71
|
+
</TextareaControl>
|
|
72
|
+
),
|
|
73
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Field as FieldPrimitive } from "@base-ui/react/field";
|
|
4
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
5
|
+
import {
|
|
6
|
+
type ChangeEvent,
|
|
7
|
+
type ComponentProps,
|
|
8
|
+
type ReactElement,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { cn } from "../../lib";
|
|
11
|
+
|
|
12
|
+
// ─── Primitives ───────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export function TextareaControl({
|
|
15
|
+
className,
|
|
16
|
+
unstyled = false,
|
|
17
|
+
children,
|
|
18
|
+
...props
|
|
19
|
+
}: ComponentProps<"span"> & { unstyled?: boolean }): ReactElement {
|
|
20
|
+
return (
|
|
21
|
+
<span
|
|
22
|
+
className={
|
|
23
|
+
cn(
|
|
24
|
+
!unstyled &&
|
|
25
|
+
"relative inline-flex w-full rounded-md border border-input bg-background text-sm transition-shadow has-focus-visible:border-ring has-aria-invalid:border-error has-focus-visible:has-aria-invalid:ring-error/20 has-disabled:cursor-not-allowed has-disabled:opacity-50",
|
|
26
|
+
className,
|
|
27
|
+
) || undefined
|
|
28
|
+
}
|
|
29
|
+
data-slot="textarea-control"
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</span>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type TextareaFieldProps = Omit<
|
|
38
|
+
ComponentProps<"textarea">,
|
|
39
|
+
"onChange"
|
|
40
|
+
> & {
|
|
41
|
+
onChange?: (value: string, event: ChangeEvent<HTMLTextAreaElement>) => void;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function TextareaField({
|
|
45
|
+
className,
|
|
46
|
+
ref,
|
|
47
|
+
onChange,
|
|
48
|
+
...props
|
|
49
|
+
}: TextareaFieldProps): ReactElement {
|
|
50
|
+
const handleChange = onChange
|
|
51
|
+
? (event: ChangeEvent<HTMLTextAreaElement>) =>
|
|
52
|
+
onChange(event.target.value, event)
|
|
53
|
+
: undefined;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<FieldPrimitive.Control
|
|
57
|
+
ref={ref}
|
|
58
|
+
value={props.value}
|
|
59
|
+
defaultValue={props.defaultValue}
|
|
60
|
+
disabled={props.disabled}
|
|
61
|
+
id={props.id}
|
|
62
|
+
name={props.name}
|
|
63
|
+
render={(defaultProps: ComponentProps<"textarea">) => (
|
|
64
|
+
<textarea
|
|
65
|
+
className={cn(
|
|
66
|
+
"field-sizing-content min-h-17.5 w-full rounded-[inherit] px-[calc(--spacing(3)-1px)] py-[calc(--spacing(1.5)-1px)] outline-none placeholder:text-muted-foreground max-sm:min-h-20.5",
|
|
67
|
+
className,
|
|
68
|
+
)}
|
|
69
|
+
data-slot="textarea"
|
|
70
|
+
{...mergeProps(defaultProps, props)}
|
|
71
|
+
onChange={handleChange}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Composite ────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
export type TextareaProps = TextareaFieldProps & {
|
|
81
|
+
unstyled?: boolean;
|
|
82
|
+
controlProps?: ComponentProps<typeof TextareaControl>;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export function Textarea({
|
|
86
|
+
className,
|
|
87
|
+
unstyled = false,
|
|
88
|
+
ref,
|
|
89
|
+
controlProps,
|
|
90
|
+
...props
|
|
91
|
+
}: TextareaProps): ReactElement {
|
|
92
|
+
return (
|
|
93
|
+
<TextareaControl
|
|
94
|
+
unstyled={unstyled}
|
|
95
|
+
className={className}
|
|
96
|
+
{...controlProps}
|
|
97
|
+
>
|
|
98
|
+
<TextareaField ref={ref} {...props} />
|
|
99
|
+
</TextareaControl>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
export { FieldPrimitive };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./timeline";
|