Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dark mode for table block #12879

Merged
merged 4 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 215 additions & 0 deletions dotcom-rendering/src/components/TableBlockComponent.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import type { Meta } from '@storybook/react';
import { TableBlockComponent } from './TableBlockComponent';

const tableHtml = `<table data-embed-type="table" class="table table--football table--striped">
<thead>
<tr>
<th class="football-stat--postition table-column--sub"><abbr title="Position">Pos</abbr></th>
<th class="football-stat--team table-column--main">Team</th>
<th class="football-stat--played"><abbr title="Played">P</abbr></th>
<th class="football-stat--difference"><abbr title="Goal difference">GD</abbr></th>
<th class="football-stat--points"><abbr title="Points">Pts</abbr></th>
</tr>
</thead>
<tbody>

<tr class="
">
<td class="football-stat--postition table-column--sub">1</td>
<td class="football-stat--team table-column--main">Liverpool</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">15</td>
<td class="football-stat--points">28</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">2</td>
<td class="football-stat--team table-column--main">Man City</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">9</td>
<td class="football-stat--points">23</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">3</td>
<td class="football-stat--team table-column--main">Nottm Forest</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">5</td>
<td class="football-stat--points">19</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">4</td>
<td class="football-stat--team table-column--main">Brighton</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">4</td>
<td class="football-stat--points">19</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">5</td>
<td class="football-stat--team table-column--main">Chelsea</td>
<td class="football-stat--played">10</td>
<td class="football-stat--difference">8</td>
<td class="football-stat--points">18</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">6</td>
<td class="football-stat--team table-column--main">Arsenal</td>
<td class="football-stat--played">10</td>
<td class="football-stat--difference">6</td>
<td class="football-stat--points">18</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">7</td>
<td class="football-stat--team table-column--main">Fulham</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">3</td>
<td class="football-stat--points">18</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">8</td>
<td class="football-stat--team table-column--main">Newcastle</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">2</td>
<td class="football-stat--points">18</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">9</td>
<td class="football-stat--team table-column--main">Aston Villa</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">0</td>
<td class="football-stat--points">18</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">10</td>
<td class="football-stat--team table-column--main">Tottenham Hotspur</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">10</td>
<td class="football-stat--points">16</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">11</td>
<td class="football-stat--team table-column--main">Brentford</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">0</td>
<td class="football-stat--points">16</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">12</td>
<td class="football-stat--team table-column--main">AFC Bournemouth</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">0</td>
<td class="football-stat--points">15</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">13</td>
<td class="football-stat--team table-column--main">Man Utd</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">0</td>
<td class="football-stat--points">15</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">14</td>
<td class="football-stat--team table-column--main">West Ham</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">-6</td>
<td class="football-stat--points">12</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">15</td>
<td class="football-stat--team table-column--main">Leicester</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">-7</td>
<td class="football-stat--points">10</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">16</td>
<td class="football-stat--team table-column--main">Everton</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">-7</td>
<td class="football-stat--points">10</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">17</td>
<td class="football-stat--team table-column--main">Ipswich</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">-10</td>
<td class="football-stat--points">8</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">18</td>
<td class="football-stat--team table-column--main">Crystal Palace</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">-7</td>
<td class="football-stat--points">7</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">19</td>
<td class="football-stat--team table-column--main">Wolverhampton</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">-11</td>
<td class="football-stat--points">6</td>
</tr>

<tr class="
">
<td class="football-stat--postition table-column--sub">20</td>
<td class="football-stat--team table-column--main">Southampton</td>
<td class="football-stat--played">11</td>
<td class="football-stat--difference">-14</td>
<td class="football-stat--points">4</td>
</tr>

</tbody>
</table>`;

const meta = {
title: 'Components/TableBlockComponent',
component: TableBlockComponent,
render: (args) => <TableBlockComponent {...args} />,
args: {
element: {
isMandatory: true,
elementId: 'table',
_type: 'model.dotcomrendering.pageElements.TableBlockElement',
html: tableHtml,
},
},
} satisfies Meta<typeof TableBlockComponent>;

export default meta;

export const Default = {};
125 changes: 92 additions & 33 deletions dotcom-rendering/src/components/TableBlockComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,113 @@
import { css } from '@emotion/react';
import { palette, text, textSans12 } from '@guardian/source/foundations';
import { unescapeData } from '../lib/escapeData';
import { textSans12 } from '@guardian/source/foundations';
import { isElement, parseHtml } from '../lib/domUtils';
import { palette } from '../palette';
import { logger } from '../server/lib/logging';
import type { TableBlockElement } from '../types/content';

const tableStyles = css`
width: 100%;
background: ${palette('--table-block-background')};
border-top: 0.0625rem solid ${palette('--table-block-border-top')};
color: ${palette('--table-block-text')};
border-collapse: inherit;
`;

const headStyles = css`
tr {
font-weight: 800;
text-align: left;
}
`;

const rowStyles = css`
${textSans12};
:nth-child(odd) > td {
background-color: ${palette('--table-block-stripe')};
}
`;

const cellPadding = css`
padding: 0.5rem;
`;

const tableEmbed = css`
.table--football {
width: 100%;
background: ${palette.neutral[97]};
border-top: 0.0625rem solid ${palette.focus[400]};
color: ${palette.neutral[7]};
border-collapse: inherit;
tr:nth-child(odd) > td {
background-color: ${palette.neutral[93]};
tr > th:first-child,
td:first-child {
color: ${palette('--table-block-text-first-column')};
}
`;

type Props = {
element: TableBlockElement;
};

const buildElementTree = (node: Node) => {
const children = Array.from(node.childNodes).map(buildElementTree);
switch (node.nodeName) {
case 'TABLE': {
return (
<table className="table--football" css={tableStyles}>
{children}
</table>
);
}
th {
padding: 0.5rem;
case 'THEAD': {
return <thead css={headStyles}>{children}</thead>;
}
td {
padding: 0.5rem;
case 'TBODY': {
return <tbody>{children}</tbody>;
}
tr {
${textSans12};
case 'ABBR': {
return (
<abbr title={(node as HTMLElement).getAttribute('title') ?? ''}>
{children}
</abbr>
);
}
thead {
tr {
font-weight: 800;
text-align: left;
}
case 'TR': {
return <tr css={rowStyles}>{children}</tr>;
}
tr > th:first-child,
td:first-child {
color: ${text.supporting};
case 'TH': {
return <th css={cellPadding}>{children}</th>;
}
.table-column--main {
width: 100%;
case 'TD': {
const isMainColumn = (node as HTMLElement).className.includes(
'table-column--main',
);
return (
<td
style={isMainColumn ? { width: '100%' } : undefined}
css={cellPadding}
>
{children}
</td>
);
}
case '#text': {
return node.textContent;
}
default:
logger.warn('TableBlockComponent: Unknown element received', {
isDev: process.env.NODE_ENV !== 'production',
element: {
name: node.nodeName,
html: isElement(node) ? node.outerHTML : undefined,
},
});
return null;
}
margin-bottom: 16px;
`;

type Props = {
element: TableBlockElement;
};

export const TableBlockComponent = ({ element }: Props) => {
const fragment = parseHtml(element.html);
return (
<div
css={tableEmbed}
style={{ marginBottom: '16px' }}
data-testid="football-table-embed"
dangerouslySetInnerHTML={{ __html: unescapeData(element.html) }}
/>
>
{Array.from(fragment.childNodes).map(buildElementTree)}
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice avoidance of dangerously setting inner html 👏 Much safer

In future we should look to send only the data from frontend, rather than all the HTML. This is a good refactor ahead of that.

);
};
Loading
Loading