Dialog Komponentinin Ekranı kaplaması özelliği

This commit is contained in:
Sedat Öztürk 2025-08-20 22:35:44 +03:00
parent 12da788288
commit fd66381e5e
6 changed files with 318 additions and 15 deletions

View file

@ -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;

View file

@ -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<HTMLSpanElement>) => 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<HTMLSpanElement>) => {
e.stopPropagation()
if (!isMaximized) {
setOriginalDimensions({ width, height })
setIsMaximized(true)
onMaximize?.()
}
}, [isMaximized, width, height, onMaximize])
const handleRestore = useCallback((e: MouseEvent<HTMLSpanElement>) => {
e.stopPropagation()
if (isMaximized) {
setIsMaximized(false)
onRestore?.()
}
}, [isMaximized, onRestore])
const renderCloseButton = (
<CloseButton absolute className="ltr:right-6 rtl:left-6" onClick={onCloseClick} />
)
const contentStyle = {
const renderWindowControls = (
<WindowControls
absolute
className="ltr:right-6 rtl:left-6"
isMaximized={isMaximized}
onClose={onCloseClick}
onMaximize={handleMaximize}
onRestore={handleRestore}
/>
)
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 (
<Modal
className={{
base: classNames('dialog', className as string),
base: classNames('dialog', className as string, isMaximized && 'maximized'),
afterOpen: 'dialog-after-open',
beforeClose: 'dialog-before-close',
}}
@ -92,12 +171,17 @@ const Dialog = (props: DialogProps) => {
>
<motion.div
className={dialogClass}
initial={{ transform: 'scale(0.9)' }}
initial={{ transform: isMaximized ? 'scale(1)' : 'scale(0.9)' }}
animate={{
transform: isOpen ? 'scale(1)' : 'scale(0.9)',
}}
style={{
width: isMaximized ? '100vw' : 'auto',
height: isMaximized ? '100vh' : 'auto',
}}
>
{closable && renderCloseButton}
{closable && !showWindowControls && renderCloseButton}
{closable && showWindowControls && renderWindowControls}
{children}
</motion.div>
</Modal>

View file

@ -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 (
<div className="p-4">
<button
onClick={handleOpen}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Open Dialog with Window Controls
</button>
<Dialog
isOpen={isOpen}
onRequestClose={handleClose}
onClose={handleClose}
onMaximize={handleMaximize}
onRestore={handleRestore}
width={600}
height={400}
showWindowControls={true}
contentClassName="p-6"
>
<div className="h-full flex flex-col">
<h2 className="text-xl font-bold mb-4">Dialog with Window Controls</h2>
<div className="flex-1 overflow-auto">
<p className="mb-4">
This dialog has maximize and restore buttons in the top-right corner.
</p>
<ul className="list-disc ml-6 space-y-2 mb-6">
<li>Click the maximize button () to make the dialog fullscreen</li>
<li>Click the restore button () to return to normal size</li>
<li>Click the close button (×) to close the dialog</li>
</ul>
<div className="space-y-4">
<p className="text-gray-600">
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.
</p>
<p className="text-gray-600">
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.
</p>
<p className="text-gray-600">
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.
</p>
</div>
</div>
</div>
</Dialog>
</div>
)
}
export default DialogExample

View file

@ -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<HTMLSpanElement>) => void
onMaximize?: (e: MouseEvent<HTMLSpanElement>) => void
onRestore?: (e: MouseEvent<HTMLSpanElement>) => void
showMaximize?: boolean
showRestore?: boolean
showClose?: boolean
}
const WindowControls = forwardRef<HTMLDivElement, WindowControlsProps>((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 (
<div className={windowControlsClass} {...rest} ref={ref}>
{showMaximize && !isMaximized && (
<span className={buttonClass} role="button" onClick={onMaximize} title="Maximize">
<FaWindowMaximize />
</span>
)}
{showRestore && isMaximized && (
<span className={buttonClass} role="button" onClick={onRestore} title="Restore">
<FaWindowRestore />
</span>
)}
{showClose && (
<span className={buttonClass} role="button" onClick={onClose} title="Close">
<FaTimes />
</span>
)}
</div>
)
})
WindowControls.displayName = 'WindowControls'
export default WindowControls

View file

@ -0,0 +1,2 @@
export { default } from './WindowControls'
export type { WindowControlsProps } from './WindowControls'

View file

@ -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;