@simoneggert/react-modal-sheet 5.4.3

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/src/styles.ts ADDED
@@ -0,0 +1,110 @@
1
+ import type { CSSProperties } from 'react';
2
+
3
+ export const styles = {
4
+ root: {
5
+ base: {
6
+ position: 'fixed',
7
+ top: 0,
8
+ bottom: 0,
9
+ left: 0,
10
+ right: 0,
11
+ overflow: 'hidden',
12
+ pointerEvents: 'none',
13
+ },
14
+ decorative: {},
15
+ },
16
+ backdrop: {
17
+ base: {
18
+ zIndex: 1,
19
+ position: 'fixed',
20
+ top: 0,
21
+ left: 0,
22
+ width: '100%',
23
+ height: '100%',
24
+ touchAction: 'none',
25
+ userSelect: 'none',
26
+ },
27
+ decorative: {
28
+ backgroundColor: 'rgba(0, 0, 0, 0.2)',
29
+ border: 'none',
30
+ WebkitTapHighlightColor: 'transparent',
31
+ },
32
+ },
33
+ container: {
34
+ base: {
35
+ zIndex: 2,
36
+ position: 'absolute',
37
+ left: 0,
38
+ bottom: 0,
39
+ width: '100%',
40
+ pointerEvents: 'auto',
41
+ display: 'flex',
42
+ flexDirection: 'column',
43
+ },
44
+ decorative: {
45
+ backgroundColor: '#fff',
46
+ borderTopRightRadius: '8px',
47
+ borderTopLeftRadius: '8px',
48
+ boxShadow: '0px -2px 16px rgba(0, 0, 0, 0.3)',
49
+ },
50
+ },
51
+ headerWrapper: {
52
+ base: {
53
+ width: '100%',
54
+ },
55
+ decorative: {},
56
+ },
57
+ header: {
58
+ base: {
59
+ width: '100%',
60
+ position: 'relative',
61
+ },
62
+ decorative: {
63
+ height: '40px',
64
+ display: 'flex',
65
+ alignItems: 'center',
66
+ justifyContent: 'center',
67
+ },
68
+ },
69
+ indicatorWrapper: {
70
+ base: {
71
+ display: 'flex',
72
+ },
73
+ decorative: {},
74
+ },
75
+ indicator: {
76
+ base: {
77
+ display: 'inline-block',
78
+ },
79
+ decorative: {
80
+ width: '18px',
81
+ height: '4px',
82
+ borderRadius: '99px',
83
+ backgroundColor: '#ddd',
84
+ },
85
+ },
86
+ content: {
87
+ base: {
88
+ minHeight: '0px',
89
+ position: 'relative',
90
+ flexGrow: 1,
91
+ display: 'flex',
92
+ flexDirection: 'column',
93
+ },
94
+ decorative: {},
95
+ },
96
+ scroller: {
97
+ base: {
98
+ height: '100%',
99
+ overflowY: 'auto',
100
+ overscrollBehaviorY: 'none',
101
+ },
102
+ decorative: {},
103
+ },
104
+ } satisfies Record<
105
+ string,
106
+ {
107
+ base: CSSProperties;
108
+ decorative: CSSProperties;
109
+ }
110
+ >;
package/src/types.tsx ADDED
@@ -0,0 +1,154 @@
1
+ import {
2
+ type DragHandler,
3
+ type EasingDefinition,
4
+ type MotionProps,
5
+ type MotionValue,
6
+ type motion,
7
+ } from 'motion/react';
8
+ import {
9
+ type ComponentPropsWithoutRef,
10
+ type ForwardRefExoticComponent,
11
+ type FunctionComponent,
12
+ type HTMLAttributes,
13
+ type ReactNode,
14
+ type RefAttributes,
15
+ type RefObject,
16
+ } from 'react';
17
+
18
+ export type SheetDetent = 'default' | 'full' | 'content';
19
+
20
+ type CommonProps = {
21
+ className?: string;
22
+ unstyled?: boolean;
23
+ };
24
+
25
+ type MotionDivProps = ComponentPropsWithoutRef<typeof motion.div>;
26
+
27
+ type MotionCommonProps = Omit<MotionDivProps, 'initial' | 'animate' | 'exit'>;
28
+
29
+ export interface SheetTweenConfig {
30
+ ease: EasingDefinition;
31
+ duration: number;
32
+ }
33
+
34
+ export type SheetProps = {
35
+ unstyled?: boolean;
36
+ avoidKeyboard?: boolean;
37
+ children: ReactNode;
38
+ detent?: SheetDetent;
39
+ disableDismiss?: boolean;
40
+ disableDrag?: boolean;
41
+ disableScrollLocking?: boolean;
42
+ dragCloseThreshold?: number;
43
+ dragVelocityThreshold?: number;
44
+ initialSnap?: number; // index of snap points array
45
+ isOpen: boolean;
46
+ modalEffectRootId?: string;
47
+ modalEffectThreshold?: number;
48
+ mountPoint?: Element;
49
+ prefersReducedMotion?: boolean;
50
+ snapPoints?: number[];
51
+ tweenConfig?: SheetTweenConfig;
52
+ onClose: () => void;
53
+ onCloseEnd?: () => void;
54
+ onCloseStart?: () => void;
55
+ onOpenEnd?: () => void;
56
+ onOpenStart?: () => void;
57
+ onSnap?: (index: number) => void;
58
+ } & MotionCommonProps;
59
+
60
+ export type SheetContainerProps = MotionCommonProps &
61
+ CommonProps & {
62
+ children: ReactNode;
63
+ };
64
+
65
+ export type SheetHeaderProps = MotionCommonProps &
66
+ CommonProps & {
67
+ children?: ReactNode;
68
+ disableDrag?: boolean;
69
+ };
70
+
71
+ export type SheetContentProps = MotionCommonProps &
72
+ CommonProps & {
73
+ disableDrag?: boolean | ((args: SheetStateInfo) => boolean);
74
+ disableScroll?: boolean | ((args: SheetStateInfo) => boolean);
75
+ scrollRef?: RefObject<HTMLDivElement | null>;
76
+ scrollClassName?: string;
77
+ scrollStyle?: MotionProps['style'];
78
+ };
79
+
80
+ export type SheetBackdropProps = MotionDivProps & CommonProps;
81
+
82
+ export type SheetDragIndicatorProps = HTMLAttributes<HTMLDivElement> &
83
+ CommonProps;
84
+
85
+ export interface SheetDragProps {
86
+ drag: 'y';
87
+ dragElastic: number;
88
+ dragMomentum: boolean;
89
+ dragPropagation: boolean;
90
+ onDrag: DragHandler;
91
+ onDragStart: DragHandler;
92
+ onDragEnd: DragHandler;
93
+ }
94
+
95
+ export type SheetStateInfo = {
96
+ scrollPosition?: 'top' | 'bottom' | 'middle';
97
+ currentSnap?: number;
98
+ };
99
+
100
+ export type SheetSnapPoint = {
101
+ snapIndex: number;
102
+ snapValue: number; // Absolute value from the bottom of the sheet
103
+ snapValueY: number; // Y value is inverted as `y = 0` means sheet is at the top
104
+ };
105
+
106
+ export interface SheetContextType {
107
+ currentSnap?: number;
108
+ detent: SheetDetent;
109
+ disableDrag: boolean;
110
+ dragProps: SheetDragProps;
111
+ indicatorRotation: MotionValue<number>;
112
+ avoidKeyboard: boolean;
113
+ prefersReducedMotion: boolean;
114
+ sheetBoundsRef: (node: HTMLDivElement | null) => void;
115
+ sheetRef: RefObject<any>;
116
+ unstyled: boolean;
117
+ y: MotionValue<any>;
118
+ }
119
+
120
+ export interface SheetScrollerContextType {
121
+ disableDrag: boolean;
122
+ setDragDisabled: () => void;
123
+ setDragEnabled: () => void;
124
+ }
125
+
126
+ type SheetComponent = ForwardRefExoticComponent<
127
+ SheetProps & RefAttributes<any>
128
+ >;
129
+
130
+ type ContainerComponent = ForwardRefExoticComponent<
131
+ SheetContainerProps & RefAttributes<any>
132
+ >;
133
+
134
+ type HeaderComponent = ForwardRefExoticComponent<
135
+ SheetHeaderProps & RefAttributes<any>
136
+ >;
137
+
138
+ type BackdropComponent = ForwardRefExoticComponent<
139
+ SheetBackdropProps & RefAttributes<any>
140
+ >;
141
+
142
+ type ContentComponent = ForwardRefExoticComponent<
143
+ SheetContentProps & RefAttributes<any>
144
+ >;
145
+
146
+ interface SheetCompoundComponent {
147
+ Container: ContainerComponent;
148
+ Header: HeaderComponent;
149
+ DragIndicator: FunctionComponent<SheetDragIndicatorProps>;
150
+ Content: ContentComponent;
151
+ Backdrop: BackdropComponent;
152
+ }
153
+
154
+ export type SheetCompound = SheetComponent & SheetCompoundComponent;
package/src/utils.ts ADDED
@@ -0,0 +1,116 @@
1
+ import { type CSSProperties, type ForwardedRef, type RefCallback } from 'react';
2
+ import { IS_SSR } from './constants';
3
+
4
+ export function applyStyles(
5
+ styles: { base: CSSProperties; decorative: CSSProperties },
6
+ unstyled: boolean
7
+ ) {
8
+ return unstyled ? styles.base : { ...styles.base, ...styles.decorative };
9
+ }
10
+
11
+ export function isAscendingOrder(arr: number[]) {
12
+ for (let i = 0; i < arr.length; i++) {
13
+ if (arr[i + 1] < arr[i]) return false;
14
+ }
15
+ return true;
16
+ }
17
+
18
+ export function mergeRefs<T = any>(refs: ForwardedRef<T>[]): RefCallback<T> {
19
+ return (value: any) => {
20
+ refs.forEach((ref: any) => {
21
+ if (typeof ref === 'function') {
22
+ ref(value);
23
+ } else if (ref) {
24
+ ref.current = value;
25
+ }
26
+ });
27
+ };
28
+ }
29
+
30
+ export function isTouchDevice() {
31
+ if (IS_SSR) return false;
32
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
33
+ }
34
+
35
+ function testPlatform(re: RegExp) {
36
+ return typeof window !== 'undefined' && window.navigator != null
37
+ ? re.test(
38
+ // @ts-expect-error
39
+ window.navigator.userAgentData?.platform || window.navigator.platform
40
+ )
41
+ : false;
42
+ }
43
+
44
+ function cached(fn: () => boolean) {
45
+ let res: boolean | null = null;
46
+ return () => {
47
+ if (res == null) {
48
+ res = fn();
49
+ }
50
+ return res;
51
+ };
52
+ }
53
+
54
+ const isMac = cached(function () {
55
+ return testPlatform(/^Mac/i);
56
+ });
57
+
58
+ const isIPhone = cached(function () {
59
+ return testPlatform(/^iPhone/i);
60
+ });
61
+
62
+ const isIPad = cached(function () {
63
+ // iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
64
+ return testPlatform(/^iPad/i) || (isMac() && navigator.maxTouchPoints > 1);
65
+ });
66
+
67
+ export const isIOS = cached(function () {
68
+ return isIPhone() || isIPad();
69
+ });
70
+
71
+ /** Wait for an element to be rendered and visible */
72
+ export function waitForElement(
73
+ className: string,
74
+ interval = 50,
75
+ maxAttempts = 20
76
+ ) {
77
+ return new Promise<HTMLElement | null>((resolve) => {
78
+ let attempts = 0;
79
+ const timer = setInterval(() => {
80
+ const element = document.getElementsByClassName(
81
+ className
82
+ )[0] as HTMLElement;
83
+ attempts++;
84
+ if (element || attempts >= maxAttempts) {
85
+ clearInterval(timer);
86
+ resolve(element);
87
+ }
88
+ }, interval);
89
+ });
90
+ }
91
+
92
+ // HTML input types that do not cause the software keyboard to appear.
93
+ const nonTextInputTypes = new Set([
94
+ 'checkbox',
95
+ 'radio',
96
+ 'range',
97
+ 'color',
98
+ 'file',
99
+ 'image',
100
+ 'button',
101
+ 'submit',
102
+ 'reset',
103
+ ]);
104
+
105
+ export function willOpenKeyboard(target: Element) {
106
+ return (
107
+ (target instanceof HTMLInputElement &&
108
+ !nonTextInputTypes.has(target.type)) ||
109
+ target instanceof HTMLTextAreaElement ||
110
+ (target instanceof HTMLElement && target.isContentEditable)
111
+ );
112
+ }
113
+
114
+ export function isHTTPS() {
115
+ return typeof window !== 'undefined' && window.isSecureContext;
116
+ }