// // Copyright 2023 DXOS.org // import { createContext } from '@radix-ui/react-context'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import { Primitive } from '@radix-ui/react-primitive'; import { Slot } from '@radix-ui/react-slot'; import React, { type ComponentPropsWithRef, type ForwardRefExoticComponent, type FunctionComponent, forwardRef, } from 'react'; import { useTranslation } from 'react-i18next'; import { osTranslations } from '@dxos/ui-theme'; import { type SlottableProps } from '@dxos/ui-types'; import { useThemeContext } from '../../hooks'; import { ElevationProvider } from '../../primitives'; import { type DialogSize } from '../../theme'; import { type ThemedClassName, composableProps, slottable } from '../../util'; import { IconButton } from '../Button'; import { Column } from '../Column'; // // Root // type DialogRootProps = DialogPrimitive.DialogProps; const DialogRoot: FunctionComponent = (props) => ( ); DialogRoot.displayName = 'Dialog.Root'; // // Trigger // type DialogTriggerProps = DialogPrimitive.DialogTriggerProps; const DialogTrigger = DialogPrimitive.Trigger; // // Portal // type DialogPortalProps = DialogPrimitive.DialogPortalProps; const DialogPortal = DialogPrimitive.Portal; // // Overlay // const DIALOG_OVERLAY_NAME = 'Dialog.Overlay'; type OverlayLayoutContextValue = { inOverlayLayout?: boolean }; const [OverlayLayoutProvider, useOverlayLayoutContext] = createContext( DIALOG_OVERLAY_NAME, {}, ); type DialogOverlayProps = ThemedClassName< DialogPrimitive.DialogOverlayProps & { blockAlign?: 'center' | 'start' | 'end' } >; const DialogOverlay: ForwardRefExoticComponent = forwardRef( ({ classNames, children, blockAlign, ...props }, forwardedRef) => { const { tx } = useThemeContext(); return ( {children} ); }, ); DialogOverlay.displayName = DIALOG_OVERLAY_NAME; // // Content // const DIALOG_CONTENT_NAME = 'Dialog.Content'; type DialogContentProps = ThemedClassName> & { size?: DialogSize; inOverlayLayout?: boolean; }; const DialogContent: ForwardRefExoticComponent = forwardRef( ({ classNames, children, size = 'sm', inOverlayLayout: propsInOverlayLayout, ...props }, forwardedRef) => { const { tx } = useThemeContext(); const { inOverlayLayout } = useOverlayLayoutContext(DIALOG_CONTENT_NAME); return ( {children} ); }, ); DialogContent.displayName = DIALOG_CONTENT_NAME; // // Header // type DialogHeaderProps = SlottableProps; const DialogHeader = slottable(({ children, asChild, ...props }, forwardedRef) => { const { className, ...rest } = composableProps(props); const Comp = asChild ? Slot : Primitive.div; const { tx } = useThemeContext(); return ( {children} ); }); DialogHeader.displayName = 'Dialog.Header'; // // ActionIconButton // type DialogActionIconButtonAction = 'close' | 'delete'; type DialogActionIconButtonProps = { action: DialogActionIconButtonAction; label?: string }; const DIALOG_ACTION_ICONS: Record = { close: 'ph--x--regular', delete: 'ph--trash--regular', }; const DIALOG_ACTION_LABEL_KEYS: Record = { // Preserves the legacy `close-dialog.label` translation key for backward compat. close: 'close-dialog.label', delete: 'toolbar-delete.label', }; const DialogActionIconButton = forwardRef( ({ action, label, ...props }, forwardedRef) => { const { t } = useTranslation(osTranslations); return ( ); }, ); DialogActionIconButton.displayName = 'Dialog.ActionIconButton'; // // Body // type DialogBodyProps = SlottableProps; const DialogBody = slottable(({ children, asChild, ...props }, forwardedRef) => { const { className, ...rest } = composableProps(props); const Comp = asChild ? Slot : Primitive.div; const { tx } = useThemeContext(); return ( {children} ); }); DialogBody.displayName = 'Dialog.Body'; // // Title // type DialogTitleProps = ThemedClassName & { srOnly?: boolean }; const DialogTitle = forwardRef( ({ classNames, srOnly, ...props }, forwardedRef) => { const { tx } = useThemeContext(); return ( ); }, ); DialogTitle.displayName = 'Dialog.Title'; // // Description // type DialogDescriptionProps = ThemedClassName & { srOnly?: boolean }; const DialogDescription = forwardRef( ({ classNames, srOnly, ...props }, forwardedRef) => { const { tx } = useThemeContext(); return ( ); }, ); DialogDescription.displayName = 'Dialog.Description'; // // ActionBar // type DialogActionBarProps = SlottableProps; const DialogActionBar = slottable(({ children, asChild, ...props }, forwardedRef) => { const { className: classNames, ...rest } = composableProps(props); const Comp = asChild ? Slot : Primitive.div; const { tx } = useThemeContext(); return ( {children} ); }); DialogActionBar.displayName = 'Dialog.ActionBar'; // // Close // type DialogCloseProps = DialogPrimitive.DialogCloseProps; const DialogClose = DialogPrimitive.Close; // // Dialog // export const Dialog = { Root: DialogRoot, Trigger: DialogTrigger, Portal: DialogPortal, Overlay: DialogOverlay, Content: DialogContent, Header: DialogHeader, Body: DialogBody, Title: DialogTitle, Description: DialogDescription, ActionBar: DialogActionBar, Close: DialogClose, ActionIconButton: DialogActionIconButton, }; export type { DialogActionBarProps, DialogActionIconButtonAction, DialogActionIconButtonProps, DialogBodyProps, DialogCloseProps, DialogContentProps, DialogDescriptionProps, DialogHeaderProps, DialogOverlayProps, DialogPortalProps, DialogRootProps, DialogTitleProps, DialogTriggerProps, };