@tcn/ui 0.12.4 → 0.12.6

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 (150) hide show
  1. package/dist/inputs/color_input/color_input.js +18 -18
  2. package/dist/inputs/color_input/color_input.js.map +1 -1
  3. package/dist/inputs/control/control.d.ts +10 -0
  4. package/dist/inputs/control/control.d.ts.map +1 -0
  5. package/dist/inputs/control/control.js +17 -0
  6. package/dist/inputs/control/control.js.map +1 -0
  7. package/dist/inputs/control_set/control_set.d.ts +5 -0
  8. package/dist/inputs/control_set/control_set.d.ts.map +1 -0
  9. package/dist/inputs/control_set/control_set.js +20 -0
  10. package/dist/inputs/{input_group/input_group.js.map → control_set/control_set.js.map} +1 -1
  11. package/dist/inputs/date_picker/date_picker_input.js +20 -20
  12. package/dist/inputs/date_picker/date_picker_input.js.map +1 -1
  13. package/dist/inputs/index.d.ts +2 -1
  14. package/dist/inputs/index.d.ts.map +1 -1
  15. package/dist/inputs/index.js +27 -24
  16. package/dist/inputs/index.js.map +1 -1
  17. package/dist/inputs/input/input.js +6 -6
  18. package/dist/inputs/input/input.js.map +1 -1
  19. package/dist/inputs/phone_number_input/phone_number_country_select_adapter.js +15 -15
  20. package/dist/inputs/phone_number_input/phone_number_country_select_adapter.js.map +1 -1
  21. package/dist/inputs/phone_number_input/phone_number_input.js +24 -24
  22. package/dist/inputs/phone_number_input/phone_number_input.js.map +1 -1
  23. package/dist/inputs/phone_number_input/phone_number_input_adapter.js +21 -21
  24. package/dist/inputs/phone_number_input/phone_number_input_adapter.js.map +1 -1
  25. package/dist/inputs/phone_number_input/sip_input.js +14 -14
  26. package/dist/inputs/phone_number_input/sip_input.js.map +1 -1
  27. package/dist/inputs/select/select.js +6 -6
  28. package/dist/inputs/select/select.js.map +1 -1
  29. package/dist/inputs/textarea/textarea.js +8 -8
  30. package/dist/inputs/textarea/textarea.js.map +1 -1
  31. package/dist/inputs/unit_input/unit_input.js +20 -20
  32. package/dist/inputs/unit_input/unit_input.js.map +1 -1
  33. package/dist/overlay/frame/frame.d.ts +2 -2
  34. package/dist/overlay/frame/frame.d.ts.map +1 -1
  35. package/dist/overlay/frame/frame.js +68 -66
  36. package/dist/overlay/frame/frame.js.map +1 -1
  37. package/dist/overlay/slide/slide.d.ts +9 -0
  38. package/dist/overlay/slide/slide.d.ts.map +1 -0
  39. package/dist/overlay/slide/slide.js +29 -0
  40. package/dist/overlay/slide/slide.js.map +1 -0
  41. package/dist/slide.css +1 -0
  42. package/dist/stacks/box/box.d.ts +5 -4
  43. package/dist/stacks/box/box.d.ts.map +1 -1
  44. package/dist/stacks/box/box.js.map +1 -1
  45. package/dist/stacks/box/detect_resize_bounds.d.ts +15 -0
  46. package/dist/stacks/box/detect_resize_bounds.d.ts.map +1 -0
  47. package/dist/stacks/box/detect_resize_bounds.js +49 -0
  48. package/dist/stacks/box/detect_resize_bounds.js.map +1 -0
  49. package/dist/stacks/box/resize_handlers.d.ts.map +1 -1
  50. package/dist/stacks/box/resize_handlers.js +51 -44
  51. package/dist/stacks/box/resize_handlers.js.map +1 -1
  52. package/dist/stacks/box/start_resize_handle.d.ts.map +1 -1
  53. package/dist/stacks/box/start_resize_handle.js +2 -1
  54. package/dist/stacks/box/start_resize_handle.js.map +1 -1
  55. package/dist/stacks/box/types.d.ts +17 -4
  56. package/dist/stacks/box/types.d.ts.map +1 -1
  57. package/dist/surfaces/drawers/drawer.d.ts +5 -0
  58. package/dist/surfaces/drawers/drawer.d.ts.map +1 -0
  59. package/dist/surfaces/drawers/drawer.js +23 -0
  60. package/dist/surfaces/drawers/drawer.js.map +1 -0
  61. package/dist/surfaces/index.d.ts +1 -4
  62. package/dist/surfaces/index.d.ts.map +1 -1
  63. package/dist/surfaces/index.js +20 -26
  64. package/dist/surfaces/index.js.map +1 -1
  65. package/dist/surfaces/modal/modal.d.ts +1 -1
  66. package/dist/surfaces/modal/modal.d.ts.map +1 -1
  67. package/dist/surfaces/modal/modal.js +22 -13
  68. package/dist/surfaces/modal/modal.js.map +1 -1
  69. package/dist/surfaces/window/window.d.ts +1 -1
  70. package/dist/surfaces/window/window.d.ts.map +1 -1
  71. package/dist/surfaces/window/window.js +21 -24
  72. package/dist/surfaces/window/window.js.map +1 -1
  73. package/dist/themes/stylesheets/reset.css +1 -1
  74. package/dist/themes/stylesheets/reset.js +2 -2
  75. package/dist/themes/stylesheets/reset.js.map +1 -1
  76. package/dist/themes/themes/ergo/ergo_theme.css +1 -1
  77. package/dist/themes/themes/ergo/ergo_theme.js +70 -6
  78. package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
  79. package/dist/utils/dnd/hooks/use_drag_container.d.ts.map +1 -1
  80. package/dist/utils/dnd/hooks/use_drag_container.js.map +1 -1
  81. package/package.json +2 -2
  82. package/src/inputs/color_input/color_input.tsx +3 -3
  83. package/src/inputs/control/control.stories.tsx +158 -0
  84. package/src/inputs/control/control.tsx +32 -0
  85. package/src/inputs/control/control_stories.module.css +7 -0
  86. package/src/inputs/control_set/control_set.stories.tsx +46 -0
  87. package/src/inputs/{input_group/input_group.tsx → control_set/control_set.tsx} +5 -5
  88. package/src/inputs/date_picker/date_picker_input.stories.tsx +1 -1
  89. package/src/inputs/date_picker/date_picker_input.tsx +1 -1
  90. package/src/inputs/index.ts +2 -1
  91. package/src/inputs/input/input.tsx +1 -1
  92. package/src/inputs/phone_number_input/phone_number_country_select_adapter.tsx +1 -1
  93. package/src/inputs/phone_number_input/phone_number_input.tsx +1 -1
  94. package/src/inputs/phone_number_input/phone_number_input_adapter.tsx +2 -2
  95. package/src/inputs/phone_number_input/sip_input.tsx +4 -4
  96. package/src/inputs/select/select.tsx +1 -1
  97. package/src/inputs/textarea/textarea.stories.tsx +1 -1
  98. package/src/inputs/textarea/textarea.tsx +1 -1
  99. package/src/inputs/unit_input/unit_input.tsx +3 -3
  100. package/src/overlay/frame/frame.tsx +43 -63
  101. package/src/overlay/slide/slide.module.css +30 -0
  102. package/src/overlay/slide/slide.stories.tsx +58 -0
  103. package/src/overlay/slide/slide.tsx +51 -0
  104. package/src/stacks/box/box.tsx +10 -16
  105. package/src/stacks/box/detect_resize_bounds.ts +84 -0
  106. package/src/stacks/box/resize_handlers.ts +27 -15
  107. package/src/stacks/box/start_resize_handle.tsx +6 -3
  108. package/src/stacks/box/types.ts +23 -25
  109. package/src/surfaces/drawers/drawer.stories.tsx +130 -0
  110. package/src/surfaces/drawers/drawer.tsx +26 -0
  111. package/src/surfaces/index.ts +1 -4
  112. package/src/surfaces/modal/__stories__/modal.stories.tsx +70 -3
  113. package/src/surfaces/modal/modal.tsx +11 -2
  114. package/src/surfaces/window/window.stories.tsx +64 -8
  115. package/src/surfaces/window/window.tsx +6 -9
  116. package/src/themes/stylesheets/reset.css +2 -2
  117. package/src/themes/themes/ergo/ergo_theme.css +70 -6
  118. package/src/utils/dnd/hooks/use_drag_container.ts +0 -7
  119. package/dist/drawer_bottom.css +0 -1
  120. package/dist/drawer_end.css +0 -1
  121. package/dist/drawer_start.css +0 -1
  122. package/dist/drawer_top.css +0 -1
  123. package/dist/inputs/input_group/input_group.d.ts +0 -5
  124. package/dist/inputs/input_group/input_group.d.ts.map +0 -1
  125. package/dist/inputs/input_group/input_group.js +0 -20
  126. package/dist/surfaces/drawers/drawer_bottom/drawer_bottom.d.ts +0 -7
  127. package/dist/surfaces/drawers/drawer_bottom/drawer_bottom.d.ts.map +0 -1
  128. package/dist/surfaces/drawers/drawer_bottom/drawer_bottom.js +0 -22
  129. package/dist/surfaces/drawers/drawer_bottom/drawer_bottom.js.map +0 -1
  130. package/dist/surfaces/drawers/drawer_end/drawer_end.d.ts +0 -7
  131. package/dist/surfaces/drawers/drawer_end/drawer_end.d.ts.map +0 -1
  132. package/dist/surfaces/drawers/drawer_end/drawer_end.js +0 -20
  133. package/dist/surfaces/drawers/drawer_end/drawer_end.js.map +0 -1
  134. package/dist/surfaces/drawers/drawer_start/drawer_start.d.ts +0 -7
  135. package/dist/surfaces/drawers/drawer_start/drawer_start.d.ts.map +0 -1
  136. package/dist/surfaces/drawers/drawer_start/drawer_start.js +0 -22
  137. package/dist/surfaces/drawers/drawer_start/drawer_start.js.map +0 -1
  138. package/dist/surfaces/drawers/drawer_top/drawer_top.d.ts +0 -7
  139. package/dist/surfaces/drawers/drawer_top/drawer_top.d.ts.map +0 -1
  140. package/dist/surfaces/drawers/drawer_top/drawer_top.js +0 -20
  141. package/dist/surfaces/drawers/drawer_top/drawer_top.js.map +0 -1
  142. package/src/surfaces/drawers/__stories__/drawers.stories.tsx +0 -26
  143. package/src/surfaces/drawers/drawer_bottom/drawer_bottom.module.css +0 -5
  144. package/src/surfaces/drawers/drawer_bottom/drawer_bottom.tsx +0 -23
  145. package/src/surfaces/drawers/drawer_end/drawer_end.module.css +0 -5
  146. package/src/surfaces/drawers/drawer_end/drawer_end.tsx +0 -24
  147. package/src/surfaces/drawers/drawer_start/drawer_start.module.css +0 -5
  148. package/src/surfaces/drawers/drawer_start/drawer_start.tsx +0 -23
  149. package/src/surfaces/drawers/drawer_top/drawer_top.module.css +0 -5
  150. package/src/surfaces/drawers/drawer_top/drawer_top.tsx +0 -24
@@ -0,0 +1,58 @@
1
+ import { Header, Scaffold, VBody } from '../../layouts/index.js';
2
+ import { ZStack } from '../../stacks/z_stack.js';
3
+ import { BodyText } from '../../typography/index.js';
4
+ import { Title } from '../../typography/title/title.js';
5
+ import { Slide, type SlideProps } from './slide.js';
6
+ import { DragHandle } from '../../utils/dnd/handle.js';
7
+
8
+ export default {
9
+ title: 'Overlays/Floating/Slide',
10
+ component: Slide,
11
+ tags: ['autodocs'],
12
+
13
+ args: {
14
+ isOpen: true,
15
+ veil: false,
16
+ resizable: true,
17
+ side: 'top',
18
+ },
19
+ argTypes: {
20
+ side: {
21
+ control: 'select',
22
+ options: ['top', 'bottom', 'start', 'end'],
23
+ },
24
+ },
25
+ };
26
+
27
+ export const SlideStory = (args: Omit<SlideProps, 'children'>) => {
28
+ const isVertical = args.side === 'top' || args.side === 'bottom';
29
+ return (
30
+ <ZStack height="100%" width="100%" minHeight="600px">
31
+ <Slide
32
+ height={isVertical ? '300px' : undefined}
33
+ width={!isVertical ? '300px' : undefined}
34
+ minHeight={isVertical ? '100px' : undefined}
35
+ minWidth={!isVertical ? '100px' : undefined}
36
+ maxHeight={isVertical ? '90%' : undefined}
37
+ maxWidth={!isVertical ? '90%' : undefined}
38
+ style={{
39
+ background: 'white',
40
+ }}
41
+ {...args}
42
+ >
43
+ <Scaffold>
44
+ <DragHandle>
45
+ <Header>
46
+ <Title> This is a Slide</Title>
47
+ </Header>
48
+ </DragHandle>
49
+ <VBody>
50
+ <BodyText>
51
+ This component is fixed to a side of a container (top, bottom, start, end).
52
+ </BodyText>
53
+ </VBody>
54
+ </Scaffold>
55
+ </Slide>
56
+ </ZStack>
57
+ );
58
+ };
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import { Frame, type FrameProps } from '../frame/frame.js';
3
+ import { clsx } from 'clsx';
4
+
5
+ // Styles
6
+ import styles from './slide.module.css';
7
+
8
+ export type SlideSide = 'top' | 'bottom' | 'start' | 'end';
9
+
10
+ export interface SlideOwnProps {
11
+ side: SlideSide;
12
+ }
13
+
14
+ export type SlideProps = Omit<
15
+ FrameProps,
16
+ | 'draggable'
17
+ | 'enableResizeOnLeft'
18
+ | 'enableResizeOnRight'
19
+ | 'enableResizeOnTop'
20
+ | 'enableResizeOnBottom'
21
+ | 'enableResizeOnStart'
22
+ | 'enableResizeOnEnd'
23
+ > &
24
+ SlideOwnProps;
25
+
26
+ // A Frame fixed to a side of a container (top, bottom, start, end) - disables dragging - and limits resizing to one side
27
+ export const Slide = React.forwardRef<HTMLElement, SlideProps>(function Slide(
28
+ { children, side, resizable = false, className, ...rest },
29
+ ref
30
+ ) {
31
+ const isVertical = side === 'top' || side === 'bottom';
32
+
33
+ return (
34
+ <Frame
35
+ data-side={side}
36
+ className={clsx(styles['slide'], className)}
37
+ draggable={false}
38
+ ref={ref}
39
+ data-is-vertical={isVertical}
40
+ data-is-horizontal={!isVertical}
41
+ resizable={resizable}
42
+ enableResizeOnTop={side === 'bottom'}
43
+ enableResizeOnBottom={side === 'top'}
44
+ enableResizeOnStart={side === 'end'}
45
+ enableResizeOnEnd={side === 'start'}
46
+ {...rest}
47
+ >
48
+ {children}
49
+ </Frame>
50
+ );
51
+ });
@@ -11,6 +11,12 @@ import { LeftResizeHandle } from './left_resize_handle.js';
11
11
  import { RightResizeHandle } from './right_resize_handle.js';
12
12
  import { StartResizeHandle } from './start_resize_handle.js';
13
13
  import { TopResizeHandle } from './top_resize_handle.js';
14
+ import type {
15
+ OnHeightResize,
16
+ OnHeightResizeEnd,
17
+ OnWidthResize,
18
+ OnWidthResizeEnd,
19
+ } from './types.js';
14
20
 
15
21
  export interface BoxProps<T extends HTMLElement = HTMLElement> extends HTMLAttributes<T> {
16
22
  as?: string;
@@ -45,22 +51,10 @@ export interface BoxProps<T extends HTMLElement = HTMLElement> extends HTMLAttri
45
51
  enableResizeOnRight?: boolean;
46
52
  horizontalHandleProps?: HandleProps;
47
53
  verticalHandleProps?: HandleProps;
48
- onWidthResize?: (
49
- width: number,
50
- origin: 'left' | 'right',
51
- totalDelta: number,
52
- currentDelta: number,
53
- atLimit: boolean
54
- ) => void;
55
- onHeightResize?: (
56
- height: number,
57
- origin: 'top' | 'bottom',
58
- totalDelta: number,
59
- currentDelta: number,
60
- atLimit: boolean
61
- ) => void;
62
- onWidthResizeEnd?: (width: number, origin: 'left' | 'right') => void;
63
- onHeightResizeEnd?: (height: number, origin: 'top' | 'bottom') => void;
54
+ onWidthResize?: OnWidthResize;
55
+ onHeightResize?: OnHeightResize;
56
+ onWidthResizeEnd?: OnWidthResizeEnd;
57
+ onHeightResizeEnd?: OnHeightResizeEnd;
64
58
  }
65
59
 
66
60
  export const Box = React.forwardRef<HTMLElement, BoxProps>(function Box(
@@ -0,0 +1,84 @@
1
+ type ConstraintAxis = 'width' | 'height';
2
+
3
+ export type DetectResizeBoundsParams = {
4
+ element: HTMLElement;
5
+ axis: ConstraintAxis;
6
+ nextSize: number;
7
+ epsilon?: number; // Tolerance for the constraint hit detection.
8
+ };
9
+
10
+ export type DetectResizeBoundsResult = {
11
+ hitMin: boolean;
12
+ hitMax: boolean;
13
+ clamped: boolean;
14
+ };
15
+
16
+ const styleKeys = {
17
+ width: {
18
+ size: 'width',
19
+ min: 'minWidth',
20
+ max: 'maxWidth',
21
+ },
22
+ height: {
23
+ size: 'height',
24
+ min: 'minHeight',
25
+ max: 'maxHeight',
26
+ },
27
+ } as const;
28
+
29
+ function parsePx(value: string): number | null {
30
+ const match = /^(-?\d+(?:\.\d+)?)px$/.exec(value.trim());
31
+ return match ? Number(match[1]) : null;
32
+ }
33
+
34
+ function detectByPixelValue(nextSize: number, min: string, max: string) {
35
+ const minPx = parsePx(min);
36
+ const maxPx = parsePx(max);
37
+ const hitMin = minPx !== null && nextSize < minPx;
38
+ const hitMax = maxPx !== null && nextSize > maxPx;
39
+ return {
40
+ hitMin,
41
+ hitMax,
42
+ clamped: hitMin || hitMax,
43
+ };
44
+ }
45
+
46
+ export function detectResizeBounds({
47
+ element,
48
+ axis,
49
+ nextSize,
50
+ epsilon = 0.5,
51
+ }: DetectResizeBoundsParams): DetectResizeBoundsResult {
52
+ const keys = styleKeys[axis];
53
+
54
+ const computed = getComputedStyle(element);
55
+ const fastPath = detectByPixelValue(nextSize, computed[keys.min], computed[keys.max]);
56
+ if (fastPath.clamped) return fastPath;
57
+
58
+ const style = element.style;
59
+ const prevInlineSize = style[keys.size]; // Save the previous size to revert later.
60
+
61
+ try {
62
+ // Temporarily apply the new size to the element to offload bound test to browser.
63
+ style[keys.size] = `${nextSize}px`;
64
+
65
+ // Force layout so browser resolves min/max/intrinsic constraints.
66
+ const rect = element.getBoundingClientRect();
67
+ const renderedSize = rect[keys.size];
68
+
69
+ const delta = renderedSize - nextSize;
70
+
71
+ const hitMin = delta > epsilon;
72
+ const hitMax = delta < -epsilon;
73
+ const clamped = hitMin || hitMax;
74
+
75
+ return {
76
+ hitMin,
77
+ hitMax,
78
+ clamped,
79
+ };
80
+ } finally {
81
+ // revert the style change
82
+ style[keys.size] = prevInlineSize;
83
+ }
84
+ }
@@ -4,6 +4,7 @@ import {
4
4
  type OnHeightResize,
5
5
  type WidthResizeOrigin,
6
6
  } from './types';
7
+ import { detectResizeBounds } from './detect_resize_bounds.js';
7
8
 
8
9
  function createVeil() {
9
10
  const veil = window.document.createElement('div');
@@ -25,9 +26,7 @@ export function createHorizontalResizeHandler(
25
26
  return function startHorizontalResize(event: React.MouseEvent) {
26
27
  const box = targetRef.current;
27
28
 
28
- if (box == null) {
29
- return;
30
- }
29
+ if (box == null) return;
31
30
 
32
31
  const veil = createVeil();
33
32
  box.appendChild(veil);
@@ -42,18 +41,24 @@ export function createHorizontalResizeHandler(
42
41
  let width = startRect.width;
43
42
 
44
43
  const drag = (event: MouseEvent) => {
45
- const beforeWidth = box.getBoundingClientRect().width;
46
44
  const totalDelta = direction * (event.clientX - startX);
47
45
  const newWidth = startRect.width + totalDelta;
46
+
47
+ const result = detectResizeBounds({
48
+ element: box,
49
+ axis: 'width',
50
+ nextSize: newWidth,
51
+ });
52
+
53
+ if (result.clamped) return;
54
+
48
55
  const currentDelta = newWidth - width;
49
56
 
57
+ // apply the new width
50
58
  width = newWidth;
51
-
52
59
  box.style.width = `${newWidth}px`;
53
60
 
54
- const afterWidth = box.getBoundingClientRect().width;
55
- const atLimit = afterWidth === beforeWidth;
56
- onWidthResize?.(newWidth, origin, totalDelta, currentDelta, atLimit);
61
+ onWidthResize?.({ width: newWidth, origin, totalDelta, currentDelta });
57
62
  event.stopPropagation();
58
63
  event.preventDefault();
59
64
  };
@@ -95,9 +100,7 @@ export function createVerticalResizeHandler(
95
100
  return function startVerticalResize(event: React.MouseEvent) {
96
101
  const box = targetRef.current;
97
102
 
98
- if (box == null) {
99
- return;
100
- }
103
+ if (box == null) return;
101
104
 
102
105
  const veil = createVeil();
103
106
  box.appendChild(veil);
@@ -107,15 +110,24 @@ export function createVerticalResizeHandler(
107
110
  let height = startRect.height;
108
111
 
109
112
  const drag = (event: MouseEvent) => {
110
- const beforeHeight = box.getBoundingClientRect().height;
111
113
  const totalDelta = direction * (event.clientY - startY);
112
114
  const newHeight = startRect.height + totalDelta;
115
+
116
+ const result = detectResizeBounds({
117
+ element: box,
118
+ axis: 'height',
119
+ nextSize: newHeight,
120
+ });
121
+
122
+ if (result.clamped) return;
123
+
113
124
  const currentDelta = newHeight - height;
125
+
126
+ // apply the new height
114
127
  height = newHeight;
115
128
  box.style.height = `${newHeight}px`;
116
- const afterHeight = box.getBoundingClientRect().height;
117
- const atLimit = afterHeight === beforeHeight;
118
- onHeightResize?.(newHeight, origin, totalDelta, currentDelta, atLimit);
129
+
130
+ onHeightResize?.({ height: newHeight, origin, totalDelta, currentDelta });
119
131
  event.stopPropagation();
120
132
  event.preventDefault();
121
133
  };
@@ -2,6 +2,7 @@ import { clsx } from 'clsx';
2
2
  import { createHorizontalResizeHandler } from './resize_handlers.js';
3
3
  import styles from './start_resize_handle.module.css';
4
4
  import type { HorizontalResizeHandleProps } from './types.js';
5
+ import { CSSProperties } from 'react';
5
6
 
6
7
  export type StartResizeHandleProps = HorizontalResizeHandleProps;
7
8
 
@@ -15,15 +16,17 @@ export function StartResizeHandle({
15
16
  targetRef,
16
17
  onWidthResize,
17
18
  onWidthResizeEnd,
18
- 'left'
19
+ 'left',
20
+ true
19
21
  );
22
+
20
23
  const offset = handleProps?.offset ? handleProps.offset : -8;
21
24
 
22
- const startResizeHandleStyle: any = {
25
+ const startResizeHandleStyle = {
23
26
  ...handleProps?.style,
24
27
  '--resize-offset': `${offset}px`,
25
28
  width: handleProps?.size || '16px',
26
- };
29
+ } as CSSProperties;
27
30
 
28
31
  return (
29
32
  <div
@@ -3,42 +3,40 @@ import type { HandleProps } from './handle_props.js';
3
3
  export type WidthResizeOrigin = 'left' | 'right';
4
4
  export type HeightResizeOrigin = 'top' | 'bottom';
5
5
 
6
- export type OnWidthResize = (
7
- // Newly calculated width
8
- width: number,
9
- // Origin of the resize - left or right of the box
10
- origin: WidthResizeOrigin,
6
+ export interface BaseOnResizePayload<
7
+ Origin extends WidthResizeOrigin | HeightResizeOrigin,
8
+ > {
9
+ origin: Origin;
11
10
  // Total delta of the resize (the sum of all deltas before end event)
12
- totalDelta: number,
11
+ totalDelta: number;
13
12
  // Current delta of the resize (the delta of the current event)
14
- currentDelta: number,
15
- // Whether the resize is at the limit of the box, this includes min-content, max-content, and min-width/height
16
- atLimit: boolean
17
- ) => void;
18
-
19
- export type OnHeightResize = (
20
- // Newly calculated height
21
- height: number,
22
- // Origin of the resize - top or bottom of the box
23
- origin: HeightResizeOrigin,
24
- // Total delta of the resize (the sum of all deltas before end event)
25
- totalDelta: number,
26
- // Current delta of the resize (the delta of the current event)
27
- currentDelta: number,
28
- // Whether the resize is at the limit of the box, this includes min-content, max-content, and min-width/height
29
- atLimit: boolean
30
- ) => void;
13
+ currentDelta: number;
14
+ }
15
+
16
+ export interface OnWidthResizePayload extends BaseOnResizePayload<WidthResizeOrigin> {
17
+ width: number;
18
+ }
19
+
20
+ export interface OnHeightResizePayload extends BaseOnResizePayload<HeightResizeOrigin> {
21
+ height: number;
22
+ }
23
+
24
+ export type OnWidthResizeEnd = (width: number, origin: WidthResizeOrigin) => void;
25
+ export type OnHeightResizeEnd = (height: number, origin: HeightResizeOrigin) => void;
26
+
27
+ export type OnWidthResize = (payload: OnWidthResizePayload) => void;
28
+ export type OnHeightResize = (payload: OnHeightResizePayload) => void;
31
29
 
32
30
  export interface HorizontalResizeHandleProps {
33
31
  targetRef: React.MutableRefObject<HTMLElement | null>;
34
32
  handleProps?: HandleProps;
35
33
  onWidthResize?: OnWidthResize;
36
- onWidthResizeEnd?: (width: number, origin: WidthResizeOrigin) => void;
34
+ onWidthResizeEnd?: OnWidthResizeEnd;
37
35
  }
38
36
 
39
37
  export interface VerticalResizeHandleProps {
40
38
  targetRef: React.MutableRefObject<HTMLElement | null>;
41
39
  handleProps?: HandleProps;
42
40
  onHeightResize?: OnHeightResize;
43
- onHeightResizeEnd?: (height: number, origin: HeightResizeOrigin) => void;
41
+ onHeightResizeEnd?: OnHeightResizeEnd;
44
42
  }
@@ -0,0 +1,130 @@
1
+ import { useState } from 'react';
2
+ import { Button } from '../../actions/index.js';
3
+ import { Footer, Header, UtilityBar, VBody } from '../../layouts/index.js';
4
+ import { ZStack } from '../../stacks/z_stack.js';
5
+ import { BodyText, Title } from '../../typography/index.js';
6
+ import { Drawer, type DrawerProps } from './drawer.js';
7
+ import { Spacer } from '../../stacks/index.js';
8
+ import { CrossIcon } from '@tcn/icons/cross_icon.js';
9
+ import { BugIcon } from '@tcn/icons/bug_icon.js';
10
+
11
+ export default {
12
+ title: 'Surfaces/Drawer',
13
+ component: Drawer,
14
+ tags: ['autodocs'],
15
+ argTypes: {
16
+ side: {
17
+ control: 'select',
18
+ options: ['start', 'end', 'top', 'bottom'],
19
+ },
20
+ veil: {
21
+ control: 'boolean',
22
+ },
23
+ resizable: {
24
+ control: 'boolean',
25
+ },
26
+ size: {
27
+ control: 'text',
28
+ description: 'Arg to swap between setting width and height',
29
+ },
30
+ minSize: {
31
+ control: 'text',
32
+ description: 'Arg to set the minimum size of the drawer',
33
+ },
34
+ maxSize: {
35
+ control: 'text',
36
+ description: 'Arg to set the maximum size of the drawer',
37
+ },
38
+ },
39
+ args: {
40
+ resizable: true,
41
+ veil: false,
42
+ side: 'start',
43
+ size: '400px',
44
+ minSize: '240px',
45
+ maxSize: '90%',
46
+ },
47
+ };
48
+
49
+ type DrawerStoryProps = Pick<DrawerProps, 'resizable' | 'veil' | 'side'> & {
50
+ size?: string;
51
+ minSize?: string;
52
+ maxSize?: string;
53
+ };
54
+
55
+ export const Baseline = ({
56
+ veil,
57
+ resizable,
58
+ side,
59
+ size,
60
+ minSize,
61
+ maxSize,
62
+ }: DrawerStoryProps) => {
63
+ const [isOpen, setIsOpen] = useState(false);
64
+
65
+ function toggle() {
66
+ setIsOpen(!isOpen);
67
+ }
68
+
69
+ const useWidth = side === 'start' || side === 'end';
70
+
71
+ return (
72
+ <ZStack height="100%" width="100%" minHeight="600px">
73
+ <Button hierarchy="secondary" onClick={toggle}>
74
+ {isOpen ? 'Close' : 'Open'}
75
+ </Button>
76
+
77
+ <Drawer
78
+ side={side}
79
+ isOpen={isOpen}
80
+ veil={veil}
81
+ resizable={resizable}
82
+ width={useWidth ? size : undefined}
83
+ minWidth={useWidth ? minSize : undefined}
84
+ maxWidth={useWidth ? maxSize : undefined}
85
+ height={useWidth ? undefined : size}
86
+ minHeight={useWidth ? undefined : minSize}
87
+ maxHeight={useWidth ? undefined : maxSize}
88
+ >
89
+ {/* Add a drag handle around the areas of the modal you want to be draggable (usually the header) */}
90
+ {/* <DragHandle> */}
91
+ <Header>
92
+ <Title>Drawer Title</Title>
93
+ <Spacer />
94
+ <Button utility hierarchy="tertiary" onClick={toggle}>
95
+ <CrossIcon />
96
+ </Button>
97
+ <Button utility hierarchy="secondary" onClick={toggle}>
98
+ <CrossIcon />
99
+ </Button>
100
+ <Button utility hierarchy="primary" onClick={toggle}>
101
+ <CrossIcon />
102
+ </Button>
103
+ </Header>
104
+ {/* </DragHandle> */}
105
+
106
+ <UtilityBar>
107
+ <Title>Utility Bar</Title>
108
+ <Spacer />
109
+ <Button utility hierarchy="tertiary">
110
+ <BugIcon />
111
+ </Button>
112
+ <Button utility hierarchy="secondary">
113
+ <BugIcon />
114
+ </Button>
115
+ <Button utility hierarchy="primary">
116
+ <BugIcon />
117
+ </Button>
118
+ </UtilityBar>
119
+ <VBody>
120
+ <BodyText>This is a drawer</BodyText>
121
+ </VBody>
122
+ <Footer>
123
+ <Spacer />
124
+ <Button hierarchy="secondary">Cancel</Button>
125
+ <Button hierarchy="primary">Save</Button>
126
+ </Footer>
127
+ </Drawer>
128
+ </ZStack>
129
+ );
130
+ };
@@ -0,0 +1,26 @@
1
+ import { clsx } from 'clsx';
2
+ import React from 'react';
3
+ import { Scaffold } from '../../layouts/scaffold/scaffold.js';
4
+ import { Slide, type SlideProps } from '../../overlay/slide/slide.js';
5
+
6
+ export type DrawerProps = SlideProps;
7
+
8
+ export const Drawer = React.forwardRef<HTMLElement, DrawerProps>(function Drawer(
9
+ { children, className, isOpen, veil = false, resizable = false, ...props }: DrawerProps,
10
+ ref
11
+ ) {
12
+ return (
13
+ <Slide
14
+ ref={ref}
15
+ isOpen={isOpen}
16
+ veil={veil}
17
+ resizable={resizable}
18
+ className={clsx('tcn-surface', 'tcn-drawer', className)}
19
+ {...props}
20
+ >
21
+ <Scaffold className={'tcn-drawer-scaffold'} width="100%" height="100%">
22
+ {children}
23
+ </Scaffold>
24
+ </Slide>
25
+ );
26
+ });
@@ -5,10 +5,7 @@ export * from './page/h_page.js';
5
5
  export * from './page/v_page.js';
6
6
  export * from './panel/panel.js';
7
7
  export * from './popover/popover.js';
8
- export * from './drawers/drawer_bottom/drawer_bottom.js';
9
- export * from './drawers/drawer_top/drawer_top.js';
10
- export * from './drawers/drawer_start/drawer_start.js';
11
- export * from './drawers/drawer_end/drawer_end.js';
8
+ export { Drawer, type DrawerProps } from './drawers/drawer.js';
12
9
  export { Window, type WindowProps } from './window/window.js';
13
10
  export { Modal, type ModalProps } from './modal/modal.js';
14
11
  export { Tooltip, type TooltipProps } from './tooltip/tooltip.js';
@@ -3,7 +3,7 @@ import { Button } from '../../../actions/index.js';
3
3
  import { Footer, Header, UtilityBar, VBody } from '../../../layouts/index.js';
4
4
  import { ZStack } from '../../../stacks/z_stack.js';
5
5
  import { BodyText, Title } from '../../../typography/index.js';
6
- import { Modal } from '../modal.js';
6
+ import { Modal, type ModalProps } from '../modal.js';
7
7
  import { Spacer } from '../../../stacks/index.js';
8
8
  import { CrossIcon } from '@tcn/icons/cross_icon.js';
9
9
  import { BugIcon } from '@tcn/icons/bug_icon.js';
@@ -12,9 +12,61 @@ export default {
12
12
  title: 'Surfaces/Modal',
13
13
  component: Modal,
14
14
  tags: ['autodocs'],
15
+ argTypes: {
16
+ draggable: {
17
+ control: 'boolean',
18
+ },
19
+ veil: {
20
+ control: 'boolean',
21
+ },
22
+ resizable: {
23
+ control: 'boolean',
24
+ },
25
+ width: {
26
+ control: 'text',
27
+ },
28
+ height: {
29
+ control: 'text',
30
+ },
31
+ minWidth: {
32
+ control: 'text',
33
+ },
34
+ minHeight: {
35
+ control: 'text',
36
+ },
37
+ maxWidth: {
38
+ control: 'text',
39
+ },
40
+ maxHeight: {
41
+ control: 'text',
42
+ },
43
+ },
15
44
  };
16
45
 
17
- export const ModalStory = () => {
46
+ type ModalStoryProps = Pick<
47
+ ModalProps,
48
+ | 'resizable'
49
+ | 'draggable'
50
+ | 'veil'
51
+ | 'width'
52
+ | 'height'
53
+ | 'minWidth'
54
+ | 'minHeight'
55
+ | 'maxWidth'
56
+ | 'maxHeight'
57
+ >;
58
+
59
+ export const Baseline = ({
60
+ resizable,
61
+ draggable,
62
+ veil,
63
+ width = '400px',
64
+ height = '500px',
65
+ minWidth,
66
+ minHeight,
67
+ maxWidth,
68
+ maxHeight,
69
+ }: ModalStoryProps) => {
18
70
  const [isOpen, setIsOpen] = useState(false);
19
71
 
20
72
  function toggle() {
@@ -27,7 +79,20 @@ export const ModalStory = () => {
27
79
  {isOpen ? 'Close' : 'Open'}
28
80
  </Button>
29
81
 
30
- <Modal isOpen={isOpen} width="400px" height="500px">
82
+ <Modal
83
+ isOpen={isOpen}
84
+ width={width}
85
+ height={height}
86
+ draggable={draggable}
87
+ veil={veil}
88
+ resizable={resizable}
89
+ minWidth={minWidth}
90
+ minHeight={minHeight}
91
+ maxWidth={maxWidth}
92
+ maxHeight={maxHeight}
93
+ >
94
+ {/* Add a drag handle around the areas of the modal you want to be draggable (usually the header) */}
95
+ {/* <DragHandle> */}
31
96
  <Header>
32
97
  <Title>Modal Title</Title>
33
98
  <Spacer />
@@ -41,6 +106,8 @@ export const ModalStory = () => {
41
106
  <CrossIcon />
42
107
  </Button>
43
108
  </Header>
109
+ {/* </DragHandle> */}
110
+
44
111
  <UtilityBar>
45
112
  <Title>Utility Bar</Title>
46
113
  <Spacer />