react-dockable-desktop 1.0.0 → 1.1.0

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 CHANGED
@@ -1,3 +1,11 @@
1
+ /*
2
+ * @file index.css
3
+ * @description Core stylesheet for the react-dockable-desktop layout engine.
4
+ * Contains the styling variables for design system tokens (Nord, Tokyo Night, macOS skins),
5
+ * layout splits, resizers, floating window shadows, sidebars, modal rendering,
6
+ * and specialized scaling overrides for embedded web canvases.
7
+ */
8
+
1
9
  :root {
2
10
  --bg-primary: #090b11;
3
11
  --bg-workspace: #0f111a;
@@ -8,6 +16,7 @@
8
16
  --border-color: rgba(255, 255, 255, 0.08);
9
17
  --accent-color: #38bdf8; /* Modern Cyan/Sky-blue accent */
10
18
  --accent-glow: rgba(56, 189, 248, 0.15);
19
+ --header-button-gap: 4px; /* Configurable gap spacing between titlebar action buttons */
11
20
  /* Window skins variables */
12
21
  --window-bg: rgba(20, 22, 28, var(--window-opacity, 0.85));
13
22
  --window-border: rgba(255, 255, 255, 0.08);
@@ -28,6 +37,13 @@
28
37
  --close-btn-hover-color: #ffffff;
29
38
  --close-btn-color: #858b99;
30
39
  --close-btn-active-color: #e2e8f0;
40
+ /* Tab focus states */
41
+ --tab-indicator-focused: var(--accent-color, #38bdf8);
42
+ --tab-indicator-unfocused: rgba(255, 255, 255, 0.3);
43
+ --tab-bg-active-focused: var(--bg-panel);
44
+ --tab-bg-active-unfocused: rgba(20, 23, 34, 0.55);
45
+ --tab-text-active-focused: var(--text-tab-active, #ffffff);
46
+ --tab-text-active-unfocused: rgba(255, 255, 255, 0.65);
31
47
  --resizer-bg: rgba(255, 255, 255, 0.08);
32
48
  --custom-btn-bg: rgba(255, 255, 255, 0.03);
33
49
  --custom-btn-border: rgba(255, 255, 255, 0.05);
@@ -127,7 +143,7 @@ body,
127
143
  color: var(--text-tab-inactive) !important; /* Clear readable gray */
128
144
  border: none !important;
129
145
  border-right: 1px solid var(--border-panel) !important; /* crisp separator */
130
- padding: 0 8px 0 16px !important;
146
+ padding: 0 6px 0 14px !important;
131
147
  font-size: 0.75rem;
132
148
  transition: background-color 0.1s ease, color 0.1s ease;
133
149
  display: flex;
@@ -141,6 +157,10 @@ body,
141
157
  box-sizing: border-box;
142
158
  }
143
159
 
160
+ .workspace-tab > .text-truncate {
161
+ margin-right: 12px;
162
+ }
163
+
144
164
  /* Remove original after element separator */
145
165
  .workspace-tab:not(.active):not(:last-child)::after {
146
166
  display: none !important;
@@ -151,28 +171,47 @@ body,
151
171
  color: var(--text-tab-hover) !important;
152
172
  }
153
173
 
154
- /* Active tab */
155
- .workspace-tab.active {
156
- background-color: var(--bg-panel) !important; /* matches panel content area */
157
- color: var(--text-tab-active) !important;
174
+ /* Active tab (focused) */
175
+ .workspace-tab.active.workspace-tab-active-focused {
176
+ background-color: var(--tab-bg-active-focused) !important;
177
+ color: var(--tab-text-active-focused) !important;
158
178
  font-weight: 500;
159
179
  border-right: 1px solid var(--border-panel) !important;
160
180
  z-index: 2;
161
181
  }
162
182
 
163
- /* Top accent line on active tab (VS Code style indicator) */
164
- .workspace-tab.active::before {
183
+ .workspace-tab.active.workspace-tab-active-focused::before {
165
184
  content: '';
166
185
  position: absolute;
167
186
  top: 0;
168
187
  left: 0;
169
188
  right: 0;
170
189
  height: 2px;
171
- background-color: var(--accent-color);
190
+ background-color: var(--tab-indicator-focused) !important;
172
191
  border-radius: 0 !important;
173
192
  box-shadow: 0 1px 4px var(--accent-glow);
174
193
  }
175
194
 
195
+ /* Active tab (unfocused/inactive group) */
196
+ .workspace-tab.active.workspace-tab-active-unfocused {
197
+ background-color: var(--tab-bg-active-unfocused) !important;
198
+ color: var(--tab-text-active-unfocused) !important;
199
+ font-weight: 500;
200
+ border-right: 1px solid var(--border-panel) !important;
201
+ z-index: 2;
202
+ }
203
+
204
+ .workspace-tab.active.workspace-tab-active-unfocused::before {
205
+ content: '';
206
+ position: absolute;
207
+ top: 0;
208
+ left: 0;
209
+ right: 0;
210
+ height: 2px;
211
+ background-color: var(--tab-indicator-unfocused) !important;
212
+ border-radius: 0 !important;
213
+ }
214
+
176
215
  .workspace-tab-inactive {
177
216
  color: var(--close-btn-color) !important;
178
217
  }
@@ -214,14 +253,66 @@ body,
214
253
 
215
254
  /* Drag resizer split bars */
216
255
  .resizer-bar {
217
- background-color: var(--resizer-bg) !important;
218
- transition: background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1) !important;
256
+ background-color: var(--border-panel, rgba(255, 255, 255, 0.08)) !important;
257
+ transition: background-color 0.15s ease, transform 0.15s ease !important;
258
+ position: relative;
259
+ z-index: 21;
260
+ }
261
+
262
+ /* Invisible 8px hit-box overlay to make grabbing easy */
263
+ .resizer-bar::before {
264
+ content: '';
265
+ position: absolute;
266
+ z-index: 22;
267
+ }
268
+
269
+ /* Grab hit-box for columns (cursor: col-resize) */
270
+ .resizer-bar[style*="cursor: col-resize"]::before {
271
+ top: 0;
272
+ bottom: 0;
273
+ left: -3px;
274
+ width: 8px;
275
+ height: 100%;
276
+ }
277
+
278
+ /* Grab hit-box for rows (cursor: row-resize) */
279
+ .resizer-bar[style*="cursor: row-resize"]::before {
280
+ left: 0;
281
+ right: 0;
282
+ top: -3px;
283
+ height: 8px;
284
+ width: 100%;
219
285
  }
220
286
 
287
+ /* Highlight state (on hover or active drag) */
221
288
  .resizer-bar:hover,
222
289
  .resizer-bar.active {
223
- background-color: var(--accent-color) !important;
224
- box-shadow: 0 0 8px var(--accent-glow);
290
+ background-color: color-mix(in srgb, var(--accent-color) 35%, transparent) !important;
291
+ box-shadow: none !important;
292
+ z-index: 25 !important;
293
+ }
294
+
295
+ .resizer-bar[style*="cursor: col-resize"]:hover,
296
+ .resizer-bar[style*="cursor: col-resize"].active {
297
+ transform: scaleX(2);
298
+ }
299
+
300
+ .resizer-bar[style*="cursor: row-resize"]:hover,
301
+ .resizer-bar[style*="cursor: row-resize"].active {
302
+ transform: scaleY(2);
303
+ }
304
+
305
+ /* Global body cursor overrides during active resize drag to prevent cursor flickering */
306
+ body.resizing-active {
307
+ user-select: none !important;
308
+ }
309
+ body.resizing-row-active,
310
+ body.resizing-row-active * {
311
+ cursor: row-resize !important;
312
+ }
313
+ body.resizing-col-active,
314
+ body.resizing-col-active * {
315
+ cursor: col-resize !important;
225
316
  }
226
317
 
227
318
  /* Control buttons inside tabs/windows */
@@ -426,7 +517,6 @@ body,
426
517
  border-color: rgba(56, 189, 248, 0.25) !important;
427
518
  }
428
519
 
429
- /* Floating Windows customization styling */
430
520
  .floating-window {
431
521
  display: flex;
432
522
  flex-direction: column;
@@ -437,16 +527,20 @@ body,
437
527
  -webkit-backdrop-filter: blur(16px) saturate(180%);
438
528
  background-color: var(--window-bg);
439
529
  overflow: hidden;
440
- opacity: 0.88;
441
- transition: opacity 0.2s, box-shadow 0.2s, border-color 0.2s;
530
+ transition: box-shadow 0.2s, border-color 0.2s;
442
531
  }
443
532
 
444
533
  .floating-window.v2-window-focused {
445
- opacity: 1;
446
534
  box-shadow: var(--window-shadow-focused), inset 0 1px 0 rgba(255, 255, 255, 0.08);
447
535
  border-color: var(--window-border-focused);
448
536
  }
449
537
 
538
+ /* Dim only titlebar when window is unfocused to keep panel content opaque */
539
+ .floating-window:not(.v2-window-focused) .floating-window-titlebar {
540
+ opacity: 0.65;
541
+ transition: opacity 0.2s ease;
542
+ }
543
+
450
544
  .floating-window.maximized {
451
545
  border-radius: 0;
452
546
  border: none;
@@ -720,6 +814,12 @@ body,
720
814
  --close-btn-hover-color: #212529;
721
815
  --close-btn-color: #6c757d;
722
816
  --close-btn-active-color: #212529;
817
+ --tab-indicator-focused: #0066cc;
818
+ --tab-indicator-unfocused: rgba(0, 0, 0, 0.25);
819
+ --tab-bg-active-focused: var(--bg-panel);
820
+ --tab-bg-active-unfocused: rgba(220, 224, 230, 0.65);
821
+ --tab-text-active-focused: #212529;
822
+ --tab-text-active-unfocused: #6c757d;
723
823
  --resizer-bg: rgba(0, 0, 0, 0.08);
724
824
  --custom-btn-bg: rgba(0, 0, 0, 0.03);
725
825
  --custom-btn-border: rgba(0, 0, 0, 0.08);
@@ -845,21 +945,36 @@ body,
845
945
  }
846
946
  [data-workspace-skin="macos"] .floating-window-titlebar {
847
947
  flex-direction: row-reverse !important;
948
+ position: relative !important;
949
+ }
950
+ [data-workspace-skin="macos"] .floating-window-titlebar .floating-window-title {
951
+ position: absolute !important;
952
+ left: 0 !important;
953
+ right: 0 !important;
954
+ text-align: center !important;
955
+ pointer-events: none !important;
956
+ margin: 0 !important;
957
+ justify-content: center !important;
958
+ }
959
+ [data-workspace-skin="macos"] .floating-window-titlebar .floating-window-title > * {
960
+ pointer-events: auto;
848
961
  }
849
962
  [data-workspace-skin="macos"] .floating-window-titlebar .d-flex.align-items-center {
850
963
  flex-direction: row-reverse !important;
964
+ position: relative !important;
965
+ z-index: 1 !important;
851
966
  }
852
967
  [data-workspace-skin="macos"] .custom-tab-btn:not(.btn-anchor-tab) svg {
853
968
  display: none !important;
854
969
  }
855
970
  [data-workspace-skin="macos"] .btn-close-tab {
856
- background-color: #ff5f56 !important;
971
+ background-color: #ff5f56 !important; /* Red (Close) */
857
972
  }
858
- [data-workspace-skin="macos"] .custom-tab-btn:nth-child(2) {
859
- background-color: #ffbd2e !important;
973
+ [data-workspace-skin="macos"] .btn-minimize-tab {
974
+ background-color: #ffbd2e !important; /* Yellow (Minimize) */
860
975
  }
861
- [data-workspace-skin="macos"] .custom-tab-btn:nth-child(3) {
862
- background-color: #27c93f !important;
976
+ [data-workspace-skin="macos"] .btn-maximize-tab {
977
+ background-color: #27c93f !important; /* Green (Maximize) */
863
978
  }
864
979
  [data-workspace-skin="macos"] .btn-anchor-tab {
865
980
  /* Default: Neutral Graphite/Slate dot when inactive */
@@ -1377,102 +1492,7 @@ html:not(.enable-animations) *::after {
1377
1492
  animation: none !important;
1378
1493
  }
1379
1494
 
1380
- /* Dirty Close Interception Warning Modal */
1381
- .close-warning-overlay {
1382
- position: absolute;
1383
- inset: 0;
1384
- background-color: rgba(9, 11, 17, 0.7);
1385
- backdrop-filter: blur(8px);
1386
- display: flex;
1387
- align-items: center;
1388
- justify-content: center;
1389
- z-index: 200000;
1390
- animation: fadeIn 0.2s ease-out;
1391
- }
1392
-
1393
- .close-warning-modal {
1394
- background: rgba(20, 23, 34, 0.95);
1395
- border: 1px solid rgba(255, 255, 255, 0.08);
1396
- box-shadow: 0 24px 48px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.05);
1397
- border-radius: 12px;
1398
- width: 420px;
1399
- max-width: 90%;
1400
- padding: 24px;
1401
- display: flex;
1402
- flex-direction: column;
1403
- gap: 20px;
1404
- font-family: 'Outfit', 'Inter', sans-serif;
1405
- color: var(--text-primary);
1406
- animation: scaleUp 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
1407
- }
1408
-
1409
- .close-warning-header {
1410
- display: flex;
1411
- align-items: center;
1412
- gap: 12px;
1413
- }
1414
-
1415
- .close-warning-icon {
1416
- width: 40px;
1417
- height: 40px;
1418
- border-radius: 50%;
1419
- background-color: rgba(239, 68, 68, 0.1);
1420
- color: #ef4444;
1421
- display: flex;
1422
- align-items: center;
1423
- justify-content: center;
1424
- font-size: 1.25rem;
1425
- }
1426
-
1427
- .close-warning-title {
1428
- font-size: 1.1rem;
1429
- font-weight: 600;
1430
- margin: 0;
1431
- }
1432
-
1433
- .close-warning-message {
1434
- font-size: 0.88rem;
1435
- color: var(--text-secondary);
1436
- line-height: 1.5;
1437
- margin: 0;
1438
- }
1439
-
1440
- .close-warning-footer {
1441
- display: flex;
1442
- justify-content: flex-end;
1443
- gap: 12px;
1444
- }
1445
-
1446
- .btn-warning-action {
1447
- font-size: 0.82rem;
1448
- font-weight: 500;
1449
- padding: 8px 16px;
1450
- border-radius: 6px;
1451
- cursor: pointer;
1452
- transition: all 0.15s ease;
1453
- border: none;
1454
- font-family: inherit;
1455
- }
1456
-
1457
- .btn-warning-cancel {
1458
- background-color: rgba(255, 255, 255, 0.05);
1459
- color: var(--text-primary);
1460
- border: 1px solid rgba(255, 255, 255, 0.08);
1461
- }
1462
-
1463
- .btn-warning-cancel:hover {
1464
- background-color: rgba(255, 255, 255, 0.1);
1465
- }
1466
-
1467
- .btn-warning-discard {
1468
- background-color: #ef4444;
1469
- color: #ffffff;
1470
- }
1471
1495
 
1472
- .btn-warning-discard:hover {
1473
- background-color: #dc2626;
1474
- box-shadow: 0 0 12px rgba(239, 68, 68, 0.3);
1475
- }
1476
1496
 
1477
1497
  @keyframes fadeIn {
1478
1498
  from { opacity: 0; }
@@ -1512,6 +1532,7 @@ html:not(.enable-animations) *::after {
1512
1532
  flex-direction: column;
1513
1533
  color: var(--text-primary);
1514
1534
  max-width: 90%;
1535
+ max-height: 90vh;
1515
1536
  animation: scaleUp 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
1516
1537
  overflow: hidden;
1517
1538
  }
@@ -1558,21 +1579,19 @@ html:not(.enable-animations) *::after {
1558
1579
 
1559
1580
  .v2-modal-body {
1560
1581
  padding: 10px;
1561
- overflow: auto;
1582
+ overflow-y: auto;
1583
+ max-height: calc(90vh - 70px);
1562
1584
  }
1563
1585
 
1564
1586
  /* Modal Sizes */
1565
1587
  .v2-modal-size-small {
1566
- width: 360px;
1567
- max-height: 270px; /* 4:3 aspect ratio */
1588
+ width: 420px;
1568
1589
  }
1569
1590
  .v2-modal-size-medium {
1570
1591
  width: 560px;
1571
- max-height: 420px; /* ~4:3 aspect ratio */
1572
1592
  }
1573
1593
  .v2-modal-size-large {
1574
1594
  width: 800px;
1575
- max-height: 500px; /* ~16:10 aspect ratio */
1576
1595
  }
1577
1596
  .v2-modal-size-fullscreen {
1578
1597
  width: 98vw;
@@ -1581,7 +1600,6 @@ html:not(.enable-animations) *::after {
1581
1600
  .v2-modal-size-auto {
1582
1601
  width: auto;
1583
1602
  min-width: 300px;
1584
- max-height: 80vh; /* Safe aspect ratio threshold for auto content */
1585
1603
  }
1586
1604
 
1587
1605
  /* Side Drawer Panels */
@@ -1762,5 +1780,131 @@ html:not(.enable-animations) *::after {
1762
1780
  height: 30%;
1763
1781
  }
1764
1782
 
1783
+ /*
1784
+ * Counteract scale for canvas and its siblings inside Luciad containers.
1785
+ *
1786
+ * When rendering a panel's mini hover-preview in the taskbar, the parent container
1787
+ * is scaled down using CSS transform scale(var(--preview-scale)). For standard HTML UI,
1788
+ * this works natively. However, for web maps (like WebGL / canvas-based engines such as
1789
+ * LuciadRIA), scaling down the browser container causes the rendering canvas and its absolute
1790
+ * siblings (e.g. HTML overlays, labels, shapes) to blur or offset.
1791
+ *
1792
+ * To counteract this, we apply a reverse scale transform (1 / scale) and multiply the size
1793
+ * of the canvas and its siblings by the scale factor. This forces the map engine to render
1794
+ * at full resolution inside the preview container without positioning glitches.
1795
+ */
1796
+ .taskbar-item-preview-host .luciad canvas,
1797
+ .taskbar-item-preview-host .luciad canvas ~ * {
1798
+ transform: scale(calc(1 / var(--preview-scale, 1))) !important;
1799
+ transform-origin: top left;
1800
+ width: calc(100% * var(--preview-scale, 1)) !important;
1801
+ height: calc(100% * var(--preview-scale, 1)) !important;
1802
+ }
1803
+
1804
+ /* ==========================================
1805
+ RTL (Right-to-Left) Support
1806
+ ========================================== */
1765
1807
 
1808
+ [dir="rtl"] .workspace-tab-bar {
1809
+ flex-direction: row-reverse !important;
1810
+ }
1811
+
1812
+ [dir="rtl"] .tab-headers-container {
1813
+ flex-direction: row-reverse !important;
1814
+ }
1766
1815
 
1816
+ [dir="rtl"] .workspace-tab {
1817
+ border-right: none !important;
1818
+ border-left: 1px solid var(--border-panel) !important;
1819
+ padding: 0 14px 0 6px !important;
1820
+ }
1821
+
1822
+ [dir="rtl"] .workspace-tab > .text-truncate {
1823
+ margin-right: 0 !important;
1824
+ margin-left: 12px !important;
1825
+ }
1826
+
1827
+ [dir="rtl"] .workspace-tab-icon {
1828
+ margin-right: 0 !important;
1829
+ margin-left: 6px !important;
1830
+ }
1831
+
1832
+ [dir="rtl"] .tab-header-actions {
1833
+ margin-left: 0 !important;
1834
+ margin-right: auto !important;
1835
+ }
1836
+
1837
+ [dir="rtl"] .close-tab-x {
1838
+ margin-left: 0 !important;
1839
+ margin-right: auto !important;
1840
+ }
1841
+
1842
+ /* Floating window title bar adjustments */
1843
+ [dir="rtl"] [data-workspace-skin="vscode"] .floating-window-titlebar,
1844
+ [dir="rtl"] [data-workspace-skin="chrome"] .floating-window-titlebar,
1845
+ [dir="rtl"] [data-workspace-skin="slate"] .floating-window-titlebar {
1846
+ flex-direction: row-reverse !important;
1847
+ }
1848
+
1849
+ [dir="rtl"] [data-workspace-skin="vscode"] .floating-window-titlebar .d-flex.align-items-center,
1850
+ [dir="rtl"] [data-workspace-skin="chrome"] .floating-window-titlebar .d-flex.align-items-center,
1851
+ [dir="rtl"] [data-workspace-skin="slate"] .floating-window-titlebar .d-flex.align-items-center {
1852
+ flex-direction: row-reverse !important;
1853
+ }
1854
+
1855
+ [dir="rtl"] .floating-window-title {
1856
+ margin-right: 0 !important;
1857
+ margin-left: auto !important;
1858
+ }
1859
+
1860
+ /* Side Panels transitions slide-in direction */
1861
+ [dir="rtl"] .v2-side-panel-left {
1862
+ left: auto !important;
1863
+ right: 0 !important;
1864
+ transform: translateX(100%) !important;
1865
+ }
1866
+ [dir="rtl"] .v2-side-panel-left.v2-side-panel-visible {
1867
+ transform: translateX(0) !important;
1868
+ border-right: none !important;
1869
+ border-left: 1px solid var(--border-panel, #3c3c3c) !important;
1870
+ }
1871
+
1872
+ [dir="rtl"] .v2-side-panel-right {
1873
+ right: auto !important;
1874
+ left: 0 !important;
1875
+ transform: translateX(-100%) !important;
1876
+ }
1877
+ [dir="rtl"] .v2-side-panel-right.v2-side-panel-visible {
1878
+ transform: translateX(0) !important;
1879
+ border-left: none !important;
1880
+ border-right: 1px solid var(--border-panel, #3c3c3c) !important;
1881
+ }
1882
+
1883
+ /* Minimize Taskbar adjustments */
1884
+ [dir="rtl"] .taskbar-footer-container {
1885
+ flex-direction: row-reverse !important;
1886
+ }
1887
+ [dir="rtl"] .taskbar-item-icon {
1888
+ margin-right: 0 !important;
1889
+ margin-left: 4px !important;
1890
+ }
1891
+
1892
+ /* macOS Skin - RTL floating window titlebar overrides
1893
+ Dots must stay on the LEFT in both LTR and RTL (real macOS behavior).
1894
+ Title is absolutely centered, so no direction-specific adjustment needed.
1895
+ In RTL, row-reverse naturally places the button group on the left. */
1896
+ [dir="rtl"][data-workspace-skin="macos"] .floating-window-titlebar {
1897
+ flex-direction: row-reverse !important;
1898
+ }
1899
+
1900
+ /* Button group: keep dots in correct visual order (Red → Yellow → Green)
1901
+ from left to right regardless of text direction */
1902
+ [dir="rtl"][data-workspace-skin="macos"] .floating-window-titlebar .d-flex.align-items-center {
1903
+ flex-direction: row !important;
1904
+ }
1905
+
1906
+ /* Mirror close-button margins for macOS tabs under RTL */
1907
+ [dir="rtl"][data-workspace-skin="macos"] .close-tab-x {
1908
+ margin-left: 0 !important;
1909
+ margin-right: 2px !important;
1910
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-dockable-desktop",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A premium, state-of-the-art window manager and dockable layout engine for React. Supports fluid grid splits, tabbed groups, floating resizable windows, zero-unmount state preservation, context menus, and internationalization.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -28,6 +28,7 @@
28
28
  "scripts": {
29
29
  "dev": "vite demo",
30
30
  "dev:ria": "vite demo-luciadria",
31
+ "dev:oldria": "vite demo-oldria",
31
32
  "build": "tsup && node -e \"require('fs').copyFileSync('src/index.css', 'dist/styles.css')\"",
32
33
  "demo:build": "vite build",
33
34
  "test": "vitest run",