@tristan2828/ui-foundation 3.20.1 → 3.22.0
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/conventions/docs/add-an-entity.md +9 -4
- package/conventions/docs/cell-patterns.md +13 -4
- package/conventions/docs/design-language.md +22 -0
- package/dist/components/app/data-table.js +21 -13
- package/dist/components/app/markdown.js +38 -20
- package/dist/components/app/milkdown-editor.js +189 -31
- package/dist/components/app/prose-look.d.ts +32 -0
- package/dist/components/app/prose-look.js +48 -0
- package/dist/components/app/rich-text-format-items.d.ts +13 -1
- package/dist/components/app/rich-text-format-items.js +43 -10
- package/dist/components/app/rich-text-formats.d.ts +15 -2
- package/dist/components/app/rich-text-formats.js +99 -10
- package/dist/components/app/rich-text-node-views.d.ts +31 -0
- package/dist/components/app/rich-text-node-views.js +79 -0
- package/dist/components/app/rich-text-shortcuts.d.ts +12 -0
- package/dist/components/app/rich-text-shortcuts.js +52 -0
- package/dist/components/app/rich-text-slash-menu.js +4 -1
- package/dist/components/app/rich-text-slash.js +26 -10
- package/dist/components/app/rich-text-table-menu.d.ts +20 -0
- package/dist/components/app/rich-text-table-menu.js +53 -0
- package/dist/components/app/rich-text-table.d.ts +19 -0
- package/dist/components/app/rich-text-table.js +162 -0
- package/dist/components/app/rich-text-toolbar.d.ts +11 -2
- package/dist/components/app/rich-text-toolbar.js +14 -23
- package/dist/components/app/rich-text-widgets.d.ts +19 -0
- package/dist/components/app/rich-text-widgets.js +34 -0
- package/dist/components/app/secondary-filters.d.ts +18 -0
- package/dist/components/app/secondary-filters.js +32 -0
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/combobox.js +7 -1
- package/dist/components/ui/dropdown-menu.d.ts +29 -0
- package/dist/components/ui/dropdown-menu.js +51 -0
- package/dist/hooks/use-table-url-state.d.ts +1 -1
- package/dist/hooks/use-table-url-state.js +6 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/markdown-callout.d.ts +38 -0
- package/dist/lib/markdown-callout.js +103 -0
- package/package.json +1 -1
- package/styles/index.css +17 -0
- package/styles/theme.css +30 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const CALLOUT_KINDS: readonly ["note", "tip", "important", "warning", "caution"];
|
|
2
|
+
export type CalloutKind = (typeof CALLOUT_KINDS)[number];
|
|
3
|
+
type MdNode = {
|
|
4
|
+
type: string;
|
|
5
|
+
children?: MdNode[];
|
|
6
|
+
value?: string;
|
|
7
|
+
data?: Record<string, unknown>;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* For react-markdown: a callout quote becomes a `<div data-callout="note">`
|
|
12
|
+
* (its marker gone), which <Markdown>'s `div` component draws as one.
|
|
13
|
+
*/
|
|
14
|
+
export declare function remarkCallouts(): (tree: unknown) => void;
|
|
15
|
+
/**
|
|
16
|
+
* For the rich-text editor: a callout quote becomes a `callout` node
|
|
17
|
+
* (`kind`, `marker`), which its schema reads and `calloutHandler` writes.
|
|
18
|
+
*/
|
|
19
|
+
export declare function remarkCalloutNodes(): (tree: unknown) => void;
|
|
20
|
+
/** The marker to write for a callout: as it was written, unless its kind changed. */
|
|
21
|
+
export declare function calloutMarker(kind: CalloutKind, written: string | null | undefined): string;
|
|
22
|
+
type ToMarkdownState = {
|
|
23
|
+
enter: (name: string) => () => void;
|
|
24
|
+
createTracker: (info: unknown) => {
|
|
25
|
+
move: (value: string) => void;
|
|
26
|
+
shift: (value: number) => void;
|
|
27
|
+
current: () => unknown;
|
|
28
|
+
};
|
|
29
|
+
containerFlow: (node: MdNode, info: unknown) => string;
|
|
30
|
+
indentLines: (value: string, map: (line: string, index: number, blank: boolean) => string) => string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Writes a `callout` node the way remark writes a quote, with the marker
|
|
34
|
+
* as its first line. A first block that isn't a paragraph gets a blank
|
|
35
|
+
* line after the marker, so it can't run into it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function calloutHandler(node: MdNode, _parent: unknown, state: ToMarkdownState, info: unknown): string;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Callouts (Notion's coloured boxes) in Markdown: GitHub's alert syntax, a
|
|
2
|
+
// quote whose first line is its kind in brackets.
|
|
3
|
+
//
|
|
4
|
+
// > [!NOTE]
|
|
5
|
+
// > Measure twice, cut once.
|
|
6
|
+
//
|
|
7
|
+
// It's plain Markdown, so a note stays readable anywhere (GitHub and
|
|
8
|
+
// Obsidian render it as a callout too, anything else as a quote), and
|
|
9
|
+
// remark parses it as a blockquote whose first text is the marker. These
|
|
10
|
+
// plugins read that marker: `remarkCallouts` for <Markdown> (an element
|
|
11
|
+
// react-markdown hands to its `div` component) and `remarkCalloutNodes`
|
|
12
|
+
// for the rich-text editor (a `callout` node it has a schema for, written
|
|
13
|
+
// back with `calloutHandler`). The merge (markdown-merge.ts) needs
|
|
14
|
+
// neither: to it a callout is the quote it's written as.
|
|
15
|
+
//
|
|
16
|
+
// Five kinds, GitHub's: note, tip, important, warning, caution. The marker
|
|
17
|
+
// is kept as it was written (`[!note]` stays lower case) unless the kind
|
|
18
|
+
// changes, so an untouched callout never reads as edited.
|
|
19
|
+
export const CALLOUT_KINDS = ['note', 'tip', 'important', 'warning', 'caution'];
|
|
20
|
+
// The marker alone on the quote's first line: `[!NOTE]`, any case, spaces
|
|
21
|
+
// after it allowed.
|
|
22
|
+
const MARKER = /^\[!(note|tip|important|warning|caution)\][ \t]*(?:\n|$)/i;
|
|
23
|
+
/**
|
|
24
|
+
* A quote that's a callout: its kind and marker as written, with the
|
|
25
|
+
* marker taken out of its first paragraph (and that paragraph too, when
|
|
26
|
+
* the marker was all of it). Null for any other node, untouched.
|
|
27
|
+
*/
|
|
28
|
+
function takeCallout(node) {
|
|
29
|
+
if (node.type !== 'blockquote')
|
|
30
|
+
return null;
|
|
31
|
+
const paragraph = node.children?.[0];
|
|
32
|
+
const text = paragraph?.type === 'paragraph' ? paragraph.children?.[0] : undefined;
|
|
33
|
+
if (!paragraph || text?.type !== 'text' || typeof text.value !== 'string')
|
|
34
|
+
return null;
|
|
35
|
+
const found = MARKER.exec(text.value);
|
|
36
|
+
if (!found)
|
|
37
|
+
return null;
|
|
38
|
+
const rest = text.value.slice(found[0].length);
|
|
39
|
+
if (rest)
|
|
40
|
+
text.value = rest;
|
|
41
|
+
else
|
|
42
|
+
paragraph.children.shift();
|
|
43
|
+
if (paragraph.children.length === 0)
|
|
44
|
+
node.children.shift();
|
|
45
|
+
return { kind: found[1].toLowerCase(), marker: found[0].trim() };
|
|
46
|
+
}
|
|
47
|
+
function visit(node, each) {
|
|
48
|
+
for (const child of node.children ?? [])
|
|
49
|
+
visit(child, each);
|
|
50
|
+
each(node);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* For react-markdown: a callout quote becomes a `<div data-callout="note">`
|
|
54
|
+
* (its marker gone), which <Markdown>'s `div` component draws as one.
|
|
55
|
+
*/
|
|
56
|
+
export function remarkCallouts() {
|
|
57
|
+
return (tree) => {
|
|
58
|
+
visit(tree, (node) => {
|
|
59
|
+
const callout = takeCallout(node);
|
|
60
|
+
if (callout)
|
|
61
|
+
node.data = { ...node.data, hName: 'div', hProperties: { dataCallout: callout.kind } };
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* For the rich-text editor: a callout quote becomes a `callout` node
|
|
67
|
+
* (`kind`, `marker`), which its schema reads and `calloutHandler` writes.
|
|
68
|
+
*/
|
|
69
|
+
export function remarkCalloutNodes() {
|
|
70
|
+
return (tree) => {
|
|
71
|
+
visit(tree, (node) => {
|
|
72
|
+
const callout = takeCallout(node);
|
|
73
|
+
if (!callout)
|
|
74
|
+
return;
|
|
75
|
+
node.type = 'callout';
|
|
76
|
+
node.kind = callout.kind;
|
|
77
|
+
node.marker = callout.marker;
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** The marker to write for a callout: as it was written, unless its kind changed. */
|
|
82
|
+
export function calloutMarker(kind, written) {
|
|
83
|
+
return written && written.slice(2, -1).toLowerCase() === kind ? written : `[!${kind.toUpperCase()}]`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Writes a `callout` node the way remark writes a quote, with the marker
|
|
87
|
+
* as its first line. A first block that isn't a paragraph gets a blank
|
|
88
|
+
* line after the marker, so it can't run into it.
|
|
89
|
+
*/
|
|
90
|
+
export function calloutHandler(node, _parent, state, info) {
|
|
91
|
+
const exit = state.enter('blockquote');
|
|
92
|
+
const tracker = state.createTracker(info);
|
|
93
|
+
tracker.move('> ');
|
|
94
|
+
tracker.shift(2);
|
|
95
|
+
const marker = calloutMarker(node.kind, node.marker);
|
|
96
|
+
// Milkdown's serializer starts a container's flow on a new line; the
|
|
97
|
+
// marker's line is that line.
|
|
98
|
+
const body = state.containerFlow(node, tracker.current()).replace(/^\n+/, '');
|
|
99
|
+
const joined = !body ? marker : node.children?.[0]?.type === 'paragraph' ? `${marker}\n${body}` : `${marker}\n\n${body}`;
|
|
100
|
+
const value = state.indentLines(joined, (line, _index, blank) => '>' + (blank ? '' : ' ') + line);
|
|
101
|
+
exit();
|
|
102
|
+
return value;
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tristan2828/ui-foundation",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.22.0",
|
|
4
4
|
"description": "Shared UI foundation for contract-first, database-backed React apps: shadcn primitives, DataTable/EntityForm composites, auth, the gateway error seam, design tokens, lint rules and the AI playbook.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/styles/index.css
CHANGED
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
--color-info: var(--info);
|
|
48
48
|
--color-info-foreground: var(--info-foreground);
|
|
49
49
|
--color-info-text: var(--info-text);
|
|
50
|
+
--color-link: var(--link);
|
|
51
|
+
--color-rule: var(--rule);
|
|
50
52
|
--color-category-1: var(--category-1);
|
|
51
53
|
--color-category-2: var(--category-2);
|
|
52
54
|
--color-category-3: var(--category-3);
|
|
@@ -105,6 +107,21 @@
|
|
|
105
107
|
line-height: var(--type-caption-leading);
|
|
106
108
|
font-weight: var(--type-caption-weight);
|
|
107
109
|
}
|
|
110
|
+
@utility type-heading-1 {
|
|
111
|
+
font-size: var(--type-heading-1-size);
|
|
112
|
+
line-height: var(--type-heading-1-leading);
|
|
113
|
+
font-weight: var(--type-heading-1-weight);
|
|
114
|
+
}
|
|
115
|
+
@utility type-heading-2 {
|
|
116
|
+
font-size: var(--type-heading-2-size);
|
|
117
|
+
line-height: var(--type-heading-2-leading);
|
|
118
|
+
font-weight: var(--type-heading-2-weight);
|
|
119
|
+
}
|
|
120
|
+
@utility type-heading-3 {
|
|
121
|
+
font-size: var(--type-heading-3-size);
|
|
122
|
+
line-height: var(--type-heading-3-leading);
|
|
123
|
+
font-weight: var(--type-heading-3-weight);
|
|
124
|
+
}
|
|
108
125
|
|
|
109
126
|
@layer base {
|
|
110
127
|
* {
|
package/styles/theme.css
CHANGED
|
@@ -123,6 +123,22 @@
|
|
|
123
123
|
--type-caption-size: 0.75rem; /* counts, metadata, helper text */
|
|
124
124
|
--type-caption-leading: 1rem;
|
|
125
125
|
--type-caption-weight: 400;
|
|
126
|
+
/*
|
|
127
|
+
* Headings inside written text (a record's notes: <Markdown> and the
|
|
128
|
+
* rich-text editor), not the screen's own outline. Three clear steps
|
|
129
|
+
* over the 14px body, roughly Notion's proportions, so `#`, `##` and
|
|
130
|
+
* `###` never look alike. Heading 1 also gets a rule under it (where
|
|
131
|
+
* the roles are used), so it reads as the top of the outline.
|
|
132
|
+
*/
|
|
133
|
+
--type-heading-1-size: 1.625rem;
|
|
134
|
+
--type-heading-1-leading: 2rem;
|
|
135
|
+
--type-heading-1-weight: 700;
|
|
136
|
+
--type-heading-2-size: 1.25rem;
|
|
137
|
+
--type-heading-2-leading: 1.75rem;
|
|
138
|
+
--type-heading-2-weight: 600;
|
|
139
|
+
--type-heading-3-size: 1.0625rem;
|
|
140
|
+
--type-heading-3-leading: 1.5rem;
|
|
141
|
+
--type-heading-3-weight: 600;
|
|
126
142
|
|
|
127
143
|
/*
|
|
128
144
|
* Table density (design-language.md "Density"). The table primitive's
|
|
@@ -167,6 +183,12 @@
|
|
|
167
183
|
--success-text: var(--green-800);
|
|
168
184
|
--warning-text: var(--amber-800);
|
|
169
185
|
--info-text: var(--blue-800);
|
|
186
|
+
/* A link in written text: coloured as well as underlined, so it reads
|
|
187
|
+
as a link at a glance (on white: 5.59). */
|
|
188
|
+
--link: var(--blue-700);
|
|
189
|
+
/* A thematic break (`---`) in written text: a 2px line meant to be
|
|
190
|
+
seen, stronger than --border's hairline (on white: 2.59). */
|
|
191
|
+
--rule: var(--gray-400);
|
|
170
192
|
/* Categorical slots — a glyph tint, never a fill behind text. */
|
|
171
193
|
--category-1: var(--category-1-700);
|
|
172
194
|
--category-2: var(--category-2-700);
|
|
@@ -176,6 +198,11 @@
|
|
|
176
198
|
--category-6: var(--category-6-700);
|
|
177
199
|
--category-7: var(--category-7-700);
|
|
178
200
|
--category-8: var(--category-8-700);
|
|
201
|
+
/* The zebra stripe (table.tsx: bg-muted/40 on even rows) mixed onto the
|
|
202
|
+
page, opaque: what a pinned table cell paints on an even row, so it
|
|
203
|
+
matches the row without letting scrolled columns show through. Mixed
|
|
204
|
+
in sRGB, as the browser composites the translucent stripe. */
|
|
205
|
+
--table-stripe: color-mix(in srgb, var(--muted) 40%, var(--background));
|
|
179
206
|
--border: var(--gray-200);
|
|
180
207
|
--input: var(--gray-200);
|
|
181
208
|
--ring: var(--gray-400);
|
|
@@ -236,6 +263,8 @@
|
|
|
236
263
|
--success-text: var(--green-400);
|
|
237
264
|
--warning-text: var(--amber-400);
|
|
238
265
|
--info-text: var(--blue-400);
|
|
266
|
+
--link: var(--blue-400); /* on near-black: 8.90 */
|
|
267
|
+
--rule: var(--gray-600); /* on near-black: 2.54 */
|
|
239
268
|
/* Categorical slots — lighter shades to read against the dark page. */
|
|
240
269
|
--category-1: var(--category-1-400);
|
|
241
270
|
--category-2: var(--category-2-400);
|
|
@@ -245,6 +274,7 @@
|
|
|
245
274
|
--category-6: var(--category-6-400);
|
|
246
275
|
--category-7: var(--category-7-400);
|
|
247
276
|
--category-8: var(--category-8-400);
|
|
277
|
+
--table-stripe: color-mix(in srgb, var(--muted) 40%, var(--background));
|
|
248
278
|
--border: var(--white-a10);
|
|
249
279
|
--input: var(--white-a15);
|
|
250
280
|
--ring: var(--gray-500);
|