@uniweb/kit 0.10.1 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/kit",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "Standard component library for Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -36,13 +36,14 @@
|
|
|
36
36
|
"node": ">=20.19"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@tailwindcss/typography": "^0.5.20",
|
|
39
40
|
"clsx": "^2.1.0",
|
|
40
41
|
"fuse.js": "^7.0.0",
|
|
41
42
|
"shiki": "^3.0.0",
|
|
42
43
|
"tailwind-merge": "^3.6.0",
|
|
44
|
+
"@uniweb/semantic-parser": "1.2.0",
|
|
43
45
|
"@uniweb/scene": "0.1.2",
|
|
44
|
-
"@uniweb/core": "0.8.0"
|
|
45
|
-
"@uniweb/semantic-parser": "1.2.0"
|
|
46
|
+
"@uniweb/core": "0.8.0"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
|
48
49
|
"react": "^19.0.0",
|
package/src/prose-tokens.css
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Tailwind Typography, wired to the theme.
|
|
3
3
|
*
|
|
4
|
-
* Usage —
|
|
4
|
+
* Usage — one line, in the foundation's stylesheet:
|
|
5
5
|
*
|
|
6
|
-
* @plugin "@tailwindcss/typography";
|
|
7
6
|
* @import "@uniweb/kit/prose-tokens.css";
|
|
8
7
|
*
|
|
8
|
+
* That is the whole setup: this file brings the plugin with it (below) and then
|
|
9
|
+
* overrides its greys. It used to take two lines, and the foundation had to
|
|
10
|
+
* install the plugin itself and remember that the order mattered.
|
|
11
|
+
*
|
|
9
12
|
* `prose` ships its own greys, which means long-form body copy is the one part
|
|
10
13
|
* of a foundation that ignores the site's theme.yml unless someone bridges it
|
|
11
14
|
* by hand. Everyone does bridge it by hand, identically, and the bridge is easy
|
|
@@ -29,6 +32,33 @@
|
|
|
29
32
|
* other one only supplies column width and padding.
|
|
30
33
|
*/
|
|
31
34
|
|
|
35
|
+
/*
|
|
36
|
+
* The plugin comes from kit, and the ORDER below is the reason.
|
|
37
|
+
*
|
|
38
|
+
* A `@plugin` inside a package's own CSS resolves from that package's
|
|
39
|
+
* dependencies, so a foundation importing this file needs nothing installed
|
|
40
|
+
* (verified 2026-07-30: a foundation with no typography dependency and no
|
|
41
|
+
* `@plugin` line of its own emitted `.prose :where(p){margin-top:1.25em}` in a
|
|
42
|
+
* production build).
|
|
43
|
+
*
|
|
44
|
+
* Why it moved here rather than staying the foundation's job. The overrides
|
|
45
|
+
* below have to come AFTER the plugin's own declarations or they lose on equal
|
|
46
|
+
* specificity — which made "install this, then import that, in this order" a
|
|
47
|
+
* three-part instruction a foundation could get subtly wrong, and the failure
|
|
48
|
+
* is silent: the variables are set, the rules they modify do not exist, and
|
|
49
|
+
* prose renders with no margins at all. Measured on a foundation missing only
|
|
50
|
+
* the plugin: `--tw-prose-body` present, every paragraph `margin-top: 0px`, no
|
|
51
|
+
* warning from anywhere. Owning both halves here makes the order unorderable.
|
|
52
|
+
*
|
|
53
|
+
* The precedent is `shiki` and `fuse.js` — kit takes DIRECT dependencies on
|
|
54
|
+
* what its own code needs at build time rather than asking a foundation to
|
|
55
|
+
* supply them (framework CLAUDE.md gotcha #10). This is that rule applied to
|
|
56
|
+
* the CSS layer. A foundation that never writes `prose` pays disk only:
|
|
57
|
+
* Tailwind emits utilities that are used, so an unused plugin generates
|
|
58
|
+
* nothing.
|
|
59
|
+
*/
|
|
60
|
+
@plugin "@tailwindcss/typography";
|
|
61
|
+
|
|
32
62
|
.prose {
|
|
33
63
|
--tw-prose-body: var(--body);
|
|
34
64
|
--tw-prose-headings: var(--heading);
|
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Table Renderer
|
|
2
|
+
* Table Renderer — the styled table, opt-in.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The engine emits a plain semantic `<table>` and leaves the look to the
|
|
5
|
+
* typography layer, because what a table LOOKS like is a foundation's call.
|
|
6
|
+
* This is the other option: visible structure — header band, row separators,
|
|
7
|
+
* zebra striping — that a foundation opts into.
|
|
8
|
+
*
|
|
9
|
+
* <Prose content={content} block={block} components={{ table: Table }} />
|
|
10
|
+
*
|
|
11
|
+
* ── What was wrong with it ──
|
|
12
|
+
*
|
|
13
|
+
* Until 2026-07-31 this read raw ProseMirror and got two things wrong, both
|
|
14
|
+
* silently. It tested `cell.type === 'tableHeader'`, a node type the reader
|
|
15
|
+
* never emits — cells are `tableCell` carrying `attrs.header` — so **no cell
|
|
16
|
+
* was ever a `<th>`** and every table rendered as an undifferentiated grid.
|
|
17
|
+
* And it read `cell.content[0].content[0].text`, the first text node of the
|
|
18
|
+
* first paragraph, so a cell was truncated to its first run with every mark
|
|
19
|
+
* discarded: `` `name` `` came out as bare `name`, and a cell holding a link
|
|
20
|
+
* lost it entirely.
|
|
21
|
+
*
|
|
22
|
+
* It now takes the sequence's table element, where a cell is a nested sequence
|
|
23
|
+
* — so a cell keeps its marks and may hold any block.
|
|
5
24
|
*
|
|
6
25
|
* @module @uniweb/kit/Section/renderers/Table
|
|
7
26
|
*/
|
|
@@ -9,43 +28,75 @@
|
|
|
9
28
|
import React from 'react'
|
|
10
29
|
import { cn } from '../../../utils/index.js'
|
|
11
30
|
import { SafeHtml } from '../../../components/SafeHtml/index.js'
|
|
31
|
+
import { SequenceElement } from '../../Render/index.jsx'
|
|
12
32
|
|
|
13
33
|
/**
|
|
14
|
-
*
|
|
34
|
+
* A cell's contents, with a lone paragraph unwrapped.
|
|
35
|
+
*
|
|
36
|
+
* `tableCell` is `paragraph+`, so a markdown cell is always exactly one
|
|
37
|
+
* paragraph — and emitting that `<p>` is what a typography layer then gives
|
|
38
|
+
* margins to, measured at 17.5px top and bottom, turning one-line rows into
|
|
39
|
+
* 75px. The engine makes the same allowance for the same reason.
|
|
40
|
+
*/
|
|
41
|
+
function cellContent(cell, block, components) {
|
|
42
|
+
const children = cell?.children || []
|
|
43
|
+
const [only] = children
|
|
44
|
+
|
|
45
|
+
if (children.length === 1 && only?.type === 'paragraph') {
|
|
46
|
+
return <SafeHtml value={only.text || ''} as="span" />
|
|
47
|
+
}
|
|
48
|
+
return children.map((el, i) => (
|
|
49
|
+
<SequenceElement key={i} element={el} block={block} components={components} />
|
|
50
|
+
))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Table - styled table renderer
|
|
15
55
|
*
|
|
16
56
|
* @param {Object} props
|
|
17
|
-
* @param {Array} props.
|
|
57
|
+
* @param {Array} [props.rows] - Rows from a sequence table element
|
|
58
|
+
* @param {Object} [props.element] - The table element itself, so this drops
|
|
59
|
+
* straight into `components={{ table: Table }}` with no adapter
|
|
60
|
+
* @param {Object} [props.block] - Block instance, for insets inside a cell
|
|
61
|
+
* @param {Object} [props.components] - Renderer overrides, for nested content
|
|
18
62
|
* @param {string} [props.className] - Additional CSS classes
|
|
19
63
|
*/
|
|
20
|
-
export function Table({
|
|
21
|
-
|
|
64
|
+
export function Table({ rows, element, block, components, className, ...props }) {
|
|
65
|
+
const data = rows ?? element?.rows
|
|
66
|
+
if (!Array.isArray(data) || data.length === 0) return null
|
|
67
|
+
|
|
68
|
+
const [first, ...rest] = data
|
|
69
|
+
const hasHeader = first.cells?.some((cell) => cell.header)
|
|
70
|
+
const bodyRows = hasHeader ? rest : data
|
|
71
|
+
|
|
72
|
+
const renderRow = (row, key, zebra) => (
|
|
73
|
+
<tr key={key} className={zebra ? 'bg-muted' : undefined}>
|
|
74
|
+
{row.cells?.map((cell, i) => {
|
|
75
|
+
const CellTag = cell.header ? 'th' : 'td'
|
|
76
|
+
return (
|
|
77
|
+
<CellTag
|
|
78
|
+
key={i}
|
|
79
|
+
style={cell.align ? { textAlign: cell.align } : undefined}
|
|
80
|
+
colSpan={cell.colspan > 1 ? cell.colspan : undefined}
|
|
81
|
+
rowSpan={cell.rowspan > 1 ? cell.rowspan : undefined}
|
|
82
|
+
className={cn(
|
|
83
|
+
'px-4 py-2 text-sm',
|
|
84
|
+
cell.header ? 'font-medium text-heading' : 'text-body'
|
|
85
|
+
)}
|
|
86
|
+
>
|
|
87
|
+
{cellContent(cell, block, components)}
|
|
88
|
+
</CellTag>
|
|
89
|
+
)
|
|
90
|
+
})}
|
|
91
|
+
</tr>
|
|
92
|
+
)
|
|
22
93
|
|
|
23
94
|
return (
|
|
24
95
|
<div className={cn('overflow-x-auto', className)} {...props}>
|
|
25
|
-
<table className="min-w-full
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{row.content?.map((cell, cellIndex) => {
|
|
30
|
-
const CellTag = cell.type === 'tableHeader' ? 'th' : 'td'
|
|
31
|
-
const cellContent = cell.content?.[0]?.content?.[0]?.text || ''
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<CellTag
|
|
35
|
-
key={cellIndex}
|
|
36
|
-
className={cn(
|
|
37
|
-
'px-4 py-2 text-sm',
|
|
38
|
-
cell.type === 'tableHeader'
|
|
39
|
-
? 'font-medium text-heading bg-muted'
|
|
40
|
-
: 'text-body'
|
|
41
|
-
)}
|
|
42
|
-
>
|
|
43
|
-
<SafeHtml value={cellContent} as="span" />
|
|
44
|
-
</CellTag>
|
|
45
|
-
)
|
|
46
|
-
})}
|
|
47
|
-
</tr>
|
|
48
|
-
))}
|
|
96
|
+
<table className="min-w-full border border-border rounded-lg">
|
|
97
|
+
{hasHeader && <thead className="bg-muted">{renderRow(first, 'h', false)}</thead>}
|
|
98
|
+
<tbody className="divide-y divide-border">
|
|
99
|
+
{bodyRows.map((row, i) => renderRow(row, i, i % 2 === 1))}
|
|
49
100
|
</tbody>
|
|
50
101
|
</table>
|
|
51
102
|
</div>
|