Skip to content

Commit

Permalink
feat: Activation panel 3000 (#18452)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite authored Nov 9, 2023
1 parent b52ed5c commit 2d4f26b
Show file tree
Hide file tree
Showing 14 changed files with 272 additions and 11 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions frontend/src/layout/navigation-3000/sidepanel/SidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IconNotebook, IconQuestion, IconInfo, IconGear } from '@posthog/icons'
import { SidePanelDocs } from './panels/SidePanelDocs'
import { SidePanelSupport } from './panels/SidePanelSupport'
import { NotebookPanel } from 'scenes/notebooks/NotebookPanel/NotebookPanel'
import { SidePanelActivation, SidePanelActivationIcon } from './panels/SidePanelActivation'
import { SidePanelSettings } from './panels/SidePanelSettings'

export const SidePanelTabs: Record<SidePanelTab, { label: string; Icon: any; Content: any }> = {
Expand All @@ -28,6 +29,12 @@ export const SidePanelTabs: Record<SidePanelTab, { label: string; Icon: any; Con
Icon: IconInfo,
Content: SidePanelDocs,
},

[SidePanelTab.Activation]: {
label: 'Quick start',
Icon: SidePanelActivationIcon,
Content: SidePanelActivation,
},
[SidePanelTab.Settings]: {
label: 'Settings',
Icon: IconGear,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useValues } from 'kea'
import { ActivationTask } from 'lib/components/ActivationSidebar/ActivationSidebar'
import { ActivationTaskType, activationLogic } from 'lib/components/ActivationSidebar/activationLogic'
import { ProfessorHog } from 'lib/components/hedgehogs'
import { LemonProgressCircle } from 'lib/lemon-ui/LemonProgressCircle'
import { LemonIconProps } from 'lib/lemon-ui/icons'

export const SidePanelActivation = (): JSX.Element => {
const { activeTasks, completionPercent, completedTasks } = useValues(activationLogic)

return (
<div className="p-4 space-y-2">
<h2>Quick Start</h2>
<p>Use our Quick Start guide to learn about everything PostHog can do for you and your product.</p>
<div className="flex items-center justify-center">
<div className="flex flex-col items-center">
<LemonProgressCircle progress={completionPercent / 100} size={100} className="text-primary">
<span className="text-2xl">{activeTasks.length}</span>
</LemonProgressCircle>
<p className="text-muted mt-2 ">still to go</p>
</div>
<div className="h-60">
<ProfessorHog className="max-h-full w-auto object-contain" />
</div>
</div>
{activeTasks.length > 0 && (
<div>
<h4>What's next?</h4>
<ul className="space-y-2">
{activeTasks.map((task: ActivationTaskType) => (
<ActivationTask key={task.id} {...task} />
))}
</ul>
</div>
)}
{completedTasks.length > 0 && (
<div>
<h4>Completed</h4>
<ul className="space-y-2">
{completedTasks.map((task: ActivationTaskType) => (
<ActivationTask key={task.id} {...task} />
))}
</ul>
</div>
)}
</div>
)
}

export const SidePanelActivationIcon = ({ className }: { className: LemonIconProps['className'] }): JSX.Element => {
const { activeTasks, completionPercent } = useValues(activationLogic)

return (
<LemonProgressCircle progress={completionPercent / 100} size={20} className={className}>
<span className="text-xs font-bold">{activeTasks.length}</span>
</LemonProgressCircle>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum SidePanelTab {
Notebooks = 'notebook',
Feedback = 'feedback',
Docs = 'docs',
Activation = 'activation',
Settings = 'settings',
}

Expand Down Expand Up @@ -59,6 +60,7 @@ export const sidePanelLogic = kea<sidePanelLogicType>([
tabs.push(SidePanelTab.Docs)
}

tabs.push(SidePanelTab.Activation)
tabs.push(SidePanelTab.Settings)

return tabs
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/layout/navigation/SideBar/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { Spinner } from 'lib/lemon-ui/Spinner/Spinner'
import { DebugNotice } from 'lib/components/DebugNotice'
import ActivationSidebar from 'lib/components/ActivationSidebar/ActivationSidebar'
import { NotebookPopover } from 'scenes/notebooks/NotebookPanel/NotebookPopover'
import { FlaggedFeature } from 'lib/components/FlaggedFeature'
import { IconNotebook } from 'scenes/notebooks/IconNotebook'
import { ActivationSidebar } from 'lib/components/ActivationSidebar/ActivationSidebar'

function Pages(): JSX.Element {
const { currentOrganization } = useValues(organizationLogic)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/layout/navigation/TopBar/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { groupsModel } from '~/models/groupsModel'
import { NotificationBell } from '~/layout/navigation/TopBar/NotificationBell'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { FEATURE_FLAGS } from 'lib/constants'
import ActivationSidebarToggle from 'lib/components/ActivationSidebar/ActivationSidebarToggle'
import { NotebookButton } from '~/layout/navigation/TopBar/NotebookButton'
import { ActivationSidebarToggle } from 'lib/components/ActivationSidebar/ActivationSidebarToggle'
import { GlobalModals } from '~/layout/GlobalModals'

export function TopBar(): JSX.Element {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ import { IconCheckmark, IconClose } from 'lib/lemon-ui/icons'
import { ProfessorHog } from '../hedgehogs'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'

const Task = ({ id, name, description, completed, canSkip, skipped, url }: ActivationTaskType): JSX.Element => {
export const ActivationTask = ({
id,
name,
description,
completed,
canSkip,
skipped,
url,
}: ActivationTaskType): JSX.Element => {
const displaySideAction = !completed && !skipped && canSkip
const { runTask, skipTask } = useActions(activationLogic)
const { reportActivationSideBarTaskClicked } = useActions(eventUsageLogic)
Expand Down Expand Up @@ -59,7 +67,7 @@ const Task = ({ id, name, description, completed, canSkip, skipped, url }: Activ
)
}

const ActivationSidebar = (): JSX.Element => {
export const ActivationSidebar = (): JSX.Element => {
const { isActivationSideBarShown } = useValues(navigationLogic)
const { hideActivationSideBar } = useActions(navigationLogic)
const { activeTasks, completedTasks, completionPercent } = useValues(activationLogic)
Expand Down Expand Up @@ -93,7 +101,7 @@ const ActivationSidebar = (): JSX.Element => {
<div className="text-muted uppercase text-xs">What's next?</div>
<ul>
{activeTasks.map((task: ActivationTaskType) => (
<Task key={task.id} {...task} />
<ActivationTask key={task.id} {...task} />
))}
</ul>
</div>
Expand All @@ -103,7 +111,7 @@ const ActivationSidebar = (): JSX.Element => {
<div className="text-muted uppercase text-xs">Completed</div>
<ul>
{completedTasks.map((task: ActivationTaskType) => (
<Task key={task.id} {...task} />
<ActivationTask key={task.id} {...task} />
))}
</ul>
</div>
Expand All @@ -113,5 +121,3 @@ const ActivationSidebar = (): JSX.Element => {
</div>
)
}

export default ActivationSidebar
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { navigationLogic } from '~/layout/navigation/navigationLogic'
import { Progress } from 'antd'
import { activationLogic } from './activationLogic'

const ActivationSidebarToggle = (): JSX.Element | null => {
export const ActivationSidebarToggle = (): JSX.Element | null => {
const { mobileLayout } = useValues(navigationLogic)
const { toggleActivationSideBar } = useActions(navigationLogic)
const { activeTasks, completionPercent, isReady, hasCompletedAllTasks } = useValues(activationLogic)
Expand Down Expand Up @@ -38,5 +38,3 @@ const ActivationSidebarToggle = (): JSX.Element | null => {
</LemonButton>
)
}

export default ActivationSidebarToggle
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.LemonProgressCircle {
display: inline-block;
position: relative;
vertical-align: text-bottom;

circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}

.LemonProgressCircle__content {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
}

.LemonButton__icon & {
display: inline-flex;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Meta, StoryFn, StoryObj } from '@storybook/react'
import { LemonProgressCircle, LemonProgressCircleProps } from './LemonProgressCircle'
import { useEffect, useState } from 'react'
import { LemonButton } from '../LemonButton'
import { IconGear } from '@posthog/icons'
import { LemonCheckbox } from '../LemonCheckbox'

type Story = StoryObj<typeof LemonProgressCircle>
const meta: Meta<typeof LemonProgressCircle> = {
title: 'Lemon UI/Lemon Progress Circle',
component: LemonProgressCircle,
parameters: {
docs: {
description: {
component: `
[Related Figma area](https://www.figma.com/file/Y9G24U4r04nEjIDGIEGuKI/PostHog-Design-System-One?node-id=3139%3A1388)
Lemon Labels provide common styling and options for labeling form elements. They can be used directly but most commonly should be used via the \`Field\` component.
`,
},
},
},
args: {
progress: 0.3,
},
tags: ['autodocs'],
}
export default meta

export const Template: StoryFn<typeof LemonProgressCircle> = (props: LemonProgressCircleProps) => {
return <LemonProgressCircle {...props} />
}

export const Basic: Story = Template.bind({})
Basic.args = {
progress: 0.3,
}

export const Overview = (): JSX.Element => {
const [progress, setProgress] = useState(0.2)
const [animate, setAnimate] = useState(false)

useEffect(() => {
if (!animate) {
return
}
const interval = setInterval(() => {
setProgress((progress) => {
const newProgress = progress + 0.1
return newProgress > 1 ? newProgress - 1 : newProgress
})
}, 500)
return () => clearInterval(interval)
}, [animate])

return (
<div className="flex flex-col gap-2">
<LemonCheckbox checked={animate} onChange={setAnimate} bordered label={'Animate'} />
<LemonProgressCircle progress={progress} />
<LemonProgressCircle progress={progress} strokePercentage={0.5} size={30} />

<span className="flex items-center gap-2">
<LemonButton
icon={<LemonProgressCircle progress={progress} />}
status="primary"
sideIcon={<IconGear />}
type="secondary"
size="small"
>
In a button!
</LemonButton>

<LemonButton
icon={<LemonProgressCircle progress={progress} size={20} />}
status="primary"
sideIcon={<IconGear />}
type="secondary"
>
In a button!
</LemonButton>

<LemonButton
icon={<LemonProgressCircle progress={progress} size={24} />}
status="primary"
sideIcon={<IconGear />}
type="secondary"
size="large"
>
In a button!
</LemonButton>
</span>

<LemonProgressCircle progress={progress} size={40}>
<span className="font-semibold text-sm">{(100 * progress).toFixed(0)}</span>
</LemonProgressCircle>

<span>
Here is one inline <LemonProgressCircle progress={progress} /> with some text...
</span>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import clsx from 'clsx'
import './LemonProgressCircle.scss'

export type LemonProgressCircleProps = {
strokePercentage?: number
backgroundStrokeOpacity?: number
size?: number
progress: number
children?: React.ReactNode
className?: string
}

export const LemonProgressCircle = ({
strokePercentage = 0.2,
backgroundStrokeOpacity = 0.2,
size = 16,
progress,
children,
className,
}: LemonProgressCircleProps): JSX.Element => {
const radius = size / 2
const stroke = radius * strokePercentage
const circumference = size * Math.PI
const strokeDashoffset = circumference - progress * circumference

return (
<span
className={clsx('LemonProgressCircle', className)}
// eslint-disable-next-line react/forbid-dom-props
style={{
height: size,
width: size,
}}
>
<svg height={size} width={size}>
<circle
stroke="currentColor"
strokeOpacity={backgroundStrokeOpacity}
fill="transparent"
strokeWidth={stroke}
r={radius - stroke / 2}
cx={radius}
cy={radius}
/>

<circle
stroke="currentColor"
fill="transparent"
strokeWidth={stroke}
strokeDasharray={circumference + ' ' + circumference}
// eslint-disable-next-line react/forbid-dom-props
style={{ strokeDashoffset }}
r={radius - stroke / 2}
cx={radius}
cy={radius}
/>
</svg>

{children ? <span className="LemonProgressCircle__content">{children}</span> : null}
</span>
)
}
1 change: 1 addition & 0 deletions frontend/src/lib/lemon-ui/LemonProgressCircle/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './LemonProgressCircle'

0 comments on commit 2d4f26b

Please sign in to comment.