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

Added support for confirmation keyword + Setup Prettier #60

Merged
merged 3 commits into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Added support for confirmation keyword
Closes #57

The following options have been added:

- confirmationKeyword
- confirmationKeywordTextFieldProps
- getConfirmationKeywordPlaceholder

If `confirmationKeyword` is provided a TextField is rendered and the confirmation button is disabled. When the contents of the TextField match the value of `confirmationKeyword` then the confirmation button becomes enabled.

Additional stories and tests have alse been added for this behavior.
  • Loading branch information
TimMikeladze committed Oct 19, 2022
commit e5ad37706c1732d66b2d745473034d144656d0ff
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
/dist

npm-debug.log*

.idea
TimMikeladze marked this conversation as resolved.
Show resolved Hide resolved
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,23 @@ representing the user choice (resolved on confirmation and rejected on cancellat

##### Options

| Name | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| **`title`** | `ReactNode` | `'Are you sure?'` | Dialog title. |
| **`description`** | `ReactNode` | `''` | Dialog content, automatically wrapped in `DialogContentText`. |
| **`content`** | `ReactNode` | `null` | Dialog content, same as `description` but not wrapped in `DialogContentText`. Supersedes `description` if present. |
| **`confirmationText`** | `ReactNode` | `'Ok'` | Confirmation button caption. |
| **`cancellationText`** | `ReactNode` | `'Cancel'` | Cancellation button caption. |
| **`dialogProps`** | `object` | `{}` | Material-UI [Dialog](https://mui.com/material-ui/api/dialog/#props) props. |
| **`dialogActionsProps`** | `object` | `{}` | Material-UI [DialogActions](https://mui.com/material-ui/api/dialog-actions/#props) props. |
| **`confirmationButtonProps`** | `object` | `{}` | Material-UI [Button](https://mui.com/material-ui/api/button/#props) props for the confirmation button. |
| **`cancellationButtonProps`** | `object` | `{}` | Material-UI [Button](https://mui.com/material-ui/api/dialog/#props) props for the cancellation button. |
| **`titleProps`** | `object` | `{}` | Material-UI [DialogTitle](https://mui.com/api/dialog-title/#props) props for the dialog title. |
| **`contentProps`** | `object` | `{}` | Material-UI [DialogContent](https://mui.com/api/dialog-content/#props) props for the dialog content. |
| **`allowClose`** | `boolean` | `true` | Whether natural close (escape or backdrop click) should close the dialog. When set to `false` force the user to either cancel or confirm explicitly. |
| Name | Type | Default | Description |
|-------------------------------|-------------|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`title`** | `ReactNode` | `'Are you sure?'` | Dialog title. |
| **`description`** | `ReactNode` | `''` | Dialog content, automatically wrapped in `DialogContentText`. |
| **`content`** | `ReactNode` | `null` | Dialog content, same as `description` but not wrapped in `DialogContentText`. Supersedes `description` if present. |
| **`confirmationText`** | `ReactNode` | `'Ok'` | Confirmation button caption. |
| **`cancellationText`** | `ReactNode` | `'Cancel'` | Cancellation button caption. |
| **`dialogProps`** | `object` | `{}` | Material-UI [Dialog](https://mui.com/material-ui/api/dialog/#props) props. |
| **`dialogActionsProps`** | `object` | `{}` | Material-UI [DialogActions](https://mui.com/material-ui/api/dialog-actions/#props) props. |
| **`confirmationButtonProps`** | `object` | `{}` | Material-UI [Button](https://mui.com/material-ui/api/button/#props) props for the confirmation button. |
| **`cancellationButtonProps`** | `object` | `{}` | Material-UI [Button](https://mui.com/material-ui/api/dialog/#props) props for the cancellation button. |
| **`titleProps`** | `object` | `{}` | Material-UI [DialogTitle](https://mui.com/api/dialog-title/#props) props for the dialog title. |
| **`contentProps`** | `object` | `{}` | Material-UI [DialogContent](https://mui.com/api/dialog-content/#props) props for the dialog content. |
| **`allowClose`** | `boolean` | `true` | Whether natural close (escape or backdrop click) should close the dialog. When set to `false` force the user to either cancel or confirm explicitly. |
| **`confirmationKeyword`** | `string` | `undefined` | If provided the confirmation button will be disabled by default and an additional textfield will be rendered. The confirmation button will only be enabled when the contents of the textfield match the value of `confirmationKeyword` |
| **`confirmationKeywordTextFieldProps`** | `object` | `{}`| Material-UI [TextField](https://mui.com/material-ui/api/text-field/) props for the confirmation keyword textfield. |
| **`getConfirmationKeywordPlaceholder`** | `function` | `getConfirmationKeywordPlaceholder: (keyword) => 'Please type "' + keyword + '" to confirm'` | Custom formatting for the placeholder in the confirmation keyword textfield. |

## Useful notes

Expand Down
7 changes: 7 additions & 0 deletions src/ConfirmProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const DEFAULT_OPTIONS = {
titleProps: {},
contentProps: {},
allowClose: true,
getConfirmationKeywordPlaceholder: (keyword) => 'Please type "' + keyword + '" to confirm',
confirmationKeywordTextFieldProps: {},
};

const buildOptions = (defaultOptions, options) => {
Expand Down Expand Up @@ -42,6 +44,10 @@ const buildOptions = (defaultOptions, options) => {
...(defaultOptions.contentProps || DEFAULT_OPTIONS.contentProps),
...(options.contentProps || {}),
};
const confirmationKeywordTextFieldProps = {
...(defaultOptions.confirmationKeywordTextFieldProps || DEFAULT_OPTIONS.confirmationKeywordTextFieldProps),
...(options.confirmationKeywordTextFieldProps || {}),
};

return {
...DEFAULT_OPTIONS,
Expand All @@ -53,6 +59,7 @@ const buildOptions = (defaultOptions, options) => {
cancellationButtonProps,
titleProps,
contentProps,
confirmationKeywordTextFieldProps,
}
};

Expand Down
30 changes: 28 additions & 2 deletions src/ConfirmationDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import TextField from '@mui/material/TextField';

const ConfirmationDialog = ({ open, options, onCancel, onConfirm, onClose }) => {
const {
Expand All @@ -20,8 +21,27 @@ const ConfirmationDialog = ({ open, options, onCancel, onConfirm, onClose }) =>
titleProps,
contentProps,
allowClose,
confirmationKeyword,
getConfirmationKeywordPlaceholder,
confirmationKeywordTextFieldProps,
} = options;

const [confirmationKeywordValue, setConfirmationKeywordValue] = React.useState('');

const confirmationButtonDisabled = confirmationKeyword && confirmationKeywordValue !== confirmationKeyword;

const confirmationContent =
<>
{confirmationKeyword && (
<TextField
onChange={e => setConfirmationKeywordValue(e.target.value)}
value={confirmationKeywordValue}
fullWidth
placeholder={getConfirmationKeywordPlaceholder(confirmationKeyword)}
{...confirmationKeywordTextFieldProps}
/>)}
</>

return (
<Dialog fullWidth {...dialogProps} open={open} onClose={allowClose ? onClose : null}>
{title && (
Expand All @@ -30,19 +50,25 @@ const ConfirmationDialog = ({ open, options, onCancel, onConfirm, onClose }) =>
{content ? (
<DialogContent {...contentProps}>
{content}
{confirmationContent}
</DialogContent>
) : (
description && (
description ? (
<DialogContent {...contentProps}>
<DialogContentText>{description}</DialogContentText>
{confirmationContent}
</DialogContent>
) : confirmationKeyword && (
<DialogContent {...contentProps}>
{confirmationContent}
</DialogContent>
)
)}
<DialogActions {...dialogActionsProps}>
<Button {...cancellationButtonProps} onClick={onCancel}>
{cancellationText}
</Button>
<Button color="primary" {...confirmationButtonProps} onClick={onConfirm}>
<Button color="primary" disabled={confirmationButtonDisabled} {...confirmationButtonProps} onClick={onConfirm}>
{confirmationText}
</Button>
</DialogActions>
Expand Down
4 changes: 4 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DialogActionsProps } from '@mui/material/DialogActions';
import { DialogTitleProps } from '@mui/material/DialogTitle';
import { DialogContentProps } from '@mui/material/DialogContent';
import { ButtonProps } from '@mui/material/Button';
import { TextFieldProps } from '@mui/material/TextField';

export interface ConfirmOptions {
title?: React.ReactNode;
Expand All @@ -18,6 +19,9 @@ export interface ConfirmOptions {
confirmationButtonProps?: ButtonProps;
cancellationButtonProps?: ButtonProps;
allowClose?: boolean;
getConfirmationKeywordPlaceholder?: (keyword: string) => string;
confirmationKeyword?: string,
confirmationKeywordTextFieldProps?: TextFieldProps,
}

export interface ConfirmProviderProps {
Expand Down
31 changes: 30 additions & 1 deletion stories/index.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,32 @@ const WithNaturalCloseDisabled = () => {
);
};

const WithConfirmationKeyword = () => {
const confirm = useConfirm();
return (
<Button onClick={() => confirm({
confirmationKeyword: 'DELETE',
}).then(confirmationAction)}>
Click
</Button>
);
}

const WithConfirmationKeywordAndCustomTextFieldProps = () => {
const confirm = useConfirm();
return (
<Button onClick={() => confirm({
getConfirmationKeywordPlaceholder: () => 'Enter DELETE',
confirmationKeyword: 'DELETE',
confirmationKeywordTextFieldProps: {
variant: 'standard',
}
}).then(confirmationAction)}>
Click
</Button>
);
}

storiesOf('Confirmation dialog', module)
.addDecorator(getStory => (
<ConfirmProvider>{getStory()}</ConfirmProvider>
Expand All @@ -177,4 +203,7 @@ storiesOf('Confirmation dialog', module)
.add('with custom callbacks', () => <WithCustomCallbacks />)
.add('with custom elements', () => <WithCustomElements />)
.add('with custom dialog content', () => <WithCustomContent />)
.add('with natural close disabled', () => <WithNaturalCloseDisabled />);
.add('with natural close disabled', () => <WithNaturalCloseDisabled />)
.add('with confirmation keyword', () => <WithConfirmationKeyword />)
.add('with confirmation keyword and custom textfield props', () => <WithConfirmationKeywordAndCustomTextFieldProps />)
;
52 changes: 51 additions & 1 deletion test/useConfirm.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { render, fireEvent, waitForElementToBeRemoved } from '@testing-library/react'
import {render, fireEvent, waitForElementToBeRemoved, getByPlaceholderText} from '@testing-library/react'

import { ConfirmProvider, useConfirm } from '../src/index';

Expand Down Expand Up @@ -124,4 +124,54 @@ describe('useConfirm', () => {
expect(queryByText('Yes')).toBeFalsy();
expect(getByText('Ok')).toBeTruthy();
});

describe('confirmation keyword', () => {
test('renders textfield when confirmation keyword is set', () => {
const { getByText, queryByPlaceholderText } = render(
<TestComponent confirmOptions={{
confirmationKeyword: 'DELETE'
}} />);

fireEvent.click(getByText('Delete'));

const textfield = queryByPlaceholderText('Please type "DELETE" to confirm');
const confirmationButton = getByText('Ok');

expect(textfield).toBeTruthy();

expect(confirmationButton.disabled).toBe(true);

fireEvent.change(textfield, { target: { value: 'DELETE' } });

expect(confirmationButton.disabled).toBe(false);
})
})
test('renders textfield with custom props', () => {
const { getByText, queryByPlaceholderText } = render(
<TestComponent confirmOptions={{
confirmationKeyword: 'DELETE',
confirmationKeywordTextFieldProps: {
placeholder: 'Custom placeholder',
}
}} />);

fireEvent.click(getByText('Delete'));

const textfield = queryByPlaceholderText('Custom placeholder');

expect(textfield).toBeTruthy();
})
test('renders textfield with custom placeholder function', () => {
const { getByText, queryByPlaceholderText } = render(
<TestComponent confirmOptions={{
confirmationKeyword: 'DELETE',
getConfirmationKeywordPlaceholder: (keyword) => keyword,
}} />);

fireEvent.click(getByText('Delete'));

const textfield = queryByPlaceholderText('DELETE');

expect(textfield).toBeTruthy();
})
});