diff --git a/package-lock.json b/package-lock.json index 95a4fdc..95a1180 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.1.1", "license": "MIT", "dependencies": { - "classnames": "^2.3.2" + "classnames": "^2.3.2", + "dayjs": "^1.11.9" }, "devDependencies": { "@babel/preset-env": "^7.22.15", @@ -9553,6 +9554,11 @@ "node": ">=12" } }, + "node_modules/dayjs": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", + "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 248a326..ee97348 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ }, "homepage": "https://github.com/leanstacks/react-common#readme", "dependencies": { - "classnames": "^2.3.2" + "classnames": "^2.3.2", + "dayjs": "^1.11.9" }, "devDependencies": { "@babel/preset-env": "^7.22.15", diff --git a/src/components/Date/Date.stories.ts b/src/components/Date/Date.stories.ts new file mode 100644 index 0000000..88d17b4 --- /dev/null +++ b/src/components/Date/Date.stories.ts @@ -0,0 +1,85 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { default as DateComponent } from './Date'; +import { DateFormat } from './Date.types'; + +const meta = { + title: 'Components/Date', + component: DateComponent, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + date: { description: 'The text to display.' }, + className: { description: 'Additional CSS classes.' }, + testId: { description: 'Unit test identifier.' }, + format: { + control: { + type: 'select', + labels: { + 'MM/DD/YYYY': 'Date', + dddd: 'Day of the Week', + 'H[h] mm[m]': 'Hours and Minutes', + 'h:mma': 'Time', + 'h:mma ddd MMM D': 'Timestamp Short', + 'dddd MMMM D [at] h:mma': 'Timestamp', + }, + }, + options: [ + DateFormat.DATE, + DateFormat.DAY_OF_WEEK, + DateFormat.HOURS_AND_MINUTES, + DateFormat.TIME, + DateFormat.TIMESTAMP, + DateFormat.TIMESTAMP_SHORT, + ], + description: 'The date format.', + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const FormatDate: Story = { + args: { + date: new Date().toISOString(), + }, +}; + +export const FormatDayOfWeek: Story = { + args: { + date: new Date().toISOString(), + format: DateFormat.DAY_OF_WEEK, + }, +}; + +export const FormatHoursAndMinutes: Story = { + args: { + date: new Date().toISOString(), + format: DateFormat.HOURS_AND_MINUTES, + }, +}; + +export const FormatTime: Story = { + args: { + date: new Date().toISOString(), + format: DateFormat.TIME, + }, +}; + +export const FormatTimestamp: Story = { + args: { + date: new Date().toISOString(), + format: DateFormat.TIMESTAMP, + }, +}; + +export const FormatTimestampShort: Story = { + args: { + date: new Date().toISOString(), + format: DateFormat.TIMESTAMP_SHORT, + }, +}; diff --git a/src/components/Date/Date.test.tsx b/src/components/Date/Date.test.tsx new file mode 100644 index 0000000..05d28bc --- /dev/null +++ b/src/components/Date/Date.test.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { getAllByTestId, render } from '@testing-library/react'; + +import Date from './Date'; +import { DateFormat } from './Date.types'; + +describe('Date', () => { + it('should render successfully', () => { + const { getByTestId } = render(); + + expect(getByTestId('date')).toBeDefined(); + }); + + it('should render format Date successfully', () => { + const { getByTestId } = render(); + + expect(getByTestId('date')).toBeDefined(); + }); + + it('should render format DayOfWeek successfully', () => { + const { getByTestId } = render(); + + expect(getByTestId('date')).toBeDefined(); + }); + + it('should render format HoursAndMinutes successfully', () => { + const { getByTestId } = render(); + + expect(getByTestId('date')).toBeDefined(); + }); + + it('should render format Time successfully', () => { + const { getByTestId } = render(); + + expect(getByTestId('date')).toBeDefined(); + }); + + it('should render format Timestamp successfully', () => { + const { getByTestId } = render(); + + expect(getByTestId('date')).toBeDefined(); + }); + + it('should render format TimestampShort successfully', () => { + const { getByTestId } = render(); + + expect(getByTestId('date')).toBeDefined(); + }); + + it('should use custom testID', () => { + const { getByTestId, queryByTestId } = render(); + + expect(queryByTestId('date')).toBeNull(); + expect(getByTestId('custom-testid')).toBeDefined(); + }); + + it('should use classes from className property', () => { + const { getByTestId } = render(); + + expect(getByTestId('date').classList).toContain('custom-class'); + }); +}); diff --git a/src/components/Date/Date.tsx b/src/components/Date/Date.tsx new file mode 100644 index 0000000..7bf2581 --- /dev/null +++ b/src/components/Date/Date.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import dayjs from 'dayjs'; + +import { DateFormat, DateProps } from './Date.types'; + +const Date: React.FC = ({ + className, + date, + testId = 'date', + format = DateFormat.DATE, +}) => { + return ( + + {dayjs(date).format(format)} + + ); +}; + +export default Date; diff --git a/src/components/Date/Date.types.ts b/src/components/Date/Date.types.ts new file mode 100644 index 0000000..f8c6a98 --- /dev/null +++ b/src/components/Date/Date.types.ts @@ -0,0 +1,15 @@ +export enum DateFormat { + DATE = 'MM/DD/YYYY', + DAY_OF_WEEK = 'dddd', + HOURS_AND_MINUTES = 'H[h] mm[m]', + TIME = 'h:mma', + TIMESTAMP_SHORT = 'h:mma ddd MMM D', + TIMESTAMP = 'dddd MMMM D [at] h:mma', +} + +export interface DateProps { + className?: string; + date: string | number; + format?: DateFormat; + testId?: string; +} diff --git a/src/components/Date/index.ts b/src/components/Date/index.ts new file mode 100644 index 0000000..79131fb --- /dev/null +++ b/src/components/Date/index.ts @@ -0,0 +1,3 @@ +export { default as Date } from './Date'; + +export { DateFormat } from './Date.types'; diff --git a/src/components/index.ts b/src/components/index.ts index b0c76af..c5fe84f 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1 +1,2 @@ +export * from './Date'; export * from './Text';