@arronqzy/ui 1.0.0 → 1.0.3
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/README.md +63 -0
- package/dist/styles.css +139 -28
- package/index.ts +1 -1
- package/package.json +2 -2
- package/src/lib/overlay-z-index.ts +22 -5
- 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/src/ui/sonner.tsx +3 -0
- package/src/ui/toast.tsx +4 -2
- package/src/ui/toaster.tsx +8 -3
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @arronqzy/ui
|
|
2
|
+
|
|
3
|
+
Abuilder 的**共享 UI 组件库**,基于 [shadcn/ui](https://ui.shadcn.com/) + Radix UI + Tailwind CSS,为编辑器与蓝图提供统一视觉与交互。
|
|
4
|
+
|
|
5
|
+
## 功能概览
|
|
6
|
+
|
|
7
|
+
- **基础组件**:Button、Input、Dialog、Dropdown、Tabs、Tooltip、Toast 等
|
|
8
|
+
- **表单**:与 `react-hook-form`、`zod` 集成的表单控件
|
|
9
|
+
- **布局**:Resizable Panels、ScrollArea、Separator
|
|
10
|
+
- **主题**:`ThemeProvider` 支持亮/暗色与 `class` 策略
|
|
11
|
+
- **层级管理**:`UI_Z_INDEX` / `MODAL_Z_INDEX` 统一浮层 z-index
|
|
12
|
+
- **图表**:封装 Recharts 常用图表(按需使用)
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @arronqzy/ui
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import "@arronqzy/ui/styles.css";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 使用
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { ThemeProvider, Button, toast } from "@arronqzy/ui";
|
|
28
|
+
import "@arronqzy/ui/styles.css";
|
|
29
|
+
|
|
30
|
+
function App() {
|
|
31
|
+
return (
|
|
32
|
+
<ThemeProvider defaultTheme="dark">
|
|
33
|
+
<Button onClick={() => toast({ title: "Hello" })}>点击</Button>
|
|
34
|
+
</ThemeProvider>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 工具函数
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { cn } from "@arronqzy/ui";
|
|
43
|
+
import { UI_Z_INDEX } from "@arronqzy/ui";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 导出说明
|
|
47
|
+
|
|
48
|
+
- `@arronqzy/ui` — 全部组件、`cn`、`ThemeProvider`、`useToast`
|
|
49
|
+
- `@arronqzy/ui/styles.css` — 编译后的 Tailwind 样式
|
|
50
|
+
|
|
51
|
+
组件源码位于 `src/ui/`,遵循 shadcn 可组合模式,可按需在本包内扩展。
|
|
52
|
+
|
|
53
|
+
## 构建样式
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pnpm -C packages/ui build:css
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
依赖 `@arronqzy/tailwind` 预设保持与设计令牌一致。
|
|
60
|
+
|
|
61
|
+
## 许可证
|
|
62
|
+
|
|
63
|
+
MIT
|
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
|
}
|
|
@@ -754,14 +779,14 @@ body {
|
|
|
754
779
|
z-index: 10000;
|
|
755
780
|
}
|
|
756
781
|
|
|
757
|
-
.z-\[100\] {
|
|
758
|
-
z-index: 100;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
782
|
.z-\[1\] {
|
|
762
783
|
z-index: 1;
|
|
763
784
|
}
|
|
764
785
|
|
|
786
|
+
.z-toast {
|
|
787
|
+
z-index: 99999;
|
|
788
|
+
}
|
|
789
|
+
|
|
765
790
|
.-mx-1 {
|
|
766
791
|
margin-left: -0.25rem;
|
|
767
792
|
margin-right: -0.25rem;
|
|
@@ -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
|
}
|
|
@@ -2300,6 +2419,10 @@ body {
|
|
|
2300
2419
|
pointer-events: none;
|
|
2301
2420
|
}
|
|
2302
2421
|
|
|
2422
|
+
.data-\[state\=closed\]\:pointer-events-none[data-state="closed"] {
|
|
2423
|
+
pointer-events: none;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2303
2426
|
.data-\[panel-group-direction\=vertical\]\:h-px[data-panel-group-direction="vertical"] {
|
|
2304
2427
|
height: 1px;
|
|
2305
2428
|
}
|
|
@@ -2493,10 +2616,6 @@ body {
|
|
|
2493
2616
|
color: hsl(var(--accent-foreground));
|
|
2494
2617
|
}
|
|
2495
2618
|
|
|
2496
|
-
.data-\[state\=open\]\:text-muted-foreground[data-state="open"] {
|
|
2497
|
-
color: hsl(var(--muted-foreground));
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
2619
|
.data-\[disabled\=true\]\:opacity-50[data-disabled="true"] {
|
|
2501
2620
|
opacity: 0.5;
|
|
2502
2621
|
}
|
|
@@ -2625,6 +2744,10 @@ body {
|
|
|
2625
2744
|
--tw-exit-scale: .95;
|
|
2626
2745
|
}
|
|
2627
2746
|
|
|
2747
|
+
.data-\[state\=open\]\:zoom-in-100[data-state="open"] {
|
|
2748
|
+
--tw-enter-scale: 1;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2628
2751
|
.data-\[state\=open\]\:zoom-in-90[data-state="open"] {
|
|
2629
2752
|
--tw-enter-scale: .9;
|
|
2630
2753
|
}
|
|
@@ -2701,6 +2824,10 @@ body {
|
|
|
2701
2824
|
--tw-enter-translate-x: -100%;
|
|
2702
2825
|
}
|
|
2703
2826
|
|
|
2827
|
+
.data-\[state\=open\]\:slide-in-from-left-0[data-state="open"] {
|
|
2828
|
+
--tw-enter-translate-x: -0px;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2704
2831
|
.data-\[state\=open\]\:slide-in-from-left-1\/2[data-state="open"] {
|
|
2705
2832
|
--tw-enter-translate-x: -50%;
|
|
2706
2833
|
}
|
|
@@ -2713,6 +2840,10 @@ body {
|
|
|
2713
2840
|
--tw-enter-translate-y: -100%;
|
|
2714
2841
|
}
|
|
2715
2842
|
|
|
2843
|
+
.data-\[state\=open\]\:slide-in-from-top-0[data-state="open"] {
|
|
2844
|
+
--tw-enter-translate-y: -0px;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2716
2847
|
.data-\[state\=open\]\:slide-in-from-top-\[48\%\][data-state="open"] {
|
|
2717
2848
|
--tw-enter-translate-y: -48%;
|
|
2718
2849
|
}
|
|
@@ -2769,18 +2900,6 @@ body {
|
|
|
2769
2900
|
}
|
|
2770
2901
|
|
|
2771
2902
|
@media (min-width: 640px) {
|
|
2772
|
-
.sm\:bottom-0 {
|
|
2773
|
-
bottom: 0px;
|
|
2774
|
-
}
|
|
2775
|
-
|
|
2776
|
-
.sm\:right-0 {
|
|
2777
|
-
right: 0px;
|
|
2778
|
-
}
|
|
2779
|
-
|
|
2780
|
-
.sm\:top-auto {
|
|
2781
|
-
top: auto;
|
|
2782
|
-
}
|
|
2783
|
-
|
|
2784
2903
|
.sm\:mt-0 {
|
|
2785
2904
|
margin-top: 0px;
|
|
2786
2905
|
}
|
|
@@ -2793,10 +2912,6 @@ body {
|
|
|
2793
2912
|
flex-direction: row;
|
|
2794
2913
|
}
|
|
2795
2914
|
|
|
2796
|
-
.sm\:flex-col {
|
|
2797
|
-
flex-direction: column;
|
|
2798
|
-
}
|
|
2799
|
-
|
|
2800
2915
|
.sm\:justify-end {
|
|
2801
2916
|
justify-content: flex-end;
|
|
2802
2917
|
}
|
|
@@ -2818,10 +2933,6 @@ body {
|
|
|
2818
2933
|
.sm\:text-left {
|
|
2819
2934
|
text-align: left;
|
|
2820
2935
|
}
|
|
2821
|
-
|
|
2822
|
-
.data-\[state\=open\]\:sm\:slide-in-from-bottom-full[data-state="open"] {
|
|
2823
|
-
--tw-enter-translate-y: 100%;
|
|
2824
|
-
}
|
|
2825
2936
|
}
|
|
2826
2937
|
|
|
2827
2938
|
@media (min-width: 768px) {
|
package/index.ts
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
export * from "./src/ui/index";
|
|
4
4
|
export * from "./src/lib/utils";
|
|
5
|
-
export { MODAL_Z_INDEX } from "./src/lib/overlay-z-index";
|
|
5
|
+
export { MODAL_Z_INDEX, UI_Z_INDEX } from "./src/lib/overlay-z-index";
|
|
6
6
|
export * from "./src/hooks/use-toast";
|
|
7
7
|
export * from "./src/theme-provider";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": "./index.ts",
|
|
6
6
|
"./counter": "./components/counter.ts",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"typescript": "^5.5.4",
|
|
65
65
|
"@arronqzy/eslint-config": "0.0.0",
|
|
66
66
|
"@arronqzy/typescript-config": "1.0.0",
|
|
67
|
-
"@arronqzy/tailwind": "1.0.
|
|
67
|
+
"@arronqzy/tailwind": "1.0.2"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"lint": "eslint \"**/*.{ts,tsx}\"",
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import type { CSSProperties } from "react";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
export const
|
|
5
|
-
|
|
3
|
+
/** 全局浮层 z-index 刻度 */
|
|
4
|
+
export const UI_Z_INDEX = {
|
|
5
|
+
tooltip: 10000,
|
|
6
|
+
/** 普通页面下拉;弹窗内下拉须高于 modalContent */
|
|
7
|
+
dropdown: 20150,
|
|
8
|
+
popover: 20170,
|
|
9
|
+
alertDialog: 10150,
|
|
10
|
+
/** 弹窗:高于画布 Ruler、工具栏、Menubar 等 */
|
|
11
|
+
modal: 20000,
|
|
12
|
+
modalContent: 20001,
|
|
13
|
+
/** Toast 通知:始终置于最顶层 */
|
|
14
|
+
toast: 99999,
|
|
15
|
+
} as const;
|
|
6
16
|
|
|
7
|
-
|
|
8
|
-
export const
|
|
17
|
+
/** @deprecated 使用 UI_Z_INDEX.modal */
|
|
18
|
+
export const MODAL_Z_INDEX = UI_Z_INDEX.modal;
|
|
19
|
+
|
|
20
|
+
/** @deprecated 使用 UI_Z_INDEX.modalContent */
|
|
21
|
+
export const MODAL_CONTENT_Z_INDEX = UI_Z_INDEX.modalContent;
|
|
22
|
+
|
|
23
|
+
export const modalOverlayStyle: CSSProperties = { zIndex: UI_Z_INDEX.modal };
|
|
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
|
package/src/ui/sonner.tsx
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
import { useTheme } from "next-themes"
|
|
11
11
|
import { Toaster as Sonner } from "sonner"
|
|
12
12
|
|
|
13
|
+
import { UI_Z_INDEX } from "../lib/overlay-z-index"
|
|
14
|
+
|
|
13
15
|
type ToasterProps = React.ComponentProps<typeof Sonner>
|
|
14
16
|
|
|
15
17
|
const Toaster = ({ ...props }: ToasterProps) => {
|
|
@@ -19,6 +21,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|
|
19
21
|
<Sonner
|
|
20
22
|
theme={theme as ToasterProps["theme"]}
|
|
21
23
|
className="toaster group"
|
|
24
|
+
style={{ zIndex: UI_Z_INDEX.toast }}
|
|
22
25
|
icons={{
|
|
23
26
|
success: <CircleCheck className="h-4 w-4" />,
|
|
24
27
|
info: <Info className="h-4 w-4" />,
|
package/src/ui/toast.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|
|
6
6
|
import { X } from "lucide-react"
|
|
7
7
|
|
|
8
8
|
import { cn } from "../lib/utils"
|
|
9
|
+
import { UI_Z_INDEX } from "../lib/overlay-z-index"
|
|
9
10
|
|
|
10
11
|
const ToastProvider = ToastPrimitives.Provider
|
|
11
12
|
|
|
@@ -15,8 +16,9 @@ const ToastViewport = React.forwardRef<
|
|
|
15
16
|
>(({ className, ...props }, ref) => (
|
|
16
17
|
<ToastPrimitives.Viewport
|
|
17
18
|
ref={ref}
|
|
19
|
+
style={{ zIndex: UI_Z_INDEX.toast }}
|
|
18
20
|
className={cn(
|
|
19
|
-
"fixed top-0 z-
|
|
21
|
+
"pointer-events-none fixed top-0 right-0 z-toast flex max-h-screen w-full flex-col gap-2 p-4 md:max-w-[420px]",
|
|
20
22
|
className
|
|
21
23
|
)}
|
|
22
24
|
{...props}
|
|
@@ -25,7 +27,7 @@ const ToastViewport = React.forwardRef<
|
|
|
25
27
|
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
|
26
28
|
|
|
27
29
|
const toastVariants = cva(
|
|
28
|
-
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full
|
|
30
|
+
"group pointer-events-auto data-[state=closed]:pointer-events-none relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full",
|
|
29
31
|
{
|
|
30
32
|
variants: {
|
|
31
33
|
variant: {
|
package/src/ui/toaster.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
+
import { createPortal } from "react-dom"
|
|
4
|
+
|
|
3
5
|
import { useToast } from "../hooks/use-toast"
|
|
4
6
|
import {
|
|
5
7
|
Toast,
|
|
@@ -13,8 +15,10 @@ import {
|
|
|
13
15
|
export function Toaster() {
|
|
14
16
|
const { toasts } = useToast()
|
|
15
17
|
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
+
if (typeof document === "undefined") return null
|
|
19
|
+
|
|
20
|
+
return createPortal(
|
|
21
|
+
<ToastProvider swipeDirection="right">
|
|
18
22
|
{toasts.map(function ({ id, title, description, action, ...props }) {
|
|
19
23
|
return (
|
|
20
24
|
<Toast key={id} {...props}>
|
|
@@ -30,6 +34,7 @@ export function Toaster() {
|
|
|
30
34
|
)
|
|
31
35
|
})}
|
|
32
36
|
<ToastViewport />
|
|
33
|
-
</ToastProvider
|
|
37
|
+
</ToastProvider>,
|
|
38
|
+
document.body
|
|
34
39
|
)
|
|
35
40
|
}
|