Skip to content

Commit

Permalink
Merge pull request #328 from ably/web-3581-table-migration
Browse files Browse the repository at this point in the history
[WEB-3581] Table migration
  • Loading branch information
jamiehenson authored Mar 26, 2024
2 parents 7d35cb3 + 787e444 commit 6e22efb
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 0 deletions.
30 changes: 30 additions & 0 deletions src/core/Table/Table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { PropsWithChildren, ReactElement, cloneElement } from "react";

type TableProps = {
id?: string;
};

export const Table = ({ id, children }: PropsWithChildren<TableProps>) => (
<table id={id} className="ui-standard-container mb-4 sm:table-fixed">
{children}
</table>
);

export const TableBody = ({ children }: PropsWithChildren) => (
<tbody>{children}</tbody>
);

export const TableHeader = ({ children }: PropsWithChildren) => (
<thead className="sticky bg-white z-10 top-0" key="sticky-block">
{cloneElement(children as ReactElement, { isHeader: true })}
</thead>
);

export const TableRowHeader = ({ children }: PropsWithChildren) => (
<tr
className="-ml-24 mt-8 sm:ml-0 sm:mt-0 ui-text-overline1 !text-cool-black bg-light-grey sm:sticky z-10"
style={{ top: "4rem" }}
>
{cloneElement(children as ReactElement, { isRowHeader: true })}
</tr>
);
84 changes: 84 additions & 0 deletions src/core/Table/TableCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { PropsWithChildren } from "react";
import Icon from "../Icon/component.tsx";

type TableCellProps = {
isRowHeader?: boolean;
} & React.TdHTMLAttributes<HTMLTableCellElement>;

const Supported = () => (
<Icon
name="icon-gui-check-circled-fill"
size="1.5rem"
color="text-gui-success"
additionalCSS="flex-grow-0 flex-shrink-0"
data-testid="supported-icon"
/>
);

const Unsupported = () => (
<Icon
name="icon-gui-cross-circled-fill"
size="1.5rem"
color="text-gui-error"
additionalCSS="flex-grow-0 flex-shrink-0"
data-testid="unsupported-icon"
/>
);

const LabelCell = ({
children,
...rest
}: PropsWithChildren<React.TdHTMLAttributes<HTMLTableCellElement>>) => {
const classes = `
ui-text-p1 !font-bold pt-24 pb-8 border-light-grey sm:p-24 sm:relative sm:top-2 flex sm:table-cell ${
rest.className ?? ""
}
`;

return (
<td {...rest} className={classes}>
{children}
</td>
);
};

const TableCell = ({
children,
isRowHeader,
...rest
}: PropsWithChildren<TableCellProps>) => (
<td
{...rest}
className={`
border-light-grey sm:p-24 leading-none flex sm:table-cell
${
isRowHeader
? "rounded-l-none rounded-r sm:rounded-lg py-20 px-24"
: "py-6"
}
${rest.className ?? ""}
`}
>
{children}
</td>
);

const HeaderCell = ({
children,
...rest
}: PropsWithChildren<React.TdHTMLAttributes<HTMLTableCellElement>>) => (
<td {...rest} className="ui-text-h3 px-24 py-24 hidden sm:table-cell">
{children}
</td>
);

const CtaCell = ({
children,
...rest
}: PropsWithChildren<React.TdHTMLAttributes<HTMLTableCellElement>>) => (
<td {...rest} className="pt-24 hidden sm:table-cell">
{children}
</td>
);

export { TableCell, LabelCell, HeaderCell, CtaCell, Supported, Unsupported };
25 changes: 25 additions & 0 deletions src/core/Table/TableRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { PropsWithChildren } from "react";

const CtaRow = ({ children }: PropsWithChildren) => (
<tr>
<td className="hidden sm:block"></td>
{children}
</tr>
);

type RowProps = {
isHeader?: boolean;
} & React.HTMLAttributes<HTMLTableRowElement>;

const TableRow = ({
children,
isHeader,
...rest
}: PropsWithChildren<RowProps>) => (
<tr {...rest}>
{isHeader && <td className="bg-white" />}
{children}
</tr>
);

export { TableRow, CtaRow };
24 changes: 24 additions & 0 deletions src/core/Table/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Table, TableRowHeader, TableHeader, TableBody } from "./Table";
import { TableRow } from "./TableRow";
import {
TableCell,
LabelCell,
HeaderCell,
CtaCell,
Supported,
Unsupported,
} from "./TableCell";

export default {
Root: Table,
Row: TableRow,
Cell: TableCell,
LabelCell,
HeaderCell,
CtaCell,
RowHeader: TableRowHeader,
Body: TableBody,
Header: TableHeader,
Supported,
Unsupported,
};
12 changes: 12 additions & 0 deletions src/core/Table/stories/Table.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PricingPageTable } from "./data";

export default {
title: "Components/Table",
component: PricingPageTable,
tags: ["autodocs"],
parameters: {
layout: "fullscreen",
},
};

export const PricingPage = {};
113 changes: 113 additions & 0 deletions src/core/Table/stories/data.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { Fragment } from "react";

import Tooltip from "../../Tooltip/component";
import { Supported, Unsupported } from "../TableCell";
import Table from "..";

const testRow = (index) => ({
label: `Label ${index + 1}`,
cells: [
{ label: "text", content: "Cell content", column: "Free" },
{
label: "yes",
content: (
<div className="flex items-center sm:flex-col sm:items-start">
<span className="sm:order-1 px-6 sm:py-6 sm:px-0">Supported</span>
<span className="sm:order-0">
<Supported />
</span>
</div>
),
column: "PAYG",
},
{
label: "no",
content: (
<div className="flex items-center sm:flex-col sm:items-start">
<span className="sm:order-1 px-6 sm:py-6 sm:px-0">Unsupported</span>
<span className="sm:order-0">
<Unsupported />
</span>
</div>
),
column: "Custom",
},
],
});

const sections = ["Features", "Support", "Technical Support"].map((label) => ({
label,
rows: [...Array(5)].map((_, i) => testRow(i)),
}));

export const PricingPageTable = () => {
return (
<div className="ui-standard-container">
<h2 className="ui-text-h2 text-center m-32">Pricing Page Table</h2>
<p className="text-center m-32">Example content</p>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Cell>
<span className="ui-text-h3 hidden sm:block">Free</span>
</Table.Cell>
<Table.Cell>
<span className="ui-text-h3 hidden sm:block">PAYG</span>
</Table.Cell>
<Table.Cell>
<span className="ui-text-h3 hidden sm:block">Custom</span>
</Table.Cell>
</Table.Row>
</Table.Header>
<Table.Body>
{sections.map((section) => (
<Fragment key={section.label}>
<Table.RowHeader>
<Table.Cell colSpan={4}>{section.label}</Table.Cell>
</Table.RowHeader>
{section.rows.map((row) => (
<Table.Row key={row.label}>
<Table.LabelCell
key={row.label}
className="border-t border-light-grey"
>
<a className="ui-link" href="#">
{row.label}
</a>
<Tooltip>Example tooltip</Tooltip>
</Table.LabelCell>
{row.cells.map((cell) => (
<Table.Cell key={cell.label} className="last:mb-16 sm:mb-0">
<div className="flex-1 sm:hidden !text-dark-grey ui-text-overline2">
{cell.column}
</div>
{cell.content}
</Table.Cell>
))}
</Table.Row>
))}
</Fragment>
))}
<Table.Row>
<Table.Cell></Table.Cell>
<Table.CtaCell>
<a href="#" className="ui-btn-secondary">
Get started
</a>
</Table.CtaCell>
<Table.CtaCell>
<a href="#" className="ui-btn-secondary">
Get started
</a>
</Table.CtaCell>
<Table.CtaCell>
<a href="#" className="ui-btn">
Contact sales
</a>
</Table.CtaCell>
</Table.Row>
</Table.Body>
</Table.Root>
</div>
);
};
27 changes: 27 additions & 0 deletions src/core/Tooltip/Tooltip.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import Tooltip from "./component";

export default {
title: "Components/Tooltip",
component: Tooltip,
tags: ["autodocs"],
args: {
children: "Example content",
},
};

export const Central = {
render: (args) => (
<div className="w-256 h-256 flex items-center justify-center m-24 border">
<Tooltip>{args.children}</Tooltip>
</div>
),
};

export const LeftBound = {
render: (args) => (
<div className="w-256 h-256 flex items-center m-24 border">
<Tooltip>{args.children}</Tooltip>
</div>
),
};
60 changes: 60 additions & 0 deletions src/core/Tooltip/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { PropsWithChildren, useEffect, useRef, useState } from "react";
import Icon from "../Icon/component.tsx";

const Tooltip = ({ children }: PropsWithChildren) => {
const [open, setOpen] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
const offset = 8;
const reference = useRef<HTMLButtonElement>(null);
const floating = useRef<HTMLDivElement>(null);

useEffect(() => {
if (open) {
const floatingRect = floating.current?.getBoundingClientRect();
const referenceRect = reference.current?.getBoundingClientRect();

if (floatingRect && referenceRect) {
setPosition({
x:
Math.min(floatingRect.width / 2, floatingRect.left) -
referenceRect.width / 2,
y: Math.min(floatingRect.height, floatingRect.top) + offset,
});
}
} else {
setPosition({ x: 0, y: 0 });
}
}, [open]);

return (
<div className="relative inline-block align-top h-16">
<button
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
className="ml-8 p-0 relative top-1 focus:outline-none"
type="button"
ref={reference}
aria-describedby="tooltip"
>
<Icon name="icon-gui-info" size="1rem" />
</button>

{open ? (
<div
className="bg-light-grey p-12 rounded pointer-events-none absolute z-20"
role="tooltip"
ref={floating}
style={{
top: -position.y,
left: -position.x,
boxShadow: "4px 4px 15px rgba(0, 0, 0, 0.2)",
}}
>
<div className="w-256">{children}</div>
</div>
) : null}
</div>
);
};

export default Tooltip;

0 comments on commit 6e22efb

Please sign in to comment.