Skip to content

Commit

Permalink
feat(ui): #1977: TableCell (#1979)
Browse files Browse the repository at this point in the history
* feat(ui): add Skeleton component

* feat(ui): implement responsive typography

* feat(ui): add `bodyTechnical` text style

* chore: changeset

* feat(ui): #1977: add `TableCell` UI component

* chore: changeset

* fix(ui): add `as` param to TableCell
  • Loading branch information
VanishMax authored Jan 20, 2025
1 parent 62eff53 commit cab203e
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/sweet-jeans-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/ui': minor
---

Add `TableCell` UI component
54 changes: 54 additions & 0 deletions packages/ui/src/TableCell/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Meta, StoryObj } from '@storybook/react';

import { TableCell } from '.';
import { Pill } from '../Pill';

const meta: Meta<typeof TableCell> = {
component: TableCell,
tags: ['autodocs', '!dev', 'density'],
};
export default meta;

type Story = StoryObj<typeof TableCell>;

export const Basic: Story = {
render: function Render() {
return (
<div className='grid grid-cols-5'>
<div className='col-span-5 grid grid-cols-subgrid'>
<TableCell heading>Name</TableCell>
<TableCell heading>Loading</TableCell>
<TableCell heading>Price</TableCell>
<TableCell heading>Amount</TableCell>
<TableCell heading>Number</TableCell>
</div>
<div className='col-span-5 grid grid-cols-subgrid'>
<TableCell cell>Hello</TableCell>
<TableCell cell loading={true}>
World
</TableCell>
<TableCell cell>What</TableCell>
<TableCell cell>
<Pill>Pending</Pill>
</TableCell>
<TableCell cell numeric>
11.1111
</TableCell>
</div>
<div className='col-span-5 grid grid-cols-subgrid'>
<TableCell footer>Hello</TableCell>
<TableCell footer loading={true}>
World
</TableCell>
<TableCell footer>What</TableCell>
<TableCell footer>
<Pill>Pending</Pill>
</TableCell>
<TableCell footer numeric>
88.2222
</TableCell>
</div>
</div>
);
},
};
133 changes: 133 additions & 0 deletions packages/ui/src/TableCell/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import cn from 'clsx';
import { ElementType, ReactNode } from 'react';
import { Density, useDensity } from '../utils/density';
import { Skeleton } from '../Skeleton';
import {
tableHeading,
tableHeadingMedium,
tableHeadingSmall,
tableItem,
tableItemMedium,
tableItemSmall,
} from '../utils/typography';

export type TableCellVariant = 'title' | 'heading' | 'cell' | 'lastCell' | 'footer';

type TableCellType = {
[K in TableCellVariant]: Record<K, true> & Partial<Record<Exclude<TableCellVariant, K>, never>>;
}[TableCellVariant];

type TableCellPropType =
| (TableCellType & { variant?: never })
| (Partial<Record<TableCellVariant, never>> & {
/** dynamic table cell type: `'title' | 'heading' | 'cell' | 'lastCell' | 'footer'` */
variant?: TableCellVariant;
});

export type TableCellProps = TableCellPropType & {
children: ReactNode;
as?: ElementType;
loading?: boolean;
/** Renders numbers in a monospace font with letters of equal size */
numeric?: boolean;
};

const classesByVariant: Record<TableCellVariant, string> = {
title: cn('text-text-primary'),
heading: cn('text-text-secondary border-b border-b-other-tonalStroke'),
cell: cn('text-text-primary border-b border-b-other-tonalStroke'),
lastCell: cn('text-text-primary'),
footer: cn('text-text-secondary'),
};

const classesByDensity: Record<Density, string> = {
sparse: 'py-1 px-3 h-14',
compact: 'py-1 px-3 h-12',
slim: 'py-1 px-3 h-8',
};

const defaultFont: Record<Density, string> = {
sparse: tableItem,
compact: tableItemMedium,
slim: tableItemSmall,
};

const headingFont: Record<Density, string> = {
sparse: tableHeading,
compact: tableHeadingMedium,
slim: tableHeadingSmall,
};

/**
* **TableCell** is a unified component for tables within Penumbra ecosystem.
* It has multiple style variants:
*
* 1. `heading` – darkened bold text
* 2. `cell` – regular info cell
* 3. `title` – emphasized cell
* 4. `lastCell` – borderless cell
* 5. `footer` – borderless darkened cell
*
* You can pass the variant as a named prop (`<TableCell heading />`) or dynamically as a string (`<TableCell variant='heading' />`).
*
* Use `numeric` prop to render numbers in a monospace font with letters of equal size.
*
* Example table with **TableCell**:
*
* ```tsx
* <div className='grid grid-cols-3'>
* <div className='col-span-3 grid grid-cols-subgrid'>
* <TableCell heading>Name</TableCell>
* <TableCell heading>Pill</TableCell>
* <TableCell heading>Amount</TableCell>
* </div>
* <div className='col-span-3 grid grid-cols-subgrid'>
* <TableCell cell loading>Hello</TableCell>
* <TableCell cell>
* <Pill>Pending</Pill>
* </TableCell>
* <TableCell cell numeric>
* 11.1111
* </TableCell>
* </div>
* </div>
* ```
*/
export const TableCell = ({
children,
loading,
numeric,
as: Container = 'div',
...props
}: TableCellProps) => {
const density = useDensity();
const type: TableCellVariant =
props.variant ??
(props.title && 'title') ??
(props.heading && 'heading') ??
(props.lastCell && 'lastCell') ??
(props.footer && 'footer') ??
(props.cell && 'cell') ??
'cell';

return (
<Container
className={cn(
'flex items-center gap-2',
classesByVariant[type],
classesByDensity[density],
type === 'heading' ? headingFont[density] : defaultFont[density],
numeric && 'font-mono tabular-nums',
)}
>
{loading ? (
<div className='h-1/2 w-full max-w-14'>
<Skeleton />
</div>
) : (
children
)}
</Container>
);
};
TableCell.displayName = 'TableCell';

0 comments on commit cab203e

Please sign in to comment.