draft-components 2.5.3 → 2.6.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.
@@ -1067,72 +1067,55 @@
1067
1067
  --dc-dialog-safe-area: 16px;
1068
1068
  --dc-dialog-viewport-gap: 16px;
1069
1069
  --dc-dialog-transition-duration: 200ms;
1070
+ --dc-dialog-open-transition-duration: 250ms;
1071
+ --dc-dialog-close-transition-duration: 150ms;
1070
1072
  --dc-dialog-text-color: var(--dc-primary-text-color);
1071
1073
  --dc-dialog-border-color: transparent;
1072
1074
  --dc-dialog-bg: var(--dc-primary-bg);
1073
- --dc-dialog-inset-shadow-color: var(--dc-border-color-transparent-2);
1075
+ --dc-dialog-divider-color: var(--dc-border-color-transparent-2);
1074
1076
  --dc-dialog-backdrop-color: rgb(var(--dc-gray-900-rgb) / 0.4);
1075
1077
 
1076
1078
  color-scheme: light;
1077
- position: relative;
1078
- z-index: var(--dc-overlay-z-index);
1079
- }
1080
-
1081
- .dc-dialog-backdrop {
1082
1079
  position: fixed;
1080
+ z-index: var(--dc-overlay-z-index);
1083
1081
  top: 0;
1084
1082
  left: 0;
1083
+ display: flex;
1084
+ align-items: center;
1085
+ justify-content: center;
1085
1086
  box-sizing: border-box;
1086
1087
  width: 100%;
1087
1088
  height: 100%;
1088
1089
  padding: var(--dc-dialog-viewport-gap);
1089
- background: var(--dc-dialog-backdrop-color);
1090
1090
  }
1091
1091
 
1092
- .dc-dialog_closed .dc-dialog-backdrop {
1093
- opacity: 0;
1094
- transition: opacity var(--dc-dialog-transition-duration) linear;
1095
- }
1096
-
1097
- .dc-dialog_opened .dc-dialog-backdrop {
1098
- opacity: 1;
1092
+ .dc-dialog__backdrop {
1093
+ position: fixed;
1094
+ inset: 0;
1095
+ background: var(--dc-dialog-backdrop-color);
1099
1096
  }
1100
1097
 
1101
- .dc-dialog-modal {
1098
+ .dc-dialog__modal {
1099
+ font: var(--dc-text-sm);
1102
1100
  color: var(--dc-dialog-text-color);
1103
- position: fixed;
1104
- top: 50%;
1105
- left: 50%;
1101
+ position: relative;
1106
1102
  display: flex;
1107
1103
  flex-direction: column;
1108
1104
  box-sizing: border-box;
1109
- width: 100%;
1110
- max-width: calc(var(--dc-dialog-max-width) + 2 * var(--dc-dialog-safe-area));
1111
- max-height: calc(100% - 2 * var(--dc-dialog-viewport-gap));
1112
- transform: translate(-50%, -50%);
1105
+ width: 480px;
1106
+ max-width: 100%;
1107
+ max-height: 100%;
1113
1108
  border-radius: 12px;
1114
1109
  background: var(--dc-dialog-bg);
1115
1110
  box-shadow: var(--dc-shadow-xl);
1116
1111
  }
1117
1112
 
1118
- .dc-dialog-modal_sm {
1119
- --dc-dialog-max-width: 320px;
1120
- }
1121
-
1122
- .dc-dialog-modal_lg {
1123
- --dc-dialog-max-width: 640px;
1124
- }
1125
-
1126
- .dc-dialog_closed .dc-dialog-modal {
1127
- opacity: 0;
1128
- transform: translate(-50%, -50%) scale(0.95);
1129
- transition: var(--dc-dialog-transition-duration) ease;
1130
- transition-property: opacity, transform;
1113
+ .dc-dialog__modal_sm {
1114
+ width: 320px;
1131
1115
  }
1132
1116
 
1133
- .dc-dialog_opened .dc-dialog-modal {
1134
- opacity: 1;
1135
- transform: translate(-50%, -50%) scale(1);
1117
+ .dc-dialog__modal_lg {
1118
+ width: 640px;
1136
1119
  }
1137
1120
 
1138
1121
  .dc-dialog-header,
@@ -1143,33 +1126,31 @@
1143
1126
 
1144
1127
  .dc-dialog-header {
1145
1128
  font: var(--dc-text-sm);
1129
+ display: grid;
1130
+ grid-template-columns: 1fr auto;
1131
+ grid-gap: 4px;
1132
+ align-items: center;
1146
1133
  }
1147
1134
 
1148
- .dc-dialog-header_has_border {
1149
- border-bottom: 1px solid var(--dc-dialog-inset-shadow-color);
1150
- }
1151
-
1152
- .dc-dialog-header__title-bar {
1153
- position: relative;
1154
- padding-right: 24px;
1135
+ .dc-dialog-header_has_divider {
1136
+ border-bottom: 1px solid var(--dc-dialog-divider-color);
1155
1137
  }
1156
1138
 
1157
- .dc-dialog-header__heading {
1139
+ .dc-dialog-header__title {
1158
1140
  font: 700 var(--dc-text-md);
1141
+ grid-column: 1 / 2;
1159
1142
  margin: 0;
1160
1143
  }
1161
1144
 
1162
- .dc-dialog-header__close-btn {
1163
- position: absolute;
1164
- top: 0;
1165
- right: 0;
1166
- width: 24px;
1167
- height: 24px;
1145
+ .dc-dialog-header__subtitle,
1146
+ .dc-dialog-header__body {
1147
+ grid-column: 1 / 3;
1168
1148
  }
1169
1149
 
1170
- .dc-dialog-header__subheading,
1171
- .dc-dialog-header__body {
1172
- margin-top: 4px;
1150
+ .dc-dialog-header__close-button {
1151
+ grid-column: 2 / 3;
1152
+ width: 24px;
1153
+ height: 24px;
1173
1154
  }
1174
1155
 
1175
1156
  .dc-dialog-body {
@@ -1184,32 +1165,61 @@
1184
1165
  .dc-dialog-footer {
1185
1166
  display: flex;
1186
1167
  flex-wrap: wrap;
1168
+ gap: 8px;
1187
1169
  align-items: center;
1188
1170
  justify-content: flex-end;
1189
1171
  }
1190
1172
 
1191
- .dc-dialog-footer > * + * {
1192
- margin-left: 8px;
1193
- }
1194
-
1195
- .dc-dialog-footer_has_border {
1196
- border-top: 1px solid var(--dc-dialog-inset-shadow-color);
1173
+ .dc-dialog-footer_has_divider {
1174
+ border-top: 1px solid var(--dc-dialog-divider-color);
1197
1175
  }
1198
1176
 
1199
- .dc-dialog-modal > .dc-dialog-header:first-child,
1200
- .dc-dialog-modal > .dc-dialog-body:first-child,
1201
- .dc-dialog-modal > .dc-dialog-footer:first-child {
1177
+ .dc-dialog__modal > :first-child {
1202
1178
  border-top-left-radius: inherit;
1203
1179
  border-top-right-radius: inherit;
1204
1180
  }
1205
1181
 
1206
- .dc-dialog-modal > .dc-dialog-header:last-child,
1207
- .dc-dialog-modal > .dc-dialog-body:last-child,
1208
- .dc-dialog-modal > .dc-dialog-footer:last-child {
1182
+ .dc-dialog__modal > :last-child {
1209
1183
  border-bottom-right-radius: inherit;
1210
1184
  border-bottom-left-radius: inherit;
1211
1185
  }
1212
1186
 
1187
+ .dc-dialog.enter {
1188
+ transition: opacity var(--dc-dialog-open-transition-duration) ease;
1189
+ }
1190
+
1191
+ .dc-dialog.enter-from {
1192
+ opacity: 0;
1193
+ }
1194
+
1195
+ .dc-dialog.enter-to {
1196
+ opacity: 1;
1197
+ }
1198
+
1199
+ .dc-dialog.enter .dc-dialog__modal {
1200
+ transition: transform var(--dc-dialog-open-transition-duration) ease-out;
1201
+ }
1202
+
1203
+ .dc-dialog.enter-from .dc-dialog__modal {
1204
+ transform: scale(0.95);
1205
+ }
1206
+
1207
+ .dc-dialog.enter-to .dc-dialog__modal {
1208
+ transform: scale(1);
1209
+ }
1210
+
1211
+ .dc-dialog.leave {
1212
+ transition: opacity var(--dc-dialog-close-transition-duration) ease;
1213
+ }
1214
+
1215
+ .dc-dialog.leave-from {
1216
+ opacity: 1;
1217
+ }
1218
+
1219
+ .dc-dialog.leave-to {
1220
+ opacity: 0;
1221
+ }
1222
+
1213
1223
  .dc-slide-over-header {
1214
1224
  color: var(--dc-slide-over-text-color);
1215
1225
  box-sizing: border-box;
@@ -339,7 +339,7 @@
339
339
  .dark.dc-dialog {
340
340
  --dc-dialog-border-color: var(--dc-gray-700);
341
341
  --dc-dialog-bg: var(--dc-gray-800);
342
- --dc-dialog-inset-shadow-color: var(--dc-border-color-transparent-1);
342
+ --dc-dialog-divider-color: var(--dc-border-color-transparent-1);
343
343
 
344
344
  color-scheme: dark;
345
345
  }
@@ -1,7 +1,7 @@
1
1
  import { ComponentPropsWithoutRef } from 'react';
2
2
  type DialogFooterHTMLProps = ComponentPropsWithoutRef<'div'>;
3
3
  export type DialogFooterProps = {
4
- hasBorder?: boolean;
4
+ hasDivider?: boolean;
5
5
  } & DialogFooterHTMLProps;
6
- export declare function DialogFooter({ hasBorder, className, children, }: DialogFooterProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function DialogFooter({ className, hasDivider, children, }: DialogFooterProps): import("react/jsx-runtime").JSX.Element;
7
7
  export {};
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { classNames } from '../../lib/react-helpers.js';
3
- export function DialogFooter({ hasBorder, className, children, }) {
3
+ export function DialogFooter({ className, hasDivider, children, }) {
4
4
  return (_jsx("div", { className: classNames(className, {
5
5
  'dc-dialog-footer': true,
6
- 'dc-dialog-footer_has_border': hasBorder,
6
+ 'dc-dialog-footer_has_divider': hasDivider,
7
7
  }), children: children }));
8
8
  }
@@ -1,9 +1,10 @@
1
1
  import { ComponentPropsWithoutRef, ReactNode } from 'react';
2
2
  type DialogHeaderHTMLProps = ComponentPropsWithoutRef<'div'>;
3
+ type DialogHeaderBaseProps = Omit<DialogHeaderHTMLProps, 'title'>;
3
4
  export type DialogHeaderProps = {
4
- hasBorder?: boolean;
5
- heading?: ReactNode;
6
- subheading?: ReactNode;
7
- } & DialogHeaderHTMLProps;
8
- export declare function DialogHeader({ hasBorder, heading, subheading, className, children, }: DialogHeaderProps): import("react/jsx-runtime").JSX.Element;
5
+ hasDivider?: boolean;
6
+ title?: ReactNode;
7
+ subtitle?: ReactNode;
8
+ } & DialogHeaderBaseProps;
9
+ export declare function DialogHeader({ className, hasDivider, title, subtitle, children, }: DialogHeaderProps): import("react/jsx-runtime").JSX.Element;
9
10
  export {};
@@ -3,13 +3,16 @@ import { classNames } from '../../lib/react-helpers.js';
3
3
  import { useDialogContext } from './dialog-context.js';
4
4
  import { IconButton } from '../button/index.js';
5
5
  import { XMarkIcon } from '../hero-icons/24/outline/x-mark-icon.js';
6
- export function DialogHeader({ hasBorder, heading, subheading, className, children, }) {
7
- const { titleId, descriptionId, onClose, } = useDialogContext();
8
- const shouldRenderHeading = Boolean(heading);
9
- const shouldRenderDescription = Boolean(subheading);
10
- const shouldRenderChildren = Boolean(children);
6
+ export function DialogHeader({ className, hasDivider, title, subtitle, children, }) {
7
+ const { titleId, descriptionId, onClose } = useDialogContext();
11
8
  return (_jsxs("div", { className: classNames(className, {
12
9
  'dc-dialog-header': true,
13
- 'dc-dialog-header_has_border': hasBorder,
14
- }), children: [_jsxs("div", { className: "dc-dialog-header__title-bar", children: [shouldRenderHeading && (_jsx("h2", { id: titleId, className: "dc-dialog-header__heading", children: heading })), _jsx(IconButton, { className: "dc-dialog-header__close-btn", buttonStyle: "plain", onClick: () => onClose(), children: _jsx(XMarkIcon, { width: 18, height: 18, strokeWidth: 2 }) })] }), shouldRenderDescription && (_jsx("div", { id: descriptionId, className: "dc-dialog-header__subheading", children: subheading })), shouldRenderChildren && (_jsx("div", { className: "dc-dialog-header__body", children: children }))] }));
10
+ 'dc-dialog-header_has_divider': hasDivider,
11
+ }), children: [title
12
+ ? _jsx("h3", { id: titleId, className: "dc-dialog-header__title", children: title })
13
+ : null, _jsx(IconButton, { buttonStyle: "plain", className: "dc-dialog-header__close-button", onClick: () => onClose(), children: _jsx(XMarkIcon, { width: 18, height: 18, strokeWidth: 2 }) }), subtitle
14
+ ? _jsx("div", { id: descriptionId, className: "dc-dialog-header__subtitle", children: subtitle })
15
+ : null, children
16
+ ? _jsx("div", { className: "dc-dialog-header__body", children: children })
17
+ : null] }));
15
18
  }
@@ -1,12 +1,28 @@
1
1
  import { ComponentPropsWithoutRef, RefObject } from 'react';
2
+ import { DialogHeader } from './dialog-header.js';
3
+ import { DialogBody } from './dialog-body.js';
4
+ import { DialogFooter } from './dialog-footer.js';
2
5
  type DialogHTMLProps = ComponentPropsWithoutRef<'section'>;
6
+ type ContainerProps = ComponentPropsWithoutRef<'div'>;
7
+ type BackdropProps = ComponentPropsWithoutRef<'div'>;
3
8
  export type DialogWidth = 'sm' | 'md' | 'lg';
4
9
  export type DialogProps = {
5
10
  width?: DialogWidth;
6
- openFocusRef?: RefObject<HTMLElement>;
7
- closeFocusRef?: RefObject<HTMLElement>;
11
+ focusAfterOpenRef?: RefObject<HTMLElement>;
12
+ focusAfterCloseRef?: RefObject<HTMLElement>;
13
+ openAnimationDurationMs?: number;
14
+ closeAnimationDurationMs?: number;
15
+ containerProps?: ContainerProps;
16
+ backdropProps?: BackdropProps;
8
17
  isOpen: boolean;
9
18
  onClose: () => void;
19
+ onOpenAnimationEnd?: () => void;
20
+ onCloseAnimationEnd?: () => void;
10
21
  } & DialogHTMLProps;
11
- export declare function Dialog({ width, isOpen, openFocusRef, closeFocusRef, onClose, className, children, ...props }: DialogProps): import("react/jsx-runtime").JSX.Element | null;
22
+ export declare function Dialog(props: DialogProps): import("react/jsx-runtime").JSX.Element | null;
23
+ export declare namespace Dialog {
24
+ var Header: typeof DialogHeader;
25
+ var Body: typeof DialogBody;
26
+ var Footer: typeof DialogFooter;
27
+ }
12
28
  export {};
@@ -1,42 +1,52 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useId, useRef } from 'react';
3
3
  import { classNames, focusElement } from '../../lib/react-helpers.js';
4
- import { useDisableBodyScroll, useEscKeyDown, useFocusTrap, useMountTransition } from '../../hooks/index.js';
4
+ import { useDisableBodyScroll, useEscKeyDown, useFocusTrap, usePreservePropsWhenClosed, useShowTransition, } from '../../hooks/index.js';
5
5
  import { Portal } from '../portal/index.js';
6
6
  import { DialogContextProvider } from './dialog-context.js';
7
- export function Dialog({ width = 'md', isOpen = false, openFocusRef, closeFocusRef, onClose, className, children, ...props }) {
7
+ import { DialogHeader } from './dialog-header.js';
8
+ import { DialogBody } from './dialog-body.js';
9
+ import { DialogFooter } from './dialog-footer.js';
10
+ export function Dialog(props) {
11
+ const { className, width = 'md', isOpen = false, focusAfterOpenRef, focusAfterCloseRef, openAnimationDurationMs = 250, closeAnimationDurationMs = 150, containerProps = {}, backdropProps = {}, children, onClose, onOpenAnimationEnd, onCloseAnimationEnd, ...otherProps } = usePreservePropsWhenClosed(props, 'isOpen');
8
12
  const defaultId = useId();
9
13
  const dialogRef = useRef(null);
10
- const durationMs = 200;
11
- const { isMounted, className: transitionClass } = useMountTransition({
12
- durationMs,
13
- isShown: isOpen,
14
- enterFrom: 'dc-dialog_closed',
15
- enterTo: 'dc-dialog_opened',
14
+ const modalRef = useRef(null);
15
+ const { shouldRender, transitionClassName } = useShowTransition({
16
+ isOpen,
17
+ enterDurationMs: openAnimationDurationMs,
18
+ leaveDurationMs: closeAnimationDurationMs,
19
+ onEnterTransitionEnd: onOpenAnimationEnd,
20
+ onLeaveTransitionEnd: onCloseAnimationEnd,
16
21
  });
22
+ useDisableBodyScroll({ isEnabled: shouldRender });
23
+ useEscKeyDown(onClose, { isEnabled: shouldRender });
24
+ useFocusTrap(modalRef, { isEnabled: shouldRender });
17
25
  useEffect(() => {
18
- if (isOpen) {
19
- const openFocus = openFocusRef?.current;
20
- const closeFocus = closeFocusRef?.current;
21
- focusElement(openFocus);
22
- return () => focusElement(closeFocus);
26
+ if (shouldRender) {
27
+ const focusAfterOpen = focusAfterOpenRef?.current;
28
+ const focusAfterClose = focusAfterCloseRef?.current;
29
+ focusElement(focusAfterOpen);
30
+ return () => {
31
+ focusElement(focusAfterClose);
32
+ };
23
33
  }
24
- }, [isOpen, openFocusRef, closeFocusRef]);
25
- useEscKeyDown(() => {
26
- onClose();
27
- }, { isEnabled: isOpen });
28
- useFocusTrap(dialogRef, { isEnabled: isOpen });
29
- useDisableBodyScroll({ isEnabled: isOpen });
30
- if (!isOpen && !isMounted) {
34
+ }, [shouldRender, focusAfterOpenRef, focusAfterCloseRef]);
35
+ if (!shouldRender) {
31
36
  return null;
32
37
  }
33
- const id = props.id || defaultId;
38
+ const id = otherProps.id || defaultId;
34
39
  const titleId = `dialog-title-${id}`;
35
40
  const descriptionId = `dialog-description-${id}`;
36
- return (_jsx(Portal, { children: _jsxs("div", { style: {
37
- '--dc-dialog-transition-duration': `${durationMs}ms`,
38
- }, className: classNames('dc-dialog', transitionClass), children: [_jsx("div", { className: "dc-dialog-backdrop" }), _jsx("div", { ...props, ref: dialogRef, className: classNames(className, {
39
- 'dc-dialog-modal': true,
40
- [`dc-dialog-modal_${width}`]: width,
41
- }), role: "dialog", id: id, "aria-labelledby": titleId, "aria-describedby": descriptionId, "aria-modal": true, children: _jsx(DialogContextProvider, { titleId: titleId, descriptionId: descriptionId, isOpen: isOpen, onClose: onClose, children: children }) })] }) }));
41
+ return (_jsx(Portal, { children: _jsxs("div", { ...containerProps, ref: dialogRef, className: classNames('dc-dialog', transitionClassName, containerProps.className), style: {
42
+ '--dc-dialog-open-transition-duration': `${openAnimationDurationMs}ms`,
43
+ '--dc-dialog-close-transition-duration': `${closeAnimationDurationMs}ms`,
44
+ ...containerProps.style,
45
+ }, children: [_jsx("div", { ...backdropProps, className: classNames('dc-dialog__backdrop', backdropProps.className) }), _jsx("div", { ...otherProps, id: id, className: classNames(className, {
46
+ 'dc-dialog__modal': true,
47
+ [`dc-dialog__modal_${width}`]: width,
48
+ }), ref: modalRef, role: "dialog", "aria-labelledby": titleId, "aria-describedby": descriptionId, "aria-modal": true, children: _jsx(DialogContextProvider, { titleId: titleId, descriptionId: descriptionId, isOpen: isOpen, onClose: onClose, children: children }) })] }) }));
42
49
  }
50
+ Dialog.Header = DialogHeader;
51
+ Dialog.Body = DialogBody;
52
+ Dialog.Footer = DialogFooter;
@@ -1,4 +1,7 @@
1
+ export * from './use-callback-ref.js';
1
2
  export * from './use-disable-body-scroll.js';
2
3
  export * from './use-esc-key-down.js';
3
4
  export * from './use-focus-trap.js';
4
5
  export * from './use-mount-transition.js';
6
+ export * from './use-show-transition.js';
7
+ export * from './use-preserve-props-when-closed.js';
@@ -1,4 +1,7 @@
1
+ export * from './use-callback-ref.js';
1
2
  export * from './use-disable-body-scroll.js';
2
3
  export * from './use-esc-key-down.js';
3
4
  export * from './use-focus-trap.js';
4
5
  export * from './use-mount-transition.js';
6
+ export * from './use-show-transition.js';
7
+ export * from './use-preserve-props-when-closed.js';
@@ -0,0 +1,3 @@
1
+ type Callback = (...args: unknown[]) => unknown;
2
+ export declare function useCallbackRef<T extends Callback>(callback: T): T;
3
+ export {};
@@ -0,0 +1,8 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ export function useCallbackRef(callback) {
3
+ const savedCallback = useRef(callback);
4
+ useEffect(() => {
5
+ savedCallback.current = callback;
6
+ }, [callback]);
7
+ return useMemo(() => ((...args) => savedCallback.current(...args)), []);
8
+ }
@@ -29,4 +29,7 @@ export type TransitionState = {
29
29
  isMounted: boolean;
30
30
  className: string;
31
31
  };
32
+ /**
33
+ * @deprecated
34
+ */
32
35
  export declare function useMountTransition({ animateFirstMount, isShown, durationMs, enterFrom, enterTo, }: TransitionParams): TransitionState;
@@ -1,5 +1,8 @@
1
1
  import { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { classNames } from '../lib/react-helpers.js';
3
+ /**
4
+ * @deprecated
5
+ */
3
6
  export function useMountTransition({ animateFirstMount = false, isShown, durationMs, enterFrom, enterTo, }) {
4
7
  const [isMounted, setIsMounted] = useState(isShown);
5
8
  const [hasEnterToClass, setHasEnterToClass] = useState(false);
@@ -0,0 +1 @@
1
+ export declare function usePrefersReducedMotion(): boolean;
@@ -0,0 +1,15 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ export function usePrefersReducedMotion() {
3
+ const media = useMemo(() => window.matchMedia('(prefers-reduced-motion)'), []);
4
+ const [reduced, setReduced] = useState(media.matches);
5
+ useEffect(() => {
6
+ const handleChange = () => {
7
+ setReduced(media.matches);
8
+ };
9
+ media.addEventListener('change', handleChange);
10
+ return () => {
11
+ media.removeEventListener('change', handleChange);
12
+ };
13
+ }, [media]);
14
+ return reduced;
15
+ }
@@ -0,0 +1 @@
1
+ export declare function usePreservePropsWhenClosed<T extends Record<string, unknown>>(props: T, openProp: keyof T): T;
@@ -0,0 +1,14 @@
1
+ import { useRef } from 'react';
2
+ export function usePreservePropsWhenClosed(props, openProp) {
3
+ const savedProps = useRef(props);
4
+ if (props[openProp]) {
5
+ savedProps.current = props;
6
+ }
7
+ else {
8
+ savedProps.current = {
9
+ ...savedProps.current,
10
+ [openProp]: false,
11
+ };
12
+ }
13
+ return savedProps.current;
14
+ }
@@ -0,0 +1,16 @@
1
+ export declare function useShowTransition({ isOpen, enterDurationMs, leaveDurationMs, enter, enterFrom, enterTo, leave, leaveFrom, leaveTo, onEnterTransitionEnd: _onEnterTransitionEnd, onLeaveTransitionEnd: _onLeaveTransitionEnd, }: {
2
+ isOpen: boolean;
3
+ enterDurationMs: number;
4
+ leaveDurationMs: number;
5
+ enter?: string;
6
+ enterFrom?: string;
7
+ enterTo?: string;
8
+ leave?: string;
9
+ leaveFrom?: string;
10
+ leaveTo?: string;
11
+ onEnterTransitionEnd?: () => void;
12
+ onLeaveTransitionEnd?: () => void;
13
+ }): {
14
+ shouldRender: boolean;
15
+ transitionClassName: string;
16
+ };
@@ -0,0 +1,69 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { useCallbackRef } from './index.js';
3
+ import { classNames, noop } from '../lib/index.js';
4
+ import { usePrefersReducedMotion } from './use-prefers-reduced-motion.js';
5
+ export function useShowTransition({ isOpen, enterDurationMs, leaveDurationMs, enter = 'enter', enterFrom = 'enter-from', enterTo = 'enter-to', leave = 'leave', leaveFrom = 'leave-from', leaveTo = 'leave-to', onEnterTransitionEnd: _onEnterTransitionEnd = noop, onLeaveTransitionEnd: _onLeaveTransitionEnd = noop, }) {
6
+ const onEnterTransitionEnd = useCallbackRef(_onEnterTransitionEnd);
7
+ const onLeaveTransitionEnd = useCallbackRef(_onLeaveTransitionEnd);
8
+ const enterTimeoutRef = useRef(-1);
9
+ const leaveTimeoutRef = useRef(-1);
10
+ const prefersReducedMotion = usePrefersReducedMotion();
11
+ const [shouldRender, setShouldRender] = useState(isOpen);
12
+ const [transitionClasses, setTransitionClasses] = useState(isOpen
13
+ ? [enter, enterTo]
14
+ : [enter, enterFrom]);
15
+ useEffect(() => {
16
+ if (prefersReducedMotion) {
17
+ return;
18
+ }
19
+ if (isOpen) {
20
+ window.clearTimeout(leaveTimeoutRef.current);
21
+ setShouldRender(true);
22
+ return () => {
23
+ window.clearTimeout(enterTimeoutRef.current);
24
+ setTransitionClasses([leave, leaveFrom]);
25
+ window.setTimeout(() => {
26
+ setTransitionClasses([leave, leaveTo]);
27
+ });
28
+ leaveTimeoutRef.current = window.setTimeout(() => {
29
+ setShouldRender(false);
30
+ setTransitionClasses([enter, enterFrom]);
31
+ onLeaveTransitionEnd();
32
+ }, leaveDurationMs);
33
+ };
34
+ }
35
+ }, [
36
+ prefersReducedMotion,
37
+ isOpen,
38
+ enter,
39
+ enterFrom,
40
+ leave,
41
+ leaveFrom,
42
+ leaveTo,
43
+ leaveDurationMs,
44
+ onLeaveTransitionEnd,
45
+ ]);
46
+ useEffect(() => {
47
+ if (prefersReducedMotion) {
48
+ return;
49
+ }
50
+ if (shouldRender) {
51
+ setTransitionClasses([enter, enterTo]);
52
+ enterTimeoutRef.current = window.setTimeout(() => {
53
+ setTransitionClasses([]);
54
+ onEnterTransitionEnd();
55
+ }, enterDurationMs);
56
+ }
57
+ }, [
58
+ prefersReducedMotion,
59
+ shouldRender,
60
+ enter,
61
+ enterTo,
62
+ enterDurationMs,
63
+ onEnterTransitionEnd,
64
+ ]);
65
+ return {
66
+ shouldRender: isOpen || shouldRender,
67
+ transitionClassName: prefersReducedMotion ? '' : classNames(...transitionClasses),
68
+ };
69
+ }
@@ -1,5 +1,6 @@
1
1
  type AnyFunction = (...args: any[]) => any;
2
2
  export declare function once<T extends AnyFunction>(fn: T): (...args: Parameters<T>) => ReturnType<T>;
3
- export declare function exhaustiveCheck(value: never, message?: string): never;
4
3
  export declare function assertIfNullable<T>(value: T, message?: string): asserts value is NonNullable<T>;
4
+ export declare function exhaustiveCheck(value: never, message?: string): never;
5
+ export declare function noop(): undefined;
5
6
  export {};
@@ -9,11 +9,14 @@ export function once(fn) {
9
9
  return result;
10
10
  };
11
11
  }
12
- export function exhaustiveCheck(value, message) {
13
- throw new Error(message);
14
- }
15
12
  export function assertIfNullable(value, message = 'assertIfNullable: value is null or undefined.') {
16
13
  if (value == null) {
17
14
  throw Error(message);
18
15
  }
19
16
  }
17
+ export function exhaustiveCheck(value, message) {
18
+ throw new Error(message);
19
+ }
20
+ export function noop() {
21
+ return undefined;
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "draft-components",
3
- "version": "2.5.3",
3
+ "version": "2.6.0",
4
4
  "description": "The React based UI components library.",
5
5
  "type": "module",
6
6
  "exports": {