@react-fabric/core 1.0.0-beta.1 → 1.0.0-beta.12

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 (155) hide show
  1. package/css/index.js +13 -13
  2. package/dist/cjs/components/actionLabel/ActionLabel.js +1 -0
  3. package/dist/cjs/components/animations/Animations.js +13 -6
  4. package/dist/cjs/components/avatar/Avatar.js +6 -7
  5. package/dist/cjs/components/badge/Badge.js +6 -4
  6. package/dist/cjs/components/breadcrumbs/Breadcrumbs.js +2 -2
  7. package/dist/cjs/components/button/Button.js +9 -7
  8. package/dist/cjs/components/button/Button.module.css +1 -1
  9. package/dist/cjs/components/button/ConfirmButton.js +4 -2
  10. package/dist/cjs/components/callout/Callout.js +3 -2
  11. package/dist/cjs/components/card/Card.js +4 -2
  12. package/dist/cjs/components/chip/Chip.js +2 -2
  13. package/dist/cjs/components/chip/Chip.module.css +1 -1
  14. package/dist/cjs/components/collapsable/Collapsable.js +5 -3
  15. package/dist/cjs/components/divider/Divider.js +3 -2
  16. package/dist/cjs/components/divider/Divider.module.css +1 -0
  17. package/dist/cjs/components/dropdown/Dropdown.js +4 -2
  18. package/dist/cjs/components/dropdown/DropdownTool.js +3 -2
  19. package/dist/cjs/components/icon/Icon.js +8 -7
  20. package/dist/cjs/components/menu/ContextMenu.js +30 -10
  21. package/dist/cjs/components/menu/Menu.js +1 -1
  22. package/dist/cjs/components/menu/MenuItem.js +1 -1
  23. package/dist/cjs/components/navigator/Navigator.js +15 -9
  24. package/dist/cjs/components/panel/Panel.js +2 -0
  25. package/dist/cjs/components/tabpanel/Tab.js +55 -36
  26. package/dist/cjs/components/tabpanel/TabPanel.js +18 -13
  27. package/dist/cjs/components/tabpanel/TabPanel.module.css +1 -1
  28. package/dist/cjs/core/aside/Aside.js +7 -2
  29. package/dist/cjs/core/content/Content.js +8 -5
  30. package/dist/cjs/core/headfoot/HeadFoot.js +1 -1
  31. package/dist/cjs/core/headfoot/HeadFoot.module.css +1 -1
  32. package/dist/cjs/core/page/Page.js +1 -0
  33. package/dist/cjs/core/section/Section.js +5 -2
  34. package/dist/cjs/hooks/useNavigator.js +6 -4
  35. package/dist/cjs/index.js +3 -2
  36. package/dist/cjs/overlays/alert/Alert.js +38 -16
  37. package/dist/cjs/overlays/modal/Modal.js +1 -1
  38. package/dist/cjs/overlays/tooltip/Tooltip.js +3 -3
  39. package/dist/cjs/overlays/tooltip/Tooltip.module.css +1 -1
  40. package/dist/cjs/types/icons.js +3 -0
  41. package/dist/cjs/typography/Abbr.js +2 -0
  42. package/dist/cjs/typography/{Link.js → Anchor.js} +2 -2
  43. package/dist/cjs/typography/Typography.module.css +1 -1
  44. package/dist/esm/components/actionLabel/ActionLabel.js +1 -0
  45. package/dist/esm/components/animations/Animations.js +13 -6
  46. package/dist/esm/components/avatar/Avatar.js +7 -8
  47. package/dist/esm/components/badge/Badge.js +7 -5
  48. package/dist/esm/components/breadcrumbs/Breadcrumbs.js +2 -2
  49. package/dist/esm/components/button/Button.js +9 -7
  50. package/dist/esm/components/button/Button.module.css +1 -1
  51. package/dist/esm/components/button/ConfirmButton.js +4 -2
  52. package/dist/esm/components/callout/Callout.js +3 -2
  53. package/dist/esm/components/card/Card.js +4 -2
  54. package/dist/esm/components/chip/Chip.js +2 -2
  55. package/dist/esm/components/chip/Chip.module.css +1 -1
  56. package/dist/esm/components/collapsable/Collapsable.js +5 -3
  57. package/dist/esm/components/divider/Divider.js +3 -2
  58. package/dist/esm/components/divider/Divider.module.css +1 -0
  59. package/dist/esm/components/dropdown/Dropdown.js +4 -2
  60. package/dist/esm/components/dropdown/DropdownTool.js +3 -2
  61. package/dist/esm/components/icon/Icon.js +8 -7
  62. package/dist/esm/components/menu/ContextMenu.js +32 -12
  63. package/dist/esm/components/menu/Menu.js +1 -1
  64. package/dist/esm/components/menu/MenuItem.js +1 -1
  65. package/dist/esm/components/navigator/Navigator.js +15 -9
  66. package/dist/esm/components/panel/Panel.js +2 -0
  67. package/dist/esm/components/tabpanel/Tab.js +56 -37
  68. package/dist/esm/components/tabpanel/TabPanel.js +18 -13
  69. package/dist/esm/components/tabpanel/TabPanel.module.css +1 -1
  70. package/dist/esm/core/aside/Aside.js +7 -2
  71. package/dist/esm/core/content/Content.js +8 -5
  72. package/dist/esm/core/headfoot/HeadFoot.js +1 -1
  73. package/dist/esm/core/headfoot/HeadFoot.module.css +1 -1
  74. package/dist/esm/core/page/Page.js +1 -0
  75. package/dist/esm/core/section/Section.js +5 -2
  76. package/dist/esm/hooks/useNavigator.js +7 -5
  77. package/dist/esm/index.js +2 -1
  78. package/dist/esm/overlays/alert/Alert.js +27 -5
  79. package/dist/esm/overlays/modal/Modal.js +1 -1
  80. package/dist/esm/overlays/tooltip/Tooltip.js +3 -3
  81. package/dist/esm/overlays/tooltip/Tooltip.module.css +1 -1
  82. package/dist/esm/types/icons.js +3 -0
  83. package/dist/esm/typography/Abbr.js +2 -0
  84. package/dist/esm/typography/{Link.js → Anchor.js} +2 -2
  85. package/dist/esm/typography/Typography.module.css +1 -1
  86. package/dist/types/components/actionLabel/ActionLabel.d.ts +2 -2
  87. package/dist/types/components/animations/Animations.d.ts +1 -1
  88. package/dist/types/components/breadcrumbs/Breadcrumbs.d.ts +2 -2
  89. package/dist/types/components/button/Button.d.ts +5 -2
  90. package/dist/types/components/button/ConfirmButton.d.ts +6 -1
  91. package/dist/types/components/callout/Callout.d.ts +3 -3
  92. package/dist/types/components/card/Card.d.ts +3 -3
  93. package/dist/types/components/collapsable/Collapsable.d.ts +9 -1
  94. package/dist/types/components/dropdown/DropdownTool.d.ts +2 -1
  95. package/dist/types/components/media/Media.d.ts +2 -2
  96. package/dist/types/components/menu/ContextMenu.d.ts +2 -1
  97. package/dist/types/components/menu/types.d.ts +2 -2
  98. package/dist/types/components/navigator/Navigator.d.ts +3 -3
  99. package/dist/types/components/tabpanel/Tab.d.ts +8 -4
  100. package/dist/types/components/tabpanel/TabPanel.d.ts +10 -1
  101. package/dist/types/core/aside/Aside.d.ts +9 -5
  102. package/dist/types/core/content/Content.d.ts +3 -2
  103. package/dist/types/core/section/Section.d.ts +2 -2
  104. package/dist/types/overlays/alert/Alert.d.ts +9 -1
  105. package/dist/types/overlays/modal/Modal.d.ts +1 -1
  106. package/dist/types/types/icons.d.ts +3 -0
  107. package/dist/types/types/index.d.ts +6 -2
  108. package/dist/types/typography/Abbr.d.ts +3 -3
  109. package/dist/types/typography/{Link.d.ts → Anchor.d.ts} +4 -3
  110. package/dist/types/typography/index.d.ts +1 -1
  111. package/package.json +3 -3
  112. package/src/components/actionLabel/ActionLabel.tsx +13 -3
  113. package/src/components/animations/Animations.tsx +9 -3
  114. package/src/components/avatar/Avatar.tsx +68 -81
  115. package/src/components/badge/Badge.tsx +24 -20
  116. package/src/components/breadcrumbs/Breadcrumbs.tsx +3 -3
  117. package/src/components/button/Button.module.css +1 -1
  118. package/src/components/button/Button.tsx +16 -10
  119. package/src/components/button/ConfirmButton.tsx +10 -4
  120. package/src/components/callout/Callout.tsx +8 -1
  121. package/src/components/card/Card.tsx +4 -0
  122. package/src/components/chip/Chip.module.css +1 -1
  123. package/src/components/chip/Chip.tsx +2 -4
  124. package/src/components/collapsable/Collapsable.tsx +12 -1
  125. package/src/components/divider/Divider.module.css +32 -0
  126. package/src/components/divider/Divider.tsx +3 -2
  127. package/src/components/dropdown/Dropdown.tsx +3 -2
  128. package/src/components/dropdown/DropdownTool.tsx +3 -1
  129. package/src/components/icon/Icon.tsx +46 -57
  130. package/src/components/media/Media.tsx +2 -2
  131. package/src/components/menu/ContextMenu.tsx +36 -11
  132. package/src/components/menu/Menu.tsx +2 -2
  133. package/src/components/menu/types.ts +2 -2
  134. package/src/components/navigator/Navigator.tsx +16 -4
  135. package/src/components/panel/Panel.tsx +2 -0
  136. package/src/components/tabpanel/Tab.tsx +78 -59
  137. package/src/components/tabpanel/TabPanel.module.css +19 -26
  138. package/src/components/tabpanel/TabPanel.tsx +32 -10
  139. package/src/core/aside/Aside.tsx +26 -4
  140. package/src/core/content/Content.tsx +16 -10
  141. package/src/core/headfoot/HeadFoot.module.css +2 -0
  142. package/src/core/headfoot/HeadFoot.tsx +1 -1
  143. package/src/core/page/Page.tsx +1 -0
  144. package/src/core/section/Section.tsx +8 -2
  145. package/src/hooks/useNavigator.tsx +10 -5
  146. package/src/overlays/alert/Alert.tsx +25 -2
  147. package/src/overlays/modal/Modal.tsx +2 -2
  148. package/src/overlays/tooltip/Tooltip.module.css +1 -1
  149. package/src/overlays/tooltip/Tooltip.tsx +4 -4
  150. package/src/types/icons.ts +6 -0
  151. package/src/types/index.ts +10 -2
  152. package/src/typography/Abbr.tsx +4 -3
  153. package/src/typography/{Link.tsx → Anchor.tsx} +6 -3
  154. package/src/typography/Typography.module.css +2 -2
  155. package/src/typography/index.ts +1 -1
@@ -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,7 +45,7 @@ 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",
@@ -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,66 +101,54 @@ 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] ?? "1em" }}
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
@@ -181,6 +169,7 @@ export const Icon = ({
181
169
  data-flip={rtlFlip}
182
170
  style={styles}
183
171
  data-clickable={!!onClick}
172
+ data-inner-clickable={!!onClick}
184
173
  >
185
174
  {iconEl}
186
175
  </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: {
@@ -162,7 +162,7 @@ const MenuComponent = ({
162
162
  (e: React.MouseEvent) => {
163
163
  const el = e.target as HTMLElement;
164
164
  if (el.dataset.id) {
165
- onClick?.(el.dataset.id);
165
+ onClick?.(el.dataset.id as AnyObject);
166
166
  }
167
167
  },
168
168
  [onClick],
@@ -224,7 +224,7 @@ const MenuComponent = ({
224
224
  "outline shadow-lg rounded-capped max-h-96 overflow-auto scroll-thin z-[var(--z-popover)]",
225
225
  )}
226
226
  autoFocus
227
- ref={mergeRefs(refs.setFloating, isNested ? undefined : ref)}
227
+ ref={mergeRefs(isNested ? refs.setFloating : ref)}
228
228
  {...getFloatingProps({
229
229
  onClick: isNested ? undefined : handleClick,
230
230
  })}
@@ -21,7 +21,7 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
- import { type ElementType } from "react";
24
+ import { type ElementType, type MouseEvent } from "react";
25
25
  import {
26
26
  type AriaProps,
27
27
  type BadgeType,
@@ -77,7 +77,7 @@ export interface MenuItemProps
77
77
  /**
78
78
  * click handler
79
79
  */
80
- onClick?: () => CallbackReturn;
80
+ onClick?: (e: MouseEvent) => CallbackReturn;
81
81
  }
82
82
 
83
83
  export interface MenuRootProps extends CssProp {
@@ -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,34 @@ 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
+ className={classNames(className, "inline-flex items-center leading-none")}
55
64
  {...aria}
56
65
  >
57
- <HotKey keyCombo="left" handler={() => onNavigate(-1)} />
58
- <HotKey keyCombo="right" handler={() => onNavigate(1)} />
66
+ <HotKey keyCombo="left" handler={navPrev} />
67
+ <HotKey keyCombo="right" handler={navNext} />
59
68
  <Icon
60
69
  rtlFlip
61
70
  color={color}
62
71
  className="text-[1em] p-1"
63
72
  aria-label="previous"
73
+ data-testid="previous"
64
74
  icon={CoreIcons.chevronLeft}
65
75
  onClick={(e) => [e.stopPropagation(), onNavigate(-1)]}
66
76
  />
77
+ {children}
67
78
  <Icon
68
79
  rtlFlip
69
80
  className="text-[1em] p-1"
70
81
  color={color}
71
82
  aria-label="next"
83
+ data-testid="next"
72
84
  icon={CoreIcons.chevronRight}
73
85
  onClick={(e) => [e.stopPropagation(), onNavigate(1)]}
74
86
  />
@@ -176,6 +176,7 @@ export const Panel = ({
176
176
  headerClassName,
177
177
  "flex flex-nowrap items-center area-head",
178
178
  )}
179
+ data-testid="panel-header"
179
180
  onClick={collapsable ? toggleCollapse : undefined}
180
181
  >
181
182
  {onBack && (
@@ -240,6 +241,7 @@ export const Panel = ({
240
241
  </header>
241
242
  {(!collapsed || expanded) && (
242
243
  <div
244
+ data-testid="panel-body"
243
245
  className={classNames(
244
246
  classes.panelBody,
245
247
  "area-content grid overflow-hidden",
@@ -21,8 +21,10 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
+ import { isString } from "@react-fabric/utilities";
24
25
  import classNames from "classnames";
25
- import { type ReactElement, useMemo } from "react";
26
+ import { Fragment, type ReactElement, useMemo } from "react";
27
+ import { Tooltip } from "../../overlays";
26
28
  import {
27
29
  type AriaProps,
28
30
  type BadgeType,
@@ -34,6 +36,7 @@ import {
34
36
  type PolymorphicProps,
35
37
  type RefProp,
36
38
  type TestProps,
39
+ type TooltipProp,
37
40
  } from "../../types";
38
41
  import { Badge, getBadgeProps } from "../badge/Badge";
39
42
  import { DropdownTool } from "../dropdown/DropdownTool";
@@ -46,7 +49,8 @@ export interface TabProps
46
49
  RefProp,
47
50
  AriaProps,
48
51
  TestProps,
49
- IconProps {
52
+ IconProps,
53
+ TooltipProp {
50
54
  /**
51
55
  * tab id
52
56
  */
@@ -62,7 +66,7 @@ export interface TabProps
62
66
  /**
63
67
  * tab badge
64
68
  */
65
- badge?: BadgeType;
69
+ badge?: string | number | BadgeType;
66
70
  /**
67
71
  * disabled
68
72
  */
@@ -83,6 +87,10 @@ export interface TabProps
83
87
  * close handler
84
88
  */
85
89
  onClose?: () => CallbackReturn;
90
+ /**
91
+ * click handler
92
+ */
93
+ onClick?: (id: string) => CallbackReturn;
86
94
  }
87
95
 
88
96
  export const Tab = <Tag extends React.ElementType = "button">({
@@ -106,6 +114,7 @@ export const Tab = <Tag extends React.ElementType = "button">({
106
114
  tabFlex,
107
115
  color,
108
116
  actions,
117
+ tooltip,
109
118
  activeClassName,
110
119
  ...aria
111
120
  }: TabProps & PolymorphicProps<Tag>) => {
@@ -113,63 +122,73 @@ export const Tab = <Tag extends React.ElementType = "button">({
113
122
  return getBadgeProps(badge);
114
123
  }, [badge]);
115
124
 
125
+ const tooltipProps = useMemo(() => {
126
+ if (isString(tooltip)) return { content: tooltip };
127
+
128
+ return tooltip ?? {};
129
+ }, [tooltip]);
130
+
116
131
  const E = as ?? "button";
132
+ const W = tooltip ? Tooltip : Fragment;
117
133
  return (
118
- <E
119
- data-id={id}
120
- disabled={disabled}
121
- data-color={color}
122
- className={classNames(
123
- classes.tabButton,
124
- className,
125
- active && "active pointer-events-none",
126
- active && activeClassName,
127
- disabled && "text-tint-500 pointer-events-none",
128
- tabFlex ? "flex-1" : "flex-initial max-w-36 min-w-24",
129
- "flex items-center justify-center px-1 rounded overflow-hidden",
130
- )}
131
- onClick={() => !active && onClick?.(id)}
132
- ref={ref as AnyObject}
133
- {...aria}
134
- >
135
- {icon && (
136
- <Icon
137
- icon={icon}
138
- bg={iconBg}
139
- color={iconColor}
140
- rtlFlip={rtlFlip}
141
- className="flex-content"
142
- />
143
- )}
144
- {!minimal && (
145
- <label className="flex-initial truncate text-center px-1 py-1">
146
- {label}
147
- </label>
148
- )}
149
- {badge && (
150
- <Badge
151
- {...badgeProps}
152
- placement={minimal ? "end" : ""}
153
- className={classNames("me-1", badgeProps.className)}
154
- />
155
- )}
156
- {actions && active && (
157
- <DropdownTool className="bg-base/20 me-1 z-5">{actions}</DropdownTool>
158
- )}
159
- {onClose && (
160
- <span
161
- role="none"
162
- className={classNames(
163
- "cursor-pointer me-1 opacity-65 hover:opacity-90 pointer-events-auto",
164
- )}
165
- onClick={(e) => {
166
- onClose?.();
167
- e.stopPropagation();
168
- }}
169
- >
170
- &times;
171
- </span>
172
- )}
173
- </E>
134
+ <W {...tooltipProps}>
135
+ <E
136
+ data-id={id}
137
+ disabled={disabled}
138
+ data-color={color}
139
+ data-inner-clickable
140
+ className={classNames(
141
+ classes.tabButton,
142
+ className,
143
+ active && "active pointer-events-none",
144
+ active && activeClassName,
145
+ disabled && "text-tint-500 pointer-events-none",
146
+ tabFlex ? "flex-1" : "flex-initial max-w-36 min-w-24",
147
+ "flex items-center justify-center px-1 rounded overflow-hidden",
148
+ )}
149
+ onClick={() => !active && onClick?.(id)}
150
+ ref={ref as AnyObject}
151
+ {...aria}
152
+ >
153
+ {icon && (
154
+ <Icon
155
+ icon={icon}
156
+ bg={iconBg}
157
+ color={iconColor}
158
+ rtlFlip={rtlFlip}
159
+ className="flex-content"
160
+ />
161
+ )}
162
+ {!minimal && (
163
+ <label className="flex-initial truncate text-center px-1 py-1">
164
+ {label}
165
+ </label>
166
+ )}
167
+ {badge && (
168
+ <Badge
169
+ {...badgeProps}
170
+ placement={minimal ? "end" : ""}
171
+ className={classNames("me-1", badgeProps.className)}
172
+ />
173
+ )}
174
+ {actions && active && (
175
+ <DropdownTool className="bg-base/20 me-1 z-5">{actions}</DropdownTool>
176
+ )}
177
+ {onClose && (
178
+ <span
179
+ role="none"
180
+ className={classNames(
181
+ "cursor-pointer me-1 opacity-65 hover:opacity-90 pointer-events-auto",
182
+ )}
183
+ onClick={(e) => {
184
+ onClose?.();
185
+ e.stopPropagation();
186
+ }}
187
+ >
188
+ &times;
189
+ </span>
190
+ )}
191
+ </E>
192
+ </W>
174
193
  );
175
194
  };