Link create
The driver component of meta creation flow
yarn add @atlaskit/link-create
4.0.0
Major Changes
- #119733
cba9cf4f58de0
- ff clean up for emotion to compiled migration. Also removed emotion dependencies. Bump up major version for visibility
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.
The Link Create component is the driver component of meta creation flow. It allows users to create new links without having to leave their current context.
If you have any questions, you can reach out to #help-bandicoots for help.
Installation
yarn add @atlaskit/link-create
Usage
To start the experience mount LinkCreate and set active to true
.
Provide onCreate 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 onComplete. 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 to edit newly created pages).
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.
When the experience is completed
or cancelled
, set active to false
and the experience will transition off the screen before unmounting any visual components.
Provide onCloseComplete to be notified when the experience has been completely transitioned off screen and is safe for complete unmounting of the LinkCreate component.
Examples
Basic Example
Example
import React, { useCallback, useMemo, useState } from 'react';
import fetchMock from 'fetch-mock/cjs/client';
import Button from '@atlaskit/button/new';
import { token } from '@atlaskit/tokens';
import { MockDisclaimer } from '../example-helpers/mock-disclaimer';
import LinkCreate, {
AsyncSelect,
CreateForm,
TextField,
useLinkCreateCallback,
type Validator,
} from '@atlaskit/link-create';
import { type CreatePayload } from '../src/common/types';
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() {
const [link, setLink] = useState<string | null>();
const [ari, setAri] = useState<string | null>();
const [active, setActive] = useState(false);
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);
}, []);
const handleComplete = useCallback(() => {
setActive(false);
}, []);
const handleFailure = useCallback(() => {
console.log('An error');
}, []);
const handleCancel = useCallback(() => {
setActive(false);
}, []);
const handleCloseComplete = useCallback(() => {
console.log('Modal closed');
}, []);
const handleOpenComplete = useCallback(() => {
console.log('Modal opened');
}, []);
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>
)}
<Button testId="link-create-show" appearance="primary" onClick={() => setActive(true)}>
Create
</Button>
<LinkCreate
active={active}
plugins={[exampleCustomPlugin]}
testId="link-create"
triggeredFrom="example"
entityKey={ENTITY_KEY}
onCreate={handleCreate}
onComplete={handleComplete}
onFailure={handleFailure}
onCancel={handleCancel}
onOpenComplete={handleOpenComplete}
onCloseComplete={handleCloseComplete}
/>
</div>
);
}