mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-27 15:24:56 +00:00
refactor: split dialog components into modules
This commit is contained in:
@@ -1,24 +1,16 @@
|
||||
// TODO: Split this file (268 LOC) into smaller organisms (<150 LOC each)
|
||||
'use client'
|
||||
|
||||
import { forwardRef, ReactNode } from 'react'
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogActions,
|
||||
Button,
|
||||
IconButton,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import CloseIcon from '@mui/icons-material/Close'
|
||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber'
|
||||
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'
|
||||
import { Dialog } from '@mui/material'
|
||||
|
||||
import {
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from './alert/Content'
|
||||
import { AlertDialogAction, AlertDialogCancel, AlertDialogFooter } from './alert/Actions'
|
||||
|
||||
// AlertDialog Root
|
||||
interface AlertDialogProps {
|
||||
open: boolean
|
||||
onClose?: () => void
|
||||
@@ -52,7 +44,6 @@ const AlertDialog = forwardRef<HTMLDivElement, AlertDialogProps>(
|
||||
)
|
||||
AlertDialog.displayName = 'AlertDialog'
|
||||
|
||||
// AlertDialogTrigger - element that opens dialog
|
||||
interface AlertDialogTriggerProps {
|
||||
children: ReactNode
|
||||
onClick?: () => void
|
||||
@@ -70,200 +61,14 @@ const AlertDialogTrigger = forwardRef<HTMLSpanElement, AlertDialogTriggerProps>(
|
||||
)
|
||||
AlertDialogTrigger.displayName = 'AlertDialogTrigger'
|
||||
|
||||
// AlertDialogContent - wrapper for dialog content
|
||||
interface AlertDialogContentProps {
|
||||
children: ReactNode
|
||||
showCloseButton?: boolean
|
||||
onClose?: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AlertDialogContent = forwardRef<HTMLDivElement, AlertDialogContentProps>(
|
||||
({ children, showCloseButton = false, onClose, className, ...props }, ref) => {
|
||||
return (
|
||||
<>
|
||||
{showCloseButton && onClose && (
|
||||
<IconButton
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: 'text.secondary',
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogContent.displayName = 'AlertDialogContent'
|
||||
|
||||
// AlertDialogHeader
|
||||
interface AlertDialogHeaderProps {
|
||||
children: ReactNode
|
||||
icon?: 'warning' | 'error' | 'info' | 'success' | ReactNode
|
||||
}
|
||||
|
||||
const AlertDialogHeader = forwardRef<HTMLDivElement, AlertDialogHeaderProps>(
|
||||
({ children, icon, ...props }, ref) => {
|
||||
const getIcon = () => {
|
||||
if (!icon) return null
|
||||
if (typeof icon !== 'string') return icon
|
||||
|
||||
const iconMap = {
|
||||
warning: <WarningAmberIcon color="warning" sx={{ fontSize: 40 }} />,
|
||||
error: <ErrorOutlineIcon color="error" sx={{ fontSize: 40 }} />,
|
||||
info: <InfoOutlinedIcon color="info" sx={{ fontSize: 40 }} />,
|
||||
success: <CheckCircleOutlineIcon color="success" sx={{ fontSize: 40 }} />,
|
||||
}
|
||||
return iconMap[icon]
|
||||
}
|
||||
|
||||
const iconElement = getIcon()
|
||||
|
||||
return (
|
||||
<DialogTitle
|
||||
ref={ref}
|
||||
id="alert-dialog-title"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: iconElement ? 'center' : 'flex-start',
|
||||
gap: 1,
|
||||
pt: 3,
|
||||
pb: 0,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{iconElement}
|
||||
{children}
|
||||
</DialogTitle>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogHeader.displayName = 'AlertDialogHeader'
|
||||
|
||||
// AlertDialogTitle
|
||||
interface AlertDialogTitleProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AlertDialogTitle = forwardRef<HTMLHeadingElement, AlertDialogTitleProps>(
|
||||
({ children, className, ...props }, ref) => {
|
||||
return (
|
||||
<Typography
|
||||
ref={ref}
|
||||
variant="h6"
|
||||
component="span"
|
||||
className={className}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
textAlign: 'inherit',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogTitle.displayName = 'AlertDialogTitle'
|
||||
|
||||
// AlertDialogDescription
|
||||
interface AlertDialogDescriptionProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AlertDialogDescription = forwardRef<HTMLDivElement, AlertDialogDescriptionProps>(
|
||||
({ children, className, ...props }, ref) => {
|
||||
return (
|
||||
<DialogContent ref={ref} className={className} sx={{ pt: 2 }} {...props}>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{children}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogDescription.displayName = 'AlertDialogDescription'
|
||||
|
||||
// AlertDialogFooter
|
||||
interface AlertDialogFooterProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const AlertDialogFooter = forwardRef<HTMLDivElement, AlertDialogFooterProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<DialogActions ref={ref} sx={{ p: 2, pt: 0 }} {...props}>
|
||||
{children}
|
||||
</DialogActions>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogFooter.displayName = 'AlertDialogFooter'
|
||||
|
||||
// AlertDialogCancel
|
||||
interface AlertDialogCancelProps {
|
||||
children?: ReactNode
|
||||
onClick?: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AlertDialogCancel = forwardRef<HTMLButtonElement, AlertDialogCancelProps>(
|
||||
({ children = 'Cancel', onClick, className, ...props }, ref) => {
|
||||
return (
|
||||
<Button ref={ref} onClick={onClick} color="inherit" className={className} {...props}>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogCancel.displayName = 'AlertDialogCancel'
|
||||
|
||||
// AlertDialogAction
|
||||
interface AlertDialogActionProps {
|
||||
children?: ReactNode
|
||||
onClick?: () => void
|
||||
color?: 'primary' | 'error' | 'warning' | 'success' | 'info'
|
||||
variant?: 'text' | 'outlined' | 'contained'
|
||||
autoFocus?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AlertDialogAction = forwardRef<HTMLButtonElement, AlertDialogActionProps>(
|
||||
({ children = 'Confirm', onClick, color = 'primary', variant = 'contained', autoFocus = true, className, ...props }, ref) => {
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
color={color}
|
||||
variant={variant}
|
||||
autoFocus={autoFocus}
|
||||
className={className}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogAction.displayName = 'AlertDialogAction'
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogCancel,
|
||||
AlertDialogAction,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
'use client'
|
||||
|
||||
export * from './command'
|
||||
export * from './Command/Palette'
|
||||
export * from './Command/Results'
|
||||
export * from './Command/useCommandState'
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client'
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { CommandDialog } from '../command'
|
||||
|
||||
interface CommandPaletteProps {
|
||||
children: ReactNode
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
placeholder?: string
|
||||
search: string
|
||||
onSearchChange: (value: string) => void
|
||||
}
|
||||
|
||||
const CommandPalette = ({
|
||||
children,
|
||||
open,
|
||||
onOpenChange,
|
||||
placeholder = 'Type a command or search...',
|
||||
search,
|
||||
onSearchChange,
|
||||
}: CommandPaletteProps) => {
|
||||
return (
|
||||
<CommandDialog open={open} onClose={() => onOpenChange(false)}>
|
||||
<CommandDialog.Input
|
||||
value={search}
|
||||
onChange={onSearchChange}
|
||||
placeholder={placeholder}
|
||||
autoFocus
|
||||
/>
|
||||
{children}
|
||||
</CommandDialog>
|
||||
)
|
||||
}
|
||||
|
||||
export { CommandPalette }
|
||||
export type { CommandPaletteProps }
|
||||
@@ -0,0 +1,56 @@
|
||||
'use client'
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
import { Box, Typography } from '@mui/material'
|
||||
|
||||
import { CommandDialog } from '../command'
|
||||
import type { CommandGroup, CommandItem } from '../command/command.types'
|
||||
|
||||
interface CommandResultsProps {
|
||||
groups: CommandGroup[]
|
||||
emptyMessage?: ReactNode
|
||||
onSelect?: (item: CommandItem) => void
|
||||
}
|
||||
|
||||
const CommandResults = ({ groups, emptyMessage = 'No results found.', onSelect }: CommandResultsProps) => {
|
||||
const hasResults = groups.some((group) => group.items.length > 0)
|
||||
|
||||
return (
|
||||
<CommandDialog.List>
|
||||
{hasResults ? (
|
||||
groups.map((group, index) => (
|
||||
<CommandDialog.Group key={group.heading ?? index} heading={group.heading}>
|
||||
{group.items.map((item) => (
|
||||
<CommandDialog.Item
|
||||
key={item.id}
|
||||
icon={item.icon}
|
||||
shortcut={item.shortcut}
|
||||
disabled={item.disabled}
|
||||
onSelect={() => {
|
||||
item.onSelect?.()
|
||||
onSelect?.(item)
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
<Typography variant="body2" component="span">
|
||||
{item.label}
|
||||
</Typography>
|
||||
{item.description && (
|
||||
<Typography variant="caption" color="text.secondary" component="span">
|
||||
{item.description}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</CommandDialog.Item>
|
||||
))}
|
||||
</CommandDialog.Group>
|
||||
))
|
||||
) : (
|
||||
<CommandDialog.Empty>{emptyMessage}</CommandDialog.Empty>
|
||||
)}
|
||||
</CommandDialog.List>
|
||||
)
|
||||
}
|
||||
|
||||
export { CommandResults }
|
||||
export type { CommandResultsProps }
|
||||
@@ -0,0 +1,59 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import type { CommandGroup, CommandItem } from '../command/command.types'
|
||||
|
||||
interface UseCommandStateOptions {
|
||||
groups: CommandGroup[]
|
||||
defaultOpen?: boolean
|
||||
}
|
||||
|
||||
interface UseCommandStateResult {
|
||||
filteredGroups: CommandGroup[]
|
||||
open: boolean
|
||||
search: string
|
||||
setOpen: (open: boolean) => void
|
||||
setSearch: (value: string) => void
|
||||
close: () => void
|
||||
}
|
||||
|
||||
const filterItems = (items: CommandItem[], query: string) => {
|
||||
if (!query) return items
|
||||
const lowered = query.toLowerCase()
|
||||
return items.filter((item) => {
|
||||
const labelMatch = item.label.toLowerCase().includes(lowered)
|
||||
const keywordsMatch = item.keywords?.some((keyword) => keyword.toLowerCase().includes(lowered))
|
||||
const descriptionMatch = item.description?.toLowerCase().includes(lowered)
|
||||
|
||||
return labelMatch || keywordsMatch || descriptionMatch
|
||||
})
|
||||
}
|
||||
|
||||
const useCommandState = ({ groups, defaultOpen = false }: UseCommandStateOptions): UseCommandStateResult => {
|
||||
const [open, setOpen] = useState(defaultOpen)
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const filteredGroups = useMemo(() => {
|
||||
if (!search) return groups
|
||||
|
||||
return groups
|
||||
.map((group) => ({
|
||||
...group,
|
||||
items: filterItems(group.items, search),
|
||||
}))
|
||||
.filter((group) => group.items.length > 0)
|
||||
}, [groups, search])
|
||||
|
||||
return {
|
||||
filteredGroups,
|
||||
open,
|
||||
search,
|
||||
setOpen,
|
||||
setSearch,
|
||||
close: () => setOpen(false),
|
||||
}
|
||||
}
|
||||
|
||||
export { useCommandState }
|
||||
export type { UseCommandStateOptions, UseCommandStateResult }
|
||||
@@ -1,255 +1,4 @@
|
||||
// TODO: Split this file (254 LOC) into smaller organisms (<150 LOC each)
|
||||
'use client'
|
||||
|
||||
import { forwardRef, ReactNode } from 'react'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerProps,
|
||||
Box,
|
||||
IconButton,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import CloseIcon from '@mui/icons-material/Close'
|
||||
|
||||
// Sheet (Drawer) Root Component
|
||||
interface SheetProps extends Omit<DrawerProps, 'anchor'> {
|
||||
children: ReactNode
|
||||
side?: 'left' | 'right' | 'top' | 'bottom'
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}
|
||||
|
||||
const Sheet = forwardRef<HTMLDivElement, SheetProps>(
|
||||
({ children, side = 'right', open, onClose, onOpenChange, ...props }, ref) => {
|
||||
const handleClose = (_event: React.SyntheticEvent | object, reason: 'backdropClick' | 'escapeKeyDown') => {
|
||||
if (typeof onClose === 'function') {
|
||||
onClose(_event, reason)
|
||||
}
|
||||
onOpenChange?.(false)
|
||||
}
|
||||
return (
|
||||
<Drawer
|
||||
ref={ref}
|
||||
anchor={side}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
)
|
||||
Sheet.displayName = 'Sheet'
|
||||
|
||||
// SheetTrigger - returns children with onClick handler
|
||||
interface SheetTriggerProps {
|
||||
children: ReactNode
|
||||
onClick?: () => void
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const SheetTrigger = forwardRef<HTMLButtonElement, SheetTriggerProps>(
|
||||
({ children, onClick, asChild, ...props }, ref) => {
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
component="span"
|
||||
onClick={onClick}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetTrigger.displayName = 'SheetTrigger'
|
||||
|
||||
// SheetContent
|
||||
interface SheetContentProps {
|
||||
children: ReactNode
|
||||
side?: 'left' | 'right' | 'top' | 'bottom'
|
||||
onClose?: () => void
|
||||
showCloseButton?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SheetContent = forwardRef<HTMLDivElement, SheetContentProps>(
|
||||
({ children, side = 'right', onClose, showCloseButton = true, ...props }, ref) => {
|
||||
const isHorizontal = side === 'left' || side === 'right'
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
sx={{
|
||||
width: isHorizontal ? 320 : '100%',
|
||||
height: isHorizontal ? '100%' : 'auto',
|
||||
maxHeight: !isHorizontal ? '80vh' : undefined,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
p: 3,
|
||||
bgcolor: 'background.paper',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{showCloseButton && (
|
||||
<IconButton
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
color: 'text.secondary',
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetContent.displayName = 'SheetContent'
|
||||
|
||||
// SheetHeader
|
||||
interface SheetHeaderProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SheetHeader = forwardRef<HTMLDivElement, SheetHeaderProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 0.5,
|
||||
pb: 2,
|
||||
textAlign: 'left',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetHeader.displayName = 'SheetHeader'
|
||||
|
||||
// SheetFooter
|
||||
interface SheetFooterProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SheetFooter = forwardRef<HTMLDivElement, SheetFooterProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column-reverse',
|
||||
gap: 1,
|
||||
pt: 2,
|
||||
mt: 'auto',
|
||||
sm: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetFooter.displayName = 'SheetFooter'
|
||||
|
||||
// SheetTitle
|
||||
interface SheetTitleProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SheetTitle = forwardRef<HTMLHeadingElement, SheetTitleProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<Typography
|
||||
ref={ref}
|
||||
variant="h6"
|
||||
component="h2"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetTitle.displayName = 'SheetTitle'
|
||||
|
||||
// SheetDescription
|
||||
interface SheetDescriptionProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SheetDescription = forwardRef<HTMLParagraphElement, SheetDescriptionProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<Typography
|
||||
ref={ref}
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetDescription.displayName = 'SheetDescription'
|
||||
|
||||
// SheetClose - button to close sheet
|
||||
interface SheetCloseProps {
|
||||
children: ReactNode
|
||||
onClick?: () => void
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const SheetClose = forwardRef<HTMLButtonElement, SheetCloseProps>(
|
||||
({ children, onClick, ...props }, ref) => {
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
component="span"
|
||||
onClick={onClick}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetClose.displayName = 'SheetClose'
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
SheetClose,
|
||||
}
|
||||
export { Sheet, SheetClose, SheetContent, SheetTrigger } from './Sheet/Drawer'
|
||||
export { SheetDescription, SheetFooter, SheetHeader, SheetTitle } from './Sheet/Header'
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
'use client'
|
||||
|
||||
import { forwardRef, ReactNode, SyntheticEvent } from 'react'
|
||||
import { Box, Drawer, DrawerProps, IconButton } from '@mui/material'
|
||||
import CloseIcon from '@mui/icons-material/Close'
|
||||
|
||||
interface SheetProps extends Omit<DrawerProps, 'anchor'> {
|
||||
children: ReactNode
|
||||
side?: 'left' | 'right' | 'top' | 'bottom'
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}
|
||||
|
||||
const Sheet = forwardRef<HTMLDivElement, SheetProps>(
|
||||
({ children, side = 'right', open, onClose, onOpenChange, ...props }, ref) => {
|
||||
const handleClose = (_event: SyntheticEvent | object, reason: 'backdropClick' | 'escapeKeyDown') => {
|
||||
if (typeof onClose === 'function') {
|
||||
onClose(_event, reason)
|
||||
}
|
||||
onOpenChange?.(false)
|
||||
}
|
||||
return (
|
||||
<Drawer
|
||||
ref={ref}
|
||||
anchor={side}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
)
|
||||
Sheet.displayName = 'Sheet'
|
||||
|
||||
interface SheetTriggerProps {
|
||||
children: ReactNode
|
||||
onClick?: () => void
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const SheetTrigger = forwardRef<HTMLButtonElement, SheetTriggerProps>(
|
||||
({ children, onClick, asChild, ...props }, ref) => {
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
component="span"
|
||||
onClick={onClick}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetTrigger.displayName = 'SheetTrigger'
|
||||
|
||||
interface SheetContentProps {
|
||||
children: ReactNode
|
||||
side?: 'left' | 'right' | 'top' | 'bottom'
|
||||
onClose?: () => void
|
||||
showCloseButton?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SheetContent = forwardRef<HTMLDivElement, SheetContentProps>(
|
||||
({ children, side = 'right', onClose, showCloseButton = true, ...props }, ref) => {
|
||||
const isHorizontal = side === 'left' || side === 'right'
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
sx={{
|
||||
width: isHorizontal ? 320 : '100%',
|
||||
height: isHorizontal ? '100%' : 'auto',
|
||||
maxHeight: !isHorizontal ? '80vh' : undefined,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
p: 3,
|
||||
bgcolor: 'background.paper',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{showCloseButton && (
|
||||
<IconButton
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
color: 'text.secondary',
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetContent.displayName = 'SheetContent'
|
||||
|
||||
interface SheetCloseProps {
|
||||
children: ReactNode
|
||||
onClick?: () => void
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const SheetClose = forwardRef<HTMLButtonElement, SheetCloseProps>(
|
||||
({ children, onClick, ...props }, ref) => {
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
component="span"
|
||||
onClick={onClick}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetClose.displayName = 'SheetClose'
|
||||
|
||||
export { Sheet, SheetClose, SheetContent, SheetTrigger }
|
||||
@@ -0,0 +1,108 @@
|
||||
'use client'
|
||||
|
||||
import { forwardRef, ReactNode } from 'react'
|
||||
import { Box, Typography } from '@mui/material'
|
||||
|
||||
interface SheetHeaderProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SheetHeader = forwardRef<HTMLDivElement, SheetHeaderProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 0.5,
|
||||
pb: 2,
|
||||
textAlign: 'left',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetHeader.displayName = 'SheetHeader'
|
||||
|
||||
interface SheetFooterProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SheetFooter = forwardRef<HTMLDivElement, SheetFooterProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column-reverse',
|
||||
gap: 1,
|
||||
pt: 2,
|
||||
mt: 'auto',
|
||||
sm: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetFooter.displayName = 'SheetFooter'
|
||||
|
||||
interface SheetTitleProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SheetTitle = forwardRef<HTMLHeadingElement, SheetTitleProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<Typography
|
||||
ref={ref}
|
||||
variant="h6"
|
||||
component="h2"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetTitle.displayName = 'SheetTitle'
|
||||
|
||||
interface SheetDescriptionProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SheetDescription = forwardRef<HTMLParagraphElement, SheetDescriptionProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<Typography
|
||||
ref={ref}
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
)
|
||||
SheetDescription.displayName = 'SheetDescription'
|
||||
|
||||
export { SheetDescription, SheetFooter, SheetHeader, SheetTitle }
|
||||
@@ -0,0 +1,69 @@
|
||||
'use client'
|
||||
|
||||
import { forwardRef, ReactNode } from 'react'
|
||||
import { Button, DialogActions } from '@mui/material'
|
||||
|
||||
interface AlertDialogFooterProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const AlertDialogFooter = forwardRef<HTMLDivElement, AlertDialogFooterProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<DialogActions ref={ref} sx={{ p: 2, pt: 0 }} {...props}>
|
||||
{children}
|
||||
</DialogActions>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogFooter.displayName = 'AlertDialogFooter'
|
||||
|
||||
interface AlertDialogCancelProps {
|
||||
children?: ReactNode
|
||||
onClick?: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AlertDialogCancel = forwardRef<HTMLButtonElement, AlertDialogCancelProps>(
|
||||
({ children = 'Cancel', onClick, className, ...props }, ref) => {
|
||||
return (
|
||||
<Button ref={ref} onClick={onClick} color="inherit" className={className} {...props}>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogCancel.displayName = 'AlertDialogCancel'
|
||||
|
||||
interface AlertDialogActionProps {
|
||||
children?: ReactNode
|
||||
onClick?: () => void
|
||||
color?: 'primary' | 'error' | 'warning' | 'success' | 'info'
|
||||
variant?: 'text' | 'outlined' | 'contained'
|
||||
autoFocus?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AlertDialogAction = forwardRef<HTMLButtonElement, AlertDialogActionProps>(
|
||||
(
|
||||
{ children = 'Confirm', onClick, color = 'primary', variant = 'contained', autoFocus = true, className, ...props },
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
color={color}
|
||||
variant={variant}
|
||||
autoFocus={autoFocus}
|
||||
className={className}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogAction.displayName = 'AlertDialogAction'
|
||||
|
||||
export { AlertDialogAction, AlertDialogCancel, AlertDialogFooter }
|
||||
@@ -0,0 +1,141 @@
|
||||
'use client'
|
||||
|
||||
import { forwardRef, ReactNode } from 'react'
|
||||
import {
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'
|
||||
import CloseIcon from '@mui/icons-material/Close'
|
||||
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'
|
||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber'
|
||||
|
||||
interface AlertDialogContentProps {
|
||||
children: ReactNode
|
||||
showCloseButton?: boolean
|
||||
onClose?: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AlertDialogContent = forwardRef<HTMLDivElement, AlertDialogContentProps>(
|
||||
({ children, showCloseButton = false, onClose, className, ...props }, ref) => {
|
||||
return (
|
||||
<>
|
||||
{showCloseButton && onClose && (
|
||||
<IconButton
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: 'text.secondary',
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogContent.displayName = 'AlertDialogContent'
|
||||
|
||||
interface AlertDialogHeaderProps {
|
||||
children: ReactNode
|
||||
icon?: 'warning' | 'error' | 'info' | 'success' | ReactNode
|
||||
}
|
||||
|
||||
const AlertDialogHeader = forwardRef<HTMLDivElement, AlertDialogHeaderProps>(
|
||||
({ children, icon, ...props }, ref) => {
|
||||
const getIcon = () => {
|
||||
if (!icon) return null
|
||||
if (typeof icon !== 'string') return icon
|
||||
|
||||
const iconMap = {
|
||||
warning: <WarningAmberIcon color="warning" sx={{ fontSize: 40 }} />,
|
||||
error: <ErrorOutlineIcon color="error" sx={{ fontSize: 40 }} />,
|
||||
info: <InfoOutlinedIcon color="info" sx={{ fontSize: 40 }} />,
|
||||
success: <CheckCircleOutlineIcon color="success" sx={{ fontSize: 40 }} />,
|
||||
}
|
||||
return iconMap[icon]
|
||||
}
|
||||
|
||||
const iconElement = getIcon()
|
||||
|
||||
return (
|
||||
<DialogTitle
|
||||
ref={ref}
|
||||
id="alert-dialog-title"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: iconElement ? 'center' : 'flex-start',
|
||||
gap: 1,
|
||||
pt: 3,
|
||||
pb: 0,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{iconElement}
|
||||
{children}
|
||||
</DialogTitle>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogHeader.displayName = 'AlertDialogHeader'
|
||||
|
||||
interface AlertDialogTitleProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AlertDialogTitle = forwardRef<HTMLHeadingElement, AlertDialogTitleProps>(
|
||||
({ children, className, ...props }, ref) => {
|
||||
return (
|
||||
<Typography
|
||||
ref={ref}
|
||||
variant="h6"
|
||||
component="span"
|
||||
className={className}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
textAlign: 'inherit',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogTitle.displayName = 'AlertDialogTitle'
|
||||
|
||||
interface AlertDialogDescriptionProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AlertDialogDescription = forwardRef<HTMLDivElement, AlertDialogDescriptionProps>(
|
||||
({ children, className, ...props }, ref) => {
|
||||
return (
|
||||
<DialogContent ref={ref} className={className} sx={{ pt: 2 }} {...props}>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{children}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
)
|
||||
}
|
||||
)
|
||||
AlertDialogDescription.displayName = 'AlertDialogDescription'
|
||||
|
||||
export {
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
}
|
||||
Reference in New Issue
Block a user