Modal trigger
Internal use only. These components are available only within Atlassian.Every modal example must wrap its triggers in ModalContextProvider. In your application, place this provider once at the application root. Each standalone example supplies its own provider.
Basic usage
Use ModalTrigger when you need to attach a modal entry point to your own button. Pass the supplied
ref to the button so the trigger can preload and open the entry point. For a standard button,
consider
Modal button trigger.
Select Open modal to load the content, then close the modal to return to the trigger. Supply
title and loadingText so the default loading screen identifies what is opening.
import React, { type Ref } from 'react';
import Button from '@atlaskit/button/default/button';
import { ModalContextProvider } from '@atlassian/entry-points/modal-context-provider';
import { ModalTrigger } from '@atlassian/entry-points/modal-trigger';
import { modalEntryPoint } from '../fixtures/modal-entry-point';
import { RelayWrapper } from '../utils/wrapper';
export default function ModalTriggerBasic(): React.JSX.Element {
return (
<RelayWrapper>
<ModalContextProvider>
<ModalTrigger
entryPoint={modalEntryPoint}
title="Example Modal"
loadingText="Loading example modal"
>
{({ ref }) => (
<Button ref={ref as Ref<HTMLButtonElement>} aria-haspopup="dialog">
Open modal
</Button>
)}
</ModalTrigger>
</ModalContextProvider>
</RelayWrapper>
);
}The examples use a mock Relay environment through RelayWrapper. In your application, use the
existing Relay environment and share one ModalContextProvider at the application root.
Default loading experience
The default loading screen shows the supplied title and loadingText while the entry point loads.
Provide internationalized values so users can understand the title and loading message in their
language. See Usage for
translation requirements.
The example uses a loading fixture that intentionally never resolves. The loading screen stays visible and never displays the loaded content. Close the modal with its close button or Escape.
import React, { type Ref } from 'react';
import Button from '@atlaskit/button/default/button';
import { ModalContextProvider } from '@atlassian/entry-points/modal-context-provider';
import { ModalTrigger } from '@atlassian/entry-points/modal-trigger';
import { loadingModalEntryPoint } from '../fixtures/constellation/loading-modal-entry-point';
import { RelayWrapper } from '../utils/wrapper';
export default function ModalTriggerLoading(): React.JSX.Element {
return (
<RelayWrapper>
<ModalContextProvider>
<ModalTrigger
entryPoint={loadingModalEntryPoint}
title="Example Modal"
loadingText="Loading example modal"
>
{({ ref }) => (
<Button ref={ref as Ref<HTMLButtonElement>} aria-haspopup="dialog">
Show default loading
</Button>
)}
</ModalTrigger>
</ModalContextProvider>
</RelayWrapper>
);
}Default error experience
The built-in modal error experience is still being finalized. Until it is available, configure an
application-wide GlobalModalErrorFallback with initEntryPointConfig. See
Custom error experience.
Customization
Custom loading experience
Pass a component to Fallback to replace the default loading content. The CustomLoading component
and its styles are defined in the example below. The trigger supplies the modal shell; the fallback
supplies its header, body, and actions. Use the supplied onClose callback for a cancel action.
Provide internationalized text and a title in the fallback itself, because title and loadingText
configure only the default loading content.
This example intentionally never finishes loading. Select Cancel, the close button, or Escape to close it.
import React, { type Ref } from 'react';
import Button from '@atlaskit/button/default/button';
import { cssMap } from '@atlaskit/css';
import FileIcon from '@atlaskit/icon/core/file';
import ModalBody from '@atlaskit/modal-dialog/modal-body';
import ModalFooter from '@atlaskit/modal-dialog/modal-footer';
import ModalHeader from '@atlaskit/modal-dialog/modal-header';
import ModalTitle from '@atlaskit/modal-dialog/modal-title';
import { Box } from '@atlaskit/primitives/compiled/box';
import { Inline } from '@atlaskit/primitives/compiled/inline';
import { Stack } from '@atlaskit/primitives/compiled/stack';
import { Text } from '@atlaskit/primitives/compiled/text';
import Spinner from '@atlaskit/spinner/spinner';
import { token } from '@atlaskit/tokens';
import { ModalContextProvider } from '@atlassian/entry-points/modal-context-provider';
import { ModalTrigger } from '@atlassian/entry-points/modal-trigger';
import { loadingModalEntryPoint } from '../fixtures/constellation/loading-modal-entry-point';
import { RelayWrapper } from '../utils/wrapper';
const styles = cssMap({
content: {
paddingBlockStart: token('space.400'),
paddingBlockEnd: token('space.400'),
textAlign: 'center',
},
indicator: {
paddingBlockStart: token('space.150'),
paddingBlockEnd: token('space.150'),
paddingInlineStart: token('space.200'),
paddingInlineEnd: token('space.200'),
borderRadius: token('radius.large'),
backgroundColor: token('color.background.discovery.bold'),
},
});
function CustomLoading({ onClose }: { onClose?: () => void }): React.JSX.Element {
return (
<>
<ModalHeader hasCloseButton>
<ModalTitle>Example Modal</ModalTitle>
</ModalHeader>
<ModalBody>
<Stack space="space.300" alignInline="center" xcss={styles.content}>
<Box aria-hidden="true" xcss={styles.indicator}>
<Inline space="space.150" alignBlock="center">
<FileIcon label="" color={token('color.icon.inverse')} />
<Spinner size="medium" appearance="invert" label="" />
</Inline>
</Box>
<Stack space="space.100" alignInline="center" role="status">
<Text weight="bold">Preparing your modal</Text>
<Text color="color.text.subtle">
Loading your content. You can cancel while you wait.
</Text>
</Stack>
</Stack>
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</>
);
}
export default function ModalTriggerCustomLoading(): React.JSX.Element {
return (
<RelayWrapper>
<ModalContextProvider>
<ModalTrigger entryPoint={loadingModalEntryPoint} Fallback={CustomLoading}>
{({ ref }) => (
<Button ref={ref as Ref<HTMLButtonElement>} aria-haspopup="dialog">
Show custom loading
</Button>
)}
</ModalTrigger>
</ModalContextProvider>
</RelayWrapper>
);
}Custom error experience
Modal errors use the application-level GlobalModalErrorFallback configured with
initEntryPointConfig; ModalTrigger does not accept an errorFallback prop. The fallback in this
example reports a translated error through a flag, then closes the modal. Its implementation is
included in the example source.
This example intentionally fails after a short loading state and never displays loaded content. The error fallback applies to every modal entry point in the application, so choose a general recovery experience. Reopen the trigger to try the example again.
import React, { type Ref, useEffect } from 'react';
import Button from '@atlaskit/button/default/button';
import { FlagsProvider } from '@atlaskit/flag/flags-provider';
import { useFlags } from '@atlaskit/flag/use-flags';
import { initEntryPointConfig } from '@atlassian/entry-point-config/init-entry-point-config';
import type { ModalErrorFallbackProps } from '@atlassian/entry-point-config/types';
import { ModalContextProvider } from '@atlassian/entry-points/modal-context-provider';
import { ModalTrigger } from '@atlassian/entry-points/modal-trigger';
import { errorModalEntryPoint } from '../fixtures/legacy/errorModal.entrypoint';
import { RelayWrapper } from '../utils/wrapper';
function CustomModalError({ closeModal }: ModalErrorFallbackProps): React.JSX.Element | null {
const { showFlag } = useFlags();
useEffect(() => {
showFlag({
title: 'Modal content is unavailable',
description: 'This example failed on purpose. Try again later.',
});
closeModal();
}, [closeModal, showFlag]);
return null;
}
initEntryPointConfig({
GlobalModalErrorFallback: CustomModalError,
});
export default function ModalTriggerCustomError(): React.JSX.Element {
return (
<FlagsProvider>
<RelayWrapper>
<ModalContextProvider>
<ModalTrigger entryPoint={errorModalEntryPoint}>
{({ ref }) => (
<Button ref={ref as Ref<HTMLButtonElement>} aria-haspopup="dialog">
Show custom error
</Button>
)}
</ModalTrigger>
</ModalContextProvider>
</RelayWrapper>
</FlagsProvider>
);
}Accessibility
Internationalization
Provide translated strings for the trigger, title, and loadingText. This example uses
react-intl with a French message catalog to populate the title and loadingText fallback props.
title identifies both the default loading and error experiences. Use your application's existing
internationalization provider in production.
The entry point intentionally never resolves, so the example only shows the translated default
loading experience. The loaded modal must translate and render its own title and content; title
does not add a title to the UI supplied by your entry point.
import React, { type Ref } from 'react';
import { defineMessage, IntlProvider, useIntl } from 'react-intl';
import Button from '@atlaskit/button/default/button';
import { ModalContextProvider } from '@atlassian/entry-points/modal-context-provider';
import { ModalTrigger } from '@atlassian/entry-points/modal-trigger';
import { loadingModalEntryPoint } from '../fixtures/constellation/loading-modal-entry-point';
import { RelayWrapper } from '../utils/wrapper';
const triggerLabel = defineMessage({
id: 'entry-points.examples.modal.trigger.ai-non-final',
defaultMessage: 'Edit issue',
description: 'Label on the example button that opens the asynchronously loaded edit issue modal.',
});
const modalTitle = defineMessage({
id: 'entry-points.examples.modal.title.ai-non-final',
defaultMessage: 'Edit issue',
description: 'Visible title on the default loading modal while the edit issue content loads.',
});
const loadingMessage = defineMessage({
id: 'entry-points.examples.modal.loading.ai-non-final',
defaultMessage: 'Loading issue',
description:
'Message displayed while the example modal waits for its edit issue content to load.',
});
const frenchMessages = {
[triggerLabel.id]: 'Modifier le ticket',
[modalTitle.id]: 'Modifier le ticket',
[loadingMessage.id]: 'Chargement du ticket',
};
function TranslatedModal(): React.JSX.Element {
const { formatMessage } = useIntl();
return (
<ModalTrigger
entryPoint={loadingModalEntryPoint}
title={formatMessage(modalTitle)}
loadingText={formatMessage(loadingMessage)}
// TODO: Add `errorTitle` and `errorText` when
// `platform_dst-a11y_accessible-error-modal` is fully rolled out.
>
{({ ref }) => (
<Button ref={ref as Ref<HTMLButtonElement>} aria-haspopup="dialog">
{formatMessage(triggerLabel)}
</Button>
)}
</ModalTrigger>
);
}
export default function ModalTriggerInternationalization(): React.JSX.Element {
return (
<IntlProvider locale="fr" defaultLocale="en" messages={frenchMessages}>
<RelayWrapper>
<ModalContextProvider>
<TranslatedModal />
</ModalContextProvider>
</RelayWrapper>
</IntlProvider>
);
}