@brett_lamy/docstream 1.17.0 → 1.18.1
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/README.md +25 -0
- package/dist/docs/DocsRenderer.d.ts.map +1 -1
- package/dist/docs/DocsRenderer.js +30 -3
- package/dist/docs/DocsRenderer.js.map +1 -1
- package/dist/docs/ImageThumbnail.d.ts +18 -0
- package/dist/docs/ImageThumbnail.d.ts.map +1 -0
- package/dist/docs/ImageThumbnail.js +33 -0
- package/dist/docs/ImageThumbnail.js.map +1 -0
- package/dist/gitbook/index.d.ts +1 -0
- package/dist/gitbook/index.d.ts.map +1 -1
- package/dist/gitbook/index.js +1 -0
- package/dist/gitbook/index.js.map +1 -1
- package/dist/gitbook/parse.d.ts.map +1 -1
- package/dist/gitbook/parse.js +3 -2
- package/dist/gitbook/parse.js.map +1 -1
- package/dist/gitbook/serialize.d.ts.map +1 -1
- package/dist/gitbook/serialize.js +2 -2
- package/dist/gitbook/serialize.js.map +1 -1
- package/dist/gitbook/thumbnail.d.ts +10 -0
- package/dist/gitbook/thumbnail.d.ts.map +1 -0
- package/dist/gitbook/thumbnail.js +18 -0
- package/dist/gitbook/thumbnail.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +146 -0
- package/package.json +1 -1
- package/src/docs/DocsRenderer.tsx +54 -7
- package/src/docs/ImageThumbnail.tsx +49 -0
- package/src/gitbook/index.ts +1 -0
- package/src/gitbook/parse.ts +2 -1
- package/src/gitbook/serialize.ts +2 -1
- package/src/gitbook/thumbnail.ts +20 -0
- package/src/index.ts +1 -0
- package/src/styles.css +146 -0
package/dist/styles.css
CHANGED
|
@@ -443,6 +443,34 @@
|
|
|
443
443
|
margin: 4px 0 6px;
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
+
/* A step's body starts at the top, so a step without a title has its first line beside its number. */
|
|
447
|
+
:is(.docs-step-main, .docs-step-media-body) > [data-docstream-blocks] > :first-child {
|
|
448
|
+
margin-top: 0;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.docs-step-main > [data-docstream-blocks]:first-child {
|
|
452
|
+
padding-top: 2px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/* A step whose heading opens with a thumbnail: the image in its own column, the title and body beside it. */
|
|
456
|
+
.docs-step-media {
|
|
457
|
+
display: flex;
|
|
458
|
+
align-items: flex-start;
|
|
459
|
+
gap: 16px;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.docs-step-media-body {
|
|
463
|
+
flex: 1;
|
|
464
|
+
min-width: 0;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
@media (max-width: 560px) {
|
|
468
|
+
.docs-step-media {
|
|
469
|
+
flex-direction: column;
|
|
470
|
+
gap: 8px;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
446
474
|
.docs-embed {
|
|
447
475
|
width: 100%;
|
|
448
476
|
aspect-ratio: 16 / 9;
|
|
@@ -1712,6 +1740,124 @@
|
|
|
1712
1740
|
text-decoration: none !important;
|
|
1713
1741
|
}
|
|
1714
1742
|
|
|
1743
|
+
/* Thumbnail images (``): a small frame with a badge; hover or focus
|
|
1744
|
+
opens a card with the full image. First in a paragraph, the text runs beside it. */
|
|
1745
|
+
/* Float, not flex: the text keeps its own first line, so a list marker lines up with it. */
|
|
1746
|
+
.docs-media {
|
|
1747
|
+
display: flow-root;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
.docs-media > .docs-thumb,
|
|
1751
|
+
.docs-media > .docs-thumb-link {
|
|
1752
|
+
float: left;
|
|
1753
|
+
margin: 3px 14px 4px 0;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
.docs-media-body {
|
|
1757
|
+
display: block;
|
|
1758
|
+
overflow: hidden;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
.docs-thumb-link {
|
|
1762
|
+
text-decoration: none !important;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
.docs-thumb {
|
|
1766
|
+
position: relative;
|
|
1767
|
+
display: inline-block;
|
|
1768
|
+
flex: none;
|
|
1769
|
+
vertical-align: top;
|
|
1770
|
+
border-radius: 10px;
|
|
1771
|
+
outline: none;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
.docs-thumb-frame {
|
|
1775
|
+
position: relative;
|
|
1776
|
+
display: block;
|
|
1777
|
+
width: var(--docs-thumb-width, 168px);
|
|
1778
|
+
aspect-ratio: 16 / 10;
|
|
1779
|
+
overflow: hidden;
|
|
1780
|
+
border: 1px solid var(--gb-border);
|
|
1781
|
+
border-radius: 10px;
|
|
1782
|
+
background: var(--gb-muted, #f4f4f5);
|
|
1783
|
+
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
|
|
1784
|
+
cursor: zoom-in;
|
|
1785
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
.docs-thumb-frame img {
|
|
1789
|
+
display: block;
|
|
1790
|
+
width: 100%;
|
|
1791
|
+
height: 100%;
|
|
1792
|
+
object-fit: cover;
|
|
1793
|
+
object-position: top left;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
.docs-thumb:hover .docs-thumb-frame,
|
|
1797
|
+
.docs-thumb:focus-visible .docs-thumb-frame {
|
|
1798
|
+
border-color: var(--gb-panel-foreground, #18181b);
|
|
1799
|
+
box-shadow: 0 0 0 2px var(--gb-border);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
.docs-thumb-badge {
|
|
1803
|
+
position: absolute;
|
|
1804
|
+
right: 7px;
|
|
1805
|
+
bottom: 7px;
|
|
1806
|
+
padding: 2px 7px;
|
|
1807
|
+
border-radius: 6px;
|
|
1808
|
+
color: #f4f4f5;
|
|
1809
|
+
background: rgba(39, 39, 42, 0.78);
|
|
1810
|
+
font: 500 12px/1.4 var(--gb-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
1811
|
+
font-variant-numeric: tabular-nums;
|
|
1812
|
+
pointer-events: none;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
.docs-thumb-card {
|
|
1816
|
+
position: absolute;
|
|
1817
|
+
top: calc(100% + 10px);
|
|
1818
|
+
left: 0;
|
|
1819
|
+
z-index: 60;
|
|
1820
|
+
display: block;
|
|
1821
|
+
width: min(var(--docs-thumb-card-width, 720px), calc(100vw - 32px));
|
|
1822
|
+
overflow: hidden;
|
|
1823
|
+
padding: 10px;
|
|
1824
|
+
border: 1px solid var(--gb-border);
|
|
1825
|
+
border-radius: 16px;
|
|
1826
|
+
background: var(--gb-panel);
|
|
1827
|
+
box-shadow: 0 18px 48px rgba(15, 23, 42, 0.22), 0 2px 8px rgba(15, 23, 42, 0.1);
|
|
1828
|
+
opacity: 0;
|
|
1829
|
+
visibility: hidden;
|
|
1830
|
+
transform: translateY(-4px);
|
|
1831
|
+
transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s;
|
|
1832
|
+
pointer-events: none;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
.docs-thumb-card img {
|
|
1836
|
+
display: block;
|
|
1837
|
+
width: 100%;
|
|
1838
|
+
height: auto;
|
|
1839
|
+
border-radius: 8px;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
.docs-thumb-card .docs-thumb-badge {
|
|
1843
|
+
right: 18px;
|
|
1844
|
+
bottom: 18px;
|
|
1845
|
+
font-size: 14px;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
.docs-thumb:hover .docs-thumb-card,
|
|
1849
|
+
.docs-thumb:focus-visible .docs-thumb-card {
|
|
1850
|
+
opacity: 1;
|
|
1851
|
+
visibility: visible;
|
|
1852
|
+
transform: translateY(0);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1856
|
+
.docs-thumb-card {
|
|
1857
|
+
transition: none;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1715
1861
|
.oas {
|
|
1716
1862
|
border: 1px solid var(--gb-border);
|
|
1717
1863
|
border-radius: 12px;
|
package/package.json
CHANGED
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
XCircle,
|
|
11
11
|
} from "lucide-react"
|
|
12
12
|
|
|
13
|
-
import type { Block, CodeBlockNode, CommandNode, DocumentNode, Inline, PackageManager, SourceRefNode } from "../gitbook/ast"
|
|
13
|
+
import type { Block, CodeBlockNode, CommandNode, DocumentNode, Inline, PackageManager, SourceRefNode, StepNode } from "../gitbook/ast"
|
|
14
14
|
import { PACKAGE_MANAGERS, packageManagerCommands } from "../gitbook/package-managers"
|
|
15
15
|
import { slugify } from "../gitbook/outline"
|
|
16
16
|
import { resolveAsset, useAsset } from "../assets"
|
|
17
|
-
import { parseMarkdown } from "../gitbook/parse"
|
|
17
|
+
import { parseBlocks, parseMarkdown } from "../gitbook/parse"
|
|
18
18
|
import { serializeMarkdown } from "../gitbook/serialize"
|
|
19
19
|
import { DocstreamDemoContext, DocstreamDemoRuntimeContext, DocstreamDemoVFSContext } from "../demo/context"
|
|
20
20
|
import type { DemoVFS } from "../demo/vfs"
|
|
@@ -36,6 +36,7 @@ import { OpenApiOperation } from "../openapi/OpenApiOperation"
|
|
|
36
36
|
import { Mermaid } from "./Mermaid"
|
|
37
37
|
import { HighlightedCode } from "./HighlightedCode"
|
|
38
38
|
import { CitationSources, InlineReference } from "./reference"
|
|
39
|
+
import { ImageThumbnail, leadingThumbnail, thumbnailLabel } from "./ImageThumbnail"
|
|
39
40
|
import { DocumentProperties } from "./DocumentProperties"
|
|
40
41
|
import { TaskCheckbox, TaskStateChangeContext, type TaskStateChangeHandler } from "./TaskCheckbox"
|
|
41
42
|
import { getTaskState } from "../gitbook/tasks"
|
|
@@ -103,6 +104,8 @@ function InlineText({ nodes }: { nodes: Inline[] }) {
|
|
|
103
104
|
if (!emphasized && !n.link) return <InlineReference key={i} node={n} />
|
|
104
105
|
el = <InlineReference node={n} />
|
|
105
106
|
} else if (n.type === "image") {
|
|
107
|
+
const label = thumbnailLabel(n)
|
|
108
|
+
if (label !== null) return <ImageThumbnail key={i} image={n} label={label} />
|
|
106
109
|
const img = (
|
|
107
110
|
<AssetImg
|
|
108
111
|
url={n.src}
|
|
@@ -334,12 +337,25 @@ function Tabs({ block, liveRenderer, sourceRenderer }: { block: Extract<Block, {
|
|
|
334
337
|
function BlockView({ block, liveRenderer, sourceRenderer }: { block: Block } & Renderers) {
|
|
335
338
|
const onTaskStateChange = useContext(TaskStateChangeContext)
|
|
336
339
|
switch (block.type) {
|
|
337
|
-
case "paragraph":
|
|
340
|
+
case "paragraph": {
|
|
341
|
+
// A paragraph that opens with a thumbnail sets its text beside it.
|
|
342
|
+
const lead = leadingThumbnail(block.children)
|
|
343
|
+
if (lead) {
|
|
344
|
+
return (
|
|
345
|
+
<p className="docs-media">
|
|
346
|
+
<ImageThumbnail image={lead.image} label={lead.label} />
|
|
347
|
+
<span className="docs-media-body">
|
|
348
|
+
<InlineText nodes={lead.rest} />
|
|
349
|
+
</span>
|
|
350
|
+
</p>
|
|
351
|
+
)
|
|
352
|
+
}
|
|
338
353
|
return (
|
|
339
354
|
<p>
|
|
340
355
|
<InlineText nodes={block.children} />
|
|
341
356
|
</p>
|
|
342
357
|
)
|
|
358
|
+
}
|
|
343
359
|
case "heading": {
|
|
344
360
|
const Tag = `h${block.level}` as const
|
|
345
361
|
return (
|
|
@@ -426,10 +442,7 @@ function BlockView({ block, liveRenderer, sourceRenderer }: { block: Block } & R
|
|
|
426
442
|
<div className="docs-step-badge">{i + 1}</div>
|
|
427
443
|
{i < block.steps.length - 1 && <div className="docs-step-line" />}
|
|
428
444
|
</div>
|
|
429
|
-
<
|
|
430
|
-
{s.title && <div className="docs-step-title">{s.title}</div>}
|
|
431
|
-
<Blocks blocks={s.children} liveRenderer={liveRenderer} sourceRenderer={sourceRenderer} />
|
|
432
|
-
</div>
|
|
445
|
+
<StepMain step={s} liveRenderer={liveRenderer} sourceRenderer={sourceRenderer} />
|
|
433
446
|
</div>
|
|
434
447
|
))}
|
|
435
448
|
</div>
|
|
@@ -601,6 +614,40 @@ function isReactLanguage(language: string | null): boolean {
|
|
|
601
614
|
return language === "jsx" || language === "tsx" || language === "react"
|
|
602
615
|
}
|
|
603
616
|
|
|
617
|
+
/**
|
|
618
|
+
* A step's title and body. A title heading that opens with a thumbnail image puts the image in a column
|
|
619
|
+
* of its own, with the title and the body beside it.
|
|
620
|
+
*/
|
|
621
|
+
function StepMain({ step, liveRenderer, sourceRenderer }: { step: StepNode } & Renderers) {
|
|
622
|
+
const lead = useMemo(() => {
|
|
623
|
+
if (!step.heading) return null
|
|
624
|
+
const [heading] = parseBlocks(step.heading.split("\n").filter((line) => line.trim()))
|
|
625
|
+
return heading?.type === "heading" ? leadingThumbnail(heading.children) : null
|
|
626
|
+
}, [step.heading])
|
|
627
|
+
const body = <Blocks blocks={step.children} liveRenderer={liveRenderer} sourceRenderer={sourceRenderer} />
|
|
628
|
+
if (!lead) {
|
|
629
|
+
return (
|
|
630
|
+
<div className="docs-step-main">
|
|
631
|
+
{step.title && <div className="docs-step-title">{step.title}</div>}
|
|
632
|
+
{body}
|
|
633
|
+
</div>
|
|
634
|
+
)
|
|
635
|
+
}
|
|
636
|
+
return (
|
|
637
|
+
<div className="docs-step-main docs-step-media">
|
|
638
|
+
<ImageThumbnail image={lead.image} label={lead.label} />
|
|
639
|
+
<div className="docs-step-media-body">
|
|
640
|
+
{step.title && (
|
|
641
|
+
<div className="docs-step-title">
|
|
642
|
+
<InlineText nodes={lead.rest} />
|
|
643
|
+
</div>
|
|
644
|
+
)}
|
|
645
|
+
{body}
|
|
646
|
+
</div>
|
|
647
|
+
</div>
|
|
648
|
+
)
|
|
649
|
+
}
|
|
650
|
+
|
|
604
651
|
function Blocks({ blocks, inline, liveRenderer, sourceRenderer }: { blocks: Block[]; inline?: boolean } & Renderers) {
|
|
605
652
|
return (
|
|
606
653
|
<div
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useId } from "react"
|
|
2
|
+
import type { Inline, InlineImageNode } from "../gitbook/ast"
|
|
3
|
+
import { useAsset } from "../asset-resolver"
|
|
4
|
+
import { thumbnailLabel } from "../gitbook/thumbnail"
|
|
5
|
+
|
|
6
|
+
export { thumbnailLabel }
|
|
7
|
+
|
|
8
|
+
/** The paragraph's leading thumbnail (whitespace before it ignored) and the inlines after it. */
|
|
9
|
+
export function leadingThumbnail(nodes: Inline[]): { image: InlineImageNode; label: string; rest: Inline[] } | null {
|
|
10
|
+
const at = nodes.findIndex((n) => !(n.type === "text" && !n.text.trim()))
|
|
11
|
+
const first = nodes[at]
|
|
12
|
+
if (!first || first.type !== "image") return null
|
|
13
|
+
const label = thumbnailLabel(first)
|
|
14
|
+
if (label === null) return null
|
|
15
|
+
const rest = nodes.slice(at + 1)
|
|
16
|
+
// Drop the space that separated the image from the text.
|
|
17
|
+
if (rest[0]?.type === "text") rest[0] = { ...rest[0], text: rest[0].text.replace(/^\s+/, "") }
|
|
18
|
+
return { image: first, label, rest }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A small framed image with an optional badge (a timestamp, say); hovering or focusing it opens a card
|
|
23
|
+
* with the full image. First in a paragraph or list item, the text runs beside it.
|
|
24
|
+
*/
|
|
25
|
+
export function ImageThumbnail({ image, label }: { image: InlineImageNode; label: string }) {
|
|
26
|
+
const src = useAsset(image.src)
|
|
27
|
+
const cardId = useId()
|
|
28
|
+
const alt = image.alt ?? ""
|
|
29
|
+
const badge = label ? <span className="docs-thumb-badge">{label}</span> : null
|
|
30
|
+
const thumb = (
|
|
31
|
+
<span className="docs-thumb" tabIndex={0} role="img" aria-label={alt || label || "Image"} aria-describedby={cardId}>
|
|
32
|
+
<span className="docs-thumb-frame">
|
|
33
|
+
<img src={src} alt="" loading="lazy" draggable={false} />
|
|
34
|
+
{badge}
|
|
35
|
+
</span>
|
|
36
|
+
<span className="docs-thumb-card" id={cardId} role="tooltip">
|
|
37
|
+
<img src={src} alt={alt} loading="lazy" />
|
|
38
|
+
{badge}
|
|
39
|
+
</span>
|
|
40
|
+
</span>
|
|
41
|
+
)
|
|
42
|
+
return image.link ? (
|
|
43
|
+
<a href={image.link} target="_blank" rel="noreferrer" className="docs-thumb-link">
|
|
44
|
+
{thumb}
|
|
45
|
+
</a>
|
|
46
|
+
) : (
|
|
47
|
+
thumb
|
|
48
|
+
)
|
|
49
|
+
}
|
package/src/gitbook/index.ts
CHANGED
|
@@ -12,3 +12,4 @@ export { PACKAGE_MANAGERS, packageManagerCommands } from "./package-managers"
|
|
|
12
12
|
export { flattenBlocks, flattenForPlainMarkdown } from "./flatten"
|
|
13
13
|
export { documentOutline, slugify } from "./outline"
|
|
14
14
|
export type { OutlineEntry } from "./outline"
|
|
15
|
+
export { stepTitleText, thumbnailLabel } from "./thumbnail"
|
package/src/gitbook/parse.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stepTitleText } from "./thumbnail"
|
|
1
2
|
import type {
|
|
2
3
|
Block,
|
|
3
4
|
CodeBlockNode,
|
|
@@ -1081,7 +1082,7 @@ function parseSteps(lines: string[]): { items: StepNode[]; end: number } {
|
|
|
1081
1082
|
const heading = inner[0]?.type === "heading" ? inner[0] : undefined
|
|
1082
1083
|
const step: StepNode = { type: "step", title, children: inner }
|
|
1083
1084
|
if (heading) {
|
|
1084
|
-
step.title =
|
|
1085
|
+
step.title = stepTitleText(heading.children)
|
|
1085
1086
|
inner.shift()
|
|
1086
1087
|
// The title heading as written (level, formatting, blank lines before it), where not `### title`.
|
|
1087
1088
|
const written = body.slice(0, (heading.gap ?? 0) + (heading.setext ? 2 : 1)).join("\n")
|
package/src/gitbook/serialize.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stepTitleText } from "./thumbnail"
|
|
1
2
|
import type {
|
|
2
3
|
Block,
|
|
3
4
|
CitationDef,
|
|
@@ -94,7 +95,7 @@ function tagsFor(b: TaggedNode): [string, string] {
|
|
|
94
95
|
function stepHeading(s: StepNode): string {
|
|
95
96
|
if (s.heading !== undefined) {
|
|
96
97
|
const again = parseBlocks(s.heading.split("\n"))
|
|
97
|
-
if (again.length === 1 && again[0].type === "heading" &&
|
|
98
|
+
if (again.length === 1 && again[0].type === "heading" && stepTitleText(again[0].children) === s.title) return s.heading
|
|
98
99
|
}
|
|
99
100
|
return s.title ? `### ${s.title}` : ""
|
|
100
101
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Inline } from "./ast"
|
|
2
|
+
import { plainText } from "./inline"
|
|
3
|
+
|
|
4
|
+
const THUMBNAIL = /^thumbnail(?:\s*:\s*(.*))?$/i
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The badge of a thumbnail image, or `null` when the image isn't one. An image is a thumbnail when its
|
|
8
|
+
* Markdown title starts with `thumbnail`: ``, or with a badge,
|
|
9
|
+
* ``. Elsewhere the title is just a tooltip.
|
|
10
|
+
*/
|
|
11
|
+
export function thumbnailLabel(node: Inline): string | null {
|
|
12
|
+
if (node.type !== "image" || !node.title) return null
|
|
13
|
+
const match = THUMBNAIL.exec(node.title.trim())
|
|
14
|
+
return match ? (match[1]?.trim() ?? "") : null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A step's title from its heading: the plain text, without thumbnail images (they show beside it). */
|
|
18
|
+
export function stepTitleText(nodes: Inline[]): string {
|
|
19
|
+
return plainText(nodes.filter((n) => thumbnailLabel(n) === null)).trim()
|
|
20
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -87,6 +87,7 @@ export type {
|
|
|
87
87
|
export type { ReplayEvent } from "./replay/types"
|
|
88
88
|
export { isReplayQaUrl, normalizeReplayEmbedUrl } from "./replay/url"
|
|
89
89
|
export { ReplayFile, isRrwebUrl } from "./replay/ReplayFile"
|
|
90
|
+
export { ImageThumbnail, leadingThumbnail, thumbnailLabel } from "./docs/ImageThumbnail"
|
|
90
91
|
export type { ReplayFileProps } from "./replay/ReplayFile"
|
|
91
92
|
export { VideoEmbed } from "./video"
|
|
92
93
|
export type { VideoEmbedProps } from "./video"
|
package/src/styles.css
CHANGED
|
@@ -443,6 +443,34 @@
|
|
|
443
443
|
margin: 4px 0 6px;
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
+
/* A step's body starts at the top, so a step without a title has its first line beside its number. */
|
|
447
|
+
:is(.docs-step-main, .docs-step-media-body) > [data-docstream-blocks] > :first-child {
|
|
448
|
+
margin-top: 0;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.docs-step-main > [data-docstream-blocks]:first-child {
|
|
452
|
+
padding-top: 2px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/* A step whose heading opens with a thumbnail: the image in its own column, the title and body beside it. */
|
|
456
|
+
.docs-step-media {
|
|
457
|
+
display: flex;
|
|
458
|
+
align-items: flex-start;
|
|
459
|
+
gap: 16px;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.docs-step-media-body {
|
|
463
|
+
flex: 1;
|
|
464
|
+
min-width: 0;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
@media (max-width: 560px) {
|
|
468
|
+
.docs-step-media {
|
|
469
|
+
flex-direction: column;
|
|
470
|
+
gap: 8px;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
446
474
|
.docs-embed {
|
|
447
475
|
width: 100%;
|
|
448
476
|
aspect-ratio: 16 / 9;
|
|
@@ -1712,6 +1740,124 @@
|
|
|
1712
1740
|
text-decoration: none !important;
|
|
1713
1741
|
}
|
|
1714
1742
|
|
|
1743
|
+
/* Thumbnail images (``): a small frame with a badge; hover or focus
|
|
1744
|
+
opens a card with the full image. First in a paragraph, the text runs beside it. */
|
|
1745
|
+
/* Float, not flex: the text keeps its own first line, so a list marker lines up with it. */
|
|
1746
|
+
.docs-media {
|
|
1747
|
+
display: flow-root;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
.docs-media > .docs-thumb,
|
|
1751
|
+
.docs-media > .docs-thumb-link {
|
|
1752
|
+
float: left;
|
|
1753
|
+
margin: 3px 14px 4px 0;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
.docs-media-body {
|
|
1757
|
+
display: block;
|
|
1758
|
+
overflow: hidden;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
.docs-thumb-link {
|
|
1762
|
+
text-decoration: none !important;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
.docs-thumb {
|
|
1766
|
+
position: relative;
|
|
1767
|
+
display: inline-block;
|
|
1768
|
+
flex: none;
|
|
1769
|
+
vertical-align: top;
|
|
1770
|
+
border-radius: 10px;
|
|
1771
|
+
outline: none;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
.docs-thumb-frame {
|
|
1775
|
+
position: relative;
|
|
1776
|
+
display: block;
|
|
1777
|
+
width: var(--docs-thumb-width, 168px);
|
|
1778
|
+
aspect-ratio: 16 / 10;
|
|
1779
|
+
overflow: hidden;
|
|
1780
|
+
border: 1px solid var(--gb-border);
|
|
1781
|
+
border-radius: 10px;
|
|
1782
|
+
background: var(--gb-muted, #f4f4f5);
|
|
1783
|
+
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
|
|
1784
|
+
cursor: zoom-in;
|
|
1785
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
.docs-thumb-frame img {
|
|
1789
|
+
display: block;
|
|
1790
|
+
width: 100%;
|
|
1791
|
+
height: 100%;
|
|
1792
|
+
object-fit: cover;
|
|
1793
|
+
object-position: top left;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
.docs-thumb:hover .docs-thumb-frame,
|
|
1797
|
+
.docs-thumb:focus-visible .docs-thumb-frame {
|
|
1798
|
+
border-color: var(--gb-panel-foreground, #18181b);
|
|
1799
|
+
box-shadow: 0 0 0 2px var(--gb-border);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
.docs-thumb-badge {
|
|
1803
|
+
position: absolute;
|
|
1804
|
+
right: 7px;
|
|
1805
|
+
bottom: 7px;
|
|
1806
|
+
padding: 2px 7px;
|
|
1807
|
+
border-radius: 6px;
|
|
1808
|
+
color: #f4f4f5;
|
|
1809
|
+
background: rgba(39, 39, 42, 0.78);
|
|
1810
|
+
font: 500 12px/1.4 var(--gb-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
1811
|
+
font-variant-numeric: tabular-nums;
|
|
1812
|
+
pointer-events: none;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
.docs-thumb-card {
|
|
1816
|
+
position: absolute;
|
|
1817
|
+
top: calc(100% + 10px);
|
|
1818
|
+
left: 0;
|
|
1819
|
+
z-index: 60;
|
|
1820
|
+
display: block;
|
|
1821
|
+
width: min(var(--docs-thumb-card-width, 720px), calc(100vw - 32px));
|
|
1822
|
+
overflow: hidden;
|
|
1823
|
+
padding: 10px;
|
|
1824
|
+
border: 1px solid var(--gb-border);
|
|
1825
|
+
border-radius: 16px;
|
|
1826
|
+
background: var(--gb-panel);
|
|
1827
|
+
box-shadow: 0 18px 48px rgba(15, 23, 42, 0.22), 0 2px 8px rgba(15, 23, 42, 0.1);
|
|
1828
|
+
opacity: 0;
|
|
1829
|
+
visibility: hidden;
|
|
1830
|
+
transform: translateY(-4px);
|
|
1831
|
+
transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s;
|
|
1832
|
+
pointer-events: none;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
.docs-thumb-card img {
|
|
1836
|
+
display: block;
|
|
1837
|
+
width: 100%;
|
|
1838
|
+
height: auto;
|
|
1839
|
+
border-radius: 8px;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
.docs-thumb-card .docs-thumb-badge {
|
|
1843
|
+
right: 18px;
|
|
1844
|
+
bottom: 18px;
|
|
1845
|
+
font-size: 14px;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
.docs-thumb:hover .docs-thumb-card,
|
|
1849
|
+
.docs-thumb:focus-visible .docs-thumb-card {
|
|
1850
|
+
opacity: 1;
|
|
1851
|
+
visibility: visible;
|
|
1852
|
+
transform: translateY(0);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1856
|
+
.docs-thumb-card {
|
|
1857
|
+
transition: none;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1715
1861
|
.oas {
|
|
1716
1862
|
border: 1px solid var(--gb-border);
|
|
1717
1863
|
border-radius: 12px;
|