Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 108 additions & 18 deletions lib/components/display/modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,149 @@ import {
DialogBackdrop,
} from "@headlessui/react";
import gwMerge from "../../gw-merge";
import { WIDTH_OPTIONS } from "../../utils/sizes";
import { useRef, useState, useEffect } from "react";

const WIDTH_OPTIONS = {
xs: "max-w-xs",
sm: "max-w-sm",
md: "max-w-md",
lg: "max-w-lg",
xl: "max-w-xl",
"2xl": "max-w-2xl",
"3xl": "max-w-3xl",
"4xl": "max-w-4xl",
"5xl": "max-w-5xl",
};

function Modal({
opened = false,
onClose,
dialogTitle,
dialogDescription,
buttons,
footer,
size = "2xl",
className,
children,
}) {
// Check if the size exists
const widthClass = WIDTH_OPTIONS[size] ?? WIDTH_OPTIONS["2xl"];
if (!WIDTH_OPTIONS[size]) {
console.error(
`Invalid size prop: ${size}. Must be one of: 'sx', 'sm', 'md', 'lg', 'xl', '2xl', '4xl', 'full'`
);
console.warn(
`Defaulting to '2xl' for size of <Modal modalTitle="${dialogTitle}" .../>`
`Modal: invalid size "${size}" passed. Falling back to "2xl".`
);
size = "2xl";
}

const panelRef = useRef(null);
// Modal Defaults
const [dimensions, setDimensions] = useState({ width: 600, height: 400 });
const [position, setPosition] = useState({ x: 0, y: 0 });
const [dragging, setDragging] = useState(false);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [resizing, setResizing] = useState(false);

const handleMouseDown = (e) => {
const rect = panelRef.current.getBoundingClientRect();
setOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top });
setDragging(true);
};

const handleMouseMove = (e) => {
if (dragging) {
setPosition({
x: e.clientX - offset.x,
y: e.clientY - offset.y,
});
} else if (resizing) {
const rect = panelRef.current.getBoundingClientRect();
setDimensions({
width: Math.max(300, e.clientX - rect.left),
height: Math.max(200, e.clientY - rect.top),
});
}
};

const handleMouseUp = () => {
setDragging(false);
setResizing(false);
};

useEffect(() => {
if (dragging || resizing) {
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
}
return () => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
};
}, [dragging, resizing, offset]);

return (
<Dialog
open={opened}
onClose={onClose}
onMouseDown={handleMouseDown}
className={gwMerge("gw-relative", "gw-z-50", className)}
>
<DialogBackdrop className="gw-fixed gw-inset-0 gw-bg-black/30" />
<div className="gw-fixed gw-inset-0 gw-w-screen gw-overflow-auto gw-p-4">
<div className="gw-flex gw-min-h-full gw-items-center gw-justify-center">
<div
className="gw-relative"
style={{ top: position.y, left: position.x }}
>
<DialogPanel
ref={panelRef}
className={gwMerge(
WIDTH_OPTIONS[size],
"gw-space-y-4",
widthClass,
"gw-border",
"gw-rounded-lg",
"gw-shadow-lg",
"gw-bg-white",
"gw-p-12"
"gw-relative"
)}
style={
dimensions.width && dimensions.height
? {
width: dimensions.width,
height: dimensions.height,
maxWidth: "80vw",
maxHeight: "90vh",
}
: undefined
}
>
{dialogTitle && (
<DialogTitle className="gw-font-bold gw-text-center">
<div className="gw-cursor-move gw-p-4 gw-bg-gray-100 gw-rounded">
<DialogTitle className="gw-font-bold gw-text-center gw-select-none">
{dialogTitle}
</DialogTitle>
)}
</div>
{dialogDescription && (
<Description>{dialogDescription}</Description>
<Description className="gw-px-5 gw-my-2">
{dialogDescription}
</Description>
)}
{children}
{buttons}
<div className="gw-overflow-auto gw-h-full gw-px-5 gw-bg-white dark:gw-bg-slate-700 dark:gw-text-white">
{children}
</div>
<div className="gw-flex gw-items-center gw-bg-gray-100 gw-rounded-b">
<div className="gw-px-4 gw-py-4">{footer}</div>
<div
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
setResizing(true);
}}
className="gw-w-6 gw-h-6 gw-cursor-se-resize gw-ml-auto gw-mt-8"
style={{
backgroundImage:
"linear-gradient(135deg, transparent 45%, #4b5563 45%, #4b5563 55%, transparent 55%)," +
"linear-gradient(135deg, transparent 65%, #4b5563 65%, #4b5563 75%, transparent 75%)",
backgroundRepeat: "no-repeat",
backgroundPosition: "bottom right",
backgroundSize: "100% 100%",
}}
title="Resize"
/>
</div>
</DialogPanel>
</div>
</div>
Expand Down
13 changes: 0 additions & 13 deletions lib/utils/sizes.js

This file was deleted.

8 changes: 4 additions & 4 deletions src/app-pages/documentation/display/modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ const componentProps = [
desc: "Description of the modal dialog.",
},
{
name: "buttons",
name: "footer",
type: "ReactNode",
default: "null",
desc: "Button components to display at the bottom of the modal.",
desc: "Footer components to display at the bottom of the modal.",
},
{
name: "children",
Expand Down Expand Up @@ -92,7 +92,7 @@ function ModalDocs() {
dialogDescription="1-7 Day Quantitative Precipitation Forecast"
size="2xl"
staticWidth={true}
buttons={
footer={
<div className="gw-flex gw-justify-end gw-gap-4">
<Button className="gw-w-full" onClick={refreshImage}>
Refresh Image
Expand Down Expand Up @@ -166,7 +166,7 @@ function Example() {
dialogDescription="1-7 Day Quantitative Precipitation Forecast"
size="2xl"
staticWidth={true}
buttons={
footer={
<div className="gw-flex gw-justify-end gw-gap-4">
<Button
className="gw-w-full"
Expand Down