Skip to content

Commit

Permalink
Fresh table pilot (#7103)
Browse files Browse the repository at this point in the history
* Add `LemonTable` base to Feature flags page

* Fix basic styling

* Rework more of the table

* Fix column `align`

* Update eventsListLogic.ts

* Align FF table columns with design

* Add pagination

* Add table loader

* Add `LemonTable` to Storybook

* Add sorting

* Increase feature flags page size to 50

* Add scroll indication

* Fix minor issues

* Fix typing

* Update E2E test

* Sort one column at a time

* Add default sorting

* Improve current page handling

* Use search params for current page state

* Don't mute disabled feature flags

* Add overlay for loading state

* Add profile picture to Created by and improve comments

* Fix `createdByColumn`

* Refactor the More button for reusability

* Fix content sizing (pagination/loader filling full width when scrolled)

* Remove need for `@ts-expect-error`
  • Loading branch information
Twixes committed Nov 19, 2021
1 parent b4abf84 commit 2f1a6af
Show file tree
Hide file tree
Showing 27 changed files with 753 additions and 199 deletions.
7 changes: 4 additions & 3 deletions cypress/integration/featureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,16 @@ describe('Feature Flags', () => {
cy.get('[data-attr=feature-flag-table]').should('not.contain', '%') // By default it's released to everyone, if a % is not specified
cy.get('[data-attr=feature-flag-table]').should('contain', 'is_demo')

cy.get(`[data-row-key=${name}]`).click()
cy.get(`[data-row-key=${name}]`).contains(name).click()
cy.get('[data-attr=feature-flag-key]')
.type('-updated')
.should('have.value', name + '-updated')
cy.get('[data-attr=feature-flag-submit]').click()
cy.get('.Toastify__toast-body').click() // clicking the toast gets you back to the list
cy.get('[data-attr=feature-flag-table]').should('contain', name + '-updated')

cy.get(`[data-row-key=${name}-updated] [data-attr=usage]`).click()
cy.get(`[data-row-key=${name}-updated] [data-attr=more-button]`).click()
cy.contains(`Use in Insights`).click()
cy.location().should((loc) => {
expect(loc.pathname.toString()).to.contain('/insight')
})
Expand All @@ -60,7 +61,7 @@ describe('Feature Flags', () => {
cy.get('[data-attr=feature-flag-submit]').click()
cy.get('.Toastify__toast-body').click() // clicking the toast gets you back to the list
cy.get('[data-attr=feature-flag-table]').should('contain', name)
cy.get('[data-row-key="' + name + '"]').click()
cy.get(`[data-row-key=${name}]`).contains(name).click()
cy.get('[data-attr=delete-flag]').click()
cy.contains('Click to undo').should('exist')
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,4 @@
margin: 0 0.5rem;
font-size: 1rem;
color: var(--primary-alt);
transform: rotate(-90deg);
}
4 changes: 2 additions & 2 deletions frontend/src/layout/navigation/Breadcrumbs/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react'
import { useValues } from 'kea'
import { IconExpandMore, IconArrowDropDown } from 'lib/components/icons'
import { IconArrowDropDown, IconChevronRight } from 'lib/components/icons'
import { Link } from 'lib/components/Link'
import './Breadcrumbs.scss'
import { Breadcrumb as IBreadcrumb, breadcrumbsLogic } from './breadcrumbsLogic'
Expand Down Expand Up @@ -52,7 +52,7 @@ export function Breadcrumbs(): JSX.Element | null {
<Breadcrumb breadcrumb={breadcrumbs[0]} />
{breadcrumbs.slice(1).map((breadcrumb) => (
<React.Fragment key={breadcrumb.name || '…'}>
<IconExpandMore className="Breadcrumbs__separator" />
<IconChevronRight className="Breadcrumbs__separator" />
<Breadcrumb breadcrumb={breadcrumb} />
</React.Fragment>
))}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/layout/navigation/SideBar/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { ToolbarModal } from '../../ToolbarModal/ToolbarModal'
import { navigationLogic } from '../navigationLogic'
import './SideBar.scss'

function SidebarProjectSwitcher(): JSX.Element {
function ProjectSwitcherInternal(): JSX.Element {
const { currentTeam } = useValues(teamLogic)
const { currentOrganization } = useValues(organizationLogic)
const { isProjectSwitcherShown } = useValues(navigationLogic)
Expand Down Expand Up @@ -203,7 +203,7 @@ export function SideBar({ children }: { children: React.ReactNode }): JSX.Elemen
<div className={clsx('SideBar', 'SideBar__layout', !isSideBarShown && 'SideBar--hidden')}>
<div className="SideBar__slider">
<div className="SideBar__content">
<SidebarProjectSwitcher />
<ProjectSwitcherInternal />
{currentTeam && (
<>
<LemonSpacer />
Expand Down
12 changes: 9 additions & 3 deletions frontend/src/lib/components/CopyToClipboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Input } from 'antd'
import { CopyOutlined } from '@ant-design/icons'
import { copyToClipboard } from 'lib/utils'
import { Tooltip } from 'lib/components/Tooltip'
import { IconCopy } from './icons'

interface InlineProps extends HTMLProps<HTMLSpanElement> {
children?: JSX.Element | string
Expand Down Expand Up @@ -41,7 +42,7 @@ export function CopyToClipboardInline({
display: 'flex',
alignItems: 'center',
flexDirection: iconPosition === 'end' ? 'row' : 'row-reverse',
flexWrap: iconPosition === 'end' ? 'wrap' : 'wrap-reverse',
flexWrap: 'nowrap',
...style,
}}
onClick={() => {
Expand All @@ -50,8 +51,13 @@ export function CopyToClipboardInline({
{...props}
>
<span style={iconPosition === 'start' ? { flexGrow: 1 } : {}}>{children}</span>
<CopyOutlined
style={iconPosition === 'end' ? { marginLeft: 4, ...iconStyle } : { marginRight: 4, ...iconStyle }}
<IconCopy
style={{
[iconPosition === 'end' ? 'marginLeft' : 'marginRight']: 4,
color: 'var(--primary)',
flexShrink: 0,
...iconStyle,
}}
/>
</span>
)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/components/LemonButton/LemonButton.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
background: var(--primary-bg-active);
}
&:disabled {
opacity: 0.7;
opacity: 0.6;
cursor: not-allowed;
}
.LemonRow__icon {
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/lib/components/LemonButton/More.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { useState } from 'react'
import { LemonButton } from '.'
import { IconEllipsis } from '../icons'
import { PopupProps } from '../Popup/Popup'

export function More({ overlay }: Pick<PopupProps, 'overlay'>): JSX.Element {
const [visible, setVisible] = useState(false)

return (
<LemonButton
compact
data-attr="more-button"
icon={<IconEllipsis />}
type="stealth"
onClick={(e) => {
setVisible((state) => !state)
e.stopPropagation()
}}
popup={{
visible,
onClickOutside: () => setVisible(false),
onClickInside: () => setVisible(false),
placement: 'bottom-end',
actionable: true,
overlay,
}}
/>
)
}
2 changes: 1 addition & 1 deletion frontend/src/lib/components/LemonRow/LemonRow.scss
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
display: flex;
align-items: center;
&:not(:first-child) {
padding-left: 0.5rem;
margin-left: 0.5rem;
}
}

Expand Down
10 changes: 10 additions & 0 deletions frontend/src/lib/components/LemonSwitch/LemonSwitch.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
background-color: var(--border);
transition: background-color 100ms ease;
.LemonSwitch--checked & {
background-color: rgba($primary, 0.25);
}
.LemonSwitch--alt.LemonSwitch--checked & {
background-color: rgba($primary_alt, 0.25);
}
}
Expand Down Expand Up @@ -49,13 +52,20 @@
border-left-color: transparent;
}
.LemonSwitch--loading &::after {
border-left-color: var(--primary);
}
.LemonSwitch--alt.LemonSwitch--loading &::after {
border-left-color: var(--primary-alt);
}
.LemonSwitch--loading.LemonSwitch--checked &::after {
border-left-color: #fff;
}
.LemonSwitch--checked & {
transform: translateX(1rem);
background-color: var(--primary);
border-color: var(--primary);
}
.LemonSwitch--alt.LemonSwitch--checked & {
background-color: var(--primary-alt);
border-color: var(--primary-alt);
}
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/lib/components/LemonSwitch/LemonSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ export interface LemonSwitchProps {
onChange: (newChecked: boolean) => void
checked: boolean
loading?: boolean
/** Whether the switch should use the alternative primary color. */
alt?: boolean
style?: React.CSSProperties
}

export function LemonSwitch({ id, onChange, checked, loading }: LemonSwitchProps): JSX.Element {
export function LemonSwitch({ id, onChange, checked, loading, alt, style }: LemonSwitchProps): JSX.Element {
const [isActive, setIsActive] = useState(false)

return (
Expand All @@ -21,12 +24,14 @@ export function LemonSwitch({ id, onChange, checked, loading }: LemonSwitchProps
'LemonSwitch',
checked && 'LemonSwitch--checked',
isActive && 'LemonSwitch--active',
loading && 'LemonSwitch--loading'
loading && 'LemonSwitch--loading',
alt && 'LemonSwitch--alt'
)}
onClick={() => onChange(!checked)}
onMouseDown={() => setIsActive(true)}
onMouseUp={() => setIsActive(false)}
onMouseOut={() => setIsActive(false)}
style={style}
>
<div className="LemonSwitch__slider" />
<div className="LemonSwitch__handle" />
Expand Down
159 changes: 159 additions & 0 deletions frontend/src/lib/components/LemonTable/LemonTable.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
.LemonTable {
position: relative;
width: 100%;
background: #fff;
border-radius: var(--radius);
border: 1px solid var(--border);
overflow: hidden;
&::before,
&::after {
transition: box-shadow 200ms ease;
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
&::before {
box-shadow: 16px 0 16px -32px rgba(0, 0, 0, 0.25) inset;
}
&.LemonTable--scrollable-left::before {
box-shadow: 16px 0 16px -16px rgba(0, 0, 0, 0.25) inset;
}
&::after {
box-shadow: -16px 0 16px -32px rgba(0, 0, 0, 0.25) inset;
}
&.LemonTable--scrollable-right::after {
box-shadow: -16px 0 16px -16px rgba(0, 0, 0, 0.25) inset;
}
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
thead {
background: var(--bg-mid);
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.03125rem;
text-transform: uppercase;
}
tbody {
tr {
border-top: 1px solid var(--border);
}
td {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
}
tr {
height: 3rem;
}
th,
td {
padding-left: 1rem;
padding-right: 1rem;
}

h4.row-name {
font-size: 0.875rem;
font-weight: 600;
color: inherit;
margin-bottom: 0.125rem;
}

span.row-description {
font-size: 0.75rem;
}
}

.LemonTable__scroll {
width: 100%;
overflow: auto hidden;
}

.LemonTable__content {
min-width: fit-content;
}

.LemonTable__overlay {
transition: opacity 200ms ease;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.5);
opacity: 0;
pointer-events: none;
.LemonTable--loading & {
opacity: 1;
pointer-events: auto;
}
}

.LemonTable__loader {
transition: height 200ms ease, top 200ms ease;
position: absolute;
left: 0;
top: 3rem;
width: 100%;
height: 0;
background: var(--primary-bg-active);
overflow: hidden;
&::after {
content: '';
position: absolute;
left: 0;
top: 0;
width: 50%;
height: 100%;
animation: loading-bar 1.5s linear infinite;
background: var(--primary);
}
.LemonTable--loading & {
top: 2.75rem;
height: 0.25rem;
}
}

.LemonTable__pagination {
display: flex;
align-items: center;
justify-content: end;
height: 3rem;
border-top: 1px solid var(--border);
padding: 0 1rem;
> span {
margin-right: 0.5rem;
}
}

.LemonTable__header--actionable {
cursor: pointer;
}

.LemonTable__header-content {
display: flex;
align-items: center;
justify-content: space-between;
}

@keyframes loading-bar {
0% {
left: 0;
width: 33.333%;
transform: translateX(-100%);
}
50% {
width: 50%;
}
100% {
left: 100%;
width: 33.333%;
transform: translateX(100%);
}
}
28 changes: 28 additions & 0 deletions frontend/src/lib/components/LemonTable/LemonTable.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react'
import { ComponentMeta } from '@storybook/react'

import { LemonTable as _LemonTable } from './LemonTable'

export default {
title: 'PostHog/Components/LemonTable',
component: _LemonTable,
parameters: { options: { showPanel: true } },
argTypes: {
loading: {
control: {
type: 'boolean',
},
},
},
} as ComponentMeta<typeof _LemonTable>

export function LemonTable({ loading }: { loading: boolean }): JSX.Element {
return (
<_LemonTable
loading={loading}
columns={[{ title: 'Column' }]}
dataSource={[] as Record<string, any>[]}
pagination={{ pageSize: 10 }}
/>
)
}

0 comments on commit 2f1a6af

Please sign in to comment.