diff --git a/.changeset/wise-drinks-throw.md b/.changeset/wise-drinks-throw.md new file mode 100644 index 00000000..aa2a98f3 --- /dev/null +++ b/.changeset/wise-drinks-throw.md @@ -0,0 +1,6 @@ +--- +"reshaped": patch +"@reshaped/utilities": patch +--- + +lockScroll: moved to @reshaped/utilities and re-exported from reshaped diff --git a/packages/reshaped/src/components/Accordion/AccordionControlled.tsx b/packages/reshaped/src/components/Accordion/AccordionControlled.tsx index 64c948aa..e126c8c7 100644 --- a/packages/reshaped/src/components/Accordion/AccordionControlled.tsx +++ b/packages/reshaped/src/components/Accordion/AccordionControlled.tsx @@ -1,10 +1,10 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import useElementId from "hooks/useElementId"; import useHandlerRef from "hooks/useHandlerRef"; -import { classNames } from "@reshaped/utilities"; import AccordionContext from "./Accordion.context"; import * as T from "./Accordion.types"; diff --git a/packages/reshaped/src/components/Accordion/AccordionTrigger.tsx b/packages/reshaped/src/components/Accordion/AccordionTrigger.tsx index 9323c9e3..da8a1eb9 100644 --- a/packages/reshaped/src/components/Accordion/AccordionTrigger.tsx +++ b/packages/reshaped/src/components/Accordion/AccordionTrigger.tsx @@ -1,12 +1,12 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import Actionable from "components/Actionable"; import Icon from "components/Icon"; import View from "components/View"; import IconChevronDown from "icons/ChevronDown"; -import { classNames } from "@reshaped/utilities"; import AccordionContext from "./Accordion.context"; import s from "./Accordion.module.css"; diff --git a/packages/reshaped/src/components/Badge/Badge.tsx b/packages/reshaped/src/components/Badge/Badge.tsx index cccf0eb7..0a047d50 100644 --- a/packages/reshaped/src/components/Badge/Badge.tsx +++ b/packages/reshaped/src/components/Badge/Badge.tsx @@ -1,10 +1,10 @@ +import { classNames } from "@reshaped/utilities"; import { forwardRef } from "react"; import Actionable, { type ActionableProps, type ActionableRef } from "components/Actionable"; import Icon from "components/Icon"; import Text from "components/Text"; import IconClose from "icons/Close"; -import { classNames } from "@reshaped/utilities"; import s from "./Badge.module.css"; diff --git a/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.tsx b/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.tsx index 67fa91cb..7747a7f0 100644 --- a/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/reshaped/src/components/Breadcrumbs/Breadcrumbs.tsx @@ -1,5 +1,6 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import Button from "components/Button"; @@ -8,7 +9,6 @@ import Text from "components/Text"; import View from "components/View"; import IconChevronRight from "icons/ChevronRight"; import IconDotsHorizontal from "icons/DotsHorizontal"; -import { classNames } from "@reshaped/utilities"; import * as T from "./Breadcrumbs.types"; diff --git a/packages/reshaped/src/components/Calendar/CalendarDate.tsx b/packages/reshaped/src/components/Calendar/CalendarDate.tsx index 829aebbe..09cc2226 100644 --- a/packages/reshaped/src/components/Calendar/CalendarDate.tsx +++ b/packages/reshaped/src/components/Calendar/CalendarDate.tsx @@ -1,8 +1,9 @@ "use client"; -import Actionable from "components/Actionable"; import { classNames } from "@reshaped/utilities"; +import Actionable from "components/Actionable"; + import s from "./Calendar.module.css"; import { getLocalISODate } from "./Calendar.utils"; diff --git a/packages/reshaped/src/components/Card/Card.tsx b/packages/reshaped/src/components/Card/Card.tsx index cf2d6637..3710cbaf 100644 --- a/packages/reshaped/src/components/Card/Card.tsx +++ b/packages/reshaped/src/components/Card/Card.tsx @@ -1,8 +1,8 @@ +import { classNames } from "@reshaped/utilities"; import React, { forwardRef } from "react"; import Actionable from "components/Actionable"; import { resolveMixin } from "styles/mixin"; -import { classNames } from "@reshaped/utilities"; import s from "./Card.module.css"; diff --git a/packages/reshaped/src/components/Carousel/CarouselControl.tsx b/packages/reshaped/src/components/Carousel/CarouselControl.tsx index e8d58fab..d4c75336 100644 --- a/packages/reshaped/src/components/Carousel/CarouselControl.tsx +++ b/packages/reshaped/src/components/Carousel/CarouselControl.tsx @@ -1,12 +1,12 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import { forwardRef, useState } from "react"; import Button from "components/Button"; import useIsomorphicLayoutEffect from "hooks/useIsomorphicLayoutEffect"; import IconChevronLeft from "icons/ChevronLeft"; import IconChevronRight from "icons/ChevronRight"; -import { classNames } from "@reshaped/utilities"; import s from "./Carousel.module.css"; import * as T from "./Carousel.types"; diff --git a/packages/reshaped/src/components/Container/Container.tsx b/packages/reshaped/src/components/Container/Container.tsx index e9c8eb0e..177b9c06 100644 --- a/packages/reshaped/src/components/Container/Container.tsx +++ b/packages/reshaped/src/components/Container/Container.tsx @@ -1,6 +1,7 @@ -import View from "components/View"; import { classNames } from "@reshaped/utilities"; +import View from "components/View"; + import s from "./Container.module.css"; import type * as T from "./Container.types"; diff --git a/packages/reshaped/src/components/Dismissible/Dismissible.tsx b/packages/reshaped/src/components/Dismissible/Dismissible.tsx index 31fc4054..14d99e75 100644 --- a/packages/reshaped/src/components/Dismissible/Dismissible.tsx +++ b/packages/reshaped/src/components/Dismissible/Dismissible.tsx @@ -1,8 +1,9 @@ "use client"; +import { classNames } from "@reshaped/utilities"; + import Button from "components/Button"; import IconClose from "icons/Close"; -import { classNames } from "@reshaped/utilities"; import s from "./Dismissible.module.css"; diff --git a/packages/reshaped/src/components/DropdownMenu/DropdownMenu.tsx b/packages/reshaped/src/components/DropdownMenu/DropdownMenu.tsx index dab434db..91467faa 100644 --- a/packages/reshaped/src/components/DropdownMenu/DropdownMenu.tsx +++ b/packages/reshaped/src/components/DropdownMenu/DropdownMenu.tsx @@ -1,5 +1,6 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import { useFlyoutContext } from "components/Flyout"; @@ -10,7 +11,6 @@ import * as keys from "constants/keys"; import useHotkeys from "hooks/useHotkeys"; import useRTL from "hooks/useRTL"; import IconChevronRight from "icons/ChevronRight"; -import { classNames } from "@reshaped/utilities"; import s from "./DropdownMenu.module.css"; diff --git a/packages/reshaped/src/components/FileUpload/FileUpload.tsx b/packages/reshaped/src/components/FileUpload/FileUpload.tsx index 7e6e677f..a4992a8c 100644 --- a/packages/reshaped/src/components/FileUpload/FileUpload.tsx +++ b/packages/reshaped/src/components/FileUpload/FileUpload.tsx @@ -1,11 +1,11 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import HiddenVisually from "components/HiddenVisually"; import View from "components/View"; import useToggle from "hooks/useToggle"; -import { classNames } from "@reshaped/utilities"; import s from "./FileUpload.module.css"; diff --git a/packages/reshaped/src/components/Flyout/FlyoutContent.tsx b/packages/reshaped/src/components/Flyout/FlyoutContent.tsx index cc694b77..36e763fe 100644 --- a/packages/reshaped/src/components/Flyout/FlyoutContent.tsx +++ b/packages/reshaped/src/components/Flyout/FlyoutContent.tsx @@ -1,10 +1,10 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import Portal from "components/_private/Portal"; import useIsomorphicLayoutEffect from "hooks/useIsomorphicLayoutEffect"; -import { classNames } from "@reshaped/utilities"; import { useFlyoutContext, ContentProvider } from "./Flyout.context"; import s from "./Flyout.module.css"; diff --git a/packages/reshaped/src/components/HiddenInput/HiddenInput.tsx b/packages/reshaped/src/components/HiddenInput/HiddenInput.tsx index ffc8307c..141b095b 100644 --- a/packages/reshaped/src/components/HiddenInput/HiddenInput.tsx +++ b/packages/reshaped/src/components/HiddenInput/HiddenInput.tsx @@ -1,7 +1,8 @@ +import { classNames } from "@reshaped/utilities"; + import { useCheckboxGroup } from "components/CheckboxGroup"; import { useFormControl } from "components/FormControl"; import { useRadioGroup } from "components/RadioGroup"; -import { classNames } from "@reshaped/utilities"; import s from "./HiddenInput.module.css"; diff --git a/packages/reshaped/src/components/Hotkey/Hotkey.tsx b/packages/reshaped/src/components/Hotkey/Hotkey.tsx index 666fdcd0..5f97af14 100644 --- a/packages/reshaped/src/components/Hotkey/Hotkey.tsx +++ b/packages/reshaped/src/components/Hotkey/Hotkey.tsx @@ -1,6 +1,7 @@ -import Text from "components/Text"; import { classNames } from "@reshaped/utilities"; +import Text from "components/Text"; + import s from "./Hotkey.module.css"; import type * as T from "./Hotkey.types"; diff --git a/packages/reshaped/src/components/Icon/Icon.tsx b/packages/reshaped/src/components/Icon/Icon.tsx index 0f97c515..1b974e51 100644 --- a/packages/reshaped/src/components/Icon/Icon.tsx +++ b/packages/reshaped/src/components/Icon/Icon.tsx @@ -1,7 +1,7 @@ +import { classNames } from "@reshaped/utilities"; import React from "react"; import { resolveMixin } from "styles/mixin"; -import { classNames } from "@reshaped/utilities"; import s from "./Icon.module.css"; diff --git a/packages/reshaped/src/components/Image/Image.tsx b/packages/reshaped/src/components/Image/Image.tsx index fcbbfc85..49b40a21 100644 --- a/packages/reshaped/src/components/Image/Image.tsx +++ b/packages/reshaped/src/components/Image/Image.tsx @@ -1,9 +1,9 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import { resolveMixin } from "styles/mixin"; -import { classNames } from "@reshaped/utilities"; import s from "./Image.module.css"; import * as T from "./Image.types"; diff --git a/packages/reshaped/src/components/Link/Link.tsx b/packages/reshaped/src/components/Link/Link.tsx index c5119bb5..0304628d 100644 --- a/packages/reshaped/src/components/Link/Link.tsx +++ b/packages/reshaped/src/components/Link/Link.tsx @@ -1,8 +1,8 @@ +import { classNames } from "@reshaped/utilities"; import { forwardRef } from "react"; import Actionable, { type ActionableRef } from "components/Actionable"; import Icon from "components/Icon"; -import { classNames } from "@reshaped/utilities"; import s from "./Link.module.css"; diff --git a/packages/reshaped/src/components/Modal/Modal.tsx b/packages/reshaped/src/components/Modal/Modal.tsx index c1ed664a..bb7ceecc 100644 --- a/packages/reshaped/src/components/Modal/Modal.tsx +++ b/packages/reshaped/src/components/Modal/Modal.tsx @@ -1,6 +1,7 @@ "use client"; import { classNames } from "@reshaped/utilities"; +import { enableScroll, disableScroll } from "@reshaped/utilities/internal"; import React from "react"; import Overlay from "components/Overlay"; @@ -10,7 +11,6 @@ import useHandlerRef from "hooks/useHandlerRef"; import useResponsiveClientValue from "hooks/useResponsiveClientValue"; import { resolveMixin } from "styles/mixin"; import { responsiveVariables, responsiveClassNames } from "utilities/props"; -import { enableScroll, disableScroll } from "utilities/scroll"; import s from "./Modal.module.css"; diff --git a/packages/reshaped/src/components/Overlay/Overlay.tsx b/packages/reshaped/src/components/Overlay/Overlay.tsx index 740fb324..9cea2431 100644 --- a/packages/reshaped/src/components/Overlay/Overlay.tsx +++ b/packages/reshaped/src/components/Overlay/Overlay.tsx @@ -1,6 +1,7 @@ "use client"; import { TrapFocus } from "@reshaped/utilities"; +import { classNames } from "@reshaped/utilities"; import { type FocusableElement } from "@reshaped/utilities/internal"; import React from "react"; @@ -12,7 +13,6 @@ import useIsomorphicLayoutEffect from "hooks/useIsomorphicLayoutEffect"; import useScrollLock from "hooks/useScrollLock"; import useToggle from "hooks/useToggle"; import { onNextFrame } from "utilities/animation"; -import { classNames } from "@reshaped/utilities"; import s from "./Overlay.module.css"; diff --git a/packages/reshaped/src/components/Popover/Popover.tsx b/packages/reshaped/src/components/Popover/Popover.tsx index 6feef39c..cb6cebf5 100644 --- a/packages/reshaped/src/components/Popover/Popover.tsx +++ b/packages/reshaped/src/components/Popover/Popover.tsx @@ -1,7 +1,8 @@ +import { classNames } from "@reshaped/utilities"; + import Dismissible, { type DismissibleProps } from "components/Dismissible"; import Flyout, { useFlyoutContext, type FlyoutProps } from "components/Flyout"; import { resolveMixin } from "styles/mixin"; -import { classNames } from "@reshaped/utilities"; import s from "./Popover.module.css"; diff --git a/packages/reshaped/src/components/Progress/Progress.tsx b/packages/reshaped/src/components/Progress/Progress.tsx index 126d4714..906d25de 100644 --- a/packages/reshaped/src/components/Progress/Progress.tsx +++ b/packages/reshaped/src/components/Progress/Progress.tsx @@ -1,6 +1,5 @@ -import React from "react"; - import { classNames } from "@reshaped/utilities"; +import React from "react"; import s from "./Progress.module.css"; diff --git a/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.tsx b/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.tsx index 575c7d4d..3fc5329d 100644 --- a/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.tsx +++ b/packages/reshaped/src/components/ProgressIndicator/ProgressIndicator.tsx @@ -1,8 +1,7 @@ "use client"; -import React from "react"; - import { classNames } from "@reshaped/utilities"; +import React from "react"; import s from "./ProgressIndicator.module.css"; diff --git a/packages/reshaped/src/components/Reshaped/Reshaped.tsx b/packages/reshaped/src/components/Reshaped/Reshaped.tsx index cccd6493..ca3a70eb 100644 --- a/packages/reshaped/src/components/Reshaped/Reshaped.tsx +++ b/packages/reshaped/src/components/Reshaped/Reshaped.tsx @@ -1,5 +1,6 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import { GlobalColorMode, PrivateTheme } from "components/Theme"; @@ -11,7 +12,6 @@ import { } from "hooks/_private/useSingletonEnvironment"; import { SingletonHotkeysProvider } from "hooks/_private/useSingletonHotkeys"; import { SingletonKeyboardModeProvider } from "hooks/_private/useSingletonKeyboardMode"; -import { classNames } from "@reshaped/utilities"; import s from "./Reshaped.module.css"; diff --git a/packages/reshaped/src/components/Resizable/Resizable.tsx b/packages/reshaped/src/components/Resizable/Resizable.tsx index 80677f3c..ae7fe05a 100644 --- a/packages/reshaped/src/components/Resizable/Resizable.tsx +++ b/packages/reshaped/src/components/Resizable/Resizable.tsx @@ -1,9 +1,9 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import View from "components/View"; -import { classNames } from "@reshaped/utilities"; import s from "./Resizable.module.css"; import { ResizableHandleContext } from "./ResizableHandle"; diff --git a/packages/reshaped/src/components/Resizable/ResizableHandle.tsx b/packages/reshaped/src/components/Resizable/ResizableHandle.tsx index 8ab4f51d..0d9bc247 100644 --- a/packages/reshaped/src/components/Resizable/ResizableHandle.tsx +++ b/packages/reshaped/src/components/Resizable/ResizableHandle.tsx @@ -1,10 +1,10 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import View from "components/View"; import useDrag from "hooks/_private/useDrag"; -import { classNames } from "@reshaped/utilities"; import s from "./Resizable.module.css"; diff --git a/packages/reshaped/src/components/Scrim/Scrim.tsx b/packages/reshaped/src/components/Scrim/Scrim.tsx index 868c7ee2..62e37644 100644 --- a/packages/reshaped/src/components/Scrim/Scrim.tsx +++ b/packages/reshaped/src/components/Scrim/Scrim.tsx @@ -1,6 +1,7 @@ -import View from "components/View"; import { classNames } from "@reshaped/utilities"; +import View from "components/View"; + import s from "./Scrim.module.css"; import type * as T from "./Scrim.types"; diff --git a/packages/reshaped/src/components/ScrollArea/ScrollArea.tsx b/packages/reshaped/src/components/ScrollArea/ScrollArea.tsx index 6e6d40b6..35490ed3 100644 --- a/packages/reshaped/src/components/ScrollArea/ScrollArea.tsx +++ b/packages/reshaped/src/components/ScrollArea/ScrollArea.tsx @@ -1,12 +1,12 @@ "use client"; +import { classNames } from "@reshaped/utilities"; +import { disableScroll, enableScroll } from "@reshaped/utilities/internal"; import React, { forwardRef } from "react"; import useHandlerRef from "hooks/useHandlerRef"; import useIsomorphicLayoutEffect from "hooks/useIsomorphicLayoutEffect"; import { resolveMixin } from "styles/mixin"; -import { classNames } from "@reshaped/utilities"; -import { disableScroll, enableScroll } from "utilities/scroll"; import s from "./ScrollArea.module.css"; diff --git a/packages/reshaped/src/components/Select/SelectNative.tsx b/packages/reshaped/src/components/Select/SelectNative.tsx index 0e254339..8e01723e 100644 --- a/packages/reshaped/src/components/Select/SelectNative.tsx +++ b/packages/reshaped/src/components/Select/SelectNative.tsx @@ -1,8 +1,7 @@ "use client"; -import React from "react"; - import { classNames } from "@reshaped/utilities"; +import React from "react"; import s from "./Select.module.css"; import SelectEndContent from "./SelectEndContent"; diff --git a/packages/reshaped/src/components/Skeleton/Skeleton.tsx b/packages/reshaped/src/components/Skeleton/Skeleton.tsx index 62bfedc6..42a04b3f 100644 --- a/packages/reshaped/src/components/Skeleton/Skeleton.tsx +++ b/packages/reshaped/src/components/Skeleton/Skeleton.tsx @@ -1,6 +1,7 @@ -import View from "components/View"; import { classNames } from "@reshaped/utilities"; +import View from "components/View"; + import s from "./Skeleton.module.css"; import * as T from "./Skeleton.types"; diff --git a/packages/reshaped/src/components/Slider/SliderControlled.tsx b/packages/reshaped/src/components/Slider/SliderControlled.tsx index c3b6488f..bc2d0e15 100644 --- a/packages/reshaped/src/components/Slider/SliderControlled.tsx +++ b/packages/reshaped/src/components/Slider/SliderControlled.tsx @@ -1,5 +1,7 @@ "use client"; +import { classNames } from "@reshaped/utilities"; +import { disableScroll, enableScroll } from "@reshaped/utilities/internal"; import React from "react"; import { useFormControl } from "components/FormControl"; @@ -7,8 +9,6 @@ import useElementId from "hooks/useElementId"; import useHandlerRef from "hooks/useHandlerRef"; import useRTL from "hooks/useRTL"; import { triggerChangeEvent } from "utilities/dom"; -import { classNames } from "@reshaped/utilities"; -import { disableScroll, enableScroll } from "utilities/scroll"; import s from "./Slider.module.css"; import { applyStepToValue, getDragCoord } from "./Slider.utilities"; diff --git a/packages/reshaped/src/components/Slider/SliderThumb.tsx b/packages/reshaped/src/components/Slider/SliderThumb.tsx index 6b30cad9..30dba364 100644 --- a/packages/reshaped/src/components/Slider/SliderThumb.tsx +++ b/packages/reshaped/src/components/Slider/SliderThumb.tsx @@ -1,10 +1,10 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import Text from "components/Text"; import Theme from "components/Theme"; -import { classNames } from "@reshaped/utilities"; import s from "./Slider.module.css"; import { getPrecision } from "./Slider.utilities"; diff --git a/packages/reshaped/src/components/Tabs/TabsItem.tsx b/packages/reshaped/src/components/Tabs/TabsItem.tsx index 71d08633..23a21f8e 100644 --- a/packages/reshaped/src/components/Tabs/TabsItem.tsx +++ b/packages/reshaped/src/components/Tabs/TabsItem.tsx @@ -1,5 +1,6 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import Actionable, { type ActionableRef } from "components/Actionable"; @@ -8,7 +9,6 @@ import Icon from "components/Icon"; import Text from "components/Text"; import useIsomorphicLayoutEffect from "hooks/useIsomorphicLayoutEffect"; import { findParent } from "utilities/dom"; -import { classNames } from "@reshaped/utilities"; import s from "./Tabs.module.css"; import { useTabs } from "./TabsContext"; diff --git a/packages/reshaped/src/components/Tabs/TabsList.tsx b/packages/reshaped/src/components/Tabs/TabsList.tsx index c3d087cb..356f1dca 100644 --- a/packages/reshaped/src/components/Tabs/TabsList.tsx +++ b/packages/reshaped/src/components/Tabs/TabsList.tsx @@ -1,5 +1,6 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import Actionable from "components/Actionable"; @@ -10,7 +11,6 @@ import useKeyboardArrowNavigation from "hooks/useKeyboardArrowNavigation"; import useRTL from "hooks/useRTL"; import IconChevronLeft from "icons/ChevronLeft"; import IconChevronRight from "icons/ChevronRight"; -import { classNames } from "@reshaped/utilities"; import s from "./Tabs.module.css"; import { useTabs } from "./TabsContext"; diff --git a/packages/reshaped/src/components/Tabs/TabsPanel.tsx b/packages/reshaped/src/components/Tabs/TabsPanel.tsx index 93803d58..2f833f97 100644 --- a/packages/reshaped/src/components/Tabs/TabsPanel.tsx +++ b/packages/reshaped/src/components/Tabs/TabsPanel.tsx @@ -1,10 +1,9 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import { getFocusableElements } from "@reshaped/utilities/internal"; import React from "react"; -import { classNames } from "@reshaped/utilities"; - import s from "./Tabs.module.css"; import { useTabs } from "./TabsContext"; diff --git a/packages/reshaped/src/components/Theme/Theme.tsx b/packages/reshaped/src/components/Theme/Theme.tsx index 936cc32f..29be34e9 100644 --- a/packages/reshaped/src/components/Theme/Theme.tsx +++ b/packages/reshaped/src/components/Theme/Theme.tsx @@ -1,9 +1,9 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import useIsomorphicLayoutEffect from "hooks/useIsomorphicLayoutEffect"; -import { classNames } from "@reshaped/utilities"; import { ThemeContext } from "./Theme.context"; import s from "./Theme.module.css"; diff --git a/packages/reshaped/src/components/Timeline/Timeline.tsx b/packages/reshaped/src/components/Timeline/Timeline.tsx index 99fcf2d6..c78efd9e 100644 --- a/packages/reshaped/src/components/Timeline/Timeline.tsx +++ b/packages/reshaped/src/components/Timeline/Timeline.tsx @@ -1,7 +1,7 @@ +import { classNames } from "@reshaped/utilities"; import React, { isValidElement } from "react"; import View from "components/View"; -import { classNames } from "@reshaped/utilities"; import s from "./Timeline.module.css"; diff --git a/packages/reshaped/src/components/Toast/ToastContainer.tsx b/packages/reshaped/src/components/Toast/ToastContainer.tsx index 056bd704..555ce58f 100644 --- a/packages/reshaped/src/components/Toast/ToastContainer.tsx +++ b/packages/reshaped/src/components/Toast/ToastContainer.tsx @@ -1,11 +1,11 @@ "use client"; import { TrapFocus } from "@reshaped/utilities"; +import { classNames } from "@reshaped/utilities"; import { checkKeyboardMode } from "@reshaped/utilities/internal"; import React from "react"; import { onNextFrame } from "utilities/animation"; -import { classNames } from "@reshaped/utilities"; import Toast from "./Toast"; import { timeouts } from "./Toast.constants"; diff --git a/packages/reshaped/src/components/Toast/ToastRegion.tsx b/packages/reshaped/src/components/Toast/ToastRegion.tsx index 87c960de..cf400349 100644 --- a/packages/reshaped/src/components/Toast/ToastRegion.tsx +++ b/packages/reshaped/src/components/Toast/ToastRegion.tsx @@ -1,10 +1,9 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import { focusableSelector } from "@reshaped/utilities/internal"; import React from "react"; -import { classNames } from "@reshaped/utilities"; - import ToastContext from "./Toast.context"; import s from "./Toast.module.css"; import * as T from "./Toast.types"; diff --git a/packages/reshaped/src/components/_private/Expandable/Expandable.tsx b/packages/reshaped/src/components/_private/Expandable/Expandable.tsx index f1458015..abf4736a 100644 --- a/packages/reshaped/src/components/_private/Expandable/Expandable.tsx +++ b/packages/reshaped/src/components/_private/Expandable/Expandable.tsx @@ -1,10 +1,10 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React from "react"; import useIsomorphicLayoutEffect from "hooks/useIsomorphicLayoutEffect"; import { onNextFrame } from "utilities/animation"; -import { classNames } from "@reshaped/utilities"; import s from "./Expandable.module.css"; import * as T from "./Expandable.types"; diff --git a/packages/reshaped/src/core/Actionable/Actionable.tsx b/packages/reshaped/src/core/Actionable/Actionable.tsx index 5a701c76..2b948f9b 100644 --- a/packages/reshaped/src/core/Actionable/Actionable.tsx +++ b/packages/reshaped/src/core/Actionable/Actionable.tsx @@ -1,9 +1,9 @@ "use client"; +import { classNames } from "@reshaped/utilities"; import React, { forwardRef } from "react"; import * as keys from "constants/keys"; -import { classNames } from "@reshaped/utilities"; import type * as T from "./Actionable.types"; diff --git a/packages/reshaped/src/hooks/_private/useDrag.ts b/packages/reshaped/src/hooks/_private/useDrag.ts index 29495009..26d242fc 100644 --- a/packages/reshaped/src/hooks/_private/useDrag.ts +++ b/packages/reshaped/src/hooks/_private/useDrag.ts @@ -1,12 +1,12 @@ "use client"; +import { disableScroll, enableScroll } from "@reshaped/utilities/internal"; import React from "react"; import * as keys from "constants/keys"; import useHandlerRef from "hooks/useHandlerRef"; import useHotkeys from "hooks/useHotkeys"; import useToggle from "hooks/useToggle"; -import { disableScroll, enableScroll } from "utilities/scroll"; export type UseDragCallbackArgs = { x: number; y: number; triggerX: number; triggerY: number }; diff --git a/packages/reshaped/src/hooks/useScrollLock.ts b/packages/reshaped/src/hooks/useScrollLock.ts index 7bceffd2..fecc7009 100644 --- a/packages/reshaped/src/hooks/useScrollLock.ts +++ b/packages/reshaped/src/hooks/useScrollLock.ts @@ -1,9 +1,8 @@ "use client"; +import { lockScroll } from "@reshaped/utilities"; import React from "react"; -import { lockScroll } from "utilities/scroll"; - const useScrollLock = (options?: { containerRef?: React.RefObject; originRef?: React.RefObject; @@ -16,12 +15,12 @@ const useScrollLock = (options?: { unlockScrollRef.current = lockScroll({ containerEl: containerRef?.current, originEl: originRef?.current, - cb: () => setLocked(true), + callback: () => setLocked(true), }); }, [containerRef, originRef]); const handleUnlockScroll = React.useCallback(() => { - unlockScrollRef.current?.(() => setLocked(false)); + unlockScrollRef.current?.({ callback: () => setLocked(false) }); unlockScrollRef.current = null; }, []); diff --git a/packages/reshaped/src/utilities/scroll/lock.ts b/packages/reshaped/src/utilities/scroll/lock.ts deleted file mode 100644 index 8071c1bf..00000000 --- a/packages/reshaped/src/utilities/scroll/lock.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { findClosestScrollableContainer } from "utilities/dom"; -import { isIOS } from "utilities/platform"; - -import lockSafariScroll from "./lockSafari"; -import lockStandardScroll from "./lockStandard"; - -let bodyLockedCount = 0; - -export const lockScroll = (args: { - containerEl?: HTMLElement | null; - originEl?: HTMLElement | null; - cb?: () => void; -}) => { - const isIOSLock = isIOS(); - let reset = () => {}; - - let container = document.body; - if (args.originEl) container = findClosestScrollableContainer({ el: args.originEl }); - if (args.containerEl) container = args.containerEl; - - const lockedBodyScroll = container === document.body; - - if (lockedBodyScroll) bodyLockedCount += 1; - if (lockedBodyScroll && bodyLockedCount > 1) return; - - if (isIOSLock && lockedBodyScroll) { - reset = lockSafariScroll(); - } else { - reset = lockStandardScroll({ container }); - } - - args.cb?.(); - - return (cb?: () => void) => { - if (lockedBodyScroll) bodyLockedCount -= 1; - - reset(); - cb?.(); - }; -}; diff --git a/packages/utilities/src/a11y/TrapFocus.ts b/packages/utilities/src/a11y/TrapFocus.ts index fdda6fd4..c7e59375 100644 --- a/packages/utilities/src/a11y/TrapFocus.ts +++ b/packages/utilities/src/a11y/TrapFocus.ts @@ -1,6 +1,5 @@ -import * as keys from "constants/keys"; - -import getShadowRoot from "dom/getShadowRoot"; +import * as keys from "@/constants/keys"; +import { getShadowRoot } from "@/dom"; import Chain from "./Chain"; import { getActiveElement, getFocusableElements, focusElement, getFocusData } from "./focus"; diff --git a/packages/utilities/src/a11y/focus.ts b/packages/utilities/src/a11y/focus.ts index e150d145..dab261ab 100644 --- a/packages/utilities/src/a11y/focus.ts +++ b/packages/utilities/src/a11y/focus.ts @@ -1,4 +1,4 @@ -import getShadowRoot from "dom/getShadowRoot"; +import { getShadowRoot } from "@/dom"; import type { FocusableElement, FocusableOptions } from "./types"; diff --git a/packages/utilities/src/css/StyleCache.ts b/packages/utilities/src/css/StyleCache.ts new file mode 100644 index 00000000..8a694fbb --- /dev/null +++ b/packages/utilities/src/css/StyleCache.ts @@ -0,0 +1,27 @@ +type Styles = Record; + +class StyleCache { + cache: Map> = new Map(); + + set = (el: HTMLElement, styles: Styles) => { + const originalStyles: Styles = {}; + const cachedStyles = this.cache.get(el); + + Object.keys(styles).forEach((key) => { + originalStyles[key] = el.style.getPropertyValue(key); + }); + + this.cache.set(el, { ...originalStyles, ...cachedStyles }); + Object.assign(el.style, styles); + }; + + reset = () => { + for (const [el, styles] of this.cache.entries()) { + Object.assign(el.style, styles); + } + + this.cache.clear(); + }; +} + +export default StyleCache; diff --git a/packages/utilities/src/helpers/classNames.ts b/packages/utilities/src/css/classNames.ts similarity index 100% rename from packages/utilities/src/helpers/classNames.ts rename to packages/utilities/src/css/classNames.ts diff --git a/packages/utilities/src/css/index.ts b/packages/utilities/src/css/index.ts new file mode 100644 index 00000000..494556de --- /dev/null +++ b/packages/utilities/src/css/index.ts @@ -0,0 +1,5 @@ +// External +export { default as classNames, type ClassName } from "./classNames"; + +// Internal +export { default as StyleCache } from "./StyleCache"; diff --git a/packages/utilities/src/css/tests/StyleCache.test.ts b/packages/utilities/src/css/tests/StyleCache.test.ts new file mode 100644 index 00000000..c5e1cf6b --- /dev/null +++ b/packages/utilities/src/css/tests/StyleCache.test.ts @@ -0,0 +1,62 @@ +import { expect, test, describe, beforeEach } from "vitest"; + +import StyleCache from "../StyleCache"; + +describe("css/StyleCache", () => { + let styleCache: StyleCache; + + beforeEach(() => { + styleCache = new StyleCache(); + }); + + test("applies styles and restores original styles on reset", () => { + const el = document.createElement("div"); + el.style.color = "red"; + el.style.overflow = "visible"; + + styleCache.set(el, { color: "blue", overflow: "hidden" }); + + expect(el.style.color).toBe("blue"); + expect(el.style.overflow).toBe("hidden"); + + styleCache.reset(); + + expect(el.style.color).toBe("red"); + expect(el.style.overflow).toBe("visible"); + }); + + test("preserves original styles across multiple set calls", () => { + const el = document.createElement("div"); + el.style.color = "red"; + + styleCache.set(el, { color: "blue" }); + styleCache.set(el, { color: "green" }); + styleCache.reset(); + + expect(el.style.color).toBe("red"); + }); + + test("handles multiple elements", () => { + const el1 = document.createElement("div"); + const el2 = document.createElement("div"); + el1.style.color = "red"; + el2.style.color = "blue"; + + styleCache.set(el1, { color: "green" }); + styleCache.set(el2, { color: "yellow" }); + styleCache.reset(); + + expect(el1.style.color).toBe("red"); + expect(el2.style.color).toBe("blue"); + }); + + test("clears cache after reset", () => { + const el = document.createElement("div"); + el.style.color = "red"; + + styleCache.set(el, { color: "blue" }); + styleCache.reset(); + + expect(styleCache.cache.size).toBe(0); + }); +}); diff --git a/packages/utilities/src/helpers/tests/classNames.test.ts b/packages/utilities/src/css/tests/classNames.test.ts similarity index 100% rename from packages/utilities/src/helpers/tests/classNames.test.ts rename to packages/utilities/src/css/tests/classNames.test.ts diff --git a/packages/utilities/src/flyout/utilities/findClosestScrollableContainer.ts b/packages/utilities/src/dom/findClosestScrollableContainer.ts similarity index 100% rename from packages/utilities/src/flyout/utilities/findClosestScrollableContainer.ts rename to packages/utilities/src/dom/findClosestScrollableContainer.ts diff --git a/packages/utilities/src/dom/index.ts b/packages/utilities/src/dom/index.ts new file mode 100644 index 00000000..9b9bfe9a --- /dev/null +++ b/packages/utilities/src/dom/index.ts @@ -0,0 +1,3 @@ +// Internal +export { default as findClosestScrollableContainer } from "./findClosestScrollableContainer"; +export { default as getShadowRoot } from "./getShadowRoot"; diff --git a/packages/utilities/src/flyout/Flyout.ts b/packages/utilities/src/flyout/Flyout.ts index 59d0f040..412f9829 100644 --- a/packages/utilities/src/flyout/Flyout.ts +++ b/packages/utilities/src/flyout/Flyout.ts @@ -1,7 +1,7 @@ -import rafThrottle from "helpers/rafThrottle"; +import { findClosestScrollableContainer } from "@/dom"; +import { rafThrottle } from "@/helpers"; import applyPosition from "./utilities/applyPosition"; -import findClosestScrollableContainer from "./utilities/findClosestScrollableContainer"; import type { Position, Options } from "./types"; diff --git a/packages/utilities/src/flyout/index.ts b/packages/utilities/src/flyout/index.ts index 8dc36315..b50d4342 100644 --- a/packages/utilities/src/flyout/index.ts +++ b/packages/utilities/src/flyout/index.ts @@ -1 +1 @@ -export { default } from "./Flyout"; +export { default as Flyout } from "./Flyout"; diff --git a/packages/utilities/src/flyout/utilities/applyPosition.ts b/packages/utilities/src/flyout/utilities/applyPosition.ts index 6b57b495..2ae699b5 100644 --- a/packages/utilities/src/flyout/utilities/applyPosition.ts +++ b/packages/utilities/src/flyout/utilities/applyPosition.ts @@ -1,5 +1,5 @@ -import getShadowRoot from "dom/getShadowRoot"; -import isRTL from "i18n/isRTL"; +import { getShadowRoot } from "@/dom"; +import isRTL from "@/i18n/isRTL"; import { VIEWPORT_OFFSET, RESET_STYLES } from "../constants"; diff --git a/packages/utilities/src/flyout/utilities/tests/findClosestScrollableContainer.test.ts b/packages/utilities/src/flyout/utilities/tests/findClosestScrollableContainer.test.ts index 34c7ea0f..e49caad0 100644 --- a/packages/utilities/src/flyout/utilities/tests/findClosestScrollableContainer.test.ts +++ b/packages/utilities/src/flyout/utilities/tests/findClosestScrollableContainer.test.ts @@ -1,6 +1,6 @@ import { expect, test, describe } from "vitest"; -import findClosestScrollableContainer from "flyout/utilities/findClosestScrollableContainer"; +import findClosestScrollableContainer from "dom/findClosestScrollableContainer"; describe("flyout/findClosestScrollableContainer", () => { test("returns null when element has no parent", () => { diff --git a/packages/utilities/src/helpers/index.ts b/packages/utilities/src/helpers/index.ts index fae892a8..7e0c28ee 100644 --- a/packages/utilities/src/helpers/index.ts +++ b/packages/utilities/src/helpers/index.ts @@ -1,2 +1,2 @@ -export { default as classNames, type ClassName } from "./classNames"; +// Internal export { default as rafThrottle } from "./rafThrottle"; diff --git a/packages/utilities/src/i18n/index.ts b/packages/utilities/src/i18n/index.ts index a9a858c9..c4203b77 100644 --- a/packages/utilities/src/i18n/index.ts +++ b/packages/utilities/src/i18n/index.ts @@ -1 +1,2 @@ +// External export { default as isRTL } from "./isRTL"; diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts index 5a0b2892..4a24a636 100644 --- a/packages/utilities/src/index.ts +++ b/packages/utilities/src/index.ts @@ -1,5 +1,6 @@ -export { default as Flyout } from "./flyout"; -export { default as TrapFocus } from "./a11y/TrapFocus"; +export { Flyout } from "./flyout"; +export { TrapFocus } from "./a11y"; -export { default as isRTL } from "./i18n/isRTL"; -export { classNames, type ClassName } from "./helpers"; +export { lockScroll } from "./scroll"; +export { isRTL } from "./i18n"; +export { classNames, type ClassName } from "./css"; diff --git a/packages/utilities/src/internal.ts b/packages/utilities/src/internal.ts index 0c0519a1..4b53fb69 100644 --- a/packages/utilities/src/internal.ts +++ b/packages/utilities/src/internal.ts @@ -1,6 +1,8 @@ /** * Internal utilities re-used in other Reshaped components but not meant to be used as a public API * Their API is subject to change without a major version bump. + * + * If you want to use one of these utilities, open an issue or a PR about moving it to the public API file */ export { @@ -17,3 +19,5 @@ export { } from "./a11y"; export type { TrapMode, FocusableElement } from "./a11y"; + +export { disableScroll, enableScroll } from "./scroll"; diff --git a/packages/reshaped/src/utilities/platform.ts b/packages/utilities/src/platform/index.ts similarity index 85% rename from packages/reshaped/src/utilities/platform.ts rename to packages/utilities/src/platform/index.ts index cdeae46d..0c9a08e2 100644 --- a/packages/reshaped/src/utilities/platform.ts +++ b/packages/utilities/src/platform/index.ts @@ -1,5 +1,5 @@ const testPlatform = (re: RegExp) => { - // Using experimentral and deprecated features here since that's the only way to detect platform consistently + // Using experimental and deprecated features here since that's the only way to detect platform consistently const platform = // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/packages/reshaped/src/utilities/scroll/disable.ts b/packages/utilities/src/scroll/disable.ts similarity index 100% rename from packages/reshaped/src/utilities/scroll/disable.ts rename to packages/utilities/src/scroll/disable.ts diff --git a/packages/reshaped/src/utilities/scroll/helpers.ts b/packages/utilities/src/scroll/helpers.ts similarity index 100% rename from packages/reshaped/src/utilities/scroll/helpers.ts rename to packages/utilities/src/scroll/helpers.ts diff --git a/packages/reshaped/src/utilities/scroll/index.ts b/packages/utilities/src/scroll/index.ts similarity index 78% rename from packages/reshaped/src/utilities/scroll/index.ts rename to packages/utilities/src/scroll/index.ts index 10290b0c..28acb641 100644 --- a/packages/reshaped/src/utilities/scroll/index.ts +++ b/packages/utilities/src/scroll/index.ts @@ -1,2 +1,5 @@ -export { disableScroll, enableScroll } from "./disable"; +// External export { lockScroll } from "./lock"; + +// Internal +export { disableScroll, enableScroll } from "./disable"; diff --git a/packages/utilities/src/scroll/lock.ts b/packages/utilities/src/scroll/lock.ts new file mode 100644 index 00000000..ab344a14 --- /dev/null +++ b/packages/utilities/src/scroll/lock.ts @@ -0,0 +1,36 @@ +import { findClosestScrollableContainer } from "@/dom"; +import { isIOS } from "@/platform"; + +import lockSafariScroll from "./lockSafari"; +import lockStandardScroll from "./lockStandard"; + +export const lockScroll = (args: { + containerEl?: HTMLElement | null; + originEl?: HTMLElement | null; + callback?: () => void; +}) => { + const isIOSLock = isIOS(); + let reset = () => {}; + + const container = + args.containerEl ?? + (args.originEl && findClosestScrollableContainer({ el: args.originEl })) ?? + document.body; + const lockedBodyScroll = container === document.body; + + // Already locked so no need to lock again and trigger the callback + if (container.style.overflow === "hidden") return; + + if (isIOSLock && lockedBodyScroll) { + reset = lockSafariScroll(); + } else { + reset = lockStandardScroll({ container }); + } + + args.callback?.(); + + return (args?: { callback?: () => void }) => { + reset(); + args?.callback?.(); + }; +}; diff --git a/packages/reshaped/src/utilities/scroll/lockSafari.ts b/packages/utilities/src/scroll/lockSafari.ts similarity index 93% rename from packages/reshaped/src/utilities/scroll/lockSafari.ts rename to packages/utilities/src/scroll/lockSafari.ts index 0e95138b..9ae40362 100644 --- a/packages/reshaped/src/utilities/scroll/lockSafari.ts +++ b/packages/utilities/src/scroll/lockSafari.ts @@ -1,4 +1,4 @@ -import { StyleCache } from "utilities/css"; +import { StyleCache } from "@/css"; const styleCache = new StyleCache(); diff --git a/packages/reshaped/src/utilities/scroll/lockStandard.ts b/packages/utilities/src/scroll/lockStandard.ts similarity index 92% rename from packages/reshaped/src/utilities/scroll/lockStandard.ts rename to packages/utilities/src/scroll/lockStandard.ts index ca0a79b9..ae545ffa 100644 --- a/packages/reshaped/src/utilities/scroll/lockStandard.ts +++ b/packages/utilities/src/scroll/lockStandard.ts @@ -1,4 +1,4 @@ -import { StyleCache } from "utilities/css"; +import { StyleCache } from "@/css"; import { getScrollbarWidth } from "./helpers"; diff --git a/packages/utilities/src/scroll/tests/lock.test.ts b/packages/utilities/src/scroll/tests/lock.test.ts new file mode 100644 index 00000000..a59c27b0 --- /dev/null +++ b/packages/utilities/src/scroll/tests/lock.test.ts @@ -0,0 +1,116 @@ +import { expect, test, describe, beforeEach, vi, afterEach } from "vitest"; + +import { lockScroll } from "../lock"; + +// Mock the platform module to avoid iOS path +vi.mock("@/platform", () => ({ + isIOS: () => false, +})); + +describe("scroll/lockScroll", () => { + beforeEach(() => { + // Reset the body styles before each test + document.body.style.overflow = ""; + document.body.style.paddingRight = ""; + document.body.innerHTML = ""; + }); + + afterEach(() => { + // Clean up + document.body.style.overflow = ""; + document.body.style.paddingRight = ""; + document.body.innerHTML = ""; + }); + + test("locks scroll on body", () => { + const unlock = lockScroll({}); + + expect(document.body.style.overflow).toBe("hidden"); + + unlock?.(); + + expect(document.body.style.overflow).toBe(""); + }); + + test("locks scroll on custom container element", () => { + const container = document.createElement("div"); + container.style.overflow = "auto"; + container.style.height = "200px"; + document.body.appendChild(container); + + const unlock = lockScroll({ containerEl: container }); + + expect(container.style.overflow).toBe("hidden"); + expect(document.body.style.overflow).not.toBe("hidden"); + + unlock?.(); + + expect(container.style.overflow).toBe("auto"); + }); + + test("finds scrollable container from origin element", () => { + const scrollableContainer = document.createElement("div"); + scrollableContainer.style.overflow = "auto"; + scrollableContainer.style.height = "200px"; + document.body.appendChild(scrollableContainer); + + // Add content to make it scrollable + const content = document.createElement("div"); + content.style.height = "500px"; + scrollableContainer.appendChild(content); + + const originEl = document.createElement("div"); + scrollableContainer.appendChild(originEl); + + const unlock = lockScroll({ originEl }); + + expect(scrollableContainer.style.overflow).toBe("hidden"); + + unlock?.(); + + expect(scrollableContainer.style.overflow).toBe("auto"); + }); + + test("unlocks after multiple locks", () => { + const unlock1 = lockScroll({}); + const unlock2 = lockScroll({}); + + expect(document.body.style.overflow).toBe("hidden"); + + unlock1?.(); + expect(document.body.style.overflow).toBe(""); + + unlock2?.(); + expect(document.body.style.overflow).toBe(""); + }); + + test("calls lock callback immediately", () => { + const lockCb = vi.fn(); + + lockScroll({ callback: lockCb }); + + expect(lockCb).toHaveBeenCalledTimes(1); + }); + + test("calls unlock callback when unlocking", () => { + const unlockCb = vi.fn(); + + const unlock = lockScroll({}); + unlock?.({ callback: unlockCb }); + + expect(unlockCb).toHaveBeenCalledTimes(1); + }); + + test("handles origin element when no scrollable container is found (falls back to body)", () => { + const originEl = document.createElement("div"); + document.body.appendChild(originEl); + + const unlock = lockScroll({ originEl }); + + expect(document.body.style.overflow).toBe("hidden"); + + unlock?.(); + + expect(document.body.style.overflow).toBe(""); + }); +}); diff --git a/packages/utilities/tsconfig.json b/packages/utilities/tsconfig.json index 2494061b..e85fe220 100644 --- a/packages/utilities/tsconfig.json +++ b/packages/utilities/tsconfig.json @@ -7,7 +7,7 @@ "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", - "paths": { "*": ["*"] } + "paths": { "@/*": ["./*"] } }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"]