This guide is only applicable for migrating existing UI Kit 1 apps to the latest version of UI Kit.
The latest version of UI Kit is now generally available. This version comes with a new major version of @forge/react
containing 37 updated components.
Use this guide to know what you need to do to move your apps from UI Kit 1 to the latest version of UI Kit.
We’re introducing a new manifest structure for UI Kit. In the new structure, you need to make the following changes:
render: native
to each module.resource
key to point to the frontend, instead of function
.resolver
key with function
properties to point to any resolvers.Your manifest.yml
file should look like the following:
1 2modules: confluence:globalPage: - key: hello-world resource: main route: hello-world resolver: function: resolver render: native title: UI Kit App function: - key: resolver handler: index.handler resources: - key: main path: src/frontend/index.jsx
The component APIs in UI Kit are very different to UI Kit 1 components. The breaking changes are:
Avatar
component has been removed. Use the User component instead.AvatarStack
component has been removed. Use the UserGroup component instead.text
prop for Badge
has been removed. Content is now placed in children
instead.Badge
has a new max
prop that defaults to the value of 99. It renders badge content as 99+
for values greater than 99.text
prop for Badge
has been removed. Content is now placed in children
instead.appearance
prop no longer accepts link
and subtle-link
types, as we now have LinkButton
.disabled
prop has been renamed to isDisabled
.icon
and iconPosition
have been updated to iconBefore
and iconAfter
. iconBefore
and iconAfter
take the same values as icon and will position before and after the button children contents accordingly.ButtonSet
component has been replaced by ButtonGroup. There are no other breaking changes outside of the component name change.children
, description
and label
props have been removed. The options
prop, HelperMessage
component, and Label
component can be used instead.1 2const CheckboxGroup = () => { return ( <Label labelFor="products">Products</Label> <CheckboxGroup name="products" options={[ { label: 'Jira', value: 'jira' }, { label: 'Confluence', value: 'confluence' }, ]} /> <HelperMessage>Pick a product</HelperMessage> ) }
Code
has now been separated into two different components:
Code
should now only be used for inline code.Code
is for inline code, the language
prop has been removed.text
prop has been removed. Content is now placed in children
instead.DateLozenge
has been removed and can be replaced by using Lozenge.description
and label
props have been removed. Use the HelperMessage
and Label
components instead.1 2<Label labelFor="start-date">Start date</Label> <DatePicker id="start-date" /> <HelperMessage>Enter a start date</HelperMessage>
submitButtonAppearance
, submitButtonText
and actionButtons
props have been removed. A Button
component with the type="submit"
prop must be used within your Form
component to allow for form submissions.Form
component should now be used with the useForm hook for state management. See example usage.FormCondition
component has been removed. Use the useForm hook for state management. See example usage.size
prop has been removed. The as
prop is now required instead. Heading accessibility guidelines should be followed.appearance
prop has been removed. Use LinkButton instead for button
and primary-button
types.ModalDialog
component has been replaced by Modal.closeButtonText
and header
props have been removed.
Button
component will need to be rendered to close Modal
.header
prop has been replaced by ModalTitle
.defaultChecked
prop has been removed. Use isChecked
instead.children
, description
and label
props have been removed. The options
prop, HelperMessage
component, and Label
component can be used instead.RadioGroup
can now be used regardless of whether it is in a Form
or in the config for the macro
extension point.1 2<Label labelFor="color">Color</Label> <RadioGroup id="color" options={[ { name: 'color', value: 'red', label: 'Red' }, { name: 'color', value: 'blue', label: 'Blue' }, { name: 'color', value: 'yellow', label: 'Yellow' }, ]} /> <HelperMessage>Pick a color</HelperMessage>
Range
can now be used anywhere, so the isRequired
prop is will not be checked if it is not in a formRange
spans the full width of its container.label
prop has been removed. Use the Label
component instead.1 2<Label labelFor="range">Range</Label> <Range id="range" />
appearance
prop now takes different values.
information
replaces info
.success
replaces confirmation
.discovery
replaces change
.children
prop has been replaced by options
.description
and label
props have been removed. Use the HelperMessage
and Label
components instead.defaultSelected
prop in Option
has been removed. Use defaultValue
in Select
instead.1 2<Label labelFor="fruit">Favourite fruit</Label> <Select inputId="fruit" options={[ { label: 'Apple', value: 'apple' }, { label: 'Banana', value: 'banana' }, ]} /> <HelperMessage>Pick a fruit</HelperMessage>
StatusLozenge
component has been replaced by Lozenge.text
prop has been removed. Content is now placed in the children
prop of the Lozenge
component instead.Table
component has been replaced by DynamicTable.Head
, Row
, and Cell
components have been removed in favor of data being passed in via arrays and objects.Tabs
has been updated. See an example of the new Tabs component.greyLight
replaces grey-light
.Text area
spans the full width of its container.description
and label
props have been removed. Use the HelperMessage
and Label
components instead.1 2<Label labelFor="message">Message</Label> <TextArea id="message" /> <HelperMessage>Enter a message</HelperMessage>
TextField
to Textfield
.Textfield
spans the full width of its container.autoComplete
prop has been removed.description
and label
props have been removed. Use the HelperMessage
and Label
components instead.1 2<Label labelFor="email">Email</Label> <Textfield id="email" /> <HelperMessage>Enter an email</HelperMessage>
label
prop has been removed. Use the Label
component instead.1 2<Label labelFor="toggle">Toggle</Label> <Toggle id="toggle" />
text
prop has been replaced by content
.UserPicker
spans the full width of its container.The following components are available in the latest version of UI Kit:
We now have components that support the xcss
prop. See the XCSS documentation for more information.
Each module in UI Kit 1 requires a top-level component at the root of the app. These components have been removed in UI Kit and do not need to be included in your app anymore. For most modules, the top-level component has no props and can simply be deleted.
1 2import ForgeUI, { AdminPage, render, Text} from '@forge/ui'; const App = () => { return ( <AdminPage> <Text>Hello, world!</Text> </AdminPage> ); }; export const run = render( <App/> );
1 2import React from 'react'; import ForgeReconciler, { Text } from '@forge/react'; const App = () => { return ( <Text>Hello, world!</Text> ); }; ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
Some modules allow a value to be submitted, where the top-level component has an onSubmit
prop
and the value gets automatically updated when the form is submitted. For example, Jira’s
Custom Field and Dashboard Gadget modules both have edit
entrypoints.
The removal of the top-level components in UI Kit means field values in these modules now have
to be manually submitted by the developer via the submit API
on @forge/bridge
.
For a complete guide on how to add macro configuration in UI Kit, see Add configuration to a macro with UI Kit. The section below highlights some key differences between UI Kit 1 and UI Kit.
The config property in the macro module is a boolean instead of a function reference.
1 2modules: macro: - key: pet-facts function: main title: Pet description: Inserts facts about a pet config: function: config-function-key function: - key: main handler: index.run - key: config-function-key handler: index.config
1 2modules: macro: - key: pet-facts resource: main render: native resolver: function: resolver title: Pet config: true function: - key: resolver handler: index.handler
The allowed form components in config no longer have the label
prop. To add a label,
a Label
component must be added as a direct sibling before the form component.
The MacroConfig
top-level component has also been removed from UI Kit.
1 2import ForgeUI, { MacroConfig, TextField, render } from '@forge/ui'; const Config = () => { return ( <MacroConfig> <Textfield name="age" label="Pet age" /> </MacroConfig> ); }; export const config = render(<Config />);
1 2import React from 'react'; import ForgeReconciler, { Label, Textfield } from '@forge/react'; const Config = () => { return ( <> <Label>Pet age</Label> <Textfield name="age" /> </> ); }; ForgeReconciler.addConfig(<Config />);
Not all hooks in UI Kit 1 exist in the latest version of UI Kit.
In the latest version, implementations of standard react
library hooks (useState
, useEffect
and useAction
) can now be directly accessed from react
instead (see Hooks API Reference – React). Note that the react
equivalent of UI kit's useAction
is useReducer
, although usage of both is identical.
The useProductContext
, useConfig
, useContentProperty
, useSpaceProperty
and useIssueProperty
hooks are available in our @forge/react
package.
In addition, since the context/configuration/property values outputted by the @forge/react
hooks take time to load, they will not be immediately available upon app mounting; i.e. these values will initially be undefined
before they are loaded with actual values.
UI Kit 1
1 2import ForgeUI, { useState, useEffect, useAction, Text } from '@forge/ui'; const App = () => { const [count, setCount] = useState(0); return ( <Text>Count: {count}</Text> ); }
Latest version of UI Kit
1 2import React, { useState, useEffect, useReducer } from 'react'; import { Text } from '@forge/react'; const App = () => { const [count, setCount] = useState(0); return ( <Text>Count: {count}</Text> ); };
The @forge/api
package cannot be used in frontend code, so some API calls in UI Kit 1 need to be moved into a resolver. For example, to call the Storage API, a function is defined in the resolver to call the API, and the frontend invokes the resolver with the invoke
function on @forge/bridge
.
src/resolvers/index.js
1 2import { storage, startsWith } from '@forge/api'; import Resolver from '@forge/resolver'; const resolver = new Resolver(); resolver.define('getExampleValue', async ({ payload, context }) => { return await storage.get('example-key'); }); export const handler = resolver.getDefinitions();
src/frontend/index.jsx
1 2import React, { useEffect, useState } from 'react'; import ForgeReconciler, { Text } from '@forge/react'; import { invoke } from '@forge/bridge'; const App = () => { const [value, setValue] = useState(undefined); useEffect(() => { invoke('getExampleValue').then(setValue); }, []); return ( <Text>{value}</Text> ) };
You'll notice that the folder structure is now different. The following modifications have been made:
src/resolvers/index.js
has been added. This is where you write backend functions for your app, such as the API calls mentioned above.index.jsx
is now at src/frontend/index.jsx
. This is where you write the application with which the user interacts directly.UI Kit
1 2ui-kit-app ├── README.md ├── manifest.yml ├── package-lock.json ├── package.json └── src ├── frontend │ └── index.jsx ├── index.js └── resolvers └── index.js
UI Kit 1
1 2ui-kit-1-app ├── README.md ├── manifest.yml ├── package-lock.json ├── package.json └── src └── index.jsx
Jira and Confluence API requests can be made from the app frontend via @forge/bridge
instead of @forge/api
.
1 2import api, { route } from '@forge/api'; await api.asUser() .requestJira(route`/rest/api/3/issue/${issueKey}/watchers`, { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json" }, body: JSON.stringify(`${accountId}`) });
1 2import { requestJira } from '@forge/bridge'; await requestJira(`/rest/api/3/issue/${issueKey}/watchers`, { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json" }, body: JSON.stringify(`${accountId}`) });
@forge/bridge
only performs requests authorized as the user (asUser
). Requests that need to authorize as the app (asApp
) still require @forge/api
and must be called from a resolver. @forge/bridge
also does not support requestBitbucket
or requestGraph
yet, so a resolver is required to call these APIs as well.
In order for UI Kit macros to export to word document, page history, or via the REST API, you need to specify an adfExport
function in your app's manifest.yml
file. This function should return a representation of the macro in Atlassian document format.
Previously on UI Kit 1, macros would export without an export
function defined. Due to technical limitations, this is not the case for UI Kit. You must define an adfExport
function in your app's manifest.yml
file in order for the macro to export successfully for all export types, with the exception of pdf export.
If your app has an export
property in the manifest, you need to rename it to adfExport
and update the underlying export function accordingly.
1 2modules: macro: export: function: exportFunction function: - key: exportFunction handler: index.macroExport
1 2modules: macro: adfExport: function: exportFunction function: - key: exportFunction handler: index.macroExport
Here is an example of an update to the export
function that you might make:
1 2import ForgeUI, { render, Text } from "@forge/ui"; export const macroExport = render( <Text>`Hello world! This is the export view for my macro.`</Text> );
1 2import { doc, p } from '@atlaskit/adf-utils/builders'; export const macroExport = async (payload) => { return doc( p(`Hello world! This is the export view for my macro.`) ); }
Check out this migration example where the Issue Translation UI Kit 1 app is transformed into UI Kit.
If you are migrating a published UI Kit 1 app, keep the same app ID to retain your license.
Rate this page: