@react-fabric/core 1.0.0-beta.5 → 1.0.0-beta.6
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.
- package/dist/cjs/components/animations/Animations.js +12 -5
- package/dist/cjs/components/avatar/Avatar.js +6 -7
- package/dist/cjs/components/button/ConfirmButton.js +4 -2
- package/dist/cjs/components/card/Card.js +4 -2
- package/dist/cjs/components/collapsable/Collapsable.js +3 -2
- package/dist/cjs/components/dropdown/Dropdown.js +2 -1
- package/dist/cjs/components/dropdown/DropdownTool.js +3 -2
- package/dist/cjs/components/icon/Icon.js +7 -7
- package/dist/cjs/components/menu/ContextMenu.js +30 -10
- package/dist/cjs/components/menu/Menu.js +1 -1
- package/dist/cjs/components/navigator/Navigator.js +2 -0
- package/dist/cjs/components/panel/Panel.js +2 -0
- package/dist/cjs/components/tabpanel/Tab.js +54 -36
- package/dist/cjs/core/aside/Aside.js +3 -0
- package/dist/cjs/core/content/Content.js +8 -5
- package/dist/cjs/core/page/Page.js +1 -0
- package/dist/cjs/typography/Typography.module.css +1 -1
- package/dist/esm/components/animations/Animations.js +12 -5
- package/dist/esm/components/avatar/Avatar.js +7 -8
- package/dist/esm/components/button/ConfirmButton.js +4 -2
- package/dist/esm/components/card/Card.js +4 -2
- package/dist/esm/components/collapsable/Collapsable.js +3 -2
- package/dist/esm/components/dropdown/Dropdown.js +2 -1
- package/dist/esm/components/dropdown/DropdownTool.js +3 -2
- package/dist/esm/components/icon/Icon.js +7 -7
- package/dist/esm/components/menu/ContextMenu.js +32 -12
- package/dist/esm/components/menu/Menu.js +1 -1
- package/dist/esm/components/navigator/Navigator.js +2 -0
- package/dist/esm/components/panel/Panel.js +2 -0
- package/dist/esm/components/tabpanel/Tab.js +55 -37
- package/dist/esm/core/aside/Aside.js +3 -0
- package/dist/esm/core/content/Content.js +8 -5
- package/dist/esm/core/page/Page.js +1 -0
- package/dist/esm/typography/Typography.module.css +1 -1
- package/dist/types/components/actionLabel/ActionLabel.d.ts +2 -2
- package/dist/types/components/animations/Animations.d.ts +1 -1
- package/dist/types/components/button/ConfirmButton.d.ts +6 -1
- package/dist/types/components/card/Card.d.ts +3 -3
- package/dist/types/components/collapsable/Collapsable.d.ts +5 -1
- package/dist/types/components/dropdown/DropdownTool.d.ts +2 -1
- package/dist/types/components/media/Media.d.ts +2 -2
- package/dist/types/components/menu/ContextMenu.d.ts +2 -1
- package/dist/types/components/tabpanel/Tab.d.ts +4 -4
- package/dist/types/core/content/Content.d.ts +3 -2
- package/dist/types/types/index.d.ts +4 -0
- package/package.json +3 -3
- package/src/components/actionLabel/ActionLabel.tsx +12 -3
- package/src/components/animations/Animations.tsx +8 -2
- package/src/components/avatar/Avatar.tsx +68 -81
- package/src/components/button/ConfirmButton.tsx +10 -4
- package/src/components/card/Card.tsx +4 -0
- package/src/components/collapsable/Collapsable.tsx +6 -0
- package/src/components/dropdown/Dropdown.tsx +1 -0
- package/src/components/dropdown/DropdownTool.tsx +3 -1
- package/src/components/icon/Icon.tsx +45 -57
- package/src/components/media/Media.tsx +2 -2
- package/src/components/menu/ContextMenu.tsx +36 -11
- package/src/components/menu/Menu.tsx +1 -1
- package/src/components/navigator/Navigator.tsx +2 -0
- package/src/components/panel/Panel.tsx +2 -0
- package/src/components/tabpanel/Tab.tsx +73 -59
- package/src/core/aside/Aside.tsx +3 -0
- package/src/core/content/Content.tsx +16 -10
- package/src/core/page/Page.tsx +1 -0
- package/src/types/index.ts +5 -0
- package/src/typography/Typography.module.css +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
|
-
import { type AriaProps, type BadgeType, type CallbackReturn, type ChildrenProp, type ColorType, type CssProp, type IconProps, type PolymorphicProps, type RefProp, type TestProps } from "../../types";
|
|
3
|
-
export interface TabProps extends Partial<ChildrenProp>, CssProp, RefProp, AriaProps, TestProps, IconProps {
|
|
2
|
+
import { type AriaProps, type BadgeType, type CallbackReturn, type ChildrenProp, type ColorType, type CssProp, type IconProps, type PolymorphicProps, type RefProp, type TestProps, type TooltipProp } from "../../types";
|
|
3
|
+
export interface TabProps extends Partial<ChildrenProp>, CssProp, RefProp, AriaProps, TestProps, IconProps, TooltipProp {
|
|
4
4
|
/**
|
|
5
5
|
* tab id
|
|
6
6
|
*/
|
|
@@ -16,7 +16,7 @@ export interface TabProps extends Partial<ChildrenProp>, CssProp, RefProp, AriaP
|
|
|
16
16
|
/**
|
|
17
17
|
* tab badge
|
|
18
18
|
*/
|
|
19
|
-
badge?: BadgeType;
|
|
19
|
+
badge?: string | number | BadgeType;
|
|
20
20
|
/**
|
|
21
21
|
* disabled
|
|
22
22
|
*/
|
|
@@ -42,4 +42,4 @@ export interface TabProps extends Partial<ChildrenProp>, CssProp, RefProp, AriaP
|
|
|
42
42
|
*/
|
|
43
43
|
onClick?: (id: string) => CallbackReturn;
|
|
44
44
|
}
|
|
45
|
-
export declare const Tab: <Tag extends import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> = "button">({ ref, as, id, className, label, icon, iconBg, iconColor, rtlFlip, badge, disabled, onClose, active, minimal, onClick, tabFlex, color, actions, activeClassName, ...aria }: TabProps & PolymorphicProps<Tag>) => import("react/jsx-runtime").JSX.Element;
|
|
45
|
+
export declare const Tab: <Tag extends import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> = "button">({ ref, as, id, className, label, icon, iconBg, iconColor, rtlFlip, badge, disabled, onClose, active, minimal, onClick, tabFlex, color, actions, tooltip, activeClassName, ...aria }: TabProps & PolymorphicProps<Tag>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type MouseEvent } from "react";
|
|
2
|
+
import { type ChildrenProp, type CssProp, type RefProp, type ScrollObject, type SizeObject, type TestProps } from "../../types";
|
|
2
3
|
export interface ContentProps extends ChildrenProp, CssProp, RefProp, TestProps {
|
|
3
4
|
/**
|
|
4
5
|
* scroll vevent handler
|
|
5
6
|
*/
|
|
6
|
-
onScroll?: (event:
|
|
7
|
+
onScroll?: (scroll: ScrollObject, event: MouseEvent<HTMLDivElement>) => void;
|
|
7
8
|
/**
|
|
8
9
|
* scroll vevent handler
|
|
9
10
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-fabric/core",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.6",
|
|
4
4
|
"description": "RUF: Core package",
|
|
5
5
|
"author": "Adarsh Pastakia <adarshpastakia@outlook.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@floating-ui/react": "^0.26.24",
|
|
39
|
-
"@react-fabric/utilities": "^1.0.0-beta.
|
|
39
|
+
"@react-fabric/utilities": "^1.0.0-beta.6",
|
|
40
40
|
"@tailwindcss/container-queries": "^0.1.1",
|
|
41
41
|
"@tailwindcss/forms": "^0.5.7",
|
|
42
42
|
"@tailwindcss/typography": "^0.5.13",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"node": ">=18.12.0",
|
|
57
57
|
"npm": ">=8.19.0"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "21167df13cedeae8b365e87b9bab6d7401298cb6"
|
|
60
60
|
}
|
|
@@ -22,12 +22,21 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import classNames from "classnames";
|
|
25
|
-
import { type ReactElement
|
|
26
|
-
import {
|
|
25
|
+
import { useCallback, type ReactElement } from "react";
|
|
26
|
+
import {
|
|
27
|
+
type AriaProps,
|
|
28
|
+
type ChildProp,
|
|
29
|
+
type CssProp,
|
|
30
|
+
type TestProps,
|
|
31
|
+
} from "../../types";
|
|
27
32
|
import { ButtonGroup } from "../button/ButtonGroup";
|
|
28
33
|
import classes from "./ActionLabel.module.css";
|
|
29
34
|
|
|
30
|
-
export interface ActionLabelProps
|
|
35
|
+
export interface ActionLabelProps
|
|
36
|
+
extends AriaProps,
|
|
37
|
+
CssProp,
|
|
38
|
+
ChildProp,
|
|
39
|
+
TestProps {
|
|
31
40
|
/**
|
|
32
41
|
* action buttons aligment
|
|
33
42
|
*/
|
|
@@ -142,6 +142,7 @@ export const AnimationSpinner = ({
|
|
|
142
142
|
className={classNames(
|
|
143
143
|
"absolute inset-0 z-10 flex justify-center items-center cursor-wait",
|
|
144
144
|
)}
|
|
145
|
+
data-testid="spinner"
|
|
145
146
|
>
|
|
146
147
|
<div
|
|
147
148
|
className={classNames(
|
|
@@ -164,15 +165,20 @@ export const Loading = () => {
|
|
|
164
165
|
classes.loader,
|
|
165
166
|
"area-[loader] relative overflow-x-clip",
|
|
166
167
|
)}
|
|
168
|
+
data-testid="loading"
|
|
167
169
|
>
|
|
168
170
|
<div className="absolute z-10 py-px inset-x-0 top-0 after:h-0.5 after:block after:bg-primary-500" />
|
|
169
171
|
</div>
|
|
170
172
|
);
|
|
171
173
|
};
|
|
172
174
|
|
|
173
|
-
export const Skeleton = () => {
|
|
175
|
+
export const Skeleton = ({ className, ...rest }: CssProp) => {
|
|
174
176
|
return (
|
|
175
|
-
<div
|
|
177
|
+
<div
|
|
178
|
+
className={classNames(className, "p-2 animate-pulse flex gap-2 max-w-lg")}
|
|
179
|
+
data-testid="skeleton"
|
|
180
|
+
{...rest}
|
|
181
|
+
>
|
|
176
182
|
<div className="bg-tint-100 rounded aspect-square h-12" />
|
|
177
183
|
<div className="flex-1 flex flex-col gap-2">
|
|
178
184
|
<div className="bg-tint-100 h-3 rounded-full" />
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
import { isSvgPath } from "@react-fabric/utilities";
|
|
25
25
|
import BoringAvatar from "boring-avatars";
|
|
26
26
|
import classNames from "classnames";
|
|
27
|
-
import { useState } from "react";
|
|
28
|
-
import { useMemoDebugger } from "../../hooks/useEffectDebugger";
|
|
27
|
+
import { useMemo, useState } from "react";
|
|
29
28
|
import {
|
|
30
29
|
type AriaProps,
|
|
31
30
|
type ColorType,
|
|
@@ -109,89 +108,77 @@ export const Avatar = ({
|
|
|
109
108
|
const [fallback, setFallback] = useState(true);
|
|
110
109
|
|
|
111
110
|
/** ***************** style map *******************/
|
|
112
|
-
const styles =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
},
|
|
129
|
-
[bg, color, size],
|
|
130
|
-
"Avatar styles",
|
|
131
|
-
);
|
|
111
|
+
const styles = useMemo(() => {
|
|
112
|
+
const s: KeyValue = {};
|
|
113
|
+
if (bg) {
|
|
114
|
+
s.backgroundColor = getColor(bg);
|
|
115
|
+
}
|
|
116
|
+
if (color) {
|
|
117
|
+
s.color = getColor(color);
|
|
118
|
+
}
|
|
119
|
+
if (size && !(size in SizeMap)) {
|
|
120
|
+
s.fontSize = size;
|
|
121
|
+
}
|
|
122
|
+
if (size && size in SizeMap) {
|
|
123
|
+
s.fontSize = SizeMap[size];
|
|
124
|
+
}
|
|
125
|
+
return s;
|
|
126
|
+
}, [bg, color, size]);
|
|
132
127
|
|
|
133
128
|
/** ***************** render icon *******************/
|
|
134
|
-
const avatarImage =
|
|
135
|
-
()
|
|
136
|
-
|
|
129
|
+
const avatarImage = useMemo(() => {
|
|
130
|
+
setFallback(!avatar);
|
|
131
|
+
return (
|
|
132
|
+
avatar && (
|
|
133
|
+
<div className="contents">
|
|
134
|
+
<div
|
|
135
|
+
className={classNames(
|
|
136
|
+
classes.imgPlaceholder,
|
|
137
|
+
"absolute inset-0 bg-gray animate-pulse",
|
|
138
|
+
)}
|
|
139
|
+
/>
|
|
140
|
+
<img
|
|
141
|
+
src={avatar}
|
|
142
|
+
alt={avatar}
|
|
143
|
+
className="align-baseline"
|
|
144
|
+
onError={() => setFallback(true)}
|
|
145
|
+
onLoad={(e) => e.currentTarget.previousElementSibling?.remove()}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
}, [avatar]);
|
|
151
|
+
|
|
152
|
+
const fallbackAvatar = useMemo(() => {
|
|
153
|
+
if (fallbackIcon) {
|
|
154
|
+
return isSvgPath(fallbackIcon) ? (
|
|
155
|
+
<svg viewBox={viewBox}>
|
|
156
|
+
<path fill="currentColor" d={fallbackIcon.toString()} />
|
|
157
|
+
</svg>
|
|
158
|
+
) : (
|
|
159
|
+
<i className={fallbackIcon} />
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (variant === "text") {
|
|
163
|
+
const [, first, second, group] =
|
|
164
|
+
name.match(/(?=(\w).* (\w).*)|(?=(\w\w).*)/) ?? [];
|
|
137
165
|
return (
|
|
138
|
-
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
onError={() => setFallback(true)}
|
|
151
|
-
onLoad={(e) => e.currentTarget.previousElementSibling?.remove()}
|
|
152
|
-
/>
|
|
153
|
-
</div>
|
|
154
|
-
)
|
|
166
|
+
<svg role="img">
|
|
167
|
+
<text
|
|
168
|
+
x="50%"
|
|
169
|
+
y="50%"
|
|
170
|
+
dy=".1em"
|
|
171
|
+
dominantBaseline="middle"
|
|
172
|
+
textAnchor="middle"
|
|
173
|
+
style={{ fontSize: ".5em", fontWeight: 500 }}
|
|
174
|
+
>
|
|
175
|
+
{group ?? `${first}${second}`}
|
|
176
|
+
</text>
|
|
177
|
+
</svg>
|
|
155
178
|
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
const fallbackAvatar = useMemoDebugger(
|
|
162
|
-
() => {
|
|
163
|
-
if (fallbackIcon) {
|
|
164
|
-
return isSvgPath(fallbackIcon) ? (
|
|
165
|
-
<svg viewBox={viewBox}>
|
|
166
|
-
<path fill="currentColor" d={fallbackIcon.toString()} />
|
|
167
|
-
</svg>
|
|
168
|
-
) : (
|
|
169
|
-
<i className={fallbackIcon} />
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
if (variant === "text") {
|
|
173
|
-
const [, first, second, group] =
|
|
174
|
-
name.match(/(?=(\w).* (\w).*)|(?=(\w\w).*)/) ?? [];
|
|
175
|
-
return (
|
|
176
|
-
<svg role="img">
|
|
177
|
-
<text
|
|
178
|
-
x="50%"
|
|
179
|
-
y="50%"
|
|
180
|
-
dy=".1em"
|
|
181
|
-
dominantBaseline="middle"
|
|
182
|
-
textAnchor="middle"
|
|
183
|
-
style={{ fontSize: ".5em", fontWeight: 500 }}
|
|
184
|
-
>
|
|
185
|
-
{group ?? `${first}${second}`}
|
|
186
|
-
</text>
|
|
187
|
-
</svg>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
return <BoringAvatar square size={64} variant={variant} name={name} />;
|
|
191
|
-
},
|
|
192
|
-
[name, variant, fallbackIcon],
|
|
193
|
-
"Avatar fallback",
|
|
194
|
-
);
|
|
179
|
+
}
|
|
180
|
+
return <BoringAvatar square size={64} variant={variant} name={name} />;
|
|
181
|
+
}, [name, variant, fallbackIcon]);
|
|
195
182
|
|
|
196
183
|
return (
|
|
197
184
|
<dfn
|
|
@@ -22,18 +22,19 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import {
|
|
25
|
-
FloatingArrow,
|
|
26
|
-
FloatingFocusManager,
|
|
27
|
-
FloatingPortal,
|
|
28
25
|
arrow,
|
|
29
26
|
autoUpdate,
|
|
30
27
|
flip,
|
|
28
|
+
FloatingArrow,
|
|
29
|
+
FloatingFocusManager,
|
|
30
|
+
FloatingPortal,
|
|
31
31
|
offset,
|
|
32
32
|
shift,
|
|
33
33
|
useClick,
|
|
34
34
|
useDismiss,
|
|
35
35
|
useFloating,
|
|
36
36
|
useInteractions,
|
|
37
|
+
type Placement,
|
|
37
38
|
} from "@floating-ui/react";
|
|
38
39
|
import { mergeRefs } from "@react-fabric/utilities";
|
|
39
40
|
import classNames from "classnames";
|
|
@@ -66,6 +67,10 @@ interface ExtendedProps extends Omit<BaseProps, "onClick">, RefProp {
|
|
|
66
67
|
* ok label
|
|
67
68
|
*/
|
|
68
69
|
cancelLabel?: string;
|
|
70
|
+
/**
|
|
71
|
+
* dropdown placement
|
|
72
|
+
*/
|
|
73
|
+
placement?: Placement;
|
|
69
74
|
/**
|
|
70
75
|
* click handler, return Promise to autoset loading state
|
|
71
76
|
*/
|
|
@@ -85,6 +90,7 @@ export const ConfirmButton = <Tag extends React.ElementType = "button">({
|
|
|
85
90
|
cancelLabel,
|
|
86
91
|
onClick,
|
|
87
92
|
loading,
|
|
93
|
+
placement = "bottom",
|
|
88
94
|
actionMessage,
|
|
89
95
|
showActionDone,
|
|
90
96
|
showActionDoneEvent = "click",
|
|
@@ -127,7 +133,7 @@ export const ConfirmButton = <Tag extends React.ElementType = "button">({
|
|
|
127
133
|
}
|
|
128
134
|
},
|
|
129
135
|
strategy: "fixed",
|
|
130
|
-
placement
|
|
136
|
+
placement,
|
|
131
137
|
whileElementsMounted: autoUpdate,
|
|
132
138
|
middleware: [
|
|
133
139
|
shift({ padding: 8 }),
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
type AriaProps,
|
|
40
40
|
type ChildrenProp,
|
|
41
41
|
type CssProp,
|
|
42
|
+
type RefProp,
|
|
42
43
|
type TestProps,
|
|
43
44
|
} from "../../types";
|
|
44
45
|
import { nodeCheck } from "../../utils";
|
|
@@ -48,6 +49,7 @@ export interface CardProps
|
|
|
48
49
|
extends CssProp,
|
|
49
50
|
AriaProps,
|
|
50
51
|
TestProps,
|
|
52
|
+
RefProp<HTMLDivElement>,
|
|
51
53
|
ChildrenProp<React.ReactElement<HeadFootProps | ContentProps>> {
|
|
52
54
|
/**
|
|
53
55
|
* apply flex fill styling
|
|
@@ -94,6 +96,7 @@ export const Card = ({
|
|
|
94
96
|
draggable,
|
|
95
97
|
dragKey,
|
|
96
98
|
dragData,
|
|
99
|
+
ref,
|
|
97
100
|
...aria
|
|
98
101
|
}: CardProps) => {
|
|
99
102
|
const [header, body, footer] = useMemo(() => {
|
|
@@ -136,6 +139,7 @@ export const Card = ({
|
|
|
136
139
|
dragKey &&
|
|
137
140
|
e.dataTransfer?.setData(dragKey, JSON.stringify(dragData))
|
|
138
141
|
}
|
|
142
|
+
ref={ref}
|
|
139
143
|
{...aria}
|
|
140
144
|
>
|
|
141
145
|
{header}
|
|
@@ -66,6 +66,10 @@ export interface CollapsableProps
|
|
|
66
66
|
* header button className
|
|
67
67
|
*/
|
|
68
68
|
headerClassName?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Header position sticky
|
|
71
|
+
*/
|
|
72
|
+
stickyHeader?: boolean;
|
|
69
73
|
/**
|
|
70
74
|
* open state icon
|
|
71
75
|
*/
|
|
@@ -100,6 +104,7 @@ export const Collapsable = ({
|
|
|
100
104
|
headerClassName,
|
|
101
105
|
children,
|
|
102
106
|
iconAlign,
|
|
107
|
+
stickyHeader,
|
|
103
108
|
iconClosed = CoreIcons.chevronRight,
|
|
104
109
|
iconOpen = CoreIcons.chevronDown,
|
|
105
110
|
open,
|
|
@@ -138,6 +143,7 @@ export const Collapsable = ({
|
|
|
138
143
|
classes.collapsableHead,
|
|
139
144
|
"flex gap-1 flex-nowrap py-2 font-medium cursor-pointer items-center select-none hover:opacity-70",
|
|
140
145
|
headerClassName,
|
|
146
|
+
stickyHeader && "bg-base sticky top-0 px-2 py-1 z-10",
|
|
141
147
|
iconAlign === "end" ? "flex-row-reverse" : "flex-row",
|
|
142
148
|
)}
|
|
143
149
|
onClick={() => !disabled && setOpenState(!openState)}
|
|
@@ -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 {
|
|
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 =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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 =
|
|
127
|
-
()
|
|
128
|
-
|
|
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 =
|
|
136
|
-
()
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
82
|
-
|
|
83
|
-
el
|
|
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
|
|
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: {
|
|
@@ -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(
|
|
227
|
+
ref={mergeRefs(isNested ? refs.setFloating : ref)}
|
|
228
228
|
{...getFloatingProps({
|
|
229
229
|
onClick: isNested ? undefined : handleClick,
|
|
230
230
|
})}
|