Skip to content

Commit

Permalink
deprecate hideCloseButton, add support for data attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
sirineJ committed Dec 11, 2024
1 parent 2f346ef commit 050f6f6
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 17 deletions.
19 changes: 19 additions & 0 deletions packages/circuit-ui/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { clsx } from '../../styles/clsx.js';
import type { ClickEvent } from '../../types/events.js';
import { isEscape } from '../../util/key-codes.js';
import { useI18n } from '../../hooks/useI18n/useI18n.js';
import { deprecate } from '../../util/logger.js';

import classes from './Modal.module.css';
import { createUseModal } from './createUseModal.js';
Expand All @@ -42,6 +43,7 @@ import {
} from './ModalService.js';
import { translations } from './translations/index.js';

type DataAttribute = `data-${string}`;
export interface ModalProps
extends Omit<HTMLAttributes<HTMLDialogElement>, 'children'> {
/**
Expand Down Expand Up @@ -77,6 +79,13 @@ export interface ModalProps
* Enables focusing a particular element in the dialog content and override default behavior
*/
initialFocusRef?: RefObject<HTMLElement>;

/**
* @deprecated this props was passed to react-modal and is no longer relevant.
* Use preventClose instead. Also see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/dialog_role#required_javascript_features
*/
hideCloseButton?: boolean;
[key: DataAttribute]: string | undefined;
}

export const animationDuration = 300;
Expand All @@ -91,11 +100,21 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>((props, ref) => {
className,
preventClose,
initialFocusRef,
hideCloseButton,
...rest
} = useI18n(props, translations);
const dialogRef = useRef<HTMLDialogElement>(null);
const lastFocusedElementRef = useRef<HTMLElement | null>(null);

if (process.env.NODE_ENV !== 'production') {
if (hideCloseButton) {
deprecate(
'Modal',
'The "hideCloseButton" prop has been deprecated. Use the `preventClose` prop instead.',
);
}
}

const hasNativeDialog = hasNativeDialogSupport();

useEffect(() => {
Expand Down
17 changes: 9 additions & 8 deletions packages/circuit-ui/components/Modal/ModalContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import {
import type { ModalProps } from './Modal.js';
import type { ModalDialogComponent } from './createUseModal.js';

export type SetModalArgs = Omit<ModalProps, 'open'>;
export type SetModalArgs<T> = Omit<T, 'open'>;

// keep initial state compatible with the old version of this component
export type ModalState<T extends ModalProps> = SetModalArgs & {
export type ModalState<T extends ModalProps> = SetModalArgs<T> & {
component: ModalDialogComponent<T>;
id: string | number;
};
Expand All @@ -38,15 +38,15 @@ type ModalContextValue<T extends ModalProps> = {
setModal: (modal: ModalState<T>) => void;
removeModal: (modal: ModalState<T>) => void;
};
export interface ModalProviderProps {
export interface ModalProviderProps<T extends ModalProps> {
/**
* The ModalProvider should wrap your entire application.
*/
children: ReactNode;
/**
* An array of modals that should be displayed immediately, e.g. on page load.
*/
initialState?: ModalState<ModalProps>[];
initialState?: ModalState<T>[];
}

// TODO replace any
Expand All @@ -55,18 +55,18 @@ export const ModalContext = createContext<ModalContextValue<any>>({
removeModal: () => {},
});

export function ModalProvider({
export function ModalProvider<T extends ModalProps>({
children,
initialState,
...defaultModalProps
}: ModalProviderProps) {
}: ModalProviderProps<T>) {
const [modals, setModals] = useState(initialState ?? []);

const setModal = useCallback((modal: ModalState<ModalProps>) => {
const setModal = useCallback((modal: ModalState<T>) => {
setModals((prevValue) => [...prevValue, modal]);
}, []);

const removeModal = useCallback((modal: ModalState<ModalProps>) => {
const removeModal = useCallback((modal: ModalState<T>) => {
if (modal.onClose) {
modal.onClose();
}
Expand All @@ -84,6 +84,7 @@ export function ModalProvider({
{modals.map((modal) => {
const { id, component: Component, ...modalProps } = modal;
return (
// @ts-expect-error type will either be ModalProps or NotificationProps
<Component
{...defaultModalProps}
{...modalProps}
Expand Down
6 changes: 3 additions & 3 deletions packages/circuit-ui/components/Modal/createUseModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ export function createUseModal<T extends ModalProps>(
component: ModalDialogComponent<T>,
) {
return (): {
setModal: (props: SetModalArgs) => void;
setModal: (props: SetModalArgs<T>) => void;
removeModal: () => void;
} => {
const id = useId();
const modalRef = useRef<SetModalArgs | null>(null);
const modalRef = useRef<SetModalArgs<T> | null>(null);
const context = useContext(ModalContext);

// biome-ignore lint/correctness/useExhaustiveDependencies: The `component` never changes
const setModal = useCallback(
(props: SetModalArgs): void => {
(props: SetModalArgs<T>): void => {
modalRef.current = props;
context.setModal({ ...props, id, component });
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ import { ModalProvider } from '../Modal/ModalContext.js';
import { Button } from '../Button/index.js';
import { modes } from '../../../../.storybook/modes.js';

import {
NotificationModal,
type NotificationModalProps,
} from './NotificationModal.js';
import { NotificationModal } from './NotificationModal.js';
import { useNotificationModal } from './useNotificationModal.js';

export default {
Expand All @@ -51,11 +48,38 @@ export default {
] as Decorator[],
};

export const Base = (modal: NotificationModalProps) => {
export const Base = () => {
const ComponentWithModal = () => {
const { setModal } = useNotificationModal();

return <Button onClick={() => setModal(modal)}>Open modal</Button>;
return (
<Button
onClick={() =>
setModal({
image: {
src: '/images/illustration-update.svg',
alt: '',
},
headline: "It's time to update your browser",
body: "You'll soon need a more up-to-date browser to continue using SumUp.",
actions: {
primary: {
children: 'Update now',
onClick: action('primary'),
},
secondary: {
children: 'Not now',
onClick: action('secondary'),
},
},
'data-selector': 'test',
closeButtonLabel: 'Close',
})
}
>
Open modal
</Button>
);
};
return (
<ModalProvider>
Expand Down

0 comments on commit 050f6f6

Please sign in to comment.