@estiva-app/ui 0.62.1 → 0.64.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/dist/CodeBlockColours.d.ts +11 -0
- package/dist/CodeBlockColours.d.ts.map +1 -0
- package/dist/ComposerTriggers.d.ts +8 -2
- package/dist/ComposerTriggers.d.ts.map +1 -1
- package/dist/KindIcon.d.ts +30 -0
- package/dist/KindIcon.d.ts.map +1 -0
- package/dist/Link.d.ts +1 -1
- package/dist/PastedImages.d.ts +33 -0
- package/dist/PastedImages.d.ts.map +1 -0
- package/dist/RichText.d.ts +2 -1
- package/dist/RichText.d.ts.map +1 -1
- package/dist/codeColours.d.ts +29 -0
- package/dist/codeColours.d.ts.map +1 -0
- package/dist/editor.d.ts +2 -0
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +86 -13
- package/dist/editor.js.map +4 -4
- package/dist/eslint/index.js +1 -0
- package/dist/eslint/index.js.map +2 -2
- package/dist/eslint/no-restyled-part.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +291 -237
- package/dist/index.js.map +4 -4
- package/package.json +8 -2
- package/registry.json +179 -16
- package/src/CodeBlockColours.mdx +48 -0
- package/src/CodeBlockColours.stories.tsx +37 -0
- package/src/CodeBlockColours.ts +14 -0
- package/src/ComposerTriggers.mdx +6 -4
- package/src/ComposerTriggers.test.tsx +30 -0
- package/src/ComposerTriggers.tsx +9 -3
- package/src/KindIcon.mdx +58 -0
- package/src/KindIcon.stories.tsx +64 -0
- package/src/KindIcon.tsx +64 -0
- package/src/Link.mdx +1 -1
- package/src/Link.stories.tsx +1 -1
- package/src/Link.test.tsx +2 -2
- package/src/Link.tsx +2 -2
- package/src/PastedImages.mdx +61 -0
- package/src/PastedImages.stories.tsx +53 -0
- package/src/PastedImages.test.ts +83 -0
- package/src/PastedImages.ts +105 -0
- package/src/RichText.stories.tsx +24 -0
- package/src/RichText.tsx +31 -17
- package/src/codeColours.ts +33 -0
- package/src/editor.ts +2 -0
- package/src/eslint/no-restyled-part.ts +1 -0
- package/src/index.ts +1 -0
- package/src/pages.test.ts +5 -1
- package/src/registry/registry.test.ts +7 -7
- package/stories/Choosing.mdx +3 -0
- package/tailwind-preset.js +8 -0
- package/tokens.css +50 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estiva-app/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.0",
|
|
4
4
|
"description": "Estiva's design tokens (the contract) and a small set of primitives (a convenience) for every Estiva app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -91,7 +91,8 @@
|
|
|
91
91
|
"@tiptap/extension-mention": "^3.22.1",
|
|
92
92
|
"@tiptap/pm": "^3.22.1",
|
|
93
93
|
"@tiptap/react": "^3.22.1",
|
|
94
|
-
"@tiptap/suggestion": "^3.22.1"
|
|
94
|
+
"@tiptap/suggestion": "^3.22.1",
|
|
95
|
+
"@tiptap/extension-code-block-lowlight": "^3.22.1"
|
|
95
96
|
},
|
|
96
97
|
"peerDependenciesMeta": {
|
|
97
98
|
"eslint": {
|
|
@@ -117,6 +118,9 @@
|
|
|
117
118
|
},
|
|
118
119
|
"@tiptap/suggestion": {
|
|
119
120
|
"optional": true
|
|
121
|
+
},
|
|
122
|
+
"@tiptap/extension-code-block-lowlight": {
|
|
123
|
+
"optional": true
|
|
120
124
|
}
|
|
121
125
|
},
|
|
122
126
|
"devDependencies": {
|
|
@@ -130,6 +134,7 @@
|
|
|
130
134
|
"@testing-library/react": "^16.3.3",
|
|
131
135
|
"@testing-library/user-event": "^14.6.7",
|
|
132
136
|
"@tiptap/core": "^3.31.3",
|
|
137
|
+
"@tiptap/extension-code-block-lowlight": "^3.31.3",
|
|
133
138
|
"@tiptap/extension-mention": "^3.31.3",
|
|
134
139
|
"@tiptap/pm": "^3.31.3",
|
|
135
140
|
"@tiptap/react": "^3.31.3",
|
|
@@ -162,6 +167,7 @@
|
|
|
162
167
|
"dependencies": {
|
|
163
168
|
"@base-ui/react": "^1.8.0",
|
|
164
169
|
"clsx": "^2.1.1",
|
|
170
|
+
"lowlight": "^3.3.0",
|
|
165
171
|
"tailwind-merge": "^3.5.0"
|
|
166
172
|
},
|
|
167
173
|
"main": "./dist/index.js",
|
package/registry.json
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
"kind": "package",
|
|
5
5
|
"repo": "estiva-ui",
|
|
6
6
|
"package": "@estiva-app/ui",
|
|
7
|
-
"packageVersion": "0.
|
|
8
|
-
"exports":
|
|
9
|
-
"typeExports":
|
|
7
|
+
"packageVersion": "0.64.0",
|
|
8
|
+
"exports": 140,
|
|
9
|
+
"typeExports": 145,
|
|
10
10
|
"files": null
|
|
11
11
|
},
|
|
12
12
|
"storybook": {
|
|
@@ -1196,6 +1196,25 @@
|
|
|
1196
1196
|
"app": null,
|
|
1197
1197
|
"looks": []
|
|
1198
1198
|
},
|
|
1199
|
+
{
|
|
1200
|
+
"name": "CodeBlockColours",
|
|
1201
|
+
"repo": "estiva-ui",
|
|
1202
|
+
"kind": "component",
|
|
1203
|
+
"importPath": "@estiva-app/ui/editor",
|
|
1204
|
+
"sourceFile": "src/CodeBlockColours.ts",
|
|
1205
|
+
"purpose": "A code block in an editor, coloured by its language as you type.",
|
|
1206
|
+
"purposeFrom": "page",
|
|
1207
|
+
"props": [],
|
|
1208
|
+
"variants": [],
|
|
1209
|
+
"ownsBehaviours": [],
|
|
1210
|
+
"status": "stable",
|
|
1211
|
+
"migrationStage": null,
|
|
1212
|
+
"docsId": "primitives-codeblockcolours--docs",
|
|
1213
|
+
"storyId": "primitives-codeblockcolours--in-an-editor",
|
|
1214
|
+
"docPage": "src/CodeBlockColours.mdx",
|
|
1215
|
+
"app": null,
|
|
1216
|
+
"looks": []
|
|
1217
|
+
},
|
|
1199
1218
|
{
|
|
1200
1219
|
"name": "CollapsibleSection",
|
|
1201
1220
|
"repo": "estiva-ui",
|
|
@@ -3228,6 +3247,68 @@
|
|
|
3228
3247
|
"app": null,
|
|
3229
3248
|
"looks": []
|
|
3230
3249
|
},
|
|
3250
|
+
{
|
|
3251
|
+
"name": "KindIcon",
|
|
3252
|
+
"repo": "estiva-ui",
|
|
3253
|
+
"kind": "component",
|
|
3254
|
+
"importPath": "@estiva-app/ui",
|
|
3255
|
+
"sourceFile": "src/KindIcon.tsx",
|
|
3256
|
+
"purpose": "The icon before a thing's name that says what kind of thing it is: a topic, a project, an issue, another app's file, or a message.",
|
|
3257
|
+
"purposeFrom": "page",
|
|
3258
|
+
"props": [
|
|
3259
|
+
{
|
|
3260
|
+
"name": "kind",
|
|
3261
|
+
"takes": "topic | project | issue | file | message",
|
|
3262
|
+
"required": true,
|
|
3263
|
+
"note": null
|
|
3264
|
+
},
|
|
3265
|
+
{
|
|
3266
|
+
"name": "size",
|
|
3267
|
+
"takes": "14 | 16",
|
|
3268
|
+
"required": false,
|
|
3269
|
+
"note": "16 in a row or a list; 14 inside a chip."
|
|
3270
|
+
},
|
|
3271
|
+
{
|
|
3272
|
+
"name": "tone",
|
|
3273
|
+
"takes": "secondary | inherit",
|
|
3274
|
+
"required": false,
|
|
3275
|
+
"note": "`secondary`: the quieter grey a list row's icon wears."
|
|
3276
|
+
},
|
|
3277
|
+
{
|
|
3278
|
+
"name": "className",
|
|
3279
|
+
"takes": "text",
|
|
3280
|
+
"required": false,
|
|
3281
|
+
"note": "Space and position only."
|
|
3282
|
+
}
|
|
3283
|
+
],
|
|
3284
|
+
"variants": [
|
|
3285
|
+
{
|
|
3286
|
+
"prop": "kind",
|
|
3287
|
+
"values": [
|
|
3288
|
+
"topic",
|
|
3289
|
+
"project",
|
|
3290
|
+
"issue",
|
|
3291
|
+
"file",
|
|
3292
|
+
"message"
|
|
3293
|
+
]
|
|
3294
|
+
},
|
|
3295
|
+
{
|
|
3296
|
+
"prop": "tone",
|
|
3297
|
+
"values": [
|
|
3298
|
+
"secondary",
|
|
3299
|
+
"inherit"
|
|
3300
|
+
]
|
|
3301
|
+
}
|
|
3302
|
+
],
|
|
3303
|
+
"ownsBehaviours": [],
|
|
3304
|
+
"status": "stable",
|
|
3305
|
+
"migrationStage": null,
|
|
3306
|
+
"docsId": "primitives-kindicon--docs",
|
|
3307
|
+
"storyId": "primitives-kindicon--default",
|
|
3308
|
+
"docPage": "src/KindIcon.mdx",
|
|
3309
|
+
"app": null,
|
|
3310
|
+
"looks": []
|
|
3311
|
+
},
|
|
3231
3312
|
{
|
|
3232
3313
|
"name": "Lightbox",
|
|
3233
3314
|
"repo": "estiva-ui",
|
|
@@ -3377,10 +3458,10 @@
|
|
|
3377
3458
|
"docPage": "src/Link.mdx",
|
|
3378
3459
|
"app": null,
|
|
3379
3460
|
"looks": [
|
|
3380
|
-
"text-
|
|
3381
|
-
"text-
|
|
3382
|
-
"text-
|
|
3383
|
-
"text-
|
|
3461
|
+
"text-text-interactive underline underline-offset-2 hover:opacity-80",
|
|
3462
|
+
"text-text-interactive underline underline-offset-2 hover:opacity-80 after:rounded-md focus-visible:outline-none",
|
|
3463
|
+
"text-text-interactive underline underline-offset-2 hover:opacity-80 truncate",
|
|
3464
|
+
"text-text-interactive underline underline-offset-2 hover:opacity-80 truncate after:rounded-md focus-visible:outline-none",
|
|
3384
3465
|
"hover:underline truncate after:rounded-md focus-visible:outline-none",
|
|
3385
3466
|
"underline underline-offset-2 hover:text-text-primary after:rounded-md focus-visible:outline-none",
|
|
3386
3467
|
"underline underline-offset-2 hover:text-text-primary truncate",
|
|
@@ -4413,6 +4494,63 @@
|
|
|
4413
4494
|
"app": null,
|
|
4414
4495
|
"looks": []
|
|
4415
4496
|
},
|
|
4497
|
+
{
|
|
4498
|
+
"name": "pastedImageName",
|
|
4499
|
+
"repo": "estiva-ui",
|
|
4500
|
+
"kind": "helper",
|
|
4501
|
+
"importPath": "@estiva-app/ui/editor",
|
|
4502
|
+
"sourceFile": "src/PastedImages.ts",
|
|
4503
|
+
"purpose": "`'Screenshot 2026-09-07 14-32-05.png'`: sortable, and readable on a file card.",
|
|
4504
|
+
"purposeFrom": "comment",
|
|
4505
|
+
"props": [],
|
|
4506
|
+
"variants": [],
|
|
4507
|
+
"ownsBehaviours": [],
|
|
4508
|
+
"status": "stable",
|
|
4509
|
+
"migrationStage": null,
|
|
4510
|
+
"docsId": "primitives-pastedimages--docs",
|
|
4511
|
+
"storyId": "primitives-pastedimages--in-an-editor",
|
|
4512
|
+
"docPage": null,
|
|
4513
|
+
"app": null,
|
|
4514
|
+
"looks": []
|
|
4515
|
+
},
|
|
4516
|
+
{
|
|
4517
|
+
"name": "pastedImages",
|
|
4518
|
+
"repo": "estiva-ui",
|
|
4519
|
+
"kind": "helper",
|
|
4520
|
+
"importPath": "@estiva-app/ui/editor",
|
|
4521
|
+
"sourceFile": "src/PastedImages.ts",
|
|
4522
|
+
"purpose": "The pictures on the clipboard, named — `[]` when there is nothing to attach.",
|
|
4523
|
+
"purposeFrom": "comment",
|
|
4524
|
+
"props": [],
|
|
4525
|
+
"variants": [],
|
|
4526
|
+
"ownsBehaviours": [],
|
|
4527
|
+
"status": "stable",
|
|
4528
|
+
"migrationStage": null,
|
|
4529
|
+
"docsId": "primitives-pastedimages--docs",
|
|
4530
|
+
"storyId": "primitives-pastedimages--in-an-editor",
|
|
4531
|
+
"docPage": null,
|
|
4532
|
+
"app": null,
|
|
4533
|
+
"looks": []
|
|
4534
|
+
},
|
|
4535
|
+
{
|
|
4536
|
+
"name": "PastedImages",
|
|
4537
|
+
"repo": "estiva-ui",
|
|
4538
|
+
"kind": "component",
|
|
4539
|
+
"importPath": "@estiva-app/ui/editor",
|
|
4540
|
+
"sourceFile": "src/PastedImages.ts",
|
|
4541
|
+
"purpose": "Pasting a picture into an editor attaches it.",
|
|
4542
|
+
"purposeFrom": "page",
|
|
4543
|
+
"props": [],
|
|
4544
|
+
"variants": [],
|
|
4545
|
+
"ownsBehaviours": [],
|
|
4546
|
+
"status": "stable",
|
|
4547
|
+
"migrationStage": null,
|
|
4548
|
+
"docsId": "primitives-pastedimages--docs",
|
|
4549
|
+
"storyId": "primitives-pastedimages--in-an-editor",
|
|
4550
|
+
"docPage": "src/PastedImages.mdx",
|
|
4551
|
+
"app": null,
|
|
4552
|
+
"looks": []
|
|
4553
|
+
},
|
|
4416
4554
|
{
|
|
4417
4555
|
"name": "peopleMenu",
|
|
4418
4556
|
"repo": "estiva-ui",
|
|
@@ -5161,7 +5299,13 @@
|
|
|
5161
5299
|
"name": "icon",
|
|
5162
5300
|
"takes": "anything",
|
|
5163
5301
|
"required": true,
|
|
5164
|
-
"note": "At each row's start: a 16px icon — unless the row brings its own (`CaptionedItem.icon`)."
|
|
5302
|
+
"note": "At each row's start: a 16px icon — unless the row brings its own (`CaptionedItem.icon`), or `iconFor` answers."
|
|
5303
|
+
},
|
|
5304
|
+
{
|
|
5305
|
+
"name": "iconFor",
|
|
5306
|
+
"takes": "a handler",
|
|
5307
|
+
"required": false,
|
|
5308
|
+
"note": "A row's 16px icon from what it points at — its `uri` — when the list holds several kinds of thing; `undefined` from it draws `icon`."
|
|
5165
5309
|
}
|
|
5166
5310
|
],
|
|
5167
5311
|
"variants": [],
|
|
@@ -5269,15 +5413,15 @@
|
|
|
5269
5413
|
"docPage": "src/RichText.mdx",
|
|
5270
5414
|
"app": null,
|
|
5271
5415
|
"looks": [
|
|
5272
|
-
"[
|
|
5273
|
-
"[&
|
|
5274
|
-
"[&
|
|
5275
|
-
"[&
|
|
5276
|
-
"[&
|
|
5416
|
+
"[&_:is(ul,ol)]:pl-5 [&_li>p]:m-0 [&_li]:break-words [&_li]:marker:text-text-secondary [&_ol]:list-decimal [&_ul]:list-disc",
|
|
5417
|
+
"[&_blockquote>p]:m-0 [&_blockquote]:border-l-[3px] [&_blockquote]:border-text-muted [&_blockquote]:pl-2.5",
|
|
5418
|
+
"[&_:not(pre)>code]:rounded-sm [&_:not(pre)>code]:bg-bg-code [&_:not(pre)>code]:px-1 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:text-text-code [&_code]:font-mono [&_code]:text-[0.85em] [&_code]:text-text-primary",
|
|
5419
|
+
"[&_pre>code]:text-[1em] [&_pre]:whitespace-pre-wrap [&_pre]:break-words [&_pre]:rounded-md [&_pre]:bg-bg-code [&_pre]:px-3 [&_pre]:py-2.5 [&_pre]:font-mono [&_pre]:text-[0.85em] [&_pre]:leading-relaxed [&_pre]:text-text-primary",
|
|
5420
|
+
"[&_:is(.hljs-keyword,.hljs-doctag,.hljs-type,.hljs-template-tag,.hljs-template-variable)]:text-syntax-keyword [&_.hljs-title]:text-syntax-function [&_:is(.hljs-number,.hljs-literal,.hljs-attr,.hljs-attribute,.hljs-meta,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id)]:text-syntax-constant [&_:is(.hljs-string,.hljs-regexp)]:text-syntax-string [&_:is(.hljs-built_in,.hljs-symbol)]:text-syntax-variable [&_.hljs-comment]:text-syntax-comment [&_:is(.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo)]:text-syntax-tag",
|
|
5277
5421
|
"[&_:is(th,td)>p]:m-0 [&_:is(th,td)]:border [&_:is(th,td)]:border-border-default [&_:is(th,td)]:px-2 [&_:is(th,td)]:py-1 [&_:is(th,td)]:text-left [&_:is(th,td)]:align-top [&_table]:w-full [&_table]:border-collapse [&_th]:bg-bg-code [&_th]:font-medium [&_th]:text-text-primary",
|
|
5278
|
-
"[&_em]:italic [&_strong]:font-semibold [&_u]:underline [&_u]:underline-offset-2",
|
|
5279
|
-
"text-body-2 [&>:is(h2,h3)]:text-body-2 [&>:is(h2,h3)]:font-semibold [&>h1]:text-h4",
|
|
5280
|
-
"text-caption leading-snug [&>:is(h2,h3)]:text-caption [&>:is(h2,h3)]:font-semibold [&>h1]:text-body-2 [&>h1]:font-semibold"
|
|
5422
|
+
"[&_em]:italic [&_strong]:font-semibold [&_strong]:text-text-primary [&_u]:underline [&_u]:decoration-text-muted [&_u]:underline-offset-2",
|
|
5423
|
+
"text-body-2 leading-normal [&>:is(h2,h3)]:text-body-2 [&>:is(h2,h3)]:font-semibold [&>h1]:text-h4",
|
|
5424
|
+
"text-caption leading-snug [&>:is(h1,h2,h3)]:mb-0.5 [&>:is(h1,h2,h3)]:mt-1.5 [&>:is(h1,h2,h3)+:is(h1,h2,h3)]:mt-1 [&>:is(h1,h2,h3):last-child]:mb-0 [&>:is(h2,h3)]:text-caption [&>:is(h2,h3)]:font-semibold [&>h1]:text-body-2 [&>h1]:font-semibold"
|
|
5281
5425
|
]
|
|
5282
5426
|
},
|
|
5283
5427
|
{
|
|
@@ -6410,6 +6554,25 @@
|
|
|
6410
6554
|
"bg-bg-field border border-border-field shadow-field hover:border-border-strong focus:border-border-focus rounded-lg text-caption text-text-primary placeholder:text-text-muted outline-none transition-colors disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled disabled:border-border-field signal:transition-shadow signal:focus:shadow-focus-ring"
|
|
6411
6555
|
]
|
|
6412
6556
|
},
|
|
6557
|
+
{
|
|
6558
|
+
"name": "thingKindOf",
|
|
6559
|
+
"repo": "estiva-ui",
|
|
6560
|
+
"kind": "helper",
|
|
6561
|
+
"importPath": "@estiva-app/ui",
|
|
6562
|
+
"sourceFile": "src/KindIcon.tsx",
|
|
6563
|
+
"purpose": "The kind of thing behind an event kind: a message is a message, a channel or a bare file is a topic, a project and an issue are themselves, and anything else is another app's file.",
|
|
6564
|
+
"purposeFrom": "comment",
|
|
6565
|
+
"props": [],
|
|
6566
|
+
"variants": [],
|
|
6567
|
+
"ownsBehaviours": [],
|
|
6568
|
+
"status": "stable",
|
|
6569
|
+
"migrationStage": null,
|
|
6570
|
+
"docsId": "primitives-kindicon--docs",
|
|
6571
|
+
"storyId": "primitives-kindicon--default",
|
|
6572
|
+
"docPage": null,
|
|
6573
|
+
"app": null,
|
|
6574
|
+
"looks": []
|
|
6575
|
+
},
|
|
6413
6576
|
{
|
|
6414
6577
|
"name": "Toast",
|
|
6415
6578
|
"repo": "estiva-ui",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Meta, Canvas } from '@storybook/addon-docs/blocks'
|
|
2
|
+
import * as CodeBlockColoursStories from './CodeBlockColours.stories'
|
|
3
|
+
|
|
4
|
+
<Meta of={CodeBlockColoursStories} />
|
|
5
|
+
|
|
6
|
+
# CodeBlockColours
|
|
7
|
+
|
|
8
|
+
A code block in an editor, coloured by its language as you type. The colours
|
|
9
|
+
are the ones RichText reads it in, so writing looks the same as reading.
|
|
10
|
+
|
|
11
|
+
<Canvas of={CodeBlockColoursStories.InAnEditor} />
|
|
12
|
+
|
|
13
|
+
## When
|
|
14
|
+
|
|
15
|
+
- **Any editor that writes RichText**: a message box, an edit box, a
|
|
16
|
+
description. Use it in place of the starter kit's code block.
|
|
17
|
+
|
|
18
|
+
## When not
|
|
19
|
+
|
|
20
|
+
- Drawing text that is already written → **RichText**, which colours its code
|
|
21
|
+
blocks itself.
|
|
22
|
+
- Code in a sentence → the code mark. It is amber and takes no other colours.
|
|
23
|
+
|
|
24
|
+
## How
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import StarterKit from '@tiptap/starter-kit'
|
|
28
|
+
import { CodeBlockColours } from '@estiva-app/ui/editor'
|
|
29
|
+
import { richTextClassName } from '@estiva-app/ui'
|
|
30
|
+
|
|
31
|
+
useEditor({
|
|
32
|
+
extensions: [StarterKit.configure({ codeBlock: false }), CodeBlockColours],
|
|
33
|
+
editorProps: { attributes: { class: richTextClassName() } },
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- **The editor's root must wear `richTextClassName`.** The colours live in its
|
|
38
|
+
class list; without it the words are named but drawn in one colour.
|
|
39
|
+
- The block keeps the name `codeBlock` and its `language`, so text written
|
|
40
|
+
before it was added reads the same.
|
|
41
|
+
- It colours highlight.js's 37 everyday languages. A block in any other
|
|
42
|
+
language, or in none, stays one colour.
|
|
43
|
+
- The app installs `@tiptap/extension-code-block-lowlight`, as it installs the
|
|
44
|
+
other Tiptap parts.
|
|
45
|
+
|
|
46
|
+
## What it owns
|
|
47
|
+
|
|
48
|
+
Nothing. It names each word of a code block; the RichText class list colours them.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { EditorContent, useEditor } from '@tiptap/react'
|
|
3
|
+
import StarterKit from '@tiptap/starter-kit'
|
|
4
|
+
import { Card } from './Card'
|
|
5
|
+
import { CodeBlockColours } from './CodeBlockColours'
|
|
6
|
+
import { richTextClassName } from './RichText'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Type in the code block: each word takes its colour as you write, the same
|
|
10
|
+
* colours RichText reads it in. Change the theme to see each set.
|
|
11
|
+
*/
|
|
12
|
+
const meta = {
|
|
13
|
+
title: 'Primitives/CodeBlockColours',
|
|
14
|
+
parameters: { layout: 'padded' },
|
|
15
|
+
} satisfies Meta
|
|
16
|
+
|
|
17
|
+
export default meta
|
|
18
|
+
type Story = StoryObj
|
|
19
|
+
|
|
20
|
+
function Box() {
|
|
21
|
+
const editor = useEditor({
|
|
22
|
+
extensions: [StarterKit.configure({ codeBlock: false }), CodeBlockColours],
|
|
23
|
+
content: [
|
|
24
|
+
'<p>A code block in a language takes its colours:</p>',
|
|
25
|
+
'<pre><code class="language-ts">// Count the items that are done\nexport function countDone(items: Item[]): number {\n const done = items.filter((item) => item.state === \'done\')\n return done.length + 0\n}</code></pre>',
|
|
26
|
+
].join(''),
|
|
27
|
+
editorProps: { attributes: { class: richTextClassName('default', 'outline-none'), 'aria-label': 'Text' } },
|
|
28
|
+
})
|
|
29
|
+
return (
|
|
30
|
+
<Card fill="surface" className="w-full max-w-lg p-3">
|
|
31
|
+
<EditorContent editor={editor} />
|
|
32
|
+
</Card>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** An editor with a code block in TypeScript. Type in it. */
|
|
37
|
+
export const InAnEditor: Story = { render: () => <Box /> }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** The editor's code block, coloured by language like RichText's (`@estiva-app/ui/editor`). */
|
|
2
|
+
import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
|
|
3
|
+
import { lowlight } from './codeColours'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A code block in the editor, with the colours RichText reads it in (Katerina,
|
|
7
|
+
* 9 October): the same highlight.js languages, the same classes, and the
|
|
8
|
+
* editor's root wears `richTextClassName`, which colours them.
|
|
9
|
+
*
|
|
10
|
+
* It replaces the starter kit's code block: `StarterKit.configure({ codeBlock: false })`,
|
|
11
|
+
* then add `CodeBlockColours`. The block keeps its name, `codeBlock`, and its
|
|
12
|
+
* `language` attribute, so nothing already written changes.
|
|
13
|
+
*/
|
|
14
|
+
export const CodeBlockColours = CodeBlockLowlight.configure({ lowlight })
|
package/src/ComposerTriggers.mdx
CHANGED
|
@@ -92,10 +92,12 @@ const urgent = editor ? urgentPubkeys(editor) : undefined
|
|
|
92
92
|
the same query; nothing waits for it. A row's `description` is its second
|
|
93
93
|
line, which the chip does not wear. A row the person has arrowed to stays
|
|
94
94
|
highlighted when the list is drawn again.
|
|
95
|
-
- **Several kinds of thing in one `[` list:**
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
- **Several kinds of thing in one `[` list:** give `ReferenceTrigger` and
|
|
96
|
+
`CaptionedReference` the same `iconFor(uri)` — at 16px for the rows, 14px
|
|
97
|
+
for the chip — and every row and the chip it inserts wear what they point
|
|
98
|
+
at. **KindIcon** is the answer for Estiva's own kinds. A row's own `icon`
|
|
99
|
+
wins over `iconFor`; a row with neither, or a `uri` `iconFor` answers
|
|
100
|
+
`undefined` for, keeps the list's one icon.
|
|
99
101
|
|
|
100
102
|
<Canvas of={ComposerTriggersStories.ReferencesOfSeveralKinds} />
|
|
101
103
|
|
|
@@ -145,6 +145,36 @@ describe('[', () => {
|
|
|
145
145
|
expect(editor!.getHTML()).toContain('data-message-mention="true"')
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
+
it('gives each row the icon `iconFor` answers for its uri; a row’s own icon wins, and no answer keeps the list’s', async () => {
|
|
149
|
+
const rows = [
|
|
150
|
+
{ ...ITEMS[0], uri: 'nostr:a' },
|
|
151
|
+
{ ...ITEMS[1], uri: 'nostr:b', icon: <i data-icon="own" /> },
|
|
152
|
+
{ ...ITEMS[2], uri: 'nostr:c' },
|
|
153
|
+
]
|
|
154
|
+
function Iconed() {
|
|
155
|
+
const made = useEditor({
|
|
156
|
+
extensions: [
|
|
157
|
+
StarterKit,
|
|
158
|
+
Chip,
|
|
159
|
+
ReferenceTrigger.configure({
|
|
160
|
+
items: () => rows,
|
|
161
|
+
nodeName: 'messageMention',
|
|
162
|
+
ariaLabel: 'Things',
|
|
163
|
+
sectionLabel: 'Things',
|
|
164
|
+
icon: <i data-icon="list" />,
|
|
165
|
+
iconFor: (uri) => (uri === 'nostr:a' ? <i data-icon="kind-a" /> : undefined),
|
|
166
|
+
}),
|
|
167
|
+
],
|
|
168
|
+
})
|
|
169
|
+
editor = made
|
|
170
|
+
return <EditorContent editor={made} />
|
|
171
|
+
}
|
|
172
|
+
render(<Iconed />)
|
|
173
|
+
await type('[')
|
|
174
|
+
const icons = within(screen.getByRole('listbox')).getAllByRole('option').map((row) => row.querySelector('[data-icon]')?.getAttribute('data-icon'))
|
|
175
|
+
expect(icons).toEqual(['kind-a', 'own', 'list'])
|
|
176
|
+
})
|
|
177
|
+
|
|
148
178
|
it('draws the app’s sections in order, skips an empty one, and redraws the open list when told', async () => {
|
|
149
179
|
const listeners = new Set<() => void>()
|
|
150
180
|
let late: CaptionedItem[] = []
|
package/src/ComposerTriggers.tsx
CHANGED
|
@@ -321,8 +321,14 @@ export interface ReferenceMenuOptions {
|
|
|
321
321
|
ariaLabel: string
|
|
322
322
|
/** The section's heading. */
|
|
323
323
|
sectionLabel: string
|
|
324
|
-
/** At each row's start: a 16px icon — unless the row brings its own (`CaptionedItem.icon`). */
|
|
324
|
+
/** At each row's start: a 16px icon — unless the row brings its own (`CaptionedItem.icon`), or `iconFor` answers. */
|
|
325
325
|
icon: ReactNode
|
|
326
|
+
/**
|
|
327
|
+
* A row's 16px icon from what it points at — its `uri` — when the list holds
|
|
328
|
+
* several kinds of thing; `undefined` from it draws `icon`. Give the chip the
|
|
329
|
+
* same answer at 14px (`CaptionedReference`'s `iconFor`), so a pick keeps it.
|
|
330
|
+
*/
|
|
331
|
+
iconFor?: (uri: string) => ReactNode | undefined
|
|
326
332
|
}
|
|
327
333
|
|
|
328
334
|
/** A row as the list holds it: which section it is drawn under, when the list has several. */
|
|
@@ -333,7 +339,7 @@ type ListedItem = CaptionedItem & { section?: string }
|
|
|
333
339
|
* One section headed `sectionLabel`, or — when the rows say which section they
|
|
334
340
|
* are in — a heading for each, in the order the rows arrive.
|
|
335
341
|
*/
|
|
336
|
-
export function referenceMenu({ ariaLabel, sectionLabel, icon }: ReferenceMenuOptions): SuggestionPopupOptions<CaptionedItem> {
|
|
342
|
+
export function referenceMenu({ ariaLabel, sectionLabel, icon, iconFor }: ReferenceMenuOptions): SuggestionPopupOptions<CaptionedItem> {
|
|
337
343
|
return {
|
|
338
344
|
ariaLabel,
|
|
339
345
|
width: 'w-[658px]',
|
|
@@ -349,7 +355,7 @@ export function referenceMenu({ ariaLabel, sectionLabel, icon }: ReferenceMenuOp
|
|
|
349
355
|
return groups
|
|
350
356
|
},
|
|
351
357
|
itemKey: (item) => `reference-${item.id}`,
|
|
352
|
-
row: (item) => ({ leading: item.icon ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: <EnterHint /> }),
|
|
358
|
+
row: (item) => ({ leading: item.icon ?? iconFor?.(item.uri) ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: <EnterHint /> }),
|
|
353
359
|
}
|
|
354
360
|
}
|
|
355
361
|
|
package/src/KindIcon.mdx
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks'
|
|
2
|
+
import * as KindIconStories from './KindIcon.stories'
|
|
3
|
+
|
|
4
|
+
<Meta of={KindIconStories} />
|
|
5
|
+
|
|
6
|
+
# KindIcon
|
|
7
|
+
|
|
8
|
+
The icon before a thing's name that says what kind of thing it is: a topic, a
|
|
9
|
+
project, an issue, another app's file, or a message. One table for every app,
|
|
10
|
+
so the same thing wears the same icon wherever it is named.
|
|
11
|
+
|
|
12
|
+
<Canvas of={KindIconStories.EveryKind} />
|
|
13
|
+
|
|
14
|
+
## When
|
|
15
|
+
|
|
16
|
+
- **Before a thing's name in a list row**, at 16px: a `[` list row, a recent
|
|
17
|
+
file, a search result.
|
|
18
|
+
- **Inside a chip**, at 14px: `InlineChip`'s `icon` when a sentence names a
|
|
19
|
+
thing.
|
|
20
|
+
- **As `iconFor` of the `[` list and its chip** (`ReferenceTrigger`,
|
|
21
|
+
`CaptionedReference`), so every row and every chip it inserts wears its own
|
|
22
|
+
kind.
|
|
23
|
+
|
|
24
|
+
<Canvas of={KindIconStories.InAChip} />
|
|
25
|
+
|
|
26
|
+
## When not
|
|
27
|
+
|
|
28
|
+
- A person → **Avatar** or **Person**. A person is a face, not a kind.
|
|
29
|
+
- A status (open, resolved) → **Chip**, or the app's own status mark.
|
|
30
|
+
- An icon for an action → **IconButton** with the icon you need.
|
|
31
|
+
|
|
32
|
+
## How
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { KindIcon, thingKindOf } from '@estiva-app/ui'
|
|
36
|
+
|
|
37
|
+
<KindIcon kind="issue" /> // 16px, in a row
|
|
38
|
+
<KindIcon kind={thingKindOf(event.kind)} size={14} tone="inherit" /> // inside a chip
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `thingKindOf(eventKind)` turns an event kind into a kind of thing. A pointer
|
|
42
|
+
written as `nostr:naddr…` carries its kind once decoded; a `nostr:nevent…`
|
|
43
|
+
is a message.
|
|
44
|
+
- The icon is `aria-hidden`: the name beside it says what it is.
|
|
45
|
+
- **Which icon is which kind is this part's.** An app never picks its own icon
|
|
46
|
+
for these. Its stroke is 1.5; it comes in two sizes and two tones: the
|
|
47
|
+
quieter grey of a list row, or `inherit`, the ink of the chip or row that
|
|
48
|
+
holds it.
|
|
49
|
+
|
|
50
|
+
<Canvas of={KindIconStories.InAList} />
|
|
51
|
+
|
|
52
|
+
## What it owns
|
|
53
|
+
|
|
54
|
+
Nothing. It only draws. Clicks and keys are yours.
|
|
55
|
+
|
|
56
|
+
## Props
|
|
57
|
+
|
|
58
|
+
<Controls of={KindIconStories.Default} />
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { InlineChip } from './InlineChip'
|
|
3
|
+
import { KindIcon, type ThingKind } from './KindIcon'
|
|
4
|
+
import { MenuItem, MenuPanel, MenuSection } from './Menu'
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Primitives/KindIcon',
|
|
8
|
+
component: KindIcon,
|
|
9
|
+
args: { kind: 'topic' },
|
|
10
|
+
argTypes: { kind: { control: 'select', options: ['topic', 'project', 'issue', 'file', 'message'] } },
|
|
11
|
+
} satisfies Meta<typeof KindIcon>
|
|
12
|
+
|
|
13
|
+
export default meta
|
|
14
|
+
type Story = StoryObj<typeof meta>
|
|
15
|
+
|
|
16
|
+
const KINDS: { kind: ThingKind; name: string }[] = [
|
|
17
|
+
{ kind: 'topic', name: 'Topic' },
|
|
18
|
+
{ kind: 'project', name: 'Project' },
|
|
19
|
+
{ kind: 'issue', name: 'Issue' },
|
|
20
|
+
{ kind: 'file', name: 'Another app’s file' },
|
|
21
|
+
{ kind: 'message', name: 'Message' },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {}
|
|
25
|
+
|
|
26
|
+
/** Every kind, at 16px, beside its name. */
|
|
27
|
+
export const EveryKind: Story = {
|
|
28
|
+
parameters: { controls: { disable: true } },
|
|
29
|
+
render: () => (
|
|
30
|
+
<div className="flex flex-col gap-2 text-body-2 text-text-primary">
|
|
31
|
+
{KINDS.map(({ kind, name }) => (
|
|
32
|
+
<div key={kind} className="flex items-center gap-2">
|
|
33
|
+
<KindIcon kind={kind} />
|
|
34
|
+
{name}
|
|
35
|
+
</div>
|
|
36
|
+
))}
|
|
37
|
+
</div>
|
|
38
|
+
),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** At 14px inside a chip, the way a sentence names a thing. */
|
|
42
|
+
export const InAChip: Story = {
|
|
43
|
+
parameters: { controls: { disable: true } },
|
|
44
|
+
render: () => (
|
|
45
|
+
<p className="text-body-2 text-text-primary">
|
|
46
|
+
Tracked in <InlineChip icon={<KindIcon kind="issue" size={14} tone="inherit" />}>Ship the block model</InlineChip> and{' '}
|
|
47
|
+
<InlineChip icon={<KindIcon kind="topic" size={14} tone="inherit" />}>Planning</InlineChip> for now.
|
|
48
|
+
</p>
|
|
49
|
+
),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** At the start of list rows, each saying what it points at. */
|
|
53
|
+
export const InAList: Story = {
|
|
54
|
+
parameters: { controls: { disable: true } },
|
|
55
|
+
render: () => (
|
|
56
|
+
<MenuPanel className="w-72">
|
|
57
|
+
<MenuSection label="Files">
|
|
58
|
+
{KINDS.map(({ kind, name }) => (
|
|
59
|
+
<MenuItem key={kind} label={name} leading={<KindIcon kind={kind} />} onClick={() => {}} />
|
|
60
|
+
))}
|
|
61
|
+
</MenuSection>
|
|
62
|
+
</MenuPanel>
|
|
63
|
+
),
|
|
64
|
+
}
|