draft-components 2.5.3 → 2.7.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/css/draft-components.css +82 -67
- package/css/draft-components.dark.css +1 -1
- package/dist/components/dialog/dialog-footer.d.ts +2 -2
- package/dist/components/dialog/dialog-footer.js +2 -2
- package/dist/components/dialog/dialog-header.d.ts +6 -5
- package/dist/components/dialog/dialog-header.js +10 -7
- package/dist/components/dialog/dialog.d.ts +19 -3
- package/dist/components/dialog/dialog.js +37 -27
- package/dist/components/menu/menu-item.d.ts +1 -0
- package/dist/components/menu/menu-item.js +2 -2
- package/dist/components/menu/menu.js +1 -1
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/use-callback-ref.d.ts +3 -0
- package/dist/hooks/use-callback-ref.js +8 -0
- package/dist/hooks/use-mount-transition.d.ts +3 -0
- package/dist/hooks/use-mount-transition.js +3 -0
- package/dist/hooks/use-prefers-reduced-motion.d.ts +1 -0
- package/dist/hooks/use-prefers-reduced-motion.js +15 -0
- package/dist/hooks/use-preserve-props-when-closed.d.ts +1 -0
- package/dist/hooks/use-preserve-props-when-closed.js +14 -0
- package/dist/hooks/use-show-transition.d.ts +16 -0
- package/dist/hooks/use-show-transition.js +69 -0
- package/dist/lib/helpers.d.ts +2 -1
- package/dist/lib/helpers.js +6 -3
- package/package.json +1 -1
package/css/draft-components.css
CHANGED
|
@@ -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-
|
|
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
|
-
}
|
|
1091
|
-
|
|
1092
|
-
.dc-dialog_closed .dc-dialog-backdrop {
|
|
1093
|
-
opacity: 0;
|
|
1094
|
-
transition: opacity var(--dc-dialog-transition-duration) linear;
|
|
1095
1090
|
}
|
|
1096
1091
|
|
|
1097
|
-
.dc-
|
|
1098
|
-
|
|
1092
|
+
.dc-dialog__backdrop {
|
|
1093
|
+
position: fixed;
|
|
1094
|
+
inset: 0;
|
|
1095
|
+
background: var(--dc-dialog-backdrop-color);
|
|
1099
1096
|
}
|
|
1100
1097
|
|
|
1101
|
-
.dc-
|
|
1098
|
+
.dc-dialog__modal {
|
|
1099
|
+
font: var(--dc-text-sm);
|
|
1102
1100
|
color: var(--dc-dialog-text-color);
|
|
1103
|
-
position:
|
|
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:
|
|
1110
|
-
max-width:
|
|
1111
|
-
max-height:
|
|
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-
|
|
1119
|
-
|
|
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-
|
|
1134
|
-
|
|
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-
|
|
1149
|
-
border-bottom: 1px solid var(--dc-dialog-
|
|
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-
|
|
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-
|
|
1163
|
-
|
|
1164
|
-
|
|
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-
|
|
1171
|
-
|
|
1172
|
-
|
|
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-
|
|
1192
|
-
|
|
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-
|
|
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-
|
|
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;
|
|
@@ -3467,6 +3477,11 @@
|
|
|
3467
3477
|
background: var(--dc-menu-item-bg);
|
|
3468
3478
|
}
|
|
3469
3479
|
|
|
3480
|
+
.dc-menu-btn:disabled {
|
|
3481
|
+
opacity: var(--dc-disabled-state-opacity);
|
|
3482
|
+
cursor: default;
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3470
3485
|
.dc-menu-btn__label {
|
|
3471
3486
|
flex-grow: 1;
|
|
3472
3487
|
}
|
|
@@ -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-
|
|
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
|
-
|
|
4
|
+
hasDivider?: boolean;
|
|
5
5
|
} & DialogFooterHTMLProps;
|
|
6
|
-
export declare function DialogFooter({
|
|
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({
|
|
3
|
+
export function DialogFooter({ className, hasDivider, children, }) {
|
|
4
4
|
return (_jsx("div", { className: classNames(className, {
|
|
5
5
|
'dc-dialog-footer': true,
|
|
6
|
-
'dc-dialog-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} &
|
|
8
|
-
export declare function DialogHeader({
|
|
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({
|
|
7
|
-
const { titleId, descriptionId, onClose
|
|
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-
|
|
14
|
-
}), 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
|
-
|
|
7
|
-
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 (
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
focusElement(
|
|
22
|
-
return () =>
|
|
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
|
-
}, [
|
|
25
|
-
|
|
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 =
|
|
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': `${
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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;
|
|
@@ -3,6 +3,7 @@ type MenuItemHTMLProps = ComponentPropsWithRef<'button'>;
|
|
|
3
3
|
type MenuItemBaseProps = Omit<MenuItemHTMLProps, 'children' | 'role'>;
|
|
4
4
|
export type MenuItemProps = {
|
|
5
5
|
role?: 'menuitem' | 'menuitemradio';
|
|
6
|
+
disabled?: boolean;
|
|
6
7
|
destructive?: boolean;
|
|
7
8
|
iconLeft?: ReactNode;
|
|
8
9
|
iconRight?: ReactNode;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
import { classNames } from '../../lib/react-helpers.js';
|
|
4
|
-
export const MenuItem = forwardRef(function MenuItem({ role = 'menuitem', destructive, iconLeft, iconRight, children, className, ...props }, ref) {
|
|
4
|
+
export const MenuItem = forwardRef(function MenuItem({ role = 'menuitem', disabled, destructive, iconLeft, iconRight, children, className, ...props }, ref) {
|
|
5
5
|
let label = children;
|
|
6
6
|
if (iconLeft || iconRight) {
|
|
7
7
|
const className = classNames('dc-menu-btn__label', {
|
|
@@ -12,5 +12,5 @@ export const MenuItem = forwardRef(function MenuItem({ role = 'menuitem', destru
|
|
|
12
12
|
}
|
|
13
13
|
return (_jsx("li", { role: "presentation", children: _jsxs("button", { ...props, ref: ref, className: classNames(className, 'dc-menu-btn', {
|
|
14
14
|
'dc-menu-btn_destructive': destructive,
|
|
15
|
-
}), type: "button", role: role, tabIndex: -1, children: [iconLeft, label, iconRight] }) }));
|
|
15
|
+
}), type: "button", disabled: disabled, role: role, tabIndex: -1, children: [iconLeft, label, iconRight] }) }));
|
|
16
16
|
});
|
|
@@ -183,6 +183,6 @@ function getMenuItems(menuId) {
|
|
|
183
183
|
if (!menuEl) {
|
|
184
184
|
return [];
|
|
185
185
|
}
|
|
186
|
-
const nodes = menuEl.querySelectorAll('button[role="menuitem"], button[role="menuitemradio"]');
|
|
186
|
+
const nodes = menuEl.querySelectorAll('button[role="menuitem"]:not(:disabled), button[role="menuitemradio"]:not(:disabled)');
|
|
187
187
|
return Array.from(nodes);
|
|
188
188
|
}
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/hooks/index.js
CHANGED
|
@@ -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,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
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/lib/helpers.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/lib/helpers.js
CHANGED
|
@@ -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
|
+
}
|