Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a024958
Initial commit
ciampo Feb 3, 2026
7c04cc4
Fix import/exports
ciampo Feb 3, 2026
9f0b9de
Remove translation domains
ciampo Feb 3, 2026
0dea466
Fix Storybook
ciampo Feb 3, 2026
34fa546
Fix ESLint error in unit test
ciampo Feb 3, 2026
c1b6961
Use regular Omit (DistributiveOmit is not necessary)
ciampo Feb 3, 2026
c027a10
Use semantic dimension tokens
ciampo Feb 3, 2026
68477af
Update tokens with the correct ones
ciampo Feb 3, 2026
98a1c6f
Format and other lint fixes
ciampo Feb 3, 2026
c02e254
CHANGELOG
ciampo Feb 3, 2026
14cddb1
Dialog: Fix CloseIcon prop customization
ciampo Feb 4, 2026
b2fc440
Dialog: Add useRender pattern to Header and Footer
ciampo Feb 4, 2026
7ebb221
Dialog: Remove redundant ThemedParagraph from stories
ciampo Feb 4, 2026
410f4a7
Dialog: Remove unnecessary jest.setTimeout from tests
ciampo Feb 4, 2026
29edc72
Dialog: rework responsive sizing
ciampo Feb 4, 2026
47ed0d9
Add "full" size option
ciampo Feb 4, 2026
b1d5513
Add defaultOpen and modal props
ciampo Feb 4, 2026
a33b9a7
Dialog: Replace title prop with Dialog.Title component
ciampo Feb 4, 2026
0d231a5
Spacing
ciampo Feb 4, 2026
3478fcf
Dialog: Add validation for empty title content
ciampo Feb 4, 2026
5b6a857
Update paddings
ciampo Feb 4, 2026
fe24d73
Set `medium` as the default size
ciampo Feb 6, 2026
43cd4b3
Use DS token for font weight
ciampo Feb 6, 2026
d6bc64d
Increase min width for larger viewport sizes
ciampo Feb 6, 2026
8dc6ca5
Add box shadow
ciampo Feb 6, 2026
59028cf
Rework Storybook examples
ciampo Feb 6, 2026
8c9dbb4
Add "stretch" size
ciampo Feb 6, 2026
73e084a
better types
ciampo Feb 6, 2026
af8de7f
Remove unnecessary conditional
ciampo Feb 6, 2026
f45d7cb
Test header ref
ciampo Feb 6, 2026
bf0722b
Wrap popup in themeprovider
ciampo Feb 6, 2026
25b7cb7
Fix `modal` prop storybook controls
ciampo Feb 6, 2026
c017d88
Remove unnecessary setTimeout in tests
ciampo Feb 6, 2026
b0aec88
better size type description
ciampo Feb 6, 2026
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
1 change: 1 addition & 0 deletions packages/ui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### New Features

- Add `Dialog` primitive ([#75183](https://github.com/WordPress/gutenberg/pull/75183)).
- Add `Tabs` primitive ([#74652](https://github.com/WordPress/gutenberg/pull/74652)).
- Add `Textarea` primitive ([#74707](https://github.com/WordPress/gutenberg/pull/74707)).

Expand Down
22 changes: 22 additions & 0 deletions packages/ui/src/dialog/action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Dialog as _Dialog } from '@base-ui/react/dialog';
import { forwardRef } from '@wordpress/element';
import { Button } from '../button';
import type { ActionProps } from './types';

/**
* Renders a button that closes the dialog when clicked.
* Accepts all Button component props for styling.
*/
const Action = forwardRef< HTMLButtonElement, ActionProps >(
function DialogAction( { render, ...props }, ref ) {
return (
<_Dialog.Close
ref={ ref }
render={ <Button render={ render } /> }
{ ...props }
/>
);
}
);

export { Action };
32 changes: 32 additions & 0 deletions packages/ui/src/dialog/close-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Dialog as _Dialog } from '@base-ui/react/dialog';
import { forwardRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { close } from '@wordpress/icons';
import { IconButton } from '../icon-button';
import type { CloseIconProps } from './types';

/**
* Renders an icon button that closes the dialog when clicked.
* Provides a default close icon and accessible label.
*/
const CloseIcon = forwardRef< HTMLButtonElement, CloseIconProps >(
function DialogCloseIcon( { icon, label, ...props }, ref ) {
return (
<_Dialog.Close
ref={ ref }
render={
<IconButton
variant="minimal"
size="compact"
tone="neutral"
{ ...props }
icon={ icon ?? close }
label={ label ?? __( 'Close' ) }
/>
}
/>
);
}
);

export { CloseIcon };
113 changes: 113 additions & 0 deletions packages/ui/src/dialog/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
} from '@wordpress/element';

/**
* Whether validation is enabled. This is a build-time constant that allows
* bundlers to tree-shake all validation code in production builds.
*/
const VALIDATION_ENABLED = process.env.NODE_ENV !== 'production';

type DialogValidationContextType = {
registerTitle: ( element: HTMLElement | null ) => void;
};

// Context is only created in development mode.
const DialogValidationContext = VALIDATION_ENABLED
? createContext< DialogValidationContextType | null >( null )
: ( null as unknown as React.Context< DialogValidationContextType | null > );

/**
* Development-only hook to access the dialog validation context.
*/
function useDialogValidationContextDev() {
return useContext( DialogValidationContext );
}

/**
* Production no-op hook.
*/
function useDialogValidationContextProd() {
return null;
}

/**
* Hook to access the dialog validation context.
* Returns null in production or if not within a Dialog.Popup.
*/
export const useDialogValidationContext = VALIDATION_ENABLED
? useDialogValidationContextDev
: useDialogValidationContextProd;

/**
* Development-only provider that tracks whether Dialog.Title is rendered.
*/
function DialogValidationProviderDev( {
children,
}: {
children: React.ReactNode;
} ) {
const titleElementRef = useRef< HTMLElement | null >( null );

const registerTitle = useCallback( ( element: HTMLElement | null ) => {
titleElementRef.current = element;
}, [] );

const contextValue = useMemo(
() => ( { registerTitle } ),
[ registerTitle ]
);

// Validate that Dialog.Title is rendered with non-empty text content
useEffect( () => {
// useLayoutEffect in Title runs before this useEffect,
// so titleElementRef should already be set if Title is present
const titleElement = titleElementRef.current;

if ( ! titleElement ) {
throw new Error(
'Dialog: Missing <Dialog.Title>. ' +
'For accessibility, every dialog requires a title. ' +
'If needed, the title can be visually hidden but must not be omitted.'
);
}

const textContent = titleElement.textContent?.trim();
if ( ! textContent ) {
throw new Error(
'Dialog: <Dialog.Title> cannot be empty. ' +
'Provide meaningful text content for the dialog title.'
);
}
}, [] );

return (
<DialogValidationContext.Provider value={ contextValue }>
{ children }
</DialogValidationContext.Provider>
);
}

/**
* Production no-op provider that just renders children.
*/
function DialogValidationProviderProd( {
children,
}: {
children: React.ReactNode;
} ) {
return <>{ children }</>;
}

/**
* Provider component that validates Dialog.Title presence in development mode.
* In production, this component is a no-op and just renders children.
*/
export const DialogValidationProvider = VALIDATION_ENABLED
? DialogValidationProviderDev
: DialogValidationProviderProd;
26 changes: 26 additions & 0 deletions packages/ui/src/dialog/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { mergeProps, useRender } from '@base-ui/react';
import clsx from 'clsx';
import { forwardRef } from '@wordpress/element';
import styles from './style.module.css';
import type { FooterProps } from './types';

/**
* Renders the footer section of the dialog, typically containing
* action buttons.
*/
const Footer = forwardRef< HTMLDivElement, FooterProps >( function DialogFooter(
{ className, render, ...props },
ref
) {
const element = useRender( {
render,
ref,
props: mergeProps< 'div' >( props, {
className: clsx( styles.footer, className ),
} ),
} );

return element;
} );

export { Footer };
26 changes: 26 additions & 0 deletions packages/ui/src/dialog/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { mergeProps, useRender } from '@base-ui/react';
import clsx from 'clsx';
import { forwardRef } from '@wordpress/element';
import styles from './style.module.css';
import type { HeaderProps } from './types';

/**
* Renders the header section of the dialog, typically containing
* the heading and close button.
*/
const Header = forwardRef< HTMLDivElement, HeaderProps >( function DialogHeader(
{ className, render, ...props },
ref
) {
const element = useRender( {
render,
ref,
props: mergeProps< 'div' >( props, {
className: clsx( styles.header, className ),
} ),
} );

return element;
} );

export { Header };
10 changes: 10 additions & 0 deletions packages/ui/src/dialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Action } from './action';
import { CloseIcon } from './close-icon';
import { Footer } from './footer';
import { Header } from './header';
import { Popup } from './popup';
import { Root } from './root';
import { Title } from './title';
import { Trigger } from './trigger';

export { Action, CloseIcon, Footer, Header, Popup, Root, Title, Trigger };
46 changes: 46 additions & 0 deletions packages/ui/src/dialog/popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Dialog as _Dialog } from '@base-ui/react/dialog';
import clsx from 'clsx';
import { forwardRef } from '@wordpress/element';
import {
type ThemeProvider as ThemeProviderType,
privateApis as themePrivateApis,
} from '@wordpress/theme';
import { unlock } from '../lock-unlock';
import { DialogValidationProvider } from './context';
import styles from './style.module.css';
import type { PopupProps } from './types';

const ThemeProvider: typeof ThemeProviderType =
unlock( themePrivateApis ).ThemeProvider;

/**
* Renders the dialog popup element that contains the dialog content.
* Uses a portal to render outside the DOM hierarchy.
*/
const Popup = forwardRef< HTMLDivElement, PopupProps >( function DialogPopup(
{ className, size = 'medium', children, ...props },
ref
) {
return (
<_Dialog.Portal>
<_Dialog.Backdrop className={ styles.backdrop } />
<ThemeProvider>
<_Dialog.Popup
ref={ ref }
className={ clsx(
styles.popup,
className,
styles[ `is-${ size }` ]
) }
{ ...props }
>
<DialogValidationProvider>
{ children }
</DialogValidationProvider>
</_Dialog.Popup>
</ThemeProvider>
</_Dialog.Portal>
);
} );

export { Popup };
14 changes: 14 additions & 0 deletions packages/ui/src/dialog/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Dialog as _Dialog } from '@base-ui/react/dialog';
import type { RootProps } from './types';

/**
* Groups the dialog trigger and popup.
*
* `Dialog` is a collection of React components that combine to render
* an ARIA-compliant dialog pattern.
*/
function Root( props: RootProps ) {
return <_Dialog.Root { ...props } />;
}

export { Root };
Loading
Loading