@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.
- package/dist/inputs/color_input/color_input.js +18 -18
- package/dist/inputs/color_input/color_input.js.map +1 -1
- package/dist/inputs/control/control.d.ts +10 -0
- package/dist/inputs/control/control.d.ts.map +1 -0
- package/dist/inputs/control/control.js +17 -0
- package/dist/inputs/control/control.js.map +1 -0
- package/dist/inputs/control_set/control_set.d.ts +5 -0
- package/dist/inputs/control_set/control_set.d.ts.map +1 -0
- package/dist/inputs/control_set/control_set.js +20 -0
- package/dist/inputs/{input_group/input_group.js.map → control_set/control_set.js.map} +1 -1
- package/dist/inputs/date_picker/date_picker_input.js +20 -20
- package/dist/inputs/date_picker/date_picker_input.js.map +1 -1
- package/dist/inputs/index.d.ts +2 -1
- package/dist/inputs/index.d.ts.map +1 -1
- package/dist/inputs/index.js +27 -24
- package/dist/inputs/index.js.map +1 -1
- package/dist/inputs/input/input.js +6 -6
- package/dist/inputs/input/input.js.map +1 -1
- package/dist/inputs/phone_number_input/phone_number_country_select_adapter.js +15 -15
- package/dist/inputs/phone_number_input/phone_number_country_select_adapter.js.map +1 -1
- package/dist/inputs/phone_number_input/phone_number_input.js +24 -24
- package/dist/inputs/phone_number_input/phone_number_input.js.map +1 -1
- package/dist/inputs/phone_number_input/phone_number_input_adapter.js +21 -21
- package/dist/inputs/phone_number_input/phone_number_input_adapter.js.map +1 -1
- package/dist/inputs/phone_number_input/sip_input.js +14 -14
- package/dist/inputs/phone_number_input/sip_input.js.map +1 -1
- package/dist/inputs/select/select.js +6 -6
- package/dist/inputs/select/select.js.map +1 -1
- package/dist/inputs/textarea/textarea.js +8 -8
- package/dist/inputs/textarea/textarea.js.map +1 -1
- package/dist/inputs/unit_input/unit_input.js +20 -20
- package/dist/inputs/unit_input/unit_input.js.map +1 -1
- package/dist/overlay/frame/frame.d.ts +2 -2
- package/dist/overlay/frame/frame.d.ts.map +1 -1
- package/dist/overlay/frame/frame.js +68 -66
- package/dist/overlay/frame/frame.js.map +1 -1
- package/dist/overlay/slide/slide.d.ts +9 -0
- package/dist/overlay/slide/slide.d.ts.map +1 -0
- package/dist/overlay/slide/slide.js +29 -0
- package/dist/overlay/slide/slide.js.map +1 -0
- package/dist/slide.css +1 -0
- package/dist/stacks/box/box.d.ts +5 -4
- package/dist/stacks/box/box.d.ts.map +1 -1
- package/dist/stacks/box/box.js.map +1 -1
- package/dist/stacks/box/detect_resize_bounds.d.ts +15 -0
- package/dist/stacks/box/detect_resize_bounds.d.ts.map +1 -0
- package/dist/stacks/box/detect_resize_bounds.js +49 -0
- package/dist/stacks/box/detect_resize_bounds.js.map +1 -0
- package/dist/stacks/box/resize_handlers.d.ts.map +1 -1
- package/dist/stacks/box/resize_handlers.js +51 -44
- package/dist/stacks/box/resize_handlers.js.map +1 -1
- package/dist/stacks/box/start_resize_handle.d.ts.map +1 -1
- package/dist/stacks/box/start_resize_handle.js +2 -1
- package/dist/stacks/box/start_resize_handle.js.map +1 -1
- package/dist/stacks/box/types.d.ts +17 -4
- package/dist/stacks/box/types.d.ts.map +1 -1
- package/dist/surfaces/drawers/drawer.d.ts +5 -0
- package/dist/surfaces/drawers/drawer.d.ts.map +1 -0
- package/dist/surfaces/drawers/drawer.js +23 -0
- package/dist/surfaces/drawers/drawer.js.map +1 -0
- package/dist/surfaces/index.d.ts +1 -4
- package/dist/surfaces/index.d.ts.map +1 -1
- package/dist/surfaces/index.js +20 -26
- package/dist/surfaces/index.js.map +1 -1
- package/dist/surfaces/modal/modal.d.ts +1 -1
- package/dist/surfaces/modal/modal.d.ts.map +1 -1
- package/dist/surfaces/modal/modal.js +22 -13
- package/dist/surfaces/modal/modal.js.map +1 -1
- package/dist/surfaces/window/window.d.ts +1 -1
- package/dist/surfaces/window/window.d.ts.map +1 -1
- package/dist/surfaces/window/window.js +21 -24
- package/dist/surfaces/window/window.js.map +1 -1
- package/dist/themes/stylesheets/reset.css +1 -1
- package/dist/themes/stylesheets/reset.js +2 -2
- package/dist/themes/stylesheets/reset.js.map +1 -1
- package/dist/themes/themes/ergo/ergo_theme.css +1 -1
- package/dist/themes/themes/ergo/ergo_theme.js +70 -6
- package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
- package/dist/utils/dnd/hooks/use_drag_container.d.ts.map +1 -1
- package/dist/utils/dnd/hooks/use_drag_container.js.map +1 -1
- package/package.json +2 -2
- package/src/inputs/color_input/color_input.tsx +3 -3
- package/src/inputs/control/control.stories.tsx +158 -0
- package/src/inputs/control/control.tsx +32 -0
- package/src/inputs/control/control_stories.module.css +7 -0
- package/src/inputs/control_set/control_set.stories.tsx +46 -0
- package/src/inputs/{input_group/input_group.tsx → control_set/control_set.tsx} +5 -5
- package/src/inputs/date_picker/date_picker_input.stories.tsx +1 -1
- package/src/inputs/date_picker/date_picker_input.tsx +1 -1
- package/src/inputs/index.ts +2 -1
- package/src/inputs/input/input.tsx +1 -1
- package/src/inputs/phone_number_input/phone_number_country_select_adapter.tsx +1 -1
- package/src/inputs/phone_number_input/phone_number_input.tsx +1 -1
- package/src/inputs/phone_number_input/phone_number_input_adapter.tsx +2 -2
- package/src/inputs/phone_number_input/sip_input.tsx +4 -4
- package/src/inputs/select/select.tsx +1 -1
- package/src/inputs/textarea/textarea.stories.tsx +1 -1
- package/src/inputs/textarea/textarea.tsx +1 -1
- package/src/inputs/unit_input/unit_input.tsx +3 -3
- package/src/overlay/frame/frame.tsx +43 -63
- package/src/overlay/slide/slide.module.css +30 -0
- package/src/overlay/slide/slide.stories.tsx +58 -0
- package/src/overlay/slide/slide.tsx +51 -0
- package/src/stacks/box/box.tsx +10 -16
- package/src/stacks/box/detect_resize_bounds.ts +84 -0
- package/src/stacks/box/resize_handlers.ts +27 -15
- package/src/stacks/box/start_resize_handle.tsx +6 -3
- package/src/stacks/box/types.ts +23 -25
- package/src/surfaces/drawers/drawer.stories.tsx +130 -0
- package/src/surfaces/drawers/drawer.tsx +26 -0
- package/src/surfaces/index.ts +1 -4
- package/src/surfaces/modal/__stories__/modal.stories.tsx +70 -3
- package/src/surfaces/modal/modal.tsx +11 -2
- package/src/surfaces/window/window.stories.tsx +64 -8
- package/src/surfaces/window/window.tsx +6 -9
- package/src/themes/stylesheets/reset.css +2 -2
- package/src/themes/themes/ergo/ergo_theme.css +70 -6
- package/src/utils/dnd/hooks/use_drag_container.ts +0 -7
- package/dist/drawer_bottom.css +0 -1
- package/dist/drawer_end.css +0 -1
- package/dist/drawer_start.css +0 -1
- package/dist/drawer_top.css +0 -1
- package/dist/inputs/input_group/input_group.d.ts +0 -5
- package/dist/inputs/input_group/input_group.d.ts.map +0 -1
- package/dist/inputs/input_group/input_group.js +0 -20
- package/dist/surfaces/drawers/drawer_bottom/drawer_bottom.d.ts +0 -7
- package/dist/surfaces/drawers/drawer_bottom/drawer_bottom.d.ts.map +0 -1
- package/dist/surfaces/drawers/drawer_bottom/drawer_bottom.js +0 -22
- package/dist/surfaces/drawers/drawer_bottom/drawer_bottom.js.map +0 -1
- package/dist/surfaces/drawers/drawer_end/drawer_end.d.ts +0 -7
- package/dist/surfaces/drawers/drawer_end/drawer_end.d.ts.map +0 -1
- package/dist/surfaces/drawers/drawer_end/drawer_end.js +0 -20
- package/dist/surfaces/drawers/drawer_end/drawer_end.js.map +0 -1
- package/dist/surfaces/drawers/drawer_start/drawer_start.d.ts +0 -7
- package/dist/surfaces/drawers/drawer_start/drawer_start.d.ts.map +0 -1
- package/dist/surfaces/drawers/drawer_start/drawer_start.js +0 -22
- package/dist/surfaces/drawers/drawer_start/drawer_start.js.map +0 -1
- package/dist/surfaces/drawers/drawer_top/drawer_top.d.ts +0 -7
- package/dist/surfaces/drawers/drawer_top/drawer_top.d.ts.map +0 -1
- package/dist/surfaces/drawers/drawer_top/drawer_top.js +0 -20
- package/dist/surfaces/drawers/drawer_top/drawer_top.js.map +0 -1
- package/src/surfaces/drawers/__stories__/drawers.stories.tsx +0 -26
- package/src/surfaces/drawers/drawer_bottom/drawer_bottom.module.css +0 -5
- package/src/surfaces/drawers/drawer_bottom/drawer_bottom.tsx +0 -23
- package/src/surfaces/drawers/drawer_end/drawer_end.module.css +0 -5
- package/src/surfaces/drawers/drawer_end/drawer_end.tsx +0 -24
- package/src/surfaces/drawers/drawer_start/drawer_start.module.css +0 -5
- package/src/surfaces/drawers/drawer_start/drawer_start.tsx +0 -23
- package/src/surfaces/drawers/drawer_top/drawer_top.module.css +0 -5
- 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
|
+
});
|
package/src/stacks/box/box.tsx
CHANGED
|
@@ -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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
|
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
|
package/src/stacks/box/types.ts
CHANGED
|
@@ -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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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?:
|
|
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?:
|
|
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
|
+
});
|
package/src/surfaces/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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 />
|