@arronqzy/ui 1.0.2 → 1.0.4
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/styles.css +131 -4
- package/package.json +3 -3
- package/src/lib/overlay-z-index.ts +4 -2
- package/src/ui/alert-dialog.tsx +99 -25
- package/src/ui/dialog.tsx +119 -31
- package/src/ui/floating-window.tsx +365 -0
- package/src/ui/index.ts +1 -0
- package/src/ui/select.tsx +4 -4
package/dist/styles.css
CHANGED
|
@@ -657,6 +657,11 @@ body {
|
|
|
657
657
|
right: 0px;
|
|
658
658
|
}
|
|
659
659
|
|
|
660
|
+
.inset-x-10 {
|
|
661
|
+
left: 2.5rem;
|
|
662
|
+
right: 2.5rem;
|
|
663
|
+
}
|
|
664
|
+
|
|
660
665
|
.inset-y-0 {
|
|
661
666
|
top: 0px;
|
|
662
667
|
bottom: 0px;
|
|
@@ -682,6 +687,10 @@ body {
|
|
|
682
687
|
bottom: 0px;
|
|
683
688
|
}
|
|
684
689
|
|
|
690
|
+
.bottom-2 {
|
|
691
|
+
bottom: 0.5rem;
|
|
692
|
+
}
|
|
693
|
+
|
|
685
694
|
.left-0 {
|
|
686
695
|
left: 0px;
|
|
687
696
|
}
|
|
@@ -706,6 +715,10 @@ body {
|
|
|
706
715
|
right: 0.5rem;
|
|
707
716
|
}
|
|
708
717
|
|
|
718
|
+
.right-3 {
|
|
719
|
+
right: 0.75rem;
|
|
720
|
+
}
|
|
721
|
+
|
|
709
722
|
.right-4 {
|
|
710
723
|
right: 1rem;
|
|
711
724
|
}
|
|
@@ -722,6 +735,10 @@ body {
|
|
|
722
735
|
top: 0.5rem;
|
|
723
736
|
}
|
|
724
737
|
|
|
738
|
+
.top-3 {
|
|
739
|
+
top: 0.75rem;
|
|
740
|
+
}
|
|
741
|
+
|
|
725
742
|
.top-4 {
|
|
726
743
|
top: 1rem;
|
|
727
744
|
}
|
|
@@ -746,6 +763,14 @@ body {
|
|
|
746
763
|
z-index: 10;
|
|
747
764
|
}
|
|
748
765
|
|
|
766
|
+
.z-20 {
|
|
767
|
+
z-index: 20;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.z-30 {
|
|
771
|
+
z-index: 30;
|
|
772
|
+
}
|
|
773
|
+
|
|
749
774
|
.z-50 {
|
|
750
775
|
z-index: 50;
|
|
751
776
|
}
|
|
@@ -876,6 +901,10 @@ body {
|
|
|
876
901
|
height: var(--cell-size);
|
|
877
902
|
}
|
|
878
903
|
|
|
904
|
+
.h-1 {
|
|
905
|
+
height: 0.25rem;
|
|
906
|
+
}
|
|
907
|
+
|
|
879
908
|
.h-1\.5 {
|
|
880
909
|
height: 0.375rem;
|
|
881
910
|
}
|
|
@@ -972,6 +1001,10 @@ body {
|
|
|
972
1001
|
max-height: var(--radix-dropdown-menu-content-available-height);
|
|
973
1002
|
}
|
|
974
1003
|
|
|
1004
|
+
.max-h-none {
|
|
1005
|
+
max-height: none;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
975
1008
|
.max-h-screen {
|
|
976
1009
|
max-height: 100vh;
|
|
977
1010
|
}
|
|
@@ -988,6 +1021,10 @@ body {
|
|
|
988
1021
|
width: 0.25rem;
|
|
989
1022
|
}
|
|
990
1023
|
|
|
1024
|
+
.w-1\.5 {
|
|
1025
|
+
width: 0.375rem;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
991
1028
|
.w-10 {
|
|
992
1029
|
width: 2.5rem;
|
|
993
1030
|
}
|
|
@@ -996,6 +1033,10 @@ body {
|
|
|
996
1033
|
width: 2.75rem;
|
|
997
1034
|
}
|
|
998
1035
|
|
|
1036
|
+
.w-12 {
|
|
1037
|
+
width: 3rem;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
999
1040
|
.w-2 {
|
|
1000
1041
|
width: 0.5rem;
|
|
1001
1042
|
}
|
|
@@ -1177,11 +1218,21 @@ body {
|
|
|
1177
1218
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1178
1219
|
}
|
|
1179
1220
|
|
|
1221
|
+
.translate-x-0 {
|
|
1222
|
+
--tw-translate-x: 0px;
|
|
1223
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1180
1226
|
.translate-x-\[-50\%\] {
|
|
1181
1227
|
--tw-translate-x: -50%;
|
|
1182
1228
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1183
1229
|
}
|
|
1184
1230
|
|
|
1231
|
+
.translate-y-0 {
|
|
1232
|
+
--tw-translate-y: 0px;
|
|
1233
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1185
1236
|
.translate-y-\[-50\%\] {
|
|
1186
1237
|
--tw-translate-y: -50%;
|
|
1187
1238
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
@@ -1201,6 +1252,10 @@ body {
|
|
|
1201
1252
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1202
1253
|
}
|
|
1203
1254
|
|
|
1255
|
+
.animate-none {
|
|
1256
|
+
animation: none;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1204
1259
|
@keyframes pulse {
|
|
1205
1260
|
50% {
|
|
1206
1261
|
opacity: .5;
|
|
@@ -1225,10 +1280,46 @@ body {
|
|
|
1225
1280
|
cursor: default;
|
|
1226
1281
|
}
|
|
1227
1282
|
|
|
1283
|
+
.cursor-e-resize {
|
|
1284
|
+
cursor: e-resize;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
.cursor-grab {
|
|
1288
|
+
cursor: grab;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
.cursor-n-resize {
|
|
1292
|
+
cursor: n-resize;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
.cursor-ne-resize {
|
|
1296
|
+
cursor: ne-resize;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
.cursor-nw-resize {
|
|
1300
|
+
cursor: nw-resize;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1228
1303
|
.cursor-pointer {
|
|
1229
1304
|
cursor: pointer;
|
|
1230
1305
|
}
|
|
1231
1306
|
|
|
1307
|
+
.cursor-s-resize {
|
|
1308
|
+
cursor: s-resize;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
.cursor-se-resize {
|
|
1312
|
+
cursor: se-resize;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
.cursor-sw-resize {
|
|
1316
|
+
cursor: sw-resize;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
.cursor-w-resize {
|
|
1320
|
+
cursor: w-resize;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1232
1323
|
.touch-none {
|
|
1233
1324
|
touch-action: none;
|
|
1234
1325
|
}
|
|
@@ -1239,6 +1330,14 @@ body {
|
|
|
1239
1330
|
user-select: none;
|
|
1240
1331
|
}
|
|
1241
1332
|
|
|
1333
|
+
.\!resize {
|
|
1334
|
+
resize: both !important;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
.resize {
|
|
1338
|
+
resize: both;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1242
1341
|
.list-none {
|
|
1243
1342
|
list-style-type: none;
|
|
1244
1343
|
}
|
|
@@ -1531,6 +1630,10 @@ body {
|
|
|
1531
1630
|
background-color: hsl(var(--muted));
|
|
1532
1631
|
}
|
|
1533
1632
|
|
|
1633
|
+
.bg-muted-foreground\/40 {
|
|
1634
|
+
background-color: hsl(var(--muted-foreground) / 0.4);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1534
1637
|
.bg-muted\/50 {
|
|
1535
1638
|
background-color: hsl(var(--muted) / 0.5);
|
|
1536
1639
|
}
|
|
@@ -1677,6 +1780,10 @@ body {
|
|
|
1677
1780
|
padding-right: 0.25rem;
|
|
1678
1781
|
}
|
|
1679
1782
|
|
|
1783
|
+
.pr-10 {
|
|
1784
|
+
padding-right: 2.5rem;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1680
1787
|
.pr-2 {
|
|
1681
1788
|
padding-right: 0.5rem;
|
|
1682
1789
|
}
|
|
@@ -1701,6 +1808,10 @@ body {
|
|
|
1701
1808
|
padding-top: 1rem;
|
|
1702
1809
|
}
|
|
1703
1810
|
|
|
1811
|
+
.pt-8 {
|
|
1812
|
+
padding-top: 2rem;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1704
1815
|
.text-left {
|
|
1705
1816
|
text-align: left;
|
|
1706
1817
|
}
|
|
@@ -1930,6 +2041,10 @@ body {
|
|
|
1930
2041
|
transition-duration: 150ms;
|
|
1931
2042
|
}
|
|
1932
2043
|
|
|
2044
|
+
.transition-none {
|
|
2045
|
+
transition-property: none;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
1933
2048
|
.transition-opacity {
|
|
1934
2049
|
transition-property: opacity;
|
|
1935
2050
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
@@ -2183,6 +2298,10 @@ body {
|
|
|
2183
2298
|
--tw-ring-offset-color: hsl(var(--background));
|
|
2184
2299
|
}
|
|
2185
2300
|
|
|
2301
|
+
.active\:cursor-grabbing:active {
|
|
2302
|
+
cursor: grabbing;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2186
2305
|
.disabled\:pointer-events-none:disabled {
|
|
2187
2306
|
pointer-events: none;
|
|
2188
2307
|
}
|
|
@@ -2497,10 +2616,6 @@ body {
|
|
|
2497
2616
|
color: hsl(var(--accent-foreground));
|
|
2498
2617
|
}
|
|
2499
2618
|
|
|
2500
|
-
.data-\[state\=open\]\:text-muted-foreground[data-state="open"] {
|
|
2501
|
-
color: hsl(var(--muted-foreground));
|
|
2502
|
-
}
|
|
2503
|
-
|
|
2504
2619
|
.data-\[disabled\=true\]\:opacity-50[data-disabled="true"] {
|
|
2505
2620
|
opacity: 0.5;
|
|
2506
2621
|
}
|
|
@@ -2629,6 +2744,10 @@ body {
|
|
|
2629
2744
|
--tw-exit-scale: .95;
|
|
2630
2745
|
}
|
|
2631
2746
|
|
|
2747
|
+
.data-\[state\=open\]\:zoom-in-100[data-state="open"] {
|
|
2748
|
+
--tw-enter-scale: 1;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2632
2751
|
.data-\[state\=open\]\:zoom-in-90[data-state="open"] {
|
|
2633
2752
|
--tw-enter-scale: .9;
|
|
2634
2753
|
}
|
|
@@ -2705,6 +2824,10 @@ body {
|
|
|
2705
2824
|
--tw-enter-translate-x: -100%;
|
|
2706
2825
|
}
|
|
2707
2826
|
|
|
2827
|
+
.data-\[state\=open\]\:slide-in-from-left-0[data-state="open"] {
|
|
2828
|
+
--tw-enter-translate-x: -0px;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2708
2831
|
.data-\[state\=open\]\:slide-in-from-left-1\/2[data-state="open"] {
|
|
2709
2832
|
--tw-enter-translate-x: -50%;
|
|
2710
2833
|
}
|
|
@@ -2717,6 +2840,10 @@ body {
|
|
|
2717
2840
|
--tw-enter-translate-y: -100%;
|
|
2718
2841
|
}
|
|
2719
2842
|
|
|
2843
|
+
.data-\[state\=open\]\:slide-in-from-top-0[data-state="open"] {
|
|
2844
|
+
--tw-enter-translate-y: -0px;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2720
2847
|
.data-\[state\=open\]\:slide-in-from-top-\[48\%\][data-state="open"] {
|
|
2721
2848
|
--tw-enter-translate-y: -48%;
|
|
2722
2849
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": "./index.ts",
|
|
6
6
|
"./counter": "./components/counter.ts",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"@types/react-dom": "^19.0.2",
|
|
63
63
|
"eslint": "^8.57.0",
|
|
64
64
|
"typescript": "^5.5.4",
|
|
65
|
-
"@arronqzy/
|
|
65
|
+
"@arronqzy/typescript-config": "1.0.0",
|
|
66
66
|
"@arronqzy/tailwind": "1.0.2",
|
|
67
|
-
"@arronqzy/
|
|
67
|
+
"@arronqzy/eslint-config": "0.0.0"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"lint": "eslint \"**/*.{ts,tsx}\"",
|
|
@@ -3,8 +3,9 @@ import type { CSSProperties } from "react";
|
|
|
3
3
|
/** 全局浮层 z-index 刻度 */
|
|
4
4
|
export const UI_Z_INDEX = {
|
|
5
5
|
tooltip: 10000,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/** 普通页面下拉;弹窗内下拉须高于 modalContent */
|
|
7
|
+
dropdown: 20150,
|
|
8
|
+
popover: 20170,
|
|
8
9
|
alertDialog: 10150,
|
|
9
10
|
/** 弹窗:高于画布 Ruler、工具栏、Menubar 等 */
|
|
10
11
|
modal: 20000,
|
|
@@ -21,3 +22,4 @@ export const MODAL_CONTENT_Z_INDEX = UI_Z_INDEX.modalContent;
|
|
|
21
22
|
|
|
22
23
|
export const modalOverlayStyle: CSSProperties = { zIndex: UI_Z_INDEX.modal };
|
|
23
24
|
export const modalContentStyle: CSSProperties = { zIndex: UI_Z_INDEX.modalContent };
|
|
25
|
+
export const dropdownStyle: CSSProperties = { zIndex: UI_Z_INDEX.dropdown };
|
package/src/ui/alert-dialog.tsx
CHANGED
|
@@ -9,6 +9,12 @@ import {
|
|
|
9
9
|
modalOverlayStyle,
|
|
10
10
|
} from "../lib/overlay-z-index"
|
|
11
11
|
import { buttonVariants } from "./button"
|
|
12
|
+
import {
|
|
13
|
+
FloatingWindowChrome,
|
|
14
|
+
FloatingWindowProvider,
|
|
15
|
+
useFloatingWindow,
|
|
16
|
+
useFloatingWindowDrag,
|
|
17
|
+
} from "./floating-window"
|
|
12
18
|
|
|
13
19
|
const AlertDialog = AlertDialogPrimitive.Root
|
|
14
20
|
|
|
@@ -32,39 +38,107 @@ const AlertDialogOverlay = React.forwardRef<
|
|
|
32
38
|
))
|
|
33
39
|
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
|
34
40
|
|
|
41
|
+
type AlertDialogContentProps = React.ComponentPropsWithoutRef<
|
|
42
|
+
typeof AlertDialogPrimitive.Content
|
|
43
|
+
> & {
|
|
44
|
+
overlayClassName?: string
|
|
45
|
+
movable?: boolean
|
|
46
|
+
resizable?: boolean
|
|
47
|
+
minWidth?: number
|
|
48
|
+
minHeight?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
const AlertDialogContent = React.forwardRef<
|
|
36
52
|
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
AlertDialogContentProps
|
|
54
|
+
>(
|
|
55
|
+
(
|
|
56
|
+
{
|
|
57
|
+
className,
|
|
58
|
+
style,
|
|
59
|
+
overlayClassName,
|
|
60
|
+
children,
|
|
61
|
+
movable = true,
|
|
62
|
+
resizable = true,
|
|
63
|
+
minWidth,
|
|
64
|
+
minHeight,
|
|
65
|
+
...props
|
|
66
|
+
},
|
|
67
|
+
ref
|
|
68
|
+
) => {
|
|
69
|
+
const floatingEnabled = movable || resizable
|
|
70
|
+
const {
|
|
71
|
+
setNodeRef,
|
|
72
|
+
floatingStyle,
|
|
73
|
+
onDragPointerDown,
|
|
74
|
+
onResizePointerDown,
|
|
75
|
+
dragContext,
|
|
76
|
+
} = useFloatingWindow({
|
|
77
|
+
enabled: floatingEnabled,
|
|
78
|
+
minWidth,
|
|
79
|
+
minHeight,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const setRefs = React.useCallback(
|
|
83
|
+
(node: HTMLDivElement | null) => {
|
|
84
|
+
setNodeRef(node)
|
|
85
|
+
if (typeof ref === "function") ref(node)
|
|
86
|
+
else if (ref) ref.current = node
|
|
87
|
+
},
|
|
88
|
+
[ref, setNodeRef]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<AlertDialogPortal>
|
|
93
|
+
<AlertDialogOverlay className={overlayClassName} />
|
|
94
|
+
<AlertDialogPrimitive.Content
|
|
95
|
+
ref={setRefs}
|
|
96
|
+
style={{ ...modalContentStyle, ...floatingStyle, ...style }}
|
|
97
|
+
className={cn(
|
|
98
|
+
"fixed left-[50%] top-[50%] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 pt-8 shadow-lg transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
|
99
|
+
floatingStyle &&
|
|
100
|
+
"max-h-none translate-x-0 translate-y-0 animate-none overflow-auto transition-none data-[state=open]:zoom-in-100 data-[state=open]:slide-in-from-left-0 data-[state=open]:slide-in-from-top-0",
|
|
101
|
+
className
|
|
102
|
+
)}
|
|
103
|
+
{...props}
|
|
104
|
+
>
|
|
105
|
+
<FloatingWindowProvider value={dragContext}>
|
|
106
|
+
<FloatingWindowChrome
|
|
107
|
+
enabled={floatingEnabled}
|
|
108
|
+
onDragPointerDown={movable ? onDragPointerDown : () => undefined}
|
|
109
|
+
onResizePointerDown={
|
|
110
|
+
resizable ? onResizePointerDown : () => () => undefined
|
|
111
|
+
}
|
|
112
|
+
/>
|
|
113
|
+
{children}
|
|
114
|
+
</FloatingWindowProvider>
|
|
115
|
+
</AlertDialogPrimitive.Content>
|
|
116
|
+
</AlertDialogPortal>
|
|
117
|
+
)
|
|
39
118
|
}
|
|
40
|
-
|
|
41
|
-
<AlertDialogPortal>
|
|
42
|
-
<AlertDialogOverlay className={overlayClassName} />
|
|
43
|
-
<AlertDialogPrimitive.Content
|
|
44
|
-
ref={ref}
|
|
45
|
-
style={{ ...modalContentStyle, ...style }}
|
|
46
|
-
className={cn(
|
|
47
|
-
"fixed left-[50%] top-[50%] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
|
48
|
-
className
|
|
49
|
-
)}
|
|
50
|
-
{...props}
|
|
51
|
-
/>
|
|
52
|
-
</AlertDialogPortal>
|
|
53
|
-
))
|
|
119
|
+
)
|
|
54
120
|
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
|
55
121
|
|
|
56
122
|
const AlertDialogHeader = ({
|
|
57
123
|
className,
|
|
124
|
+
onPointerDown,
|
|
58
125
|
...props
|
|
59
|
-
}: React.HTMLAttributes<HTMLDivElement>) =>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
className
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
126
|
+
}: React.HTMLAttributes<HTMLDivElement>) => {
|
|
127
|
+
const drag = useFloatingWindowDrag()
|
|
128
|
+
return (
|
|
129
|
+
<div
|
|
130
|
+
className={cn(
|
|
131
|
+
"flex cursor-grab flex-col space-y-2 text-center active:cursor-grabbing sm:text-left",
|
|
132
|
+
className
|
|
133
|
+
)}
|
|
134
|
+
onPointerDown={(event) => {
|
|
135
|
+
drag?.onDragPointerDown(event)
|
|
136
|
+
onPointerDown?.(event)
|
|
137
|
+
}}
|
|
138
|
+
{...props}
|
|
139
|
+
/>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
68
142
|
AlertDialogHeader.displayName = "AlertDialogHeader"
|
|
69
143
|
|
|
70
144
|
const AlertDialogFooter = ({
|
package/src/ui/dialog.tsx
CHANGED
|
@@ -9,6 +9,12 @@ import {
|
|
|
9
9
|
modalContentStyle,
|
|
10
10
|
modalOverlayStyle,
|
|
11
11
|
} from "../lib/overlay-z-index"
|
|
12
|
+
import {
|
|
13
|
+
FloatingWindowChrome,
|
|
14
|
+
FloatingWindowProvider,
|
|
15
|
+
useFloatingWindow,
|
|
16
|
+
useFloatingWindowDrag,
|
|
17
|
+
} from "./floating-window"
|
|
12
18
|
|
|
13
19
|
const Dialog = DialogPrimitive.Root
|
|
14
20
|
|
|
@@ -34,45 +40,127 @@ const DialogOverlay = React.forwardRef<
|
|
|
34
40
|
))
|
|
35
41
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
36
42
|
|
|
43
|
+
type DialogContentProps = React.ComponentPropsWithoutRef<
|
|
44
|
+
typeof DialogPrimitive.Content
|
|
45
|
+
> & {
|
|
46
|
+
overlayClassName?: string
|
|
47
|
+
/** 默认 true;AI 浮窗等场景可关掉,避免整屏黑遮罩 */
|
|
48
|
+
showOverlay?: boolean
|
|
49
|
+
/** 默认 true:显示右上角关闭按钮 */
|
|
50
|
+
showClose?: boolean
|
|
51
|
+
/** 默认 true:支持拖动标题条移动 */
|
|
52
|
+
movable?: boolean
|
|
53
|
+
/** 默认 true:支持边缘/角落缩放 */
|
|
54
|
+
resizable?: boolean
|
|
55
|
+
minWidth?: number
|
|
56
|
+
minHeight?: number
|
|
57
|
+
}
|
|
58
|
+
|
|
37
59
|
const DialogContent = React.forwardRef<
|
|
38
60
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
39
|
-
|
|
40
|
-
|
|
61
|
+
DialogContentProps
|
|
62
|
+
>(
|
|
63
|
+
(
|
|
64
|
+
{
|
|
65
|
+
className,
|
|
66
|
+
style,
|
|
67
|
+
children,
|
|
68
|
+
overlayClassName,
|
|
69
|
+
showOverlay = true,
|
|
70
|
+
showClose = true,
|
|
71
|
+
movable = true,
|
|
72
|
+
resizable = true,
|
|
73
|
+
minWidth,
|
|
74
|
+
minHeight,
|
|
75
|
+
...props
|
|
76
|
+
},
|
|
77
|
+
ref
|
|
78
|
+
) => {
|
|
79
|
+
const floatingEnabled = movable || resizable
|
|
80
|
+
const {
|
|
81
|
+
setNodeRef,
|
|
82
|
+
floatingStyle,
|
|
83
|
+
onDragPointerDown,
|
|
84
|
+
onResizePointerDown,
|
|
85
|
+
dragContext,
|
|
86
|
+
} = useFloatingWindow({
|
|
87
|
+
enabled: floatingEnabled,
|
|
88
|
+
minWidth,
|
|
89
|
+
minHeight,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const setRefs = React.useCallback(
|
|
93
|
+
(node: HTMLDivElement | null) => {
|
|
94
|
+
setNodeRef(node)
|
|
95
|
+
if (typeof ref === "function") ref(node)
|
|
96
|
+
else if (ref) ref.current = node
|
|
97
|
+
},
|
|
98
|
+
[ref, setNodeRef]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<DialogPortal>
|
|
103
|
+
{showOverlay ? (
|
|
104
|
+
<DialogOverlay className={overlayClassName} />
|
|
105
|
+
) : null}
|
|
106
|
+
<DialogPrimitive.Content
|
|
107
|
+
ref={setRefs}
|
|
108
|
+
style={{ ...modalContentStyle, ...floatingStyle, ...style }}
|
|
109
|
+
className={cn(
|
|
110
|
+
"fixed left-[50%] top-[50%] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
|
111
|
+
floatingStyle &&
|
|
112
|
+
"max-h-none translate-x-0 translate-y-0 animate-none overflow-auto transition-none data-[state=open]:zoom-in-100 data-[state=open]:slide-in-from-left-0 data-[state=open]:slide-in-from-top-0",
|
|
113
|
+
className
|
|
114
|
+
)}
|
|
115
|
+
{...props}
|
|
116
|
+
>
|
|
117
|
+
<FloatingWindowProvider value={dragContext}>
|
|
118
|
+
<FloatingWindowChrome
|
|
119
|
+
enabled={floatingEnabled}
|
|
120
|
+
onDragPointerDown={movable ? onDragPointerDown : () => undefined}
|
|
121
|
+
onResizePointerDown={
|
|
122
|
+
resizable ? onResizePointerDown : () => () => undefined
|
|
123
|
+
}
|
|
124
|
+
/>
|
|
125
|
+
{children}
|
|
126
|
+
</FloatingWindowProvider>
|
|
127
|
+
{showClose ? (
|
|
128
|
+
<DialogPrimitive.Close
|
|
129
|
+
data-no-drag
|
|
130
|
+
className="absolute right-3 top-3 z-50 inline-flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"
|
|
131
|
+
>
|
|
132
|
+
<X className="h-4 w-4" />
|
|
133
|
+
<span className="sr-only">Close</span>
|
|
134
|
+
</DialogPrimitive.Close>
|
|
135
|
+
) : null}
|
|
136
|
+
</DialogPrimitive.Content>
|
|
137
|
+
</DialogPortal>
|
|
138
|
+
)
|
|
41
139
|
}
|
|
42
|
-
|
|
43
|
-
<DialogPortal>
|
|
44
|
-
<DialogOverlay className={overlayClassName} />
|
|
45
|
-
<DialogPrimitive.Content
|
|
46
|
-
ref={ref}
|
|
47
|
-
style={{ ...modalContentStyle, ...style }}
|
|
48
|
-
className={cn(
|
|
49
|
-
"fixed left-[50%] top-[50%] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
|
50
|
-
className
|
|
51
|
-
)}
|
|
52
|
-
{...props}
|
|
53
|
-
>
|
|
54
|
-
{children}
|
|
55
|
-
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
|
56
|
-
<X className="h-4 w-4" />
|
|
57
|
-
<span className="sr-only">Close</span>
|
|
58
|
-
</DialogPrimitive.Close>
|
|
59
|
-
</DialogPrimitive.Content>
|
|
60
|
-
</DialogPortal>
|
|
61
|
-
))
|
|
140
|
+
)
|
|
62
141
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
|
63
142
|
|
|
64
143
|
const DialogHeader = ({
|
|
65
144
|
className,
|
|
145
|
+
onPointerDown,
|
|
66
146
|
...props
|
|
67
|
-
}: React.HTMLAttributes<HTMLDivElement>) =>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
className
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
147
|
+
}: React.HTMLAttributes<HTMLDivElement>) => {
|
|
148
|
+
const drag = useFloatingWindowDrag()
|
|
149
|
+
return (
|
|
150
|
+
<div
|
|
151
|
+
className={cn(
|
|
152
|
+
// Reserve space for the absolute close button so titles/actions never sit under it
|
|
153
|
+
"flex cursor-grab flex-col space-y-1.5 pr-10 text-center active:cursor-grabbing sm:text-left",
|
|
154
|
+
className
|
|
155
|
+
)}
|
|
156
|
+
onPointerDown={(event) => {
|
|
157
|
+
drag?.onDragPointerDown(event)
|
|
158
|
+
onPointerDown?.(event)
|
|
159
|
+
}}
|
|
160
|
+
{...props}
|
|
161
|
+
/>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
76
164
|
DialogHeader.displayName = "DialogHeader"
|
|
77
165
|
|
|
78
166
|
const DialogFooter = ({
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
export type FloatingBounds = {
|
|
7
|
+
x: number
|
|
8
|
+
y: number
|
|
9
|
+
width: number
|
|
10
|
+
height: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type ResizeEdge = "n" | "s" | "e" | "w" | "ne" | "nw" | "se" | "sw"
|
|
14
|
+
|
|
15
|
+
const DEFAULT_MIN_WIDTH = 320
|
|
16
|
+
const DEFAULT_MIN_HEIGHT = 220
|
|
17
|
+
|
|
18
|
+
function clamp(n: number, min: number, max: number) {
|
|
19
|
+
return Math.min(max, Math.max(min, n))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isInteractiveDragTarget(target: EventTarget | null) {
|
|
23
|
+
if (!(target instanceof Element)) return false
|
|
24
|
+
return Boolean(
|
|
25
|
+
target.closest(
|
|
26
|
+
"button,a,input,textarea,select,option,[role='button'],[role='combobox'],[role='menuitem'],[data-no-drag],[data-radix-collection-item]"
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type FloatingWindowContextValue = {
|
|
32
|
+
onDragPointerDown: (event: React.PointerEvent) => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const FloatingWindowContext =
|
|
36
|
+
React.createContext<FloatingWindowContextValue | null>(null)
|
|
37
|
+
|
|
38
|
+
export function useFloatingWindowDrag() {
|
|
39
|
+
return React.useContext(FloatingWindowContext)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function FloatingWindowProvider({
|
|
43
|
+
value,
|
|
44
|
+
children,
|
|
45
|
+
}: {
|
|
46
|
+
value: FloatingWindowContextValue
|
|
47
|
+
children: React.ReactNode
|
|
48
|
+
}) {
|
|
49
|
+
return (
|
|
50
|
+
<FloatingWindowContext.Provider value={value}>
|
|
51
|
+
{children}
|
|
52
|
+
</FloatingWindowContext.Provider>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type UseFloatingWindowOptions = {
|
|
57
|
+
enabled?: boolean
|
|
58
|
+
minWidth?: number
|
|
59
|
+
minHeight?: number
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function applyBoundsToDom(el: HTMLElement | null, bounds: FloatingBounds) {
|
|
63
|
+
if (!el) return
|
|
64
|
+
el.style.setProperty("left", `${bounds.x}px`)
|
|
65
|
+
el.style.setProperty("top", `${bounds.y}px`)
|
|
66
|
+
el.style.setProperty("width", `${bounds.width}px`)
|
|
67
|
+
el.style.setProperty("height", `${bounds.height}px`)
|
|
68
|
+
el.style.setProperty("max-width", "none")
|
|
69
|
+
el.style.setProperty("transform", "none")
|
|
70
|
+
el.style.setProperty("margin", "0")
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function useFloatingWindow({
|
|
74
|
+
enabled = true,
|
|
75
|
+
minWidth = DEFAULT_MIN_WIDTH,
|
|
76
|
+
minHeight = DEFAULT_MIN_HEIGHT,
|
|
77
|
+
}: UseFloatingWindowOptions = {}) {
|
|
78
|
+
const nodeRef = React.useRef<HTMLElement | null>(null)
|
|
79
|
+
const liveBoundsRef = React.useRef<FloatingBounds | null>(null)
|
|
80
|
+
const [bounds, setBounds] = React.useState<FloatingBounds | null>(null)
|
|
81
|
+
const dragRef = React.useRef<{
|
|
82
|
+
pointerId: number
|
|
83
|
+
offsetX: number
|
|
84
|
+
offsetY: number
|
|
85
|
+
} | null>(null)
|
|
86
|
+
const resizeRef = React.useRef<{
|
|
87
|
+
pointerId: number
|
|
88
|
+
edge: ResizeEdge
|
|
89
|
+
startX: number
|
|
90
|
+
startY: number
|
|
91
|
+
origin: FloatingBounds
|
|
92
|
+
} | null>(null)
|
|
93
|
+
const rafRef = React.useRef<number | null>(null)
|
|
94
|
+
const pendingBoundsRef = React.useRef<FloatingBounds | null>(null)
|
|
95
|
+
|
|
96
|
+
const measure = React.useCallback((): FloatingBounds | null => {
|
|
97
|
+
const el = nodeRef.current
|
|
98
|
+
if (!el) return null
|
|
99
|
+
const rect = el.getBoundingClientRect()
|
|
100
|
+
return {
|
|
101
|
+
x: rect.left,
|
|
102
|
+
y: rect.top,
|
|
103
|
+
width: rect.width,
|
|
104
|
+
height: rect.height,
|
|
105
|
+
}
|
|
106
|
+
}, [])
|
|
107
|
+
|
|
108
|
+
const paintBounds = React.useCallback((next: FloatingBounds) => {
|
|
109
|
+
liveBoundsRef.current = next
|
|
110
|
+
pendingBoundsRef.current = next
|
|
111
|
+
if (rafRef.current != null) return
|
|
112
|
+
rafRef.current = window.requestAnimationFrame(() => {
|
|
113
|
+
rafRef.current = null
|
|
114
|
+
const pending = pendingBoundsRef.current
|
|
115
|
+
if (!pending) return
|
|
116
|
+
applyBoundsToDom(nodeRef.current, pending)
|
|
117
|
+
})
|
|
118
|
+
}, [])
|
|
119
|
+
|
|
120
|
+
const commitBounds = React.useCallback((next: FloatingBounds) => {
|
|
121
|
+
liveBoundsRef.current = next
|
|
122
|
+
applyBoundsToDom(nodeRef.current, next)
|
|
123
|
+
setBounds(next)
|
|
124
|
+
}, [])
|
|
125
|
+
|
|
126
|
+
const ensureBounds = React.useCallback((): FloatingBounds | null => {
|
|
127
|
+
if (liveBoundsRef.current) return liveBoundsRef.current
|
|
128
|
+
const measured = measure()
|
|
129
|
+
if (!measured) return null
|
|
130
|
+
// Switch from centered transform layout to absolute coords immediately (no React lag).
|
|
131
|
+
commitBounds(measured)
|
|
132
|
+
return measured
|
|
133
|
+
}, [commitBounds, measure])
|
|
134
|
+
|
|
135
|
+
const setNodeRef = React.useCallback((node: HTMLElement | null) => {
|
|
136
|
+
nodeRef.current = node
|
|
137
|
+
if (node && liveBoundsRef.current) {
|
|
138
|
+
applyBoundsToDom(node, liveBoundsRef.current)
|
|
139
|
+
}
|
|
140
|
+
}, [])
|
|
141
|
+
|
|
142
|
+
React.useEffect(() => {
|
|
143
|
+
if (!enabled) return
|
|
144
|
+
|
|
145
|
+
const onPointerMove = (event: PointerEvent) => {
|
|
146
|
+
const drag = dragRef.current
|
|
147
|
+
if (drag && event.pointerId === drag.pointerId) {
|
|
148
|
+
event.preventDefault()
|
|
149
|
+
const prev = liveBoundsRef.current
|
|
150
|
+
if (!prev) return
|
|
151
|
+
const maxX = Math.max(0, window.innerWidth - prev.width)
|
|
152
|
+
const maxY = Math.max(0, window.innerHeight - 48)
|
|
153
|
+
paintBounds({
|
|
154
|
+
...prev,
|
|
155
|
+
x: clamp(event.clientX - drag.offsetX, 0, maxX),
|
|
156
|
+
y: clamp(event.clientY - drag.offsetY, 0, maxY),
|
|
157
|
+
})
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const resize = resizeRef.current
|
|
162
|
+
if (!resize || event.pointerId !== resize.pointerId) return
|
|
163
|
+
event.preventDefault()
|
|
164
|
+
const { edge, startX, startY, origin } = resize
|
|
165
|
+
const dx = event.clientX - startX
|
|
166
|
+
const dy = event.clientY - startY
|
|
167
|
+
let { x, y, width, height } = origin
|
|
168
|
+
|
|
169
|
+
if (edge.includes("e")) {
|
|
170
|
+
width = clamp(origin.width + dx, minWidth, window.innerWidth - origin.x)
|
|
171
|
+
}
|
|
172
|
+
if (edge.includes("s")) {
|
|
173
|
+
height = clamp(
|
|
174
|
+
origin.height + dy,
|
|
175
|
+
minHeight,
|
|
176
|
+
window.innerHeight - origin.y
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
if (edge.includes("w")) {
|
|
180
|
+
const nextWidth = clamp(
|
|
181
|
+
origin.width - dx,
|
|
182
|
+
minWidth,
|
|
183
|
+
origin.x + origin.width
|
|
184
|
+
)
|
|
185
|
+
x = origin.x + (origin.width - nextWidth)
|
|
186
|
+
width = nextWidth
|
|
187
|
+
}
|
|
188
|
+
if (edge.includes("n")) {
|
|
189
|
+
const nextHeight = clamp(
|
|
190
|
+
origin.height - dy,
|
|
191
|
+
minHeight,
|
|
192
|
+
origin.y + origin.height
|
|
193
|
+
)
|
|
194
|
+
y = origin.y + (origin.height - nextHeight)
|
|
195
|
+
height = nextHeight
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
paintBounds({
|
|
199
|
+
x: clamp(x, 0, Math.max(0, window.innerWidth - width)),
|
|
200
|
+
y: clamp(y, 0, Math.max(0, window.innerHeight - 48)),
|
|
201
|
+
width,
|
|
202
|
+
height,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const onPointerUp = (event: PointerEvent) => {
|
|
207
|
+
const wasDragging =
|
|
208
|
+
dragRef.current?.pointerId === event.pointerId ||
|
|
209
|
+
resizeRef.current?.pointerId === event.pointerId
|
|
210
|
+
if (dragRef.current?.pointerId === event.pointerId) dragRef.current = null
|
|
211
|
+
if (resizeRef.current?.pointerId === event.pointerId) {
|
|
212
|
+
resizeRef.current = null
|
|
213
|
+
}
|
|
214
|
+
if (wasDragging && liveBoundsRef.current) {
|
|
215
|
+
// Final sync into React state after gesture ends.
|
|
216
|
+
setBounds({ ...liveBoundsRef.current })
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
window.addEventListener("pointermove", onPointerMove, { passive: false })
|
|
221
|
+
window.addEventListener("pointerup", onPointerUp)
|
|
222
|
+
window.addEventListener("pointercancel", onPointerUp)
|
|
223
|
+
return () => {
|
|
224
|
+
window.removeEventListener("pointermove", onPointerMove)
|
|
225
|
+
window.removeEventListener("pointerup", onPointerUp)
|
|
226
|
+
window.removeEventListener("pointercancel", onPointerUp)
|
|
227
|
+
if (rafRef.current != null) {
|
|
228
|
+
window.cancelAnimationFrame(rafRef.current)
|
|
229
|
+
rafRef.current = null
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}, [enabled, minHeight, minWidth, paintBounds])
|
|
233
|
+
|
|
234
|
+
const onDragPointerDown = React.useCallback(
|
|
235
|
+
(event: React.PointerEvent) => {
|
|
236
|
+
if (!enabled || event.button !== 0) return
|
|
237
|
+
if (isInteractiveDragTarget(event.target)) return
|
|
238
|
+
const current = ensureBounds()
|
|
239
|
+
if (!current) return
|
|
240
|
+
event.preventDefault()
|
|
241
|
+
event.stopPropagation()
|
|
242
|
+
try {
|
|
243
|
+
;(event.currentTarget as HTMLElement).setPointerCapture?.(event.pointerId)
|
|
244
|
+
} catch {
|
|
245
|
+
// ignore
|
|
246
|
+
}
|
|
247
|
+
dragRef.current = {
|
|
248
|
+
pointerId: event.pointerId,
|
|
249
|
+
offsetX: event.clientX - current.x,
|
|
250
|
+
offsetY: event.clientY - current.y,
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
[enabled, ensureBounds]
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
const onResizePointerDown = React.useCallback(
|
|
257
|
+
(edge: ResizeEdge) => (event: React.PointerEvent) => {
|
|
258
|
+
if (!enabled || event.button !== 0) return
|
|
259
|
+
const current = ensureBounds()
|
|
260
|
+
if (!current) return
|
|
261
|
+
event.preventDefault()
|
|
262
|
+
event.stopPropagation()
|
|
263
|
+
try {
|
|
264
|
+
;(event.currentTarget as HTMLElement).setPointerCapture?.(
|
|
265
|
+
event.pointerId
|
|
266
|
+
)
|
|
267
|
+
} catch {
|
|
268
|
+
// ignore
|
|
269
|
+
}
|
|
270
|
+
resizeRef.current = {
|
|
271
|
+
pointerId: event.pointerId,
|
|
272
|
+
edge,
|
|
273
|
+
startX: event.clientX,
|
|
274
|
+
startY: event.clientY,
|
|
275
|
+
origin: { ...current },
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
[enabled, ensureBounds]
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
const floatingStyle: React.CSSProperties | undefined = bounds
|
|
282
|
+
? {
|
|
283
|
+
left: bounds.x,
|
|
284
|
+
top: bounds.y,
|
|
285
|
+
width: bounds.width,
|
|
286
|
+
height: bounds.height,
|
|
287
|
+
maxWidth: "none",
|
|
288
|
+
transform: "none",
|
|
289
|
+
margin: 0,
|
|
290
|
+
}
|
|
291
|
+
: undefined
|
|
292
|
+
|
|
293
|
+
const resetBounds = React.useCallback(() => {
|
|
294
|
+
liveBoundsRef.current = null
|
|
295
|
+
setBounds(null)
|
|
296
|
+
}, [])
|
|
297
|
+
|
|
298
|
+
const dragContext = React.useMemo(
|
|
299
|
+
() => ({ onDragPointerDown }),
|
|
300
|
+
[onDragPointerDown]
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
bounds,
|
|
305
|
+
setNodeRef,
|
|
306
|
+
floatingStyle,
|
|
307
|
+
onDragPointerDown,
|
|
308
|
+
onResizePointerDown,
|
|
309
|
+
resetBounds,
|
|
310
|
+
dragContext,
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const RESIZE_HANDLES: Array<{
|
|
315
|
+
edge: ResizeEdge
|
|
316
|
+
className: string
|
|
317
|
+
}> = [
|
|
318
|
+
{ edge: "n", className: "left-2 right-2 top-0 h-1.5 cursor-n-resize" },
|
|
319
|
+
{ edge: "s", className: "left-2 right-2 bottom-0 h-1.5 cursor-s-resize" },
|
|
320
|
+
{ edge: "e", className: "top-2 bottom-2 right-0 w-1.5 cursor-e-resize" },
|
|
321
|
+
{ edge: "w", className: "top-2 bottom-2 left-0 w-1.5 cursor-w-resize" },
|
|
322
|
+
{ edge: "ne", className: "right-0 top-0 h-3 w-3 cursor-ne-resize" },
|
|
323
|
+
{ edge: "nw", className: "left-0 top-0 h-3 w-3 cursor-nw-resize" },
|
|
324
|
+
{ edge: "se", className: "right-0 bottom-0 h-3 w-3 cursor-se-resize" },
|
|
325
|
+
{ edge: "sw", className: "left-0 bottom-0 h-3 w-3 cursor-sw-resize" },
|
|
326
|
+
]
|
|
327
|
+
|
|
328
|
+
export function FloatingWindowChrome({
|
|
329
|
+
enabled = true,
|
|
330
|
+
onDragPointerDown,
|
|
331
|
+
onResizePointerDown,
|
|
332
|
+
className,
|
|
333
|
+
}: {
|
|
334
|
+
enabled?: boolean
|
|
335
|
+
onDragPointerDown: (event: React.PointerEvent) => void
|
|
336
|
+
onResizePointerDown: (edge: ResizeEdge) => (event: React.PointerEvent) => void
|
|
337
|
+
className?: string
|
|
338
|
+
}) {
|
|
339
|
+
if (!enabled) return null
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<>
|
|
343
|
+
<div
|
|
344
|
+
data-floating-drag-handle
|
|
345
|
+
onPointerDown={onDragPointerDown}
|
|
346
|
+
className={cn(
|
|
347
|
+
"absolute inset-x-10 top-0 z-20 flex h-9 cursor-grab touch-none items-center justify-center active:cursor-grabbing",
|
|
348
|
+
className
|
|
349
|
+
)}
|
|
350
|
+
aria-hidden
|
|
351
|
+
>
|
|
352
|
+
<div className="h-1 w-12 rounded-full bg-muted-foreground/40" />
|
|
353
|
+
</div>
|
|
354
|
+
{RESIZE_HANDLES.map((handle) => (
|
|
355
|
+
<div
|
|
356
|
+
key={handle.edge}
|
|
357
|
+
data-floating-resize={handle.edge}
|
|
358
|
+
onPointerDown={onResizePointerDown(handle.edge)}
|
|
359
|
+
className={cn("absolute z-30 touch-none", handle.className)}
|
|
360
|
+
aria-hidden
|
|
361
|
+
/>
|
|
362
|
+
))}
|
|
363
|
+
</>
|
|
364
|
+
)
|
|
365
|
+
}
|
package/src/ui/index.ts
CHANGED
package/src/ui/select.tsx
CHANGED
|
@@ -5,11 +5,10 @@ import * as SelectPrimitive from "@radix-ui/react-select"
|
|
|
5
5
|
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
|
6
6
|
|
|
7
7
|
import { cn } from "../lib/utils"
|
|
8
|
+
import { dropdownStyle } from "../lib/overlay-z-index"
|
|
8
9
|
|
|
9
10
|
const Select = SelectPrimitive.Root
|
|
10
|
-
|
|
11
11
|
const SelectGroup = SelectPrimitive.Group
|
|
12
|
-
|
|
13
12
|
const SelectValue = SelectPrimitive.Value
|
|
14
13
|
|
|
15
14
|
const SelectTrigger = React.forwardRef<
|
|
@@ -70,12 +69,13 @@ SelectScrollDownButton.displayName =
|
|
|
70
69
|
const SelectContent = React.forwardRef<
|
|
71
70
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
72
71
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
73
|
-
>(({ className, children, position = "popper", ...props }, ref) => (
|
|
72
|
+
>(({ className, children, position = "popper", style, ...props }, ref) => (
|
|
74
73
|
<SelectPrimitive.Portal>
|
|
75
74
|
<SelectPrimitive.Content
|
|
76
75
|
ref={ref}
|
|
76
|
+
style={{ ...dropdownStyle, ...style }}
|
|
77
77
|
className={cn(
|
|
78
|
-
"relative
|
|
78
|
+
"relative max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
|
|
79
79
|
position === "popper" &&
|
|
80
80
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
81
81
|
className
|