Skip to content
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
59 changes: 59 additions & 0 deletions src/components/Currency/Currency.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Meta, StoryObj } from '@storybook/react';

import Currency from './Currency';
import {
CurrencyCode,
CurrencyDisplay,
CurrencySign,
SignDisplay,
Unit,
UnitDisplay,
} from '../../utils';

const meta = {
title: 'Components/Currency',
component: Currency,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
className: { description: 'Additional CSS classes.' },
testId: { description: 'Unit test identifier.' },
currency: { control: 'select', description: 'The ISO 4217 currency code.' },
currencyDisplay: { control: 'select', description: 'How to format the currency for display.' },
currencySign: { control: 'select', description: 'How to display negative values.' },
value: { description: 'The amount to be formatted and displayed.' },
},
} satisfies Meta<typeof Currency>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
value: 15.99,
},
};

export const Code: Story = {
args: {
value: 15.99,
currency: CurrencyCode.GBP,
},
};

export const Narrow: Story = {
args: {
value: 15.99,
currencyDisplay: CurrencyDisplay.NarrowSymbol,
},
};

export const Accounting: Story = {
args: {
value: -15.99,
currencySign: CurrencySign.Accounting,
},
};
68 changes: 68 additions & 0 deletions src/components/Currency/Currency.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import { render } from '@testing-library/react';

import Currency from './Currency';
import { CurrencyCode, CurrencyDisplay, CurrencySign } from '../../utils/constants';

describe('Currency', () => {
it('should render successfully', () => {
const { getByTestId } = render(<Currency value={19.99} />);

expect(getByTestId('currency')).toBeDefined();
});

it('should use custom test ID', () => {
const { getByTestId, queryByTestId } = render(
<Currency value={19.99} testId="custom-testid" />,
);

expect(queryByTestId('currency')).toBeNull();
expect(getByTestId('custom-testid')).toBeDefined();
});

it('should use classes from className property', () => {
const { getByTestId } = render(<Currency value={19.99} className="custom-class" />);

expect(getByTestId('currency').classList).toContain('custom-class');
});

it('should use default currency code', () => {
const { getByTestId } = render(<Currency value={19.99} />);

expect(getByTestId('currency').textContent).toBe('$19.99');
});

it('should use specified currency code', () => {
const { getByTestId } = render(<Currency value={19.99} currency={CurrencyCode.CAD} />);

expect(getByTestId('currency').textContent).toBe('CA$19.99');
});

it('should use default currency display', () => {
const { getByTestId } = render(<Currency value={19.99} currency={CurrencyCode.CAD} />);

expect(getByTestId('currency').textContent).toBe('CA$19.99');
});

it('should use specified currency display', () => {
const { getByTestId } = render(
<Currency value={19.99} currency={CurrencyCode.CAD} currencyDisplay={CurrencyDisplay.Name} />,
);

expect(getByTestId('currency').textContent).toBe('19.99 Canadian dollars');
});

it('should use default currency sign', () => {
const { getByTestId } = render(<Currency value={-19.99} />);

expect(getByTestId('currency').textContent).toBe('-$19.99');
});

it('should use accounting currency sign', () => {
const { getByTestId } = render(
<Currency value={-19.99} currencySign={CurrencySign.Accounting} />,
);

expect(getByTestId('currency').textContent).toBe('($19.99)');
});
});
37 changes: 37 additions & 0 deletions src/components/Currency/Currency.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { useMemo } from 'react';

import { CurrencyProps } from './Currency.types';
import { CurrencyCode } from '../../utils/constants';
import { formatNumber } from '../../utils/numbers';

/**
* The `Currency` Reactcomponent formats and renders a currency value.
* @param {CurrencyProps} props - Component properties.
* @returns {JSX.Element} JSX
*/
const Currency: React.FC<CurrencyProps> = ({
className,
currency = CurrencyCode.USD,
currencyDisplay,
currencySign,
value,
testId = 'currency',
}: CurrencyProps): JSX.Element => {
const val = useMemo(() => {
const formatOptions: Intl.NumberFormatOptions = {
style: 'currency',
currency,
currencyDisplay,
currencySign,
};
return formatNumber(value, formatOptions);
}, [value, currency, currencyDisplay, currencySign]);

return (
<span className={className} data-testid={testId}>
{val}
</span>
);
};

export default Currency;
19 changes: 19 additions & 0 deletions src/components/Currency/Currency.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { CurrencyCode, CurrencyDisplay, CurrencySign } from '../../utils/constants';

/**
* Properties for the `Currency` component.
* @param {string} [className] - Optional. CSS classes to apply to the component.
* @param {CurrencyCode} [currency] - Optional. The ISO 4217 currency code. Default: `USD`.
* @param {CurrencyDisplay} [currencyDisplay] - Optional. How the currency is displayed. Default: `symbol`.
* @param {CurrencySign} [currencySign] - Optional. How negative values are displayed. Default: `standard`.
* @param {number} value - The amount.
* @param {string} [testId] - Optional. A test library identifier. Default: `currency`.
*/
export interface CurrencyProps {
className?: string;
currency?: CurrencyCode;
currencyDisplay?: CurrencyDisplay;
currencySign?: CurrencySign;
value: number;
testId?: string;
}
1 change: 1 addition & 0 deletions src/components/Currency/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Currency } from './Currency';
2 changes: 1 addition & 1 deletion src/components/Decimal/Decimal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';

import { DecimalProps } from './decimal.types';
import { DecimalProps } from './Decimal.types';
import { formatNumber } from '../../utils/numbers';

/**
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './Button';
export * from './Currency';
export * from './Date';
export * from './DayOfTheWeek';
export * from './Decimal';
Expand Down
34 changes: 34 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
/**
* ISO 4217 Currency Codes
* @see https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes
*/
export enum CurrencyCode {
CAD = 'CAD', // Canadian Dollar
EUR = 'EUR', // Euro
GBP = 'GBP', // Pound sterling (Great Britian)
MXN = 'MXN', // Mexican Peso
USD = 'USD', // US Dollar
}

/**
* Possible `currencyDisplay` values to use with `Intl.NumberFormat`.
* Default: `symbol`.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
*/
export enum CurrencyDisplay {
Code = 'code',
Name = 'name',
NarrowSymbol = 'narrowSymbol',
Symbol = 'symbol',
}

/**
* Possible `currencySign` values to use with `Intl.NumberFormat`.
* Default: `standard`.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
*/
export enum CurrencySign {
Accounting = 'accounting',
Standard = 'standard',
}

/**
* Possible `signDisplay` values to use with `Intl.NumberFormat`. By default,
* display sign for negative numbers only, including negative zero. Default: `auto`.
Expand Down
9 changes: 8 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
export { SignDisplay, Unit, UnitDisplay } from './constants';
export {
CurrencyCode,
CurrencyDisplay,
CurrencySign,
SignDisplay,
Unit,
UnitDisplay,
} from './constants';