Skip to content

Commit

Permalink
Merge pull request #6 from broadlume/typecheck-fix
Browse files Browse the repository at this point in the history
Typecheck fix + optimizing Summary Tile
  • Loading branch information
dreadhalor authored Nov 9, 2023
2 parents 9a40618 + 47b2584 commit 3f1ef46
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 63 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@broadlume/willow-ui",
"version": "0.0.2",
"version": "0.0.3",
"author": {
"name": "dreadhalor",
"url": "https://scotthetrick.com"
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import './assets/typekit.css';
import './index.scss';

export * from '@src/assets/icons';
export * from '@src/lib/icons';

export * from '@components/accordion/accordion';
export * from '@components/alert-dialog/alert-dialog';
Expand Down
File renamed without changes.
80 changes: 56 additions & 24 deletions src/reports/summary-tile/summary-tile.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react';
import { SummaryTile } from './summary-tile';
import { SummaryTile, SummaryTileNumProps } from './summary-tile';
import { FaMousePointer, FaPhoneAlt, FaShoppingCart } from 'react-icons/fa';
import { BsChatFill } from 'react-icons/bs';

Expand All @@ -11,49 +11,73 @@ const meta: Meta<typeof SummaryTile> = {
export default meta;
type Story = StoryObj<typeof meta>;

const data = [
{ number: 1355003, label: 'Sessions', delta: '6%' },
{ number: 1115314, label: 'Users', delta: '7%' },
{ number: 3945916, label: 'Page Views', delta: '6%' },
const data: SummaryTileNumProps[] = [
{
number: '1:30',
label: 'Users',
current: 18246,
previous: 19328,
},
{
label: 'Sessions',
current: 10311,
previous: 10018,
},
{
label: 'Lawsuits',
current: 19605,
previous: 21967,
negativeIsGood: true,
},
{
label: 'Page Views',
current: 11674,
},
];

const customData: SummaryTileNumProps[] = [
{
current: '1:30',
label: 'Average Session Duration',
delta: '-3 Second',
negative: true,
},
{
current: '0:10',
label: 'Average Page Load',
delta: '-8 Second',
negative: true,
negativeIsGood: true,
},
];

const stackedData = [
const stackedData: SummaryTileNumProps[] = [
{
number: 81911,
current: 81911,
previous: 86006,

label: 'Total Leads',
delta: '-5%',
negative: true,
},
{
number: 57997,
current: 57997,
label: (
<>
<FaPhoneAlt />
Calls
</>
),
delta: '-4%',
negative: true,
},
{
number: 21257,
current: 21257,
previous: 19866,
label: (
<>
<FaMousePointer />
Forms
</>
),
delta: '-7%',
negative: true,
},
{
number: 2446,
current: 2446,
label: (
<>
<BsChatFill />
Expand All @@ -64,23 +88,31 @@ const stackedData = [
negative: true,
},
{
number: 57997,
current: 57997,
previous: 58577,
label: (
<>
<FaShoppingCart />
Sample Orders
</>
),
delta: '-1%',
negative: true,
},
];

export const Demo: Story = {
render: (_) => (
<div className='~flex ~flex-wrap ~gap-4'>
{data.map((tile) => (
<SummaryTile {...tile} />
{data.map((tile, index) => (
<SummaryTile key={index} {...tile} />
))}
</div>
),
};
export const CustomUnits: Story = {
render: (_) => (
<div className='~flex ~flex-wrap ~gap-4'>
{customData.map((tile, index) => (
<SummaryTile key={index} {...tile} />
))}
</div>
),
Expand All @@ -89,8 +121,8 @@ export const Demo: Story = {
export const Loading: Story = {
render: (_) => (
<div className='~flex ~flex-wrap ~gap-4'>
{data.map((tile) => (
<SummaryTile {...tile} loading />
{data.map((tile, index) => (
<SummaryTile key={index} {...tile} loading />
))}
</div>
),
Expand Down
146 changes: 111 additions & 35 deletions src/reports/summary-tile/summary-tile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,118 @@ import { Skeleton } from '@components/skeleton/skeleton';
import { cn } from '@src/lib/utils';
import { FaArrowCircleDown, FaArrowCircleUp } from 'react-icons/fa';

export type SummaryTileProps = {
number: number | string;
label: string | JSX.Element;
delta: string;
// Component to display the main number
const NumberDisplay = ({ loading, numberString }) =>
loading ? (
<Skeleton className='~h-6 ~w-24 ~rounded-md' />
) : (
<h5>{numberString}</h5>
);

// Component to display the label
const LabelDisplay = ({ loading, label }) =>
loading ? (
<Skeleton className='~h-4 ~w-28 ~rounded-md' />
) : (
<div className='~flex ~items-center ~gap-2'>{label}</div>
);

// Component to display the delta value with arrow
const DeltaDisplay = ({ loading, Arrow, deltaString, deltaClass, stacked }) => (
<div
className={`~flex ~items-center ~whitespace-nowrap ~pt-2 ~text-[10px] ${deltaClass}`}
>
{loading ? (
<Skeleton className='~h-4 ~w-20' />
) : deltaString ? (
// Show the arrow only if there is a delta value.
<>
<Arrow className='~text-xs' />
&nbsp;{deltaString}&nbsp;
<span className='~text-card-foreground'>Change</span>
</>
) : (
// Place the invisble arrow as a spacer if there is no delta value.
// If stacked, don't put the spacer.
!stacked && <Arrow className='~invisible ~text-xs' />
)}
</div>
);

type DefaultNumProps = {
/** The previous value. If no value is supplied, custom formatting is used. */
previous?: number;
};
type CustomNumProps = {
/** FOR CUSTOM FORMATTING: What number + unit to show as the change, i.e. "3%", "-8 second". */
delta?: string;
/** FOR CUSTOM FORMATTING: Whether to show negative colors + arrows. */
negative?: boolean;
};
export type SummaryTileNumProps = DefaultNumProps &
CustomNumProps & {
/** The current value. */
current?: number | string;
/** Label, i.e. "Total Sales" or "Users", or a JSX component to go where the label would be. */
label?: string | JSX.Element;
/** Should the colors for negative & positive be swapped? */
negativeIsGood?: boolean;
};

export type SummaryTileProps = SummaryTileNumProps & {
/** Whether to display loading placeholder. */
loading?: boolean;
/** Classnames for the Card container. */
className?: string;
/** Whether to remove y-padding to display the tile as one of a stacked tile group. */
stacked?: boolean;
loading?: boolean;
};

const SummaryTile = ({
number,
current,
previous,
label,
delta,
negative,
negativeIsGood = false,
stacked = false,
loading = false,
className,
stacked,
loading,
}: SummaryTileProps) => {
const deltaClass = negative ? '~text-danger' : '~text-beryl';
const Arrow = negative ? FaArrowCircleDown : FaArrowCircleUp;
const numberString =
typeof number === 'number' ? number.toLocaleString('en-US') : number;
// Determine whether to use custom or default formatting.
const noCustomFormatting =
current !== undefined &&
typeof current === 'number' &&
previous !== undefined;

// Function to get the number as a string with formatting.
const getNumberString = () => {
if (noCustomFormatting) return current.toLocaleString('en-US');
return (
current?.toLocaleString('en-US') ||
(typeof current === 'number' ? current.toLocaleString('en-US') : current)
);
};

// Function to calculate and get the delta string.
const getDeltaString = () => {
if (noCustomFormatting) {
const delta = ((current - previous) / previous) * 100;
return `${Math.round(delta).toLocaleString('en-US')}%`;
}
return delta;
};

// Function to determine if the number is negative.
const isNegative = noCustomFormatting ? current < previous : negative;

// Function to determine if the delta is good or not.
const isDeltaGood = isNegative ? !negativeIsGood : negativeIsGood;

// Classes and components based on conditions.
const deltaClass = isDeltaGood ? '~text-danger' : '~text-beryl';
const Arrow = isNegative ? FaArrowCircleDown : FaArrowCircleUp;

return (
<Card
className={cn(
Expand All @@ -40,36 +129,23 @@ const SummaryTile = ({
>
<div className='~py-auto ~flex ~flex-1 ~flex-col ~items-center ~justify-center'>
{!stacked && <div className='~flex-1'></div>} {/* spacer */}
{loading ? (
<Skeleton className='~h-6 ~w-24 ~rounded-md' />
) : (
<h5>{numberString}</h5>
)}
<NumberDisplay loading={loading} numberString={getNumberString()} />
<p
className={`caption-2 ~pt-1 ~text-center ~font-normal ~leading-5 ${
stacked ? '' : '~flex-1'
}`}
>
{loading ? (
<Skeleton className='~h-4 ~w-28 ~rounded-md' />
) : (
<div className='~flex ~items-center ~gap-2'>{label}</div>
)}
<LabelDisplay loading={loading} label={label} />
</p>
</div>
<div
className={`~flex ~items-center ~whitespace-nowrap ~pt-2 ~text-[10px] ${deltaClass}`}
>
{loading ? (
<Skeleton className='~h-4 ~w-20' />
) : (
<>
<Arrow className='~text-xs' />
&nbsp;{delta}&nbsp;
<span className='~text-card-foreground'>Change</span>
</>
)}
</div>

<DeltaDisplay
loading={loading}
Arrow={Arrow}
deltaString={getDeltaString()}
deltaClass={deltaClass}
stacked={stacked}
/>
</CardContent>
</Card>
);
Expand Down

0 comments on commit 3f1ef46

Please sign in to comment.