Developer
News and Updates
Get Support
Sign in
Get Support
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Last updated Nov 2, 2026

global:ui UI Kit components (Preview)

The Forge global:ui module is now in Preview, and therefore fully supported. However, it remains under active development and may be subject to shorter deprecation windows. Preview features are suitable for early adopters in production environments.

The global:ui module currently supports only UI Kit (render: native).

Atlassian releases preview features so partners and developers can study, test, and integrate them before General Availability (GA). For more details, see Forge EAP, Preview, and GA.

The global UI Kit components provide the navigation experience for your app. They deliver a complete layout with a header, sidebar navigation, and main content area that integrates with the Atlassian platform.

You can only use the Global component inside the global:ui module.

These UI Kit components are exported from the @forge/react/global entry point.

Import

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {
  Global,
  Sidebar,
  SidebarFooter,
  LinkMenuItem,
  FlyOutMenuItem,
  SearchableFlyoutMenuItems,
  ExpandableMenuItem,
  MenuSection,
  MenuSpacer,
  ReorderableMenuItems,
  HelpLink,
  PersonalSettings,
  PersonalSettingsItem,
  CreateButton,
  CreateMenuItem,
  Main,
} from "@forge/react/global";

Component hierarchy

The Global component is composed of sub-components that work together to create the full layout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<Global>
├─ <HelpLink />
│
├─ <CreateButton />
│  └─ <CreateMenuItem />
│
├─ <PersonalSettings />
│  └─ <PersonalSettingsItem />
│
├─ <Sidebar />
│  ├─ <MenuSection />
│  │  ├─ <LinkMenuItem />
│  │  ├─ <ReorderableMenuItems />
│  │  ├─ <FlyOutMenuItem />
│  │  └─ <ExpandableMenuItem />
│  │
│  ├─ <LinkMenuItem />
│  │
│  ├─ <MenuSpacer />
│  │
│  ├─ <ReorderableMenuItems />
│  │
│  ├─ <FlyOutMenuItem />
│  │  └─ <LinkMenuItem />
│  │
│  ├─ <SearchableFlyoutMenuItems />
│  │
│  ├─ <ExpandableMenuItem />
│  │  └─ <LinkMenuItem />
│  │
│  └─ <SidebarFooter />
│
└─ <Main />
   └─ {Your app content}

Both <Sidebar> and <Main> must be present as direct children of <Global>.

Layout areas

The component renders three layout areas:

  1. Header — The platform renders the header and uses your manifest for branding. It includes the search component, Rovo Chat, profile, and settings. Add HelpLink, CreateButton, and PersonalSettings to configure optional header actions.
  2. Sidebar — The left navigation panel. When the forYouMenuItem prop is true, the For you menu item is displayed at the top of the sidebar and the app uses the route /for-you as its home page. When the forYouMenuItem prop is false, the For you menu item is hidden and the app uses the root route (/) as its home page. Below it, you configure additional menu items using Sidebar, LinkMenuItem, ExpandableMenuItem, and related components.
  3. Main content — The central area for your app's primary content, wrapped in <Main>.

This illustrative layout uses Atlassian Design System navigation components to show the header, sidebar, and main content areas. The app branding and main content are examples.

Example global UI navigation with a header containing search and Rovo Chat, a sidebar, and a main content area.

Props

Global

The root component that wraps the full layout.

NameTypeRequiredDescription
childrenForgeElementYesAccepts HelpLink, CreateButton, PersonalSettings, Sidebar, and Main.

Renders the left navigation panel.

NameTypeRequiredDescription
childrenForgeElementYesAccepts LinkMenuItem, ExpandableMenuItem, FlyOutMenuItem, SearchableFlyoutMenuItems, MenuSection, MenuSpacer, ReorderableMenuItems, and SidebarFooter.
forYouMenuItembooleanNoWhether to show the built-in For you menu item. Defaults to true. When true, displays the For you item and uses /for-you as the home page. When false, hides the item and uses the root route / as the home page.

LinkMenuItem

A clickable navigation link in the sidebar.

NameTypeRequiredDescription
labelstringYesThe text displayed for the menu item.
hrefstringYesThe route this item navigates to.
iconstringNoThe icon to display before the label. Accepts an Atlassian Design System (ADS) icon glyph name, such as "chart-bar" or "settings", or an image source. If omitted, a default icon is displayed. Always use with a visible label. See Using icons.
iconDarkstringNoThe icon to display instead of icon in dark mode. Accepts the same values as icon. If omitted, icon is also used in dark mode.
activePathstring | string[]NoOne or more glob patterns, such as "/projects/*/settings", that highlight this item when the current path matches. Use this to keep an item highlighted on routes nested below its href.

Highlighting nested paths

By default, a LinkMenuItem is highlighted only when the current path matches its href exactly. Apps with deeply nested routes often need the item to stay highlighted on child routes as well. For example, you might want Projects to stay highlighted while the user navigates to /projects/alpha/boards/3.

The activePath prop takes a glob pattern, or an array of glob patterns, that the platform matches against the current path. If any pattern matches, the item is highlighted. Matching against the item's own href still applies, so you only need to describe the additional paths.

1
2
3
4
5
6
7
<LinkMenuItem
  label="Projects"
  href="/projects"
  icon="folder-closed"
  activePath="/projects/**"
/>

Patterns support the following wildcards:

WildcardDescription
*Matches exactly one path segment (no slashes).
**Matches one or more path segments, at any depth.

Unlike some glob libraries, ** matches one or more path segments, not zero or more. To also match the path itself, combine it with an exact pattern: activePath={["/projects", "/projects/**"]}.

PatternMatchesDoesn't match
"/projects/*"/projects/alpha/projects, /projects/alpha/boards
"/projects/*/settings"/projects/alpha/settings/projects/alpha/boards/settings
"/projects/**"/projects/alpha, /projects/alpha/boards/3/projects
"/projects/**/settings"/projects/alpha/settings, /projects/alpha/boards/3/settings/projects/settings

Using icons

The icon prop accepts either an ADS icon glyph name or an image source.

When you use a glyph name, the platform renders the icon at the correct size and color for the sidebar context. Browse all available glyph names in the ADS icon explorer.

1
2
<LinkMenuItem label="Reports" href="/reports" icon="chart-bar" />

To use your own image, pass one of the following as the icon value:

  • A local image imported from your app's source, such as import reportsIcon from "./reports.svg".
  • A hosted image URL. You must declare the image's host in permissions.external.images in your app's manifest.
  • A data URI.

Use the iconDark prop to provide a different icon for dark mode. If you omit iconDark, the platform uses icon in both light and dark mode.

1
2
3
4
5
6
7
8
9
10
import reportsIcon from "./reports.svg";
import reportsIconDark from "./reports-dark.svg";

<LinkMenuItem
  label="Reports"
  href="/reports"
  icon={reportsIcon}
  iconDark={reportsIconDark}
/>

Use icons to help users identify destinations more quickly. Icons work alongside labels — they don't replace them.

Icon guidelines

Follow these guidelines when choosing icons for sidebar navigation:

  1. Use icons that represent destinations, not actions. Choose icons for places your users navigate to, such as a dashboard, a reports area, or a settings section. Avoid icons that suggest transient actions like create, edit, or run — those belong in buttons and menus.

  2. Always pair icons with labels. The icon prop should always appear alongside a label. Don't rely on an icon alone to communicate a destination — very few icons are universally understood, especially in a custom app context.

  3. Maintain a one-to-one relationship between icons and destinations. Each icon should represent exactly one destination, and each destination should use exactly one icon. Duplicating an icon across two items, or swapping icons for the same item in different parts of the navigation, weakens recognition.

  4. Be mindful of Atlassian's existing navigation icons. Some ADS icons are already associated with specific Atlassian products and navigation areas. Reusing them for unrelated destinations in your app can mislead users into thinking they are navigating to an Atlassian-native area.

The following icons are used in Atlassian's own global navigation for specific primary objects and areas. We recommend avoiding them for destinations that are unrelated to the Atlassian product they represent.

ADS glyph nameADS iconMeaningGuidance
"spaces"spaces iconSpaces directory in ConfluenceAvoid if your destination isn't related to Confluence Spaces.
"project"project iconProjects directory in JiraAvoid if your destination isn't related to Jira Projects.
"goal"goal iconGoal object in the Goals appAvoid if your destination isn't related to Atlassian Goals.
"dashboard"dashboard iconDashboard object in the Home appAvoid if your destination isn't related to Atlassian Dashboards.
"teams"teams iconTeams object in the Teams appAvoid if your destination isn't related to Atlassian Teams.
"person"person icon"For you" landing page in Atlassian navigationAvoid reusing this icon to prevent confusion with Atlassian's "For you" navigation item.
"clock"clock icon"Recent" flyout in Atlassian navigationAvoid to prevent confusion with Atlassian's "Recent" navigation item.
"star-starred", "star-unstarred"star-starred icon star-unstarred icon"Starred" flyout in Atlassian navigationAvoid unless your destination represents starred or favourited content.

Groups sidebar menu items under an optional section header. Use the label prop to display a section header above the items.

NameTypeRequiredDescription
labelstringNoThe section header displayed above the grouped menu items.
childrenForgeElementYesAccepts LinkMenuItem, ExpandableMenuItem, FlyOutMenuItem, and ReorderableMenuItems components.

Adding a section header

Use MenuSection with a label prop to add a section header for menu items in the sidebar.

1
2
3
4
5
6
7
8
9
<Global>
  <Sidebar>
    <MenuSection label="Your homes">
      <LinkMenuItem label="Home 1" href="/home-1" />
      <LinkMenuItem label="Home 2" href="/home-2" />
    </MenuSection>
  </Sidebar>
</Global>

Adds vertical spacing between groups of top-level sidebar items. It has no props and can only be used as a direct child of Sidebar.

1
2
3
4
5
6
7
8
9
10
11
<Global>
  <Sidebar>
    <LinkMenuItem label="Overview" href="/overview" />
    <LinkMenuItem label="Recent" href="/recent" />

    <MenuSpacer />

    <LinkMenuItem label="Settings" href="/settings" />
  </Sidebar>
</Global>

SidebarFooter

Adds content to the bottom of the sidebar. The footer stays at the bottom of the sidebar, below the menu items. Use it as a direct child of Sidebar.

NameTypeRequiredDescription
childrenForgeElementYesThe content to display at the bottom of the sidebar.

Add content to the bottom of the sidebar

The following example adds a feedback link to the bottom of the sidebar. Use the UI Kit Link component for links inside the footer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { Link, Text } from "@forge/react";
import {
  Global,
  Sidebar,
  SidebarFooter,
  LinkMenuItem,
  Main,
} from "@forge/react/global";

<Global>
  <Sidebar>
    <LinkMenuItem label="Overview" href="/overview" />
    <LinkMenuItem label="Reports" href="/reports" />

    <SidebarFooter>
      <Text>
        Have an idea?{" "}
        <Link href="https://example.com/feedback" openNewTab>
          Share feedback
        </Link>
      </Text>
    </SidebarFooter>
  </Sidebar>
  <Main>
    <Text>Select an item from the sidebar.</Text>
  </Main>
</Global>

ExpandableMenuItem

An expandable menu item that reveals nested links when selected.

NameTypeRequiredDescription
labelstringYesThe text displayed for the expandable menu item.
childrenForgeElementYesAccepts LinkMenuItem, ExpandableMenuItem (can be nested up to 3 levels), and ReorderableMenuItems.

FlyOutMenuItem

A sidebar item that opens a flyout containing nested menu items.

NameTypeRequiredDescription
labelstringYesThe text displayed for the flyout menu item.
childrenForgeElementYesAccepts nested sidebar menu items, such as LinkMenuItem.

SearchableFlyoutMenuItems

A sidebar item that opens a searchable flyout. Use it when users need to find an item in a dynamic or long list, such as spaces, projects, or customers.

SearchableFlyoutMenuItems uses a controlled data flow:

  1. Provide the sections currently displayed through items.
  2. When a user changes the search input, onSearchTextChanged receives { searchText }.
  3. Filter or fetch the matching data in your app, then render the resulting sections through items again.
NameTypeRequiredDescription
labelstringYesThe label displayed for the sidebar item that opens the flyout.
items
1
2
3
4
{
  header: string;
  items: { primaryLabel: string; href: string }[];
}[]
YesThe sections currently displayed in the flyout. Each section requires a header string. For standard link results, each entry in the inner items array should be an object with a primaryLabel string and an app-relative href string.
onSearchTextChanged
1
2
3
(params: {
  searchText?: string;
}) => void
YesCalled when the user changes the search input. Update the data supplied through items in response.
searchPlaceholderstringNoPlaceholder text for the search input. Defaults to Search.

The following example filters a fixed list of spaces. In an app backed by remote data, use searchText to fetch the next matching sections instead.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import React, { useState } from "react";
import ForgeReconciler, { Text } from "@forge/react";
import {
  Global,
  Main,
  SearchableFlyoutMenuItems,
  Sidebar,
} from "@forge/react/global";

const allSpaces = [
  { primaryLabel: "Design", href: "spaces/design" },
  { primaryLabel: "Engineering", href: "spaces/engineering" },
  { primaryLabel: "Marketing", href: "spaces/marketing" },
];

const initialSections = [{ header: "Spaces", items: allSpaces }];

const App = () => {
  const [sections, setSections] = useState(initialSections);

  const handleSearchTextChange = ({ searchText = "" }) => {
    const query = searchText.trim().toLowerCase();
    const matchingSpaces = allSpaces.filter(({ primaryLabel }) =>
      primaryLabel.toLowerCase().includes(query)
    );

    setSections(
      matchingSpaces.length > 0
        ? [{ header: "Spaces", items: matchingSpaces }]
        : []
    );
  };

  return (
    <Global>
      <Sidebar>
        <SearchableFlyoutMenuItems
          label="Spaces"
          items={sections}
          onSearchTextChanged={handleSearchTextChange}
          searchPlaceholder="Search spaces"
        />
      </Sidebar>
      <Main>
        <Text>Select a space from the sidebar.</Text>
      </Main>
    </Global>
  );
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

For link results, use app-relative values such as spaces/design, not tenant-root values such as /spaces/design. The platform resolves the result relative to your Global App installation URL.

ReorderableMenuItems

Groups sidebar menu items that users can reorder. Use this when your app supports a customizable navigation order.

The component renders items in the order you pass to items, and doesn't change that order on its own. Your app is responsible for handling every reorder. Update items in onReorder to show the new order, and restore the previous order in onError if saving the new order fails.

NameTypeRequiredDescription
items
1
2
3
4
5
6
7
8
9
{
  id: string;
  label: string;
  href: string;
  icon?: string;
  iconDark?: string;
  activePath?:
    string | string[]
}[]
YesAn ordered array of sidebar menu items that the user can reorder. Each item must include a stable, unique id, a display label, and a navigation href. Each item can also include an icon and an iconDark, which accept the same values as the LinkMenuItem props of the same names, and an activePath glob pattern, or array of patterns, that highlights the item on nested routes. See Using icons and Highlighting nested paths.
onReorder
1
2
3
(
  newItems: Item[]
) => void | Promise<void>
NoA callback invoked after the user finishes dragging an item. Receives the items in their new order. Use it to update the items you pass to the component and to save the new order. Can return a promise.
onError
1
2
3
4
5
(
  error: unknown,
  previousItems: Item[],
  newItems: Item[]
) => void
NoA callback invoked if onReorder throws an error or returns a rejected promise. Receives the error, the item order before the drag, and the new item order that failed to save. Use this callback to restore the previous order and to show an error message.

Main

Wraps your app's main content area.

NameTypeRequiredDescription
childrenForgeElementYesYour app's main content.

Renders a help link in the app header. Use it to direct users to support, documentation, or other help resources for your app.

NameTypeRequiredDescription
hrefstringYesThe URL opened when the help link is selected.

PersonalSettings

Renders a personal settings menu in the app header. Use it to group user-specific settings and account actions.

NameTypeRequiredDescription
childrenForgeElementYesAccepts PersonalSettingsItem components.

PersonalSettingsItem

Renders an item inside the personal settings menu.

NameTypeRequiredDescription
labelstringYesThe text displayed for the settings item.
onClick() => voidYesThe handler called when the item is selected.

CreateButton

Renders a create button in the app header. Use it to expose one or more create actions for your app.

NameTypeRequiredDescription
childrenForgeElementYesAccepts CreateMenuItem components.

CreateMenuItem

Renders an item inside the create menu.

NameTypeRequiredDescription
labelstringYesThe text displayed for the create action.
onClick() => voidYesThe handler called when the item is selected.

The platform renders the header automatically, using your manifest for app branding. You can configure additional header actions with HelpLink, CreateButton, and PersonalSettings. The header uses the following properties from your manifest:

Manifest propertyDescription
titleDisplayed in the header next to your app icon. Also appears in the Atlassian app switcher.
iconA reference to a local SVG resource, in the format resource:<resource-key>;<filename>. Displayed as a 24×24px icon in the header. In smaller viewports, only the icon is shown.

The header automatically includes:

  • App name and logo — From your manifest title and icon. Selecting it navigates to the root route (/).
  • Search — Lets users search platform-indexed content, prioritizing Teamwork Graph objects associated with your app. See Search.
  • Settings — A settings dropdown for app-specific personal settings and app admins to access the manage apps console. See personal settings.
  • Rovo Chat — Opens the Rovo AI chat sidebar. See Rovo Chat.
  • Profile — Opens the user's profile and account settings.

For details on configuring title and icon, see the global:ui module reference.

The header includes the Rovo search bar. It searches content indexed by the platform, including content from Atlassian apps linked to your app.

Improve search relevance

Search prioritizes results from Teamwork Graph objects associated with the global:ui app the user is viewing. To make your app's content discoverable and improve relevance, ingest it into Teamwork Graph using the Teamwork Graph connector.

Build a custom search results page

When a user presses Enter in the search bar, the platform navigates to your app's /search?text=<input> route. Implement a page at /search to provide a search results experience suited to your app:

  1. Read the search query from the text query parameter.
  2. Use the query to retrieve results from your app's data sources.
  3. Display the results in your app's main content area, including a useful empty state when there are no matches.

For example, a search for roadmap navigates to /search?text=roadmap within your app.

Rovo Chat

Users can open the Rovo chat sidebar from the header for AI assistance alongside your app. To connect actions in your app to contextual conversations, explore the Forge bridge rovo API. It provides methods to check whether Rovo is enabled and to open a conversation with an optional prompt or a specific agent. See the API reference for supported modules and agent requirements before integrating it into your app.

Requirements and limitations

  • <Sidebar> and <Main> must both be present as direct children of <Global>.
  • Custom UI is not supported for global:ui. The platform enforces a UI chrome around the module and UI Kit is the public API that lets you control it. To embed custom web content in the main content area, use the Frame component inside <Main>.
  • By default, the sidebar displays the platform-provided For you item as its first entry. This item navigates to the route /for-you and acts as the app's home page. Set forYouMenuItem to false to hide it; the app then uses the root route / as the home page.

Usage example

The following example assembles a complete app layout with header actions, sidebar navigation, and main content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import React, { useState } from "react";
import ForgeReconciler, { Button, Text, Link } from "@forge/react";
import { invoke } from "@forge/bridge";
import {
  Global,
  Sidebar,
  LinkMenuItem,
  ExpandableMenuItem,
  MenuSection,
  MenuSpacer,
  HelpLink,
  PersonalSettings,
  PersonalSettingsItem,
  ReorderableMenuItems,
  SidebarFooter,
  CreateButton,
  CreateMenuItem,
  FlyOutMenuItem,
  Main,
} from "@forge/react/global";

const initialNotes = [
  { id: "1", label: "Document 1", href: "/notes/1" },
  { id: "2", label: "Document 2", href: "/notes/2" },
  { id: "3", label: "Document 3", href: "/notes/3" },
];

const App = () => {
  const [notes, setNotes] = useState(initialNotes);
  const [message, setMessage] = useState(
    "Select an action from the header or sidebar."
  );

  const handleCreateDocument = () => {
    setMessage("Create document selected.");
  };

  const handleCreateFolder = () => {
    setMessage("Create folder selected.");
  };

  return (
    <Global>
      <HelpLink href="https://support.atlassian.com/" />

      <CreateButton>
        <CreateMenuItem
          label="Create document"
          onClick={handleCreateDocument}
        />
        <CreateMenuItem label="Create folder" onClick={handleCreateFolder} />
      </CreateButton>

      <PersonalSettings>
        <PersonalSettingsItem
          label="Notification preferences"
          onClick={() => {}}
        />
        <PersonalSettingsItem label="Preferences" onClick={() => {}} />
      </PersonalSettings>

      <Sidebar>
        <LinkMenuItem label="Reports" href="/reports" icon="chart-bar" />
        <LinkMenuItem label="Recent" href="/recent" />

        <MenuSpacer />

        <MenuSection label="Dashboards">
          <LinkMenuItem
            label="Sales performance"
            href="/reports/sales-performance"
          />
          <LinkMenuItem label="Team activity" href="/reports/team-activity" />
        </MenuSection>

        <ExpandableMenuItem label="Projects">
          <LinkMenuItem label="Project Alpha" href="/projects/alpha" />
          <LinkMenuItem label="Project Beta" href="/projects/beta" />
        </ExpandableMenuItem>

        <MenuSpacer />

        <FlyOutMenuItem label="Resources">
          <LinkMenuItem label="Documentation" href="/resources/docs" />
          <LinkMenuItem label="Templates" href="/resources/templates" />
        </FlyOutMenuItem>

        <ReorderableMenuItems
          items={notes}
          onReorder={async (items) => {
            setNotes(items);
            await invoke("saveNoteOrder", {
              ids: items.map((item) => item.id),
            });
            setMessage("Sidebar order saved.");
          }}
          onError={(error, previousItems, newItems) => {
            console.error("Failed to save sidebar order:", error);
            setNotes(previousItems);
            setMessage("Couldn't save the new sidebar order.");
          }}
        />

        <SidebarFooter>
          <Link href="https://support.atlassian.com/" openNewTab>
            Get support
          </Link>
        </SidebarFooter>
      </Sidebar>

      <Main>
        <Text>{message}</Text>
        <Button onClick={() => setMessage("Main content action selected.")}>
          Run action
        </Button>
      </Main>
    </Global>
  );
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Using dispatch to update state

If using a Frame component in the Main content area of your app, there may be scenarios where you want to update the app state from within the Frame resource. You can use the dispatch prop to define a function that can be called inside the resource to dispatch updates to its parent.

The implementation of the function is up to you, although the most common pattern will be to use a reducer and its associated dispatch function. This is a pattern that will be familiar to developers who have used React’s useReducer hook or React Redux for complex state management.

Example

Main app

In the main app, the sidebar state is defined as a list of items that can be dynamically updated, with each item mapping to a LinkMenuItem component. The reducer is a pure function that updates the state based on a provided action, which it receives when dispatch is called.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import React, { useReducer } from "react";
import ForgeReconciler, { Frame } from "@forge/react";
import {
  Global,
  Sidebar,
  LinkMenuItem,
  LinkMenuItemProps,
  Main,
} from "@forge/react/global";

interface GlobalState {
  sidebar: LinkMenuItemProps[];
}

type AddSidebarItemAction = {
  type: "ADD_SIDEBAR_ITEM";
  item: LinkMenuItemProps;
};
const initialState: GlobalState = {
  sidebar: [
    { id: "dashboard", label: "Dashboard", href: "/dashboard" },
    { id: "bugs", label: "Bugs", href: "/bugs" },
    {
      id: "reports",
      label: "Reports",
      type: "expandable",
      children: [
        { id: "weekly", label: "Weekly", href: "/reports/weekly" },
        { id: "monthly", label: "Monthly", href: "/reports/monthly" },
      ],
    },
  ],
};

const reducer = (state: GlobalState, action: Action) => {
  switch (action.type) {
    case "ADD_SIDEBAR_ITEM":
      return {
        ...state,
        sidebar: [...state.sidebar, action.item],
      };
    default:
      return state;
  }
};

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <Global>
      <Sidebar>
        {state.sidebar.map((item) => (
          <LinkMenuItem key={item.id} {...item} />
        ))}
      </Sidebar>
      <Main>
        <Frame resource="my-resource" dispatch={dispatch} />
      </Main>
    </Global>
  );
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

my-resource

Inside the resource, the dispatch function can be retrieved by calling view.getFrameDispatch(). It can then be used to dispatch actions to the parent to trigger state updates. In this example, it adds a new Settings item to the sidebar menu.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import React, { useEffect, useState } from "react";
import { view } from "@forge/bridge";

const App = () => {
  const [dispatch, setDispatch] = useState(undefined);

  useEffect(() => {
    view
      .getFrameDispatch()
      // If using React to set the dispatch function in state to use, make sure to
      // wrap it in a callback. Otherwise React treats dispatch as a functional updater
      // and tries to execute it.
      .then((dispatch) => setDispatch(() => dispatch));
  }, []);

  if (!dispatch) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <button
        onClick={() =>
          dispatch({
            type: "ADD_SIDEBAR_ITEM",
            item: {
              id: "settings",
              label: "Settings",
              href: "/settings",
            },
          })
        }
      >
        Add Settings menu item
      </button>
    </div>
  );
};

export default App;

Rate this page: