Skip to content
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

Typecheck fix #6

Merged
merged 4 commits into from
Nov 9, 2023
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
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