@noya-app/noya-designsystem 0.0.14 → 0.0.16
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/.eslintrc.js +11 -0
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-lint.log +3 -0
- package/CHANGELOG.md +24 -0
- package/dist/index.d.mts +133 -111
- package/dist/index.d.ts +133 -111
- package/dist/index.js +7832 -646
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7819 -601
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -7
- package/src/components/Chip.tsx +101 -82
- package/src/components/Dialog.tsx +51 -26
- package/src/components/DraggableMenuButton.tsx +116 -0
- package/src/components/InputField.tsx +196 -132
- package/src/components/InputFieldWithCompletions.tsx +43 -6
- package/src/components/InspectorPrimitives.tsx +1 -1
- package/src/components/ListView.tsx +77 -38
- package/src/components/Select.tsx +2 -0
- package/src/components/Sortable.tsx +45 -36
- package/src/components/Stack.tsx +63 -62
- package/src/components/Switch.tsx +25 -25
- package/src/components/Text.tsx +3 -0
- package/src/contexts/DialogContext.tsx +3 -0
- package/src/index.tsx +3 -2
- package/src/theme/light.ts +1 -1
- package/src/utils/getGradientBackground.tsx +8 -10
package/package.json
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-designsystem",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsup src/index.tsx --format cjs,esm --dts --sourcemap",
|
|
9
|
-
"dev": "npm run build -- --watch"
|
|
9
|
+
"dev": "npm run build -- --watch",
|
|
10
|
+
"lint": "eslint . --max-warnings 0"
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
13
|
"@dnd-kit/core": "3.1.1",
|
|
13
14
|
"@dnd-kit/modifiers": "3.0.0",
|
|
14
15
|
"@dnd-kit/sortable": "4.0.0",
|
|
15
|
-
"@noya-app/noya-colorpicker": "0.0.
|
|
16
|
-
"@noya-app/noya-utils": "0.0.
|
|
17
|
-
"@noya-app/noya-geometry": "0.0.
|
|
18
|
-
"@noya-app/noya-icons": "0.0.
|
|
19
|
-
"@noya-app/noya-keymap": "0.0.
|
|
16
|
+
"@noya-app/noya-colorpicker": "0.0.10",
|
|
17
|
+
"@noya-app/noya-utils": "0.0.13",
|
|
18
|
+
"@noya-app/noya-geometry": "0.0.14",
|
|
19
|
+
"@noya-app/noya-icons": "0.0.7",
|
|
20
|
+
"@noya-app/noya-keymap": "0.0.9",
|
|
20
21
|
"@radix-ui/primitive": "^1.0.0",
|
|
21
22
|
"@radix-ui/react-avatar": "^1.0.1",
|
|
22
23
|
"@radix-ui/react-compose-refs": "^1.0.0",
|
package/src/components/Chip.tsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Cross1Icon, PlusIcon } from
|
|
2
|
-
import React, { memo } from
|
|
3
|
-
import styled from
|
|
4
|
-
import { useDesignSystemTheme } from
|
|
5
|
-
import { useHover } from
|
|
1
|
+
import { Cross1Icon, PlusIcon } from "@noya-app/noya-icons";
|
|
2
|
+
import React, { memo } from "react";
|
|
3
|
+
import styled from "styled-components";
|
|
4
|
+
import { useDesignSystemTheme } from "../contexts/DesignSystemConfiguration";
|
|
5
|
+
import { useHover } from "../hooks/useHover";
|
|
6
6
|
|
|
7
|
-
type ChipColorScheme =
|
|
8
|
-
type ChipSize =
|
|
9
|
-
type ChipVariant =
|
|
7
|
+
type ChipColorScheme = "primary" | "secondary" | "error";
|
|
8
|
+
type ChipSize = "small" | "medium";
|
|
9
|
+
type ChipVariant = "solid" | "outlined" | "ghost";
|
|
10
10
|
|
|
11
11
|
const ChipElement = styled.span<{
|
|
12
12
|
colorScheme?: ChipColorScheme;
|
|
@@ -16,69 +16,74 @@ const ChipElement = styled.span<{
|
|
|
16
16
|
isInteractive: boolean;
|
|
17
17
|
}>(({ theme, colorScheme, size, variant, monospace, isInteractive }) => {
|
|
18
18
|
const color =
|
|
19
|
-
colorScheme ===
|
|
19
|
+
colorScheme === "primary"
|
|
20
20
|
? theme.colors.primary
|
|
21
|
-
: colorScheme ===
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
: colorScheme === "secondary"
|
|
22
|
+
? theme.colors.secondary
|
|
23
|
+
: theme.colors.text;
|
|
24
24
|
|
|
25
25
|
const backgroundColor =
|
|
26
|
-
colorScheme ===
|
|
27
|
-
?
|
|
28
|
-
: colorScheme ===
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
colorScheme === "primary"
|
|
27
|
+
? "rgb(238, 229, 255)"
|
|
28
|
+
: colorScheme === "secondary"
|
|
29
|
+
? "rgb(205, 238, 231)"
|
|
30
|
+
: colorScheme === "error"
|
|
31
|
+
? "rgb(255, 219, 219)"
|
|
32
|
+
: theme.colors.inputBackground;
|
|
33
33
|
|
|
34
34
|
const subtleColor =
|
|
35
|
-
colorScheme ===
|
|
36
|
-
?
|
|
37
|
-
: colorScheme ===
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
colorScheme === "primary"
|
|
36
|
+
? "rgba(238, 229, 255, 0.2)"
|
|
37
|
+
: colorScheme === "secondary"
|
|
38
|
+
? "rgba(205, 238, 231, 0.2)"
|
|
39
|
+
: colorScheme === "error"
|
|
40
|
+
? "rgba(255, 219, 219, 0.2)"
|
|
41
|
+
: "rgba(0, 0, 0, 0.1)";
|
|
42
42
|
|
|
43
43
|
return {
|
|
44
|
-
textTransform:
|
|
44
|
+
textTransform: "initial",
|
|
45
45
|
borderRadius: 4,
|
|
46
|
-
userSelect:
|
|
47
|
-
display:
|
|
48
|
-
alignItems:
|
|
49
|
-
lineHeight:
|
|
50
|
-
|
|
46
|
+
userSelect: "none",
|
|
47
|
+
display: "inline-flex",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
lineHeight: "1.4",
|
|
50
|
+
whiteSpace: "pre",
|
|
51
|
+
...(size === "medium"
|
|
51
52
|
? {
|
|
52
|
-
fontSize:
|
|
53
|
-
padding:
|
|
53
|
+
fontSize: "11px",
|
|
54
|
+
padding: "4px 8px",
|
|
54
55
|
}
|
|
55
56
|
: {
|
|
56
|
-
fontSize:
|
|
57
|
-
padding:
|
|
57
|
+
fontSize: "9px",
|
|
58
|
+
padding: "0px 4px",
|
|
59
|
+
lineHeight: "15px",
|
|
58
60
|
}),
|
|
59
61
|
...(monospace && {
|
|
60
62
|
fontFamily: theme.fonts.monospace,
|
|
61
63
|
}),
|
|
62
|
-
...(variant ===
|
|
64
|
+
...(variant === "solid"
|
|
63
65
|
? {
|
|
64
66
|
color,
|
|
65
67
|
backgroundColor,
|
|
66
68
|
}
|
|
67
|
-
: variant ===
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
...(variant ===
|
|
69
|
+
: variant === "outlined"
|
|
70
|
+
? {
|
|
71
|
+
color,
|
|
72
|
+
backgroundColor: "transparent",
|
|
73
|
+
boxShadow: `0 0 0 1px ${subtleColor} inset`,
|
|
74
|
+
}
|
|
75
|
+
: {}),
|
|
76
|
+
"&:hover": {
|
|
77
|
+
...(variant === "outlined" && {
|
|
76
78
|
backgroundColor: subtleColor,
|
|
77
|
-
boxShadow:
|
|
79
|
+
boxShadow: "none",
|
|
78
80
|
}),
|
|
79
81
|
},
|
|
80
82
|
...(isInteractive && {
|
|
81
|
-
cursor:
|
|
83
|
+
cursor: "pointer",
|
|
84
|
+
"&:hover": {
|
|
85
|
+
opacity: 0.85,
|
|
86
|
+
},
|
|
82
87
|
}),
|
|
83
88
|
};
|
|
84
89
|
});
|
|
@@ -86,27 +91,27 @@ const ChipElement = styled.span<{
|
|
|
86
91
|
const DeleteElement = styled(Cross1Icon)<{
|
|
87
92
|
size: ChipSize;
|
|
88
93
|
}>(({ size }) => ({
|
|
89
|
-
position:
|
|
90
|
-
marginRight:
|
|
91
|
-
cursor:
|
|
94
|
+
position: "relative",
|
|
95
|
+
marginRight: "-2px",
|
|
96
|
+
cursor: "pointer",
|
|
92
97
|
opacity: 0.5,
|
|
93
98
|
|
|
94
|
-
...(size ===
|
|
99
|
+
...(size === "medium"
|
|
95
100
|
? {
|
|
96
|
-
marginLeft:
|
|
101
|
+
marginLeft: "2px",
|
|
97
102
|
// top: '-1px',
|
|
98
|
-
transform:
|
|
103
|
+
transform: "scale(0.75)",
|
|
99
104
|
}
|
|
100
105
|
: {
|
|
101
|
-
transform:
|
|
106
|
+
transform: "scale(0.6)",
|
|
102
107
|
}),
|
|
103
108
|
|
|
104
|
-
|
|
109
|
+
"&:hover": {
|
|
105
110
|
opacity: 0.85,
|
|
106
111
|
},
|
|
107
112
|
}));
|
|
108
113
|
|
|
109
|
-
const ignoredProps = new Set([
|
|
114
|
+
const ignoredProps = new Set(["size", "isOnlyChild"]);
|
|
110
115
|
|
|
111
116
|
const AddElement = styled(PlusIcon).withConfig({
|
|
112
117
|
shouldForwardProp: (prop) => !ignoredProps.has(prop),
|
|
@@ -114,23 +119,23 @@ const AddElement = styled(PlusIcon).withConfig({
|
|
|
114
119
|
size: ChipSize;
|
|
115
120
|
isOnlyChild: boolean;
|
|
116
121
|
}>(({ size, isOnlyChild }) => ({
|
|
117
|
-
position:
|
|
118
|
-
|
|
119
|
-
cursor:
|
|
122
|
+
position: "relative",
|
|
123
|
+
marginLeft: "-2px",
|
|
124
|
+
cursor: "pointer",
|
|
120
125
|
opacity: 0.5,
|
|
121
126
|
|
|
122
|
-
...(size ===
|
|
127
|
+
...(size === "medium"
|
|
123
128
|
? {
|
|
124
|
-
|
|
125
|
-
top:
|
|
126
|
-
transform:
|
|
129
|
+
marginRight: "2px",
|
|
130
|
+
top: "-1px",
|
|
131
|
+
transform: "scale(0.75)",
|
|
127
132
|
}
|
|
128
133
|
: {
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
marginRight: "-2px",
|
|
135
|
+
transform: "scale(0.6)",
|
|
131
136
|
}),
|
|
132
137
|
|
|
133
|
-
|
|
138
|
+
"&:hover": {
|
|
134
139
|
opacity: 0.85,
|
|
135
140
|
},
|
|
136
141
|
}));
|
|
@@ -157,8 +162,8 @@ export const Chip = memo(function Chip({
|
|
|
157
162
|
deletable,
|
|
158
163
|
addable,
|
|
159
164
|
style,
|
|
160
|
-
size =
|
|
161
|
-
variant =
|
|
165
|
+
size = "medium",
|
|
166
|
+
variant = "solid",
|
|
162
167
|
monospace = false,
|
|
163
168
|
tabIndex,
|
|
164
169
|
onDelete,
|
|
@@ -175,11 +180,11 @@ export const Chip = memo(function Chip({
|
|
|
175
180
|
const handleClick = !children && !deletable && addable ? onAdd : onClick;
|
|
176
181
|
|
|
177
182
|
const color =
|
|
178
|
-
colorScheme ===
|
|
183
|
+
colorScheme === "primary"
|
|
179
184
|
? theme.colors.primary
|
|
180
|
-
: colorScheme ===
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
: colorScheme === "secondary"
|
|
186
|
+
? theme.colors.secondary
|
|
187
|
+
: theme.colors.text;
|
|
183
188
|
|
|
184
189
|
return (
|
|
185
190
|
<ChipElement
|
|
@@ -192,20 +197,34 @@ export const Chip = memo(function Chip({
|
|
|
192
197
|
isInteractive={!!handleClick}
|
|
193
198
|
tabIndex={tabIndex}
|
|
194
199
|
>
|
|
195
|
-
{
|
|
196
|
-
|
|
197
|
-
<DeleteElement
|
|
200
|
+
{addable && (
|
|
201
|
+
<AddElement
|
|
198
202
|
size={size}
|
|
199
|
-
{
|
|
200
|
-
onClick={
|
|
203
|
+
isOnlyChild={!children && !deletable}
|
|
204
|
+
onClick={
|
|
205
|
+
onAdd
|
|
206
|
+
? (event) => {
|
|
207
|
+
event.stopPropagation();
|
|
208
|
+
onAdd?.();
|
|
209
|
+
}
|
|
210
|
+
: undefined
|
|
211
|
+
}
|
|
201
212
|
color={color}
|
|
202
213
|
/>
|
|
203
214
|
)}
|
|
204
|
-
{
|
|
205
|
-
|
|
215
|
+
{children}
|
|
216
|
+
{deletable && (
|
|
217
|
+
<DeleteElement
|
|
206
218
|
size={size}
|
|
207
|
-
|
|
208
|
-
onClick={
|
|
219
|
+
{...(hoverDeleteProps as any)}
|
|
220
|
+
onClick={
|
|
221
|
+
onDelete
|
|
222
|
+
? (event) => {
|
|
223
|
+
event.stopPropagation();
|
|
224
|
+
onDelete?.();
|
|
225
|
+
}
|
|
226
|
+
: undefined
|
|
227
|
+
}
|
|
209
228
|
color={color}
|
|
210
229
|
/>
|
|
211
230
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as DialogPrimitive from
|
|
1
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
2
2
|
import React, {
|
|
3
3
|
ComponentProps,
|
|
4
4
|
ForwardedRef,
|
|
@@ -6,35 +6,35 @@ import React, {
|
|
|
6
6
|
ReactNode,
|
|
7
7
|
useImperativeHandle,
|
|
8
8
|
useRef,
|
|
9
|
-
} from
|
|
10
|
-
import styled from
|
|
11
|
-
import { IconButton } from
|
|
12
|
-
import { Spacer } from
|
|
9
|
+
} from "react";
|
|
10
|
+
import styled from "styled-components";
|
|
11
|
+
import { IconButton } from "./IconButton";
|
|
12
|
+
import { Spacer } from "./Spacer";
|
|
13
13
|
|
|
14
14
|
const StyledOverlay = styled(DialogPrimitive.Overlay)({
|
|
15
|
-
backgroundColor:
|
|
16
|
-
position:
|
|
15
|
+
backgroundColor: "rgba(0,0,0,0.5)",
|
|
16
|
+
position: "fixed",
|
|
17
17
|
inset: 0,
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
const StyledContent = styled(DialogPrimitive.Content)(({ theme }) => ({
|
|
21
21
|
boxShadow:
|
|
22
|
-
|
|
23
|
-
position:
|
|
24
|
-
top:
|
|
25
|
-
left:
|
|
26
|
-
transform:
|
|
27
|
-
width:
|
|
28
|
-
maxWidth:
|
|
29
|
-
maxHeight:
|
|
22
|
+
"hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
|
|
23
|
+
position: "fixed",
|
|
24
|
+
top: "50%",
|
|
25
|
+
left: "50%",
|
|
26
|
+
transform: "translate(-50%, -50%)",
|
|
27
|
+
width: "90vw",
|
|
28
|
+
maxWidth: "450px",
|
|
29
|
+
maxHeight: "85vh",
|
|
30
30
|
padding: theme.sizes.dialog.padding,
|
|
31
31
|
borderRadius: 2,
|
|
32
32
|
...theme.textStyles.small,
|
|
33
33
|
backgroundColor: theme.colors.popover.background,
|
|
34
|
-
overflowY:
|
|
34
|
+
overflowY: "auto",
|
|
35
35
|
color: theme.colors.textMuted,
|
|
36
|
-
|
|
37
|
-
pointerEvents:
|
|
36
|
+
"&:focus": { outline: "none" },
|
|
37
|
+
pointerEvents: "all",
|
|
38
38
|
zIndex: 1000,
|
|
39
39
|
}));
|
|
40
40
|
|
|
@@ -51,13 +51,13 @@ const StyledDescription = styled(DialogPrimitive.Description)(({ theme }) => ({
|
|
|
51
51
|
}));
|
|
52
52
|
|
|
53
53
|
export const CloseButtonContainer = styled.div(({ theme }) => ({
|
|
54
|
-
position:
|
|
54
|
+
position: "absolute",
|
|
55
55
|
top: theme.sizes.dialog.padding,
|
|
56
56
|
right: theme.sizes.dialog.padding,
|
|
57
57
|
zIndex: 1,
|
|
58
58
|
backgroundColor: theme.colors.popover.background,
|
|
59
|
-
padding:
|
|
60
|
-
borderRadius:
|
|
59
|
+
padding: "4px 6px",
|
|
60
|
+
borderRadius: "2px",
|
|
61
61
|
// boxShadow: `0 0 2px rgba(0, 0, 0, 0.2)`,
|
|
62
62
|
border: `1px solid rgba(128,128,128,0.2)`,
|
|
63
63
|
}));
|
|
@@ -70,12 +70,12 @@ interface Props {
|
|
|
70
70
|
title?: ReactNode;
|
|
71
71
|
description?: ReactNode;
|
|
72
72
|
children?: ReactNode;
|
|
73
|
-
style?: ComponentProps<typeof StyledContent>[
|
|
74
|
-
open: ComponentProps<typeof DialogPrimitive.Root>[
|
|
75
|
-
onOpenChange?: ComponentProps<typeof DialogPrimitive.Root>[
|
|
73
|
+
style?: ComponentProps<typeof StyledContent>["style"];
|
|
74
|
+
open: ComponentProps<typeof DialogPrimitive.Root>["open"];
|
|
75
|
+
onOpenChange?: ComponentProps<typeof DialogPrimitive.Root>["onOpenChange"];
|
|
76
76
|
onOpenAutoFocus?: ComponentProps<
|
|
77
77
|
typeof DialogPrimitive.Content
|
|
78
|
-
>[
|
|
78
|
+
>["onOpenAutoFocus"];
|
|
79
79
|
closeOnInteractOutside?: boolean;
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -90,7 +90,7 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
90
90
|
onOpenAutoFocus,
|
|
91
91
|
closeOnInteractOutside = true,
|
|
92
92
|
}: Props,
|
|
93
|
-
forwardedRef: ForwardedRef<IDialog
|
|
93
|
+
forwardedRef: ForwardedRef<IDialog>
|
|
94
94
|
) {
|
|
95
95
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
96
96
|
|
|
@@ -141,3 +141,28 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
141
141
|
</DialogPrimitive.Root>
|
|
142
142
|
);
|
|
143
143
|
});
|
|
144
|
+
|
|
145
|
+
export const FullscreenDialog = forwardRef(function FullscreenDialog(
|
|
146
|
+
{ style, ...rest }: Props,
|
|
147
|
+
forwardedRef: ForwardedRef<IDialog>
|
|
148
|
+
) {
|
|
149
|
+
return (
|
|
150
|
+
<Dialog
|
|
151
|
+
ref={forwardedRef}
|
|
152
|
+
style={{
|
|
153
|
+
width: "100%",
|
|
154
|
+
height: "100%",
|
|
155
|
+
maxWidth: "100%",
|
|
156
|
+
maxHeight: "100%",
|
|
157
|
+
top: "0",
|
|
158
|
+
left: "0",
|
|
159
|
+
transform: "none",
|
|
160
|
+
display: "flex",
|
|
161
|
+
flexDirection: "column",
|
|
162
|
+
padding: 0,
|
|
163
|
+
...style,
|
|
164
|
+
}}
|
|
165
|
+
{...rest}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { DragHandleDots2Icon } from "@noya-app/noya-icons";
|
|
2
|
+
import React, { memo, useCallback, useState } from "react";
|
|
3
|
+
import styled from "styled-components";
|
|
4
|
+
import { useDesignSystemTheme } from "../contexts/DesignSystemConfiguration";
|
|
5
|
+
import { DropdownMenu } from "./DropdownMenu";
|
|
6
|
+
import { MenuItem } from "./internal/Menu";
|
|
7
|
+
|
|
8
|
+
const DotButtonElement = styled.div(({ theme }) => ({
|
|
9
|
+
cursor: "pointer",
|
|
10
|
+
borderRadius: "4px",
|
|
11
|
+
display: "flex",
|
|
12
|
+
alignItems: "center",
|
|
13
|
+
justifyContent: "center",
|
|
14
|
+
padding: "2px 0",
|
|
15
|
+
"&:hover": {
|
|
16
|
+
background: theme.colors.inputBackgroundLight,
|
|
17
|
+
},
|
|
18
|
+
"&:active": {
|
|
19
|
+
background: theme.colors.activeBackground,
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const TriggerElement = styled.div({
|
|
24
|
+
pointerEvents: "none",
|
|
25
|
+
height: "15px",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A button that opens a menu when clicked, but also allows dragging the
|
|
30
|
+
*/
|
|
31
|
+
export const DraggableMenuButton = memo(function DraggableMenuButton<
|
|
32
|
+
T extends string,
|
|
33
|
+
>({
|
|
34
|
+
items,
|
|
35
|
+
onSelect,
|
|
36
|
+
isVisible = true,
|
|
37
|
+
}: {
|
|
38
|
+
items: MenuItem<T>[];
|
|
39
|
+
onSelect: (value: T) => void;
|
|
40
|
+
isVisible?: boolean;
|
|
41
|
+
}) {
|
|
42
|
+
const color = useDesignSystemTheme().colors.icon;
|
|
43
|
+
const [open, setOpen] = useState(false);
|
|
44
|
+
const [downPosition, setDownPosition] = React.useState<{
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
} | null>(null);
|
|
48
|
+
|
|
49
|
+
const handlePointerDownCapture = useCallback(
|
|
50
|
+
(event: React.PointerEvent) => {
|
|
51
|
+
if (open) {
|
|
52
|
+
setDownPosition(null);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setDownPosition({
|
|
57
|
+
x: event.clientX,
|
|
58
|
+
y: event.clientY,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
[open]
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const handlePointerUp = useCallback(
|
|
65
|
+
(event: React.PointerEvent) => {
|
|
66
|
+
if (open || !downPosition) {
|
|
67
|
+
setDownPosition(null);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const dx = event.clientX - downPosition.x;
|
|
72
|
+
const dy = event.clientY - downPosition.y;
|
|
73
|
+
|
|
74
|
+
const distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
|
|
75
|
+
|
|
76
|
+
if (distance < 5) {
|
|
77
|
+
setOpen(true);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setDownPosition(null);
|
|
81
|
+
},
|
|
82
|
+
[downPosition, open]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const handleOpenChange = useCallback(
|
|
86
|
+
(isOpen: boolean) => {
|
|
87
|
+
if (!isOpen) setOpen(false);
|
|
88
|
+
},
|
|
89
|
+
[setOpen]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<DotButtonElement
|
|
94
|
+
onPointerDownCapture={handlePointerDownCapture}
|
|
95
|
+
onPointerUp={handlePointerUp}
|
|
96
|
+
onClick={(event) => {
|
|
97
|
+
event.stopPropagation();
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<DropdownMenu
|
|
102
|
+
open={open}
|
|
103
|
+
onOpenChange={handleOpenChange}
|
|
104
|
+
items={items}
|
|
105
|
+
onSelect={onSelect}
|
|
106
|
+
shouldBindKeyboardShortcuts={false}
|
|
107
|
+
>
|
|
108
|
+
<TriggerElement>
|
|
109
|
+
<DragHandleDots2Icon
|
|
110
|
+
color={isVisible || open ? color : "transparent"}
|
|
111
|
+
/>
|
|
112
|
+
</TriggerElement>
|
|
113
|
+
</DropdownMenu>
|
|
114
|
+
</DotButtonElement>
|
|
115
|
+
);
|
|
116
|
+
});
|