@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/index.js CHANGED
@@ -741,47 +741,29 @@ const EmptyState = ({ icon, title, message, action, size = 'default', }) => {
741
741
  return (jsxRuntime.jsxs("div", { className: wrapperClass, children: [icon && jsxRuntime.jsx("div", { className: styles$z.icon, children: icon }), title && jsxRuntime.jsx("h3", { className: styles$z.title, children: title }), jsxRuntime.jsx("p", { className: styles$z.message, children: message }), action && jsxRuntime.jsx("div", { className: styles$z.action, children: action })] }));
742
742
  };
743
743
 
744
- 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"};
744
+ 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"};
745
745
 
746
- /**
747
- * Modal Component
748
- *
749
- * @component
750
- * @description
751
- * An accessible overlay dialog with a backdrop, title header, optional
752
- * header actions, and a flexible body. Renders into a React portal on
753
- * `document.body`, animates in/out with Framer Motion, closes on Escape
754
- * key or backdrop click, and clicks inside the dialog don't propagate
755
- * to the backdrop.
756
- *
757
- * @example
758
- * // Basic usage
759
- * <Modal open={isOpen} title="Confirm" onClose={() => setIsOpen(false)}>
760
- * <p>Are you sure?</p>
761
- * </Modal>
762
- *
763
- * @example
764
- * // With header actions and wide size
765
- * <Modal
766
- * open={isOpen}
767
- * title="Edit profile"
768
- * size="wide"
769
- * actions={<button onClick={save}>Save</button>}
770
- * onClose={handleClose}
771
- * >
772
- * <ProfileForm />
773
- * </Modal>
774
- *
775
- * @example
776
- * // Flush body (no padding) for editors and full-bleed content
777
- * <Modal open={isOpen} title="Document" padding={false} onClose={handleClose}>
778
- * <DocEditor content={content} onSave={save} />
779
- * </Modal>
780
- *
781
- * @param {ModalProps} props - The props for the Modal component
782
- * @returns {React.ReactPortal | null} A portal rendering the modal, or null on the server
783
- */
784
- const Modal = ({ open, title, onClose, children, size = 'default', actions, padding = true, }) => {
746
+ const MOBILE_BREAKPOINT = '(max-width: 640px)';
747
+ const useIsMobile = () => {
748
+ const [isMobile, setIsMobile] = React.useState(() => {
749
+ if (typeof window === 'undefined')
750
+ return false;
751
+ return window.matchMedia(MOBILE_BREAKPOINT).matches;
752
+ });
753
+ React.useEffect(() => {
754
+ if (typeof window === 'undefined')
755
+ return;
756
+ const mq = window.matchMedia(MOBILE_BREAKPOINT);
757
+ const onChange = (e) => setIsMobile(e.matches);
758
+ mq.addEventListener('change', onChange);
759
+ return () => mq.removeEventListener('change', onChange);
760
+ }, []);
761
+ return isMobile;
762
+ };
763
+ const Modal = ({ open, title, onClose, children, size = 'default', actions, padding = true, mobileVariant = 'sheet', draggable = true, }) => {
764
+ const isMobile = useIsMobile();
765
+ const dragControls = framerMotion.useDragControls();
766
+ const headerRef = React.useRef(null);
785
767
  React.useEffect(() => {
786
768
  if (!open)
787
769
  return;
@@ -794,14 +776,38 @@ const Modal = ({ open, title, onClose, children, size = 'default', actions, padd
794
776
  }, [open, onClose]);
795
777
  if (typeof document === 'undefined')
796
778
  return null;
779
+ const isSheet = mobileVariant === 'sheet' && isMobile;
780
+ const isDraggable = isSheet && draggable;
781
+ const backdropClass = [styles$y.backdrop, isSheet && styles$y.backdropSheet]
782
+ .filter(Boolean)
783
+ .join(' ');
797
784
  const dialogClass = [
798
785
  styles$y.dialog,
799
786
  size === 'compact' && styles$y.dialogCompact,
800
787
  size === 'wide' && styles$y.dialogWide,
788
+ isSheet && styles$y.dialogSheet,
801
789
  ]
802
790
  .filter(Boolean)
803
791
  .join(' ');
804
- return reactDom.createPortal(jsxRuntime.jsx(framerMotion.AnimatePresence, { children: open && (jsxRuntime.jsx(framerMotion.motion.div, { className: styles$y.backdrop, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, onClick: onClose, "aria-hidden": "true", children: jsxRuntime.jsxs(framerMotion.motion.div, { className: dialogClass, role: "dialog", "aria-modal": "true", "aria-label": title, initial: { opacity: 0, scale: 0.96, y: 8 }, animate: { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.96, y: 8 }, transition: { duration: 0.15, ease: 'easeOut' }, onClick: (e) => e.stopPropagation(), children: [jsxRuntime.jsxs("div", { className: styles$y.header, children: [jsxRuntime.jsx("span", { className: styles$y.title, children: title }), actions && jsxRuntime.jsx("div", { className: styles$y.headerActions, children: actions }), jsxRuntime.jsx("button", { className: styles$y.closeButton, onClick: onClose, "aria-label": "Close modal", type: "button", children: jsxRuntime.jsx(lucideReact.X, { size: 16 }) })] }), jsxRuntime.jsx("div", { className: padding ? styles$y.body : styles$y.bodyFlush, children: children })] }) })) }), document.body);
792
+ const enteringAnimation = isSheet
793
+ ? { initial: { y: '100%' }, animate: { y: 0 }, exit: { y: '100%' } }
794
+ : {
795
+ initial: { opacity: 0, scale: 0.96, y: 8 },
796
+ animate: { opacity: 1, scale: 1, y: 0 },
797
+ exit: { opacity: 0, scale: 0.96, y: 8 },
798
+ };
799
+ return reactDom.createPortal(jsxRuntime.jsx(framerMotion.AnimatePresence, { children: open && (jsxRuntime.jsx(framerMotion.motion.div, { className: backdropClass, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, onClick: onClose, "aria-hidden": "true", children: jsxRuntime.jsxs(framerMotion.motion.div, { className: dialogClass, role: "dialog", "aria-modal": "true", "aria-label": title, ...enteringAnimation, transition: isSheet
800
+ ? { type: 'spring', stiffness: 320, damping: 32 }
801
+ : { 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) => {
802
+ if (info.offset.y > 80)
803
+ onClose();
804
+ }, children: [isSheet && jsxRuntime.jsx("div", { className: styles$y.grabBar }), jsxRuntime.jsxs("div", { ref: headerRef, className: styles$y.header, onPointerDown: (e) => {
805
+ if (!isDraggable)
806
+ return;
807
+ if (e.target.closest('button'))
808
+ return;
809
+ dragControls.start(e);
810
+ }, children: [jsxRuntime.jsx("span", { className: styles$y.title, children: title }), actions && jsxRuntime.jsx("div", { className: styles$y.headerActions, children: actions }), jsxRuntime.jsx("button", { className: styles$y.closeButton, onClick: onClose, "aria-label": "Close modal", type: "button", children: jsxRuntime.jsx(lucideReact.X, { size: 16 }) })] }), jsxRuntime.jsx("div", { className: padding ? styles$y.body : styles$y.bodyFlush, children: children })] }) })) }), document.body);
805
811
  };
806
812
 
807
813
  var styles$x = {"checkboxLabel":"Checkbox-module_checkboxLabel__4tBVg","checkbox":"Checkbox-module_checkbox__BbJul","checkboxText":"Checkbox-module_checkboxText__oJsc9"};