Skip to content

Commit

Permalink
feat: TET-903 add InlineMetrics (#154)
Browse files Browse the repository at this point in the history
* feat: TET-903 initial commit

* fix: TET-903 fix build error
  • Loading branch information
karolinaszarek authored Sep 2, 2024
1 parent 19253d4 commit 9236719
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 0 deletions.
11 changes: 11 additions & 0 deletions src/components/InlineMetrics/InlineMetrics.props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { InlineMetricsConfig } from './InlineMetrics.styles';

export type TrendType = 'None' | 'Positive' | 'Negative';

export type InlineMetricsProps = {
metrics?: string;
label?: string;
trend?: TrendType;
trendValue?: string;
custom?: InlineMetricsConfig;
};
39 changes: 39 additions & 0 deletions src/components/InlineMetrics/InlineMetrics.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react';

import { InlineMetrics } from './InlineMetrics';

import { InlineMetricsDocs } from '@/docs-components/InlineMetricsDocs';
import { TetDocs } from '@/docs-components/TetDocs';

const meta = {
title: 'Metrics / InlineMetrics',
component: InlineMetrics,
tags: ['autodocs'],
args: {},
parameters: {
backgrounds: {},
docs: {
description: {
component:
'A set of several grouped components that displays numerical data, such as, for example, key performance indicators (KPIs). Metrics provide users with a clear, visual representation of essential statistics or progress.',
},
page: () => (
<TetDocs docs="https://docs.tetrisly.com/components/in-progress/metrics">
<InlineMetricsDocs />
</TetDocs>
),
},
},
} satisfies Meta<typeof InlineMetrics>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
trend: 'Negative',
trendValue: '+24%',
metrics: '$123.12',
label: 'Total Earnings',
},
};
76 changes: 76 additions & 0 deletions src/components/InlineMetrics/InlineMetrics.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { TrendType } from './InlineMetrics.props';

import type { BaseProps } from '@/types/BaseProps';
import { IconName } from '@/utility-types/IconName';

export type InlineMetricsConfig = {
innerElements: {
label: BaseProps;
metric: BaseProps;
trendContainer: BaseProps;
trend: { trend: Partial<Record<TrendType, BaseProps>> } & BaseProps;
icon: BaseProps;
trendValue: BaseProps;
};
} & BaseProps;

export const defaultConfig = {
w: 'fill',
h: '',
display: 'flex',
flexDirection: 'column',
innerElements: {
trendContainer: {
display: 'flex',
color: '$color-content-primary',
gap: '$space-component-gap-medium',
},
label: {
color: '$color-content-secondary',
text: '$typo-body-medium',
marginBottom: '$space-component-gap-medium',
},
metric: {
text: '$typo-header-4xLarge',
color: '$color-content-primary',
},
trend: {
gap: '$space-component-gap-small',
padding: '$space-component-padding-xSmall 0',
display: 'flex',
alignItems: 'center',
alignSelf: 'flex-end',
trend: {
None: {},
Positive: {
color: '$color-content-positive-secondary',
},
Negative: {
color: '$color-content-negative-secondary',
},
},
},
icon: {
display: 'flex',
},
trendValue: {
text: '$typo-body-strong-medium',
display: 'flex',
alignItems: 'end',
},
},
} satisfies InlineMetricsConfig;

export const inlineMetricsStyles = {
defaultConfig,
};

export const resolveIconName = (trend: TrendType) => {
const iconConfig = {
None: '20-minus',
Positive: '20-trend-up',
Negative: '20-trend-down',
} satisfies Record<TrendType, IconName<20>>;

return iconConfig[trend];
};
45 changes: 45 additions & 0 deletions src/components/InlineMetrics/InlineMetrics.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { InlineMetrics } from './InlineMetrics';
import { render } from '../../tests/render';

import { customPropTester } from '@/tests/customPropTester';

const getInlineMetrics = (jsx: JSX.Element) => {
const { getByTestId } = render(jsx);
return {
container: getByTestId('inline-metrics'),
trendContainer: getByTestId('inline-metrics-trend'),
};
};

describe('Inline Metrics', () => {
customPropTester(<InlineMetrics />, {
containerId: 'inline-metrics',
props: {
trend: ['Negative', 'None', 'Positive'],
},
});

it('should render the inline metrics', () => {
const { container } = getInlineMetrics(<InlineMetrics />);
expect(container).toBeInTheDocument();
});

it('should render the inline metrics with trend with proper color (Positive)', () => {
const { trendContainer } = getInlineMetrics(
<InlineMetrics trend="Positive" />,
);
expect(trendContainer).toHaveStyle('color: rgb(29, 124, 77)');
});

it('should render the inline metrics with trend with proper color (None)', () => {
const { trendContainer } = getInlineMetrics(<InlineMetrics trend="None" />);
expect(trendContainer).toHaveStyle('color: rgb(39, 46, 53)');
});

it('should render the inline metrics with trend with proper color (Negative)', () => {
const { trendContainer } = getInlineMetrics(
<InlineMetrics trend="Negative" />,
);
expect(trendContainer).toHaveStyle('color: rgb(197, 52, 52)');
});
});
56 changes: 56 additions & 0 deletions src/components/InlineMetrics/InlineMetrics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Icon } from '@virtuslab/tetrisly-icons';
import { MarginProps } from '@xstyled/styled-components';
import { type FC, useMemo } from 'react';

import { InlineMetricsProps } from './InlineMetrics.props';
import { resolveIconName } from './InlineMetrics.styles';
import { stylesBuilder } from './stylesBuilder';
import { Label } from '../Label';

import { tet } from '@/tetrisly';

export const InlineMetrics: FC<InlineMetricsProps & MarginProps> = ({
metrics,
label,
trend = 'None',
trendValue,
custom,
...restProps
}) => {
const styles = useMemo(
() => stylesBuilder({ trend, custom }),
[trend, custom],
);

const iconName = useMemo(() => resolveIconName(trend), [trend]);

return (
<tet.div {...styles.container} data-testid="inline-metrics" {...restProps}>
{label && (
<Label
label={label}
{...styles.label}
data-testid="inline-metrics-label"
/>
)}

<tet.div
{...styles.trendContainer}
data-testid="inline-metrics-trend-container"
>
<tet.div {...styles.metric} data-testid="inline-metrics-metric">
{metrics}
</tet.div>
<tet.div {...styles.trend} data-testid="inline-metrics-trend">
<Icon {...styles.icon} name={iconName} />
<tet.span
{...styles.trendValue}
data-testid="inline-metrics-trend-value"
>
{trendValue}
</tet.span>
</tet.div>
</tet.div>
</tet.div>
);
};
3 changes: 3 additions & 0 deletions src/components/InlineMetrics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { InlineMetrics } from './InlineMetrics';
export type { InlineMetricsProps } from './InlineMetrics.props';
export { inlineMetricsStyles } from './InlineMetrics.styles';
26 changes: 26 additions & 0 deletions src/components/InlineMetrics/stylesBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { InlineMetricsProps, TrendType } from './InlineMetrics.props';
import { defaultConfig } from './InlineMetrics.styles';

import { mergeConfigWithCustom } from '@/services';

type StylesBuilderParams = {
trend: TrendType;
custom: InlineMetricsProps['custom'];
};

export const stylesBuilder = ({ trend, custom }: StylesBuilderParams) => {
const { innerElements, ...restStyles } = mergeConfigWithCustom({
defaultConfig,
custom,
});
const { trend: trendContainer } = innerElements;
const { trend: trendStyles, ...restTrendStyles } = trendContainer;

const trendContainerStyles = { ...trendStyles[trend], ...restTrendStyles };

return {
container: restStyles,
...innerElements,
trend: trendContainerStyles,
};
};
52 changes: 52 additions & 0 deletions src/docs-components/InlineMetricsDocs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { InlineMetrics } from '@/components/InlineMetrics/InlineMetrics';
import { TrendType } from '@/components/InlineMetrics/InlineMetrics.props';
import { tet } from '@/tetrisly';

const trends: TrendType[] = ['None', 'Positive', 'Negative'];
const intentNames: Record<TrendType, string> = {
None: 'Neutral',
Positive: 'Positive',
Negative: 'Negative',
};

export const InlineMetricsDocs = () => (
<tet.section py="$space-component-padding-4xLarge">
<tet.div px="$dimension-800" py="$dimension-300">
<tet.div display="flex">
{trends.map((trend) => (
<tet.section
key={trend}
display="flex"
flexDirection="column"
px="$dimension-400"
my="auto"
>
<tet.div
color="$color-content-secondary"
text="$typo-body-large"
borderBottom="1px solid"
borderColor="$color-border-default"
>
Intent: {intentNames[trend]}
</tet.div>
<tet.div
key={trend}
display="flex"
flexBasis="100px"
flexShrink="0"
flexGrow="1"
mt={40}
>
<InlineMetrics
trend={trend}
trendValue="24%"
metrics="$123.12"
label="Total Earnings"
/>
</tet.div>
</tet.section>
))}
</tet.div>
</tet.div>
</tet.section>
);
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from './components/Icon';
export * from './components/IconButton';
export * from './components/InlineBanner';
export * from './components/InlineMessage';
export * from './components/InlineMetrics';
export * from './components/InlineSearchInput';
export * from './components/Label';
export * from './components/Loader';
Expand Down

0 comments on commit 9236719

Please sign in to comment.