@stfrigerio/sito-template 0.1.80 → 0.1.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/atoms/Modal/Modal.d.ts +4 -38
- package/dist/components/atoms/Modal/Modal.d.ts.map +1 -1
- package/dist/index.esm.js +48 -42
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +47 -41
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
- package/storybook-static/project.json +1 -1
|
@@ -18,44 +18,10 @@ export interface ModalProps {
|
|
|
18
18
|
actions?: ReactNode;
|
|
19
19
|
/** When true, the body is padded; when false, the body is flush to the edges */
|
|
20
20
|
padding?: boolean;
|
|
21
|
+
/** How the modal renders on narrow viewports. `sheet` slides from the bottom; `center` stays centered. Defaults to `sheet`. */
|
|
22
|
+
mobileVariant?: 'sheet' | 'center';
|
|
23
|
+
/** When the modal is in sheet mode on mobile, allow the user to swipe the header down to dismiss. Defaults to true. */
|
|
24
|
+
draggable?: boolean;
|
|
21
25
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Modal Component
|
|
24
|
-
*
|
|
25
|
-
* @component
|
|
26
|
-
* @description
|
|
27
|
-
* An accessible overlay dialog with a backdrop, title header, optional
|
|
28
|
-
* header actions, and a flexible body. Renders into a React portal on
|
|
29
|
-
* `document.body`, animates in/out with Framer Motion, closes on Escape
|
|
30
|
-
* key or backdrop click, and clicks inside the dialog don't propagate
|
|
31
|
-
* to the backdrop.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* // Basic usage
|
|
35
|
-
* <Modal open={isOpen} title="Confirm" onClose={() => setIsOpen(false)}>
|
|
36
|
-
* <p>Are you sure?</p>
|
|
37
|
-
* </Modal>
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* // With header actions and wide size
|
|
41
|
-
* <Modal
|
|
42
|
-
* open={isOpen}
|
|
43
|
-
* title="Edit profile"
|
|
44
|
-
* size="wide"
|
|
45
|
-
* actions={<button onClick={save}>Save</button>}
|
|
46
|
-
* onClose={handleClose}
|
|
47
|
-
* >
|
|
48
|
-
* <ProfileForm />
|
|
49
|
-
* </Modal>
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* // Flush body (no padding) for editors and full-bleed content
|
|
53
|
-
* <Modal open={isOpen} title="Document" padding={false} onClose={handleClose}>
|
|
54
|
-
* <DocEditor content={content} onSave={save} />
|
|
55
|
-
* </Modal>
|
|
56
|
-
*
|
|
57
|
-
* @param {ModalProps} props - The props for the Modal component
|
|
58
|
-
* @returns {React.ReactPortal | null} A portal rendering the modal, or null on the server
|
|
59
|
-
*/
|
|
60
26
|
export declare const Modal: React.FC<ModalProps>;
|
|
61
27
|
//# sourceMappingURL=Modal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../../../src/components/atoms/Modal/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../../../src/components/atoms/Modal/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAA+B,MAAM,OAAO,CAAC;AAMtE;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,6CAA6C;IAC7C,IAAI,EAAE,OAAO,CAAC;IACd,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,qGAAqG;IACrG,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,yBAAyB;IACzB,QAAQ,EAAE,SAAS,CAAC;IACpB,gGAAgG;IAChG,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IACtC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+HAA+H;IAC/H,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACnC,uHAAuH;IACvH,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAqBD,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,UAAU,CA+GtC,CAAC"}
|
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { motion, AnimatePresence, useMotionValue, useTransform, animate, LayoutGroup } from 'framer-motion';
|
|
2
|
+
import { motion, AnimatePresence, useDragControls, useMotionValue, useTransform, animate, LayoutGroup } from 'framer-motion';
|
|
3
3
|
import React, { useRef, useEffect, useCallback, useState, useMemo, createContext, useContext, Fragment as Fragment$1, memo } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { X, Calendar as Calendar$1, ChevronDown, Search, Check, Edit, Folder, Users, Book, MessageSquare, UserPlus, Sun, Moon, Info, Github, SquareKanban, ChevronRight, Plus, Loader2, AlertTriangle, Play, Brain, FolderSearch, FilePlus, Pencil, FileText, Terminal, Trash2, GripVertical, Download, Menu, ChevronLeft, Maximize, Share2, Pause } from 'lucide-react';
|
|
@@ -720,47 +720,29 @@ const EmptyState = ({ icon, title, message, action, size = 'default', }) => {
|
|
|
720
720
|
return (jsxs("div", { className: wrapperClass, children: [icon && jsx("div", { className: styles$z.icon, children: icon }), title && jsx("h3", { className: styles$z.title, children: title }), jsx("p", { className: styles$z.message, children: message }), action && jsx("div", { className: styles$z.action, children: action })] }));
|
|
721
721
|
};
|
|
722
722
|
|
|
723
|
-
var styles$y = {"backdrop":"Modal-module_backdrop__yOx-a","dialog":"Modal-module_dialog__yEXTq","dialogCompact":"Modal-module_dialogCompact__z1Wxp","dialogWide":"Modal-module_dialogWide__9PTXK","header":"Modal-module_header__NHHKd","title":"Modal-module_title__i3R0x","headerActions":"Modal-module_headerActions__g28UN","closeButton":"Modal-module_closeButton__siC-1","body":"Modal-module_body__U7jvM","bodyFlush":"Modal-module_bodyFlush__wtk3q"};
|
|
723
|
+
var styles$y = {"backdrop":"Modal-module_backdrop__yOx-a","dialog":"Modal-module_dialog__yEXTq","dialogCompact":"Modal-module_dialogCompact__z1Wxp","dialogWide":"Modal-module_dialogWide__9PTXK","header":"Modal-module_header__NHHKd","title":"Modal-module_title__i3R0x","headerActions":"Modal-module_headerActions__g28UN","closeButton":"Modal-module_closeButton__siC-1","body":"Modal-module_body__U7jvM","bodyFlush":"Modal-module_bodyFlush__wtk3q","backdropSheet":"Modal-module_backdropSheet__o9kW3","dialogSheet":"Modal-module_dialogSheet__EjpwP","grabBar":"Modal-module_grabBar__qhl9f"};
|
|
724
724
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
* title="Edit profile"
|
|
747
|
-
* size="wide"
|
|
748
|
-
* actions={<button onClick={save}>Save</button>}
|
|
749
|
-
* onClose={handleClose}
|
|
750
|
-
* >
|
|
751
|
-
* <ProfileForm />
|
|
752
|
-
* </Modal>
|
|
753
|
-
*
|
|
754
|
-
* @example
|
|
755
|
-
* // Flush body (no padding) for editors and full-bleed content
|
|
756
|
-
* <Modal open={isOpen} title="Document" padding={false} onClose={handleClose}>
|
|
757
|
-
* <DocEditor content={content} onSave={save} />
|
|
758
|
-
* </Modal>
|
|
759
|
-
*
|
|
760
|
-
* @param {ModalProps} props - The props for the Modal component
|
|
761
|
-
* @returns {React.ReactPortal | null} A portal rendering the modal, or null on the server
|
|
762
|
-
*/
|
|
763
|
-
const Modal = ({ open, title, onClose, children, size = 'default', actions, padding = true, }) => {
|
|
725
|
+
const MOBILE_BREAKPOINT = '(max-width: 640px)';
|
|
726
|
+
const useIsMobile = () => {
|
|
727
|
+
const [isMobile, setIsMobile] = useState(() => {
|
|
728
|
+
if (typeof window === 'undefined')
|
|
729
|
+
return false;
|
|
730
|
+
return window.matchMedia(MOBILE_BREAKPOINT).matches;
|
|
731
|
+
});
|
|
732
|
+
useEffect(() => {
|
|
733
|
+
if (typeof window === 'undefined')
|
|
734
|
+
return;
|
|
735
|
+
const mq = window.matchMedia(MOBILE_BREAKPOINT);
|
|
736
|
+
const onChange = (e) => setIsMobile(e.matches);
|
|
737
|
+
mq.addEventListener('change', onChange);
|
|
738
|
+
return () => mq.removeEventListener('change', onChange);
|
|
739
|
+
}, []);
|
|
740
|
+
return isMobile;
|
|
741
|
+
};
|
|
742
|
+
const Modal = ({ open, title, onClose, children, size = 'default', actions, padding = true, mobileVariant = 'sheet', draggable = true, }) => {
|
|
743
|
+
const isMobile = useIsMobile();
|
|
744
|
+
const dragControls = useDragControls();
|
|
745
|
+
const headerRef = useRef(null);
|
|
764
746
|
useEffect(() => {
|
|
765
747
|
if (!open)
|
|
766
748
|
return;
|
|
@@ -773,14 +755,38 @@ const Modal = ({ open, title, onClose, children, size = 'default', actions, padd
|
|
|
773
755
|
}, [open, onClose]);
|
|
774
756
|
if (typeof document === 'undefined')
|
|
775
757
|
return null;
|
|
758
|
+
const isSheet = mobileVariant === 'sheet' && isMobile;
|
|
759
|
+
const isDraggable = isSheet && draggable;
|
|
760
|
+
const backdropClass = [styles$y.backdrop, isSheet && styles$y.backdropSheet]
|
|
761
|
+
.filter(Boolean)
|
|
762
|
+
.join(' ');
|
|
776
763
|
const dialogClass = [
|
|
777
764
|
styles$y.dialog,
|
|
778
765
|
size === 'compact' && styles$y.dialogCompact,
|
|
779
766
|
size === 'wide' && styles$y.dialogWide,
|
|
767
|
+
isSheet && styles$y.dialogSheet,
|
|
780
768
|
]
|
|
781
769
|
.filter(Boolean)
|
|
782
770
|
.join(' ');
|
|
783
|
-
|
|
771
|
+
const enteringAnimation = isSheet
|
|
772
|
+
? { initial: { y: '100%' }, animate: { y: 0 }, exit: { y: '100%' } }
|
|
773
|
+
: {
|
|
774
|
+
initial: { opacity: 0, scale: 0.96, y: 8 },
|
|
775
|
+
animate: { opacity: 1, scale: 1, y: 0 },
|
|
776
|
+
exit: { opacity: 0, scale: 0.96, y: 8 },
|
|
777
|
+
};
|
|
778
|
+
return createPortal(jsx(AnimatePresence, { children: open && (jsx(motion.div, { className: backdropClass, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, onClick: onClose, "aria-hidden": "true", children: jsxs(motion.div, { className: dialogClass, role: "dialog", "aria-modal": "true", "aria-label": title, ...enteringAnimation, transition: isSheet
|
|
779
|
+
? { type: 'spring', stiffness: 320, damping: 32 }
|
|
780
|
+
: { duration: 0.15, ease: 'easeOut' }, onClick: (e) => e.stopPropagation(), drag: isDraggable ? 'y' : false, dragControls: isDraggable ? dragControls : undefined, dragConstraints: { top: 0 }, dragElastic: { top: 0, bottom: 0.3 }, dragListener: false, onDragEnd: (_, info) => {
|
|
781
|
+
if (info.offset.y > 80)
|
|
782
|
+
onClose();
|
|
783
|
+
}, children: [isSheet && jsx("div", { className: styles$y.grabBar }), jsxs("div", { ref: headerRef, className: styles$y.header, onPointerDown: (e) => {
|
|
784
|
+
if (!isDraggable)
|
|
785
|
+
return;
|
|
786
|
+
if (e.target.closest('button'))
|
|
787
|
+
return;
|
|
788
|
+
dragControls.start(e);
|
|
789
|
+
}, children: [jsx("span", { className: styles$y.title, children: title }), actions && jsx("div", { className: styles$y.headerActions, children: actions }), jsx("button", { className: styles$y.closeButton, onClick: onClose, "aria-label": "Close modal", type: "button", children: jsx(X, { size: 16 }) })] }), jsx("div", { className: padding ? styles$y.body : styles$y.bodyFlush, children: children })] }) })) }), document.body);
|
|
784
790
|
};
|
|
785
791
|
|
|
786
792
|
var styles$x = {"checkboxLabel":"Checkbox-module_checkboxLabel__4tBVg","checkbox":"Checkbox-module_checkbox__BbJul","checkboxText":"Checkbox-module_checkboxText__oJsc9"};
|