@trackunit/react-components 1.21.14 → 1.21.15
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/index.cjs.js +2044 -193
- package/index.esm.js +2039 -194
- package/package.json +1 -1
- package/src/components/Sheet/Sheet.d.ts +29 -0
- package/src/components/Sheet/Sheet.variants.d.ts +63 -0
- package/src/components/Sheet/SheetHandle.d.ts +64 -0
- package/src/components/Sheet/SheetOverlay.d.ts +20 -0
- package/src/components/Sheet/sheet-animation-reducer.d.ts +30 -0
- package/src/components/Sheet/sheet-gesture-utils.d.ts +55 -0
- package/src/components/Sheet/sheet-height-utils.d.ts +20 -0
- package/src/components/Sheet/sheet-snap-config.d.ts +64 -0
- package/src/components/Sheet/sheet-types.d.ts +209 -0
- package/src/components/Sheet/snap-reducer.d.ts +48 -0
- package/src/components/Sheet/useDockedContentHeight.d.ts +26 -0
- package/src/components/Sheet/useProximityAnimation.d.ts +19 -0
- package/src/components/Sheet/useSheet.d.ts +27 -0
- package/src/components/Sheet/useSheetDismissIntent.d.ts +28 -0
- package/src/components/Sheet/useSheetFitHeight.d.ts +31 -0
- package/src/components/Sheet/useSheetGestures.d.ts +49 -0
- package/src/components/Sheet/useSheetLifecycle.d.ts +35 -0
- package/src/components/Sheet/useSheetMeasurements.d.ts +31 -0
- package/src/components/Sheet/useSheetMotionOverflow.d.ts +30 -0
- package/src/components/Sheet/useSheetSnap.d.ts +14 -0
- package/src/index.d.ts +5 -0
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
|
+
import type { SheetProps } from "./sheet-types";
|
|
3
|
+
/**
|
|
4
|
+
* Container-scoped bottom sheet with adaptive snap levels and gesture support.
|
|
5
|
+
*
|
|
6
|
+
* Use Sheet for contextual surfaces that slide up from the bottom of a
|
|
7
|
+
* container -- action menus, detail panels, element settings, or filters.
|
|
8
|
+
* Every Sheet renders via Portal into a required `container` element.
|
|
9
|
+
* On small screens, consider using Sheet instead of a Popover for better
|
|
10
|
+
* touch UX. For app-level blocking dialogs, use Modal instead (which
|
|
11
|
+
* automatically renders as a Sheet on small screens).
|
|
12
|
+
*
|
|
13
|
+
* When `variant="modal"`, the sheet behaves as a dialog: it renders a
|
|
14
|
+
* dimming backdrop, sets `role="dialog"` and `aria-modal` on the panel,
|
|
15
|
+
* and traps keyboard focus via FloatingFocusManager. Pass
|
|
16
|
+
* `trapFocus={false}` when a parent component provides its own focus
|
|
17
|
+
* management (e.g. Modal in sheet mode).
|
|
18
|
+
*
|
|
19
|
+
* Snap levels are adaptive: the Sheet measures the container and creates
|
|
20
|
+
* fewer stops in shorter containers. Consumers navigate with directional
|
|
21
|
+
* methods (`snapUp`, `snapDown`, `expand`, `collapse`, `dock`) instead of
|
|
22
|
+
* naming specific snap points.
|
|
23
|
+
*
|
|
24
|
+
* The outermost wrapper sets `container-type: size` so cqh/cqw units
|
|
25
|
+
* resolve against the container's dimensions. Open/close animations use
|
|
26
|
+
* CSS transitions on transform; the component stays mounted during the
|
|
27
|
+
* close animation and unmounts after the transition completes.
|
|
28
|
+
*/
|
|
29
|
+
export declare const Sheet: ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, onCloseGesture, floatingUi, ref, anchor, snapping, resizable, variant, trapFocus, container, dockedContent, className, "data-testid": dataTestId, onCloseComplete, entryAnimation, children, }: SheetProps) => ReactElement | null;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import type { SheetAnimationState } from "./sheet-animation-reducer";
|
|
3
|
+
import type { SheetVariant } from "./sheet-types";
|
|
4
|
+
/**
|
|
5
|
+
* Container wrapper that creates the positioning context and container query
|
|
6
|
+
* context for the Sheet. Sets `container-type: size` so cqh and cqw units
|
|
7
|
+
* resolve against this wrapper's dimensions.
|
|
8
|
+
*
|
|
9
|
+
* Always absolutely positioned within the provided container element.
|
|
10
|
+
*/
|
|
11
|
+
export declare const cvaSheetContainer: (props?: ({
|
|
12
|
+
docked?: boolean | null | undefined;
|
|
13
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Sheet panel — the main content surface, always positioned at the bottom
|
|
16
|
+
* of the container.
|
|
17
|
+
*
|
|
18
|
+
* Height and vertical positioning are controlled via inline styles returned
|
|
19
|
+
* by `getSheetPanelStyle()`:
|
|
20
|
+
*
|
|
21
|
+
* - `height`: Target height for the current snap (e.g., "50cqh"), or
|
|
22
|
+
* `--sheet-drag-height` during drag so the sheet stays anchored at the bottom
|
|
23
|
+
* - `transform`: translateY for entry slide-up animation
|
|
24
|
+
* - `transition`: separate curves for entry (transform), close (height collapse),
|
|
25
|
+
* and snap changes (height)
|
|
26
|
+
*
|
|
27
|
+
* During drag, the gesture hook sets `--sheet-drag-height` and `transition: none`
|
|
28
|
+
* directly on the element for immediate visual feedback, then restores on release.
|
|
29
|
+
*/
|
|
30
|
+
export declare const cvaSheetPanel: (props?: ({
|
|
31
|
+
anchor?: "center" | "start" | "end" | null | undefined;
|
|
32
|
+
variant?: "default" | "modal" | "floating" | null | undefined;
|
|
33
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
34
|
+
/**
|
|
35
|
+
* Scrollable area below the drag handle. Nested inside the Sheet panel (Card)
|
|
36
|
+
* so the Card's base `overflow-clip` clips this element's scrollbar at the
|
|
37
|
+
* rounded corners. The handle stays fixed above, outside the scroll context.
|
|
38
|
+
*
|
|
39
|
+
* `fillHeight` controls whether the scroll area stretches to fill available
|
|
40
|
+
* panel space. Enabled for level/docked modes (fixed panel height); disabled
|
|
41
|
+
* for fit mode so that `scrollHeight` reflects natural content height rather
|
|
42
|
+
* than the stretched layout — critical for correct fit-height measurement.
|
|
43
|
+
*/
|
|
44
|
+
export declare const cvaSheetScrollArea: (props?: ({
|
|
45
|
+
fillHeight?: boolean | null | undefined;
|
|
46
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
47
|
+
/**
|
|
48
|
+
* Returns inline styles for the sheet panel element.
|
|
49
|
+
*
|
|
50
|
+
* Entry animation uses a transform slide-up (translateY 100% → 0).
|
|
51
|
+
* Close animation is variant-aware — see `getCloseStyle` for details.
|
|
52
|
+
* Snap-level changes animate height while the sheet is open.
|
|
53
|
+
*/
|
|
54
|
+
export declare const getSheetPanelStyle: ({ snapHeight, isOpen, closePhase, variant, autoHeight, maxHeight, isDragging, suppressTransition, }: {
|
|
55
|
+
readonly snapHeight: string;
|
|
56
|
+
readonly isOpen: boolean;
|
|
57
|
+
readonly closePhase: SheetAnimationState["closePhase"];
|
|
58
|
+
readonly variant: SheetVariant;
|
|
59
|
+
readonly autoHeight?: boolean;
|
|
60
|
+
readonly maxHeight?: string;
|
|
61
|
+
readonly isDragging?: boolean;
|
|
62
|
+
readonly suppressTransition?: boolean;
|
|
63
|
+
}) => CSSProperties;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { MouseEventHandler, ReactElement, PointerEvent as ReactPointerEvent } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Centralized visual intensity parameters for the sheet handle line.
|
|
4
|
+
*
|
|
5
|
+
* All values control the handle bar (line) color, which mixes between
|
|
6
|
+
* `base` and `emphasized` colors. The mixing factor is the maximum of:
|
|
7
|
+
* - `--sheet-dismiss-progress` (0-1, set on the panel by gesture hook or Sheet effect)
|
|
8
|
+
* - `--sheet-snap-proximity` * proximityWeight (approaching a snap point)
|
|
9
|
+
* - `--handle-interaction` (hover / dragging / pressed states below)
|
|
10
|
+
*
|
|
11
|
+
* Interaction values are set via CSS custom property on the handle div
|
|
12
|
+
* (Tailwind arbitrary properties + pseudo-classes). Keep these in sync
|
|
13
|
+
* with the CVA class strings in `cvaSheetHandle`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const HANDLE_VISUALS: {
|
|
16
|
+
readonly line: {
|
|
17
|
+
readonly base: "var(--color-neutral-300)";
|
|
18
|
+
readonly emphasized: "var(--color-neutral-600)";
|
|
19
|
+
readonly interaction: {
|
|
20
|
+
readonly idle: 0;
|
|
21
|
+
readonly hover: 0.15;
|
|
22
|
+
readonly dragging: 0.25;
|
|
23
|
+
readonly pressed: 0.5;
|
|
24
|
+
};
|
|
25
|
+
readonly proximityWeight: 0.5;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Props for the SheetHandle component.
|
|
30
|
+
*/
|
|
31
|
+
export type SheetHandleProps = {
|
|
32
|
+
/** Pointer event handler from useSheetGestures. */
|
|
33
|
+
readonly onPointerDown?: (e: ReactPointerEvent) => void;
|
|
34
|
+
/** Pointer event handler from useSheetGestures. */
|
|
35
|
+
readonly onPointerMove?: (e: ReactPointerEvent) => void;
|
|
36
|
+
/** Pointer event handler from useSheetGestures. */
|
|
37
|
+
readonly onPointerUp?: (e: ReactPointerEvent) => void;
|
|
38
|
+
/** Resets drag state when the browser cancels the pointer stream (e.g. tab switch, touch interruption). */
|
|
39
|
+
readonly onLostPointerCapture?: () => void;
|
|
40
|
+
/** Click handler for cycling snap levels and modifier-based actions. */
|
|
41
|
+
readonly onClick?: MouseEventHandler<HTMLDivElement>;
|
|
42
|
+
/** Whether the handle is currently being dragged. Controls visual state. */
|
|
43
|
+
readonly isDragging?: boolean;
|
|
44
|
+
/** Mouse enter handler (from useHover). */
|
|
45
|
+
readonly onMouseEnter?: () => void;
|
|
46
|
+
/** Mouse leave handler (from useHover). */
|
|
47
|
+
readonly onMouseLeave?: () => void;
|
|
48
|
+
/** Test ID for the handle element. */
|
|
49
|
+
readonly "data-testid"?: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Visual drag indicator at the top of the Sheet.
|
|
53
|
+
*
|
|
54
|
+
* Renders a single handle line that narrows into a short stub as the user
|
|
55
|
+
* drags toward the dismiss threshold, with color fading from neutral to dark.
|
|
56
|
+
* The dismiss visual is driven entirely by the `--sheet-dismiss-progress`
|
|
57
|
+
* CSS custom property set on the parent panel element — this component
|
|
58
|
+
* has no dismiss-related state or logic.
|
|
59
|
+
*
|
|
60
|
+
* Accepts pointer event handlers from useSheetGestures and forwards them
|
|
61
|
+
* to the handle element. The element has `touch-action: none` so pointer
|
|
62
|
+
* events work reliably on touch devices.
|
|
63
|
+
*/
|
|
64
|
+
export declare const SheetHandle: ({ onPointerDown, onPointerMove, onPointerUp, onLostPointerCapture, onClick, isDragging, onMouseEnter, onMouseLeave, "data-testid": dataTestId, }: SheetHandleProps) => ReactElement;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Props for the SheetOverlay component.
|
|
4
|
+
*/
|
|
5
|
+
export type SheetOverlayProps = {
|
|
6
|
+
/** Whether the overlay is visible. */
|
|
7
|
+
readonly visible: boolean;
|
|
8
|
+
/** Test ID for the overlay element. */
|
|
9
|
+
readonly "data-testid"?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Semi-transparent backdrop overlay for the Sheet.
|
|
13
|
+
*
|
|
14
|
+
* Fades in/out using CSS transitions. When not visible, the overlay is
|
|
15
|
+
* transparent with pointer-events disabled so the background remains
|
|
16
|
+
* interactable. Dismiss is handled by Floating UI's `useDismiss`
|
|
17
|
+
* outside-press detection — clicks on the visible overlay bubble to the
|
|
18
|
+
* document where `useDismiss` triggers the close flow.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SheetOverlay: ({ visible, "data-testid": dataTestId }: SheetOverlayProps) => ReactElement;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation lifecycle reducer for the Sheet component.
|
|
3
|
+
* Manages mount/unmount transitions and close animation phases.
|
|
4
|
+
*
|
|
5
|
+
* Since snap state no longer resets on close, Sheet reads level/sizingMode
|
|
6
|
+
* directly from SheetState during close animations — no need to freeze
|
|
7
|
+
* display values here.
|
|
8
|
+
*/
|
|
9
|
+
export type SheetAnimationState = {
|
|
10
|
+
readonly shouldRender: boolean;
|
|
11
|
+
readonly visuallyOpen: boolean;
|
|
12
|
+
readonly closePhase: "idle" | "collapsing" | "vanishing";
|
|
13
|
+
readonly prevIsOpen: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare const INITIAL_ANIMATION_STATE: SheetAnimationState;
|
|
16
|
+
export type SheetAnimationAction = {
|
|
17
|
+
readonly type: "SYNC_OPEN";
|
|
18
|
+
readonly isOpen: boolean;
|
|
19
|
+
readonly skipEntryAnimation: boolean;
|
|
20
|
+
} | {
|
|
21
|
+
readonly type: "SET_VISUALLY_OPEN";
|
|
22
|
+
} | {
|
|
23
|
+
readonly type: "UNMOUNT";
|
|
24
|
+
} | {
|
|
25
|
+
readonly type: "ENSURE_RENDER";
|
|
26
|
+
} | {
|
|
27
|
+
readonly type: "START_VANISH";
|
|
28
|
+
};
|
|
29
|
+
/** Reducer managing the Sheet component's mount/unmount animation lifecycle. */
|
|
30
|
+
export declare const sheetAnimationReducer: (state: SheetAnimationState, action: SheetAnimationAction) => SheetAnimationState;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recorded pointer position for velocity calculation.
|
|
3
|
+
*/
|
|
4
|
+
export type PointerSample = {
|
|
5
|
+
readonly y: number;
|
|
6
|
+
readonly timestamp: number;
|
|
7
|
+
};
|
|
8
|
+
/** Minimum velocity (px/ms) to trigger directional snap selection. */
|
|
9
|
+
export declare const VELOCITY_THRESHOLD = 0.5;
|
|
10
|
+
/** Dampening factor for rubber-band effect past snap bounds. */
|
|
11
|
+
export declare const RUBBER_BAND_FACTOR = 0.3;
|
|
12
|
+
/**
|
|
13
|
+
* Calculates vertical velocity from pointer position history.
|
|
14
|
+
* Positive = downward, negative = upward.
|
|
15
|
+
*/
|
|
16
|
+
export declare const calculateVelocity: (samples: ReadonlyArray<PointerSample>) => number;
|
|
17
|
+
/**
|
|
18
|
+
* Applies rubber-band dampening when the effective height exceeds snap bounds.
|
|
19
|
+
*
|
|
20
|
+
* Within [minSnap, maxSnap] the raw drag delta passes through unchanged.
|
|
21
|
+
* Beyond maxSnap the excess movement is dampened by RUBBER_BAND_FACTOR.
|
|
22
|
+
*
|
|
23
|
+
* Below minSnap behaviour depends on `closable`:
|
|
24
|
+
* - **closable = true** — the sheet follows the finger freely (clamped at 0)
|
|
25
|
+
* so the user can always reach the close threshold.
|
|
26
|
+
* - **closable = false** — rubber-band dampening is applied (same feel as
|
|
27
|
+
* the upward over-stretch) because the sheet cannot be dismissed.
|
|
28
|
+
*
|
|
29
|
+
* Returns the constrained effective height in pixels.
|
|
30
|
+
*/
|
|
31
|
+
export declare const computeConstrainedEffectiveHeight: (rawDelta: number, activeSnapHeight: number, minSnap: number, maxSnap: number, closable: boolean) => number;
|
|
32
|
+
/**
|
|
33
|
+
* Computes how far the sheet has progressed toward dismissal (0 = at min snap, 1 = at close threshold).
|
|
34
|
+
*
|
|
35
|
+
* A dead zone below the min snap absorbs small overdrags before the dismiss
|
|
36
|
+
* indicator starts. Returns 0 when close is not available.
|
|
37
|
+
*/
|
|
38
|
+
export declare const computeDismissProgress: (rawEffectiveHeight: number, minSnap: number, containerHeight: number, closeThresholdFraction: number, dismissDeadZonePx: number) => number;
|
|
39
|
+
/**
|
|
40
|
+
* Computes a 0-1 proximity value indicating how close the drag position is
|
|
41
|
+
* to the nearest snap point in the current drag direction.
|
|
42
|
+
*
|
|
43
|
+
* 1 = directly at a snap point, 0 = further than `proximityRange` away.
|
|
44
|
+
*/
|
|
45
|
+
export declare const computeSnapProximity: (constrainedHeight: number, snapHeights: ReadonlyArray<number>, direction: -1 | 0 | 1, proximityRange: number) => number;
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the target snap height based on effective position and velocity.
|
|
48
|
+
*
|
|
49
|
+
* - High upward velocity -> next larger snap above the current effective height
|
|
50
|
+
* - High downward velocity -> next smaller snap below the current effective height
|
|
51
|
+
* - Low velocity -> snap to the nearest point
|
|
52
|
+
*
|
|
53
|
+
* snapHeights must be sorted ascending.
|
|
54
|
+
*/
|
|
55
|
+
export declare const resolveSnapTarget: (effectiveHeight: number, snapHeights: ReadonlyArray<number>, velocity: number) => number;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SheetSizingMode } from "./sheet-types";
|
|
2
|
+
/**
|
|
3
|
+
* Computes the active snap height in pixels for the current display state.
|
|
4
|
+
*
|
|
5
|
+
* Docked → measured docked height.
|
|
6
|
+
* Fit mode with a measured height → use that height.
|
|
7
|
+
* Otherwise → look up the level height from the precomputed array.
|
|
8
|
+
*/
|
|
9
|
+
export declare const computeActiveSnapHeight: (sizingMode: SheetSizingMode, fitHeight: number, levelHeights: ReadonlyArray<number>, displayLevel: number, dockedHeightPx?: number) => number;
|
|
10
|
+
/**
|
|
11
|
+
* Builds the set of pixel heights the gesture system snaps to.
|
|
12
|
+
*
|
|
13
|
+
* - Snapping disabled → empty array.
|
|
14
|
+
* - Fit mode with measured height → `[dockedHeight?, fitHeight]`.
|
|
15
|
+
* - Fit mode before measurement → falls back to level-based snaps.
|
|
16
|
+
* - Normal mode → level heights with optional docked height prepended.
|
|
17
|
+
*
|
|
18
|
+
* The returned array is always sorted ascending.
|
|
19
|
+
*/
|
|
20
|
+
export declare const computeGestureSnapHeights: (effectiveSnapping: boolean, sizingMode: SheetSizingMode, fitHeight: number, levelHeights: ReadonlyArray<number>, dockingEnabled: boolean, dockedHeightPx?: number) => ReadonlyArray<number>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Width constraint for anchored sheets (start/end positioning).
|
|
3
|
+
* Uses container-relative units with a pixel cap.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ANCHORED_SHEET_MAX_WIDTH: "min(90cqw, 420px)";
|
|
6
|
+
/**
|
|
7
|
+
* Margin from container edges for anchored sheets.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ANCHORED_SHEET_MARGIN: "12px";
|
|
10
|
+
/**
|
|
11
|
+
* Transition timing for snap animations — gentle deceleration with minimal overshoot.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SHEET_TRANSITION_DURATION_MS: 300;
|
|
14
|
+
export declare const SHEET_TRANSITION_DURATION: "300ms";
|
|
15
|
+
export declare const SHEET_TRANSITION_EASING: "cubic-bezier(0.2, 0.0, 0.0, 1.0)";
|
|
16
|
+
/**
|
|
17
|
+
* Threshold (as a fraction of container height) below the smallest snap
|
|
18
|
+
* at which the sheet will close on release.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CLOSE_THRESHOLD_FRACTION: 0.15;
|
|
21
|
+
/**
|
|
22
|
+
* Dead zone in pixels below the smallest snap before the dismiss
|
|
23
|
+
* visual indicator starts showing. Prevents the cross from appearing
|
|
24
|
+
* too early when the user barely drags past the lowest snap.
|
|
25
|
+
*/
|
|
26
|
+
export declare const DISMISS_DEAD_ZONE_PX = 20;
|
|
27
|
+
/**
|
|
28
|
+
* Pixel range within which the snap-proximity visual feedback activates.
|
|
29
|
+
* The handle widens and darkens as the sheet approaches a snap point.
|
|
30
|
+
*/
|
|
31
|
+
export declare const SNAP_PROXIMITY_RANGE_PX = 50;
|
|
32
|
+
/** Fallback pixel height for the docked state. */
|
|
33
|
+
export declare const DOCKED_HEIGHT_PX: 64;
|
|
34
|
+
/**
|
|
35
|
+
* Top margin subtracted from the largest snap level so the sheet never
|
|
36
|
+
* fully covers the container (leaves a small gap at the top).
|
|
37
|
+
*/
|
|
38
|
+
export declare const FULL_HEIGHT_TOP_MARGIN_PX = 40;
|
|
39
|
+
/**
|
|
40
|
+
* Computes an array of snap level heights in pixels, evenly distributed
|
|
41
|
+
* across the available space. When docking is enabled the distribution
|
|
42
|
+
* starts from the docked height rather than 0, so the first level is
|
|
43
|
+
* always at least `MIN_SNAP_SPACING_PX` above the dock.
|
|
44
|
+
*
|
|
45
|
+
* The resulting heights are sorted ascending (smallest first).
|
|
46
|
+
*
|
|
47
|
+
* @param containerHeight - Container height in pixels.
|
|
48
|
+
* @param dockingEnabled - Whether docked mode is available.
|
|
49
|
+
* @param dockedHeightPx - Pixel height of the docked state (measured from content).
|
|
50
|
+
* @returns {ReadonlyArray<number>} Array of pixel heights for each snap level.
|
|
51
|
+
*/
|
|
52
|
+
export declare const computeSnapLevelHeights: (containerHeight: number, dockingEnabled: boolean, dockedHeightPx?: number) => ReadonlyArray<number>;
|
|
53
|
+
/**
|
|
54
|
+
* Converts a snap level index to a CSS height string using container-relative units.
|
|
55
|
+
*
|
|
56
|
+
* When docking is enabled the height range starts from the docked height
|
|
57
|
+
* so the CSS value reflects the shifted distribution.
|
|
58
|
+
*
|
|
59
|
+
* @param levelIndex - Zero-based snap level index.
|
|
60
|
+
* @param totalLevels - Total number of snap levels.
|
|
61
|
+
* @param dockingEnabled - Whether docked mode is available.
|
|
62
|
+
* @param dockedHeightPx - Pixel height of the docked state (measured from content).
|
|
63
|
+
*/
|
|
64
|
+
export declare const getSnapLevelCssHeight: (levelIndex: number, totalLevels: number, dockingEnabled: boolean, dockedHeightPx?: number) => string;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { UseFloatingReturn } from "@floating-ui/react";
|
|
2
|
+
import type { ReactNode, Ref, RefObject } from "react";
|
|
3
|
+
import type { UseOverlayDismissibleProps } from "../../overlay-dismissible/types";
|
|
4
|
+
/**
|
|
5
|
+
* Horizontal positioning of the bottom sheet.
|
|
6
|
+
* - center: Full container width (default bottom sheet)
|
|
7
|
+
* - start: Anchored to inline-start (left in LTR), constrained width
|
|
8
|
+
* - end: Anchored to inline-end (right in LTR), constrained width
|
|
9
|
+
*/
|
|
10
|
+
export type SheetAnchor = "center" | "start" | "end";
|
|
11
|
+
/**
|
|
12
|
+
* Initial size when the sheet opens.
|
|
13
|
+
* - fit: Open at content height (fit-content). Supports dismiss via gesture and docking.
|
|
14
|
+
* - collapsed: Open at the smallest non-docked snap level.
|
|
15
|
+
* - expanded: Open at the largest snap level.
|
|
16
|
+
* - docked: Open in docked mode (requires dockedContent on Sheet).
|
|
17
|
+
*/
|
|
18
|
+
export type SheetDefaultSize = "fit" | "collapsed" | "expanded" | "docked";
|
|
19
|
+
/**
|
|
20
|
+
* Variant controlling the sheet's appearance, backdrop, and modal behavior.
|
|
21
|
+
* - default: Standard panel, no backdrop, flush to container bottom, top-only rounded corners.
|
|
22
|
+
* - modal: Dimming backdrop that blocks background interaction, focus-trapped dialog
|
|
23
|
+
* with `role="dialog"` and `aria-modal`. Flush bottom, top-only rounded corners.
|
|
24
|
+
* - floating: No backdrop, rounded corners on all sides, offset from the container bottom (iPad slideover style).
|
|
25
|
+
*/
|
|
26
|
+
export type SheetVariant = "default" | "modal" | "floating";
|
|
27
|
+
/**
|
|
28
|
+
* Entry animation behavior when the sheet opens.
|
|
29
|
+
* - always: Animate every open.
|
|
30
|
+
* - subsequent: Skip the animation on the very first open, animate thereafter.
|
|
31
|
+
* - never: No entry animation; the sheet appears instantly.
|
|
32
|
+
*/
|
|
33
|
+
export type SheetEntryAnimation = "always" | "subsequent" | "never";
|
|
34
|
+
/**
|
|
35
|
+
* Discriminant for how the sheet determines its height.
|
|
36
|
+
* Prevents impossible states (e.g. fit + docked simultaneously).
|
|
37
|
+
*/
|
|
38
|
+
export type SheetSizingMode = "fit" | "level" | "docked";
|
|
39
|
+
/**
|
|
40
|
+
* Unified read-only state of the Sheet.
|
|
41
|
+
* Does NOT reset on close — reflects the last known position.
|
|
42
|
+
* Replaces both the old `SheetSizeInfo` and `SheetSnapDisplay`.
|
|
43
|
+
*/
|
|
44
|
+
export type SheetState = {
|
|
45
|
+
readonly sizingMode: SheetSizingMode;
|
|
46
|
+
/** Current snap level index (0 = collapsed). Excludes docked. */
|
|
47
|
+
readonly level: number;
|
|
48
|
+
/** Total number of snap levels available (excludes docked). */
|
|
49
|
+
readonly totalLevels: number;
|
|
50
|
+
/** Only meaningful when `sizingMode === "level"`. */
|
|
51
|
+
readonly isExpanded: boolean;
|
|
52
|
+
/** Only meaningful when `sizingMode === "level"`. */
|
|
53
|
+
readonly isCollapsed: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Measured height of the docked content in pixels. Always reflects the
|
|
56
|
+
* docked size — does not change when the sheet expands. Falls back to the
|
|
57
|
+
* default docked height (64px) before the first measurement.
|
|
58
|
+
* Zero when the sheet has no dockedContent.
|
|
59
|
+
*/
|
|
60
|
+
readonly dockedHeight: number;
|
|
61
|
+
readonly levelHeights: ReadonlyArray<number>;
|
|
62
|
+
readonly effectiveDockedHeight: number;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Measurement data reported by Sheet to the snap system.
|
|
66
|
+
* Sent via `onGeometryChange` whenever container or content geometry changes.
|
|
67
|
+
*/
|
|
68
|
+
export type SheetGeometry = {
|
|
69
|
+
readonly containerHeight: number;
|
|
70
|
+
readonly fitHeight: number;
|
|
71
|
+
readonly dockedHeight: number;
|
|
72
|
+
};
|
|
73
|
+
/** Navigation commands returned by `useSheetSnap` and accepted by Sheet. */
|
|
74
|
+
export type SheetSnapActions = {
|
|
75
|
+
readonly snapUp: () => void;
|
|
76
|
+
readonly snapDown: () => void;
|
|
77
|
+
readonly expand: () => void;
|
|
78
|
+
readonly collapse: () => void;
|
|
79
|
+
readonly dock: () => void;
|
|
80
|
+
readonly fit: () => void;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Floating UI props returned by `useSheet` for Sheet's focus management
|
|
84
|
+
* and dismiss interaction binding. Mirrors `useModal`'s `floatingUi` shape.
|
|
85
|
+
*/
|
|
86
|
+
export type SheetFloatingUiProps = {
|
|
87
|
+
readonly refs: UseFloatingReturn["refs"];
|
|
88
|
+
readonly context: UseFloatingReturn["context"];
|
|
89
|
+
readonly getFloatingProps: (userProps?: Record<string, unknown>) => Record<string, unknown>;
|
|
90
|
+
};
|
|
91
|
+
/** Props for the useSheet hook. */
|
|
92
|
+
export type UseSheetProps = UseOverlayDismissibleProps & {
|
|
93
|
+
/**
|
|
94
|
+
* Variant controlling appearance and dismiss behavior.
|
|
95
|
+
* Outside-press dismiss is only active when `"modal"`.
|
|
96
|
+
* Flows through to the Sheet component via spread.
|
|
97
|
+
*
|
|
98
|
+
* @default "default"
|
|
99
|
+
*/
|
|
100
|
+
readonly variant?: SheetVariant;
|
|
101
|
+
/** Where the sheet opens. Default: 'fit' (opens at content height). */
|
|
102
|
+
readonly defaultSize?: SheetDefaultSize;
|
|
103
|
+
/** Called when the sheet's state changes (snap level, sizing mode, etc.). */
|
|
104
|
+
readonly onStateChange?: (state: SheetState) => void;
|
|
105
|
+
/** Called when container/fit/docked geometry changes. */
|
|
106
|
+
readonly onGeometryChange?: (geometry: SheetGeometry) => void;
|
|
107
|
+
};
|
|
108
|
+
/** Return value of the useSheet hook. */
|
|
109
|
+
export type UseSheetReturnValue = {
|
|
110
|
+
readonly isOpen: boolean;
|
|
111
|
+
readonly ref: RefObject<HTMLDivElement | null>;
|
|
112
|
+
/** Unified sheet state. Does NOT reset on close. */
|
|
113
|
+
readonly state: SheetState;
|
|
114
|
+
/** Resolved variant, passed through to Sheet via spread. */
|
|
115
|
+
readonly variant: SheetVariant;
|
|
116
|
+
readonly open: () => void;
|
|
117
|
+
readonly close: () => void;
|
|
118
|
+
/** Snap navigation commands. */
|
|
119
|
+
readonly snap: SheetSnapActions;
|
|
120
|
+
/**
|
|
121
|
+
* Floating UI props for Sheet's focus management and dismiss binding.
|
|
122
|
+
* Sheet uses `context` for FloatingFocusManager and merges
|
|
123
|
+
* `refs.setFloating` onto the panel. ESC dismiss is always active;
|
|
124
|
+
* outside-press is only active when `variant="modal"`.
|
|
125
|
+
*/
|
|
126
|
+
readonly floatingUi: SheetFloatingUiProps;
|
|
127
|
+
readonly onGeometryChange: (geometry: SheetGeometry, dockingEnabled: boolean) => void;
|
|
128
|
+
readonly onSnap: (heightPx: number, useLevelSnap: boolean) => void;
|
|
129
|
+
readonly onClickHandle: () => void;
|
|
130
|
+
/** Gesture close handler for Sheet. Undefined when dismiss.gesture is false. */
|
|
131
|
+
readonly onCloseGesture: (() => void) | undefined;
|
|
132
|
+
};
|
|
133
|
+
/** Return value of the useSheetSnap hook. */
|
|
134
|
+
export type UseSheetSnapReturn = {
|
|
135
|
+
readonly state: SheetState;
|
|
136
|
+
readonly snap: SheetSnapActions;
|
|
137
|
+
readonly onGeometryChange: (geometry: SheetGeometry, dockingEnabled: boolean) => void;
|
|
138
|
+
readonly onSnap: (heightPx: number, useLevelSnap: boolean) => void;
|
|
139
|
+
readonly onClickHandle: () => void;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Props for the Sheet component.
|
|
143
|
+
*
|
|
144
|
+
* Decoupled from UseSheetReturnValue — Sheet only declares the props it
|
|
145
|
+
* actually reads. Consumers spread the hook return (`{...sheet}`) and
|
|
146
|
+
* TypeScript allows extra properties on spread expressions.
|
|
147
|
+
*/
|
|
148
|
+
export type SheetProps = {
|
|
149
|
+
readonly isOpen: boolean;
|
|
150
|
+
/** Unified sheet state from useSheetSnap. */
|
|
151
|
+
readonly state: SheetState;
|
|
152
|
+
/** Snap navigation commands from useSheetSnap. */
|
|
153
|
+
readonly snap: SheetSnapActions;
|
|
154
|
+
readonly onGeometryChange: (geometry: SheetGeometry, dockingEnabled: boolean) => void;
|
|
155
|
+
readonly onSnap: (heightPx: number, useLevelSnap: boolean) => void;
|
|
156
|
+
readonly onClickHandle: () => void;
|
|
157
|
+
/** Gesture close handler. Undefined when gesture dismiss is disabled. */
|
|
158
|
+
readonly onCloseGesture: (() => void) | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* Floating UI props for focus management and dismiss interaction binding.
|
|
161
|
+
* Provided by `useSheet`. When omitted (e.g. Modal in sheet mode),
|
|
162
|
+
* Sheet skips its own focus trap and dismiss — the parent owns those.
|
|
163
|
+
*/
|
|
164
|
+
readonly floatingUi?: SheetFloatingUiProps;
|
|
165
|
+
readonly ref?: Ref<HTMLDivElement>;
|
|
166
|
+
/** Horizontal positioning of the sheet. */
|
|
167
|
+
readonly anchor?: SheetAnchor;
|
|
168
|
+
/** Enable/disable drag-to-snap between levels. Default: true. */
|
|
169
|
+
readonly snapping?: boolean;
|
|
170
|
+
/** Show the drag handle and enable all resize interactions (drag, click-to-cycle). Default: true. When false, the handle is hidden and snapping is implicitly disabled. */
|
|
171
|
+
readonly resizable?: boolean;
|
|
172
|
+
/** Variant controlling appearance, backdrop, and modal behavior. When "modal", the sheet traps focus and sets dialog ARIA attributes (unless `trapFocus` is false). Default: "default". */
|
|
173
|
+
readonly variant?: SheetVariant;
|
|
174
|
+
/**
|
|
175
|
+
* Whether the sheet should manage its own focus trap when `variant="modal"`.
|
|
176
|
+
* When true (default for modal variant), the sheet activates a
|
|
177
|
+
* FloatingFocusManager to trap keyboard focus inside the panel.
|
|
178
|
+
* Set to false when a parent component (e.g. Modal) provides its own
|
|
179
|
+
* focus management.
|
|
180
|
+
*
|
|
181
|
+
* Ignored when variant is not "modal".
|
|
182
|
+
*
|
|
183
|
+
* @default true
|
|
184
|
+
*/
|
|
185
|
+
readonly trapFocus?: boolean;
|
|
186
|
+
/** The container element the sheet renders into via Portal. Required. Use a callback ref (useState setter) so mounting triggers a re-render. */
|
|
187
|
+
readonly container: HTMLElement | null;
|
|
188
|
+
/** Content rendered when in docked mode. Presence enables docking behavior. */
|
|
189
|
+
readonly dockedContent?: ReactNode;
|
|
190
|
+
/** Custom class name. */
|
|
191
|
+
readonly className?: string;
|
|
192
|
+
/** Test ID for the sheet. */
|
|
193
|
+
readonly "data-testid"?: string;
|
|
194
|
+
/** Sheet content. */
|
|
195
|
+
readonly children?: ReactNode;
|
|
196
|
+
/** Called after the close CSS transition completes (transitionend on transform). */
|
|
197
|
+
readonly onCloseComplete?: () => void;
|
|
198
|
+
/**
|
|
199
|
+
* Controls how the sheet animates when opening.
|
|
200
|
+
*
|
|
201
|
+
* - `"always"` — slide-up animation every time the sheet opens.
|
|
202
|
+
* - `"subsequent"` — skip the animation on the very first open (e.g. page
|
|
203
|
+
* load / refresh) but animate on every subsequent open.
|
|
204
|
+
* - `"never"` — no entry animation; the sheet appears instantly.
|
|
205
|
+
*
|
|
206
|
+
* Default: `"subsequent"`.
|
|
207
|
+
*/
|
|
208
|
+
readonly entryAnimation?: SheetEntryAnimation;
|
|
209
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SheetDefaultSize, SheetSizingMode } from "./sheet-types";
|
|
2
|
+
export type SnapState = {
|
|
3
|
+
readonly currentLevel: number;
|
|
4
|
+
readonly sizingMode: SheetSizingMode;
|
|
5
|
+
readonly initialSizeApplied: boolean;
|
|
6
|
+
readonly prevIsOpen: boolean;
|
|
7
|
+
readonly totalLevels: number;
|
|
8
|
+
readonly dockingEnabled: boolean;
|
|
9
|
+
readonly levelHeights: ReadonlyArray<number>;
|
|
10
|
+
readonly effectiveDockedHeight: number;
|
|
11
|
+
readonly fitHeight: number;
|
|
12
|
+
};
|
|
13
|
+
export declare const INITIAL_SNAP_STATE: SnapState;
|
|
14
|
+
export type SnapAction = {
|
|
15
|
+
readonly type: "SYNC_OPEN";
|
|
16
|
+
readonly isOpen: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
readonly type: "SYNC_MEASUREMENTS";
|
|
19
|
+
readonly measurements: {
|
|
20
|
+
readonly fitHeight: number;
|
|
21
|
+
readonly dockedHeight: number;
|
|
22
|
+
readonly dockingEnabled: boolean;
|
|
23
|
+
};
|
|
24
|
+
readonly levelHeights: ReadonlyArray<number>;
|
|
25
|
+
} | {
|
|
26
|
+
readonly type: "APPLY_INITIAL_SIZE";
|
|
27
|
+
readonly defaultSize: SheetDefaultSize;
|
|
28
|
+
} | {
|
|
29
|
+
readonly type: "SNAP_UP";
|
|
30
|
+
} | {
|
|
31
|
+
readonly type: "SNAP_DOWN";
|
|
32
|
+
} | {
|
|
33
|
+
readonly type: "EXPAND";
|
|
34
|
+
} | {
|
|
35
|
+
readonly type: "COLLAPSE";
|
|
36
|
+
} | {
|
|
37
|
+
readonly type: "DOCK";
|
|
38
|
+
} | {
|
|
39
|
+
readonly type: "FIT";
|
|
40
|
+
} | {
|
|
41
|
+
readonly type: "GESTURE_SNAP";
|
|
42
|
+
readonly heightPx: number;
|
|
43
|
+
readonly useLevelSnap: boolean;
|
|
44
|
+
} | {
|
|
45
|
+
readonly type: "CYCLE_SNAP";
|
|
46
|
+
};
|
|
47
|
+
/** Reducer managing snap-level positioning and measurement-derived state. */
|
|
48
|
+
export declare const snapReducer: (state: SnapState, action: SnapAction) => SnapState;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SheetSizingMode } from "./sheet-types";
|
|
2
|
+
/**
|
|
3
|
+
* Measures the panel's natural content height when docked and persists it for
|
|
4
|
+
* the next dock transition.
|
|
5
|
+
*
|
|
6
|
+
* The stored "last measured" height is used as the animation target when
|
|
7
|
+
* transitioning from expanded to docked, so the sheet animates smoothly to
|
|
8
|
+
* the correct height. If no value has been stored yet (first dock), the
|
|
9
|
+
* consumer falls back to DOCKED_HEIGHT_PX — no animation is acceptable.
|
|
10
|
+
*
|
|
11
|
+
* When transitioning from expanded to docked, the panel retains its previous
|
|
12
|
+
* height during layout, so scrollHeight returns the expanded value. We measure
|
|
13
|
+
* via a hidden clone (height: auto) so we never mutate the panel — the height
|
|
14
|
+
* transition runs without disruption.
|
|
15
|
+
*
|
|
16
|
+
* The value is intentionally NOT reset when leaving docked mode, so re-entry
|
|
17
|
+
* always has the correct target for the animation.
|
|
18
|
+
*
|
|
19
|
+
* Does NOT use a ResizeObserver because the gesture system manipulates the
|
|
20
|
+
* element's CSS height during drag, which inflates `scrollHeight` beyond the
|
|
21
|
+
* true content size and would create a feedback loop.
|
|
22
|
+
*
|
|
23
|
+
* Returns 0 before the first measurement. The consumer falls back to
|
|
24
|
+
* DOCKED_HEIGHT_PX until a positive value is available.
|
|
25
|
+
*/
|
|
26
|
+
export declare const useDockedContentHeight: (element: HTMLDivElement | null, shouldRender: boolean, sizingMode: SheetSizingMode) => number;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
export type UseProximityAnimationReturn = {
|
|
3
|
+
/** Cancel any running proximity RAF loop and clear the CSS property. */
|
|
4
|
+
readonly cancelProximityAnimation: () => void;
|
|
5
|
+
/**
|
|
6
|
+
* Start a RAF loop that drives `--sheet-snap-proximity` as the sheet
|
|
7
|
+
* settles into a snap, then fades it back to 0.
|
|
8
|
+
*/
|
|
9
|
+
readonly startProximityAnimation: (sheet: HTMLElement, targetHeight: number) => void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Manages the `--sheet-snap-proximity` CSS custom property animation
|
|
13
|
+
* during post-release snap transitions.
|
|
14
|
+
*
|
|
15
|
+
* Tracks the sheet height via `getBoundingClientRect` each frame,
|
|
16
|
+
* computes proximity to the target snap, and fades the value out once
|
|
17
|
+
* the sheet has arrived.
|
|
18
|
+
*/
|
|
19
|
+
export declare const useProximityAnimation: (sheetRef: RefObject<HTMLElement | null>) => UseProximityAnimationReturn;
|