Skip to content

Commit

Permalink
Decorator Modal (#18101)
Browse files Browse the repository at this point in the history
* feat: added decorator

* fix: fixed styles

* fix: fixed ai style

* test: fixed tests

* fix: delete datepicker decorator example

* fix: fixed styles

* fix: fixed styles

* fix: fixed styles

* fix: yarn format

* fix: merge style with composedmodal

* fix: fixed revert icon

* fix: removed revert from stories

* fix: removed modal stories

* Update packages/styles/scss/components/modal/_modal.scss

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

---------

Co-authored-by: Ariella Gilmore <[email protected]>
  • Loading branch information
guidari and ariellalgilmore authored Nov 20, 2024
1 parent f926887 commit 39642f0
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 44 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 @@ -4851,6 +4851,9 @@ Map {
"danger": Object {
"type": "bool",
},
"decorator": Object {
"type": "node",
},
"hasScrollingContent": Object {
"type": "bool",
},
Expand Down Expand Up @@ -4966,9 +4969,7 @@ Map {
],
"type": "oneOf",
},
"slug": Object {
"type": "node",
},
"slug": [Function],
},
"render": [Function],
},
Expand Down
17 changes: 1 addition & 16 deletions packages/react/src/components/DatePicker/DatePicker.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ const aiLabel = (
);

export const withAILabel = () => (
<div style={{ width: 400, display: 'flex', gap: '1rem' }}>
<div style={{ width: 400 }}>
<DatePicker datePickerType="single">
<DatePickerInput
placeholder="mm/dd/yyyy"
Expand All @@ -176,21 +176,6 @@ export const withAILabel = () => (
decorator={aiLabel}
/>
</DatePicker>

{/* Test decorator with Tooltip TO BE REMOVED!! */}
<DatePicker datePickerType="single">
<DatePickerInput
placeholder="mm/dd/yyyy"
labelText="Date Picker label"
size="md"
id="date-picker"
decorator={
<Tooltip>
<Information />
</Tooltip>
}
/>
</DatePicker>
</div>
);

Expand Down
15 changes: 15 additions & 0 deletions packages/react/src/components/Modal/Modal-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,21 @@ describe('Modal', () => {
expect(screen.getByRole('button', { name: 'Cancel' })).toBeDisabled();
});

it('should respect decorator prop', () => {
const { container } = render(
<Modal
danger
primaryButtonText="Danger button text"
data-testid="modal-5"
decorator={<AILabel />}
/>
);

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

it('should respect slug prop', () => {
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const { container } = render(
<Modal
danger
Expand All @@ -379,6 +393,7 @@ describe('Modal', () => {
);

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

Expand Down
8 changes: 6 additions & 2 deletions packages/react/src/components/Modal/Modal.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Modal from './Modal';
import Button from '../Button';
import Select from '../Select';
import MultiSelect from '../MultiSelect';
import { Checkbox as CheckboxIcon } from '@carbon/icons-react';
import { Checkbox as CheckboxIcon, Information } from '@carbon/icons-react';
import { Popover, PopoverContent } from '../Popover';
import Dropdown from '../Dropdown';
import SelectItem from '../SelectItem';
Expand All @@ -32,6 +32,7 @@ import TextArea from '../TextArea';
import { AILabel, AILabelContent, AILabelActions } from '../AILabel';
import { IconButton } from '../IconButton';
import { View, FolderOpen, Folders } from '@carbon/icons-react';
import { Tooltip } from '../Tooltip';

export default {
title: 'Components/Modal',
Expand Down Expand Up @@ -788,14 +789,17 @@ export const withAILabel = {
return (
<div className="ai-label-modal">
<Button onClick={() => setOpen(true)}>Launch modal</Button>
<Button onClick={() => setOpen2(true)}>
Launch modal decorator tooltip
</Button>
<Modal
open={open}
onRequestClose={() => setOpen(false)}
modalHeading="Add a custom domain"
modalLabel="Account resources"
primaryButtonText="Add"
secondaryButtonText="Cancel"
slug={aiLabel}>
decorator={aiLabel}>
<p style={{ marginBottom: '1rem' }}>
Custom domains direct requests for your apps in this Cloud Foundry
organization to a URL that you own. A custom domain can be a shared
Expand Down
52 changes: 41 additions & 11 deletions packages/react/src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { ReactAttr } from '../../types/common';
import { InlineLoadingStatus } from '../InlineLoading/InlineLoading';
import { useFeatureFlag } from '../FeatureFlags';
import { composeEventHandlers } from '../../tools/events';
import deprecate from '../../prop-types/deprecate';

export const ModalSizes = ['xs', 'sm', 'md', 'lg'] as const;

Expand Down Expand Up @@ -74,6 +75,11 @@ export interface ModalProps extends ReactAttr<HTMLDivElement> {
*/
danger?: boolean;

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

/**
* Specify whether the modal contains scrolling content
*/
Expand Down Expand Up @@ -212,6 +218,7 @@ export interface ModalProps extends ReactAttr<HTMLDivElement> {
size?: ModalSize;

/**
* @deprecated please use decorator instead.
* **Experimental**: Provide a `Slug` component to be rendered inside the `Modal` component
*/
slug?: ReactNode;
Expand All @@ -222,6 +229,7 @@ const Modal = React.forwardRef(function Modal(
'aria-label': ariaLabelProp,
children,
className,
decorator,
modalHeading = '',
modalLabel = '',
modalAriaLabel,
Expand Down Expand Up @@ -354,6 +362,7 @@ const Modal = React.forwardRef(function Modal(
'is-visible': open,
[`${prefix}--modal--danger`]: danger,
[`${prefix}--modal--slug`]: slug,
[`${prefix}--modal--decorator`]: decorator,
},
className
);
Expand Down Expand Up @@ -473,12 +482,20 @@ const Modal = React.forwardRef(function Modal(
};
}, []);

// Slug is always size `sm`
let normalizedSlug;
if (slug && slug['type']?.displayName === 'AILabel') {
normalizedSlug = React.cloneElement(slug as React.ReactElement<any>, {
size: 'sm',
});
// AILabel 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',
}
);
}

const modalButton = (
Expand Down Expand Up @@ -525,7 +542,15 @@ const Modal = React.forwardRef(function Modal(
className={`${prefix}--modal-header__heading`}>
{modalHeading}
</Text>
{normalizedSlug}
{slug ? (
normalizedDecorator
) : decorator ? (
<div className={`${prefix}--modal--inner__decorator`}>
{normalizedDecorator}
</div>
) : (
''
)}
{!passiveModal && modalButton}
</div>
<Layer
Expand Down Expand Up @@ -650,6 +675,11 @@ Modal.propTypes = {
*/
danger: PropTypes.bool,

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

/**
* Specify whether the modal contains scrolling content
*/
Expand Down Expand Up @@ -822,10 +852,10 @@ Modal.propTypes = {
*/
size: PropTypes.oneOf(ModalSizes),

/**
* **Experimental**: Provide a `Slug` component to be rendered inside the `Modal` component
*/
slug: PropTypes.node,
slug: deprecate(
PropTypes.node,
'The `slug` prop has been deprecated and will be removed in the next major version. Use the decorator prop instead.'
),
};

export default Modal;
27 changes: 15 additions & 12 deletions packages/styles/scss/components/modal/_modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -367,16 +367,12 @@

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

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

Expand All @@ -389,9 +385,7 @@

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

Expand Down Expand Up @@ -450,9 +444,18 @@
inset-inline-end: convert.to-rem(48px);
}

.#{$prefix}--modal--slug
.#{$prefix}--modal-content--overflow-indicator::before,
.#{$prefix}--modal--slug .#{$prefix}--modal-content--overflow-indicator,
.#{$prefix}--modal-header
> .#{$prefix}--modal--inner__decorator:not(:has(.#{$prefix}--ai-label))
> * {
inset-block-start: 1rem;
}

.#{$prefix}--modal-header
> .#{$prefix}--modal--inner__decorator:has(.#{$prefix}--ai-label--revert)
> * {
inset-block-start: 1.475rem;
}

.#{$prefix}--modal--decorator
.#{$prefix}--modal-content--overflow-indicator::before,
.#{$prefix}--modal--decorator .#{$prefix}--modal-content--overflow-indicator {
Expand Down

0 comments on commit 39642f0

Please sign in to comment.