Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/free-windows-stand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@reshaped/headless": minor
"reshaped": minor
"@reshaped/utilities": minor
---

Extracted Reshaped provider and all of its dependencies into @reshaped/headless
4 changes: 2 additions & 2 deletions .storybook/preview.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from "react";
import { useRTL } from "../packages/headless";
import Reshaped from "../packages/reshaped/src/components/Reshaped";
import Button from "../packages/reshaped/src/components/Button";
import View from "../packages/reshaped/src/components/View";
import Text from "../packages/reshaped/src/components/Text";
import Hidden from "../packages/reshaped/src/components/Hidden";
import DropdownMenu from "../packages/reshaped/src/components/DropdownMenu";
import Icon from "../packages/reshaped/src/components/Icon";
import useRTL from "../packages/reshaped/src/hooks/useRTL";
import useTheme from "../packages/reshaped/src/components/Theme";
import IconCheckmark from "../packages/reshaped/src/icons/Checkmark";
import { useTheme } from "../packages/reshaped/src/components/Theme";
import "../packages/reshaped/src/themes/reshaped/theme.css";
import "../packages/reshaped/src/themes/slate/theme.css";
import "../packages/reshaped/src/themes/figma/theme.css";
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/index.ts

This file was deleted.

File renamed without changes.
12 changes: 9 additions & 3 deletions packages/core/package.json → packages/headless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./internal": {
"types": "./dist/internal.d.ts",
"import": "./dist/internal.js",
"default": "./dist/internal.js"
},
"./package.json": "./package.json"
},

"scripts": {
"clean": "rimraf dist",
"dev": "tsc --watch -p tsconfig.json",
"build": "pnpm clean && tsc -p tsconfig.json"
"clean": "rm -rf dist",
"dev": "tsc-watch --onSuccess \"resolve-tspaths\"",
"build": "tsc && resolve-tspaths"
},
"peerDependencies": {
"react": "^18 || ^19",
Expand Down
25 changes: 25 additions & 0 deletions packages/headless/src/components/Reshaped/Reshaped.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import React from "react";

import { SingletonHotkeysProvider } from "@/hooks/_internal/useSingletonHotkeys";
import { SingletonKeyboardModeProvider } from "@/hooks/_internal/useSingletonKeyboardMode";
import { SingletonRTLProvider } from "@/hooks/_internal/useSingletonRTL";

import type * as T from "./Reshaped.types";

const Reshaped: React.FC<T.Props> = (props) => {
const { children } = props;

return (
<SingletonRTLProvider>
<SingletonKeyboardModeProvider>
<SingletonHotkeysProvider>{children}</SingletonHotkeysProvider>
</SingletonKeyboardModeProvider>
</SingletonRTLProvider>
);
};

Reshaped.displayName = "Headless.ReshapedProvider";

export default Reshaped;
6 changes: 6 additions & 0 deletions packages/headless/src/components/Reshaped/Reshaped.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type React from "react";

export type Props = {
/** Node for inserting children */
children?: React.ReactNode;
};
2 changes: 2 additions & 0 deletions packages/headless/src/components/Reshaped/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from "./Reshaped";
export type { Props as ReshapedProps } from "./Reshaped.types";
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import React from "react";

/**
Expand Down Expand Up @@ -156,7 +158,7 @@ const globalHotkeyStore = new HotkeyStore();
/**
* Components / Hooks
*/
export const HotkeyContext = React.createContext({} as Context);
const HotkeyContext = React.createContext({} as Context);

export const SingletonHotkeysProvider: React.FC<{ children: React.ReactNode }> = (props) => {
const { children } = props;
Expand Down Expand Up @@ -269,6 +271,4 @@ export const SingletonHotkeysProvider: React.FC<{ children: React.ReactNode }> =
);
};

const useSingletonHotkeys = () => React.useContext(HotkeyContext);

export default useSingletonHotkeys;
export const useSingletonHotkeys = () => React.useContext(HotkeyContext);
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { activateKeyboardMode, deactivateKeyboardMode } from "@reshaped/utilities/internal";
import React from "react";

import * as keys from "constants/keys";
const ESC = "Escape";

type ContextProps = {
disabledRef: React.RefObject<boolean> | null;
Expand All @@ -13,7 +13,7 @@ type ContextProps = {
deactivate: () => void;
};

export const SingletonKeyboardModeContext = React.createContext<ContextProps>({
const SingletonKeyboardModeContext = React.createContext<ContextProps>({
disabledRef: null,
disable: () => {},
enable: () => {},
Expand Down Expand Up @@ -46,7 +46,7 @@ export const SingletonKeyboardModeProvider: React.FC<{ children: React.ReactNode
(e: KeyboardEvent) => {
if (e.metaKey || e.altKey || e.ctrlKey) return;
// Prevent focus ring from appearing when using mouse but closing with esc
if (e.key === keys.ESC) return;
if (e.key === ESC) return;
activate();
},
[activate]
Expand Down Expand Up @@ -83,3 +83,5 @@ export const SingletonKeyboardModeProvider: React.FC<{ children: React.ReactNode
</SingletonKeyboardModeContext.Provider>
);
};

export const useSingletonKeyboardMode = () => React.useContext(SingletonKeyboardModeContext);
59 changes: 59 additions & 0 deletions packages/headless/src/hooks/_internal/useSingletonRTL.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client";

import { isRTL } from "@reshaped/utilities";
import React from "react";

import useIsomorphicLayoutEffect from "@/hooks/useIsomorphicLayoutEffect";

type Context = {
rtl: [boolean, (state: boolean) => void];
};

const SingletonRTLContext = React.createContext<Context>({
rtl: [false, () => {}],
});

export const useSingletonRTL = (defaultRTL?: boolean) => {
const state = React.useState(defaultRTL || false);
const [rtl, setRTL] = state;

/**
* Handle changing dir attribute directly
*/
useIsomorphicLayoutEffect(() => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName !== "dir") return;

const nextRTL = isRTL();
if (rtl !== nextRTL) setRTL(nextRTL);
});
});

observer.observe(document.documentElement, { attributes: true });
return () => observer.disconnect();
}, [rtl]);

/**
* Handle setRTL usage
*/
useIsomorphicLayoutEffect(() => {
document.documentElement.setAttribute("dir", rtl ? "rtl" : "ltr");
}, [rtl]);

return state;
};

export const SingletonRTLProvider: React.FC<{
children: React.ReactNode;
defaultRTL?: boolean;
}> = (props) => {
const { children, defaultRTL } = props;
const rtlState = useSingletonRTL(defaultRTL);

return (
<SingletonRTLContext.Provider value={{ rtl: rtlState }}>
{children}
</SingletonRTLContext.Provider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import React from "react";

import useSingletonHotkey, { type Hotkeys } from "./_private/useSingletonHotkeys";
import { useSingletonHotkeys, type Hotkeys } from "@/hooks/_internal/useSingletonHotkeys";

const useHotkeys = <Element extends HTMLElement>(
hotkeys: Hotkeys,
deps: unknown[] = [],
options?: { ref?: React.RefObject<Element | null>; disabled?: boolean; preventDefault?: boolean }
) => {
const { addHotkeys, isPressed } = useSingletonHotkey();
const { addHotkeys, isPressed } = useSingletonHotkeys();
const generatedRef = React.useRef<Element>(null);
const elementRef = options?.ref || generatedRef;

Expand Down
3 changes: 3 additions & 0 deletions packages/headless/src/hooks/useKeyboardMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { useSingletonKeyboardMode } from "@/hooks/_internal/useSingletonKeyboardMode";

export default useSingletonKeyboardMode;
3 changes: 3 additions & 0 deletions packages/headless/src/hooks/useRTL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { useSingletonRTL } from "@/hooks/_internal/useSingletonRTL";

export default useSingletonRTL;
15 changes: 15 additions & 0 deletions packages/headless/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Utilities
export { classNames, TrapFocus } from "@reshaped/utilities";

// Components
export { default as Reshaped, type ReshapedProps } from "./components/Reshaped";

// Hooks
export { default as useHotkeys } from "./hooks/useHotkeys";
export { default as useKeyboardMode } from "./hooks/useKeyboardMode";
export { default as useRTL } from "./hooks/useRTL";
export { default as useIsomorphicLayoutEffect } from "./hooks/useIsomorphicLayoutEffect";

// Types
export type { ClassName } from "@reshaped/utilities";
export type { Attributes, CSSVariable, StyleAttribute } from "@/types/global";
25 changes: 25 additions & 0 deletions packages/headless/src/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* 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 { lockScroll, Flyout } from "@reshaped/utilities";

export {
disableScroll,
enableScroll,
rafThrottle,
checkKeyboardMode,
findParent,
getFocusableElements,
focusableSelector,
focusFirstElement,
focusLastElement,
focusNextElement,
focusPreviousElement,
type FocusableElement,
type TrapMode,
type Coordinates,
} from "@reshaped/utilities/internal";
13 changes: 13 additions & 0 deletions packages/headless/src/types/global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type CSSVariable = `--${string}`;
export type StyleAttribute =
| React.CSSProperties
| (React.CSSProperties & Record<CSSVariable, string | number | undefined>);

type DataAttributes = object | Record<`data-${string}`, string | boolean>;

export type Attributes<TagName extends keyof React.JSX.IntrinsicElements | void = void> =
// If tag name is not passed, fallback to HTMLElement attributes
(TagName extends keyof React.JSX.IntrinsicElements
? React.JSX.IntrinsicElements[TagName]
: React.HTMLAttributes<HTMLElement>) &
DataAttributes & { style?: StyleAttribute };
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"outDir": "./dist",
"baseUrl": "./src",
"rootDir": "./src",
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Node"
"moduleResolution": "Bundler",
"paths": { "@/*": ["./*"] }
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

2 changes: 1 addition & 1 deletion packages/reshaped/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
"react-dom": "^18 || ^19"
},
"dependencies": {
"@reshaped/utilities": "workspace:*",
"@reshaped/headless": "workspace:*",
"@csstools/postcss-global-data": "3.1.0",
"chalk": "4.1.2",
"commander": "14.0.2",
Expand Down
5 changes: 3 additions & 2 deletions packages/reshaped/src/components/Accordion/Accordion.types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Attributes, ClassName } from "@reshaped/headless";
import type { IconProps } from "components/Icon";
import type React from "react";
import type * as G from "types/global";
Expand All @@ -14,9 +15,9 @@ export type BaseProps = {
/** Callback when the accordion is expanded or collapsed */
onToggle?: (active: boolean) => void;
/** Additional classname for the root element */
className?: G.ClassName;
className?: ClassName;
/** Additional attributes for the root element */
attributes?: G.Attributes<"div">;
attributes?: Attributes<"div">;
};

export type TriggerProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { classNames } from "@reshaped/utilities";
import { classNames } from "@reshaped/headless";
import React from "react";

import useElementId from "hooks/useElementId";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { classNames } from "@reshaped/utilities";
import { classNames } from "@reshaped/headless";
import React from "react";

import Actionable from "components/Actionable";
Expand Down
2 changes: 1 addition & 1 deletion packages/reshaped/src/components/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { classNames } from "@reshaped/utilities";
import { classNames } from "@reshaped/headless";

import View from "components/View";
import { responsiveVariables } from "utilities/props";
Expand Down
5 changes: 3 additions & 2 deletions packages/reshaped/src/components/ActionBar/ActionBar.types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Attributes, ClassName } from "@reshaped/headless";
import type { ViewProps } from "components/View";
import type React from "react";
import type * as G from "types/global";
Expand All @@ -18,7 +19,7 @@ export type Props = Pick<ViewProps, "paddingBlock" | "paddingInline" | "padding"
/** Node for inserting the content */
children?: React.ReactNode;
/** Additional classname for the root element */
className?: G.ClassName;
className?: ClassName;
/** Additional attributes for the root element */
attributes?: G.Attributes<"div">;
attributes?: Attributes<"div">;
};
5 changes: 3 additions & 2 deletions packages/reshaped/src/components/Alert/Alert.types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Attributes, ClassName } from "@reshaped/headless";
import type { IconProps } from "components/Icon";
import type React from "react";
import type * as G from "types/global";
Expand All @@ -20,7 +21,7 @@ export type Props = {
/** Apply negative margin and remove side borders, base unit token number multiplier */
bleed?: G.Responsive<number>;
/** Additional classname for the root element */
className?: G.ClassName;
className?: ClassName;
/** Additional attributes for the root element */
attributes?: G.Attributes<"div">;
attributes?: Attributes<"div">;
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"use client";

import { useIsomorphicLayoutEffect, useHotkeys } from "@reshaped/headless";
import React from "react";

import DropdownMenu from "components/DropdownMenu";
import TextField from "components/TextField";
import * as keys from "constants/keys";
import useElementId from "hooks/useElementId";
import useHandlerRef from "hooks/useHandlerRef";
import useHotkeys from "hooks/useHotkeys";
import useIsomorphicLayoutEffect from "hooks/useIsomorphicLayoutEffect";

import s from "./Autocomplete.module.css";
import * as T from "./Autocomplete.types";
Expand Down
2 changes: 1 addition & 1 deletion packages/reshaped/src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { classNames } from "@reshaped/utilities";
import { classNames } from "@reshaped/headless";

import Icon from "components/Icon";
import Image, { type ImageProps } from "components/Image";
Expand Down
Loading