Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Components: Add experimental ConfirmDialog #34153

Merged
merged 92 commits into from
Nov 25, 2021
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
72cd96b
Proof of Concept
fullofcaffeine Aug 19, 2021
335053a
Improve UX to more closely resembles a native confirm
fullofcaffeine Aug 20, 2021
f4a3396
Remove unused import and outdated comment
fullofcaffeine Aug 20, 2021
1b2f104
Improve the story
fullofcaffeine Aug 20, 2021
d5e8d73
Spelling fix
fullofcaffeine Aug 20, 2021
f7250b6
Remove debug code
fullofcaffeine Aug 20, 2021
6062648
Fix invalid props warning
fullofcaffeine Aug 21, 2021
a01225f
Empty dependency array to avoid listener from being re-added at each …
fullofcaffeine Aug 24, 2021
a4d2453
Remove unused style
fullofcaffeine Aug 24, 2021
18e0231
wip
fullofcaffeine Aug 25, 2021
2391e35
Address code review suggestions partially, and refactor to use the ex…
fullofcaffeine Aug 25, 2021
a878802
Improve component by allowing it to be used without the `confirm` hel…
fullofcaffeine Aug 25, 2021
b09651b
Update README
fullofcaffeine Aug 26, 2021
c221e9d
Update confirm call in post-visibility
fullofcaffeine Aug 26, 2021
cac2732
Remove role prop as it is not explicitely used at the moment
fullofcaffeine Aug 26, 2021
0942707
Add basic tests
fullofcaffeine Aug 26, 2021
7d629b8
Test that the Confirm closes when clicking the overlay
fullofcaffeine Aug 27, 2021
12f0b18
Test that the Confirm closes when pressing Escape
fullofcaffeine Aug 27, 2021
f99c357
Merge branch 'trunk' into replace/confirm-et-al
fullofcaffeine Sep 9, 2021
f746119
Fix types and forwardRef, and adapt to the new functional Modal compo…
fullofcaffeine Sep 9, 2021
5fd979f
Merge branch 'trunk' into replace/confirm-et-al
fullofcaffeine Sep 9, 2021
13bf86a
Fix tests
fullofcaffeine Sep 9, 2021
d68508f
Simple naive inline declarative usage without a Context
fullofcaffeine Sep 9, 2021
56ea610
Fix z-index to show it above all elements, and forwardRef to Modal's …
fullofcaffeine Sep 9, 2021
6245385
Improve callback handler name
fullofcaffeine Sep 10, 2021
814fc6f
Make `onCancel` optional
fullofcaffeine Sep 10, 2021
2b47a46
Add styles.ts
fullofcaffeine Sep 10, 2021
bf9c062
Add more tests
fullofcaffeine Sep 11, 2021
37befa2
Handle confirm on enter and add corresponding test
fullofcaffeine Sep 11, 2021
31b88e2
Rename to `ConfirmDialog`
fullofcaffeine Sep 13, 2021
2cd86e9
Redo stories, improve types and `selfClose` handling
fullofcaffeine Sep 14, 2021
21600b0
Fix tests after renaming to `ConfirmDialog`
fullofcaffeine Sep 14, 2021
b21c175
Update the post-visibility example after renaming to `ConfirmDialog`
fullofcaffeine Sep 14, 2021
922c103
Forward all other props to the underlying `Modal`
fullofcaffeine Sep 14, 2021
9c931c6
Refactor tests to test controlled and uncontrolled scenarios
fullofcaffeine Sep 15, 2021
5494585
Mark it as experimental, linter autofixes and snapshot updates
fullofcaffeine Sep 15, 2021
2c43770
Add a proper README
fullofcaffeine Sep 15, 2021
fdab69f
Fix typo
fullofcaffeine Sep 15, 2021
835c2e6
Fix grammar and add section about multiple instances to the README
fullofcaffeine Sep 15, 2021
61f7c1a
Fix stories
fullofcaffeine Sep 15, 2021
c1cf65a
Add message text knob to stories
fullofcaffeine Sep 16, 2021
20e2d23
Reset components/package.json
fullofcaffeine Sep 16, 2021
33e97e1
Merge branch 'trunk' into replace/confirm-et-al
fullofcaffeine Sep 16, 2021
566fe18
Misc changes after code review
fullofcaffeine Sep 24, 2021
d1be655
Abstract polymorphic event
fullofcaffeine Sep 24, 2021
81d9e76
Simplify tests to not use snapshots and be more explicit
fullofcaffeine Sep 24, 2021
8665ad6
Update README.md to reflect the new polymorphic event type
fullofcaffeine Sep 24, 2021
29c8c3a
Make the `Cancel` button a `tertiary` and DRY and improve types
fullofcaffeine Oct 7, 2021
f59553d
Make the title optional and adjust some styles
fullofcaffeine Oct 7, 2021
55ac46a
Fix typo
fullofcaffeine Oct 8, 2021
c96f575
Update packages/components/src/confirm-dialog/README.md
fullofcaffeine Oct 13, 2021
3abfc81
Update packages/components/src/confirm-dialog/README.md
fullofcaffeine Oct 13, 2021
605c2ae
Update packages/components/src/confirm-dialog/README.md
fullofcaffeine Oct 13, 2021
6a60f92
Remove unused imported `MouseEvent`
fullofcaffeine Oct 13, 2021
995f45f
Type `handleEvent`'s `callback` argument as optional
fullofcaffeine Oct 13, 2021
40cb594
Update packages/components/src/confirm-dialog/README.md
fullofcaffeine Oct 13, 2021
ceabb07
Update packages/components/src/confirm-dialog/README.md
fullofcaffeine Oct 13, 2021
aa7025d
Remove portion about the singleton wrapper component as it will not b…
fullofcaffeine Oct 13, 2021
d130ed1
Improve component types
fullofcaffeine Oct 13, 2021
054caae
Use `props.children` to pass the dialog message contents
fullofcaffeine Oct 13, 2021
85e9510
Add DOM structure tests
fullofcaffeine Oct 13, 2021
c20f85d
Convert all findBy* to getBy*
fullofcaffeine Oct 13, 2021
e9ee813
Update README to reflect the new API
fullofcaffeine Oct 13, 2021
316afc5
Improve title and xCloseButton tests
fullofcaffeine Oct 13, 2021
5b2b026
Add better and more concise description for the `title` prop in the R…
fullofcaffeine Oct 13, 2021
4e5abed
Try stacked margin
fullofcaffeine Oct 14, 2021
9bf6f88
Update packages/components/tsconfig.json
fullofcaffeine Oct 14, 2021
64c2816
Update packages/components/src/modal/index.js
fullofcaffeine Oct 14, 2021
414ab15
Destructure props in the function body in `Modal` to prevent TS error…
fullofcaffeine Oct 14, 2021
49eaa0e
Merge branch 'trunk' into replace/confirm-et-al
fullofcaffeine Oct 21, 2021
9f62ad3
Use VStack for a stacked margin approach and remove ability to set a …
fullofcaffeine Oct 21, 2021
af340b2
Update snapshots for the `preferences-modal` unit tests
fullofcaffeine Oct 22, 2021
710932d
Update snapshots for the `keyboard-shortcut-help-modal` unit tests
fullofcaffeine Oct 23, 2021
5e24ff0
Merge branch 'trunk' into replace/confirm-et-al
fullofcaffeine Oct 25, 2021
7d5860c
Merge branch 'trunk' into replace/confirm-et-al
fullofcaffeine Nov 20, 2021
febda4a
Revert post-visibility sample changes
fullofcaffeine Nov 20, 2021
8bed797
Keep modal's default behavior of showing the title div and remove an …
fullofcaffeine Nov 20, 2021
16ce455
Fix wrong message string in one of the stories
fullofcaffeine Nov 20, 2021
2ddbabc
Remove unnecessary explanation about unset props
ciampo Nov 24, 2021
bd6eb33
Rename selfClose to shouldSelfClose
ciampo Nov 24, 2021
94f8b47
Memoize callbacks
ciampo Nov 24, 2021
4d35f84
Extract `confirm` and `cancel` labels
ciampo Nov 24, 2021
74c5293
Improve stories: use `Heading`, better status sentence
ciampo Nov 24, 2021
f69a973
Temporarily enable knobs
ciampo Nov 24, 2021
541490c
Fix typo in test name
ciampo Nov 24, 2021
8eeb0c7
More descriptive storybook sentende, take 2
ciampo Nov 24, 2021
3c3f49a
Sort export alphabetically
ciampo Nov 24, 2021
173f2d4
Undo changes to `Modal` component (to be carried out in a separate PR)
ciampo Nov 24, 2021
4a81ba6
Undo changes to `Modal`-related snapshots (to be carried out in a sep…
ciampo Nov 24, 2021
e2ce3ef
Merge branch 'trunk' into replace/confirm-et-al
ciampo Nov 25, 2021
c2a9adb
Refactor `showTitle` prop on the Modal to `__experimentalHideHeader`
ciampo Nov 25, 2021
a320d85
CHANGELOG
ciampo Nov 25, 2021
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
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,12 @@
"markdown_source": "../packages/components/src/combobox-control/README.md",
"parent": "components"
},
{
"title": "ConfirmDialog",
"slug": "confirm-dialog",
"markdown_source": "../packages/components/src/confirm-dialog/README.md",
"parent": "components"
},
{
"title": "CustomSelectControl",
"slug": "custom-select-control",
Expand Down
102 changes: 102 additions & 0 deletions packages/components/src/confirm-dialog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# `ConfirmDialog`

<div class="callout callout-alert">
This feature is still experimental. "Experimental" means this is an early implementation subject to drastic and breaking changes.
</div>

`ConfirmDialog` displays a confirmation dialog as a `Modal`, with `OK` and `Cancel` buttons. It's confirmed by clicking `OK` or by pressing the `Enter` key. It's cancelled by clicking `Cancel` or by pressing the `ESC` key or clicking outside the dialog focus (i.e, the overlay).

It's built on top of `Modal`, so you can override any of the `Modal` props if you'd like. This also means that it's added as a child of the `body` element in the DOM tree through a React `Portal`, regardless of where it's declared in the React tree.
fullofcaffeine marked this conversation as resolved.
Show resolved Hide resolved

## Usage

`ConfirmDialog` has two main implicit modes: controlled and uncontrolled.

### Uncontrolled mode

Allows the component to be used standalone, just by declaring it as part of another React's component render method:
* It will be automatically open (displayed) upon mounting;
* It will automatically close itself when the `x`, `Cancel`, `Confirm` or overlay is clicked;
fullofcaffeine marked this conversation as resolved.
Show resolved Hide resolved
* `onCancel` is not mandatory but can be passed. Even if passed, the dialog will still be able to close itself.

Activating this mode is as simple as omitting the `isOpen` prop. The only mandatory prop, in this case, is the `onConfirm` callback.

```jsx
import {
__experimentalConfirmDialog as ConfirmDialog
} from '@wordpress/components';

function Example() {
return (
<ConfirmDialog onConfirm={ () => console.debug(' Confirmed! ') }>
)
fullofcaffeine marked this conversation as resolved.
Show resolved Hide resolved
}
```

### Controlled mode

Let the parent component control when the dialog is open/closed. It's activated when a boolean value is passed to `isOpen`:
* It will not be automatically closed. You need to let it know when to open/close by updating the value of the `isOpen` prop;
* Both `onConfirm` and the `onCancel` callbacks are mandatory props in this mode;
* You'll want to update the state that controls `isOpen` by updating it from the `onCancel` and `onConfirm` callbacks.


```jsx
import {
__experimentalConfirmDialog as ConfirmDialog
} from '@wordpress/components';

function Example() {
const [ isOpen, setIsOpen ] = useState( true );

const handleConfirm = () => {
console.debug( 'Confirmed!' );
setIsOpen( false );
}

const handleCancel = () => {
console.debug( 'Cancelled!' );
setIsOpen( false );
}

return (
<ConfirmDialog isOpen={ isOpen } onConfirm={ handleConfirm } onCancel={ handleCancel }>
)
fullofcaffeine marked this conversation as resolved.
Show resolved Hide resolved
}
```

### Unsupported: Multiple instances

Multiple `ConfirmDialog's is an edge case that's currently not officially supported by this component. At the moment, new instances will end up closing the last instance due to the way the `Modal` is implemented (more specifically, because of the use of the `onFocusOutside` to detect a blur in order to close it). This will end up automatically cancelling the previous instance, which might or might not be what you want.
fullofcaffeine marked this conversation as resolved.
Show resolved Hide resolved

Suppose you need to handle multiple confirmations without discarding other instances. In that case, it might be better to have a singleton wrapper component that provides a context-based API to trigger the dialog, keeping track of multiple instances. Here's an [example](https://github.com/WordPress/gutenberg/pull/34153#issuecomment-908342367).
ciampo marked this conversation as resolved.
Show resolved Hide resolved

## Custom Types

```ts
type DialogInputEvent =
| KeyboardEvent< HTMLDivElement >
| MouseEvent< HTMLButtonElement >
```

## Props
fullofcaffeine marked this conversation as resolved.
Show resolved Hide resolved

### `isOpen`: `boolean`

Defines if the dialog is open (displayed) or closed (not rendered/displayed). It also implicitly toggles the controlled mode if set or the uncontrolled mode if it's not set.

### `onConfirm`: `( event: DialogInputEvent ) => void`

- Required: Yes

The callback that's called when the user confirms. A confirmation can happen when the `OK` button is clicked or when `Enter` is pressed.

### `onCancel`: `(event: DialogInputEvent ) => void`

- Required: Yes if `isOpen` is set, No if `isOpen` is not set
Copy link
Contributor

@ciampo ciampo Sep 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Required: Yes if isOpen is set, No if isOpen is not set

Is there any way we can improve this situation, so that we don't have conditionally required props? Maybe the best solution is to make it always required?

Or to expose two different components, one controlled and one uncontrolled? @diegohaz what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also try experimenting with TypeScript types, something like

type typeA = {
  isOpen?: boolean;
  onCancel?: (event: SyntheticEvent ) => void;
};

// `onCancel` is required if `isOpen` is set
type typeB = {
  isOpen: boolean;
  onCancel: (event: SyntheticEvent ) => void;
};

type union = typeA | typeB;

Not sure.

Copy link
Member Author

@fullofcaffeine fullofcaffeine Sep 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is the part I want the most feedback on. I came up with this API as I built the component to look and behave like the native confirm component (and be easy and straightforward to use), while still allowing it to be used as a controlled component. I think it mainly depends on how we plan on consuming this component:

  • If we think it's fine to just use it uncontrolled inline in the components that need it, then yes, we should keep this or break into two components as you said;
  • if we'll instead add more infrastructure around it (like what Diego suggested here) maybe it doesn't make sense to keep the uncontrolled aspect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @fullofcaffeine , sorry I haven't had much time to follow up on this. Have you made any changes to how the controlled vs uncontrolled components are exposed, or to how the variables are typed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment may actually include a good solution for typing this!


The callback that's called when the user cancels. A cancellation can happen when the `Cancel` button is clicked, when the `ESC` key is pressed, or when a click outside of the dialog focus is detected (i.e. in the overlay).

It's not required if `isOpen` is not set (uncontrolled mode), as the component will take care of closing itself, but you can still pass a callback if something must be done upon cancelling (the component will still close itself in this case).

If `isOpen` is set (controlled mode), then it's required, and you need to set the state that defines `isOpen` to `false` as part of this callback if you want the dialog to close when the user cancels.
116 changes: 116 additions & 0 deletions packages/components/src/confirm-dialog/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* External dependencies
*/
// eslint-disable-next-line no-restricted-imports
import React, { useEffect, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import type { Ref, MouseEvent, KeyboardEvent } from 'react';
ciampo marked this conversation as resolved.
Show resolved Hide resolved

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import Modal from '../modal';
import type { OwnProps, DialogInputEvent } from './types';
import {
useContextSystem,
contextConnect,
WordPressComponentProps,
} from '../ui/context';
import { Flex } from '../flex';
import Button from '../button';
import * as styles from './styles';
import { useCx } from '../utils/hooks/use-cx';

function ConfirmDialog(
props: WordPressComponentProps< OwnProps, 'div', false >,
forwardedRef: Ref< any >
) {
const {
isOpen: isOpenProp,
title,
message,
onConfirm,
onCancel,
...otherProps
} = useContextSystem( props, 'ConfirmDialog' );

const hasTitle = !! title;
const cx = useCx();
const wrapperClassNames = cx(
styles.wrapper,
! hasTitle && styles.withoutTitle
);
const buttonsWrapperClassNames = cx( styles.buttonsWrapper );

const [ isOpen, setIsOpen ] = useState< boolean >();
const [ selfClose, setSelfClose ] = useState< boolean >();

useEffect( () => {
// We only allow the dialog to close itself if `isOpenProp` is *not* set.
// If `isOpenProp` is set, then it (probably) means it's controlled by a
// parent component. In that case, `selfClose` might do more harm than
// good, so we disable it.
const isIsOpenSet = typeof isOpenProp !== 'undefined';
setIsOpen( isIsOpenSet ? isOpenProp : true );
Copy link
Contributor

@ciampo ciampo Nov 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what happens if:

  • isOpenProp is set to false — the dialog is not shown
  • later, isOpenProp is unset — would the dialog suddenly show?

Definitely somewhat of an edge case, can be tackled in a follow-up PR

Copy link
Member Author

@fullofcaffeine fullofcaffeine Nov 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, not sure. I can add a test case for that in a follow-up PR, I'll take note!

setSelfClose( ! isIsOpenSet );
}, [ isOpenProp ] );

const handleEvent = ( callback: ( event: DialogInputEvent ) => void ) => (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, but this could probably be memoised with the useCallback hook

event: DialogInputEvent
) => {
// `onCancel` is optional
callback?.( event );
fullofcaffeine marked this conversation as resolved.
Show resolved Hide resolved
if ( selfClose ) {
setIsOpen( false );
}
};

const handleEnter = ( event: KeyboardEvent< HTMLDivElement > ) => {
ciampo marked this conversation as resolved.
Show resolved Hide resolved
if ( event.key === 'Enter' ) {
handleEvent( onConfirm )( event );
}
};

return (
<>
{ isOpen && (
<Modal
title={ title }
overlayClassName={ wrapperClassNames }
onRequestClose={ handleEvent( onCancel ) }
onKeyDown={ handleEnter }
closeButtonLabel={ __( 'Cancel' ) }
isDismissible={ true }
forwardedRef={ forwardedRef }
{ ...otherProps }
>
<p>{ message }</p>
<Flex
justify="flex-end"
className={ buttonsWrapperClassNames }
>
<Button
variant="tertiary"
onClick={ handleEvent( onCancel ) }
>
{ __( 'Cancel' ) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should allow users of this component to change the Cancel and OK text? We could potentially expose them as string props, something like confirmLabel and cancelLabel ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first version was meant to be a (mostly) 1:1 model of confirm, hence the hardcoded labels, but yep! @jasmussen also suggested it in his comment above.

Copy link
Member Author

@fullofcaffeine fullofcaffeine Sep 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm considering working on that in a follow-up PR, and include a more detailed review / discussion of the visual design and UX aspects. What do you think? cc @ciampo @jasmussen

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the design still looks like #34153 (comment), I think we need to at least replace the secondary style cancel button with a tertiary style.

The title wrapping two lines is a bit challenging. The default confirm dialog shows the "Would you like to privately publish this post now?" as just text, not a heading. Can we remove the heading unless explicitly set? I.e. something like:

Screenshot 2021-09-25 at 11 18 48

We do have a little wiggleroom to keep PRs small, but these would be nice changes to get along.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasmussen That's great feedback, thanks! I'll include those changes as part of this changeset.

Copy link
Member Author

@fullofcaffeine fullofcaffeine Oct 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasmussen I've implemented the changes you suggested, you can see all scenarios on the storybook for ConfirmDialog, here's a video:

Peek.2021-10-07.18-57.mp4

I could use some feedback regarding the standardization of CSS rules' values. I see some older non-G2 components use some standardized (and sometimes dynamically-calculated) values that are set to SASS variables. I also found what seems to be the equivalent for emotion but not sure what / when to use each. For now, I just hardcoded to good old pixel units. More details here.

cc @ciampo @diegohaz

EDIT: I just noticed I misspelled "privately" in the screencast above as "privetely". Already fixed in the code.

</Button>
<Button
variant="primary"
onClick={ handleEvent( onConfirm ) }
>
{ __( 'OK' ) }
</Button>
</Flex>
</Modal>
) }
</>
);
}

export default contextConnect( ConfirmDialog, 'ConfirmDialog' );
6 changes: 6 additions & 0 deletions packages/components/src/confirm-dialog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Internal dependencies
*/
import ConfirmDialog from './component';

export { ConfirmDialog };
126 changes: 126 additions & 0 deletions packages/components/src/confirm-dialog/stories/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* External dependencies
*/
// eslint-disable-next-line no-restricted-imports
import React, { useState } from 'react';
import { text } from '@storybook/addon-knobs';

/**
* Internal dependencies
*/
import Button from '../../button';
import { ConfirmDialog } from '..';

export default {
component: ConfirmDialog,
title: 'Components (Experimental)/ConfirmDialog',
};

const daText = () =>
text( 'message', 'Would you like to privately publish the post now?' );

// Simplest usage: just declare the component with the required `onConfirm` prop.
export const _default = () => {
const [ confirmVal, setConfirmVal ] = useState( 'Not confirmed' );

return (
<>
<ConfirmDialog
message={ daText() }
onConfirm={ () => setConfirmVal( 'Confirmed!' ) }
/>
<h1>{ confirmVal }</h1>
</>
);
};

export const WithTitle = () => {
const [ confirmVal, setConfirmVal ] = useState( 'Not confirmed' );

return (
<>
<ConfirmDialog
title="Are you sure?"
message={ daText() }
onConfirm={ () => setConfirmVal( 'Confirmed!' ) }
/>
<h1>{ confirmVal }</h1>
</>
);
};

export const WithJSXMessage = () => {
const [ confirmVal, setConfirmVal ] = useState( 'Not confirmed' );

return (
<>
<ConfirmDialog
message={ <h1>{ daText() }</h1> }
onConfirm={ () => setConfirmVal( 'Confirmed!' ) }
/>
<h1>{ confirmVal }</h1>
</>
);
};

export const VeeeryLongMessage = () => {
const [ confirmVal, setConfirmVal ] = useState( 'Not confirmed' );

return (
<>
<ConfirmDialog
message={ daText().repeat( 20 ) }
onConfirm={ () => setConfirmVal( 'Confirmed!' ) }
/>
<h1>{ confirmVal }</h1>
</>
);
};

export const UncontrolledAndWithExplicitOnCancel = () => {
const [ confirmVal, setConfirmVal ] = useState( 'Not confirmed' );

return (
<>
<ConfirmDialog
message={ daText() }
onConfirm={ () => setConfirmVal( 'Confirmed!' ) }
onCancel={ () => setConfirmVal( 'Cancelled' ) }
/>
<h1>{ confirmVal }</h1>
</>
);
};

// Controlled `ConfirmDialog`s require both `onConfirm` *and* `onCancel to be passed
// It's expected that the user will then use it to hide the dialog, too (see the
// `setIsOpen` calls below).
export const Controlled = () => {
const [ isOpen, setIsOpen ] = useState( false );
const [ confirmVal, setConfirmVal ] = useState( 'Not confirmed' );

const handleConfirm = () => {
setConfirmVal( 'Confirmed!' );
setIsOpen( false );
};

const handleCancel = () => {
setConfirmVal( 'Cancelled' );
setIsOpen( false );
};

return (
<>
<ConfirmDialog
message={ daText() }
isOpen={ isOpen }
onConfirm={ handleConfirm }
onCancel={ handleCancel }
/>
<Button variant="primary" onClick={ () => setIsOpen( true ) }>
Open ConfirmDialog
</Button>
<h1>{ confirmVal }</h1>
</>
);
};
27 changes: 27 additions & 0 deletions packages/components/src/confirm-dialog/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* External dependencies
*/
import { css } from '@emotion/react';

export const wrapper = css`
z-index: 9999999;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary, even if we use the Modal component (which I assumed already had styles to render on top of every other element)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my tests with the post-visibiliy popover, it is. I'll double-check now and let you know.

`;

// @todo it's unfortunate that I had to hardcode 24px below, given the SCSSs use
// variables (i.e https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/modal/style.scss#L112)
// Should I perhaps use any of the consts from here? https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/utils/config-values.js?
// if you know a better way to adjust this, let me know! -Marcelo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can definitely use variables from the configuration that you linked. By having a quick look at it, though, I don't see a value that you could use for the padding-top rule.

I think your best option would be to use the space util directly. It is based on a grid of 4px, which is great in your case since you have 24px and 20px values to replace.

Here's what you could do
diff --git a/packages/components/src/confirm-dialog/styles.ts b/packages/components/src/confirm-dialog/styles.ts
index 4dd7affefc..b3308764ec 100644
--- a/packages/components/src/confirm-dialog/styles.ts
+++ b/packages/components/src/confirm-dialog/styles.ts
@@ -3,6 +3,11 @@
  */
 import { css } from '@emotion/react';
 
+/**
+ * Internal dependencies
+ */
+import { space } from '../ui/utils/space';
+
 export const wrapper = css`
 	z-index: 9999999;
 `;
@@ -18,10 +23,10 @@ export const withoutTitle = css`
 
 	.components-modal__content {
 		margin: 0;
-		padding-top: 24px;
+		padding-top: ${ space( 6 ) };
 	}
 `;
 
 export const buttonsWrapper = css`
-	padding-top: 20px;
+	padding-top: ${ space( 5 ) };
 `;

export const withoutTitle = css`
.components-modal__header {
display: none;
}

.components-modal__content {
margin: 0;
padding-top: 24px;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should never rely on hardcoded classnames when styling our components. The correct solution here would be to improve the Modal component to:

  • use Emotion for internal styling
  • expose "documented" ways to hide its header (e.g. via a prop) and consequently style its header

We should at least add a comment saying that we should refactor away from hardcoded classnames ASAP by improving the Modal component.

See also a similar conversation on the ToolsPanel component
#35415 (comment)

`;

export const buttonsWrapper = css`
padding-top: 20px;
`;
Loading