@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.2",
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
- * Renders HTML tables from structured content.
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
- * Table - Table renderer
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.content - Table content as rows/cells array
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({ content, className, ...props }) {
21
- if (!content || !Array.isArray(content)) return null
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 divide-y divide-border border border-border rounded-lg">
26
- <tbody className="divide-y divide-border bg-section">
27
- {content.map((row, rowIndex) => (
28
- <tr key={rowIndex} className={rowIndex % 2 === 0 ? 'bg-section' : 'bg-muted'}>
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>