@uniweb/kit 0.10.2 → 0.10.4
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,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/kit",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "Standard component library for Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.js",
|
|
8
8
|
"./xref": "./src/xref/index.js",
|
|
9
9
|
"./theme-tokens.css": "./src/theme-tokens.css",
|
|
10
|
-
"./prose-tokens.css": "./src/prose-tokens.css"
|
|
10
|
+
"./prose-tokens.css": "./src/prose-tokens.css",
|
|
11
|
+
"./callout-tokens.css": "./src/callout-tokens.css"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"src",
|
|
@@ -41,9 +42,9 @@
|
|
|
41
42
|
"fuse.js": "^7.0.0",
|
|
42
43
|
"shiki": "^3.0.0",
|
|
43
44
|
"tailwind-merge": "^3.6.0",
|
|
45
|
+
"@uniweb/semantic-parser": "1.2.0",
|
|
44
46
|
"@uniweb/core": "0.8.0",
|
|
45
|
-
"@uniweb/scene": "0.1.2"
|
|
46
|
-
"@uniweb/semantic-parser": "1.2.0"
|
|
47
|
+
"@uniweb/scene": "0.1.2"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
50
|
"react": "^19.0.0",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Callout styling for the five standard concept tags, wired to the theme.
|
|
3
|
+
*
|
|
4
|
+
* Usage — one line, in the foundation's stylesheet, alongside the others:
|
|
5
|
+
*
|
|
6
|
+
* @import "@uniweb/kit/callout-tokens.css";
|
|
7
|
+
*
|
|
8
|
+
* OPT-IN, and that is the point. kit's render engine emits a concept block as
|
|
9
|
+
* a plain bordered box carrying `data-concept="<tag>"` and decides nothing
|
|
10
|
+
* about how it looks, because what a callout looks like is a foundation's
|
|
11
|
+
* design. This file is the answer for the foundations that do not want to make
|
|
12
|
+
* that decision — the common look, themed, in one import.
|
|
13
|
+
*
|
|
14
|
+
* ── Why five names may live here when they may not live in a component ──
|
|
15
|
+
*
|
|
16
|
+
* kit answers for no NAME: it must never render its own accordion for `faq` or
|
|
17
|
+
* its own Alert for a `@Alert` reference, because that shadows a foundation
|
|
18
|
+
* shipping its own and cannot be overridden without forking. Two things make a
|
|
19
|
+
* stylesheet different:
|
|
20
|
+
*
|
|
21
|
+
* 1. CSS loses to specificity. A foundation that disagrees writes one rule
|
|
22
|
+
* and wins, keeping kit's walk and its own look.
|
|
23
|
+
* 2. These five are not names we invented. `NOTE`, `TIP`, `IMPORTANT`,
|
|
24
|
+
* `WARNING` and `CAUTION` are GitHub's alert kinds, which content-reader
|
|
25
|
+
* accepts as a second spelling of a concept block. Implementing a
|
|
26
|
+
* published convention is the same category as shipping icon-library
|
|
27
|
+
* prefixes; inventing a list would be the concept registry the whole
|
|
28
|
+
* design forbids.
|
|
29
|
+
*
|
|
30
|
+
* ── The colours are the site's ──
|
|
31
|
+
*
|
|
32
|
+
* Every value below is a semantic token the site sets in `theme.yml` —
|
|
33
|
+
* `--info`, `--warning`, `--error`, `--success` and their `-subtle` surfaces.
|
|
34
|
+
* kit supplies the MAPPING (which tag means which token); the site supplies
|
|
35
|
+
* what those tokens are, and they flip with the visitor's scheme on their own.
|
|
36
|
+
* No fixed palette appears here, for the reason `tests/no-fixed-palettes.test.js`
|
|
37
|
+
* exists.
|
|
38
|
+
*
|
|
39
|
+
* ── Tags with no rule ──
|
|
40
|
+
*
|
|
41
|
+
* A concept block whose tag is not one of the five keeps the engine's plain
|
|
42
|
+
* bordered box. That is deliberate: an unrecognized concept should look
|
|
43
|
+
* unremarkable rather than borrow a severity it was never given.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
[data-concept] {
|
|
47
|
+
border-radius: var(--radius, 0.5rem);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[data-concept="note"],
|
|
51
|
+
[data-concept="info"] {
|
|
52
|
+
border-color: var(--info);
|
|
53
|
+
background-color: var(--info-subtle);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[data-concept="tip"],
|
|
57
|
+
[data-concept="success"] {
|
|
58
|
+
border-color: var(--success);
|
|
59
|
+
background-color: var(--success-subtle);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
[data-concept="important"] {
|
|
63
|
+
border-color: var(--primary);
|
|
64
|
+
background-color: var(--muted);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
[data-concept="warning"] {
|
|
68
|
+
border-color: var(--warning);
|
|
69
|
+
background-color: var(--warning-subtle);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
[data-concept="caution"],
|
|
73
|
+
[data-concept="error"] {
|
|
74
|
+
border-color: var(--error);
|
|
75
|
+
background-color: var(--error-subtle);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* A callout inside a `prose` container should not inherit the container's
|
|
80
|
+
* first-child/last-child margin collapsing on its own children, or the box
|
|
81
|
+
* gains dead space at top and bottom that its neighbours do not have.
|
|
82
|
+
*/
|
|
83
|
+
[data-concept] > :first-child {
|
|
84
|
+
margin-top: 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
[data-concept] > :last-child {
|
|
88
|
+
margin-bottom: 0;
|
|
89
|
+
}
|
|
@@ -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>
|