@charcuterie/ui 3.12.0 → 3.14.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/Avatar/Avatar.d.ts +163 -0
- package/dist/Avatar/Avatar.d.ts.map +1 -0
- package/dist/Avatar/Avatar.js +139 -0
- package/dist/Avatar/Avatar.js.map +1 -0
- package/dist/Board/BoardLaneList.d.ts +29 -0
- package/dist/Board/BoardLaneList.d.ts.map +1 -1
- package/dist/Board/BoardLaneList.js +2 -1
- package/dist/Board/BoardLaneList.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/markdownEditorCodeMirror/MarkdownView.d.ts +140 -0
- package/dist/markdownEditorCodeMirror/MarkdownView.d.ts.map +1 -0
- package/dist/markdownEditorCodeMirror/MarkdownView.js +268 -0
- package/dist/markdownEditorCodeMirror/MarkdownView.js.map +1 -0
- package/dist/markdownEditorCodeMirror/index.d.ts +14 -3
- package/dist/markdownEditorCodeMirror/index.d.ts.map +1 -1
- package/dist/markdownEditorCodeMirror/index.js +12 -3
- package/dist/markdownEditorCodeMirror/index.js.map +1 -1
- package/dist/markdownEditorCodeMirror/livePreview.d.ts +42 -2
- package/dist/markdownEditorCodeMirror/livePreview.d.ts.map +1 -1
- package/dist/markdownEditorCodeMirror/livePreview.js +243 -26
- package/dist/markdownEditorCodeMirror/livePreview.js.map +1 -1
- package/dist/markdownEditorCodeMirror/livePreviewRanges.d.ts.map +1 -1
- package/dist/markdownEditorCodeMirror/livePreviewRanges.js +49 -7
- package/dist/markdownEditorCodeMirror/livePreviewRanges.js.map +1 -1
- package/dist/markdownEditorCodeMirror/safeUrls.d.ts +46 -0
- package/dist/markdownEditorCodeMirror/safeUrls.d.ts.map +1 -0
- package/dist/markdownEditorCodeMirror/safeUrls.js +123 -0
- package/dist/markdownEditorCodeMirror/safeUrls.js.map +1 -0
- package/package.json +1 -1
- package/src/Avatar/Avatar.mdx +92 -0
- package/src/Avatar/Avatar.stories.tsx +254 -0
- package/src/Avatar/Avatar.test.tsx +143 -0
- package/src/Avatar/Avatar.tsx +270 -0
- package/src/Board/Board.mdx +26 -0
- package/src/Board/Board.stories.tsx +47 -21
- package/src/Board/Board.test.tsx +62 -0
- package/src/Board/BoardLaneList.tsx +52 -1
- package/src/MarkdownEditor/MarkdownEditor.mdx +5 -0
- package/src/index.ts +5 -0
- package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.mdx +11 -0
- package/src/markdownEditorCodeMirror/MarkdownView.mdx +157 -0
- package/src/markdownEditorCodeMirror/MarkdownView.stories.tsx +314 -0
- package/src/markdownEditorCodeMirror/MarkdownView.test.tsx +288 -0
- package/src/markdownEditorCodeMirror/MarkdownView.tsx +373 -0
- package/src/markdownEditorCodeMirror/index.ts +17 -2
- package/src/markdownEditorCodeMirror/livePreview.ts +329 -33
- package/src/markdownEditorCodeMirror/livePreviewRanges.ts +64 -7
- package/src/markdownEditorCodeMirror/safeUrls.test.ts +0 -0
- package/src/markdownEditorCodeMirror/safeUrls.ts +142 -0
- package/src/sourceRules.test.ts +9 -1
|
@@ -15,6 +15,11 @@ stored string.
|
|
|
15
15
|
|
|
16
16
|
<Canvas of={MarkdownEditorStories.Default} />
|
|
17
17
|
|
|
18
|
+
> **Reading rather than editing?** `isReadOnly` here leaves the toolbar on screen, because it
|
|
19
|
+
> means "an editor you cannot type in". A surface with no toolbar in the DOM is
|
|
20
|
+
> [`MarkdownView`](?path=/docs/components-data-markdownview--docs), in the
|
|
21
|
+
> `@charcuterie/ui/markdown-editor-codemirror` subpath.
|
|
22
|
+
|
|
18
23
|
## Props
|
|
19
24
|
|
|
20
25
|
<Controls />
|
package/src/index.ts
CHANGED
|
@@ -63,6 +63,11 @@ export type {
|
|
|
63
63
|
AlertSize,
|
|
64
64
|
} from "./Alert/Alert.tsx"
|
|
65
65
|
export { Alert } from "./Alert/Alert.tsx"
|
|
66
|
+
export type {
|
|
67
|
+
AvatarProps,
|
|
68
|
+
AvatarSize,
|
|
69
|
+
} from "./Avatar/Avatar.tsx"
|
|
70
|
+
export { Avatar } from "./Avatar/Avatar.tsx"
|
|
66
71
|
export type { BadgeProps } from "./Badge/Badge.tsx"
|
|
67
72
|
export { Badge } from "./Badge/Badge.tsx"
|
|
68
73
|
export type { BadgeShapeProps } from "./Badge/useBadgeShape.tsx"
|
|
@@ -22,6 +22,17 @@ import { MarkdownEditorCodeMirror } from "@charcuterie/ui/markdown-editor-codemi
|
|
|
22
22
|
|
|
23
23
|
<Controls />
|
|
24
24
|
|
|
25
|
+
## Reading, not editing
|
|
26
|
+
|
|
27
|
+
`isReadOnly` here means *an editor you cannot type in*: the toolbar stays, drawn in its
|
|
28
|
+
disabled colours above a document nobody is editing. That is right for a form field that is
|
|
29
|
+
temporarily locked, and wrong for reading a file.
|
|
30
|
+
|
|
31
|
+
For reading, take **[`MarkdownView`](?path=/docs/components-data-markdownview--docs)** from
|
|
32
|
+
this same subpath. It has no toolbar in the DOM at all, it is controlled rather than
|
|
33
|
+
uncontrolled, its content is a `role="article"` instead of a `role="textbox"`, and it renders
|
|
34
|
+
**identically** to this component because it is the same code — same range rules, same theme.
|
|
35
|
+
|
|
25
36
|
## Which of the two editors you want
|
|
26
37
|
|
|
27
38
|
`MarkdownEditor` is still the default, and most consumers should keep it.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Canvas, Controls, Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
+
|
|
3
|
+
import * as MarkdownViewStories from "./MarkdownView.stories";
|
|
4
|
+
|
|
5
|
+
<Meta of={MarkdownViewStories} />
|
|
6
|
+
|
|
7
|
+
# MarkdownView
|
|
8
|
+
|
|
9
|
+
Rendered markdown, with **no toolbar in the DOM** and nothing to type into. The same
|
|
10
|
+
document, drawn the same way, as the editor beside it.
|
|
11
|
+
|
|
12
|
+
It is **opt-in**, from the same subpath as the editor:
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { MarkdownView } from "@charcuterie/ui/markdown-editor-codemirror"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
<Canvas of={MarkdownViewStories.Default} />
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
<Controls />
|
|
23
|
+
|
|
24
|
+
## Which of the three you want
|
|
25
|
+
|
|
26
|
+
| | `MarkdownEditor` | `MarkdownEditorCodeMirror` | `MarkdownView` |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| For | typing markdown | typing markdown, rendered | **reading** markdown |
|
|
29
|
+
| Surface | `<textarea>` + painted layer | CodeMirror 6 | CodeMirror 6 |
|
|
30
|
+
| New dependency | none | ~176 KB gz, optional peer | the same one |
|
|
31
|
+
| Toolbar | nine buttons | nine buttons + a switch | **none, ever** |
|
|
32
|
+
| Value | `defaultValue` | `defaultValue` | **`value`, controlled** |
|
|
33
|
+
| Content role | `textbox` | `textbox` | **`article`** |
|
|
34
|
+
|
|
35
|
+
`isReadOnly` on either editor is **not** this. It means *an editor you cannot type in*: the
|
|
36
|
+
nine formatting buttons are still there, still in the accessibility tree, drawn in their
|
|
37
|
+
disabled colours above a document nobody is editing. That is right for a form field that is
|
|
38
|
+
temporarily locked, and wrong for reading a file — which is what this exists for.
|
|
39
|
+
|
|
40
|
+
## No toolbar means no toolbar
|
|
41
|
+
|
|
42
|
+
`queryAllByRole("toolbar")` returns nothing here, and so does `queryAllByRole("button")`.
|
|
43
|
+
That is asserted rather than described, because the workaround it replaces was a CSS rule
|
|
44
|
+
in a consuming app keyed on `role="toolbar"`, and CSS is a poor place to keep a promise:
|
|
45
|
+
`visibility: hidden` leaves the buttons in the accessibility tree, `opacity: 0` leaves them
|
|
46
|
+
in the tab order, and even `display: none` leaves the *overflow menu button* the toolbar
|
|
47
|
+
collapses into at narrow widths.
|
|
48
|
+
|
|
49
|
+
## It renders what the editor renders
|
|
50
|
+
|
|
51
|
+
<Canvas of={MarkdownViewStories.SameAsTheEditor} />
|
|
52
|
+
|
|
53
|
+
One string, two surfaces, side by side. They agree because they **are the same code** —
|
|
54
|
+
`livePreviewRanges.ts` decides what every construct looks like and both call it, over the
|
|
55
|
+
same GFM parse, under the same theme.
|
|
56
|
+
|
|
57
|
+
That is the whole reason this lives in the CodeMirror subpath instead of being a
|
|
58
|
+
`react-markdown` reader in the main barrel. A second renderer agrees on the day it is
|
|
59
|
+
written and drifts afterwards, one construct at a time, and the bug arrives six months later
|
|
60
|
+
as *"the table changed when I saved it"*. There is no second renderer here to drift.
|
|
61
|
+
|
|
62
|
+
The cost is honest and worth stating: a consumer who wants **only** the reader still installs
|
|
63
|
+
CodeMirror. For an app that renders markdown in three places and edits it in one, that is the
|
|
64
|
+
right way round — the dependency is paid once.
|
|
65
|
+
|
|
66
|
+
## It is controlled, and the editors are not
|
|
67
|
+
|
|
68
|
+
`value` in, and that is it. Fetch a file at a different commit hash, pass the new string, and
|
|
69
|
+
the page changes — no `key`, no remount, no stale document sitting above a new title.
|
|
70
|
+
|
|
71
|
+
The editors take `defaultValue` because reassigning an `EditorView`'s document from outside
|
|
72
|
+
throws away the undo history that belonged to it, which is a real cost and is why they ask
|
|
73
|
+
for a `key` instead. A view has no undo history and no caret to protect, so it has nothing to
|
|
74
|
+
lose by simply rendering what it is given.
|
|
75
|
+
|
|
76
|
+
## A `- [ ]` box is inert unless you say where the tick goes
|
|
77
|
+
|
|
78
|
+
<Canvas of={MarkdownViewStories.AllStates} />
|
|
79
|
+
|
|
80
|
+
Pass `onToggleTask` and the checkboxes are operable; leave it out and they render
|
|
81
|
+
**disabled**. The state is still on screen either way — that is a fact about the document
|
|
82
|
+
and belongs there — but nothing pretends the reader can change it.
|
|
83
|
+
|
|
84
|
+
This is the same shape as `onUploadImage` on the editors: omit the handler and the capability
|
|
85
|
+
is simply not offered, which beats offering one that half works. Two of the three surfaces
|
|
86
|
+
this component was built for — a rendered comment, and a repo file fetched at a commit hash —
|
|
87
|
+
have nowhere to put a tick, and a box that visibly toggles and then loses the change on
|
|
88
|
+
reload is worse than one that says up front it cannot.
|
|
89
|
+
|
|
90
|
+
`onToggleTask` hands back the **whole next markdown document**, exactly like the editors'
|
|
91
|
+
`onChange`, so one autosave wires to both. The view applies the tick to its own document
|
|
92
|
+
immediately so the box responds under the pointer; if the consumer rejects the change, the
|
|
93
|
+
next `value` puts it back.
|
|
94
|
+
|
|
95
|
+
## Hostile markdown
|
|
96
|
+
|
|
97
|
+
This will render files fetched from a git host, so the document is untrusted by default.
|
|
98
|
+
|
|
99
|
+
<Canvas of={MarkdownViewStories.HostileMarkdown} />
|
|
100
|
+
|
|
101
|
+
**Raw HTML in the source is never parsed as HTML.** There is no `innerHTML` in this subpath,
|
|
102
|
+
no `dangerouslySetInnerHTML`, and no sanitiser to have a bug in. CodeMirror's state holds the
|
|
103
|
+
source as *text* and every rendered node is `createElement` plus `textContent`, so a
|
|
104
|
+
`<script>` in a fetched README is drawn as the characters `<script>`. It is not stripped,
|
|
105
|
+
not escaped and not "sanitised" — there is simply no code path that would turn it into an
|
|
106
|
+
element.
|
|
107
|
+
|
|
108
|
+
**A URL is checked before it is used as one.** That was the one place the document reached the
|
|
109
|
+
platform, and it was live in the editor too: `[click](javascript:…)` concealed its markup,
|
|
110
|
+
painted as a link, and `window.open` ran it in this origin. Both components now go through an
|
|
111
|
+
allowlist that normalises the way a browser does, so `JaVaScript:` and a tab in the middle of
|
|
112
|
+
the word are the same URL to it. A rejected link is **left as source** — brackets, parentheses
|
|
113
|
+
and all — rather than repainted as a link-coloured word that silently refuses to work.
|
|
114
|
+
Images take `http:`, `https:` and `data:image/*` only; `data:text/html` is not an image.
|
|
115
|
+
|
|
116
|
+
**The reader is not a beacon.** Remote images carry `referrerpolicy="no-referrer"`, so an
|
|
117
|
+
image in somebody else's file cannot learn which page loaded it.
|
|
118
|
+
|
|
119
|
+
What is *not* claimed: nothing here stops a document linking somewhere unpleasant, and the
|
|
120
|
+
images the markdown names are still fetched — that is what rendering them means.
|
|
121
|
+
|
|
122
|
+
## Accessibility
|
|
123
|
+
|
|
124
|
+
The content is `role="article"`, not the `role="textbox"` CodeMirror writes by default, and
|
|
125
|
+
that swap is the difference between reading a document and being trapped in a text field: a
|
|
126
|
+
screen reader entering a textbox switches to forms mode and reads the contents as one flat
|
|
127
|
+
string, with no heading navigation and no table navigation.
|
|
128
|
+
|
|
129
|
+
So the structure has to be real, and two pieces of it are:
|
|
130
|
+
|
|
131
|
+
- **Headings carry `role="heading"` and an `aria-level`**, so the H key steps between
|
|
132
|
+
sections. The editor deliberately has none of this — ARIA flattens a textbox's contents, so
|
|
133
|
+
a heading role inside one would be a decoration that lies about what it does.
|
|
134
|
+
- **Links are real `<a href>` elements**, in the tab order, activated by Enter, with an
|
|
135
|
+
address the browser will show and copy. The editor cannot have these: an anchor inside a
|
|
136
|
+
`contenteditable` competes with the caret for its line, which is why the editor paints a
|
|
137
|
+
span and translates a `mousedown` instead — and why clicking link text *there* cannot place
|
|
138
|
+
a caret.
|
|
139
|
+
|
|
140
|
+
<Canvas of={MarkdownViewStories.Interactive} />
|
|
141
|
+
|
|
142
|
+
**The document itself is not a tab stop.** There is nothing in it to operate, and a reading
|
|
143
|
+
surface that swallowed a Tab would be one more thing between the reader and the next control.
|
|
144
|
+
Every link inside it is reachable, in document order.
|
|
145
|
+
|
|
146
|
+
List structure is **visual only**, and worth knowing rather than discovering: the `-` bullet
|
|
147
|
+
is drawn as a dimmed marker rather than concealed, the same way it is in the editor, so a
|
|
148
|
+
list reads as a list on screen but is not a `<ul>` in the tree. Tables are real `<table>`
|
|
149
|
+
elements with real `<th>`s, because the table widget builds one.
|
|
150
|
+
|
|
151
|
+
## The Narrow View
|
|
152
|
+
|
|
153
|
+
<Canvas of={MarkdownViewStories.Responsive} />
|
|
154
|
+
|
|
155
|
+
A table too wide for its column scrolls **inside its own block** rather than pushing the
|
|
156
|
+
column sideways, and an image is capped at the width it is given. Nothing here is keyed to a
|
|
157
|
+
viewport width; a column in a three-up board is narrow on a 4K display.
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import { useState } from "react"
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ContainerBoard,
|
|
6
|
+
StoryCell,
|
|
7
|
+
StoryGrid,
|
|
8
|
+
} from "../board.storyHelpers.tsx"
|
|
9
|
+
import { Card } from "../Card/Card.tsx"
|
|
10
|
+
import { MarkdownEditorCodeMirror } from "./MarkdownEditorCodeMirror.tsx"
|
|
11
|
+
import { MarkdownView } from "./MarkdownView.tsx"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Invented, like every fixture in this repo — a task in a made-up
|
|
15
|
+
* homelab project, and deliberately the **same string** the editor's
|
|
16
|
+
* stories use.
|
|
17
|
+
*
|
|
18
|
+
* That is what makes the `SameAsTheEditor` story an argument rather
|
|
19
|
+
* than an illustration: the two surfaces are handed one document and
|
|
20
|
+
* a reader compares the frames.
|
|
21
|
+
*/
|
|
22
|
+
const SAMPLE = `# Rack move, phase two
|
|
23
|
+
|
|
24
|
+
The **short** version: pull the _old_ switch, land the new one, and
|
|
25
|
+
keep the ~~three~~ two uplinks live throughout.
|
|
26
|
+
|
|
27
|
+
## Before the window
|
|
28
|
+
|
|
29
|
+
- [x] Label every patch lead
|
|
30
|
+
- [ ] Photograph the current cabling
|
|
31
|
+
- [ ] Cut over at 02:00
|
|
32
|
+
|
|
33
|
+
1. Power down the shelf
|
|
34
|
+
2. Swap the rails
|
|
35
|
+
3. Bring it back up
|
|
36
|
+
|
|
37
|
+
> Nothing here touches the storage pool.
|
|
38
|
+
|
|
39
|
+
Run \`ping -c 3 gateway.invalid\` before and after, and read
|
|
40
|
+
[the runbook](https://example.invalid/runbook) first.
|
|
41
|
+
|
|
42
|
+
| Port | Goes to |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| 1 | uplink |
|
|
45
|
+
| 2 | shelf |
|
|
46
|
+
|
|
47
|
+
\`\`\`sh
|
|
48
|
+
ip -br addr show
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
Replacement rails ordered from https://example.invalid/product?id=1234
|
|
52
|
+
`
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A drawing rather than a fetched file, so the story needs no
|
|
56
|
+
* network and the frame is the same on every machine. The same
|
|
57
|
+
* technique `Avatar`'s portrait uses, and one of the few things
|
|
58
|
+
* `data:` is genuinely good for.
|
|
59
|
+
*/
|
|
60
|
+
const DIAGRAM_URL = `data:image/svg+xml;utf8,${encodeURIComponent(
|
|
61
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 60"><rect width="240" height="60" rx="6" fill="#2F6F4E"/><rect x="12" y="14" width="96" height="32" rx="4" fill="#F2E8CF"/><rect x="132" y="14" width="96" height="32" rx="4" fill="#F2E8CF"/><path d="M108 30h24" stroke="#F2E8CF" stroke-width="4"/></svg>',
|
|
62
|
+
)}`
|
|
63
|
+
|
|
64
|
+
const WITH_IMAGE = `## Rack inventory
|
|
65
|
+
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
| Port | Goes to | **Speed** |
|
|
69
|
+
| :--- | :---: | ---: |
|
|
70
|
+
| 1 | uplink | 10G |
|
|
71
|
+
| 2 | shelf | 1G |
|
|
72
|
+
`
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The document nobody in this house wrote: what a fetched file looks
|
|
76
|
+
* like when its author would rather it did something else.
|
|
77
|
+
*
|
|
78
|
+
* Every line here is a thing that used to work. The `javascript:`
|
|
79
|
+
* link opened and ran; the `<script>` was never a script but is
|
|
80
|
+
* worth showing anyway; the `data:text/html` image was an `src` this
|
|
81
|
+
* component wrote verbatim.
|
|
82
|
+
*/
|
|
83
|
+
const HOSTILE = `# A file fetched from somewhere else
|
|
84
|
+
|
|
85
|
+
[Looks like a link](javascript:alert(document.domain)) — and is not one.
|
|
86
|
+
|
|
87
|
+
<script>alert("this is four coloured characters, not a script")</script>
|
|
88
|
+
|
|
89
|
+
<img src=x onerror="alert(1)">
|
|
90
|
+
|
|
91
|
+
</script>)
|
|
92
|
+
|
|
93
|
+
[This one is fine](https://example.invalid/runbook), and so is ./RELEASING.md.
|
|
94
|
+
`
|
|
95
|
+
|
|
96
|
+
const CHECKLIST = `## Before the window
|
|
97
|
+
|
|
98
|
+
- [x] Label every patch lead
|
|
99
|
+
- [ ] Photograph the current cabling
|
|
100
|
+
- [ ] Cut over at 02:00
|
|
101
|
+
`
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A second list rather than the same one twice, and the difference
|
|
105
|
+
* is load bearing: two checkboxes with the same accessible name on
|
|
106
|
+
* one page are two things `getByRole("checkbox", { name })` cannot
|
|
107
|
+
* tell apart — which is the drivability property this library gates
|
|
108
|
+
* on, failing in a *story* rather than in a component.
|
|
109
|
+
*/
|
|
110
|
+
const TICKABLE_CHECKLIST = `## After the window
|
|
111
|
+
|
|
112
|
+
- [x] Bring the shelf back up
|
|
113
|
+
- [ ] Re-check both uplinks
|
|
114
|
+
- [ ] Close the change record
|
|
115
|
+
`
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The state the checkbox story cannot show without one: a tick that
|
|
119
|
+
* goes somewhere.
|
|
120
|
+
*
|
|
121
|
+
* `onToggleTask` hands back the whole next markdown document, which
|
|
122
|
+
* is the same shape the editors' `onChange` uses — so a consumer
|
|
123
|
+
* wires one autosave and points both at it.
|
|
124
|
+
*/
|
|
125
|
+
const TickableChecklist = () => {
|
|
126
|
+
const [value, setValue] = useState(TICKABLE_CHECKLIST)
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<MarkdownView
|
|
130
|
+
label="Tickable checklist"
|
|
131
|
+
onToggleTask={setValue}
|
|
132
|
+
value={value}
|
|
133
|
+
/>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const meta = {
|
|
138
|
+
title: "Components/Data/MarkdownView",
|
|
139
|
+
component: MarkdownView,
|
|
140
|
+
parameters: { layout: "padded" },
|
|
141
|
+
args: {
|
|
142
|
+
label: "Description",
|
|
143
|
+
onToggleTask: undefined,
|
|
144
|
+
value: SAMPLE,
|
|
145
|
+
},
|
|
146
|
+
} satisfies Meta<typeof MarkdownView>
|
|
147
|
+
|
|
148
|
+
export default meta
|
|
149
|
+
|
|
150
|
+
type Story = StoryObj<typeof meta>
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The whole point in one frame: the document is drawn, and there is
|
|
154
|
+
* **no toolbar above it**. Not a disabled one, not a hidden one —
|
|
155
|
+
* `getAllByRole("toolbar")` returns nothing, because nothing in this
|
|
156
|
+
* component can render one.
|
|
157
|
+
*/
|
|
158
|
+
export const Default: Story = {}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The same string, in both surfaces, side by side.
|
|
162
|
+
*
|
|
163
|
+
* This is the requirement the component exists to meet, and it is
|
|
164
|
+
* the one a screenshot can actually check: the heading is the same
|
|
165
|
+
* size, the table has the same borders and the same alignment, the
|
|
166
|
+
* checkboxes are in the same places, and the bare URL is a link in
|
|
167
|
+
* both. They agree because they are the same code — one parser, one
|
|
168
|
+
* set of range rules, one theme — rather than because somebody kept
|
|
169
|
+
* two renderers in step.
|
|
170
|
+
*/
|
|
171
|
+
export const SameAsTheEditor: Story = {
|
|
172
|
+
render: (viewProps) => (
|
|
173
|
+
<StoryGrid columns={2}>
|
|
174
|
+
<StoryCell align="stretch" label="MarkdownView">
|
|
175
|
+
<MarkdownView {...viewProps} />
|
|
176
|
+
</StoryCell>
|
|
177
|
+
|
|
178
|
+
<StoryCell
|
|
179
|
+
align="stretch"
|
|
180
|
+
label="MarkdownEditorCodeMirror"
|
|
181
|
+
>
|
|
182
|
+
<MarkdownEditorCodeMirror
|
|
183
|
+
defaultValue={SAMPLE}
|
|
184
|
+
label="Description"
|
|
185
|
+
/>
|
|
186
|
+
</StoryCell>
|
|
187
|
+
</StoryGrid>
|
|
188
|
+
),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Everything the reader can be shown, including the two states that
|
|
193
|
+
* are easy to get wrong: an empty document, and a checkbox nobody
|
|
194
|
+
* may tick.
|
|
195
|
+
*/
|
|
196
|
+
export const AllStates: Story = {
|
|
197
|
+
render: (viewProps) => (
|
|
198
|
+
<StoryGrid columns={2}>
|
|
199
|
+
<StoryCell
|
|
200
|
+
align="stretch"
|
|
201
|
+
label="checkboxes are inert without onToggleTask"
|
|
202
|
+
>
|
|
203
|
+
<MarkdownView
|
|
204
|
+
{...viewProps}
|
|
205
|
+
label="Read-only checklist"
|
|
206
|
+
value={CHECKLIST}
|
|
207
|
+
/>
|
|
208
|
+
</StoryCell>
|
|
209
|
+
|
|
210
|
+
<StoryCell
|
|
211
|
+
align="stretch"
|
|
212
|
+
label="…and operable with one"
|
|
213
|
+
>
|
|
214
|
+
<TickableChecklist />
|
|
215
|
+
</StoryCell>
|
|
216
|
+
|
|
217
|
+
<StoryCell
|
|
218
|
+
align="stretch"
|
|
219
|
+
label="an image and a table"
|
|
220
|
+
>
|
|
221
|
+
<MarkdownView
|
|
222
|
+
{...viewProps}
|
|
223
|
+
label="Rack inventory"
|
|
224
|
+
value={WITH_IMAGE}
|
|
225
|
+
/>
|
|
226
|
+
</StoryCell>
|
|
227
|
+
|
|
228
|
+
<StoryCell align="stretch" label="an empty document">
|
|
229
|
+
<MarkdownView
|
|
230
|
+
{...viewProps}
|
|
231
|
+
label="Nothing written yet"
|
|
232
|
+
value=""
|
|
233
|
+
/>
|
|
234
|
+
</StoryCell>
|
|
235
|
+
</StoryGrid>
|
|
236
|
+
),
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* A file from a git host, rendering as the characters it contains.
|
|
241
|
+
*
|
|
242
|
+
* The `javascript:` link keeps its brackets and its parentheses on
|
|
243
|
+
* purpose. A link-coloured word that silently refuses to work tells
|
|
244
|
+
* the reader nothing; the source tells them exactly what the file
|
|
245
|
+
* tried to do.
|
|
246
|
+
*/
|
|
247
|
+
export const HostileMarkdown: Story = {
|
|
248
|
+
args: {
|
|
249
|
+
label: "README.md at 9f3c1ab",
|
|
250
|
+
value: HOSTILE,
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* The three container widths. A table that will not fit scrolls
|
|
256
|
+
* inside its own block rather than pushing the column sideways, and
|
|
257
|
+
* an image is capped at the width it is given — which is what keeps
|
|
258
|
+
* the Narrow View readable instead of merely small.
|
|
259
|
+
*/
|
|
260
|
+
export const Responsive: Story = {
|
|
261
|
+
render: (viewProps) => (
|
|
262
|
+
<ContainerBoard>
|
|
263
|
+
{(width) => (
|
|
264
|
+
<Card heading={`Rack inventory (${width})`}>
|
|
265
|
+
<MarkdownView
|
|
266
|
+
{...viewProps}
|
|
267
|
+
label={`Rack inventory at ${width}`}
|
|
268
|
+
value={WITH_IMAGE}
|
|
269
|
+
/>
|
|
270
|
+
</Card>
|
|
271
|
+
)}
|
|
272
|
+
</ContainerBoard>
|
|
273
|
+
),
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The complete keyboard path, which for a reader is short and is the
|
|
278
|
+
* point.
|
|
279
|
+
*
|
|
280
|
+
* The document itself is **not** a tab stop — there is nothing in it
|
|
281
|
+
* to operate, and a reading surface that swallowed a Tab would be
|
|
282
|
+
* one more thing between the reader and the next control. What *is*
|
|
283
|
+
* reachable is every link in it, in document order, because they are
|
|
284
|
+
* real anchors rather than painted spans: Tab to one, Enter to open
|
|
285
|
+
* it, and the context menu will copy its address.
|
|
286
|
+
*
|
|
287
|
+
* The buttons on either side are there so the path is visible — Tab
|
|
288
|
+
* from the first lands on each link in turn and ends on the second.
|
|
289
|
+
*/
|
|
290
|
+
export const Interactive: Story = {
|
|
291
|
+
args: {
|
|
292
|
+
label: "Rack move, phase two",
|
|
293
|
+
value: SAMPLE,
|
|
294
|
+
},
|
|
295
|
+
render: (viewProps) => (
|
|
296
|
+
<div className="flex flex-col items-start gap-3">
|
|
297
|
+
<button
|
|
298
|
+
className="rounded-md border border-border-default px-3 py-1 text-content-primary text-sm"
|
|
299
|
+
type="button"
|
|
300
|
+
>
|
|
301
|
+
{"Before"}
|
|
302
|
+
</button>
|
|
303
|
+
|
|
304
|
+
<MarkdownView {...viewProps} />
|
|
305
|
+
|
|
306
|
+
<button
|
|
307
|
+
className="rounded-md border border-border-default px-3 py-1 text-content-primary text-sm"
|
|
308
|
+
type="button"
|
|
309
|
+
>
|
|
310
|
+
{"After"}
|
|
311
|
+
</button>
|
|
312
|
+
</div>
|
|
313
|
+
),
|
|
314
|
+
}
|