Skip to content

Commit b02c094

Browse files
amixclaude
andauthored
feat(prose): add table styling support (#1063)
* feat(prose): add table styling support Tables rendered from Markdown (or raw HTML) inside <Prose> were unstyled. Add bordered table styles with a public --reactist-prose-table-border custom property, horizontal scrolling for wide tables, and start-aligned headers that still respect explicit column alignment from Markdown renderers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(prose): add a width-constrained table overflow story The playground example only shows a narrow table, so Chromatic never exercised the horizontal-scroll layout for wide tables. A dedicated story renders a table with unbreakable links inside a 400px container, guaranteeing the overflow path is snapshotted at any viewport size. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(prose): add table background custom properties Allow consumers to configure the table header row background and odd/even body row backgrounds via --reactist-prose-table-header-fill, --reactist-prose-table-row-odd-fill, and --reactist-prose-table-row-even-fill. All default to transparent, so tables render unchanged unless customized. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 4f22088 commit b02c094

3 files changed

Lines changed: 151 additions & 1 deletion

File tree

src/prose/prose-example.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ Now a nested list:
7474
7575
Do not bump wooden spoon or it will fall.
7676
77+
And here's a table, with column alignment:
78+
79+
| Item | Quantity | Price |
80+
| ------- | :------: | ----: |
81+
| Carrots | 3 | $1.20 |
82+
| Celery | 1 | $0.95 |
83+
| Lentils | 500g | $2.40 |
84+
7785
---
7886
7987
A horizontal rule follows.
@@ -82,3 +90,27 @@ A horizontal rule follows.
8290
8391
The End
8492
`)
93+
94+
export const proseTableOverflowExample = marked(`
95+
Tables whose content cannot wrap any further (e.g. long unbreakable links) scroll horizontally
96+
instead of stretching the surrounding content:
97+
98+
| Resource | Reference |
99+
| -------- | --------- |
100+
| Prose styles | https://github.com/Doist/reactist/blob/main/src/prose/prose.module.css |
101+
| Prose stories | https://github.com/Doist/reactist/blob/main/src/prose/prose.stories.tsx |
102+
`)
103+
104+
export const proseTableColorsExample = marked(`
105+
Consumers can give the header row and odd/even body rows their own background colors via the
106+
\`--reactist-prose-table-header-fill\`, \`--reactist-prose-table-row-odd-fill\`, and
107+
\`--reactist-prose-table-row-even-fill\` custom properties (all default to \`transparent\`):
108+
109+
| Item | Quantity | Price |
110+
| ------- | :------: | ----: |
111+
| Carrots | 3 | $1.20 |
112+
| Celery | 1 | $0.95 |
113+
| Lentils | 500g | $2.40 |
114+
| Onions | 2 | $0.80 |
115+
| Stock | 1L | $3.10 |
116+
`)

src/prose/prose.module.css

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@
1010
--reactist-prose-link-hover-underline: #006f85;
1111

1212
--reactist-prose-horizontal-rule-color: var(--reactist-divider-primary);
13+
14+
--reactist-prose-table-border: var(--reactist-divider-primary);
15+
16+
/* `transparent` (the initial value of `background-color`) rather than `initial`, which is
17+
special-cased in custom property values to mean "guaranteed invalid", or `inherit`, which
18+
backgrounds achieve through transparency rather than inheritance. */
19+
--reactist-prose-table-header-fill: transparent;
20+
--reactist-prose-table-row-odd-fill: transparent;
21+
--reactist-prose-table-row-even-fill: transparent;
1322
}
1423

1524
/* Internals for spacing. These are not considered public API. */
@@ -212,6 +221,66 @@
212221
overflow: auto;
213222
}
214223

224+
/* tables */
225+
226+
.prose table {
227+
/* Let wide tables scroll horizontally instead of stretching the surrounding content. */
228+
display: block;
229+
width: max-content;
230+
max-width: 100%;
231+
overflow: auto;
232+
margin-top: var(--reactist-prose-space-2);
233+
border-collapse: collapse;
234+
235+
/* `.prose`'s `word-break: break-word` acts like `overflow-wrap: anywhere`, shrinking each
236+
word's min-content width to a single character. That lets wide tables squeeze into
237+
`max-width: 100%` by breaking words mid-word instead of overflowing into the scroll above. */
238+
word-break: normal;
239+
}
240+
241+
.prose th,
242+
.prose td {
243+
border: 1px solid var(--reactist-prose-table-border);
244+
padding: calc(var(--reactist-prose-space-1) / 2) var(--reactist-prose-space-1);
245+
}
246+
247+
/* The header fill goes on the row rather than `th` so that row headers in table bodies
248+
(`<tbody><tr><th scope="row">…`) stripe with their row instead of picking up the header fill. */
249+
.prose thead tr {
250+
background-color: var(--reactist-prose-table-header-fill);
251+
}
252+
253+
.prose tbody tr:nth-child(odd) {
254+
background-color: var(--reactist-prose-table-row-odd-fill);
255+
}
256+
257+
.prose tbody tr:nth-child(even) {
258+
background-color: var(--reactist-prose-table-row-even-fill);
259+
}
260+
261+
.prose th {
262+
font-weight: 600;
263+
}
264+
265+
/* Browsers center `th` by default; align with `td` instead. Markdown renderers express explicit
266+
column alignment either as an inline `text-align` style (which wins over this rule) or as the
267+
`align` attribute (which would lose to it, hence the `:not([align])` guard). */
268+
.prose th:not([align]) {
269+
text-align: start;
270+
}
271+
272+
/* Markdown table cells hold inline content only, but raw HTML tables can nest block elements
273+
(e.g. `<td><p>…</p></td>`), whose margins would add awkward vertical spacing. */
274+
.prose th > :first-child,
275+
.prose td > :first-child {
276+
margin-top: 0;
277+
}
278+
279+
.prose th > :last-child,
280+
.prose td > :last-child {
281+
margin-bottom: 0;
282+
}
283+
215284
/* headings in relation to other tags */
216285

217286
.prose h1 + * {

src/prose/prose.stories.tsx

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as React from 'react'
33
import { Box } from '../box'
44

55
import { Prose } from './prose'
6-
import { proseExample } from './prose-example'
6+
import { proseExample, proseTableColorsExample, proseTableOverflowExample } from './prose-example'
77

88
import type { ProseProps } from './prose'
99

@@ -68,3 +68,52 @@ ProsePlaygroundStory.argTypes = {
6868
dangerouslySetInnerHTML: { control: false },
6969
exceptionallySetClassName: { control: false },
7070
}
71+
72+
//
73+
// Table overflow
74+
//
75+
76+
export function ProseTableOverflowStory() {
77+
// The narrow container guarantees the table overflows into a horizontal scroll at any
78+
// viewport size, so the snapshot exercises the scrolling layout deterministically.
79+
return (
80+
<Box padding="xlarge" style={{ maxWidth: 400 }}>
81+
<Prose
82+
darkModeTypography={false}
83+
dangerouslySetInnerHTML={{ __html: proseTableOverflowExample }}
84+
/>
85+
</Box>
86+
)
87+
}
88+
89+
ProseTableOverflowStory.storyName = 'Table overflow'
90+
ProseTableOverflowStory.parameters = {
91+
chromatic: { disableSnapshot: false },
92+
}
93+
94+
//
95+
// Table colors
96+
//
97+
98+
export function ProseTableColorsStory() {
99+
return (
100+
<Box
101+
padding="xlarge"
102+
style={{
103+
// @ts-expect-error
104+
'--reactist-prose-table-header-fill': 'rgb(233, 239, 242)',
105+
'--reactist-prose-table-row-even-fill': 'rgb(247, 250, 251)',
106+
}}
107+
>
108+
<Prose
109+
darkModeTypography={false}
110+
dangerouslySetInnerHTML={{ __html: proseTableColorsExample }}
111+
/>
112+
</Box>
113+
)
114+
}
115+
116+
ProseTableColorsStory.storyName = 'Table colors'
117+
ProseTableColorsStory.parameters = {
118+
chromatic: { disableSnapshot: false },
119+
}

0 commit comments

Comments
 (0)