reshaped 3.2.5 → 3.2.6-rc.0

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.
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
+ import React from "react";
3
4
  import { classNames } from "../../utilities/helpers.js";
4
5
  import { GlobalColorMode, PrivateTheme } from "../Theme/index.js";
5
6
  import { ToastProvider } from "../Toast/index.js";
7
+ import { useGlobalColorMode } from "../Theme/useTheme.js";
6
8
  import useSingletonKeyboardMode from "../../hooks/_private/useSingletonKeyboardMode.js";
7
9
  import { SingletonEnvironmentContext, useSingletonRTL, } from "../../hooks/_private/useSingletonEnvironment.js";
8
10
  import { SingletonHotkeysProvider } from "../../hooks/_private/useSingletonHotkeys.js";
@@ -17,6 +19,8 @@ const ReshapedInner = (props) => {
17
19
  const Reshaped = (props) => {
18
20
  const { theme, defaultTheme = "reshaped", defaultColorMode, scoped, className } = props;
19
21
  const rootClassNames = classNames(s.root, className);
20
- return (_jsx(GlobalColorMode, { defaultMode: defaultColorMode, children: _jsx(PrivateTheme, { name: theme, defaultName: defaultTheme, className: rootClassNames, scoped: scoped, children: _jsx(ReshapedInner, { ...props, children: props.children }) }) }));
22
+ const scopeRef = React.useRef(null);
23
+ const parentGlobalColorMode = useGlobalColorMode();
24
+ return (_jsx(GlobalColorMode, { defaultMode: defaultColorMode || parentGlobalColorMode.mode || "light", scopeRef: !!parentGlobalColorMode && scoped ? scopeRef : undefined, children: _jsx(PrivateTheme, { name: theme, defaultName: defaultTheme, className: rootClassNames, scoped: scoped, scopeRef: !!parentGlobalColorMode && scoped ? scopeRef : undefined, children: _jsx(ReshapedInner, { ...props, children: props.children }) }) }));
21
25
  };
22
26
  export default Reshaped;
@@ -60,30 +60,21 @@ const TabsList = (props) => {
60
60
  };
61
61
  }, [elScrollableRef]);
62
62
  const { ref: hotkeysRef } = useHotkeys({
63
- "ArrowLeft, ArrowUp": (e) => {
64
- if (name)
65
- return;
66
- e.preventDefault();
63
+ "ArrowLeft, ArrowUp": () => {
67
64
  focusPreviousElement(elScrollableRef.current);
68
65
  },
69
- "ArrowRight, ArrowDown": (e) => {
70
- if (name)
71
- return;
72
- e.preventDefault();
66
+ "ArrowRight, ArrowDown": () => {
73
67
  focusNextElement(elScrollableRef.current);
74
68
  },
75
- Home: (e) => {
76
- if (name)
77
- return;
78
- e.preventDefault();
69
+ Home: () => {
79
70
  focusFirstElement(elScrollableRef.current);
80
71
  },
81
- End: (e) => {
82
- if (name)
83
- return;
84
- e.preventDefault();
72
+ End: () => {
85
73
  focusLastElement(elScrollableRef.current);
86
74
  },
75
+ }, [], {
76
+ preventDefault: true,
77
+ disabled: !!name,
87
78
  });
88
79
  useIsomorphicLayoutEffect(() => {
89
80
  if (value)
@@ -3,13 +3,18 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import useIsomorphicLayoutEffect from "../../hooks/useIsomorphicLayoutEffect.js";
5
5
  import { enableTransitions, disableTransitions, onNextFrame } from "../../utilities/animation.js";
6
+ import { useGlobalColorMode } from "./useTheme.js";
6
7
  import { GlobalColorModeContext } from "./Theme.context.js";
7
8
  import { getRootThemeEl } from "./Theme.utilities.js";
8
9
  const GlobalColorMode = (props) => {
9
- const { defaultMode, children } = props;
10
- const [mode, setMode] = React.useState(defaultMode || "light");
10
+ const { defaultMode, scopeRef, children } = props;
11
+ const [mode, setMode] = React.useState(defaultMode);
12
+ const parentGlobalColorMode = useGlobalColorMode();
11
13
  const changeColorMode = React.useCallback((targetMode) => {
12
- getRootThemeEl().setAttribute("data-rs-color-mode", targetMode);
14
+ getRootThemeEl(scopeRef?.current).setAttribute("data-rs-color-mode", targetMode);
15
+ if (parentGlobalColorMode.mode && !scopeRef) {
16
+ parentGlobalColorMode.setMode(targetMode);
17
+ }
13
18
  setMode((prevMode) => {
14
19
  if (prevMode !== targetMode) {
15
20
  // Avoid components styles animating when switching to another color mode
@@ -17,7 +22,7 @@ const GlobalColorMode = (props) => {
17
22
  }
18
23
  return targetMode;
19
24
  });
20
- }, []);
25
+ }, [scopeRef, parentGlobalColorMode]);
21
26
  useIsomorphicLayoutEffect(() => {
22
27
  onNextFrame(() => {
23
28
  enableTransitions();
@@ -28,10 +33,10 @@ const GlobalColorMode = (props) => {
28
33
  * This could happen if we're receiving the mode on the client but before React hydration
29
34
  */
30
35
  useIsomorphicLayoutEffect(() => {
31
- const nextColorMode = getRootThemeEl().getAttribute("data-rs-color-mode");
36
+ const nextColorMode = getRootThemeEl(scopeRef?.current).getAttribute("data-rs-color-mode");
32
37
  if (nextColorMode)
33
38
  changeColorMode(nextColorMode);
34
- }, []);
39
+ }, [changeColorMode, scopeRef]);
35
40
  const value = React.useMemo(() => ({
36
41
  mode,
37
42
  setMode: changeColorMode,
@@ -1,16 +1,6 @@
1
1
  "use client";
2
2
  import React from "react";
3
3
  /* Context used to store data responsible for switching between modes of a theme */
4
- export const ThemeContext = React.createContext({
5
- theme: "",
6
- rootTheme: "",
7
- colorMode: "light",
8
- setTheme: () => { },
9
- setRootTheme: () => { },
10
- });
4
+ export const ThemeContext = React.createContext({});
11
5
  /* Context used to globally define mode, used only within the library */
12
- export const GlobalColorModeContext = React.createContext({
13
- mode: "light",
14
- setMode: () => { },
15
- invertMode: () => { },
16
- });
6
+ export const GlobalColorModeContext = React.createContext({});
@@ -9,15 +9,15 @@ import { useTheme, useGlobalColorMode } from "./useTheme.js";
9
9
  import s from "./Theme.module.css";
10
10
  const Theme = (props) => _jsx(PrivateTheme, { ...props });
11
11
  export const PrivateTheme = (props) => {
12
- const { name, defaultName, colorMode, scoped, children, className } = props;
12
+ const { name, defaultName, colorMode, scoped, scopeRef, children, className } = props;
13
13
  const [mounted, setMounted] = React.useState(false);
14
14
  const [stateTheme, setStateTheme] = React.useState(defaultName);
15
15
  const globalColorMode = useGlobalColorMode();
16
16
  const parentTheme = useTheme();
17
17
  const isRootProvider = !parentTheme.theme;
18
18
  const usedTheme = name || stateTheme || parentTheme.theme;
19
- const rootTheme = isRootProvider ? usedTheme : parentTheme.rootTheme;
20
- const parentColorMode = isRootProvider ? globalColorMode : parentTheme.colorMode;
19
+ const rootTheme = isRootProvider || scoped ? usedTheme : parentTheme.rootTheme;
20
+ const parentColorMode = isRootProvider || scoped ? globalColorMode.mode : parentTheme.colorMode;
21
21
  const invertedColorMode = parentColorMode === "light" ? "dark" : "light";
22
22
  const usedColorMode = colorMode === "inverted" ? invertedColorMode : colorMode || parentColorMode;
23
23
  const rootClassNames = classNames(s.root, className);
@@ -38,7 +38,7 @@ export const PrivateTheme = (props) => {
38
38
  useIsomorphicLayoutEffect(() => {
39
39
  if (!document || !isRootProvider)
40
40
  return;
41
- const themeRootEl = getRootThemeEl();
41
+ const themeRootEl = getRootThemeEl(scopeRef?.current);
42
42
  const hasColorModeApplied = themeRootEl.getAttribute("data-rs-color-mode");
43
43
  themeRootEl.setAttribute("data-rs-theme", usedTheme);
44
44
  if (!hasColorModeApplied)
@@ -48,7 +48,7 @@ export const PrivateTheme = (props) => {
48
48
  if (!hasColorModeApplied)
49
49
  themeRootEl.removeAttribute("data-rs-color-mode");
50
50
  };
51
- }, [usedTheme, usedColorMode, isRootProvider]);
51
+ }, [usedTheme, usedColorMode, isRootProvider, scopeRef]);
52
52
  const value = React.useMemo(() => ({
53
53
  theme: usedTheme,
54
54
  rootTheme,
@@ -56,6 +56,6 @@ export const PrivateTheme = (props) => {
56
56
  setTheme,
57
57
  setRootTheme,
58
58
  }), [usedTheme, usedColorMode, setTheme, setRootTheme, rootTheme]);
59
- return (_jsx(ThemeContext.Provider, { value: value, children: _jsx("div", { className: rootClassNames, "data-rs-root": scoped ? true : undefined, "data-rs-theme": isRootProvider ? undefined : usedTheme, "data-rs-color-mode": isRootProvider || (!colorMode && !mounted) ? undefined : usedColorMode, children: children }) }));
59
+ return (_jsx(ThemeContext.Provider, { value: value, children: _jsx("div", { className: rootClassNames, ref: scopeRef, "data-rs-root": scoped ? true : undefined, "data-rs-theme": isRootProvider ? undefined : usedTheme, "data-rs-color-mode": isRootProvider || (!colorMode && !mounted) ? undefined : usedColorMode, children: children }) }));
60
60
  };
61
61
  export default Theme;
@@ -22,9 +22,10 @@ export type Props = {
22
22
  };
23
23
  export type PrivateProps = Props & {
24
24
  scoped?: boolean;
25
+ scopeRef?: React.RefObject<HTMLDivElement>;
25
26
  };
26
27
  export type GlobalColorModeProps = {
27
- defaultMode?: ColorMode;
28
- scoped?: boolean;
28
+ defaultMode: ColorMode;
29
+ scopeRef?: React.RefObject<HTMLDivElement>;
29
30
  children?: React.ReactNode;
30
31
  };
@@ -1 +1 @@
1
- export declare const getRootThemeEl: () => Element;
1
+ export declare const getRootThemeEl: (scopeEl?: HTMLElement | null) => HTMLElement;
@@ -1 +1,10 @@
1
- export const getRootThemeEl = () => document.querySelector("[data-rs-root]") || document.documentElement;
1
+ export const getRootThemeEl = (scopeEl) => {
2
+ if (!scopeEl)
3
+ return document.documentElement;
4
+ if (scopeEl.hasAttribute("data-rs-root") ||
5
+ scopeEl === document.documentElement ||
6
+ !scopeEl.parentElement) {
7
+ return scopeEl;
8
+ }
9
+ return getRootThemeEl(scopeEl.parentElement);
10
+ };
@@ -9,4 +9,5 @@ declare const _default: {
9
9
  };
10
10
  export default _default;
11
11
  export declare const uncontrolled: () => import("react").JSX.Element;
12
+ export declare const nestedReshaped: () => import("react").JSX.Element;
12
13
  export declare const edgeCases: () => import("react").JSX.Element;
@@ -4,6 +4,7 @@ import View from "../../View/index.js";
4
4
  import MenuItem from "../../MenuItem/index.js";
5
5
  import Theme, { useTheme } from "../index.js";
6
6
  import { Example } from "../../../utilities/storybook/index.js";
7
+ import Reshaped from "../../Reshaped/index.js";
7
8
  export default {
8
9
  title: "Utilities/Theme",
9
10
  component: Theme,
@@ -15,7 +16,7 @@ export default {
15
16
  };
16
17
  const UncontrolledDemo = () => {
17
18
  const { setTheme, theme } = useTheme();
18
- return (<Button color="primary" onClick={() => setTheme(theme === "reshaped" ? "fake" : "reshaped")}>
19
+ return (<Button color="primary" onClick={() => setTheme(theme === "reshaped" ? "slate" : "reshaped")}>
19
20
  Toggle theme
20
21
  </Button>);
21
22
  };
@@ -26,6 +27,38 @@ export const uncontrolled = () => {
26
27
  </Example.Item>
27
28
  </Example>);
28
29
  };
30
+ const Nested = () => {
31
+ const { invertColorMode, colorMode } = useTheme();
32
+ return (<Button color="primary" variant="faded" onClick={invertColorMode}>
33
+ Slate: {colorMode}
34
+ </Button>);
35
+ };
36
+ export const nestedReshaped = () => {
37
+ const { invertColorMode, colorMode } = useTheme();
38
+ return (<Example>
39
+ <Example.Item title="reshaped + scoped slate">
40
+ <View gap={2} direction="row">
41
+ <Button color="primary" variant="faded" onClick={invertColorMode}>
42
+ Reshaped: {colorMode}
43
+ </Button>
44
+ <Reshaped theme="slate" scoped>
45
+ <Nested />
46
+ </Reshaped>
47
+ </View>
48
+ </Example.Item>
49
+
50
+ <Example.Item title="reshaped + not scoped slate">
51
+ <View gap={2} direction="row">
52
+ <Button color="primary" variant="faded" onClick={invertColorMode}>
53
+ Reshaped: {colorMode}
54
+ </Button>
55
+ <Reshaped theme="slate">
56
+ <Nested />
57
+ </Reshaped>
58
+ </View>
59
+ </Example.Item>
60
+ </Example>);
61
+ };
29
62
  const Demo = () => {
30
63
  const { invertColorMode } = useTheme();
31
64
  return (<View gap={3}>
@@ -1,4 +1,4 @@
1
- export declare const useGlobalColorMode: () => import("./Theme.types").ColorMode;
1
+ export declare const useGlobalColorMode: () => import("./Theme.types").GlobalColorModeContextData;
2
2
  export declare const useTheme: () => {
3
3
  theme: string;
4
4
  setTheme: (theme: string) => void;
@@ -2,8 +2,7 @@
2
2
  import React from "react";
3
3
  import { ThemeContext, GlobalColorModeContext } from "./Theme.context.js";
4
4
  export const useGlobalColorMode = () => {
5
- const { mode } = React.useContext(GlobalColorModeContext);
6
- return mode;
5
+ return React.useContext(GlobalColorModeContext);
7
6
  };
8
7
  export const useTheme = () => {
9
8
  const { colorMode, theme, setTheme, rootTheme, setRootTheme } = React.useContext(ThemeContext);
@@ -2,9 +2,9 @@ import React from "react";
2
2
  /**
3
3
  * Types
4
4
  */
5
- type Callback = (e: KeyboardEvent) => void;
6
- type PressedMap = Record<string, KeyboardEvent>;
7
- type Hotkeys = Record<string, Callback | null>;
5
+ type Callback = (e?: KeyboardEvent) => void;
6
+ type PressedMap = Map<string, KeyboardEvent>;
7
+ export type Hotkeys = Record<string, Callback | null>;
8
8
  type HotkeyOptions = {
9
9
  preventDefault?: boolean;
10
10
  };
@@ -4,7 +4,7 @@ import React from "react";
4
4
  * Utilities
5
5
  */
6
6
  const COMBINATION_DELIMETER = "+";
7
- const pressedMap = {};
7
+ const pressedMap = new Map();
8
8
  let modifiedKeys = [];
9
9
  const formatHotkey = (hotkey) => {
10
10
  if (hotkey === " ")
@@ -62,9 +62,9 @@ export class HotkeyStore {
62
62
  });
63
63
  };
64
64
  handleKeyDown = (pressedMap, e) => {
65
- const pressedKeys = Object.keys(pressedMap);
66
- if (!pressedKeys.length)
65
+ if (!pressedMap.size)
67
66
  return;
67
+ const pressedKeys = [...pressedMap.keys()];
68
68
  const pressedId = getHotkeyId(pressedKeys.join(COMBINATION_DELIMETER));
69
69
  const pressedFormattedKeys = pressedId.split(COMBINATION_DELIMETER);
70
70
  const hotkeyData = this.hotkeyMap[pressedId];
@@ -86,12 +86,12 @@ export class HotkeyStore {
86
86
  !(eventTarget === data.ref.current || data.ref.current.contains(eventTarget))) {
87
87
  return;
88
88
  }
89
- const resolvedEvent = pressedMap[pressedId];
89
+ const resolvedEvent = pressedMap.get(pressedId);
90
90
  if (data.options.preventDefault) {
91
91
  resolvedEvent?.preventDefault();
92
92
  e.preventDefault();
93
93
  }
94
- data.callback(resolvedEvent);
94
+ data.callback(e);
95
95
  });
96
96
  }
97
97
  });
@@ -114,13 +114,13 @@ export const SingletonHotkeysProvider = (props) => {
114
114
  const eventKey = getEventKey(e);
115
115
  if (!eventKey)
116
116
  return;
117
- pressedMap[eventKey] = e;
118
- setTriggerCount(Object.keys(pressedMap).length);
117
+ pressedMap.set(eventKey, e);
118
+ setTriggerCount(pressedMap.size);
119
119
  // Key up won't trigger for other keys while Meta is pressed so we need to cache them
120
120
  // and remove on Meta keyup
121
121
  if (e.metaKey)
122
- modifiedKeys.push(...Object.keys(pressedMap));
123
- if (pressedMap.Meta)
122
+ modifiedKeys.push(...pressedMap.keys());
123
+ if (pressedMap.has("Meta"))
124
124
  modifiedKeys.push(eventKey);
125
125
  }, [hooksCount]);
126
126
  const removePressedKey = React.useCallback((e) => {
@@ -129,23 +129,23 @@ export const SingletonHotkeysProvider = (props) => {
129
129
  const eventKey = getEventKey(e);
130
130
  if (!eventKey)
131
131
  return;
132
- delete pressedMap[eventKey];
132
+ pressedMap.delete(eventKey);
133
133
  if (eventKey === "meta" || eventKey === "control") {
134
- delete pressedMap.mod;
134
+ pressedMap.delete("mod");
135
135
  }
136
136
  if (eventKey === "meta") {
137
137
  modifiedKeys.forEach((key) => {
138
- if (!pressedMap[key])
138
+ if (!pressedMap.has(key))
139
139
  return;
140
- delete pressedMap[key];
140
+ pressedMap.delete(key);
141
141
  });
142
142
  modifiedKeys = [];
143
143
  }
144
- setTriggerCount(Object.keys(pressedMap).length);
144
+ setTriggerCount(pressedMap.size);
145
145
  }, [hooksCount]);
146
146
  const isPressed = (hotkey) => {
147
147
  const keys = formatHotkey(hotkey).split(COMBINATION_DELIMETER);
148
- if (keys.some((key) => !pressedMap[key]))
148
+ if (keys.some((key) => !pressedMap.has(key)))
149
149
  return false;
150
150
  return true;
151
151
  };
@@ -161,6 +161,10 @@ export const SingletonHotkeysProvider = (props) => {
161
161
  return;
162
162
  removePressedKey(e);
163
163
  }, [removePressedKey]);
164
+ const handleWindowBlur = React.useCallback(() => {
165
+ pressedMap.clear();
166
+ modifiedKeys = [];
167
+ }, []);
164
168
  const addHotkeys = React.useCallback((hotkeys, ref, options = {}) => {
165
169
  setHooksCount((prev) => prev + 1);
166
170
  globalHotkeyStore.bindHotkeys(hotkeys, ref, options);
@@ -172,11 +176,13 @@ export const SingletonHotkeysProvider = (props) => {
172
176
  React.useEffect(() => {
173
177
  window.addEventListener("keydown", handleWindowKeyDown);
174
178
  window.addEventListener("keyup", handleWindowKeyUp);
179
+ window.addEventListener("blur", handleWindowBlur);
175
180
  return () => {
176
181
  window.removeEventListener("keydown", handleWindowKeyDown);
177
182
  window.removeEventListener("keyup", handleWindowKeyUp);
183
+ window.removeEventListener("blur", handleWindowBlur);
178
184
  };
179
- }, [handleWindowKeyDown, handleWindowKeyUp]);
185
+ }, [handleWindowKeyDown, handleWindowKeyUp, handleWindowBlur]);
180
186
  return (_jsx(HotkeyContext.Provider, { value: { addHotkeys, isPressed }, children: children }));
181
187
  };
182
188
  const useSingletonHotkeys = () => React.useContext(HotkeyContext);
@@ -4,8 +4,11 @@ export default { title: "Hooks/useHotkeys" };
4
4
  function Example() {
5
5
  const { checkHotkeyState } = useHotkeys({
6
6
  "shift + b + n": () => console.log("pressed"),
7
- "c + v": () => console.log(111),
8
- "Meta + v": () => console.log(222),
7
+ "c + v": () => console.log("c + v"),
8
+ "Meta + k": () => console.log("meta + k"),
9
+ "Meta + f": () => console.log("meta + f"),
10
+ "Meta + v": () => console.log("meta + v"),
11
+ "Meta + b": () => console.log("meta + b"),
9
12
  "control + enter": () => console.log("control + enter"),
10
13
  "meta + enter": () => console.log("meta + enter"),
11
14
  "mod + enter": () => console.log("mod + enter"),
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
- declare const useHotkeys: <Element extends HTMLElement>(hotkeys: Record<string, ((e: KeyboardEvent) => void) | null>, deps?: unknown[], options?: {
2
+ import { type Hotkeys } from "./_private/useSingletonHotkeys";
3
+ declare const useHotkeys: <Element extends HTMLElement>(hotkeys: Hotkeys, deps?: unknown[], options?: {
3
4
  ref?: React.RefObject<Element>;
4
5
  disabled?: boolean;
5
6
  preventDefault?: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reshaped",
3
3
  "description": "Professionally crafted design system in React & Figma for building products of any scale and complexity",
4
- "version": "3.2.5",
4
+ "version": "3.2.6-rc.0",
5
5
  "license": "MIT",
6
6
  "email": "hello@reshaped.so",
7
7
  "homepage": "https://reshaped.so",