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;
|
||||
}
|
||||
|
||||
.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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
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;
|
||||
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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue