diff --git a/ui/src/assets/styles/components/_dialog.css b/ui/src/assets/styles/components/_dialog.css index 27a5f0b3..009c4524 100644 --- a/ui/src/assets/styles/components/_dialog.css +++ b/ui/src/assets/styles/components/_dialog.css @@ -3,34 +3,61 @@ @apply mx-auto; } +.dialog.maximized { + max-width: 100vw !important; + width: 100vw !important; + height: 100vh !important; + margin: 0 !important; +} + @screen sm { .dialog { @apply max-w-xl; } + .dialog.maximized { + max-width: 100vw !important; + } } @screen md { .dialog { @apply max-w-2xl; } + .dialog.maximized { + max-width: 100vw !important; + } } @screen lg { .dialog { @apply max-w-4xl; } + .dialog.maximized { + max-width: 100vw !important; + } } @screen xl { .dialog { @apply max-w-6xl; } + .dialog.maximized { + max-width: 100vw !important; + } } .dialog-content { @apply p-6 rounded-lg shadow-xl sm:my-16 relative h-full bg-white dark:bg-gray-800; } +.dialog-content.maximized { + border-radius: 0 !important; + max-height: 100vh !important; + height: 100vh !important; + margin: 0 !important; + @apply my-0; +} + .dialog-overlay { transition: all 0.15s ease-in-out; @apply bg-opacity-60 inset-0 fixed z-30 bg-black dark:bg-opacity-80; diff --git a/ui/src/components/ui/Dialog/Dialog.tsx b/ui/src/components/ui/Dialog/Dialog.tsx index e76959d4..4263f048 100644 --- a/ui/src/components/ui/Dialog/Dialog.tsx +++ b/ui/src/components/ui/Dialog/Dialog.tsx @@ -1,9 +1,11 @@ import Modal from 'react-modal' import classNames from 'classnames' import CloseButton from '../CloseButton' +import WindowControls from '../WindowControls' import { motion } from 'framer-motion' import { SCREENS } from '@/utils/tailwind' import useWindowSize from '../hooks/useWindowSize' +import { useState, useCallback } from 'react' import type ReactModal from 'react-modal' import type { MouseEvent } from 'react' @@ -13,10 +15,18 @@ export interface DialogProps extends ReactModal.Props { height?: string | number onClose?: (e: MouseEvent) => void width?: string | number + showWindowControls?: boolean + onMaximize?: () => void + onRestore?: () => void } const Dialog = (props: DialogProps) => { const currentSize = useWindowSize() + const [isMaximized, setIsMaximized] = useState(false) + const [originalDimensions, setOriginalDimensions] = useState<{ + width?: string | number + height?: string | number + }>({}) const { bodyOpenClassName, @@ -33,6 +43,9 @@ const Dialog = (props: DialogProps) => { portalClassName, style, width = 520, + showWindowControls = true, + onMaximize, + onRestore, ...rest } = props @@ -40,39 +53,105 @@ const Dialog = (props: DialogProps) => { onClose?.(e) } + const handleMaximize = useCallback((e: MouseEvent) => { + e.stopPropagation() + if (!isMaximized) { + setOriginalDimensions({ width, height }) + setIsMaximized(true) + onMaximize?.() + } + }, [isMaximized, width, height, onMaximize]) + + const handleRestore = useCallback((e: MouseEvent) => { + e.stopPropagation() + if (isMaximized) { + setIsMaximized(false) + onRestore?.() + } + }, [isMaximized, onRestore]) + const renderCloseButton = ( ) - const contentStyle = { + const renderWindowControls = ( + + ) + + const contentStyle: any = { content: { inset: 'unset', }, + overlay: {}, ...style, } - if (width !== undefined) { - contentStyle.content.width = width + // Set dimensions based on maximized state + const currentWidth = isMaximized ? '100vw' : width + const currentHeight = isMaximized ? '100vh' : height - if ( - typeof currentSize.width !== 'undefined' && - currentSize.width <= SCREENS.sm - ) { - contentStyle.content.width = 'auto' + if (isMaximized) { + // Reset all positioning for fullscreen + contentStyle.content = { + position: 'fixed', + top: '0px', + left: '0px', + right: '0px', + bottom: '0px', + width: '100vw', + height: '100vh', + margin: '0px', + padding: '0px', + border: 'none', + borderRadius: '0px', + inset: '0px', + transform: 'none', + WebkitTransform: 'none', + } + contentStyle.overlay = { + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(0, 0, 0, 0.6)', + zIndex: 1000, + } + } else { + if (currentWidth !== undefined) { + contentStyle.content.width = currentWidth + + if ( + typeof currentSize.width !== 'undefined' && + currentSize.width <= SCREENS.sm + ) { + contentStyle.content.width = 'auto' + } + } + if (currentHeight !== undefined) { + contentStyle.content.height = currentHeight } - } - if (height !== undefined) { - contentStyle.content.height = height } const defaultDialogContentClass = 'dialog-content' - const dialogClass = classNames(defaultDialogContentClass, contentClassName) + const dialogClass = classNames( + defaultDialogContentClass, + isMaximized && 'maximized', + contentClassName + ) return ( { > - {closable && renderCloseButton} + {closable && !showWindowControls && renderCloseButton} + {closable && showWindowControls && renderWindowControls} {children} diff --git a/ui/src/components/ui/Dialog/DialogExample.tsx b/ui/src/components/ui/Dialog/DialogExample.tsx new file mode 100644 index 00000000..8a106bed --- /dev/null +++ b/ui/src/components/ui/Dialog/DialogExample.tsx @@ -0,0 +1,73 @@ +import React, { useState } from 'react' +import Dialog from './Dialog' + +const DialogExample: React.FC = () => { + const [isOpen, setIsOpen] = useState(false) + + const handleOpen = () => setIsOpen(true) + const handleClose = () => setIsOpen(false) + + const handleMaximize = () => { + console.log('Dialog maximized - should cover full screen') + } + + const handleRestore = () => { + console.log('Dialog restored - should return to normal size') + } + + return ( +
+ + + +
+

Dialog with Window Controls

+
+

+ This dialog has maximize and restore buttons in the top-right corner. +

+
    +
  • Click the maximize button (□) to make the dialog fullscreen
  • +
  • Click the restore button (⧉) to return to normal size
  • +
  • Click the close button (×) to close the dialog
  • +
+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris. +

+

+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum + dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +

+

+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium + doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore + veritatis et quasi architecto beatae vitae dicta sunt explicabo. +

+
+
+
+
+
+ ) +} + +export default DialogExample diff --git a/ui/src/components/ui/WindowControls/WindowControls.tsx b/ui/src/components/ui/WindowControls/WindowControls.tsx new file mode 100644 index 00000000..6556a0bd --- /dev/null +++ b/ui/src/components/ui/WindowControls/WindowControls.tsx @@ -0,0 +1,68 @@ +import { forwardRef } from 'react' +import { FaTimes, FaWindowMaximize, FaWindowRestore } from 'react-icons/fa' +import classNames from 'classnames' +import type { CommonProps } from '../@types/common' +import type { MouseEvent } from 'react' + +export interface WindowControlsProps extends CommonProps { + absolute?: boolean + defaultStyle?: boolean + isMaximized?: boolean + onClose?: (e: MouseEvent) => void + onMaximize?: (e: MouseEvent) => void + onRestore?: (e: MouseEvent) => void + showMaximize?: boolean + showRestore?: boolean + showClose?: boolean +} + +const WindowControls = forwardRef((props, ref) => { + const { + absolute, + className, + defaultStyle, + isMaximized, + onClose, + onMaximize, + onRestore, + showMaximize = true, + showRestore = true, + showClose = true, + ...rest + } = props + + const windowControlsAbsoluteClass = 'absolute z-10' + + const windowControlsClass = classNames( + 'window-controls flex items-center gap-2', + defaultStyle && 'window-controls-default', + absolute && windowControlsAbsoluteClass, + className + ) + + const buttonClass = 'close-btn cursor-pointer hover:opacity-70 transition-opacity' + + return ( +
+ {showMaximize && !isMaximized && ( + + + + )} + {showRestore && isMaximized && ( + + + + )} + {showClose && ( + + + + )} +
+ ) +}) + +WindowControls.displayName = 'WindowControls' + +export default WindowControls diff --git a/ui/src/components/ui/WindowControls/index.tsx b/ui/src/components/ui/WindowControls/index.tsx new file mode 100644 index 00000000..7337269d --- /dev/null +++ b/ui/src/components/ui/WindowControls/index.tsx @@ -0,0 +1,2 @@ +export { default } from './WindowControls' +export type { WindowControlsProps } from './WindowControls' diff --git a/ui/src/index.css b/ui/src/index.css index de04fd3e..68f07acd 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -8,8 +8,57 @@ div.dialog-after-open > div.dialog-content { overflow-y: auto !important; max-height: 90vh; } + +/* Maximized dialog specific styles */ +div.dialog-after-open > div.dialog-content.maximized { + max-height: 100vh !important; + height: 100vh !important; + margin: 0 !important; + border-radius: 0 !important; +} + +.dialog.maximized { + max-width: 100vw !important; + width: 100vw !important; + height: 100vh !important; + margin: 0 !important; +} /* React Modal prevent bg scroll */ +/* Window Controls Styles */ +.window-controls { + top: 12px; +} + +.window-controls .close-btn { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 4px; + transition: all 0.2s ease; + color: #666; + font-size: 14px; +} + +.window-controls .close-btn:hover { + background-color: rgba(0, 0, 0, 0.1); + color: #333; +} + +.window-controls .close-btn:last-child:hover { + background-color: #e74c3c; + color: white; +} + +/* Maximized dialog styles */ +.dialog-content.maximized { + border-radius: 0 !important; + max-height: 100vh !important; +} +/* Window Controls Styles */ + /* React Pivot Vert Headers cok uzun olmaması icin */ .dx-pivotgrid tbody.dx-pivotgrid-vertical-headers > tr > td { min-width: fit-content !important;