Skip to content

data display #326

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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: 16 additions & 0 deletions src/core/locale.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {createContext, ReactNode, useContext} from 'react';

// default value is provided for storybook. Otherwise, this should always be set
// explicitly in the root component.
export const LocaleContext = createContext(new Intl.Locale('en-US'));

export interface LocaleProviderProps {
children: ReactNode;
locale: Intl.Locale;
}

export const LocaleProvider = ({children, locale}: LocaleProviderProps) => (
<LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
);

export const useLocale = () => useContext(LocaleContext);
12 changes: 12 additions & 0 deletions src/data-display/data-grid/csstype.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// eslint-disable-next-line import/no-unresolved
import 'csstype';

declare module 'csstype' {
// eslint-disable-next-line @typescript-eslint/ban-types
interface Properties<TLength = (string & {}) | 0, TTime = string & {}> {
'--data-unit'?: string;
'--data-gap'?: string;
'--data-grid__item_height'?: string;
'--data-grid__item_width'?: string;
}
}
33 changes: 33 additions & 0 deletions src/data-display/data-grid/data-grid-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import cx from 'classnames';
import React, {ReactNode} from 'react';

export interface DataGridItemProps
extends React.DetailedHTMLProps<
React.HTMLAttributes<HTMLSpanElement>,
HTMLSpanElement
> {
children: ReactNode;
height?: number;
width?: number;
}

export const DataGridItem = ({
children,
className,
height = 1,
style,
width = 1,
...rest
}: DataGridItemProps) => (
<span
{...rest}
className={cx('data-grid__item', className)}
style={{
...style,
'--data-grid__item_height': `${height}`,
'--data-grid__item_width': `${width}`,
}}
>
{children}
</span>
);
97 changes: 97 additions & 0 deletions src/data-display/data-grid/data-grid.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {faker} from '@faker-js/faker';
import React from 'react';

import {DataGrid} from './data-grid';
import {DataGridItem} from './data-grid-item';

export default {
component: DataGrid,
title: 'Data Display/Data Grid',
};

const sizes = [1, 2, 3];

/** helper */
function rand() {
return faker.helpers.arrayElement(sizes);
}

export const NormalGrid = () => (
<DataGrid style={{backgroundColor: 'red'}}>
{new Array(37).fill('x').map((_, i) => (
<DataGridItem
style={{
alignItems: 'center',
backgroundColor: 'pink',
display: 'flex',
justifyContent: 'center',
}}
key={i}
height={rand()}
width={rand()}
>
{i}
</DataGridItem>
))}
</DataGrid>
);

export const DenseColumns = () => (
<DataGrid denseColumns style={{backgroundColor: 'red'}}>
{new Array(37).fill('x').map((_, i) => (
<DataGridItem
style={{
alignItems: 'center',
backgroundColor: 'pink',
display: 'flex',
justifyContent: 'center',
}}
key={i}
height={rand()}
width={rand()}
>
{i}
</DataGridItem>
))}
</DataGrid>
);

export const DenseRows = () => (
<DataGrid denseRows style={{backgroundColor: 'red'}}>
{new Array(37).fill('x').map((_, i) => (
<DataGridItem
style={{
alignItems: 'center',
backgroundColor: 'pink',
display: 'flex',
justifyContent: 'center',
}}
key={i}
height={rand()}
width={rand()}
>
{i}
</DataGridItem>
))}
</DataGrid>
);

export const DenseColumnsAndRows = () => (
<DataGrid denseColumns denseRows style={{backgroundColor: 'red'}}>
{new Array(37).fill('x').map((_, i) => (
<DataGridItem
style={{
alignItems: 'center',
backgroundColor: 'pink',
display: 'flex',
justifyContent: 'center',
}}
key={i}
height={rand()}
width={rand()}
>
{i}
</DataGridItem>
))}
</DataGrid>
);
72 changes: 72 additions & 0 deletions src/data-display/data-grid/data-grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import cx from 'classnames';
import React, {ReactNode} from 'react';

export interface DataGridProps
extends React.DetailedHTMLProps<
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
> {
denseColumns?: boolean;
denseRows?: boolean;
children: ReactNode;
}

export const DataGrid = ({
children,
className,
denseColumns = false,
denseRows = false,
...rest
}: DataGridProps) => (
<>
{/* This is... not ideal, but so far, it's the least-worst way I've found to
include the data-grid styles in the exported bundle. */}
<style>
{
/* css */ `
.data-grid {
--grid-unit: 4rem;
--grid-gap: 1rem;

gap: var(--grid-gap);

align-content: start;
justify-content: start;

display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(var(--grid-unit), 100%), 1fr));
}

.data-grid--dense {
grid-auto-flow: dense;
}

.data-grid__item {
--data-grid__item_height: 1;
--data-grid__item_width: 1;

height: calc(var(--grid-unit) * var(--data-grid__item_height));
width: calc(var(--grid-unit) * var(--data-grid__item_width));

grid-column-start: span var(--data-grid__item_width);
}

.data-grid--dense-rows .data-grid__item {
grid-row-start: span var(--data-grid__item_height);
}
`
}
</style>
<div
{...rest}
className={cx(
'data-grid',
denseColumns && 'data-grid--dense',
denseRows && 'data-grid--dense-rows',
className
)}
>
{children}
</div>
</>
);
2 changes: 2 additions & 0 deletions src/data-display/data-grid/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './data-grid-item';
export * from './data-grid';
16 changes: 16 additions & 0 deletions src/data-display/fact/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {createContext} from 'react';

import {AnyRenderer, Renderer as RendererType} from '../../renderers';

import {FactCard} from './fact-card';
import {FactContainer} from './types';

export interface FactContextProps {
Container: FactContainer;
Renderer: RendererType;
}

export const FactContext = createContext<FactContextProps>({
Container: FactCard,
Renderer: AnyRenderer,
});
21 changes: 21 additions & 0 deletions src/data-display/fact/fact-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {Card} from 'react-bootstrap';

import {FactContainer} from './types';

/**
* Default container for the Fact component
*
* Note that this uses Bootstrap's Card directly rather than the local wrapper.
* The local wrapper does things with headings that may or may not be
* appropriate at this time.
*/
export const FactCard: FactContainer = ({label, output}) => (
<Card className="fact-container fact-card">
<Card.Header className="fact-container__label fact-card__label">
{label}
</Card.Header>
<Card.Body className="fact_container__output fact-card__output">
{output}
</Card.Body>
</Card>
);
39 changes: 39 additions & 0 deletions src/data-display/fact/fact.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {useContext} from 'react';
import {Card} from 'react-bootstrap';

import {CurrencyRenderer} from '../../renderers/currency-renderer';

import {FactContext} from './context';
import {Fact} from './fact';
import {FactContainer} from './types';

export default {
component: Fact,
title: 'Data Display/Fact',
};

export const bigNumber = () => (
<Fact label="Population of San Francisco" value={815000} />
);

export const word = () => <Fact label={'Status'} value={'COMPLETE'} />;

export const currency = () => (
<Fact label={'Monthly Price'} value={9.99} Renderer={CurrencyRenderer} />
);

const FooterFactCard: FactContainer = ({label, output}) => (
<Card className="fact-container fact-card">
<Card.Body className="fact-card__value">{output}</Card.Body>
<Card.Footer className="fact-card__label">{label}</Card.Footer>
</Card>
);

export const AlternateContainer = () => {
const defaults = useContext(FactContext);
return (
<FactContext.Provider value={{...defaults, Container: FooterFactCard}}>
<Fact label={'Monthly Price'} value={9.99} Renderer={CurrencyRenderer} />
</FactContext.Provider>
);
};
32 changes: 32 additions & 0 deletions src/data-display/fact/fact.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {ReactNode} from 'react';

import {Renderer as RendererType} from '../../renderers';
import {useContextWithDefaults} from '../../support';

import {FactContext, FactContextProps} from './context';

export interface FactProps<T>
extends Partial<Omit<FactContextProps, 'Renderer'>> {
label: ReactNode;
value: T;
Renderer?: RendererType<T>;
}

export const Fact = <T extends unknown>({
label,
value,
Renderer: OverrideRenderer,
...rest
}: FactProps<T>) => {
const {Container, Renderer: DefaultRenderer} = useContextWithDefaults(
FactContext,
rest
);
const Renderer = OverrideRenderer ?? DefaultRenderer;

return (
<>
<Container label={label} output={<Renderer value={value} />}></Container>
</>
);
};
3 changes: 3 additions & 0 deletions src/data-display/fact/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './fact';
export * from './fact-card';
export * from './types';
8 changes: 8 additions & 0 deletions src/data-display/fact/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {ComponentType, ReactNode} from 'react';

export interface FactContainerProps {
label: ReactNode;
output: ReactNode;
}

export type FactContainer = ComponentType<FactContainerProps>;
7 changes: 6 additions & 1 deletion src/renderers/any-renderer/any-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {useContextWithDefaults} from '../../support';
import {BooleanRenderer, BooleanRendererContextType} from '../boolean-renderer';
import {DateRenderer, DateRendererContextProps} from '../date-renderer';
import {NullRenderer, NullRendererContextType} from '../null-renderer';
import {NumberRenderer} from '../number-renderer';
import {ObjectRenderer} from '../object-renderer';
import {RendererProps} from '../types';

Expand Down Expand Up @@ -50,7 +51,11 @@ export const AnyRenderer = ({value, ...rest}: AnyRendererProps) => {
return <NullRenderer value={null} {...nullDefaults} />;
}

if (typeof value === 'number' || typeof value === 'bigint') {
if (typeof value === 'number') {
return <NumberRenderer value={value}></NumberRenderer>;
}

if (typeof value === 'bigint') {
return <>{value}</>;
}

Expand Down
Loading