-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(storybook): Add guide for adding tabs to a scene #18495
Changes from all commits
360918a
ca4ded4
56d57f6
134b4c9
359bade
fdfb050
1641246
a78d1ac
e7f731b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { Meta } from '@storybook/addon-docs' | ||
|
||
<Meta title="How to add tabs to a scene?" /> | ||
|
||
# How to add tabs to a scene? | ||
|
||
This guide follows on from ["How to build a scene"](/docs/how-to-build-a-scene--page) – we'll be adding tabs to that guide's Dashboards scene. | ||
|
||
1. Add an enum to `frontend/src/types.ts` to define the tabs: | ||
|
||
```ts title="frontend/src/types.ts" | ||
export enum DashboardsTabs { | ||
First = 'first', | ||
Second = 'second', | ||
} | ||
``` | ||
|
||
2. Update the URL in `frontend/src/scenes/urls.ts` to handle tabs: | ||
|
||
```ts title="frontend/src/scenes/urls.ts" | ||
dashboards: (tab: DashboardsTabs = DashboardsTabs.First): string => `/dashboards/${tab}` | ||
``` | ||
|
||
3. Add entries to routes per tab in `frontend/src/scenes/scenes.ts`: | ||
|
||
```ts title="frontend/src/scenes/scenes.ts" | ||
// One entry for every available tab | ||
...Object.fromEntries( | ||
Object.values(DashboardsTabs).map((tab) => [urls.dashboards(tab), Scene.Dashboards]) | ||
) as Record<string, Scene>, | ||
Comment on lines
+28
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
``` | ||
|
||
4. Update `dashboardsLogic` to handle tab changes in `frontend/src/scenes/dashboards/dashboardsLogic.ts`: | ||
|
||
```ts title="frontend/src/scenes/dashboards/dashboardsLogic.ts" | ||
import { kea, reducers, path, actions, selectors } from 'kea' | ||
import { actionToUrl, urlToAction } from 'kea-router' | ||
|
||
import { Breadcrumb, DashboardsTabs } from '~/types' | ||
import { urls } from 'scenes/urls' | ||
|
||
export const DASHBOARDS_TAB_TO_NAME: Record<DashboardsTabs, string> = { | ||
[DashboardsTabs.First]: 'First', | ||
[DashboardsTabs.Second]: 'Second', | ||
} | ||
Comment on lines
+42
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was slightly overkill for this to be a function, so turned into an object |
||
|
||
export const dashboardsLogic = kea([ | ||
path(['scenes', 'dashboard', 'dashboardsLogic']), | ||
actions({ | ||
setCurrentTab: (tab: DashboardsTabs = DashboardsTabs.First) => ({ tab }), | ||
}), | ||
reducers({ | ||
currentTab: [ | ||
DashboardsTabs.First as DashboardsTabs, | ||
{ | ||
setCurrentTab: (_, { tab }) => tab, | ||
}, | ||
], | ||
}), | ||
selectors(() => ({ | ||
breadcrumbs: [ | ||
// Optional if you'd like the breadcrumbs to show the current tab | ||
(s) => [s.currentTab], | ||
(tab): Breadcrumb[] => { | ||
const breadcrumbs: Breadcrumb[] = [{ name: 'Dashboards' }] | ||
breadcrumbs.push({ | ||
name: DASHBOARDS_TAB_TO_NAME[tab], | ||
}) | ||
|
||
return breadcrumbs | ||
}, | ||
], | ||
})), | ||
actionToUrl(({ values }) => { | ||
return { | ||
setCurrentTab: () => [urls.dashboards(values.currentTab)], | ||
} | ||
}), | ||
urlToAction(({ actions, values }) => ({ | ||
'/dashboards/:tab': ({ tab }) => { | ||
if (tab !== values.currentTab) { | ||
actions.setCurrentTab(tab as DashboardsTabs) | ||
} | ||
}, | ||
})), | ||
]) | ||
``` | ||
|
||
5. Update `frontend/src/scenes/dashboards/Dashboards.tsx` to render the tabs: | ||
|
||
```tsx title="frontend/src/scenes/dashboards/Dashboards.tsx" | ||
import { useValues } from 'kea' | ||
import { router } from 'kea-router' | ||
import { DashboardsTabs } from '~/types' | ||
import { SceneExport } from 'scenes/sceneTypes' | ||
import { PageHeader } from 'lib/components/PageHeader' | ||
import { LemonTabs } from 'lib/lemon-ui/LemonTabs/LemonTabs' | ||
import { urls } from 'scenes/urls' | ||
import { dashboardsLogic, humanFriendlyTabName } from './dashboardsLogic' | ||
|
||
const DASHBOARDS_TAB_TO_CONTENT: Record<DashboardsTabs, JSX.Element> = { | ||
[DashboardsTabs.First]: <div>First tab content</div>, | ||
[DashboardsTabs.Second]: <div>Second tab content</div>, | ||
} | ||
|
||
export const scene: SceneExport = { | ||
component: Dashboards, | ||
logic: dashboardsLogic, | ||
} | ||
|
||
export function Dashboards(): JSX.Element { | ||
const { currentTab } = useValues(dashboardsLogic) | ||
|
||
return ( | ||
<div> | ||
<PageHeader title="Dashboards" /> | ||
<LemonTabs | ||
activeKey={currentTab} | ||
onChange={(tab) => router.actions.push(urls.dashboards(tab as DashboardsTabs))} | ||
tabs={Object.values(DashboardsTabs).map((tab) => ({ | ||
label: DASHBOARDS_TAB_TO_NAME[tab], | ||
key: tab, | ||
content: DASHBOARDS_TAB_TO_CONTENT[tab], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For non-advanced use cases it's better for the content to be part of the tab's definition There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you bring me examples of when it's advanced vs not? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically if you want to put some extra UI between the tab bar and tab contents. Otherwise, it's simpler to keep everything together, |
||
}))} | ||
/> | ||
</div> | ||
) | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default args > optional args with a constant fallback deeper in the function