Skip to content

Commit

Permalink
feat: new decorator prop for ComposedModal (#18075)
Browse files Browse the repository at this point in the history
* feat: new decorator prop

* feat: formatted

* Update packages/react/src/components/ComposedModal/ComposedModal.tsx

Co-authored-by: Ariella Gilmore <[email protected]>

* Update packages/react/src/components/ComposedModal/ComposedModal.tsx

Co-authored-by: Ariella Gilmore <[email protected]>

* chore: remove decorator story

* chore: format

* chore: format

---------

Co-authored-by: Ariella Gilmore <[email protected]>
Co-authored-by: Anna Wen <[email protected]>
  • Loading branch information
3 people authored Nov 18, 2024
1 parent 550ad44 commit eb30ad4
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 22 deletions.
7 changes: 4 additions & 3 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1452,6 +1452,9 @@ Map {
"danger": Object {
"type": "bool",
},
"decorator": Object {
"type": "node",
},
"isFullWidth": Object {
"type": "bool",
},
Expand Down Expand Up @@ -1510,9 +1513,7 @@ Map {
],
"type": "oneOf",
},
"slug": Object {
"type": "node",
},
"slug": [Function],
},
"render": [Function],
},
Expand Down
26 changes: 23 additions & 3 deletions packages/react/src/components/ComposedModal/ComposedModal-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,9 @@ describe('ComposedModal', () => {
expect(screen.getByRole('button', { name: 'Cancel' })).toBeDisabled();
});

it('should respect slug prop', () => {
it('should respect decorator prop', () => {
const { container } = render(
<ComposedModal open slug={<AILabel />}>
<ComposedModal open decorator={<AILabel />}>
<ModalHeader>Modal header</ModalHeader>
<ModalBody>This is the modal body content</ModalBody>
<ModalFooter
Expand All @@ -357,10 +357,30 @@ describe('ComposedModal', () => {
</ComposedModal>
);

expect(container.firstChild).toHaveClass(`${prefix}--modal--slug`);
expect(container.firstChild).toHaveClass(`${prefix}--modal--decorator`);
});
});

it('should respect the deprecated slug prop', () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
render(
<ComposedModal open slug={<AILabel />}>
<ModalHeader>Modal header</ModalHeader>
<ModalBody>This is the modal body content</ModalBody>
<ModalFooter
primaryButtonText="Add"
secondaryButtonText="Cancel"
loadingStatus="active"
loadingDescription="loading..."></ModalFooter>
</ComposedModal>
);

expect(
screen.getByRole('button', { name: 'AI - Show information' })
).toBeInTheDocument();
spy.mockRestore();
});

it('should handle onClick events', async () => {
const onClick = jest.fn();
render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Select from '../Select';
import SelectItem from '../SelectItem';
import TextInput from '../TextInput';
import Button from '../Button';
import { Information } from '@carbon/icons-react';
import {
StructuredListWrapper,
StructuredListHead,
Expand Down Expand Up @@ -391,7 +392,7 @@ export const _withAILabel = {
<ComposedModal
open={open}
onClose={() => setOpen(false)}
slug={aiLabel}>
decorator={aiLabel}>
<ModalHeader label="Account resources" title="Add a custom domain" />
<ModalBody>
<p style={{ marginBottom: '1rem' }}>
Expand Down
55 changes: 44 additions & 11 deletions packages/react/src/components/ComposedModal/ComposedModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import React, {
type ReactNode,
type ReactElement,
type RefObject,
useMemo,
isValidElement,
} from 'react';
import { isElement } from 'react-is';
import PropTypes from 'prop-types';
Expand All @@ -28,6 +30,7 @@ import { usePrefix } from '../../internal/usePrefix';
import { keys, match } from '../../internal/keyboard';
import { useFeatureFlag } from '../FeatureFlags';
import { composeEventHandlers } from '../../tools/events';
import deprecate from '../../prop-types/deprecate';

export interface ModalBodyProps extends HTMLAttributes<HTMLDivElement> {
/** Specify the content to be placed in the ModalBody. */
Expand Down Expand Up @@ -172,6 +175,11 @@ export interface ComposedModalProps extends HTMLAttributes<HTMLDivElement> {
*/
danger?: boolean;

/**
* **Experimental**: Provide a `decorator` component to be rendered inside the `ComposedModal` component
*/
decorator?: ReactNode;

/**
* Specify whether the Modal content should have any inner padding.
*/
Expand Down Expand Up @@ -212,6 +220,7 @@ export interface ComposedModalProps extends HTMLAttributes<HTMLDivElement> {
size?: 'xs' | 'sm' | 'md' | 'lg';

/**
* @deprecated please use `decorator` instead.
* **Experimental**: Provide a `Slug` component to be rendered inside the `ComposedModal` component
*/
slug?: ReactNode;
Expand All @@ -226,6 +235,7 @@ const ComposedModal = React.forwardRef<HTMLDivElement, ComposedModalProps>(
className: customClassName,
containerClassName,
danger,
decorator,
isFullWidth,
onClose,
onKeyDown,
Expand Down Expand Up @@ -335,6 +345,7 @@ const ComposedModal = React.forwardRef<HTMLDivElement, ComposedModalProps>(
'is-visible': isOpen,
[`${prefix}--modal--danger`]: danger,
[`${prefix}--modal--slug`]: slug,
[`${prefix}--modal--decorator`]: decorator,
},
customClassName
);
Expand Down Expand Up @@ -421,12 +432,20 @@ const ComposedModal = React.forwardRef<HTMLDivElement, ComposedModalProps>(
}
}, [open, selectorPrimaryFocus, isOpen]);

// Slug is always size `sm`
let normalizedSlug;
if (slug && slug['type']?.displayName === 'AILabel') {
normalizedSlug = React.cloneElement(slug as React.ReactElement<any>, {
size: 'sm',
});
// AILabel is always size `sm`
let normalizedDecorator = React.isValidElement(slug ?? decorator)
? (slug ?? decorator)
: null;
if (
normalizedDecorator &&
normalizedDecorator['type']?.displayName === 'AILabel'
) {
normalizedDecorator = React.cloneElement(
normalizedDecorator as React.ReactElement<any>,
{
size: 'sm',
}
);
}

return (
Expand Down Expand Up @@ -456,7 +475,15 @@ const ComposedModal = React.forwardRef<HTMLDivElement, ComposedModalProps>(
</button>
)}
<div ref={innerModal} className={`${prefix}--modal-container-body`}>
{normalizedSlug}
{slug ? (
normalizedDecorator
) : decorator ? (
<div className={`${prefix}--modal--inner__decorator`}>
{normalizedDecorator}
</div>
) : (
''
)}
{childrenWithProps}
</div>
{/* Non-translatable: Focus-wrap code makes this `<button>` not actually read by screen readers */}
Expand Down Expand Up @@ -506,6 +533,11 @@ ComposedModal.propTypes = {
*/
danger: PropTypes.bool,

/**
* **Experimental**: Provide a `decorator` component to be rendered inside the `ComposedModal` component
*/
decorator: PropTypes.node,

/**
* Specify whether the Modal content should have any inner padding.
*/
Expand Down Expand Up @@ -557,10 +589,11 @@ ComposedModal.propTypes = {
*/
size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']),

/**
* **Experimental**: Provide a `Slug` component to be rendered inside the `ComposedModal` component
*/
slug: PropTypes.node,
slug: deprecate(
PropTypes.node,
'The `slug` prop for `ComposedModal` has ' +
'been deprecated in favor of the new `decorator` prop. It will be removed in the next major release.'
),
};

export default ComposedModal;
37 changes: 33 additions & 4 deletions packages/styles/scss/components/modal/_modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -365,12 +365,19 @@
margin: 0;
}

// Slug styles
.#{$prefix}--modal--slug.#{$prefix}--modal {
// AIlabel styles
.#{$prefix}--modal--slug.#{$prefix}--modal,
.#{$prefix}--modal--decorator.#{$prefix}--modal:has(
.#{$prefix}--modal--inner__decorator .#{$prefix}--ai-label
) {
background-color: $ai-overlay;
}

.#{$prefix}--modal--slug .#{$prefix}--modal-container {
.#{$prefix}--modal--slug .#{$prefix}--modal-container,
.#{$prefix}--modal--decorator:has(
.#{$prefix}--modal--inner__decorator .#{$prefix}--ai-label
)
.#{$prefix}--modal-container {
@include ai-popover-gradient('default', 0, 'layer');

border: 1px solid transparent;
Expand All @@ -381,6 +388,10 @@
}

.#{$prefix}--modal--slug
.#{$prefix}--modal-container:has(.#{$prefix}--modal-footer),
.#{$prefix}--modal--decorator:has(
.#{$prefix}--modal--inner__decorator .#{$prefix}--ai-label
)
.#{$prefix}--modal-container:has(.#{$prefix}--modal-footer) {
@include ai-popover-gradient('default', 64px, 'layer');

Expand All @@ -391,6 +402,8 @@
}

.#{$prefix}--modal--slug
.#{$prefix}--modal-content.#{$prefix}--modal-scroll-content,
.#{$prefix}--modal--decorator
.#{$prefix}--modal-content.#{$prefix}--modal-scroll-content {
mask-image: linear-gradient(
to bottom,
Expand All @@ -403,6 +416,19 @@
linear-gradient(to top, $layer 0, 2px, transparent 2px);
}

.#{$prefix}--modal-header
> .#{$prefix}--modal--inner__decorator:has(
+ .#{$prefix}--modal-close-button
)
> *,
.#{$prefix}--modal-header
> .#{$prefix}--modal-close-button
~ .#{$prefix}--modal--inner__decorator
> *,
.#{$prefix}--modal--decorator
.#{$prefix}--modal-container-body
> .#{$prefix}--modal--inner__decorator
> *,
.#{$prefix}--modal-header
> .#{$prefix}--ai-label:has(+ .#{$prefix}--modal-close-button),
.#{$prefix}--modal-header
Expand All @@ -426,7 +452,10 @@

.#{$prefix}--modal--slug
.#{$prefix}--modal-content--overflow-indicator::before,
.#{$prefix}--modal--slug .#{$prefix}--modal-content--overflow-indicator {
.#{$prefix}--modal--slug .#{$prefix}--modal-content--overflow-indicator,
.#{$prefix}--modal--decorator
.#{$prefix}--modal-content--overflow-indicator::before,
.#{$prefix}--modal--decorator .#{$prefix}--modal-content--overflow-indicator {
display: none;
}

Expand Down

0 comments on commit eb30ad4

Please sign in to comment.