Link create - Inline create
Note: This component is designed for internal Atlassian development.
External contributors will be able to use this component but will not be able to submit issues.
As an alternative to the default modal experience link create can also be used inline via the exported <InlineCreate />
component.
This is great for use cases where you want to display link create alongside other content on the page or within a custom UI component (such as a drawer or dropdown).
If you have any questions, you can reach out to #help-bandicoots for help.
Installation
yarn add @atlaskit/link-create
InlineCreate Props
testId
string
A testId
prop is provided for specified elements, which is a unique
string that appears as a data attribute data-testid
in the rendered code,
serving as a hook for automated tests
plugins
arrayType
required
Arraygroup required label string required icon string required key string required label string required icon string required key required Keyform required react.ReactNodeeditView { payload, onClose } => JSX.Element
groupKey
string
The initial group key for create. If this is provided, it will jump directly to the entity selection screen
entityKey
string
required
The initial entity name for create. If this is provided, it will jump directly to the entity creation form. Note: it will be non-optional for now and can move to optional when we have the meta creation flow built.
onCreate
function
This callback for when the resource has been successfully created.
payload => One of Promiseundefined, undefined
onComplete
function
This callback for when the LinkCreate experience has successfully been completed. Note: this callback is one of the requirements to enable the LinkCreate post-create edit functionality
() => undefined
onFailure
function
This callback for any errors
error => undefined
onCancel
function
This callback for when the form was manually discarded by user
() => undefined
triggeredFrom
string
This value tells where the linkCreate was triggered from. And it's for analytic purpose only. Default: unknown
Usage
To start the experience, simply mount the <InlineCreate />
component.
Provide an onCreate
callback to be notified of objects being created. When an object is created, this is not necessarily indication that the experience is completed/finished.
To be notified of when the experience is completed provide an onComplete
callback. By providing onComplete
it enables link create to maintain control of the experience beyond the point of a single object creation (e.g. show a "Create and open" button in Confluence).
You should also provide an onCancel
callback to be notified when the user cancels the experience without completing an object. This may be useful if you have a different experience journey to follow depending on whether or not the user completed object creation or not.
Note the component must be unmounted when the experience is completed, inline create does not have an implicit 'done' state.
Exit warning modal
You must ensure the <InlineCreate />
component is wrapped in a parent <LinkCreateExitWarningProvider />
context provider, not doing so will result in an exception being thrown. This ensures that the user is warned if they attempt to navigate away from the form without completing the experience.
Adopters can also trigger the exit warning by wrapping a custom callback with the utility function returned from the useWithExitWarning()
hook. This is useful for scenarios where you want to trigger the exit warning from a custom button or link. The example below triggers the exit warning if the drawer is closed after the form has been changed.
Examples
Inline create example
import React, { useCallback, useMemo, useState } from 'react';
import fetchMock from 'fetch-mock/cjs/client';
import Button from '@atlaskit/button/new';
import { Drawer, DrawerCloseButton, DrawerContent, DrawerSidebar } from '@atlaskit/drawer/compiled';
import { Box } from '@atlaskit/primitives/compiled';
import { token } from '@atlaskit/tokens';
import { MockDisclaimer } from '../example-helpers/mock-disclaimer';
import {
AsyncSelect,
CreateForm,
LinkCreateExitWarningProvider,
TextField,
useLinkCreateCallback,
useWithExitWarning,
type Validator,
} from '@atlaskit/link-create';
import { type CreatePayload } from '../src/common/types';
import { InlineCreate } from '@atlaskit/link-create/ui';
const fetchMockNetworkRequest = () => {
const search = new URLSearchParams(window.location.search);
if (search.get('disableFetchMock') !== 'true') {
fetchMock.get(
'*',
[
{ label: 'Option 1', value: 'option-1' },
{ label: 'Option 2', value: 'option-2' },
],
{
delay: 20,
overwriteRoutes: false,
},
);
}
};
fetchMockNetworkRequest();
const ENTITY_KEY = 'object-name';
function ExampleCustomPluginForm() {
const { onCreate, onCancel } = useLinkCreateCallback();
type MockOptions = {
label: string;
value: string;
};
type MockedFormData = {
textFieldName?: string | undefined;
asyncSelectName?: MockOptions | null;
};
const mockHandleSubmit = async () => {
if (onCreate) {
await onCreate({
url: 'https://atlassian.com/product/new-object-id',
objectId: 'new-object-id',
objectType: 'object-type',
data: {},
ari: 'example-ari',
});
}
};
const mockValidator: Validator = useMemo(
() => ({
isValid: (val: unknown) => !!val,
errorMessage: 'Validation Error: You need to provide a value.',
}),
[],
);
/**
* Must be stable callback otherwise re-render will trigger re-fetch
*/
const mockLoadOptions = useCallback(async (query: string) => {
const res = await fetch(`/options?filter=${query}`);
if (!res.ok) {
throw res;
}
return res.json();
}, []);
return (
<div>
<MockDisclaimer />
<CreateForm<MockedFormData> onSubmit={mockHandleSubmit} onCancel={onCancel}>
<TextField
name={'textFieldName'}
label={'Enter some Text'}
placeholder={'Type something here...'}
validators={[mockValidator]}
autoFocus
maxLength={255}
/>
<AsyncSelect<MockOptions>
isRequired
isSearchable
name={'asyncSelectName'}
label={'Select an Option'}
validators={[mockValidator]}
loadOptions={mockLoadOptions}
/>
</CreateForm>
</div>
);
}
const exampleCustomPlugin = {
group: {
label: 'test',
icon: 'test-icon',
key: 'mock-plugin',
},
label: 'My Plugin Object',
icon: 'icon',
key: ENTITY_KEY,
form: <ExampleCustomPluginForm />,
};
export default function CreateBasic() {
return (
<LinkCreateExitWarningProvider>
<Example />
</LinkCreateExitWarningProvider>
);
}
const Example = () => {
const withExitWarning = useWithExitWarning();
const [drawerOpen, setDrawerOpen] = useState<boolean>(false);
const [link, setLink] = useState<string | null>();
const [ari, setAri] = useState<string | null>();
const handleCreate = useCallback(async (payload: CreatePayload) => {
await new Promise<void>((resolve) => {
setTimeout(() => resolve(), 2000);
});
console.log('handleCreate payload is:', payload);
setLink(payload.url);
setAri(payload.ari);
setDrawerOpen(false);
}, []);
const handleComplete = useCallback(() => {
console.log('Completed');
setDrawerOpen(false);
}, []);
const handleFailure = useCallback(() => {
console.log('An error');
}, []);
const handleCancel = useCallback(() => {
console.log('Cancelled');
setDrawerOpen(false);
}, []);
const handleDrawerClose = useCallback(() => {
setDrawerOpen(false);
}, []);
return (
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
<div style={{ padding: token('space.250', '20px') }}>
{ari && (
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
<div style={{ marginBottom: token('space.400', '2rem') }}>
<p>ARI: {ari}</p>
</div>
)}
{link && (
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
<div style={{ marginBottom: token('space.200', '1rem') }}>
<a href={link} target="_blank" rel="noopener noreferrer nofollow">
{link}
</a>
</div>
)}
<Drawer
label="Default drawer"
onClose={withExitWarning(handleDrawerClose)}
isOpen={drawerOpen}
width="medium"
>
<DrawerSidebar>
<DrawerCloseButton />
</DrawerSidebar>
<DrawerContent>
<Box paddingInlineEnd="space.250">
<InlineCreate
plugins={[exampleCustomPlugin]}
testId="inline-create"
triggeredFrom="example"
entityKey={ENTITY_KEY}
onCreate={handleCreate}
onComplete={handleComplete}
onFailure={handleFailure}
onCancel={handleCancel}
/>
</Box>
</DrawerContent>
</Drawer>
<Button appearance="primary" onClick={() => setDrawerOpen(true)}>
Open drawer
</Button>
</div>
);
};