Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/components/ui/AlertDialog/AlertDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import AlertDialogCancel from './fragments/AlertDialogCancel';
import AlertDialogAction from './fragments/AlertDialogAction';
import AlertDialogTitle from './fragments/AlertDialogTitle';
import AlertDialogDescription from './fragments/AlertDialogDescription';
// Explicit extension to satisfy ESM linting/resolution
export type {
AlertDialogRootProps as __fix_types_1
} from './types.ts';

const AlertDialog = () => {
console.warn('Direct usage of AlertDialog is not supported. Please use AlertDialog.Root, AlertDialog.Content, etc. instead.');
Expand Down
16 changes: 14 additions & 2 deletions src/components/ui/AlertDialog/contexts/AlertDialogContext.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
'use client';
import { createContext } from 'react';

type AlertDialogContextType = {
export type AlertDialogContextType = {
rootClass: string;
isOpen: boolean;
setIsOpen: (open: boolean) => void;
titleId?: string;
descriptionId?: string;
setTitleId: (id: string | undefined) => void;
setDescriptionId: (id: string | undefined) => void;
};

export const AlertDialogContext = createContext<AlertDialogContextType>({
rootClass: ''
rootClass: '',
isOpen: false,
setIsOpen: () => {},
titleId: undefined,
descriptionId: undefined,
setTitleId: () => {},
setDescriptionId: () => {}
});
24 changes: 21 additions & 3 deletions src/components/ui/AlertDialog/fragments/AlertDialogContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,30 @@ type DialogPrimitiveContentProps = React.ComponentPropsWithoutRef<typeof DialogP

export type AlertDialogContentProps = DialogPrimitiveContentProps & {
className?: string;
asChild?: boolean;
forceMount?: boolean;
};

const AlertDialogContent = forwardRef<AlertDialogContentElement, AlertDialogContentProps>(({ children, className = '', ...props }, ref) => {
const { rootClass } = useContext(AlertDialogContext);
const AlertDialogContent = forwardRef<AlertDialogContentElement, AlertDialogContentProps>(({
children,
className = '',
asChild = false,
forceMount = false,
...props
}, ref) => {
const { rootClass, titleId, descriptionId } = useContext(AlertDialogContext);
return (
<DialogPrimitive.Content ref={ref} className={clsx(`${rootClass}-content`, className)} {...props}>
<DialogPrimitive.Content
ref={ref}
className={clsx(`${rootClass}-content`, className)}
asChild={asChild}
forceMount={forceMount}
role="alertdialog"
aria-modal={true}
aria-labelledby={titleId}
aria-describedby={descriptionId}
{...props}
>
{children}
</DialogPrimitive.Content>
);
Expand Down
55 changes: 50 additions & 5 deletions src/components/ui/AlertDialog/fragments/AlertDialogDescription.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,63 @@
'use client';

import React, { forwardRef, useContext } from 'react';
import React, { forwardRef, useContext, useEffect, useRef } from 'react';
import { AlertDialogContext } from '../contexts/AlertDialogContext';
import Floater from '~/core/primitives/Floater';

import Primitive from '~/core/primitives/Primitive';

type AlertDialogDescriptionElement = React.ElementRef<typeof Primitive.p>;
type PrimitivePProps = React.ComponentPropsWithoutRef<typeof Primitive.p>;

export type AlertDialogDescriptionProps = PrimitivePProps & { className?: string };
export type AlertDialogDescriptionProps = PrimitivePProps & {
className?: string;
asChild?: boolean;
};

const AlertDialogDescription = forwardRef<AlertDialogDescriptionElement, AlertDialogDescriptionProps>(({ children, className = '', ...props }, ref) => {
const { rootClass } = useContext(AlertDialogContext);
return <Primitive.p ref={ref} className={`${rootClass}-description ${className}`} {...props}>{children}</Primitive.p>;
const AlertDialogDescription = forwardRef<AlertDialogDescriptionElement, AlertDialogDescriptionProps>(({
children,
className = '',
asChild = false,
id,
...props
}, ref) => {
const { rootClass, setDescriptionId, descriptionId: currentDescriptionId } = useContext(AlertDialogContext);
const generatedId = Floater.useId();
const descriptionId = id ?? generatedId;
const descriptionIdRef = useRef(descriptionId);
const latestCurrentDescriptionIdRef = useRef(currentDescriptionId);

// Update the latest current description ID ref whenever it changes
useEffect(() => {
latestCurrentDescriptionIdRef.current = currentDescriptionId;
}, [currentDescriptionId]);

useEffect(() => {
descriptionIdRef.current = descriptionId;
if (descriptionId) {
setDescriptionId(descriptionId);
}

// Cleanup: clear the descriptionId when this component unmounts
// Only clear if the stored id still matches to avoid clobbering other instances
return () => {
if (latestCurrentDescriptionIdRef.current === descriptionIdRef.current) {
setDescriptionId(undefined);
}
};
}, [descriptionId, setDescriptionId]);

return (
<Primitive.p
ref={ref}
id={descriptionId}
className={`${rootClass}-description ${className}`}
asChild={asChild}
{...props}
>
{children}
</Primitive.p>
);
});

AlertDialogDescription.displayName = 'AlertDialogDescription';
Expand Down
23 changes: 20 additions & 3 deletions src/components/ui/AlertDialog/fragments/AlertDialogOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,31 @@ import DialogPrimitive from '~/core/primitives/Dialog';
type AlertDialogOverlayElement = React.ElementRef<typeof DialogPrimitive.Overlay>;
type DialogPrimitiveOverlayProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>;

type AlertDialogOverlayProps = DialogPrimitiveOverlayProps & {
export type AlertDialogOverlayProps = DialogPrimitiveOverlayProps & {
className?: string;
asChild?: boolean;
forceMount?: boolean;
children?: React.ReactNode;
};

const AlertDialogOverlay = forwardRef<AlertDialogOverlayElement, AlertDialogOverlayProps>(({ className = '', ...props }, ref) => {
const AlertDialogOverlay = forwardRef<AlertDialogOverlayElement, AlertDialogOverlayProps>(({
className = '',
asChild = false,
forceMount = false,
children,
...props
}, ref) => {
const { rootClass } = useContext(AlertDialogContext);
return (
<DialogPrimitive.Overlay ref={ref} className={clsx(`${rootClass}-overlay`, className)} {...props}></DialogPrimitive.Overlay>
<DialogPrimitive.Overlay
ref={ref}
className={clsx(`${rootClass}-overlay`, className)}
asChild={asChild}
forceMount={forceMount}
{...props}
>
{children}
</DialogPrimitive.Overlay>
);
});

Expand Down
20 changes: 14 additions & 6 deletions src/components/ui/AlertDialog/fragments/AlertDialogPortal.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
'use client';
import React, { forwardRef } from 'react';
import React from 'react';
import DialogPrimitive from '~/core/primitives/Dialog';

type AlertDialogPortalElement = React.ElementRef<'div'>;
type DialogPrimitivePortalProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Portal>;

export type AlertDialogPortalProps = DialogPrimitivePortalProps;
export type AlertDialogPortalProps = DialogPrimitivePortalProps & {
container?: Element | null;
forceMount?: boolean;
keepMounted?: boolean;
};

const AlertDialogPortal = forwardRef<AlertDialogPortalElement, AlertDialogPortalProps>(({ children, ...props }, _ref) => {
const AlertDialogPortal = ({
children,
...props
}: AlertDialogPortalProps) => {
return (
<DialogPrimitive.Portal {...props}>
<DialogPrimitive.Portal
{...props}
>
{children}
</DialogPrimitive.Portal>
);
});
};

AlertDialogPortal.displayName = 'AlertDialogPortal';

Expand Down
35 changes: 30 additions & 5 deletions src/components/ui/AlertDialog/fragments/AlertDialogRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,52 @@
'use client';
import React, { forwardRef } from 'react';
import React, { forwardRef, useState } from 'react';
import { customClassSwitcher } from '~/core';
import { AlertDialogContext } from '../contexts/AlertDialogContext';
import { clsx } from 'clsx';
import { useControllableState } from '~/core/hooks/useControllableState';

import DialogPrimitive from '~/core/primitives/Dialog';

type AlertDialogRootElement = React.ElementRef<typeof DialogPrimitive.Root>;
type DialogPrimitiveRootProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root>;

export type AlertDialogRootProps = DialogPrimitiveRootProps & {
export type AlertDialogRootProps = Omit<DialogPrimitiveRootProps, 'open' | 'onOpenChange'> & {
customRootClass?: string;
className?: string;
defaultOpen?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
};

const COMPONENT_NAME = 'AlertDialog';

const AlertDialogRoot = forwardRef<AlertDialogRootElement, AlertDialogRootProps>(({ children, className = '', customRootClass = '', open = false, ...props }, ref) => {
const AlertDialogRoot = forwardRef<AlertDialogRootElement, AlertDialogRootProps>(({
children,
className = '',
customRootClass = '',
defaultOpen = false,
open,
onOpenChange,
...props
}, ref) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);

const contextProps = { rootClass };
const [isOpen, setIsOpen] = useControllableState(open, defaultOpen, onOpenChange);
const [titleId, setTitleId] = useState<string | undefined>(undefined);
const [descriptionId, setDescriptionId] = useState<string | undefined>(undefined);

const contextProps = {
rootClass,
isOpen,
setIsOpen,
titleId,
descriptionId,
setTitleId,
setDescriptionId
};

return (
<DialogPrimitive.Root open={open} className={clsx(rootClass, className)} {...props}>
<DialogPrimitive.Root open={isOpen} onOpenChange={setIsOpen} className={clsx(rootClass, className)} {...props}>
<AlertDialogContext.Provider value={contextProps}>
<div ref={ref} className={clsx(rootClass, className)}>
{children}
Expand Down
51 changes: 47 additions & 4 deletions src/components/ui/AlertDialog/fragments/AlertDialogTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client';

import React, { forwardRef, useContext } from 'react';
import React, { forwardRef, useContext, useEffect, useRef } from 'react';
import { AlertDialogContext } from '../contexts/AlertDialogContext';
import Floater from '~/core/primitives/Floater';

import Primitive from '~/core/primitives/Primitive';

Expand All @@ -10,11 +11,53 @@ type PrimitiveH2Props = React.ComponentPropsWithoutRef<typeof Primitive.h2>;

export type AlertDialogTitleProps = PrimitiveH2Props & {
className?: string;
asChild?: boolean;
};

const AlertDialogTitle = forwardRef<AlertDialogTitleElement, AlertDialogTitleProps>(({ children, className = '', ...props }, ref) => {
const { rootClass } = useContext(AlertDialogContext);
return <Primitive.h2 ref={ref} className={`${rootClass}-title ${className}`} {...props}>{children}</Primitive.h2>;
const AlertDialogTitle = forwardRef<AlertDialogTitleElement, AlertDialogTitleProps>(({
children,
className = '',
asChild = false,
id,
...props
}, ref) => {
const { rootClass, setTitleId, titleId: currentTitleId } = useContext(AlertDialogContext);
const generatedId = Floater.useId();
const titleId = id ?? generatedId;
const titleIdRef = useRef(titleId);
const latestCurrentTitleIdRef = useRef(currentTitleId);

// Update the latest current title ID ref whenever it changes
useEffect(() => {
latestCurrentTitleIdRef.current = currentTitleId;
}, [currentTitleId]);

useEffect(() => {
titleIdRef.current = titleId;
if (titleId) {
setTitleId(titleId);
}

// Cleanup: clear the titleId when this component unmounts
// Only clear if the stored id still matches to avoid clobbering other instances
return () => {
if (latestCurrentTitleIdRef.current === titleIdRef.current) {
setTitleId(undefined);
}
};
}, [titleId, setTitleId]);

return (
<Primitive.h2
ref={ref}
id={titleId}
className={`${rootClass}-title ${className}`}
asChild={asChild}
{...props}
>
{children}
</Primitive.h2>
);
});

AlertDialogTitle.displayName = 'AlertDialogTitle';
Expand Down
25 changes: 21 additions & 4 deletions src/components/ui/AlertDialog/fragments/AlertDialogTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,33 @@ type DialogPrimitiveTriggerProps = React.ComponentPropsWithoutRef<typeof DialogP

export type AlertDialogTriggerProps = DialogPrimitiveTriggerProps & {
className?: string;
disabled?: boolean;
};

const AlertDialogTrigger = forwardRef<AlertDialogTriggerElement, AlertDialogTriggerProps>(({ children, asChild, className = '', ...props }, ref) => {
const { rootClass } = useContext(AlertDialogContext);
const AlertDialogTrigger = forwardRef<AlertDialogTriggerElement, AlertDialogTriggerProps>(({
children,
asChild,
className = '',
disabled = false,
...props
}, ref) => {
const { rootClass, isOpen } = useContext(AlertDialogContext);

const dataState = isOpen ? 'open' : 'closed';
const dataDisabled = disabled ? '' : undefined;

return (
<DialogPrimitive.Trigger ref={ref} className={clsx(`${rootClass}-trigger`, className)} asChild={asChild} {...props}>
<DialogPrimitive.Trigger
ref={ref}
className={clsx(`${rootClass}-trigger`, className)}
asChild={asChild}
disabled={disabled}
data-state={dataState}
data-disabled={dataDisabled}
{...props}
>
{children}
</DialogPrimitive.Trigger>

);
});

Expand Down
Loading
Loading