@tcn/ui 0.15.0 → 0.17.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.
Files changed (204) hide show
  1. package/dist/card.css +1 -0
  2. package/dist/column.css +1 -1
  3. package/dist/containers.css +1 -1
  4. package/dist/containers.module-BmICKsOK.js +5 -0
  5. package/dist/containers.module-BmICKsOK.js.map +1 -0
  6. package/dist/form/field/field.js +11 -8
  7. package/dist/form/field/field.js.map +1 -1
  8. package/dist/inputs/color_input/color_picker.js +5 -2
  9. package/dist/inputs/color_input/color_picker.js.map +1 -1
  10. package/dist/inputs/combo_box/combo_box.js +18 -15
  11. package/dist/inputs/combo_box/combo_box.js.map +1 -1
  12. package/dist/inputs/date_picker/date_picker.js +13 -10
  13. package/dist/inputs/date_picker/date_picker.js.map +1 -1
  14. package/dist/inputs/date_picker/date_picker_input.js +20 -17
  15. package/dist/inputs/date_picker/date_picker_input.js.map +1 -1
  16. package/dist/inputs/date_picker/date_picker_year_selector.js +5 -2
  17. package/dist/inputs/date_picker/date_picker_year_selector.js.map +1 -1
  18. package/dist/inputs/mask_input/key_capture_input.js +26 -23
  19. package/dist/inputs/mask_input/key_capture_input.js.map +1 -1
  20. package/dist/inputs/mask_input/mask_input.js +5 -2
  21. package/dist/inputs/mask_input/mask_input.js.map +1 -1
  22. package/dist/inputs/multiselect/multiselect.js +22 -19
  23. package/dist/inputs/multiselect/multiselect.js.map +1 -1
  24. package/dist/inputs/phone_number_input/phone_number_context.js +7 -4
  25. package/dist/inputs/phone_number_input/phone_number_context.js.map +1 -1
  26. package/dist/inputs/select/select.js +5 -2
  27. package/dist/inputs/select/select.js.map +1 -1
  28. package/dist/inputs/slider/slider.js +19 -16
  29. package/dist/inputs/slider/slider.js.map +1 -1
  30. package/dist/inputs/suggestions/suggestion_list.js +5 -2
  31. package/dist/inputs/suggestions/suggestion_list.js.map +1 -1
  32. package/dist/inputs/switch/switch.js +18 -15
  33. package/dist/inputs/switch/switch.js.map +1 -1
  34. package/dist/inputs/unit_input/unit_input.js +15 -12
  35. package/dist/inputs/unit_input/unit_input.js.map +1 -1
  36. package/dist/layouts/containers/columns/column.d.ts +6 -0
  37. package/dist/layouts/containers/columns/column.d.ts.map +1 -0
  38. package/dist/layouts/containers/columns/column.js +20 -0
  39. package/dist/layouts/containers/columns/column.js.map +1 -0
  40. package/dist/layouts/containers/columns/columns.d.ts +11 -0
  41. package/dist/layouts/containers/columns/columns.d.ts.map +1 -0
  42. package/dist/layouts/containers/columns/columns.js +34 -0
  43. package/dist/layouts/containers/columns/columns.js.map +1 -0
  44. package/dist/layouts/containers/rail.d.ts +2 -5
  45. package/dist/layouts/containers/rail.d.ts.map +1 -1
  46. package/dist/layouts/containers/rail.js +17 -55
  47. package/dist/layouts/containers/rail.js.map +1 -1
  48. package/dist/layouts/containers/rows/index.d.ts +3 -0
  49. package/dist/layouts/containers/rows/index.d.ts.map +1 -0
  50. package/dist/layouts/containers/rows/index.js +7 -0
  51. package/dist/layouts/containers/rows/index.js.map +1 -0
  52. package/dist/layouts/containers/rows/row.d.ts +6 -0
  53. package/dist/layouts/containers/rows/row.d.ts.map +1 -0
  54. package/dist/layouts/containers/rows/row.js +20 -0
  55. package/dist/layouts/containers/rows/row.js.map +1 -0
  56. package/dist/layouts/containers/rows/rows.d.ts +11 -0
  57. package/dist/layouts/containers/rows/rows.d.ts.map +1 -0
  58. package/dist/layouts/containers/rows/rows.js +34 -0
  59. package/dist/layouts/containers/rows/rows.js.map +1 -0
  60. package/dist/layouts/containers/scaffold.d.ts +2 -5
  61. package/dist/layouts/containers/scaffold.d.ts.map +1 -1
  62. package/dist/layouts/containers/scaffold.js +17 -55
  63. package/dist/layouts/containers/scaffold.js.map +1 -1
  64. package/dist/layouts/index.d.ts +4 -2
  65. package/dist/layouts/index.d.ts.map +1 -1
  66. package/dist/layouts/index.js +35 -31
  67. package/dist/layouts/index.js.map +1 -1
  68. package/dist/mobile/inputs/date_picker/mobile_date_picker_header.js +5 -2
  69. package/dist/mobile/inputs/date_picker/mobile_date_picker_header.js.map +1 -1
  70. package/dist/mobile/inputs/date_picker/mobile_date_picker_input.js +5 -2
  71. package/dist/mobile/inputs/date_picker/mobile_date_picker_input.js.map +1 -1
  72. package/dist/mobile/inputs/date_picker/mobile_date_picker_year_selector.js +8 -5
  73. package/dist/mobile/inputs/date_picker/mobile_date_picker_year_selector.js.map +1 -1
  74. package/dist/navigation/tabs/state/link/tab_link.js +9 -6
  75. package/dist/navigation/tabs/state/link/tab_link.js.map +1 -1
  76. package/dist/overlay/menu/menu.js +3 -0
  77. package/dist/overlay/menu/menu.js.map +1 -1
  78. package/dist/overlay/popper/context_popper.js +8 -5
  79. package/dist/overlay/popper/context_popper.js.map +1 -1
  80. package/dist/overlay/popper/element_popper.js +9 -6
  81. package/dist/overlay/popper/element_popper.js.map +1 -1
  82. package/dist/overlay/popper/legacy/popper.js +13 -10
  83. package/dist/overlay/popper/legacy/popper.js.map +1 -1
  84. package/dist/overlay/popper/preview_popper.js +10 -7
  85. package/dist/overlay/popper/preview_popper.js.map +1 -1
  86. package/dist/overlay/tethered/tethered.js +11 -8
  87. package/dist/overlay/tethered/tethered.js.map +1 -1
  88. package/dist/resizable.css +1 -0
  89. package/dist/resizable.module-I6iyBAvM.js +5 -0
  90. package/dist/resizable.module-I6iyBAvM.js.map +1 -0
  91. package/dist/resize_handle.css +1 -0
  92. package/dist/row.css +1 -0
  93. package/dist/stacks/box/box.js +12 -9
  94. package/dist/stacks/box/box.js.map +1 -1
  95. package/dist/stacks/box/detect_resize_bounds.d.ts +1 -0
  96. package/dist/stacks/box/detect_resize_bounds.d.ts.map +1 -1
  97. package/dist/stacks/box/detect_resize_bounds.js +22 -20
  98. package/dist/stacks/box/detect_resize_bounds.js.map +1 -1
  99. package/dist/stacks/h_collapsible_box.js +17 -14
  100. package/dist/stacks/h_collapsible_box.js.map +1 -1
  101. package/dist/stacks/v_collapsible_box.js +19 -16
  102. package/dist/stacks/v_collapsible_box.js.map +1 -1
  103. package/dist/surfaces/card/card.d.ts.map +1 -1
  104. package/dist/surfaces/card/card.js +14 -6
  105. package/dist/surfaces/card/card.js.map +1 -1
  106. package/dist/surfaces/pop_confirm/pop_confirm.js +6 -4
  107. package/dist/surfaces/pop_confirm/pop_confirm.js.map +1 -1
  108. package/dist/test-setup.d.ts +2 -0
  109. package/dist/test-setup.d.ts.map +1 -0
  110. package/dist/test-setup.js +10 -0
  111. package/dist/test-setup.js.map +1 -0
  112. package/dist/themes/theme.d.ts.map +1 -1
  113. package/dist/themes/theme.js +17 -22
  114. package/dist/themes/theme.js.map +1 -1
  115. package/dist/themes/themes/ergo/ergo_theme.css +1 -1
  116. package/dist/themes/themes/ergo/ergo_theme.js +225 -27
  117. package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
  118. package/dist/utils/index.d.ts +1 -0
  119. package/dist/utils/index.d.ts.map +1 -1
  120. package/dist/utils/index.js +39 -26
  121. package/dist/utils/index.js.map +1 -1
  122. package/dist/utils/resize/context.d.ts +4 -0
  123. package/dist/utils/resize/context.d.ts.map +1 -0
  124. package/dist/utils/resize/context.js +10 -0
  125. package/dist/utils/resize/context.js.map +1 -0
  126. package/dist/utils/resize/handle_config.d.ts +32 -0
  127. package/dist/utils/resize/handle_config.d.ts.map +1 -0
  128. package/dist/utils/resize/handle_config.js +85 -0
  129. package/dist/utils/resize/handle_config.js.map +1 -0
  130. package/dist/utils/resize/index.d.ts +10 -0
  131. package/dist/utils/resize/index.d.ts.map +1 -0
  132. package/dist/utils/resize/index.js +16 -0
  133. package/dist/utils/resize/index.js.map +1 -0
  134. package/dist/utils/resize/resizable.d.ts +11 -0
  135. package/dist/utils/resize/resizable.d.ts.map +1 -0
  136. package/dist/utils/resize/resizable.js +52 -0
  137. package/dist/utils/resize/resizable.js.map +1 -0
  138. package/dist/utils/resize/resize_handle.d.ts +7 -0
  139. package/dist/utils/resize/resize_handle.d.ts.map +1 -0
  140. package/dist/utils/resize/resize_handle.js +100 -0
  141. package/dist/utils/resize/resize_handle.js.map +1 -0
  142. package/dist/utils/resize/resize_strategy.d.ts +47 -0
  143. package/dist/utils/resize/resize_strategy.d.ts.map +1 -0
  144. package/dist/utils/resize/resize_strategy.js +108 -0
  145. package/dist/utils/resize/resize_strategy.js.map +1 -0
  146. package/dist/utils/resize/types.d.ts +28 -0
  147. package/dist/utils/resize/types.d.ts.map +1 -0
  148. package/dist/utils/resize/types.js +2 -0
  149. package/dist/utils/resize/types.js.map +1 -0
  150. package/package.json +3 -3
  151. package/src/layouts/__stories__/columns.stories.tsx +77 -0
  152. package/src/layouts/__stories__/composed.stories.tsx +77 -8
  153. package/src/layouts/__stories__/rail.stories.tsx +4 -4
  154. package/src/layouts/__stories__/rows.stories.tsx +77 -0
  155. package/src/layouts/__stories__/utils.tsx +2 -84
  156. package/src/layouts/containers/columns/column.module.css +15 -0
  157. package/src/layouts/containers/columns/column.tsx +22 -0
  158. package/src/layouts/containers/columns/columns.tsx +42 -0
  159. package/src/layouts/containers/containers.module.css +27 -29
  160. package/src/layouts/containers/rail.tsx +9 -51
  161. package/src/layouts/containers/rows/index.ts +2 -0
  162. package/src/layouts/containers/rows/row.module.css +15 -0
  163. package/src/layouts/containers/rows/row.tsx +22 -0
  164. package/src/layouts/containers/rows/rows.tsx +42 -0
  165. package/src/layouts/containers/scaffold.tsx +9 -49
  166. package/src/layouts/index.ts +4 -2
  167. package/src/stacks/box/detect_resize_bounds.ts +5 -1
  168. package/src/surfaces/card/card.module.css +5 -0
  169. package/src/surfaces/card/card.stories.tsx +66 -8
  170. package/src/surfaces/card/card.tsx +6 -2
  171. package/src/surfaces/page/page.stories.tsx +109 -25
  172. package/src/surfaces/panel/__stories__/panel.stories.tsx +139 -1
  173. package/src/test-setup.ts +11 -0
  174. package/src/themes/theme.tsx +6 -16
  175. package/src/themes/themes/ergo/ergo_theme.css +223 -25
  176. package/src/utils/index.ts +2 -0
  177. package/src/utils/resize/__stories__/resizable.stories.tsx +214 -0
  178. package/src/utils/resize/__stories__/resizable_stories.module.css +47 -0
  179. package/src/utils/resize/__tests__/handle_config.test.ts +269 -0
  180. package/src/utils/resize/__tests__/resize_strategy.test.ts +163 -0
  181. package/src/utils/resize/context.ts +9 -0
  182. package/src/utils/resize/handle_config.ts +142 -0
  183. package/src/utils/resize/index.ts +37 -0
  184. package/src/utils/resize/resizable.module.css +5 -0
  185. package/src/utils/resize/resizable.tsx +97 -0
  186. package/src/utils/resize/resize_handle.module.css +146 -0
  187. package/src/utils/resize/resize_handle.tsx +165 -0
  188. package/src/utils/resize/resize_strategy.ts +190 -0
  189. package/src/utils/resize/types.ts +64 -0
  190. package/dist/containers.module-DlGySre0.js +0 -5
  191. package/dist/containers.module-DlGySre0.js.map +0 -1
  192. package/dist/layouts/column/column.d.ts +0 -10
  193. package/dist/layouts/column/column.d.ts.map +0 -1
  194. package/dist/layouts/column/column.js +0 -52
  195. package/dist/layouts/column/column.js.map +0 -1
  196. package/dist/layouts/containers/side/side.d.ts +0 -6
  197. package/dist/layouts/containers/side/side.d.ts.map +0 -1
  198. package/dist/layouts/containers/side/side.js +0 -22
  199. package/dist/layouts/containers/side/side.js.map +0 -1
  200. package/dist/side.css +0 -1
  201. package/src/layouts/column/column.module.css +0 -35
  202. package/src/layouts/column/column.tsx +0 -57
  203. package/src/layouts/containers/side/side.module.css +0 -7
  204. package/src/layouts/containers/side/side.tsx +0 -25
@@ -609,8 +609,7 @@
609
609
  min-width: var(--ergo-sizing-bar-md);
610
610
  }
611
611
 
612
- .tcn-utility-strip,
613
- .tcn-side {
612
+ .tcn-utility-strip {
614
613
  padding-block: var(--padding-medium);
615
614
  gap: var(--gap-medium);
616
615
  }
@@ -635,20 +634,19 @@
635
634
  .tcn-scaffold {
636
635
  --scaffold-divide-footer: var(--divide-footer, 1);
637
636
  --scaffold-divide-header: var(--divide-header, 1);
638
- --pad-inline: var(--padding-large);
637
+ /* Since Scaffold now acts as body too - there is no pad inline default - each outer container should set it */
638
+ /* --pad-inline: var(--padding-large); */
639
639
 
640
640
  /* Border appears on nested scaffold/rail when it follows header or utility-bar */
641
- :where(.tcn-header) + :where(.tcn-scaffold),
642
- :where(.tcn-header) + :where(.tcn-rail),
643
- :where(.tcn-utility-bar) + :where(.tcn-scaffold),
644
- :where(.tcn-utility-bar) + :where(.tcn-rail) {
645
- border-top: calc(var(--scaffold-divide-header) * 1px) solid
641
+ :where(.tcn-header) {
642
+ border-bottom: calc(var(--scaffold-divide-header) * 1px) solid
646
643
  var(--ergo-material-border);
647
644
  }
645
+ :where(.tcn-utility-bar) {
646
+ border-bottom: 1px solid var(--ergo-material-border);
647
+ }
648
648
 
649
- /* Border appears on footer when it follows scaffold/rail */
650
- :where(.tcn-scaffold) + :where(.tcn-footer),
651
- :where(.tcn-rail) + :where(.tcn-footer) {
649
+ :where(.tcn-footer) {
652
650
  border-top: calc(var(--scaffold-divide-footer) * 1px) solid
653
651
  var(--ergo-material-border);
654
652
  }
@@ -662,20 +660,15 @@
662
660
  .tcn-page {
663
661
  --divide-header: 0;
664
662
  --divide-footer: 0;
663
+ --pad-inline: var(--padding-large);
665
664
  --material: var(--background-color-tertiary);
666
665
  background-color: var(--material);
667
- padding: var(--padding-medium);
668
- }
669
-
670
- /* TODO: remove - shim to replicate Column at Page level */
671
- .tcn-page > .tcn-scaffold-stack > .tcn-scaffold > .tcn-scaffold-stack,
672
- .tcn-page > .tcn-scaffold-stack > .tcn-rail > .tcn-rail-stack {
673
- gap: var(--gap-medium);
674
666
  }
675
667
 
676
668
  /* MODAL: */
677
669
  .tcn-modal {
678
670
  --divide-header: 0;
671
+ --pad-inline: var(--padding-large);
679
672
  box-shadow: 0px 4px 34px 0px #00000096;
680
673
  border-radius: var(--ergo-shape-radius-medium);
681
674
  background-color: var(--background-color-primary);
@@ -699,6 +692,7 @@
699
692
  /* DRAWER: */
700
693
  .tcn-drawer {
701
694
  --divide-header: 0;
695
+ --pad-inline: var(--padding-large);
702
696
  box-shadow: 0px 4px 34px 0px #00000096;
703
697
  background-color: var(--background-color-primary);
704
698
  overflow: hidden;
@@ -721,6 +715,7 @@
721
715
  .tcn-window {
722
716
  box-shadow: 0px 4px 34px 0px #00000096;
723
717
  --divide-header: 0;
718
+ --pad-inline: var(--padding-large);
724
719
  border-radius: var(--ergo-shape-radius-medium);
725
720
  background-color: var(--background-color-primary);
726
721
  overflow: hidden;
@@ -745,7 +740,7 @@
745
740
  --tether-pad-size: 16px;
746
741
  --pad-inline: var(--padding-medium);
747
742
 
748
- .tcn-pop-confirm-scaffold > .tcn-scaffold-stack {
743
+ .tcn-pop-confirm-scaffold {
749
744
  border-radius: var(--ergo-shape-radius-medium);
750
745
  box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.4);
751
746
  background-color: var(--background-color-primary);
@@ -802,6 +797,7 @@
802
797
  .tcn-panel {
803
798
  --divide-header: 1;
804
799
  --divide-footer: 1;
800
+ --pad-inline: var(--padding-large);
805
801
  --material: var(--background-color-primary);
806
802
  background-color: var(--material);
807
803
  border-radius: var(--ergo-shape-radius-medium);
@@ -816,6 +812,7 @@
816
812
  .tcn-aside {
817
813
  --divide-header: 1;
818
814
  --divide-footer: 1;
815
+ --pad-inline: var(--padding-large);
819
816
 
820
817
  --material: var(--background-color-secondary);
821
818
  background-color: var(--material);
@@ -863,12 +860,70 @@
863
860
  :where(.tcn-utility-bar) {
864
861
  min-height: var(--ergo-sizing-bar-sm);
865
862
  }
863
+ }
864
+
865
+ /* Columns: */
866
+ .tcn-panel {
867
+ :where(.tcn-columns) {
868
+ padding: var(--padding-large);
869
+
870
+ :where(.tcn-column) {
871
+ gap: var(--gap-medium);
872
+ border-right: 1px solid var(--ergo-material-border);
873
+ padding-inline-end: var(--padding-medium);
874
+ }
875
+
876
+ :where(.tcn-column):not(:first-child) {
877
+ padding-inline-start: var(--padding-medium);
878
+ }
879
+ }
880
+ }
881
+
882
+ /* Resize handle offsets */
883
+ :where(.tcn-rail.tcn-columns) {
884
+ --resize-offset: calc(-1 * var(--padding-medium));
885
+ }
886
+
887
+ :where(.tcn-rail):not(.tcn-columns) {
888
+ --resize-offset: 0px;
889
+ }
890
+
891
+ .tcn-page {
892
+ :where(.tcn-columns) {
893
+ padding: var(--padding-medium);
894
+
895
+ :where(.tcn-column) {
896
+ gap: var(--gap-medium);
897
+ }
898
+
899
+ > .tcn-column:not(:last-child) {
900
+ padding-inline-end: var(--padding-medium);
901
+ }
902
+ }
903
+ }
904
+
905
+ /* Rows: */
906
+ .tcn-panel,
907
+ .tcn-card {
908
+ :where(.tcn-rows) {
909
+ :where(.tcn-row):not(:last-child) {
910
+ border-bottom: 1px solid var(--ergo-material-border);
911
+ padding-block-end: var(--padding-medium);
912
+ }
913
+
914
+ :where(.tcn-row):not(:first-child) {
915
+ padding-block-start: var(--padding-medium);
916
+ }
917
+ }
918
+ }
919
+
920
+ .tcn-page {
921
+ :where(.tcn-rows) {
922
+ padding: var(--padding-medium);
866
923
 
867
- :where(.tcn-scaffold-stack) > :where(.tcn-scaffold) > :where(.tcn-scaffold-stack),
868
- :where(.tcn-scaffold-stack) > :where(.tcn-rail) > :where(.tcn-rail-stack),
869
- :where(.tcn-rail-stack) > :where(.tcn-scaffold) > :where(.tcn-scaffold-stack),
870
- :where(.tcn-rail-stack) > :where(.tcn-rail) > :where(.tcn-rail-stack) {
871
- padding-inline: var(--padding-medium);
924
+ :where(.tcn-row):not(:last-child) {
925
+ padding-block-end: var(--padding-medium);
926
+ }
872
927
  }
873
928
  }
874
929
 
@@ -920,7 +975,7 @@
920
975
  padding-inline-end: var(--pad-inline, var(--padding-medium));
921
976
  }
922
977
 
923
- .tcn-scaffold-stack > .tcn-detail {
978
+ .tcn-scaffold > .tcn-detail {
924
979
  padding-inline: var(--pad-inline, var(--padding-medium));
925
980
  }
926
981
 
@@ -1405,4 +1460,147 @@
1405
1460
  --accent-color: var(--ergo-accent-blue);
1406
1461
  }
1407
1462
  }
1463
+
1464
+ /* ===== Resize Handle ===== */
1465
+
1466
+ /* Edge indicators: centered pill chip */
1467
+ .tcn-resize-handle[data-axis="horizontal"] {
1468
+ width: 8px;
1469
+ }
1470
+
1471
+ .tcn-resize-handle[data-axis="vertical"] {
1472
+ height: 8px;
1473
+ }
1474
+
1475
+ .tcn-resize-handle[data-axis="horizontal"]::after,
1476
+ .tcn-resize-handle[data-axis="vertical"]::after {
1477
+ content: "";
1478
+ position: absolute;
1479
+ border-radius: var(--ergo-shape-radius-medium);
1480
+ background-color: var(--ergo-grey-light);
1481
+ pointer-events: none;
1482
+ transition: background-color 0.3s;
1483
+ top: 50%;
1484
+ left: 50%;
1485
+ transform: translate(-50%, -50%);
1486
+ }
1487
+
1488
+ .tcn-resize-handle[data-axis="horizontal"]::after {
1489
+ width: 4px;
1490
+ height: 16px;
1491
+ }
1492
+
1493
+ .tcn-resize-handle[data-axis="vertical"]::after {
1494
+ width: 16px;
1495
+ height: 4px;
1496
+ }
1497
+
1498
+ /* Corner L-shape indicator */
1499
+ .tcn-resize-handle[data-axis="corner"]::after {
1500
+ content: "";
1501
+ position: absolute;
1502
+ width: 8px;
1503
+ height: 8px;
1504
+ border-style: solid;
1505
+ border-color: var(--ergo-grey-light);
1506
+ border-width: 0;
1507
+ pointer-events: none;
1508
+ transition: border-color 0.15s;
1509
+ }
1510
+
1511
+ /* Physical corners */
1512
+ .tcn-resize-handle[data-position="top-left"]::after {
1513
+ top: 0;
1514
+ left: 0;
1515
+ border-top-width: 4px;
1516
+ border-left-width: 4px;
1517
+ }
1518
+
1519
+ .tcn-resize-handle[data-position="top-right"]::after {
1520
+ top: 0;
1521
+ right: 0;
1522
+ border-top-width: 4px;
1523
+ border-right-width: 4px;
1524
+ }
1525
+
1526
+ .tcn-resize-handle[data-position="bottom-left"]::after {
1527
+ bottom: 0;
1528
+ left: 0;
1529
+ border-bottom-width: 4px;
1530
+ border-left-width: 4px;
1531
+ }
1532
+
1533
+ .tcn-resize-handle[data-position="bottom-right"]::after {
1534
+ bottom: 0;
1535
+ right: 0;
1536
+ border-bottom-width: 4px;
1537
+ border-right-width: 4px;
1538
+ }
1539
+
1540
+ /* Logical corners (RTL-aware) */
1541
+ .tcn-resize-handle[data-position="top-start"]:dir(ltr)::after {
1542
+ top: 0;
1543
+ left: 0;
1544
+ border-top-width: 4px;
1545
+ border-left-width: 4px;
1546
+ }
1547
+
1548
+ .tcn-resize-handle[data-position="top-start"]:dir(rtl)::after {
1549
+ top: 0;
1550
+ right: 0;
1551
+ border-top-width: 4px;
1552
+ border-right-width: 4px;
1553
+ }
1554
+
1555
+ .tcn-resize-handle[data-position="top-end"]:dir(ltr)::after {
1556
+ top: 0;
1557
+ right: 0;
1558
+ border-top-width: 4px;
1559
+ border-right-width: 4px;
1560
+ }
1561
+
1562
+ .tcn-resize-handle[data-position="top-end"]:dir(rtl)::after {
1563
+ top: 0;
1564
+ left: 0;
1565
+ border-top-width: 4px;
1566
+ border-left-width: 4px;
1567
+ }
1568
+
1569
+ .tcn-resize-handle[data-position="bottom-start"]:dir(ltr)::after {
1570
+ bottom: 0;
1571
+ left: 0;
1572
+ border-bottom-width: 4px;
1573
+ border-left-width: 4px;
1574
+ }
1575
+
1576
+ .tcn-resize-handle[data-position="bottom-start"]:dir(rtl)::after {
1577
+ bottom: 0;
1578
+ right: 0;
1579
+ border-bottom-width: 4px;
1580
+ border-right-width: 4px;
1581
+ }
1582
+
1583
+ .tcn-resize-handle[data-position="bottom-end"]:dir(ltr)::after {
1584
+ bottom: 0;
1585
+ right: 0;
1586
+ border-bottom-width: 4px;
1587
+ border-right-width: 4px;
1588
+ }
1589
+
1590
+ .tcn-resize-handle[data-position="bottom-end"]:dir(rtl)::after {
1591
+ bottom: 0;
1592
+ left: 0;
1593
+ border-bottom-width: 4px;
1594
+ border-left-width: 4px;
1595
+ }
1596
+
1597
+ /* Hover */
1598
+ .tcn-resize-handle[data-axis="horizontal"]:hover::after,
1599
+ .tcn-resize-handle[data-axis="vertical"]:hover::after {
1600
+ background-color: var(--ergo-grey-dark);
1601
+ }
1602
+
1603
+ .tcn-resize-handle:hover::after {
1604
+ border-color: var(--ergo-grey-dark);
1605
+ }
1408
1606
  }
@@ -21,3 +21,5 @@ export * from './types/variations.js';
21
21
 
22
22
  export * from './dnd/draggable/draggable.js';
23
23
  export * from './dnd/handle.js';
24
+
25
+ export * from './resize/index.js';
@@ -0,0 +1,214 @@
1
+ import { useRef, useState } from 'react';
2
+ import { Resizable } from '../resizable.js';
3
+ import { ResizeHandle } from '../resize_handle.js';
4
+ import { Box } from '../../../stacks/box/box.js';
5
+ import { VStack } from '../../../stacks/v_stack.js';
6
+ import { BodyText } from '../../../typography/index.js';
7
+ import styles from './resizable_stories.module.css';
8
+
9
+ export default {
10
+ title: 'Utils/Resizable',
11
+ component: Resizable,
12
+ tags: ['autodocs'],
13
+ };
14
+
15
+ export const HorizontalResize = () => {
16
+ const [width, setWidth] = useState(300);
17
+
18
+ return (
19
+ <VStack gap="8px" padding="16px" minHeight="400px">
20
+ <BodyText>Drag the right edge to resize horizontally.</BodyText>
21
+ <Resizable onWidthResize={({ width: w }) => setWidth(Math.round(w))}>
22
+ <Box width={`${width}px`} height="200px" className={styles['demo-box']}>
23
+ <BodyText>Resizable content</BodyText>
24
+ <div className={styles['size-display']}>width: {width}px</div>
25
+ </Box>
26
+ <ResizeHandle position="right" className={styles['demo-handle']} />
27
+ </Resizable>
28
+ </VStack>
29
+ );
30
+ };
31
+
32
+ export const VerticalResize = () => {
33
+ const [height, setHeight] = useState(200);
34
+
35
+ return (
36
+ <VStack gap="8px" padding="16px" minHeight="600px">
37
+ <BodyText>Drag the bottom edge to resize vertically.</BodyText>
38
+ <Resizable onHeightResize={({ height: h }) => setHeight(Math.round(h))}>
39
+ <Box width="400px" height={`${height}px`} className={styles['demo-box']}>
40
+ <BodyText>Resizable content</BodyText>
41
+ <div className={styles['size-display']}>height: {height}px</div>
42
+ </Box>
43
+ <ResizeHandle position="bottom" className={styles['demo-handle']} />
44
+ </Resizable>
45
+ </VStack>
46
+ );
47
+ };
48
+
49
+ export const CornerResize = () => {
50
+ const [width, setWidth] = useState(300);
51
+ const [height, setHeight] = useState(200);
52
+
53
+ return (
54
+ <VStack gap="8px" padding="16px" minHeight="600px">
55
+ <BodyText>
56
+ Drag the bottom-right corner to resize both axes simultaneously.
57
+ </BodyText>
58
+ <Resizable
59
+ onWidthResize={({ width: w }) => setWidth(Math.round(w))}
60
+ onHeightResize={({ height: h }) => setHeight(Math.round(h))}
61
+ >
62
+ <Box width={`${width}px`} height={`${height}px`} className={styles['demo-box']}>
63
+ <BodyText>Resizable content</BodyText>
64
+ <div className={styles['size-display']}>
65
+ {width}px × {height}px
66
+ </div>
67
+ </Box>
68
+ <ResizeHandle position="bottom-right" className={styles['demo-handle']} />
69
+ </Resizable>
70
+ </VStack>
71
+ );
72
+ };
73
+
74
+ export const MultipleHandles = () => {
75
+ const [width, setWidth] = useState(400);
76
+ const [height, setHeight] = useState(300);
77
+
78
+ return (
79
+ <VStack gap="8px" padding="16px" minHeight="600px">
80
+ <BodyText>Left, right, bottom edges and bottom-right corner — all active.</BodyText>
81
+ <Resizable
82
+ onWidthResize={({ width: w }) => setWidth(Math.round(w))}
83
+ onHeightResize={({ height: h }) => setHeight(Math.round(h))}
84
+ >
85
+ <Box width={`${width}px`} height={`${height}px`} className={styles['demo-box']}>
86
+ <BodyText>Resizable content</BodyText>
87
+ <div className={styles['size-display']}>
88
+ {width}px × {height}px
89
+ </div>
90
+ </Box>
91
+ <ResizeHandle position="left" className={styles['demo-handle']} />
92
+ <ResizeHandle position="right" className={styles['demo-handle']} />
93
+ <ResizeHandle position="bottom" className={styles['demo-handle']} />
94
+ <ResizeHandle
95
+ position="bottom-right"
96
+ className={styles['demo-handle']}
97
+ style={{
98
+ zIndex: 2,
99
+ }}
100
+ />
101
+ </Resizable>
102
+ </VStack>
103
+ );
104
+ };
105
+
106
+ export const RTL = () => {
107
+ const [widthStart, setWidthStart] = useState(300);
108
+ const [widthLeft, setWidthLeft] = useState(300);
109
+
110
+ return (
111
+ <VStack gap="16px" padding="16px" minHeight="400px">
112
+ <BodyText>
113
+ RTL context: &quot;start&quot; flips to the right edge, &quot;left&quot; stays
114
+ physical left.
115
+ </BodyText>
116
+ <div className={styles['rtl-container']}>
117
+ <VStack gap="16px">
118
+ <BodyText>position=&quot;start&quot; (logical — flips in RTL)</BodyText>
119
+ <Resizable onWidthResize={({ width: w }) => setWidthStart(Math.round(w))}>
120
+ <Box width={`${widthStart}px`} height="100px" className={styles['demo-box']}>
121
+ <div className={styles['size-display']}>start handle: {widthStart}px</div>
122
+ </Box>
123
+ <ResizeHandle position="start" className={styles['demo-handle']} />
124
+ </Resizable>
125
+
126
+ <BodyText>position=&quot;left&quot; (physical — stays on left in RTL)</BodyText>
127
+ <Resizable onWidthResize={({ width: w }) => setWidthLeft(Math.round(w))}>
128
+ <Box width={`${widthLeft}px`} height="100px" className={styles['demo-box']}>
129
+ <div className={styles['size-display']}>left handle: {widthLeft}px</div>
130
+ </Box>
131
+ <ResizeHandle position="left" className={styles['demo-handle']} />
132
+ </Resizable>
133
+ </VStack>
134
+ </div>
135
+ </VStack>
136
+ );
137
+ };
138
+
139
+ export const NestedResizable = () => {
140
+ const [outerWidth, setOuterWidth] = useState(500);
141
+ const [innerWidth, setInnerWidth] = useState(250);
142
+
143
+ return (
144
+ <VStack gap="8px" padding="16px" minHeight="400px">
145
+ <BodyText>Nested: each handle binds to its nearest Resizable provider.</BodyText>
146
+ <Resizable onWidthResize={({ width: w }) => setOuterWidth(Math.round(w))}>
147
+ <Box width={`${outerWidth}px`} height="300px" className={styles['nested-outer']}>
148
+ <BodyText>Outer ({outerWidth}px)</BodyText>
149
+ <Resizable onWidthResize={({ width: w }) => setInnerWidth(Math.round(w))}>
150
+ <Box
151
+ width={`${innerWidth}px`}
152
+ height="150px"
153
+ className={styles['nested-inner']}
154
+ >
155
+ <BodyText>Inner ({innerWidth}px)</BodyText>
156
+ </Box>
157
+ <ResizeHandle position="right" className={styles['demo-handle']} />
158
+ </Resizable>
159
+ </Box>
160
+ <ResizeHandle position="right" className={styles['demo-handle']} />
161
+ </Resizable>
162
+ </VStack>
163
+ );
164
+ };
165
+
166
+ export const OverflowHidden = () => {
167
+ const [width, setWidth] = useState(300);
168
+
169
+ return (
170
+ <VStack gap="8px" padding="16px" minHeight="400px">
171
+ <BodyText>
172
+ Outer box is the resize target (minWidth: 100px). Inner box clips content with
173
+ overflow:hidden. Shrink below 200px to clip the purple box. It stops at 100px due
174
+ to the min-width constraint.
175
+ </BodyText>
176
+ <Resizable onWidthResize={({ width: w }) => setWidth(Math.round(w))}>
177
+ <Box width={`${width}px`} minWidth="100px" height="200px">
178
+ <Box height="100%" overflow="hidden" className={styles['demo-box']}>
179
+ <Box minWidth="200px" height="80px" className={styles['clip-target']} />
180
+ <div className={styles['size-display']}>width: {width}px</div>
181
+ </Box>
182
+ </Box>
183
+ <ResizeHandle position="right" className={styles['demo-handle']} />
184
+ </Resizable>
185
+ </VStack>
186
+ );
187
+ };
188
+
189
+ export const CustomHandle = () => {
190
+ const handleRef = useRef<HTMLDivElement>(null);
191
+ const [width, setWidth] = useState(300);
192
+
193
+ return (
194
+ <VStack gap="8px" padding="16px" minHeight="400px">
195
+ <BodyText>Custom styled handle with forwarded ref and aria attributes.</BodyText>
196
+ <Resizable onWidthResize={({ width: w }) => setWidth(Math.round(w))}>
197
+ <Box width={`${width}px`} height="200px" className={styles['demo-box']}>
198
+ <BodyText>Resizable content</BodyText>
199
+ <div className={styles['size-display']}>width: {width}px</div>
200
+ </Box>
201
+ <ResizeHandle
202
+ ref={handleRef}
203
+ position="right"
204
+ style={{
205
+ background: 'rgba(234, 67, 53, 0.4)',
206
+ width: '8px',
207
+ }}
208
+ aria-label="Resize handle"
209
+ data-testid="custom-handle"
210
+ />
211
+ </Resizable>
212
+ </VStack>
213
+ );
214
+ };
@@ -0,0 +1,47 @@
1
+ @layer tcn-system {
2
+ :where(.demo-box) {
3
+ background: var(--tcn-color-surface-secondary, #f0f0f0);
4
+ border: 1px solid var(--tcn-color-border, #ccc);
5
+ padding: 16px;
6
+ position: relative;
7
+ }
8
+
9
+ :where(.demo-handle) {
10
+ background: rgba(0, 120, 212, 0.3);
11
+ }
12
+
13
+ :where(.demo-handle:hover) {
14
+ background: rgba(0, 120, 212, 0.6);
15
+ }
16
+
17
+ :where(.size-display) {
18
+ font-family: monospace;
19
+ font-size: 12px;
20
+ color: var(--tcn-color-text-secondary, #666);
21
+ margin-top: 8px;
22
+ }
23
+
24
+ :where(.rtl-container) {
25
+ direction: rtl;
26
+ border: 2px dashed #999;
27
+ padding: 16px;
28
+ }
29
+
30
+ :where(.clip-target) {
31
+ background: #7b1fa2;
32
+ border-radius: 4px;
33
+ flex-shrink: 0;
34
+ }
35
+
36
+ :where(.nested-outer) {
37
+ background: #e8f0fe;
38
+ border: 2px solid #4285f4;
39
+ padding: 24px;
40
+ }
41
+
42
+ :where(.nested-inner) {
43
+ background: #fce8e6;
44
+ border: 2px solid #ea4335;
45
+ padding: 16px;
46
+ }
47
+ }