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

[ChipDelete][joy] Add onDelete prop to ChipDelete #35412

Merged
merged 19 commits into from Dec 19, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 13 additions & 4 deletions docs/data/joy/components/chip/DeleteButtonChip.js
@@ -1,7 +1,7 @@
import * as React from 'react';
import Box from '@mui/joy/Box';
import Chip from '@mui/joy/Chip';
import ChipDelete from '@mui/joy/ChipDelete';
import * as React from 'react';

export default function DeleteButtonChip() {
return (
Expand All @@ -10,14 +10,23 @@ export default function DeleteButtonChip() {
size="sm"
variant="outlined"
color="danger"
endDecorator={<ChipDelete />}
endDecorator={<ChipDelete onDelete={() => alert('Delete')} />}
>
Remove
</Chip>
<Chip variant="soft" color="danger" endDecorator={<ChipDelete />}>
<Chip
variant="soft"
color="danger"
endDecorator={<ChipDelete onDelete={() => alert('Delete')} />}
>
Delete
</Chip>
<Chip size="lg" variant="solid" color="danger" endDecorator={<ChipDelete />}>
<Chip
size="lg"
variant="solid"
color="danger"
endDecorator={<ChipDelete onDelete={() => alert('Delete')} />}
>
Delete
</Chip>
</Box>
Expand Down
17 changes: 13 additions & 4 deletions docs/data/joy/components/chip/DeleteButtonChip.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import Box from '@mui/joy/Box';
import Chip from '@mui/joy/Chip';
import ChipDelete from '@mui/joy/ChipDelete';
import * as React from 'react';

export default function DeleteButtonChip() {
return (
Expand All @@ -10,14 +10,23 @@ export default function DeleteButtonChip() {
size="sm"
variant="outlined"
color="danger"
endDecorator={<ChipDelete />}
endDecorator={<ChipDelete onDelete={() => alert('Delete')} />}
>
Remove
</Chip>
<Chip variant="soft" color="danger" endDecorator={<ChipDelete />}>
<Chip
variant="soft"
color="danger"
endDecorator={<ChipDelete onDelete={() => alert('Delete')} />}
>
Delete
</Chip>
<Chip size="lg" variant="solid" color="danger" endDecorator={<ChipDelete />}>
<Chip
size="lg"
variant="solid"
color="danger"
endDecorator={<ChipDelete onDelete={() => alert('Delete')} />}
>
Delete
</Chip>
</Box>
Expand Down
14 changes: 0 additions & 14 deletions docs/data/joy/components/chip/DeleteButtonChip.tsx.preview

This file was deleted.

6 changes: 5 additions & 1 deletion docs/data/joy/components/chip/chip.md
Expand Up @@ -49,7 +49,11 @@ Use the `startDecorator` and/or `endDecorator` props to add supporting icons to
### Delete button

To add a delete action inside a chip, use the complementary `ChipDelete` component.
Note that its design will automatically adapt to the parent `Chip`.

The `onDelete` callback is fired on `ChipDelete` either when:

- `Backspace`, `Enter` or `Delete` is pressed.
- The `ChipDelete` is clicked.

```jsx
import ChipDelete from '@mui/joy/ChipDelete';
Expand Down
31 changes: 30 additions & 1 deletion packages/mui-joy/src/ChipDelete/ChipDelete.test.js
@@ -1,6 +1,7 @@
import * as React from 'react';
import { expect } from 'chai';
import { createRenderer, describeConformance } from 'test/utils';
import { spy } from 'sinon';
import { createRenderer, describeConformance, act, fireEvent } from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import Chip from '@mui/joy/Chip';
import ChipDelete, { chipDeleteClasses as classes } from '@mui/joy/ChipDelete';
Expand Down Expand Up @@ -67,4 +68,32 @@ describe('<ChipDelete />', () => {
expect(getByRole('button')).to.have.class(classes.colorNeutral);
});
});
describe('Chip onDelete', () => {
it('should call onDelete function when backspace, enter or delete is pressed', () => {
const handleDelete = spy();
const { getByRole } = render(<ChipDelete onDelete={handleDelete} onClick={() => {}} />);
const chipDelete = getByRole('button');
act(() => {
chipDelete.focus();
});
fireEvent.keyDown(chipDelete, { key: 'Backspace' });
fireEvent.keyDown(chipDelete, { key: 'Enter' });
fireEvent.keyDown(chipDelete, { key: 'Delete' });
fireEvent.click(chipDelete);
expect(handleDelete.callCount).to.equal(4);
});

it('should not call onDelete function when ChipDelete is disabled', () => {
const handleDelete = spy();
const { getByRole } = render(
<ChipDelete disabled onDelete={handleDelete} onClick={() => {}} />,
);
const chipDelete = getByRole('button');
act(() => {
chipDelete.focus();
});
fireEvent.click(chipDelete);
expect(handleDelete.callCount).to.equal(0);
});
});
});
43 changes: 42 additions & 1 deletion packages/mui-joy/src/ChipDelete/ChipDelete.tsx
Expand Up @@ -75,6 +75,9 @@ const ChipDelete = React.forwardRef(function ChipDelete(inProps, ref) {
variant: variantProp,
color: colorProp,
disabled: disabledProp,
onKeyDown,
onDelete,
onClick,
...other
} = props;
const chipContext = React.useContext(ChipContext);
Expand All @@ -101,6 +104,27 @@ const ChipDelete = React.forwardRef(function ChipDelete(inProps, ref) {

const classes = useUtilityClasses(ownerState);

const handleClickDelete = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (!disabled && onDelete) {
onDelete(event);
}
if (onClick) {
onClick(event);
}
};

const handleKeyDelete = (event: React.KeyboardEvent<HTMLButtonElement>) => {
if (['Backspace', 'Enter', 'Delete'].includes(event.key)) {
event.preventDefault();
if (!disabled && onDelete) {
onDelete(event);
}
}
if (onKeyDown) {
onKeyDown(event);
}
};

const rootProps = useSlotProps({
elementType: ChipDeleteRoot,
getSlotProps: getRootProps,
Expand All @@ -109,11 +133,14 @@ const ChipDelete = React.forwardRef(function ChipDelete(inProps, ref) {
ownerState,
additionalProps: {
as: component,
onKeyDown: handleKeyDelete,
onClick: handleClickDelete,
},
className: classes.root,
});

return <ChipDeleteRoot {...rootProps}>{children ?? <Cancel />}</ChipDeleteRoot>;
const { onDelete: excludeOnDelete, ...restOfRootProps } = rootProps;
return <ChipDeleteRoot {...restOfRootProps}>{children ?? <Cancel />}</ChipDeleteRoot>;
}) as OverridableComponent<ChipDeleteTypeMap>;

ChipDelete.propTypes /* remove-proptypes */ = {
Expand Down Expand Up @@ -143,6 +170,20 @@ ChipDelete.propTypes /* remove-proptypes */ = {
* If `undefined`, the value inherits from the parent chip via a React context.
*/
disabled: PropTypes.bool,
/**
* @ignore
*/
onClick: PropTypes.func,
/**
* Callback fired when the component is not disabled and either:
* - `Backspace`, `Enter` or `Delete` is pressed.
* - The component is clicked.
*/
onDelete: PropTypes.func,
/**
* @ignore
*/
onKeyDown: PropTypes.func,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
Expand Down
8 changes: 8 additions & 0 deletions packages/mui-joy/src/ChipDelete/ChipDeleteProps.ts
Expand Up @@ -23,6 +23,14 @@ export interface ChipDeleteTypeMap<P = {}, D extends React.ElementType = 'button
* If `undefined`, the value inherits from the parent chip via a React context.
*/
disabled?: boolean;
/**
* Callback fired when the component is not disabled and either:
* - `Backspace`, `Enter` or `Delete` is pressed.
* - The component is clicked.
*/
onDelete?: React.EventHandler<
React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>
>;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
Expand Down