Skip to content

Commit

Permalink
feat: #1334 polymorphic base component
Browse files Browse the repository at this point in the history
  • Loading branch information
brettdorrans committed Apr 15, 2023
1 parent 8dc71f5 commit ab3a05e
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 14 deletions.
30 changes: 16 additions & 14 deletions src/components/Box/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import React from 'react';
import React, { ComponentProps, ElementType } from 'react';
import cx from 'classnames';
import type { BaseProps, SizeType } from '../types';
import styles from './Box.module.scss';
import Component, { CommonComponentProps } from '../Component';

export interface BoxProps {
readonly elevation?: SizeType;
readonly stroke?: SizeType;
readonly gutter?: SizeType;
readonly gutterX?: SizeType;
readonly gutterY?: SizeType;
}
export type BoxProps<T> = {
elevation?: SizeType;
stroke?: SizeType;
gutter?: SizeType;
gutterX?: SizeType;
gutterY?: SizeType;
} & CommonComponentProps<T>;

const Box = ({
const Box = <T extends ElementType = 'div'>({
as,
className,
as: Component = 'div',
testId = 'Box',
elevation = 'none',
gutter = 'none',
gutterX = 'none',
gutterY = 'none',
...restProps
}: BaseProps & BoxProps) => (
...props
}: BoxProps<T>) => (
<Component
as={as}
className={cx(
styles.this,
styles[`elevation-${elevation}`],
Expand All @@ -32,8 +34,8 @@ const Box = ({
},
className
)}
data-testid={testId}
{...restProps}
testId={testId}
{...props}
/>
);

Expand Down
51 changes: 51 additions & 0 deletions src/components/Component/Component.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { describe, expect, test, afterEach } from 'vitest';
import { cleanup, render, screen } from '@testing-library/react';

import Component from './index';

const setup = (Component: React.ReactElement) => render(Component);

afterEach(cleanup);

describe('Component', () => {
describe('as prop', () => {
test('it falls back to div', () => {
setup(<Component>Hello world</Component>);
expect(screen.getByTestId('Component').nodeName).toBe('DIV');
});

test('it works with HTML element', () => {
setup(<Component as="button">Hello world</Component>);
expect(screen.getByTestId('Component').nodeName).toBe('BUTTON');
expect(screen.getByRole('button')).toBeTruthy();
});

test('it works with React component', () => {
const TestComponent = ({ status }: { status: string }) => (
<p>test {status}</p>
);
setup(<Component as={TestComponent} status="info" />);
expect(screen.getByText('test info')).toBeTruthy();
});
});

describe('testId prop', () => {
test('it works with default testId', () => {
setup(<Component>Hello world</Component>);
expect(screen.getByTestId('Component')).toBeTruthy();
});

test('it works with specified testId', () => {
setup(<Component data-testid="TestId">Hello world</Component>);
expect(screen.getByTestId('TestId')).toBeTruthy();
});
});

test('it works with className', () => {
setup(<Component className="test">Hello world</Component>);
expect(
screen.getByTestId('Component').classList.contains('test')
).toBeTruthy();
});
});
26 changes: 26 additions & 0 deletions src/components/Component/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';

export type CommonComponentProps<T extends ElementType> = {
as?: T;
children?: ReactNode;
className?: string;
testId?: string;
};

const Component = <T extends ElementType = 'div'>({
as,
children,
testId = 'Component',
...props
}: CommonComponentProps<T> &
Omit<ComponentPropsWithoutRef<T>, keyof CommonComponentProps<T>>) => {
const PolymorphicComponent = as || 'div';
return (
<PolymorphicComponent data-testid={testId} {...props}>
{children}
</PolymorphicComponent>
);
};

Component.displayName = 'Component';
export default Component;

0 comments on commit ab3a05e

Please sign in to comment.