@react-fabric/core 1.0.0-beta.3 → 1.0.0-beta.30

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.
Files changed (207) hide show
  1. package/css/index.js +14 -14
  2. package/css/reset/_reset.css +1 -1
  3. package/dist/cjs/components/actionLabel/ActionLabel.js +1 -0
  4. package/dist/cjs/components/animations/Animations.js +14 -7
  5. package/dist/cjs/components/avatar/Avatar.js +8 -10
  6. package/dist/cjs/components/avatar/AvatarGroup.js +1 -0
  7. package/dist/cjs/components/badge/Badge.js +5 -4
  8. package/dist/cjs/components/breadcrumbs/Breadcrumbs.js +5 -2
  9. package/dist/cjs/components/button/Button.js +5 -4
  10. package/dist/cjs/components/button/Button.module.css +1 -1
  11. package/dist/cjs/components/button/ButtonGroup.js +3 -1
  12. package/dist/cjs/components/button/ConfirmButton.js +7 -2
  13. package/dist/cjs/components/button/ToggleButtonGroup.js +2 -1
  14. package/dist/cjs/components/callout/Callout.js +7 -2
  15. package/dist/cjs/components/card/Card.js +5 -3
  16. package/dist/cjs/components/card/CardCover.js +1 -1
  17. package/dist/cjs/components/chip/Chip.js +9 -4
  18. package/dist/cjs/components/chip/Chip.module.css +1 -1
  19. package/dist/cjs/components/collapsable/Collapsable.js +3 -2
  20. package/dist/cjs/components/divider/Divider.js +4 -3
  21. package/dist/cjs/components/divider/Divider.module.css +1 -0
  22. package/dist/cjs/components/dropdown/Dropdown.js +4 -2
  23. package/dist/cjs/components/dropdown/DropdownTool.js +4 -3
  24. package/dist/cjs/components/icon/Icon.js +14 -12
  25. package/dist/cjs/components/icon/Icon.module.css +1 -1
  26. package/dist/cjs/components/menu/ContextMenu.js +30 -10
  27. package/dist/cjs/components/menu/Menu.js +39 -20
  28. package/dist/cjs/components/menu/MenuItem.js +29 -14
  29. package/dist/cjs/components/navigator/Navigator.js +16 -9
  30. package/dist/cjs/components/panel/Panel.js +3 -1
  31. package/dist/cjs/components/progress/ProgressBar.js +2 -1
  32. package/dist/cjs/components/progress/ProgressCircle.js +2 -1
  33. package/dist/cjs/components/tabpanel/Tab.js +55 -36
  34. package/dist/cjs/components/tabpanel/TabPanel.js +19 -14
  35. package/dist/cjs/components/tabpanel/TabPanel.module.css +1 -1
  36. package/dist/cjs/core/aside/Aside.js +6 -2
  37. package/dist/cjs/core/content/Content.js +8 -5
  38. package/dist/cjs/core/headfoot/HeadFoot.js +3 -2
  39. package/dist/cjs/core/headfoot/HeadFoot.module.css +1 -1
  40. package/dist/cjs/core/page/Page.js +1 -0
  41. package/dist/cjs/core/responsive/Responsive.js +1 -1
  42. package/dist/cjs/core/section/Section.module.css +1 -1
  43. package/dist/cjs/hooks/useNavigator.js +6 -4
  44. package/dist/cjs/index.js +7 -2
  45. package/dist/cjs/overlays/alert/Alert.js +42 -17
  46. package/dist/cjs/overlays/alert/Alert.module.css +1 -1
  47. package/dist/cjs/overlays/flyout/Flyout.js +40 -42
  48. package/dist/cjs/overlays/message/Message.js +1 -1
  49. package/dist/cjs/overlays/modal/Modal.js +4 -5
  50. package/dist/cjs/overlays/toast/Toast.js +1 -1
  51. package/dist/cjs/overlays/tooltip/Tooltip.js +3 -3
  52. package/dist/cjs/overlays/tooltip/Tooltip.module.css +1 -1
  53. package/dist/cjs/types/icons.js +3 -0
  54. package/dist/cjs/typography/Abbr.js +1 -1
  55. package/dist/cjs/typography/{Link.js → Anchor.js} +4 -3
  56. package/dist/cjs/typography/Mark.js +1 -1
  57. package/dist/cjs/typography/Text.js +2 -2
  58. package/dist/cjs/typography/Title.js +3 -2
  59. package/dist/cjs/typography/Typography.module.css +1 -1
  60. package/dist/cjs/utils/cloneElement.js +44 -0
  61. package/dist/esm/components/actionLabel/ActionLabel.js +1 -0
  62. package/dist/esm/components/animations/Animations.js +14 -7
  63. package/dist/esm/components/avatar/Avatar.js +9 -11
  64. package/dist/esm/components/avatar/AvatarGroup.js +1 -0
  65. package/dist/esm/components/badge/Badge.js +5 -4
  66. package/dist/esm/components/breadcrumbs/Breadcrumbs.js +5 -2
  67. package/dist/esm/components/button/Button.js +5 -4
  68. package/dist/esm/components/button/Button.module.css +1 -1
  69. package/dist/esm/components/button/ButtonGroup.js +4 -2
  70. package/dist/esm/components/button/ConfirmButton.js +7 -2
  71. package/dist/esm/components/button/ToggleButtonGroup.js +3 -2
  72. package/dist/esm/components/callout/Callout.js +7 -2
  73. package/dist/esm/components/card/Card.js +5 -3
  74. package/dist/esm/components/card/CardCover.js +1 -1
  75. package/dist/esm/components/chip/Chip.js +9 -4
  76. package/dist/esm/components/chip/Chip.module.css +1 -1
  77. package/dist/esm/components/collapsable/Collapsable.js +3 -2
  78. package/dist/esm/components/divider/Divider.js +4 -3
  79. package/dist/esm/components/divider/Divider.module.css +1 -0
  80. package/dist/esm/components/dropdown/Dropdown.js +4 -2
  81. package/dist/esm/components/dropdown/DropdownTool.js +4 -3
  82. package/dist/esm/components/icon/Icon.js +14 -12
  83. package/dist/esm/components/icon/Icon.module.css +1 -1
  84. package/dist/esm/components/menu/ContextMenu.js +32 -12
  85. package/dist/esm/components/menu/Menu.js +41 -22
  86. package/dist/esm/components/menu/MenuItem.js +23 -8
  87. package/dist/esm/components/navigator/Navigator.js +16 -9
  88. package/dist/esm/components/panel/Panel.js +3 -1
  89. package/dist/esm/components/progress/ProgressBar.js +2 -1
  90. package/dist/esm/components/progress/ProgressCircle.js +2 -1
  91. package/dist/esm/components/tabpanel/Tab.js +56 -37
  92. package/dist/esm/components/tabpanel/TabPanel.js +19 -14
  93. package/dist/esm/components/tabpanel/TabPanel.module.css +1 -1
  94. package/dist/esm/core/aside/Aside.js +6 -2
  95. package/dist/esm/core/content/Content.js +8 -5
  96. package/dist/esm/core/headfoot/HeadFoot.js +3 -2
  97. package/dist/esm/core/headfoot/HeadFoot.module.css +1 -1
  98. package/dist/esm/core/page/Page.js +1 -0
  99. package/dist/esm/core/responsive/Responsive.js +1 -1
  100. package/dist/esm/core/section/Section.module.css +1 -1
  101. package/dist/esm/hooks/useNavigator.js +7 -5
  102. package/dist/esm/index.js +4 -1
  103. package/dist/esm/overlays/alert/Alert.js +31 -6
  104. package/dist/esm/overlays/alert/Alert.module.css +1 -1
  105. package/dist/esm/overlays/flyout/Flyout.js +41 -43
  106. package/dist/esm/overlays/message/Message.js +1 -1
  107. package/dist/esm/overlays/modal/Modal.js +5 -6
  108. package/dist/esm/overlays/toast/Toast.js +1 -1
  109. package/dist/esm/overlays/tooltip/Tooltip.js +3 -3
  110. package/dist/esm/overlays/tooltip/Tooltip.module.css +1 -1
  111. package/dist/esm/types/icons.js +3 -0
  112. package/dist/esm/typography/Abbr.js +1 -1
  113. package/dist/esm/typography/{Link.js → Anchor.js} +4 -3
  114. package/dist/esm/typography/Mark.js +1 -1
  115. package/dist/esm/typography/Text.js +2 -2
  116. package/dist/esm/typography/Title.js +3 -2
  117. package/dist/esm/typography/Typography.module.css +1 -1
  118. package/dist/esm/utils/cloneElement.js +44 -0
  119. package/dist/types/components/actionLabel/ActionLabel.d.ts +2 -2
  120. package/dist/types/components/animations/Animations.d.ts +1 -1
  121. package/dist/types/components/badge/Badge.d.ts +5 -1
  122. package/dist/types/components/breadcrumbs/Breadcrumbs.d.ts +2 -2
  123. package/dist/types/components/button/Button.d.ts +5 -3
  124. package/dist/types/components/button/ConfirmButton.d.ts +6 -1
  125. package/dist/types/components/card/Card.d.ts +3 -3
  126. package/dist/types/components/chip/Chip.d.ts +1 -1
  127. package/dist/types/components/collapsable/Collapsable.d.ts +5 -1
  128. package/dist/types/components/dropdown/DropdownTool.d.ts +2 -1
  129. package/dist/types/components/media/Media.d.ts +2 -2
  130. package/dist/types/components/menu/ContextMenu.d.ts +2 -1
  131. package/dist/types/components/menu/MenuItem.d.ts +1 -1
  132. package/dist/types/components/navigator/Navigator.d.ts +3 -3
  133. package/dist/types/components/tabpanel/Tab.d.ts +4 -4
  134. package/dist/types/components/tabpanel/TabPanel.d.ts +10 -1
  135. package/dist/types/core/aside/Aside.d.ts +7 -3
  136. package/dist/types/core/content/Content.d.ts +3 -2
  137. package/dist/types/core/responsive/Responsive.d.ts +1 -1
  138. package/dist/types/core/theme/ThemeProvider.d.ts +1 -1
  139. package/dist/types/hooks/useOverlayService.d.ts +3 -3
  140. package/dist/types/index.d.ts +3 -0
  141. package/dist/types/overlays/alert/Alert.d.ts +9 -1
  142. package/dist/types/overlays/flyout/Flyout.d.ts +3 -2
  143. package/dist/types/overlays/modal/Modal.d.ts +4 -3
  144. package/dist/types/types/icons.d.ts +3 -0
  145. package/dist/types/types/index.d.ts +8 -1
  146. package/dist/types/typography/{Link.d.ts → Anchor.d.ts} +2 -2
  147. package/dist/types/typography/index.d.ts +1 -1
  148. package/dist/types/utils/cloneElement.d.ts +2 -0
  149. package/dist/types/utils/index.d.ts +1 -0
  150. package/package.json +5 -5
  151. package/src/components/actionLabel/ActionLabel.tsx +13 -3
  152. package/src/components/animations/Animations.tsx +9 -3
  153. package/src/components/avatar/Avatar.tsx +69 -81
  154. package/src/components/avatar/AvatarGroup.tsx +1 -0
  155. package/src/components/badge/Badge.tsx +9 -4
  156. package/src/components/breadcrumbs/Breadcrumbs.tsx +9 -4
  157. package/src/components/button/Button.module.css +8 -1
  158. package/src/components/button/Button.tsx +12 -9
  159. package/src/components/button/ButtonGroup.tsx +3 -2
  160. package/src/components/button/ConfirmButton.tsx +15 -5
  161. package/src/components/button/ToggleButtonGroup.tsx +3 -2
  162. package/src/components/callout/Callout.tsx +12 -3
  163. package/src/components/card/Card.tsx +4 -0
  164. package/src/components/chip/Chip.module.css +4 -1
  165. package/src/components/chip/Chip.tsx +9 -4
  166. package/src/components/collapsable/Collapsable.tsx +6 -0
  167. package/src/components/divider/Divider.module.css +32 -0
  168. package/src/components/divider/Divider.tsx +3 -2
  169. package/src/components/dropdown/Dropdown.tsx +3 -2
  170. package/src/components/dropdown/DropdownTool.tsx +4 -2
  171. package/src/components/icon/Icon.module.css +0 -1
  172. package/src/components/icon/Icon.tsx +48 -58
  173. package/src/components/media/Media.tsx +2 -2
  174. package/src/components/menu/ContextMenu.tsx +36 -11
  175. package/src/components/menu/Menu.tsx +51 -30
  176. package/src/components/menu/MenuItem.tsx +14 -3
  177. package/src/components/navigator/Navigator.tsx +17 -4
  178. package/src/components/panel/Panel.tsx +3 -1
  179. package/src/components/tabpanel/Tab.tsx +74 -59
  180. package/src/components/tabpanel/TabPanel.module.css +19 -30
  181. package/src/components/tabpanel/TabPanel.tsx +33 -10
  182. package/src/core/aside/Aside.tsx +17 -3
  183. package/src/core/content/Content.tsx +16 -10
  184. package/src/core/headfoot/HeadFoot.module.css +2 -0
  185. package/src/core/headfoot/HeadFoot.tsx +1 -1
  186. package/src/core/page/Page.tsx +1 -0
  187. package/src/core/responsive/Responsive.tsx +3 -2
  188. package/src/core/section/Section.module.css +1 -1
  189. package/src/core/theme/ThemeProvider.tsx +1 -1
  190. package/src/hooks/useNavigator.tsx +10 -5
  191. package/src/hooks/useOverlayService.tsx +12 -4
  192. package/src/index.ts +4 -0
  193. package/src/overlays/alert/Alert.module.css +1 -1
  194. package/src/overlays/alert/Alert.tsx +30 -4
  195. package/src/overlays/flyout/Flyout.tsx +63 -62
  196. package/src/overlays/modal/Modal.tsx +13 -8
  197. package/src/overlays/tooltip/Tooltip.module.css +7 -7
  198. package/src/overlays/tooltip/Tooltip.tsx +4 -4
  199. package/src/types/icons.ts +6 -0
  200. package/src/types/index.ts +10 -9
  201. package/src/typography/{Link.tsx → Anchor.tsx} +3 -3
  202. package/src/typography/Text.tsx +1 -1
  203. package/src/typography/Title.tsx +0 -1
  204. package/src/typography/Typography.module.css +4 -2
  205. package/src/typography/index.ts +1 -1
  206. package/src/utils/cloneElement.ts +44 -0
  207. package/src/utils/index.ts +1 -0
@@ -30,6 +30,7 @@ import {
30
30
  type PaletteType,
31
31
  } from "../../types";
32
32
  import { getBgClass, getBorderClass, getColorClass } from "../../utils";
33
+ import classes from "./Divider.module.css";
33
34
 
34
35
  export interface DividerProps extends CssProp, Partial<ChildProp> {
35
36
  /**
@@ -67,7 +68,7 @@ export const Divider = ({
67
68
  className,
68
69
  vertical && "vertical",
69
70
  !flex && "self-stretch",
70
- vertical ? "mx-1" : "my-1",
71
+ vertical ? classes.vertical : classes.horizontal,
71
72
  color && getBorderClass(color + "-200"),
72
73
  )}
73
74
  />
@@ -88,7 +89,7 @@ export const Divider = ({
88
89
  <div
89
90
  className={classNames(
90
91
  "px-2 whitespace-nowrap",
91
- vertical ? "mx-1" : "my-1",
92
+ vertical ? classes.vertical : classes.horizontal,
92
93
  fill && getBgClass((color ?? "tint") + "-200"),
93
94
  !fill && getColorClass((color ?? "tint") + "-600"),
94
95
  "rounded-full text-[0.75em]",
@@ -224,6 +224,7 @@ const DropdownElement = ({
224
224
  e.stopPropagation();
225
225
  },
226
226
  }),
227
+ "data-inner-clickable": "true",
227
228
  "data-dropdown-open": isOpen ? true : undefined,
228
229
  ref: innerRef,
229
230
  })}
@@ -248,6 +249,7 @@ const DropdownElement = ({
248
249
  zIndex: "var(--z-popover)",
249
250
  ...floatingStyles,
250
251
  }}
252
+ data-ref="dropdownBody"
251
253
  {...getFloatingProps({
252
254
  onClick: (e) => e.stopPropagation(),
253
255
  })}
@@ -257,8 +259,7 @@ const DropdownElement = ({
257
259
  className={classNames(
258
260
  classes.dropdownBody,
259
261
  dropdownClassName,
260
- !plainDropdown &&
261
- "shadow-lg shadow-base bg-base ring-1 ring-tint-100",
262
+ !plainDropdown && "shadow-lg bg-base ring-1 ring-tint-100",
262
263
  "rounded-capped overflow-auto scroll-thin grid max-h-[70vh]",
263
264
  )}
264
265
  onMouseUpCapture={tryClosing}
@@ -31,10 +31,12 @@ export const DropdownTool = ({
31
31
  children,
32
32
  className,
33
33
  groupHover,
34
+ icon,
34
35
  ...props
35
36
  }: ChildProp &
36
37
  CssProp &
37
38
  Omit<DropdownProps, "children"> & {
39
+ icon?: string;
38
40
  /**
39
41
  * hide until group hovered
40
42
  */
@@ -43,12 +45,12 @@ export const DropdownTool = ({
43
45
  return (
44
46
  <Dropdown {...props}>
45
47
  <Icon
46
- icon={CoreIcons.caretDown}
48
+ icon={icon ?? CoreIcons.caretDown}
47
49
  className={classNames(
48
50
  className,
49
51
  "outline bg-base flex-content pointer-events-auto cursor-pointer",
50
52
  groupHover &&
51
- "hidden group-hover:inline-block data-[dropdown-open]:inline-block",
53
+ "hidden group-hover/tool:inline-block data-[dropdown-open]:inline-block",
52
54
  )}
53
55
  />
54
56
  {children}
@@ -42,7 +42,6 @@
42
42
  line-height: 1;
43
43
 
44
44
  object-fit: cover;
45
- border-radius: inherit;
46
45
 
47
46
  fill: currentcolor;
48
47
  }
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { isString, isSvgPath } from "@react-fabric/utilities";
25
25
  import classNames from "classnames";
26
- import { useMemoDebugger } from "../../hooks/useEffectDebugger";
26
+ import { useMemo } from "react";
27
27
  import {
28
28
  type AriaProps,
29
29
  type ColorType,
@@ -101,71 +101,60 @@ export const Icon = ({
101
101
  ...rest
102
102
  }: IconProps) => {
103
103
  /** ***************** style map *******************/
104
- const styles = useMemoDebugger(
105
- () => {
106
- const s: KeyValue = {};
107
- if (bg) {
108
- s.backgroundColor = getColor(bg);
109
- }
110
- if (color) {
111
- s.color = getColor(color);
112
- }
113
- if (size && !(size in SizeMap)) {
114
- s.fontSize = size;
115
- }
116
- if (size && size in SizeMap) {
117
- s.fontSize = SizeMap[size];
118
- }
119
- return s;
120
- },
121
- [bg, color, size],
122
- "Icon styles",
123
- );
104
+ const styles = useMemo(() => {
105
+ const s: KeyValue = {};
106
+ if (bg) {
107
+ s.backgroundColor = getColor(bg);
108
+ }
109
+ if (color) {
110
+ s.color = getColor(color);
111
+ }
112
+ if (size && !(size in SizeMap)) {
113
+ s.fontSize = size;
114
+ }
115
+ if (size && size in SizeMap) {
116
+ s.fontSize = SizeMap[size];
117
+ }
118
+ return s;
119
+ }, [bg, color, size]);
124
120
 
125
121
  /** ***************** check if icon is svg path *******************/
126
- const isSvg = useMemoDebugger(
127
- () => {
128
- return isSvgPath(icon);
129
- },
130
- [icon],
131
- "Icon isSvg",
132
- );
122
+ const isSvg = useMemo(() => {
123
+ return isSvgPath(icon);
124
+ }, [icon]);
133
125
 
134
126
  /** ***************** render icon *******************/
135
- const iconEl = useMemoDebugger(
136
- () => {
137
- if (!isString(icon)) {
138
- throw Error("Invalid icon expected string");
139
- }
140
- return isSvg ? (
141
- <svg viewBox={viewBox}>
142
- <path fill="currentColor" d={icon.toString()} />
143
- </svg>
144
- ) : icon?.toString().length <= 4 ? (
145
- <svg role="img">
146
- <text
147
- x="50%"
148
- y="50%"
149
- dy=".1em"
150
- dominantBaseline="middle"
151
- textAnchor="middle"
152
- style={{ fontSize: SvgTextSize[icon.length] ?? "1em" }}
153
- >
154
- {icon}
155
- </text>
156
- </svg>
157
- ) : (
158
- <i className={`${icon}`} />
159
- );
160
- },
161
- [icon, isSvg, viewBox],
162
- "Icon el",
163
- );
127
+ const iconEl = useMemo(() => {
128
+ if (!isString(icon)) {
129
+ throw Error("Invalid icon expected string");
130
+ }
131
+ return isSvg ? (
132
+ <svg viewBox={viewBox}>
133
+ <path fill="currentColor" d={icon.toString()} />
134
+ </svg>
135
+ ) : icon?.toString().length <= 4 ? (
136
+ <svg role="img">
137
+ <text
138
+ x="50%"
139
+ y="50%"
140
+ dy=".1em"
141
+ dominantBaseline="middle"
142
+ textAnchor="middle"
143
+ style={{ fontSize: SvgTextSize[icon.length] }}
144
+ >
145
+ {icon}
146
+ </text>
147
+ </svg>
148
+ ) : (
149
+ <i className={`${icon}`} />
150
+ );
151
+ }, [icon, isSvg, viewBox]);
164
152
 
165
153
  return (
166
154
  <dfn
167
- {...rest}
155
+ data-ref="icon"
168
156
  ref={ref}
157
+ {...rest}
169
158
  role="presentation"
170
159
  className={classNames(
171
160
  classes.icon,
@@ -181,6 +170,7 @@ export const Icon = ({
181
170
  data-flip={rtlFlip}
182
171
  style={styles}
183
172
  data-clickable={!!onClick}
173
+ data-inner-clickable={!!onClick}
184
174
  >
185
175
  {iconEl}
186
176
  </dfn>
@@ -24,12 +24,12 @@
24
24
  import { getImageColorset } from "@react-fabric/utilities";
25
25
  import classNames from "classnames";
26
26
  import { useMemo, useRef, useState } from "react";
27
- import { type AriaProps, type CssProp } from "../../types";
27
+ import { type AriaProps, type CssProp, type TestProps } from "../../types";
28
28
  import { CoreIcons } from "../../types/icons";
29
29
  import { Button } from "../button/Button";
30
30
  import classes from "./Media.module.css";
31
31
 
32
- export interface BaseProps extends CssProp, AriaProps {
32
+ export interface BaseProps extends CssProp, TestProps, AriaProps {
33
33
  /**
34
34
  * media source
35
35
  */
@@ -29,30 +29,41 @@ import {
29
29
  useClick,
30
30
  useDismiss,
31
31
  useFloating,
32
+ useFloatingNodeId,
32
33
  useInteractions,
33
34
  } from "@floating-ui/react";
34
- import { cloneElement, useRef, useState } from "react";
35
+ import { cloneElement, useEffect, useRef, useState } from "react";
35
36
  import { type ChildrenProp } from "../../types";
36
37
  import { type Menu } from "./Menu";
37
38
 
38
39
  export interface ContextMenuProps extends ChildrenProp {
39
40
  menu: React.ReactElement<typeof Menu>;
41
+
42
+ disabled?: boolean;
40
43
  }
41
44
 
42
- export const ContextMenu = ({ children, menu }: ContextMenuProps) => {
45
+ export const ContextMenu = ({ children, menu, disabled }: ContextMenuProps) => {
43
46
  const [isOpen, setIsOpen] = useState(false);
47
+ const nodeId = useFloatingNodeId();
44
48
 
45
49
  const { refs, floatingStyles, context } = useFloating({
46
50
  open: isOpen,
47
- onOpenChange: setIsOpen,
51
+ nodeId,
52
+ onOpenChange: (open, evt, reason) => {
53
+ const target = evt?.target as HTMLElement;
54
+ if (!target?.closest(".menu-list")) setIsOpen(open);
55
+ },
48
56
  strategy: "fixed",
49
57
  placement: "bottom-start",
50
58
  whileElementsMounted: autoUpdate,
51
59
  middleware: [offset(8), flip()],
52
60
  });
53
61
 
54
- const click = useClick(context, {});
55
- const dismiss = useDismiss(context, {});
62
+ const click = useClick(context, {
63
+ toggle: true,
64
+ ignoreMouse: true,
65
+ });
66
+ const dismiss = useDismiss(context, { bubbles: true });
56
67
 
57
68
  const { getFloatingProps } = useInteractions([dismiss, click]);
58
69
 
@@ -78,10 +89,19 @@ export const ContextMenu = ({ children, menu }: ContextMenuProps) => {
78
89
  return false;
79
90
  });
80
91
 
81
- const wrapperRef = useRef((el: HTMLElement) => {
82
- el?.parentElement?.removeEventListener("contextmenu", handleClick.current);
83
- el?.parentElement?.addEventListener("contextmenu", handleClick.current);
84
- });
92
+ const wrapperRef = useRef<HTMLDivElement>(null);
93
+ useEffect(() => {
94
+ const el = wrapperRef.current;
95
+ if (el && !disabled) {
96
+ el?.parentElement?.addEventListener("contextmenu", handleClick.current);
97
+ return () => {
98
+ el?.parentElement?.removeEventListener(
99
+ "contextmenu",
100
+ handleClick.current,
101
+ );
102
+ };
103
+ }
104
+ }, [disabled]);
85
105
 
86
106
  const tryClosing = useRef((e: React.MouseEvent) => {
87
107
  if ((e.target as HTMLElement)?.closest("[data-dropdown-dismiss='true']"))
@@ -91,12 +111,17 @@ export const ContextMenu = ({ children, menu }: ContextMenuProps) => {
91
111
  return (
92
112
  <div
93
113
  className="contents"
94
- ref={wrapperRef.current as AnyObject}
114
+ ref={wrapperRef}
95
115
  onMouseUpCapture={tryClosing.current}
96
116
  >
97
117
  {children}
98
118
  {isOpen && (
99
- <FloatingPortal>
119
+ <FloatingPortal
120
+ root={
121
+ refs.domReference.current?.closest<HTMLElement>(".theme-base") ??
122
+ undefined
123
+ }
124
+ >
100
125
  {cloneElement(menu as AnyObject, {
101
126
  ...getFloatingProps(),
102
127
  style: {
@@ -48,7 +48,6 @@ import {
48
48
  import { mergeRefs } from "@react-fabric/utilities";
49
49
  import classNames from "classnames";
50
50
  import {
51
- Children,
52
51
  cloneElement,
53
52
  Fragment,
54
53
  useCallback,
@@ -56,8 +55,9 @@ import {
56
55
  useRef,
57
56
  useState,
58
57
  } from "react";
58
+ import { HotKeyWrapper } from "../../hotkeys/HotKeyWrapper";
59
59
  import { CoreIcons } from "../../types/icons";
60
- import { nodeCheck } from "../../utils";
60
+ import { cloneChildren, nodeCheck } from "../../utils";
61
61
  import { MenuItem } from "./MenuItem";
62
62
  import { type MenuProps } from "./types";
63
63
 
@@ -104,8 +104,9 @@ const MenuComponent = ({
104
104
  handleClose: safePolygon({ blockPointerEvents: true }),
105
105
  });
106
106
  const click = useClick(context, {
107
- toggle: true,
108
- ignoreMouse: true,
107
+ event: "mousedown",
108
+ toggle: !isNested,
109
+ ignoreMouse: isNested,
109
110
  });
110
111
  const role = useRole(context, { role: "menu" });
111
112
  const dismiss = useDismiss(context, { bubbles: true });
@@ -187,6 +188,16 @@ const MenuComponent = ({
187
188
  );
188
189
  }, [isOpen]);
189
190
 
191
+ useEffect(() => {
192
+ if ((!isNested || isOpen) && activeIndex) {
193
+ setTimeout(() => {
194
+ elementsRef.current[activeIndex]?.scrollIntoView({
195
+ block: "nearest",
196
+ });
197
+ }, 100);
198
+ }
199
+ }, [isOpen, activeIndex]);
200
+
190
201
  return (
191
202
  <FloatingNode id={nodeId}>
192
203
  {isNested && (
@@ -210,29 +221,29 @@ const MenuComponent = ({
210
221
  {(!isNested || isOpen) && (
211
222
  <Wrapper {...wrapperProps}>
212
223
  {isNested && <FloatingOverlay />}
213
- <FloatingFocusManager
214
- context={context}
215
- modal={!isNested}
216
- initialFocus={isNested ? -1 : 0}
217
- returnFocus={!isNested}
218
- >
219
- <div
220
- className={classNames(
221
- menuClassName,
222
- "flex flex-col bg-base p-1 menu-list",
223
- isNested &&
224
- "outline shadow-lg rounded-capped max-h-96 overflow-auto scroll-thin z-[var(--z-popover)]",
225
- )}
226
- autoFocus
227
- ref={mergeRefs(refs.setFloating, isNested ? undefined : ref)}
228
- {...getFloatingProps({
229
- onClick: isNested ? undefined : handleClick,
230
- })}
231
- // @ts-expect-error ignore
232
- {...{ style: !isNested ? rest.style : floatingStyles }}
224
+ <HotKeyWrapper>
225
+ <FloatingFocusManager
226
+ context={context}
227
+ modal={!isNested}
228
+ initialFocus={isNested ? -1 : 0}
229
+ returnFocus={!isNested}
233
230
  >
234
- {Children.map(children, (child: AnyObject, index) => {
235
- if (child) {
231
+ <div
232
+ className={classNames(
233
+ menuClassName,
234
+ "flex flex-col bg-base p-1 menu-list",
235
+ isNested &&
236
+ "outline shadow-lg rounded-capped max-h-96 overflow-auto scroll-thin z-[var(--z-popover)]",
237
+ )}
238
+ autoFocus
239
+ ref={mergeRefs(isNested ? refs.setFloating : ref)}
240
+ {...getFloatingProps({
241
+ onClick: isNested ? undefined : handleClick,
242
+ })}
243
+ // @ts-expect-error ignore
244
+ {...{ style: !isNested ? rest.style : floatingStyles }}
245
+ >
246
+ {cloneChildren(children, (child: AnyObject, index) => {
236
247
  labelsRef.current[index] = child.props.label;
237
248
  return cloneElement(
238
249
  child,
@@ -249,10 +260,10 @@ const MenuComponent = ({
249
260
  }
250
261
  : {},
251
262
  );
252
- }
253
- })}
254
- </div>
255
- </FloatingFocusManager>
263
+ })}
264
+ </div>
265
+ </FloatingFocusManager>
266
+ </HotKeyWrapper>
256
267
  </Wrapper>
257
268
  )}
258
269
  </FloatingList>
@@ -262,6 +273,7 @@ const MenuComponent = ({
262
273
 
263
274
  export const Menu = (props: MenuProps) => {
264
275
  const parentId = useFloatingParentNodeId();
276
+ const tree = useFloatingTree();
265
277
 
266
278
  if (parentId === null) {
267
279
  return (
@@ -274,5 +286,14 @@ export const Menu = (props: MenuProps) => {
274
286
  );
275
287
  }
276
288
 
289
+ if (tree) {
290
+ return (
291
+ <MenuComponent
292
+ {...(props as AnyObject)}
293
+ menuClassName={props.className}
294
+ />
295
+ );
296
+ }
297
+
277
298
  return <MenuComponent {...props} />;
278
299
  };
@@ -22,9 +22,10 @@
22
22
  */
23
23
 
24
24
  import { useFloatingTree } from "@floating-ui/react";
25
- import { iconToken } from "@react-fabric/utilities";
25
+ import { iconToken, mergeRefs } from "@react-fabric/utilities";
26
26
  import classNames from "classnames";
27
- import { Fragment, useCallback, useMemo } from "react";
27
+ import { Fragment, useCallback, useEffect, useMemo, useRef } from "react";
28
+ import { HotKey } from "../../hotkeys/HotKey";
28
29
  import { HotKeyLabel } from "../../hotkeys/HotKeyLabel";
29
30
  import { Tooltip } from "../../overlays";
30
31
  import { type PolymorphicProps } from "../../types";
@@ -50,6 +51,7 @@ export const MenuItem = <Tag extends React.ElementType = "button">({
50
51
  onClick,
51
52
  badge,
52
53
  hotKey,
54
+ tabIndex = -1,
53
55
  // @ts-expect-error ignore
54
56
  minimal,
55
57
  // @ts-expect-error ignore
@@ -58,11 +60,16 @@ export const MenuItem = <Tag extends React.ElementType = "button">({
58
60
  ref,
59
61
  ...aria
60
62
  }: MenuItemProps & PolymorphicProps<Tag>) => {
63
+ const elRef = useRef<HTMLElement>(null);
61
64
  const tree = useFloatingTree();
62
65
  const badgeProps = useMemo(() => {
63
66
  return getBadgeProps(badge);
64
67
  }, [badge]);
65
68
 
69
+ useEffect(() => {
70
+ tabIndex === 0 && elRef.current?.focus();
71
+ }, [tabIndex]);
72
+
66
73
  const iconEl = useMemo(() => {
67
74
  let path = icon;
68
75
  if (minimal && !path) {
@@ -135,7 +142,7 @@ export const MenuItem = <Tag extends React.ElementType = "button">({
135
142
  return (
136
143
  <Wrapper {...aria}>
137
144
  <E
138
- ref={ref}
145
+ ref={mergeRefs(ref, elRef)}
139
146
  data-dropdown-dismiss
140
147
  data-color={color}
141
148
  className={classNames(
@@ -150,11 +157,15 @@ export const MenuItem = <Tag extends React.ElementType = "button">({
150
157
  data-open={childOpen}
151
158
  disabled={disabled}
152
159
  data-id={id}
160
+ tabIndex={tabIndex}
153
161
  onClick={(e) => {
154
162
  onClick?.(e) !== false && tree?.events.emit("click");
155
163
  }}
156
164
  {...(minimal ? {} : aria)}
157
165
  >
166
+ {hotKey && (
167
+ <HotKey keyCombo={hotKey} handler={() => elRef.current?.click?.()} />
168
+ )}
158
169
  {menuLabel}
159
170
  </E>
160
171
  </Wrapper>
@@ -24,6 +24,7 @@
24
24
  import classNames from "classnames";
25
25
  import { HotKey } from "../../hotkeys/HotKey";
26
26
  import {
27
+ type ChildProp,
27
28
  type AriaProps,
28
29
  type ColorType,
29
30
  type CssProp,
@@ -31,8 +32,13 @@ import {
31
32
  } from "../../types";
32
33
  import { CoreIcons } from "../../types/icons";
33
34
  import { Icon } from "../icon/Icon";
35
+ import { useCallback } from "react";
34
36
 
35
- export interface NavigatorProps extends CssProp, AriaProps, TestProps {
37
+ export interface NavigatorProps
38
+ extends CssProp,
39
+ AriaProps,
40
+ TestProps,
41
+ Partial<ChildProp> {
36
42
  /**
37
43
  * navigator button color
38
44
  */
@@ -47,28 +53,35 @@ export const Navigator = ({
47
53
  className,
48
54
  onNavigate,
49
55
  color,
56
+ children,
50
57
  ...aria
51
58
  }: NavigatorProps) => {
59
+ const navPrev = useCallback(() => onNavigate(-1), [onNavigate]);
60
+ const navNext = useCallback(() => onNavigate(+1), [onNavigate]);
52
61
  return (
53
62
  <div
54
- className={classNames(className, "inline-block leading-none")}
63
+ data-ref="navigator"
64
+ className={classNames(className, "inline-flex items-center leading-none")}
55
65
  {...aria}
56
66
  >
57
- <HotKey keyCombo="left" handler={() => onNavigate(-1)} />
58
- <HotKey keyCombo="right" handler={() => onNavigate(1)} />
67
+ <HotKey keyCombo="left" handler={navPrev} />
68
+ <HotKey keyCombo="right" handler={navNext} />
59
69
  <Icon
60
70
  rtlFlip
61
71
  color={color}
62
72
  className="text-[1em] p-1"
63
73
  aria-label="previous"
74
+ data-ref="previous"
64
75
  icon={CoreIcons.chevronLeft}
65
76
  onClick={(e) => [e.stopPropagation(), onNavigate(-1)]}
66
77
  />
78
+ {children}
67
79
  <Icon
68
80
  rtlFlip
69
81
  className="text-[1em] p-1"
70
82
  color={color}
71
83
  aria-label="next"
84
+ data-ref="next"
72
85
  icon={CoreIcons.chevronRight}
73
86
  onClick={(e) => [e.stopPropagation(), onNavigate(1)]}
74
87
  />
@@ -176,6 +176,7 @@ export const Panel = ({
176
176
  headerClassName,
177
177
  "flex flex-nowrap items-center area-head",
178
178
  )}
179
+ data-ref="panelHeader"
179
180
  onClick={collapsable ? toggleCollapse : undefined}
180
181
  >
181
182
  {onBack && (
@@ -204,7 +205,7 @@ export const Panel = ({
204
205
  >
205
206
  {title}
206
207
  </div>
207
- <div className="px-1">{actions}</div>
208
+ <div className="px-1 leading-none">{actions}</div>
208
209
  {hasTools && (
209
210
  <div
210
211
  role="none"
@@ -240,6 +241,7 @@ export const Panel = ({
240
241
  </header>
241
242
  {(!collapsed || expanded) && (
242
243
  <div
244
+ data-ref="panelBody"
243
245
  className={classNames(
244
246
  classes.panelBody,
245
247
  "area-content grid overflow-hidden",