Dialog Komponentinin Ekranı kaplaması özelliği
This commit is contained in:
parent
12da788288
commit
fd66381e5e
6 changed files with 318 additions and 15 deletions
|
|
@ -3,34 +3,61 @@
|
||||||
@apply mx-auto;
|
@apply mx-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog.maximized {
|
||||||
|
max-width: 100vw !important;
|
||||||
|
width: 100vw !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
@screen sm {
|
@screen sm {
|
||||||
.dialog {
|
.dialog {
|
||||||
@apply max-w-xl;
|
@apply max-w-xl;
|
||||||
}
|
}
|
||||||
|
.dialog.maximized {
|
||||||
|
max-width: 100vw !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@screen md {
|
@screen md {
|
||||||
.dialog {
|
.dialog {
|
||||||
@apply max-w-2xl;
|
@apply max-w-2xl;
|
||||||
}
|
}
|
||||||
|
.dialog.maximized {
|
||||||
|
max-width: 100vw !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@screen lg {
|
@screen lg {
|
||||||
.dialog {
|
.dialog {
|
||||||
@apply max-w-4xl;
|
@apply max-w-4xl;
|
||||||
}
|
}
|
||||||
|
.dialog.maximized {
|
||||||
|
max-width: 100vw !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@screen xl {
|
@screen xl {
|
||||||
.dialog {
|
.dialog {
|
||||||
@apply max-w-6xl;
|
@apply max-w-6xl;
|
||||||
}
|
}
|
||||||
|
.dialog.maximized {
|
||||||
|
max-width: 100vw !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-content {
|
.dialog-content {
|
||||||
@apply p-6 rounded-lg shadow-xl sm:my-16 relative h-full bg-white dark:bg-gray-800;
|
@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 {
|
.dialog-overlay {
|
||||||
transition: all 0.15s ease-in-out;
|
transition: all 0.15s ease-in-out;
|
||||||
@apply bg-opacity-60 inset-0 fixed z-30 bg-black dark:bg-opacity-80;
|
@apply bg-opacity-60 inset-0 fixed z-30 bg-black dark:bg-opacity-80;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import Modal from 'react-modal'
|
import Modal from 'react-modal'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import CloseButton from '../CloseButton'
|
import CloseButton from '../CloseButton'
|
||||||
|
import WindowControls from '../WindowControls'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import { SCREENS } from '@/utils/tailwind'
|
import { SCREENS } from '@/utils/tailwind'
|
||||||
import useWindowSize from '../hooks/useWindowSize'
|
import useWindowSize from '../hooks/useWindowSize'
|
||||||
|
import { useState, useCallback } from 'react'
|
||||||
import type ReactModal from 'react-modal'
|
import type ReactModal from 'react-modal'
|
||||||
import type { MouseEvent } from 'react'
|
import type { MouseEvent } from 'react'
|
||||||
|
|
||||||
|
|
@ -13,10 +15,18 @@ export interface DialogProps extends ReactModal.Props {
|
||||||
height?: string | number
|
height?: string | number
|
||||||
onClose?: (e: MouseEvent<HTMLSpanElement>) => void
|
onClose?: (e: MouseEvent<HTMLSpanElement>) => void
|
||||||
width?: string | number
|
width?: string | number
|
||||||
|
showWindowControls?: boolean
|
||||||
|
onMaximize?: () => void
|
||||||
|
onRestore?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dialog = (props: DialogProps) => {
|
const Dialog = (props: DialogProps) => {
|
||||||
const currentSize = useWindowSize()
|
const currentSize = useWindowSize()
|
||||||
|
const [isMaximized, setIsMaximized] = useState(false)
|
||||||
|
const [originalDimensions, setOriginalDimensions] = useState<{
|
||||||
|
width?: string | number
|
||||||
|
height?: string | number
|
||||||
|
}>({})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
bodyOpenClassName,
|
bodyOpenClassName,
|
||||||
|
|
@ -33,6 +43,9 @@ const Dialog = (props: DialogProps) => {
|
||||||
portalClassName,
|
portalClassName,
|
||||||
style,
|
style,
|
||||||
width = 520,
|
width = 520,
|
||||||
|
showWindowControls = true,
|
||||||
|
onMaximize,
|
||||||
|
onRestore,
|
||||||
...rest
|
...rest
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
|
|
@ -40,19 +53,80 @@ const Dialog = (props: DialogProps) => {
|
||||||
onClose?.(e)
|
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 = (
|
const renderCloseButton = (
|
||||||
<CloseButton absolute className="ltr:right-6 rtl:left-6" onClick={onCloseClick} />
|
<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: {
|
content: {
|
||||||
inset: 'unset',
|
inset: 'unset',
|
||||||
},
|
},
|
||||||
|
overlay: {},
|
||||||
...style,
|
...style,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width !== undefined) {
|
// Set dimensions based on maximized state
|
||||||
contentStyle.content.width = width
|
const currentWidth = isMaximized ? '100vw' : width
|
||||||
|
const currentHeight = isMaximized ? '100vh' : height
|
||||||
|
|
||||||
|
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 (
|
if (
|
||||||
typeof currentSize.width !== 'undefined' &&
|
typeof currentSize.width !== 'undefined' &&
|
||||||
|
|
@ -61,18 +135,23 @@ const Dialog = (props: DialogProps) => {
|
||||||
contentStyle.content.width = 'auto'
|
contentStyle.content.width = 'auto'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (height !== undefined) {
|
if (currentHeight !== undefined) {
|
||||||
contentStyle.content.height = height
|
contentStyle.content.height = currentHeight
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultDialogContentClass = 'dialog-content'
|
const defaultDialogContentClass = 'dialog-content'
|
||||||
|
|
||||||
const dialogClass = classNames(defaultDialogContentClass, contentClassName)
|
const dialogClass = classNames(
|
||||||
|
defaultDialogContentClass,
|
||||||
|
isMaximized && 'maximized',
|
||||||
|
contentClassName
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className={{
|
className={{
|
||||||
base: classNames('dialog', className as string),
|
base: classNames('dialog', className as string, isMaximized && 'maximized'),
|
||||||
afterOpen: 'dialog-after-open',
|
afterOpen: 'dialog-after-open',
|
||||||
beforeClose: 'dialog-before-close',
|
beforeClose: 'dialog-before-close',
|
||||||
}}
|
}}
|
||||||
|
|
@ -92,12 +171,17 @@ const Dialog = (props: DialogProps) => {
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
className={dialogClass}
|
className={dialogClass}
|
||||||
initial={{ transform: 'scale(0.9)' }}
|
initial={{ transform: isMaximized ? 'scale(1)' : 'scale(0.9)' }}
|
||||||
animate={{
|
animate={{
|
||||||
transform: isOpen ? 'scale(1)' : 'scale(0.9)',
|
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}
|
{children}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
||||||
73
ui/src/components/ui/Dialog/DialogExample.tsx
Normal file
73
ui/src/components/ui/Dialog/DialogExample.tsx
Normal 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
|
||||||
68
ui/src/components/ui/WindowControls/WindowControls.tsx
Normal file
68
ui/src/components/ui/WindowControls/WindowControls.tsx
Normal 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
|
||||||
2
ui/src/components/ui/WindowControls/index.tsx
Normal file
2
ui/src/components/ui/WindowControls/index.tsx
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default } from './WindowControls'
|
||||||
|
export type { WindowControlsProps } from './WindowControls'
|
||||||
|
|
@ -8,8 +8,57 @@ div.dialog-after-open > div.dialog-content {
|
||||||
overflow-y: auto !important;
|
overflow-y: auto !important;
|
||||||
max-height: 90vh;
|
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 */
|
/* 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 */
|
/* React Pivot Vert Headers cok uzun olmaması icin */
|
||||||
.dx-pivotgrid tbody.dx-pivotgrid-vertical-headers > tr > td {
|
.dx-pivotgrid tbody.dx-pivotgrid-vertical-headers > tr > td {
|
||||||
min-width: fit-content !important;
|
min-width: fit-content !important;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue