fix(ux): add back replay project notice, add right slot to tabs (#39167)

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Adam Leith
2025-10-06 13:50:58 +01:00
committed by GitHub
parent 32b7115c49
commit f4af41ec52
25 changed files with 72 additions and 55 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -1,5 +1,4 @@
.LemonTabs {
--lemon-tabs-gap: 2rem;
--lemon-tabs-margin-bottom: 1rem;
--lemon-tabs-content-padding: 0.75rem 0;
@@ -9,7 +8,6 @@
align-self: stretch;
&--small {
--lemon-tabs-gap: 1rem;
--lemon-tabs-margin-bottom: 0.5rem;
--lemon-tabs-content-padding: 0.375rem 0;
}
@@ -25,8 +23,8 @@
display: flex;
flex-direction: row;
flex-shrink: 0;
gap: var(--lemon-tabs-gap);
align-items: stretch;
justify-content: space-between;
margin-bottom: var(--lemon-tabs-margin-bottom);
overflow-x: auto;
list-style: none;

View File

@@ -1,6 +1,7 @@
import { Meta, StoryFn, StoryObj } from '@storybook/react'
import { useState } from 'react'
import { LemonButton } from '../LemonButton'
import { LemonTab, LemonTabs as LemonTabsComponent } from './LemonTabs'
type Story = StoryObj<typeof LemonTabsComponent>
@@ -55,3 +56,12 @@ Default.args = {}
export const Small: Story = Template.bind({})
Small.args = { size: 'small' }
export const RightSlot: Story = Template.bind({})
RightSlot.args = {
rightSlot: (
<LemonButton type="secondary" size="small">
Right slot
</LemonButton>
),
}

View File

@@ -38,6 +38,8 @@ export interface LemonTabsProps<T extends string | number> {
barClassName?: string
className?: string
sceneInset?: boolean
/** Pass in JSX to be sticky to the right of the tabs. */
rightSlot?: React.ReactNode
}
interface LemonTabsCSSProperties extends React.CSSProperties {
@@ -54,6 +56,7 @@ export function LemonTabs<T extends string | number>({
className,
'data-attr': dataAttr,
sceneInset = false,
rightSlot,
}: LemonTabsProps<T>): JSX.Element {
const { containerRef, selectionRef, sliderWidth, sliderOffset, transitioning } = useSliderPositioning<
HTMLUListElement,
@@ -83,50 +86,62 @@ export function LemonTabs<T extends string | number>({
data-attr={dataAttr}
>
<ul className={cn('LemonTabs__bar', barClassName)} role="tablist" ref={containerRef}>
{realTabs.map((tab) => {
const content = (
<>
{tab.label}
{tab.tooltip && <IconInfo className="ml-1 text-base shrink-0" />}
</>
)
return (
<Tooltip
key={tab.key}
title={tab.tooltip}
placement="top"
offset={0}
docLink={tab.tooltipDocLink}
>
<li
className={cn('LemonTabs__tab', tab.key === activeKey && 'LemonTabs__tab--active')}
onClick={onChange ? () => onChange(tab.key) : undefined}
role="tab"
aria-selected={tab.key === activeKey}
tabIndex={0}
onKeyDown={
onChange
? (e) => {
if (e.key === 'Enter') {
onChange(tab.key)
}
}
: undefined
}
ref={tab.key === activeKey ? selectionRef : undefined}
data-attr={tab['data-attr']}
<div
className={cn('flex gap-x-4 md:gap-x-8', {
'gap-x-2': size === 'small',
'pr-4': rightSlot,
})}
>
{realTabs.map((tab) => {
const content = (
<>
{tab.label}
{tab.tooltip && <IconInfo className="ml-1 text-base shrink-0" />}
</>
)
return (
<Tooltip
key={tab.key}
title={tab.tooltip}
placement="top"
offset={0}
docLink={tab.tooltipDocLink}
>
{tab.link ? (
<Link className="LemonTabs__tab-content" to={tab.link}>
{content}
</Link>
) : (
<div className="LemonTabs__tab-content">{content}</div>
)}
</li>
</Tooltip>
)
})}
<li
className={cn('LemonTabs__tab', tab.key === activeKey && 'LemonTabs__tab--active')}
onClick={onChange ? () => onChange(tab.key) : undefined}
role="tab"
aria-selected={tab.key === activeKey}
tabIndex={0}
onKeyDown={
onChange
? (e) => {
if (e.key === 'Enter') {
onChange(tab.key)
}
}
: undefined
}
ref={tab.key === activeKey ? selectionRef : undefined}
data-attr={tab['data-attr']}
>
{tab.link ? (
<Link className="LemonTabs__tab-content" to={tab.link}>
{content}
</Link>
) : (
<div className="LemonTabs__tab-content">{content}</div>
)}
</li>
</Tooltip>
)
})}
</div>
{rightSlot && (
<div className="mb-[1px] flex gap-x-2 shrink-0 items-center justify-end sticky right-0 bg-primary pr-4">
{rightSlot}
</div>
)}
</ul>
{activeTab && 'content' in activeTab && (
<div className={cn('LemonTabs__content', sceneInset && 'p-4')} key={activeKey}>

View File

@@ -279,7 +279,6 @@ export const sceneConfigurations: Record<Scene | string, SceneConfig> = {
name: 'Settings',
activityScope: ActivityScope.REPLAY,
defaultDocsPath: '/docs/session-replay',
hideProjectNotice: true,
},
[Scene.ReplaySingle]: {
projectBased: true,
@@ -292,7 +291,6 @@ export const sceneConfigurations: Record<Scene | string, SceneConfig> = {
name: 'Session replay',
activityScope: ActivityScope.REPLAY,
defaultDocsPath: '/docs/session-replay',
hideProjectNotice: true,
layout: 'app-full-scene-height',
},
[Scene.RevenueAnalytics]: {

View File

@@ -251,12 +251,11 @@ export function SessionRecordingsPageTabs(): JSX.Element {
return (
// TRICKY @adamleithp: since session replay doesn't want a scene title section, we need to add our SceneActions to the top of the page
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-2 relative">
<LemonTabs
activeKey={tab}
className={cn('flex -mt-4')}
// TRICKY @adamleithp: we need to add a right padding to the tabs bar to account for the SceneActions
barClassName="mb-0 pr-48"
barClassName="mb-0"
onChange={(t) => router.actions.push(urls.replay(t as ReplayTabs))}
sceneInset
tabs={ReplayPageTabs.map((replayTab): LemonTab<string> => {
@@ -275,11 +274,8 @@ export function SessionRecordingsPageTabs(): JSX.Element {
'data-attr': replayTab['data-attr'],
}
})}
rightSlot={<Header />}
/>
{/* TRICKY @adamleithp: position the actions to the right of the tabs bar absolutely */}
<div className="absolute right-0 top-0 pt-[6px] pr-4 bg-primary flex gap-x-2 shrink-0">
<Header />
</div>
</div>
)
}