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

Array
group required
label string required
icon string required
key string required
label string required
icon string required
key required Key
form required react.ReactNode
editView { 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> ); };