This repository has been archived by the owner on Sep 30, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Code Insights: Refactor creation UI lang stats preview (#33553)
* Add insight card abstraction * Adjust lang stats live preview chart * Fix default code insights backend ts types
- Loading branch information
1 parent
9e96bbb
commit 668bc01
Showing
20 changed files
with
525 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
client/web/src/enterprise/insights/components/views/card/InsightCard.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
184 changes: 184 additions & 0 deletions
184
client/web/src/enterprise/insights/components/views/card/InsightCard.story.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
93 changes: 93 additions & 0 deletions
93
client/web/src/enterprise/insights/components/views/card/InsightCard.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.