Skip to content

Commit

Permalink
Components Pages (#42)
Browse files Browse the repository at this point in the history
* text components

* button components

* table component

* docs

* tests

* button component table

* badge components

* card components

* components

* tests

* tests

* tests

* docs

* docs
  • Loading branch information
mwarman authored Mar 22, 2024
1 parent 536162b commit 8e0d430
Show file tree
Hide file tree
Showing 15 changed files with 715 additions and 23 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ The technology stack includes:

- Create React App - the foundation
- React Router Dom - routing
- React Query - data manipulation and caching
- TanStack React Query - data manipulation and caching
- Axios - http client
- Formik - form management
- Yup - validation
- Tailwind - styling
- Material Symbols - icons
- React Spring - animation
- Lodash - utility functions
- DayJS - date utility functions
- TanStack React Table - tables and datagrids
- Tailwind - styling
- Material Symbols - icons
- Testing Library React - tests
- Jest - tests
- MSW - API mocking
Expand Down
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@react-spring/web": "9.7.3",
"@tanstack/react-query": "5.24.1",
"@tanstack/react-query-devtools": "5.24.1",
"@tanstack/react-table": "8.14.0",
"axios": "1.6.7",
"classnames": "2.5.1",
"dayjs": "1.11.10",
Expand Down
12 changes: 11 additions & 1 deletion src/components/Router/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import AppearanceSettings from 'pages/SettingsPage/components/AppearanceSettings
import ComponentsPage from 'pages/ComponentsPage/ComponentsPage';
import TextComponents from 'pages/ComponentsPage/components/TextComponents';
import ButtonComponents from 'pages/ComponentsPage/components/ButtonComponents';
import BadgeComponents from 'pages/ComponentsPage/components/BadgeComponents';
import CardComponents from 'pages/ComponentsPage/components/CardComponents';
import UsersPage from 'pages/UsersPage/UsersPage';
import UserDetailLayout from 'pages/UsersPage/components/UserDetailLayout';
import UserDetail from 'pages/UsersPage/components/UserDetail';
Expand Down Expand Up @@ -61,9 +63,17 @@ export const routes: RouteObject[] = [
element: <TextComponents />,
},
{
path: 'button',
path: 'badges',
element: <BadgeComponents />,
},
{
path: 'buttons',
element: <ButtonComponents />,
},
{
path: 'cards',
element: <CardComponents />,
},
],
},
{
Expand Down
71 changes: 71 additions & 0 deletions src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { PropsWithClassName, PropsWithTestId } from '@leanstacks/react-common';
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import classNames from 'classnames';

/**
* Properties for the `Table` React component.
* @template TData - The type of the table data object.
* @param {ColumnDef<TData>[]} columns - An array of `ColumnDef`, column definition, objects.
* @param {TData[]} data - An array of data objects, of type `TData`,
* which are used to populate the rows of the table.
*/
interface TableProps<TData = unknown> extends PropsWithClassName, PropsWithTestId {
columns: ColumnDef<TData, any>[];
data: TData[];
}

/**
* The `Table` component renders a `table` element using the column definitions
* and data supplied in the properties.
*
* Uses TanStack Table.
* @template TData - The type of the table data object.
* @param {TableProps} props - Component properteis.
* @returns {JSX.Element} JSX
* @see {@link https://tanstack.com/table/latest TanStack Table}
*/
const Table = <TData,>({
className,
columns,
data,
testId = 'table',
}: TableProps<TData>): JSX.Element => {
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });

return (
<table
className={classNames('w-full border-collapse text-left text-sm', className)}
data-testid={testId}
>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
className="border-b border-neutral-400/25 py-2 pr-2 font-semibold"
>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</th>
))}
</tr>
))}
</thead>
<tbody className="align-baseline">
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="border-t border-neutral-400/10 py-2 pr-2">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
);
};

export default Table;
62 changes: 62 additions & 0 deletions src/components/Table/__tests__/Table.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { createColumnHelper } from '@tanstack/react-table';
import { render, screen } from 'test/test-utils';
import Table from '../Table';

describe('Table', () => {
type Foo = {
bar: string;
baz: number;
max: number;
};
const data: Foo[] = [
{
bar: 'one',
baz: 1,
max: 20,
},
{
bar: 'two',
baz: 2,
max: 40,
},
];
const columnHelper = createColumnHelper<Foo>();
const columns = [
columnHelper.group({
id: 'grouped',
header: 'Grouped',
columns: [
columnHelper.accessor('bar', { cell: (info) => info.renderValue(), header: 'Bar' }),
columnHelper.accessor('baz', { cell: (info) => info.renderValue(), header: 'Baz' }),
],
}),
columnHelper.accessor('max', { cell: (info) => info.renderValue(), header: 'Max' }),
];

it('should render successfully', async () => {
// ARRANGE
render(<Table<Foo> data={data} columns={columns} />);
await screen.findByTestId('table');

// ASSERT
expect(screen.getByTestId('table')).toBeDefined();
});

it('should use custom testId', async () => {
// ARRANGE
render(<Table<Foo> data={data} columns={columns} testId="custom-testId" />);
await screen.findByTestId('custom-testId');

// ASSERT
expect(screen.getByTestId('custom-testId')).toBeDefined();
});

it('should use custom className', async () => {
// ARRANGE
render(<Table<Foo> data={data} columns={columns} className="custom-className" />);
await screen.findByTestId('table');

// ASSERT
expect(screen.getByTestId('table').classList).toContain('custom-className');
});
});
10 changes: 8 additions & 2 deletions src/pages/ComponentsPage/ComponentsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,14 @@ const ComponentsPage = (): JSX.Element => {
<MenuNavLink to="text" iconName="text_fields" styleActive>
Text
</MenuNavLink>
<MenuNavLink to="button" iconName="buttons_alt" styleActive>
Button
<MenuNavLink to="badges" iconName="counter_3" styleActive>
Badges
</MenuNavLink>
<MenuNavLink to="buttons" iconName="buttons_alt" styleActive>
Buttons
</MenuNavLink>
<MenuNavLink to="cards" iconName="crop_square" styleActive>
Cards
</MenuNavLink>
</div>
<div className="md:col-span-3" data-testid="page-components-content">
Expand Down
102 changes: 102 additions & 0 deletions src/pages/ComponentsPage/components/BadgeComponents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { PropsWithClassName, PropsWithTestId } from '@leanstacks/react-common';
import { createColumnHelper } from '@tanstack/react-table';

import { ComponentProperty } from '../model/components';
import Text from 'components/Text/Text';
import Table from 'components/Table/Table';
import CodeSnippet from 'components/Text/CodeSnippet';
import Badge from 'components/Badge/Badge';

/**
* Properties for the `BadgeComponents` React component.
* @see {@link PropsWithClassName}
* @see {@link PropsWithTestId}
*/
interface BadgeComponentsProps extends PropsWithClassName, PropsWithTestId {}

/**
* The `BadgeComponents` React component renders a set of examples illustrating
* the use of the `Badge` component.
* @param {BadgeComponentsProps} props - Component properties.
* @returns {JSX.Element} JSX
*/
const BadgeComponents = ({
className,
testId = 'components-badge',
}: BadgeComponentsProps): JSX.Element => {
const data: ComponentProperty[] = [
{
name: 'children',
description: 'The content to be displayed.',
},
{
name: 'className',
description: 'Optional. Additional CSS class names.',
},
{
name: 'testId',
description: 'Optional. Identifier for testing.',
},
];
const columnHelper = createColumnHelper<ComponentProperty>();
const columns = [
columnHelper.accessor('name', {
cell: (info) => <span className="font-mono text-sky-600">{info.getValue()}</span>,
header: () => 'Name',
}),
columnHelper.accessor('description', {
cell: (info) => info.renderValue(),
header: () => 'Description',
}),
];

return (
<section className={className} data-testid={testId}>
<Text variant="heading2" className="mb-4">
Badge Component
</Text>

<div className="my-8">
The <span className="font-mono font-bold">Badge</span> component displays a stylized
counter. Useful for displaying the number of items of a specific type, for example, the
number of notifications.
</div>

<div className="my-8">
<Text variant="heading3" className="mb-2">
Properties
</Text>
<Table<ComponentProperty> data={data} columns={columns} />
</div>

<Text variant="heading3">Examples</Text>
<div className="my-8">
<div className="mb-2 flex place-content-center rounded border border-neutral-500/10 p-4 dark:bg-neutral-700/25">
<Badge>3</Badge>
</div>
<CodeSnippet className="my-2" code={`<Badge>3</Badge>`} />
</div>

<div className="my-8">
<div className="mb-2 flex place-content-center rounded border border-neutral-500/10 p-4 dark:bg-neutral-700/25">
<Badge>999+</Badge>
</div>
<CodeSnippet className="my-2" code={`<Badge>999+</Badge>`} />
</div>

<div className="my-8">
<div className="mb-2 flex place-content-center rounded border border-neutral-500/10 p-4 dark:bg-neutral-700/25">
<Badge className="bg-blue-500" testId="my-badge">
19
</Badge>
</div>
<CodeSnippet
className="my-2"
code={`<Badge className='bg-blue-500' testId='my-badge'>19</Badge>`}
/>
</div>
</section>
);
};

export default BadgeComponents;
Loading

0 comments on commit 8e0d430

Please sign in to comment.