diff --git a/src/components/InlineMetrics/InlineMetrics.props.ts b/src/components/InlineMetrics/InlineMetrics.props.ts new file mode 100644 index 00000000..ab060c82 --- /dev/null +++ b/src/components/InlineMetrics/InlineMetrics.props.ts @@ -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; +}; diff --git a/src/components/InlineMetrics/InlineMetrics.stories.tsx b/src/components/InlineMetrics/InlineMetrics.stories.tsx new file mode 100644 index 00000000..5d9dd31f --- /dev/null +++ b/src/components/InlineMetrics/InlineMetrics.stories.tsx @@ -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: () => ( + + + + ), + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + trend: 'Negative', + trendValue: '+24%', + metrics: '$123.12', + label: 'Total Earnings', + }, +}; diff --git a/src/components/InlineMetrics/InlineMetrics.styles.ts b/src/components/InlineMetrics/InlineMetrics.styles.ts new file mode 100644 index 00000000..5210eff1 --- /dev/null +++ b/src/components/InlineMetrics/InlineMetrics.styles.ts @@ -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> } & 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>; + + return iconConfig[trend]; +}; diff --git a/src/components/InlineMetrics/InlineMetrics.test.tsx b/src/components/InlineMetrics/InlineMetrics.test.tsx new file mode 100644 index 00000000..73e7825b --- /dev/null +++ b/src/components/InlineMetrics/InlineMetrics.test.tsx @@ -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(, { + containerId: 'inline-metrics', + props: { + trend: ['Negative', 'None', 'Positive'], + }, + }); + + it('should render the inline metrics', () => { + const { container } = getInlineMetrics(); + expect(container).toBeInTheDocument(); + }); + + it('should render the inline metrics with trend with proper color (Positive)', () => { + const { trendContainer } = getInlineMetrics( + , + ); + expect(trendContainer).toHaveStyle('color: rgb(29, 124, 77)'); + }); + + it('should render the inline metrics with trend with proper color (None)', () => { + const { trendContainer } = getInlineMetrics(); + expect(trendContainer).toHaveStyle('color: rgb(39, 46, 53)'); + }); + + it('should render the inline metrics with trend with proper color (Negative)', () => { + const { trendContainer } = getInlineMetrics( + , + ); + expect(trendContainer).toHaveStyle('color: rgb(197, 52, 52)'); + }); +}); diff --git a/src/components/InlineMetrics/InlineMetrics.tsx b/src/components/InlineMetrics/InlineMetrics.tsx new file mode 100644 index 00000000..aff774ac --- /dev/null +++ b/src/components/InlineMetrics/InlineMetrics.tsx @@ -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 = ({ + metrics, + label, + trend = 'None', + trendValue, + custom, + ...restProps +}) => { + const styles = useMemo( + () => stylesBuilder({ trend, custom }), + [trend, custom], + ); + + const iconName = useMemo(() => resolveIconName(trend), [trend]); + + return ( + + {label && ( + + ); +}; diff --git a/src/components/InlineMetrics/index.ts b/src/components/InlineMetrics/index.ts new file mode 100644 index 00000000..c35225aa --- /dev/null +++ b/src/components/InlineMetrics/index.ts @@ -0,0 +1,3 @@ +export { InlineMetrics } from './InlineMetrics'; +export type { InlineMetricsProps } from './InlineMetrics.props'; +export { inlineMetricsStyles } from './InlineMetrics.styles'; diff --git a/src/components/InlineMetrics/stylesBuilder.ts b/src/components/InlineMetrics/stylesBuilder.ts new file mode 100644 index 00000000..1b59d602 --- /dev/null +++ b/src/components/InlineMetrics/stylesBuilder.ts @@ -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, + }; +}; diff --git a/src/docs-components/InlineMetricsDocs.tsx b/src/docs-components/InlineMetricsDocs.tsx new file mode 100644 index 00000000..94989262 --- /dev/null +++ b/src/docs-components/InlineMetricsDocs.tsx @@ -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 = { + None: 'Neutral', + Positive: 'Positive', + Negative: 'Negative', +}; + +export const InlineMetricsDocs = () => ( + + + + {trends.map((trend) => ( + + + Intent: {intentNames[trend]} + + + + + + ))} + + + +); diff --git a/src/index.ts b/src/index.ts index fcc44c65..f4d1ef90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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';