@timmbr/ui 1.15.1 → 1.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.
- package/dist/components/Card/Card.styles.d.ts +1 -1
- package/dist/components/Panel/Panel.context.d.ts +17 -0
- package/dist/components/Panel/Panel.context.d.ts.map +1 -0
- package/dist/components/Panel/Panel.context.js +17 -0
- package/dist/components/Panel/Panel.d.ts +4 -0
- package/dist/components/Panel/Panel.d.ts.map +1 -0
- package/dist/components/Panel/Panel.helpers.d.ts +103 -0
- package/dist/components/Panel/Panel.helpers.d.ts.map +1 -0
- package/dist/components/Panel/Panel.helpers.js +179 -0
- package/dist/components/Panel/Panel.js +174 -0
- package/dist/components/Panel/Panel.styles.d.ts +42 -0
- package/dist/components/Panel/Panel.styles.d.ts.map +1 -0
- package/dist/components/Panel/Panel.styles.js +118 -0
- package/dist/components/Panel/Panel.types.d.ts +59 -0
- package/dist/components/Panel/Panel.types.d.ts.map +1 -0
- package/dist/components/Panel/index.d.ts +7 -0
- package/dist/components/Panel/index.d.ts.map +1 -0
- package/dist/components/Panel/subcomponents/PanelBody.d.ts +4 -0
- package/dist/components/Panel/subcomponents/PanelBody.d.ts.map +1 -0
- package/dist/components/Panel/subcomponents/PanelBody.js +58 -0
- package/dist/components/Panel/subcomponents/PanelDescription.d.ts +4 -0
- package/dist/components/Panel/subcomponents/PanelDescription.d.ts.map +1 -0
- package/dist/components/Panel/subcomponents/PanelDescription.js +24 -0
- package/dist/components/Panel/subcomponents/PanelFooter.d.ts +4 -0
- package/dist/components/Panel/subcomponents/PanelFooter.d.ts.map +1 -0
- package/dist/components/Panel/subcomponents/PanelFooter.js +27 -0
- package/dist/components/Panel/subcomponents/PanelHeader.d.ts +4 -0
- package/dist/components/Panel/subcomponents/PanelHeader.d.ts.map +1 -0
- package/dist/components/Panel/subcomponents/PanelHeader.js +101 -0
- package/dist/components/Panel/subcomponents/PanelResizeHandle.d.ts +11 -0
- package/dist/components/Panel/subcomponents/PanelResizeHandle.d.ts.map +1 -0
- package/dist/components/Panel/subcomponents/PanelResizeHandle.js +29 -0
- package/dist/components/Panel/subcomponents/PanelTitle.d.ts +4 -0
- package/dist/components/Panel/subcomponents/PanelTitle.d.ts.map +1 -0
- package/dist/components/Panel/subcomponents/PanelTitle.js +24 -0
- package/dist/components/Panel/subcomponents/index.d.ts +6 -0
- package/dist/components/Panel/subcomponents/index.d.ts.map +1 -0
- package/dist/components/Stepper/Stepper.d.ts +2 -0
- package/dist/components/Stepper/Stepper.d.ts.map +1 -1
- package/dist/components/Stepper/Stepper.js +77 -63
- package/dist/components/Stepper/Stepper.styles.d.ts +9 -0
- package/dist/components/Stepper/Stepper.styles.d.ts.map +1 -1
- package/dist/components/Stepper/Stepper.styles.js +43 -1
- package/dist/components/Stepper/Stepper.types.d.ts +14 -1
- package/dist/components/Stepper/Stepper.types.d.ts.map +1 -1
- package/dist/components/Stepper/subcomponents/ProgressStepper.d.ts +13 -0
- package/dist/components/Stepper/subcomponents/ProgressStepper.d.ts.map +1 -0
- package/dist/components/Stepper/subcomponents/ProgressStepper.js +77 -0
- package/dist/components/Stepper/subcomponents/index.d.ts +3 -0
- package/dist/components/Stepper/subcomponents/index.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type VariantProps } from 'class-variance-authority';
|
|
2
2
|
export declare const cardVariants: (props?: ({
|
|
3
|
-
variant?: "inset" | "outline" | "
|
|
3
|
+
variant?: "inset" | "outline" | "elevated" | "subtle" | "interactive" | null | undefined;
|
|
4
4
|
padding?: "none" | "sm" | "md" | "lg" | "default" | null | undefined;
|
|
5
5
|
selected?: boolean | null | undefined;
|
|
6
6
|
sectioned?: boolean | null | undefined;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { panelSectionPadding } from './Panel.styles';
|
|
3
|
+
export type PanelPadding = keyof typeof panelSectionPadding;
|
|
4
|
+
export type PanelPlacement = 'inline' | 'left' | 'right';
|
|
5
|
+
export interface PanelContextValue {
|
|
6
|
+
sectioned: boolean;
|
|
7
|
+
padding: PanelPadding;
|
|
8
|
+
collapsed: boolean;
|
|
9
|
+
collapsible: boolean;
|
|
10
|
+
shouldAnimate: boolean;
|
|
11
|
+
panelId: string;
|
|
12
|
+
placement: PanelPlacement;
|
|
13
|
+
onToggleCollapse: () => void;
|
|
14
|
+
}
|
|
15
|
+
export declare const PanelContext: React.Context<PanelContextValue>;
|
|
16
|
+
export declare const usePanelContext: () => PanelContextValue;
|
|
17
|
+
//# sourceMappingURL=Panel.context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Panel.context.d.ts","sourceRoot":"","sources":["../../../src/components/Panel/Panel.context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,mBAAmB,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,CAAC;IAC1B,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,eAAO,MAAM,YAAY,kCASvB,CAAC;AAEH,eAAO,MAAM,eAAe,yBAAuC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
const PanelContext = React.createContext({
|
|
5
|
+
sectioned: false,
|
|
6
|
+
padding: "default",
|
|
7
|
+
collapsed: false,
|
|
8
|
+
collapsible: false,
|
|
9
|
+
shouldAnimate: true,
|
|
10
|
+
panelId: "",
|
|
11
|
+
placement: "inline",
|
|
12
|
+
onToggleCollapse: () => {
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const usePanelContext = () => React.useContext(PanelContext);
|
|
16
|
+
|
|
17
|
+
export { PanelContext, usePanelContext };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Panel.d.ts","sourceRoot":"","sources":["../../../src/components/Panel/Panel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAkB/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,eAAO,MAAM,KAAK,mFAqMjB,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useMotionValue } from '@timmbr/motion';
|
|
3
|
+
export declare const DEFAULT_H_MIN = 120;
|
|
4
|
+
export declare const DEFAULT_H_MAX = 640;
|
|
5
|
+
export declare const DEFAULT_H_SIZE = 320;
|
|
6
|
+
export declare const DEFAULT_H_COLLAPSED = 40;
|
|
7
|
+
export declare const DEFAULT_V_MIN = 80;
|
|
8
|
+
export declare const DEFAULT_V_MAX = 480;
|
|
9
|
+
export declare const DEFAULT_V_SIZE = 240;
|
|
10
|
+
export declare const DEFAULT_V_COLLAPSED = 40;
|
|
11
|
+
export interface PanelSizes {
|
|
12
|
+
isH: boolean;
|
|
13
|
+
resolvedMin: number;
|
|
14
|
+
resolvedMax: number;
|
|
15
|
+
resolvedDefault: number;
|
|
16
|
+
resolvedCollapsedSize: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Derives the concrete px values for min/max/default/collapsed from the
|
|
20
|
+
* raw props, falling back to axis-specific defaults.
|
|
21
|
+
*
|
|
22
|
+
* Side panels (left/right) always collapse along the horizontal axis;
|
|
23
|
+
* only inline panels with resizable='vertical' use the vertical axis.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolvePanelSizes(resizable: false | 'horizontal' | 'vertical', placement: 'inline' | 'left' | 'right', minSize?: number, maxSize?: number, defaultSize?: number, collapsedSize?: number): PanelSizes;
|
|
26
|
+
export interface UsePanelResizeOptions {
|
|
27
|
+
containerRef: React.RefObject<HTMLElement | null>;
|
|
28
|
+
resizable: false | 'horizontal' | 'vertical';
|
|
29
|
+
/** Which edge the panel is docked to — determines whether delta should be inverted. */
|
|
30
|
+
placement?: 'inline' | 'left' | 'right';
|
|
31
|
+
minSize: number;
|
|
32
|
+
maxSize: number;
|
|
33
|
+
defaultSize: number;
|
|
34
|
+
/** Starting size for the motion value — may differ from defaultSize when initially collapsed. */
|
|
35
|
+
initialValue?: number;
|
|
36
|
+
onResize?: (size: number) => void;
|
|
37
|
+
}
|
|
38
|
+
export interface UsePanelResizeReturn {
|
|
39
|
+
size: number;
|
|
40
|
+
isResizing: boolean;
|
|
41
|
+
/** Live motion value driving the panel's inline width/height — also used by the collapse spring. */
|
|
42
|
+
sizeMotionValue: ReturnType<typeof useMotionValue<number>>;
|
|
43
|
+
handleProps: React.HTMLAttributes<HTMLDivElement> & {
|
|
44
|
+
'data-resizing'?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface UsePanelCollapseOptions {
|
|
48
|
+
collapsible: boolean;
|
|
49
|
+
collapsed?: boolean;
|
|
50
|
+
defaultCollapsed?: boolean;
|
|
51
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
52
|
+
}
|
|
53
|
+
export interface UsePanelCollapseReturn {
|
|
54
|
+
collapsed: boolean;
|
|
55
|
+
toggle: () => void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Manages drag-to-resize behaviour for the Panel.
|
|
59
|
+
*
|
|
60
|
+
* During drag, `sizeMotionValue` is updated directly for zero-stutter 60fps
|
|
61
|
+
* tracking. The collapse spring in Panel.tsx reuses the same motion value so
|
|
62
|
+
* both interactions drive the element through a single binding.
|
|
63
|
+
*
|
|
64
|
+
* When animations are disabled, the same code path runs — motion values still
|
|
65
|
+
* work as plain reactive values; only the spring in the collapse path is skipped.
|
|
66
|
+
*/
|
|
67
|
+
export declare function usePanelResize({ containerRef: _containerRef, resizable, placement, minSize, maxSize, defaultSize, initialValue, onResize, }: UsePanelResizeOptions): UsePanelResizeReturn;
|
|
68
|
+
/**
|
|
69
|
+
* Imperatively spring-animates the panel size motion value to a target.
|
|
70
|
+
* Called from Panel.tsx when the collapse state changes.
|
|
71
|
+
*/
|
|
72
|
+
export declare function animatePanelSize(motionValue: ReturnType<typeof useMotionValue<number>>, target: number, shouldAnimate: boolean): void;
|
|
73
|
+
/**
|
|
74
|
+
* Manages controlled/uncontrolled collapse state for the Panel toggle button.
|
|
75
|
+
*/
|
|
76
|
+
export declare function usePanelCollapse({ collapsible, collapsed, defaultCollapsed, onCollapsedChange, }: UsePanelCollapseOptions): UsePanelCollapseReturn;
|
|
77
|
+
export interface UsePanelSnapCoordinationOptions {
|
|
78
|
+
/** Whether snap-to-collapse coordination is active (requires collapsible + resizable + snapToCollapse). */
|
|
79
|
+
activelySnapping: boolean;
|
|
80
|
+
handleProps: UsePanelResizeReturn['handleProps'];
|
|
81
|
+
collapsed: boolean;
|
|
82
|
+
sizeMotionValue: ReturnType<typeof useMotionValue<number>>;
|
|
83
|
+
resolvedMin: number;
|
|
84
|
+
resolvedCollapsedSize: number;
|
|
85
|
+
toggle: () => void;
|
|
86
|
+
isH: boolean;
|
|
87
|
+
placement: 'inline' | 'left' | 'right';
|
|
88
|
+
/** Mutable ref tracking the last expanded size — owned by Panel root, shared here. */
|
|
89
|
+
expandedSizeRef: React.MutableRefObject<number>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Wraps the resize handle props to coordinate snap-to-collapse behaviour:
|
|
93
|
+
*
|
|
94
|
+
* EXPANDING (panel is collapsed):
|
|
95
|
+
* - A bare click must NOT expand. Panel stays at collapsedSize until drag crosses minSize.
|
|
96
|
+
* - Once the threshold is crossed the panel expands and normal resize takes over.
|
|
97
|
+
*
|
|
98
|
+
* COLLAPSING (panel is expanded):
|
|
99
|
+
* - When the user releases at minSize (the clamped floor), they wanted to go smaller —
|
|
100
|
+
* the panel smooth-animates to collapsedSize.
|
|
101
|
+
*/
|
|
102
|
+
export declare function usePanelSnapCoordination({ activelySnapping, handleProps, collapsed, sizeMotionValue, resolvedMin, resolvedCollapsedSize, toggle, isH, placement, expandedSizeRef, }: UsePanelSnapCoordinationOptions): UsePanelResizeReturn['handleProps'];
|
|
103
|
+
//# sourceMappingURL=Panel.helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Panel.helpers.d.ts","sourceRoot":"","sources":["../../../src/components/Panel/Panel.helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useControllableState } from '@timmbr/hooks';
|
|
3
|
+
import { animate, useMotionValue } from '@timmbr/motion';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_H_MIN = 120;
|
|
6
|
+
const DEFAULT_H_MAX = 640;
|
|
7
|
+
const DEFAULT_H_SIZE = 320;
|
|
8
|
+
const DEFAULT_H_COLLAPSED = 40;
|
|
9
|
+
const DEFAULT_V_MIN = 80;
|
|
10
|
+
const DEFAULT_V_MAX = 480;
|
|
11
|
+
const DEFAULT_V_SIZE = 240;
|
|
12
|
+
const DEFAULT_V_COLLAPSED = 40;
|
|
13
|
+
function resolvePanelSizes(resizable, placement, minSize, maxSize, defaultSize, collapsedSize) {
|
|
14
|
+
const isH = resizable === "horizontal" || placement === "right" || placement === "left";
|
|
15
|
+
return {
|
|
16
|
+
isH,
|
|
17
|
+
resolvedMin: minSize ?? (isH ? DEFAULT_H_MIN : DEFAULT_V_MIN),
|
|
18
|
+
resolvedMax: maxSize ?? (isH ? DEFAULT_H_MAX : DEFAULT_V_MAX),
|
|
19
|
+
resolvedDefault: defaultSize ?? (isH ? DEFAULT_H_SIZE : DEFAULT_V_SIZE),
|
|
20
|
+
resolvedCollapsedSize: collapsedSize ?? (isH ? DEFAULT_H_COLLAPSED : DEFAULT_V_COLLAPSED)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function usePanelResize({
|
|
24
|
+
containerRef: _containerRef,
|
|
25
|
+
resizable,
|
|
26
|
+
placement = "inline",
|
|
27
|
+
minSize,
|
|
28
|
+
maxSize,
|
|
29
|
+
defaultSize,
|
|
30
|
+
initialValue,
|
|
31
|
+
onResize
|
|
32
|
+
}) {
|
|
33
|
+
const [size, setSize] = React.useState(defaultSize);
|
|
34
|
+
const [isResizing, setIsResizing] = React.useState(false);
|
|
35
|
+
const sizeMotionValue = useMotionValue(initialValue ?? defaultSize);
|
|
36
|
+
const dragStartPos = React.useRef(0);
|
|
37
|
+
const dragStartSize = React.useRef(defaultSize);
|
|
38
|
+
const clamp = React.useCallback(
|
|
39
|
+
(v) => Math.min(maxSize, Math.max(minSize, v)),
|
|
40
|
+
[minSize, maxSize]
|
|
41
|
+
);
|
|
42
|
+
const handlePointerDown = React.useCallback(
|
|
43
|
+
(e) => {
|
|
44
|
+
if (resizable === false) return;
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
e.target.setPointerCapture(e.pointerId);
|
|
47
|
+
dragStartPos.current = resizable === "horizontal" ? e.clientX : e.clientY;
|
|
48
|
+
dragStartSize.current = sizeMotionValue.get();
|
|
49
|
+
setIsResizing(true);
|
|
50
|
+
},
|
|
51
|
+
[resizable, sizeMotionValue]
|
|
52
|
+
);
|
|
53
|
+
const handlePointerMove = React.useCallback(
|
|
54
|
+
(e) => {
|
|
55
|
+
if (!e.buttons || resizable === false) return;
|
|
56
|
+
const rawDelta = (resizable === "horizontal" ? e.clientX : e.clientY) - dragStartPos.current;
|
|
57
|
+
const delta = placement === "right" ? -rawDelta : rawDelta;
|
|
58
|
+
const next = clamp(dragStartSize.current + delta);
|
|
59
|
+
sizeMotionValue.set(next);
|
|
60
|
+
},
|
|
61
|
+
[resizable, placement, clamp, sizeMotionValue]
|
|
62
|
+
);
|
|
63
|
+
const handlePointerUp = React.useCallback(() => {
|
|
64
|
+
if (!isResizing) return;
|
|
65
|
+
const final = sizeMotionValue.get();
|
|
66
|
+
setIsResizing(false);
|
|
67
|
+
setSize(final);
|
|
68
|
+
onResize?.(final);
|
|
69
|
+
}, [isResizing, sizeMotionValue, onResize]);
|
|
70
|
+
const handleProps = {
|
|
71
|
+
role: "separator",
|
|
72
|
+
"aria-label": resizable === "horizontal" ? "Resize panel width" : "Resize panel height",
|
|
73
|
+
onPointerDown: handlePointerDown,
|
|
74
|
+
onPointerMove: handlePointerMove,
|
|
75
|
+
onPointerUp: handlePointerUp,
|
|
76
|
+
...isResizing ? { "data-resizing": "true" } : {}
|
|
77
|
+
};
|
|
78
|
+
return { size, isResizing, sizeMotionValue, handleProps };
|
|
79
|
+
}
|
|
80
|
+
function animatePanelSize(motionValue, target, shouldAnimate) {
|
|
81
|
+
if (shouldAnimate) {
|
|
82
|
+
animate(motionValue, target, {
|
|
83
|
+
type: "tween",
|
|
84
|
+
duration: 0.2,
|
|
85
|
+
ease: [0.25, 1, 0.5, 1]
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
motionValue.set(target);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function usePanelCollapse({
|
|
92
|
+
collapsible,
|
|
93
|
+
collapsed,
|
|
94
|
+
defaultCollapsed = false,
|
|
95
|
+
onCollapsedChange
|
|
96
|
+
}) {
|
|
97
|
+
const [isCollapsed, setIsCollapsed] = useControllableState({
|
|
98
|
+
value: collapsed,
|
|
99
|
+
defaultValue: defaultCollapsed,
|
|
100
|
+
onChange: onCollapsedChange
|
|
101
|
+
});
|
|
102
|
+
const toggle = React.useCallback(() => {
|
|
103
|
+
if (!collapsible) return;
|
|
104
|
+
setIsCollapsed((prev) => !prev);
|
|
105
|
+
}, [collapsible, setIsCollapsed]);
|
|
106
|
+
return { collapsed: isCollapsed ?? false, toggle };
|
|
107
|
+
}
|
|
108
|
+
function usePanelSnapCoordination({
|
|
109
|
+
activelySnapping,
|
|
110
|
+
handleProps,
|
|
111
|
+
collapsed,
|
|
112
|
+
sizeMotionValue,
|
|
113
|
+
resolvedMin,
|
|
114
|
+
resolvedCollapsedSize,
|
|
115
|
+
toggle,
|
|
116
|
+
isH,
|
|
117
|
+
placement,
|
|
118
|
+
expandedSizeRef
|
|
119
|
+
}) {
|
|
120
|
+
const dragFromCollapsedRef = React.useRef(false);
|
|
121
|
+
const pendingExpandRef = React.useRef(false);
|
|
122
|
+
const pendingDragStartPos = React.useRef(0);
|
|
123
|
+
return React.useMemo(() => {
|
|
124
|
+
if (!activelySnapping) return handleProps;
|
|
125
|
+
const origDown = handleProps.onPointerDown;
|
|
126
|
+
const origMove = handleProps.onPointerMove;
|
|
127
|
+
const origUp = handleProps.onPointerUp;
|
|
128
|
+
return {
|
|
129
|
+
...handleProps,
|
|
130
|
+
onPointerDown: (e) => {
|
|
131
|
+
if (collapsed) {
|
|
132
|
+
pendingExpandRef.current = true;
|
|
133
|
+
dragFromCollapsedRef.current = true;
|
|
134
|
+
pendingDragStartPos.current = isH ? e.clientX : e.clientY;
|
|
135
|
+
} else {
|
|
136
|
+
pendingExpandRef.current = false;
|
|
137
|
+
dragFromCollapsedRef.current = false;
|
|
138
|
+
}
|
|
139
|
+
origDown?.(e);
|
|
140
|
+
},
|
|
141
|
+
onPointerMove: (e) => {
|
|
142
|
+
if (!pendingExpandRef.current) {
|
|
143
|
+
origMove?.(e);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const curPos = isH ? e.clientX : e.clientY;
|
|
147
|
+
const rawDelta = curPos - pendingDragStartPos.current;
|
|
148
|
+
const delta = placement === "right" ? -rawDelta : rawDelta;
|
|
149
|
+
const wouldBe = resolvedCollapsedSize + delta;
|
|
150
|
+
if (wouldBe >= resolvedMin) {
|
|
151
|
+
pendingExpandRef.current = false;
|
|
152
|
+
expandedSizeRef.current = resolvedMin;
|
|
153
|
+
toggle();
|
|
154
|
+
origMove?.(e);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
onPointerUp: (e) => {
|
|
158
|
+
const wasPending = pendingExpandRef.current;
|
|
159
|
+
pendingExpandRef.current = false;
|
|
160
|
+
origUp?.(e);
|
|
161
|
+
if (wasPending) {
|
|
162
|
+
expandedSizeRef.current = resolvedMin;
|
|
163
|
+
dragFromCollapsedRef.current = false;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (dragFromCollapsedRef.current) {
|
|
167
|
+
dragFromCollapsedRef.current = false;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!collapsed && sizeMotionValue.get() <= resolvedMin) {
|
|
171
|
+
expandedSizeRef.current = resolvedMin;
|
|
172
|
+
toggle();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}, [activelySnapping, handleProps, collapsed, sizeMotionValue, resolvedMin, resolvedCollapsedSize, toggle, isH, placement]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { DEFAULT_H_COLLAPSED, DEFAULT_H_MAX, DEFAULT_H_MIN, DEFAULT_H_SIZE, DEFAULT_V_COLLAPSED, DEFAULT_V_MAX, DEFAULT_V_MIN, DEFAULT_V_SIZE, animatePanelSize, resolvePanelSizes, usePanelCollapse, usePanelResize, usePanelSnapCoordination };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
5
|
+
import { cn } from '@timmbr/utils';
|
|
6
|
+
import { motion } from '@timmbr/motion';
|
|
7
|
+
import { usePrevious } from '@timmbr/hooks';
|
|
8
|
+
import { resolveMotion } from '../../types/motion.js';
|
|
9
|
+
import { panelVariants } from './Panel.styles.js';
|
|
10
|
+
import { resolvePanelSizes, usePanelResize, usePanelCollapse, animatePanelSize, usePanelSnapCoordination } from './Panel.helpers.js';
|
|
11
|
+
import { PanelContext } from './Panel.context.js';
|
|
12
|
+
import { PanelResizeHandle } from './subcomponents/PanelResizeHandle.js';
|
|
13
|
+
import { useGlobalAnimation } from '../../providers/AnimationProvider.js';
|
|
14
|
+
|
|
15
|
+
const Panel = React.forwardRef(
|
|
16
|
+
({
|
|
17
|
+
className,
|
|
18
|
+
variant,
|
|
19
|
+
padding,
|
|
20
|
+
placement = "inline",
|
|
21
|
+
sectioned = false,
|
|
22
|
+
asChild = false,
|
|
23
|
+
resizable = false,
|
|
24
|
+
minSize,
|
|
25
|
+
maxSize,
|
|
26
|
+
defaultSize,
|
|
27
|
+
onResize,
|
|
28
|
+
collapsible = false,
|
|
29
|
+
collapsed: controlledCollapsed,
|
|
30
|
+
defaultCollapsed = false,
|
|
31
|
+
collapsedSize,
|
|
32
|
+
onCollapsedChange,
|
|
33
|
+
snapToCollapse = true,
|
|
34
|
+
motion: motionProp,
|
|
35
|
+
children,
|
|
36
|
+
...props
|
|
37
|
+
}, ref) => {
|
|
38
|
+
const globalMotion = useGlobalAnimation();
|
|
39
|
+
const { shouldAnimate, motionClass } = resolveMotion(motionProp, globalMotion);
|
|
40
|
+
const rawId = React.useId();
|
|
41
|
+
const panelId = rawId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
42
|
+
const containerRef = React.useRef(null);
|
|
43
|
+
const resolvedPlacement = placement ?? "inline";
|
|
44
|
+
const { isH, resolvedMin, resolvedMax, resolvedDefault, resolvedCollapsedSize } = resolvePanelSizes(resizable, resolvedPlacement, minSize, maxSize, defaultSize, collapsedSize);
|
|
45
|
+
const isInitiallyCollapsed = collapsible && (defaultCollapsed || controlledCollapsed === true);
|
|
46
|
+
const initialMotionValue = isInitiallyCollapsed ? resolvedCollapsedSize : resolvedDefault;
|
|
47
|
+
const expandedSizeRef = React.useRef(isInitiallyCollapsed ? resolvedDefault : initialMotionValue);
|
|
48
|
+
const { isResizing, sizeMotionValue, handleProps } = usePanelResize({
|
|
49
|
+
containerRef,
|
|
50
|
+
resizable,
|
|
51
|
+
placement: resolvedPlacement,
|
|
52
|
+
minSize: resolvedMin,
|
|
53
|
+
maxSize: resolvedMax,
|
|
54
|
+
defaultSize: resolvedDefault,
|
|
55
|
+
initialValue: initialMotionValue,
|
|
56
|
+
onResize: (newSize) => {
|
|
57
|
+
expandedSizeRef.current = newSize;
|
|
58
|
+
onResize?.(newSize);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const { collapsed, toggle } = usePanelCollapse({
|
|
62
|
+
collapsible,
|
|
63
|
+
collapsed: controlledCollapsed,
|
|
64
|
+
defaultCollapsed,
|
|
65
|
+
onCollapsedChange
|
|
66
|
+
});
|
|
67
|
+
const prevCollapsed = usePrevious(collapsed);
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
if (!collapsible || prevCollapsed === void 0 || prevCollapsed === collapsed) return;
|
|
70
|
+
animatePanelSize(
|
|
71
|
+
sizeMotionValue,
|
|
72
|
+
collapsed ? resolvedCollapsedSize : expandedSizeRef.current,
|
|
73
|
+
shouldAnimate
|
|
74
|
+
);
|
|
75
|
+
}, [collapsed, collapsible, resolvedCollapsedSize, sizeMotionValue, shouldAnimate]);
|
|
76
|
+
const coordHandleProps = usePanelSnapCoordination({
|
|
77
|
+
activelySnapping: collapsible && resizable !== false && snapToCollapse,
|
|
78
|
+
handleProps,
|
|
79
|
+
collapsed,
|
|
80
|
+
sizeMotionValue,
|
|
81
|
+
resolvedMin,
|
|
82
|
+
resolvedCollapsedSize,
|
|
83
|
+
toggle,
|
|
84
|
+
isH,
|
|
85
|
+
placement: resolvedPlacement,
|
|
86
|
+
expandedSizeRef
|
|
87
|
+
});
|
|
88
|
+
const context = React.useMemo(
|
|
89
|
+
() => ({
|
|
90
|
+
sectioned,
|
|
91
|
+
padding: padding ?? "default",
|
|
92
|
+
collapsed,
|
|
93
|
+
collapsible,
|
|
94
|
+
shouldAnimate,
|
|
95
|
+
panelId,
|
|
96
|
+
placement: resolvedPlacement,
|
|
97
|
+
onToggleCollapse: toggle
|
|
98
|
+
}),
|
|
99
|
+
[sectioned, padding, collapsed, collapsible, shouldAnimate, panelId, resolvedPlacement, toggle]
|
|
100
|
+
);
|
|
101
|
+
const needsSizeControl = resizable !== false || collapsible;
|
|
102
|
+
const dimension = isH ? "width" : "height";
|
|
103
|
+
const handleEdge = resizable === "horizontal" ? resolvedPlacement === "right" ? "left" : "right" : "bottom";
|
|
104
|
+
const rootClasses = cn(
|
|
105
|
+
panelVariants({ variant, padding, sectioned, placement }),
|
|
106
|
+
needsSizeControl && "overflow-hidden",
|
|
107
|
+
!shouldAnimate && "transition-none animate-none",
|
|
108
|
+
motionClass,
|
|
109
|
+
className
|
|
110
|
+
);
|
|
111
|
+
const combinedRef = React.useCallback(
|
|
112
|
+
(node) => {
|
|
113
|
+
containerRef.current = node;
|
|
114
|
+
if (typeof ref === "function") ref(node);
|
|
115
|
+
else if (ref) ref.current = node;
|
|
116
|
+
},
|
|
117
|
+
[ref]
|
|
118
|
+
);
|
|
119
|
+
const rootContent = /* @__PURE__ */ jsxs(PanelContext.Provider, { value: context, children: [
|
|
120
|
+
children,
|
|
121
|
+
resizable !== false && /* @__PURE__ */ jsx(
|
|
122
|
+
PanelResizeHandle,
|
|
123
|
+
{
|
|
124
|
+
direction: handleEdge,
|
|
125
|
+
isResizing,
|
|
126
|
+
handleProps: coordHandleProps
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
] });
|
|
130
|
+
if (asChild) {
|
|
131
|
+
return /* @__PURE__ */ jsx(
|
|
132
|
+
Slot,
|
|
133
|
+
{
|
|
134
|
+
ref,
|
|
135
|
+
"data-slot": "panel",
|
|
136
|
+
"data-collapsed": collapsed || void 0,
|
|
137
|
+
className: rootClasses,
|
|
138
|
+
...props,
|
|
139
|
+
children: rootContent
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (needsSizeControl && shouldAnimate) {
|
|
144
|
+
return /* @__PURE__ */ jsx(
|
|
145
|
+
motion.div,
|
|
146
|
+
{
|
|
147
|
+
ref: combinedRef,
|
|
148
|
+
"data-slot": "panel",
|
|
149
|
+
"data-collapsed": collapsed || void 0,
|
|
150
|
+
className: rootClasses,
|
|
151
|
+
style: { [dimension]: sizeMotionValue },
|
|
152
|
+
...props,
|
|
153
|
+
children: rootContent
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const staticStyle = needsSizeControl ? { [dimension]: collapsed ? resolvedCollapsedSize : sizeMotionValue.get(), ...props.style ?? {} } : props.style ?? void 0;
|
|
158
|
+
return /* @__PURE__ */ jsx(
|
|
159
|
+
"div",
|
|
160
|
+
{
|
|
161
|
+
ref: combinedRef,
|
|
162
|
+
"data-slot": "panel",
|
|
163
|
+
"data-collapsed": collapsed || void 0,
|
|
164
|
+
className: rootClasses,
|
|
165
|
+
style: staticStyle,
|
|
166
|
+
...props,
|
|
167
|
+
children: rootContent
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
Panel.displayName = "Panel";
|
|
173
|
+
|
|
174
|
+
export { Panel };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
2
|
+
export declare const panelVariants: (props?: ({
|
|
3
|
+
variant?: "outline" | "ghost" | "elevated" | "subtle" | null | undefined;
|
|
4
|
+
padding?: "none" | "sm" | "lg" | "default" | null | undefined;
|
|
5
|
+
sectioned?: boolean | null | undefined;
|
|
6
|
+
placement?: "left" | "right" | "inline" | null | undefined;
|
|
7
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
+
export declare const panelSectionPadding: {
|
|
9
|
+
readonly none: "";
|
|
10
|
+
readonly sm: "p-4";
|
|
11
|
+
readonly default: "p-6";
|
|
12
|
+
readonly lg: "p-8";
|
|
13
|
+
};
|
|
14
|
+
export declare const panelSectionFooterPadding: {
|
|
15
|
+
readonly none: "";
|
|
16
|
+
readonly sm: "px-4 py-2";
|
|
17
|
+
readonly default: "px-6 py-3";
|
|
18
|
+
readonly lg: "px-8 py-4";
|
|
19
|
+
};
|
|
20
|
+
export declare const panelHeaderVariants: (props?: ({
|
|
21
|
+
sectioned?: boolean | null | undefined;
|
|
22
|
+
divider?: boolean | null | undefined;
|
|
23
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
24
|
+
export declare const panelTitleVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
25
|
+
export declare const panelDescriptionVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
26
|
+
export declare const panelBodyVariants: (props?: ({
|
|
27
|
+
overflow?: "auto" | "hidden" | "visible" | null | undefined;
|
|
28
|
+
sectioned?: boolean | null | undefined;
|
|
29
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
30
|
+
export declare const panelFooterVariants: (props?: ({
|
|
31
|
+
sectioned?: boolean | null | undefined;
|
|
32
|
+
justify?: "start" | "end" | "between" | null | undefined;
|
|
33
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
34
|
+
export declare const panelResizeHandleVariants: (props?: ({
|
|
35
|
+
direction?: "left" | "right" | "bottom" | null | undefined;
|
|
36
|
+
resizing?: boolean | null | undefined;
|
|
37
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
38
|
+
export type PanelVariants = VariantProps<typeof panelVariants>;
|
|
39
|
+
export type PanelBodyVariants = VariantProps<typeof panelBodyVariants>;
|
|
40
|
+
export type PanelFooterVariants = VariantProps<typeof panelFooterVariants>;
|
|
41
|
+
export type PanelResizeHandleVariants = VariantProps<typeof panelResizeHandleVariants>;
|
|
42
|
+
//# sourceMappingURL=Panel.styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Panel.styles.d.ts","sourceRoot":"","sources":["../../../src/components/Panel/Panel.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAElE,eAAO,MAAM,aAAa;;;;;8EAiCzB,CAAC;AAEF,eAAO,MAAM,mBAAmB;;;;;CAKtB,CAAC;AAEX,eAAO,MAAM,yBAAyB;;;;;CAK5B,CAAC;AAEX,eAAO,MAAM,mBAAmB;;;8EAa9B,CAAC;AAEH,eAAO,MAAM,kBAAkB,oFAE9B,CAAC;AAEF,eAAO,MAAM,wBAAwB,oFAEpC,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;8EAa5B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;8EAgB/B,CAAC;AAEF,eAAO,MAAM,yBAAyB;;;8EAgBrC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAC3E,MAAM,MAAM,yBAAyB,GAAG,YAAY,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|