The shared Planning Center product Topbar
In general, it is responsible for a number of things that are shared across our apps.
- App switcher (via
AppsProvider
) - Profile switcher (via
ConnectedPeopleProvider
) - Admin notifications inbox dropdown (via
NotificationsMenu
) - Platform and/or product announcements (via
PlatformAnnouncements
andProductAnnouncements
)
To a limited extent, each product can customize the display of these items as they see fit. For the majority of the products, implementation is very similar. You can view product-specific examples below.
The global determination of whether the notifications inbox is enabled or disabled is controlled via a People flipper flag.
However, if your app needs to disable the notifications inbox for whatever reason, set window.notificationsDisabled = true
when your app renders and we'll honor that setting.
Because settings and flags are cached for one load cycle, it will take a fresh reload from the user to have the disable override to take effect.
New in v7.3 is the admin notifications inbox feature. Visually, this is a bell in the topbar that, when clicked, will display a dropdown containing notifications from every app in their account. Because a notification can be sent to a user as they are working on a page, topbar subscribes to a special notifications channel for the current user that provides push updates to the dropdown.
For a handful of reasons (not least of which is because we pay per Jolt connection), if the current page of an app is already using a Jolt connection, topbar would like to piggy-back on that connection for its own subscription. Note that this connection sharing is entirely optional for the parent app, but strongly encouraged. If a Jolt connection is not shared with topbar, it will gladly initiate its own connection specifically for its purposes.
To reuse your app's Jolt connection, topbar requires a Promise that resolves into a Jolt client connection.
If nothing is passed—or if a Promise is passed that resolves to undefined
or void
—topbar will initiate its own connection.
Topbar hooks into pco-page-expired
and will disconnect
when the session is invalidated (i.e. logged out in a different tab).
This occurs for topbar
-created jolt instances as well as jolt instances provided to topbar
via the joltPromise
prop.
CSRF tokens are invalidated when the session is invalidated.
Non-GET APIv2 requests (i.e. auth/subscribe) require a valid CSRF token and will never succeed in that state.
As an example, we'll use what Services has done for the v7.3 alpha.
As a layout partial, they attach two Jolt-related things to window
:
-
joltListener
(an instance of their own wrapper around thejolt-client
library) -
joltListenerPromise
(ajolt-client
instance that has gone through (or will go through asynchronously) the connection and authorization process)
<script>
window.joltListener = new window.JoltListener()
window.joltListenerPromise = window.joltListener.connect()
</script>
In their Topbar implementation component, they then create the function that provides topbar the required Promise...
const waitForWindowJolt = async () =>
window.joltListenerPromise.then(listener => {
if (listener && listener.jolt) {
return listener.jolt
}
})
...and passes that function in for the JoltPromise
prop to topbar.
Topbar can then use this Promise to determine whether or not it needs to create its own connection or piggyback on one provided.
The implementation details for your app can be decided by you and dependent on your apps current usage of Jolt.
yarn add @planningcenter/topbar
cd ~/Code/planningcenter/design/planningcenter/topbar
npm i
npm run dev
The dev
script starts a server at http://localhost:9000.
The dev/dev.tsx
is the main dev environment component.
If you are working on a new feature or run into a specific bug in your application and need to inspect the problem locally before publishing, you can use yalc
to install and run the Topbar package locally.
To get started:
- In
/topbar
:yalc publish
- In your product:
yalc add @planningcenter/topbar
When you make topbar
changes, run yalc publish --push
to update your product.
When you're done, in your product directory run yalc remove @planningcenter/topbar
.
If you are not seeing published updates after running yalc publish --push
or things break in your app after running yalc remove @planningcenter/topbar
then in your product you will want to be running bin/webpack-dev-server
.
From experience, we have seen that the People app will automatically reload while running bin/webpack-dev-server
.
Unfortunately, in the Services app we have had to restart bin/webpack-dev-server
every time for a change to come in.
This may change depending on what app you're adding your yalc changes to.
The jolt-client
library has the ability to log debug information to the console.
This functionality is configured when creating a client and is disabled by default.
If you are passing your own Jolt client object into Topbar via props, you can configure it as required before providing it to Topbar.
If you are relying on Topbar to create its own Jolt connection and would like to enable logging, pass enableJoltLogging={true}
as a prop to whichever topbar components you are using
you are using (AppsProvider
/Tasks
/Toolbar
).
Toolbar lives alongside Topbar on the right side of the viewport. It is responsible for the global concerns of Profile, Help Desk, Tasks, Notifications, and Core Messaging.
Tool implementations are self-contained modules that live in the modules/toolbar/tools/
directory.
A tool module is required to have a named export which adheres to the following ToolbarTool
interface.
interface ToolbarTool {
component: unknown // A React component. Stronger typing is in the works.
icon: string
name: string
}
-
component
: the React component that will be rendered into the drawer when the tool is selected. -
icon
: the name of thetapestry-react
icon that will be rendered as the button in Toolbar. -
name
: the formal name of the tool. For now, this is only surfaced for accessibility concerns on the tool's toolbarButton
.
The following example implements an example tool using the shared DrawerHeader
and DrawerHeading
components.
-
DrawerHeader
handles the placement and styling of the close button. -
DrawerHeading
handles the styling of the drawer heading.
// tool_name.tsx
import { DrawerHeader, DrawerHeading } from "../components"
import { ToolbarTool } from "../types"
function ToolComponent({ closeDrawer }) {
return (
<>
<DrawerHeader closeDrawer={closeDrawer}>
<DrawerHeading>Tool name</DrawerHeading>
</DrawerHeader>
{/* Tool drawer content implementation */}
</>
)
}
const ToolName: ToolbarTool = {
component: ToolComponent,
icon: "general.toolbarIconName",
name: "Tool name",
}
export { ToolName }
These migration docs will guide you thru the process of updating to recent major releases.
This is a limited history. Topbar releases often require a coordinated effort across products and no app can stay an outdated version for very long. So, this CHANGELOG is a little lazy.
If you like history, here are the initial release notes with technical considerations and trade-offs.