Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Code Insights: Refactor creation UI lang stats preview #33553

Merged
merged 3 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions client/web/src/charts/components/pie-chart/PieChart.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactElement, useMemo, useState } from 'react'
import React, { ReactElement, SVGProps, useMemo, useState } from 'react'

import { Group } from '@visx/group'
import Pie, { PieArcDatum } from '@visx/shape/lib/shapes/Pie'
Expand All @@ -25,7 +25,7 @@ function getSubtitle<Datum>(arc: PieArcDatum<Datum>, total: number): string {
return `${((100 * arc.value) / total).toFixed(2)}%`
}

export interface PieChartProps<Datum> extends CategoricalLikeChart<Datum> {
export interface PieChartProps<Datum> extends CategoricalLikeChart<Datum>, SVGProps<SVGSVGElement> {
width: number
height: number
padding?: typeof DEFAULT_PADDING
Expand All @@ -40,8 +40,10 @@ export function PieChart<Datum>(props: PieChartProps<Datum>): ReactElement | nul
getDatumValue,
getDatumName,
getDatumColor,
getDatumLink,
getDatumLink = noop,
onDatumLinkClick = noop,
className,
...attributes
} = props

// We have to track which arc is hovered to change order of rendering.
Expand Down Expand Up @@ -76,7 +78,13 @@ export function PieChart<Datum>(props: PieChartProps<Datum>): ReactElement | nul
}

return (
<svg aria-label="Pie chart" width={width} height={height} className={styles.svg}>
<svg
{...attributes}
aria-label="Pie chart"
width={width}
height={height}
className={classNames(styles.svg, className)}
>
<Group top={centerY + padding.top} left={centerX + padding.left}>
<Pie data={sortedData} pieValue={getDatumValue} outerRadius={radius} cornerRadius={3}>
{pie => {
Expand Down
2 changes: 1 addition & 1 deletion client/web/src/charts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface CategoricalLikeChart<Datum> {
getDatumValue: (datum: Datum) => number
getDatumName: (datum: Datum) => string
getDatumColor: (datum: Datum) => string | undefined
getDatumLink: (datum: Datum) => string | undefined
getDatumLink?: (datum: Datum) => string | undefined | void
onDatumLinkClick?: (event: React.MouseEvent) => void
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.view {
display: flex;
flex-direction: column;
padding: 1rem 1rem;
cursor: default;
outline: none;

&:focus,
&:focus-within {
box-shadow: var(--focus-box-shadow);
}
}

.header {
margin-bottom: 0.75rem;
}

.header-content {
display: flex;
}

.title {
// Truncation for multiple lines
// stylelint-disable-next-line value-no-vendor-prefix
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
margin-bottom: 0.25rem;
flex-grow: 1;
}

.action {
align-self: start;
display: flex;
align-items: center;
// Move icons in action panel visually closer to card border
margin-right: -0.5rem;
margin-top: -0.25rem;
}

.subtitle {
flex-basis: 100%;
}

.error-boundary {
padding-top: 0;
}

// Content states

.loading-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import React from 'react'

import { Meta, Story } from '@storybook/react'
import { noop } from 'lodash'
import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon'
import FilterOutlineIcon from 'mdi-react/FilterOutlineIcon'

import { ErrorAlert } from '@sourcegraph/branded/src/components/alerts'
import { Button, Menu, MenuButton, MenuItem, MenuList } from '@sourcegraph/wildcard'

import { getLineColor, LegendItem, LegendList, ParentSize, Series } from '../../../../../charts'
import { WebStory } from '../../../../../components/WebStory'
import { SeriesChart } from '../chart'
import { SeriesBasedChartTypes } from '../types'

import * as Card from './InsightCard'

export default {
title: 'web/insights/shared-components',
decorators: [story => <WebStory>{() => story()}</WebStory>],
} as Meta

export const InsightCardShowcase: Story = () => (
<main style={{ display: 'flex', flexWrap: 'wrap', gap: '1rem' }}>
<section>
<h2>Empty view</h2>
<Card.Root style={{ width: '400px', height: '400px' }}>
<Card.Header title="Empty card" />
</Card.Root>
</section>

<section>
<h2>View with loading content</h2>
<Card.Root style={{ width: '400px', height: '400px' }}>
<Card.Header title="Loading insight card" subtitle="View with loading content example" />
<Card.Loading>Loading insight</Card.Loading>
</Card.Root>
</section>

<section>
<h2>View with error-like content</h2>
<Card.Root style={{ width: '400px', height: '400px' }}>
<Card.Header title="Loading insight card" subtitle="View with errored content example" />
<ErrorAlert error={new Error("We couldn't find code insight")} />
</Card.Root>
</section>

<section>
<h2>Card with banner content (resizing state)</h2>
<Card.Root style={{ width: '400px', height: '400px' }}>
<Card.Header title="Resizing insight card" subtitle="Resizing insight card" />
<Card.Banner>Resizing</Card.Banner>
</Card.Root>
</section>

<section>
<h2>Card with insight chart</h2>
<InsightCardWithChart />
</section>

<section>
<h2>View with context action item</h2>
<Card.Root style={{ width: 400, height: 400 }}>
<Card.Header
title="Chart view and looooooong loooooooooooooooong name of insight card block"
subtitle="Subtitle chart description"
>
<Button variant="icon" className="p-1">
<FilterOutlineIcon size="1rem" />
</Button>
<Menu>
<MenuButton variant="icon" className="p-1">
<DotsVerticalIcon size={16} />
</MenuButton>
<MenuList>
<MenuItem onSelect={noop}>Create</MenuItem>
<MenuItem onSelect={noop}>Update</MenuItem>
<MenuItem onSelect={noop}>Delete</MenuItem>
</MenuList>
</Menu>
</Card.Header>
</Card.Root>
</section>
</main>
)

interface StandardDatum {
a: number | null
b: number | null
c: number | null
x: number
}

const DATA: StandardDatum[] = [
{
x: 1588965700286 - 4 * 24 * 60 * 60 * 1000,
a: 4000,
b: 15000,
c: 5000,
},
{
x: 1588965700286 - 3 * 24 * 60 * 60 * 1000,
a: 4000,
b: 26000,
c: 5000,
},
{
x: 1588965700286 - 2 * 24 * 60 * 60 * 1000,
a: 5600,
b: 20000,
c: 5000,
},
{
x: 1588965700286 - 24 * 60 * 60 * 1000,
a: 9800,
b: 19000,
c: 5000,
},
{
x: 1588965700286,
a: 6000,
b: 17000,
c: 5000,
},
]

const SERIES: Series<StandardDatum>[] = [
{
dataKey: 'a',
name: 'A metric',
color: 'var(--blue)',
},
{
dataKey: 'b',
name: 'B metric',
color: 'var(--orange)',
},
{
dataKey: 'c',
name: 'C metric',
color: 'var(--indigo)',
},
]

const getXValue = (datum: { x: number }) => new Date(datum.x)

function InsightCardWithChart() {
return (
<Card.Root style={{ width: '400px', height: '400px' }}>
<Card.Header title="Insight with chart" subtitle="CSS migration insight chart">
<Button variant="icon" className="p-1">
<FilterOutlineIcon size="1rem" />
</Button>
<Menu>
<MenuButton variant="icon" className="p-1">
<DotsVerticalIcon size={16} />
</MenuButton>
<MenuList>
<MenuItem onSelect={noop}>Create</MenuItem>
<MenuItem onSelect={noop}>Update</MenuItem>
<MenuItem onSelect={noop}>Delete</MenuItem>
</MenuList>
</Menu>
</Card.Header>
<ParentSize>
{parent => (
<SeriesChart
type={SeriesBasedChartTypes.Line}
data={DATA}
series={SERIES}
getXValue={getXValue}
width={parent.width}
height={parent.height}
/>
)}
</ParentSize>
<LegendList className="mt-3">
{SERIES.map(line => (
<LegendItem key={line.dataKey.toString()} color={getLineColor(line)} name={line.name} />
))}
</LegendList>
</Card.Root>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { forwardRef, HTMLAttributes, ReactNode } from 'react'

import classNames from 'classnames'
import { useLocation } from 'react-router-dom'

import { Card, ForwardReferenceComponent, LoadingSpinner } from '@sourcegraph/wildcard'

import { ErrorBoundary } from '../../../../../components/ErrorBoundary'

import styles from './InsightCard.module.scss'

const InsightCard = forwardRef((props, reference) => {
const { title, children, className, as = 'section', ...otherProps } = props

return (
<Card as={as} tabIndex={0} ref={reference} {...otherProps} className={classNames(className, styles.view)}>
<ErrorBoundary location={useLocation()} className={styles.errorBoundary}>
{children}
</ErrorBoundary>
</Card>
)
}) as ForwardReferenceComponent<'section'>

interface InsightCardTitleProps {
title: string
subtitle?: string

/**
* It's primarily conceived as a slot for card actions (like filter buttons) that render
* element right after header text at the right top corner of the card.
*/
children?: ReactNode
}

const InsightCardHeader = forwardRef((props, reference) => {
const { as: Component = 'header', title, subtitle, className, children, ...attributes } = props

return (
<Component {...attributes} ref={reference} className={classNames(styles.header, className)}>
<div className={styles.headerContent}>
<h4 title={title} className={styles.title}>
{title}
</h4>

{children && (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
<div
// View component usually is rendered within a view grid component. To suppress
// bad click that lead to card DnD events in view grid we stop event bubbling for
// clicks.
onClick={event => event.stopPropagation()}
onMouseDown={event => event.stopPropagation()}
className={styles.action}
>
{children}
</div>
)}
</div>

{subtitle && <div className={styles.subtitle}>{subtitle}</div>}
</Component>
)
}) as ForwardReferenceComponent<'header', InsightCardTitleProps>

const InsightCardLoading: React.FunctionComponent = props => (
<InsightCardBanner>
<LoadingSpinner />
{props.children}
</InsightCardBanner>
)

const InsightCardBanner: React.FunctionComponent<HTMLAttributes<HTMLDivElement>> = props => (
<div {...props} className={classNames(styles.loadingContent, props.className)}>
{props.children}
</div>
)

const Root = InsightCard
const Header = InsightCardHeader
const Loading = InsightCardLoading
const Banner = InsightCardBanner

export {
InsightCard,
InsightCardHeader,
InsightCardLoading,
InsightCardBanner,
// * as Card imports
Root,
Header,
Loading,
Banner,
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react'

import { Meta, Story } from '@storybook/react'

import { WebStory } from '../../../../../components/WebStory'
import { CategoricalBasedChartTypes } from '../types'
import { WebStory } from '../../../../../../components/WebStory'
import { CategoricalBasedChartTypes } from '../../types'

import { CategoricalChart } from './CategoricalChart'

Expand Down
Loading