@honeydeck/honeydeck 0.1.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/AGENTS.md +25 -0
- package/DEVELOPMENT.md +522 -0
- package/LICENSE +21 -0
- package/Readme.md +49 -0
- package/SPEC.md +88 -0
- package/docs/components.md +63 -0
- package/docs/configuration.md +91 -0
- package/docs/getting-started.md +116 -0
- package/docs/kit-authoring.md +207 -0
- package/docs/kits.md +387 -0
- package/docs/local-development.md +95 -0
- package/docs/mermaid.md +198 -0
- package/docs/mobile.md +108 -0
- package/docs/navigation.md +93 -0
- package/docs/next-steps.md +377 -0
- package/docs/pdf-export.md +91 -0
- package/docs/presenter-mode.md +104 -0
- package/docs/slides.md +130 -0
- package/docs/slidev-migration.md +42 -0
- package/docs/steps-and-reveals.md +171 -0
- package/package.json +134 -0
- package/skills/SPEC.md +21 -0
- package/skills/honeydeck/SKILL.md +65 -0
- package/skills/presentation-writing/SKILL.md +75 -0
- package/skills/slidev-migration/SKILL.md +153 -0
- package/src/SPEC.md +89 -0
- package/src/assets.d.ts +30 -0
- package/src/cli/SPEC.md +230 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/banner.ts +9 -0
- package/src/cli/bin.js +5 -0
- package/src/cli/build.ts +229 -0
- package/src/cli/deck-path.ts +32 -0
- package/src/cli/dev.ts +263 -0
- package/src/cli/index.ts +126 -0
- package/src/cli/init.ts +369 -0
- package/src/cli/pdf.ts +923 -0
- package/src/cli/skill.ts +75 -0
- package/src/cli/templates/SPEC.md +70 -0
- package/src/cli/templates/deck-mdx.ts +15 -0
- package/src/cli/templates/package-json.ts +36 -0
- package/src/cli/templates/sparkle-button.ts +15 -0
- package/src/cli/templates/starter/components/SparkleButton.tsx +84 -0
- package/src/cli/templates/starter/deck.mdx +153 -0
- package/src/cli/templates/starter/styles.css +14 -0
- package/src/cli/templates/styles-css.ts +14 -0
- package/src/defaults.ts +1 -0
- package/src/layouts/ColorModeImage.tsx +55 -0
- package/src/layouts/SPEC.md +393 -0
- package/src/layouts/SlideFrame.tsx +48 -0
- package/src/layouts/bee/Blank.tsx +12 -0
- package/src/layouts/bee/Cover.tsx +70 -0
- package/src/layouts/bee/Default.tsx +42 -0
- package/src/layouts/bee/Image/Image.tsx +151 -0
- package/src/layouts/bee/Image/placeholder-dark.webp +0 -0
- package/src/layouts/bee/Image/placeholder-vertical-dark.webp +0 -0
- package/src/layouts/bee/Image/placeholder-vertical.webp +0 -0
- package/src/layouts/bee/Image/placeholder.webp +0 -0
- package/src/layouts/bee/ImageLeft.tsx +27 -0
- package/src/layouts/bee/ImageRight.tsx +27 -0
- package/src/layouts/bee/ImageSide.tsx +107 -0
- package/src/layouts/bee/Section.tsx +40 -0
- package/src/layouts/bee/TwoCol.tsx +108 -0
- package/src/layouts/bee/index.ts +40 -0
- package/src/layouts/clean/Blank.tsx +12 -0
- package/src/layouts/clean/Cover.tsx +58 -0
- package/src/layouts/clean/Default.tsx +33 -0
- package/src/layouts/clean/Image/Image.tsx +103 -0
- package/src/layouts/clean/ImageLeft.tsx +27 -0
- package/src/layouts/clean/ImageRight.tsx +27 -0
- package/src/layouts/clean/ImageSide.tsx +113 -0
- package/src/layouts/clean/Section.tsx +35 -0
- package/src/layouts/clean/TwoCol.tsx +63 -0
- package/src/layouts/clean/index.ts +40 -0
- package/src/layouts/index.ts +60 -0
- package/src/layouts/placeholders.ts +9 -0
- package/src/layouts/utils.ts +13 -0
- package/src/remark/SPEC.md +49 -0
- package/src/remark/h1-extract.ts +124 -0
- package/src/remark/index.ts +4 -0
- package/src/remark/shiki-code-blocks.ts +325 -0
- package/src/remark/step-numbering.ts +412 -0
- package/src/runtime/Deck.tsx +533 -0
- package/src/runtime/SPEC.md +256 -0
- package/src/runtime/SlideCanvas.tsx +95 -0
- package/src/runtime/TimelineContext.tsx +122 -0
- package/src/runtime/app-shell/index.html +31 -0
- package/src/runtime/app-shell/main.tsx +42 -0
- package/src/runtime/aspectRatio.ts +34 -0
- package/src/runtime/colorMode.ts +23 -0
- package/src/runtime/components/BrowserFrame.tsx +233 -0
- package/src/runtime/components/Button.tsx +57 -0
- package/src/runtime/components/CodeBlock.tsx +210 -0
- package/src/runtime/components/ColorModeCycleButton.tsx +59 -0
- package/src/runtime/components/ErrorBoundary.tsx +125 -0
- package/src/runtime/components/Keyboard.tsx +87 -0
- package/src/runtime/components/ListStyle.tsx +203 -0
- package/src/runtime/components/NavBar.tsx +223 -0
- package/src/runtime/components/NavBarButton.tsx +47 -0
- package/src/runtime/components/NavBarDivider.tsx +3 -0
- package/src/runtime/components/Notes.tsx +171 -0
- package/src/runtime/components/Reveal.tsx +82 -0
- package/src/runtime/components/RevealGroup.tsx +193 -0
- package/src/runtime/components/SPEC.md +263 -0
- package/src/runtime/components/SlideNumberBadge.tsx +11 -0
- package/src/runtime/components/TimelineSteps.tsx +115 -0
- package/src/runtime/components/index.ts +55 -0
- package/src/runtime/index.ts +42 -0
- package/src/runtime/inputOwnership.ts +68 -0
- package/src/runtime/keyboardTarget.ts +7 -0
- package/src/runtime/lastSlideRoute.ts +56 -0
- package/src/runtime/navigation.ts +211 -0
- package/src/runtime/router.ts +157 -0
- package/src/runtime/slideData.ts +137 -0
- package/src/runtime/sync.ts +267 -0
- package/src/runtime/types.ts +182 -0
- package/src/runtime/useKeyboardNav.ts +138 -0
- package/src/runtime/useSwipeNav.ts +257 -0
- package/src/runtime/views/DocsView.tsx +74 -0
- package/src/runtime/views/OverviewView.tsx +386 -0
- package/src/runtime/views/PresenterNotesPanel.tsx +76 -0
- package/src/runtime/views/PresenterView.tsx +340 -0
- package/src/runtime/views/SPEC.md +152 -0
- package/src/runtime/views/docs/ComponentsTab.tsx +178 -0
- package/src/runtime/views/docs/DocsHeader.tsx +101 -0
- package/src/runtime/views/docs/Intro.tsx +20 -0
- package/src/runtime/views/docs/LayoutsTab.tsx +324 -0
- package/src/runtime/views/docs/ThemeTab.tsx +110 -0
- package/src/runtime/views/index.ts +7 -0
- package/src/runtime/views/overviewGrid.ts +106 -0
- package/src/runtime/views/presenterPreview.ts +27 -0
- package/src/runtime/virtual-modules.d.ts +98 -0
- package/src/theme/SPEC.md +179 -0
- package/src/theme/base.css +623 -0
- package/src/theme/bee.css +35 -0
- package/src/theme/clean.css +38 -0
- package/src/vite-plugin/SPEC.md +114 -0
- package/src/vite-plugin/component-doc-crawler.ts +350 -0
- package/src/vite-plugin/deck-loader.ts +148 -0
- package/src/vite-plugin/index.ts +373 -0
- package/src/vite-plugin/layout-demo-crawler.ts +802 -0
- package/src/vite-plugin/splitter.ts +353 -0
- package/src/vite-plugin/token-manifest.ts +163 -0
- package/src/vite-plugin/virtual-modules.ts +587 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as componentReference from "virtual:honeydeck/components";
|
|
2
|
+
import type { ComponentDoc, ComponentPropDoc } from "../../types.ts";
|
|
3
|
+
import { Intro } from "./Intro.tsx";
|
|
4
|
+
|
|
5
|
+
const { componentDocWarnings, componentDocs, componentMap, componentNames } =
|
|
6
|
+
componentReference;
|
|
7
|
+
|
|
8
|
+
type ComponentEntry = {
|
|
9
|
+
name: string;
|
|
10
|
+
doc?: ComponentDoc;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function buildComponentEntries(
|
|
14
|
+
names: string[],
|
|
15
|
+
docs: Record<string, ComponentDoc>,
|
|
16
|
+
): ComponentEntry[] {
|
|
17
|
+
const entries: ComponentEntry[] = [];
|
|
18
|
+
|
|
19
|
+
for (const name of names) {
|
|
20
|
+
if (!componentMap[name]) continue;
|
|
21
|
+
|
|
22
|
+
const entry: ComponentEntry = { name };
|
|
23
|
+
const doc = docs[name];
|
|
24
|
+
if (doc) entry.doc = doc;
|
|
25
|
+
entries.push(entry);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return entries;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ComponentPropsTable({ props }: { props: ComponentPropDoc[] }) {
|
|
32
|
+
if (props.length === 0) {
|
|
33
|
+
return (
|
|
34
|
+
<div className="rounded-md border border-dashed border-border bg-background p-4 text-sm leading-6 text-surface-foreground/65 shadow-sm">
|
|
35
|
+
<div className="font-medium text-surface-foreground">
|
|
36
|
+
No props documented
|
|
37
|
+
</div>
|
|
38
|
+
<div className="mt-1">
|
|
39
|
+
Add an exported props type with JSDoc comments to document component
|
|
40
|
+
params here.
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="overflow-x-auto rounded-md border border-border bg-background shadow-sm">
|
|
48
|
+
<table className="w-full min-w-180 table-fixed border-collapse text-left text-sm">
|
|
49
|
+
<thead className="border-b border-border bg-surface/60 text-xs uppercase tracking-wider text-surface-foreground/60">
|
|
50
|
+
<tr>
|
|
51
|
+
<th className="w-[16%] px-4 py-3 font-medium">Param</th>
|
|
52
|
+
<th className="w-[20%] px-4 py-3 font-medium">Type</th>
|
|
53
|
+
<th className="w-[12%] px-4 py-3 font-medium">Default</th>
|
|
54
|
+
<th className="w-[52%] px-4 py-3 font-medium">Description</th>
|
|
55
|
+
</tr>
|
|
56
|
+
</thead>
|
|
57
|
+
<tbody className="divide-y divide-border">
|
|
58
|
+
{props.map((prop) => (
|
|
59
|
+
<tr key={prop.name}>
|
|
60
|
+
<td className="px-4 py-3 align-top">
|
|
61
|
+
<code className="font-mono text-sm font-medium text-surface-foreground">
|
|
62
|
+
{prop.name}
|
|
63
|
+
{prop.required ? "" : "?"}
|
|
64
|
+
</code>
|
|
65
|
+
</td>
|
|
66
|
+
<td className="px-4 py-3 align-top">
|
|
67
|
+
<code className="break-words font-mono text-xs text-surface-foreground/70">
|
|
68
|
+
{prop.type}
|
|
69
|
+
</code>
|
|
70
|
+
</td>
|
|
71
|
+
<td className="px-4 py-3 align-top">
|
|
72
|
+
{prop.defaultValue !== undefined ? (
|
|
73
|
+
<code className="break-words font-mono text-xs text-surface-foreground/70">
|
|
74
|
+
{prop.defaultValue}
|
|
75
|
+
</code>
|
|
76
|
+
) : (
|
|
77
|
+
<span className="text-surface-foreground/40">—</span>
|
|
78
|
+
)}
|
|
79
|
+
</td>
|
|
80
|
+
<td className="px-4 py-3 align-top leading-5 text-surface-foreground/70">
|
|
81
|
+
{prop.description || "—"}
|
|
82
|
+
</td>
|
|
83
|
+
</tr>
|
|
84
|
+
))}
|
|
85
|
+
</tbody>
|
|
86
|
+
</table>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function ComponentsTab() {
|
|
92
|
+
const components = buildComponentEntries(componentNames, componentDocs);
|
|
93
|
+
|
|
94
|
+
function scrollToComponent(name: string) {
|
|
95
|
+
document
|
|
96
|
+
.getElementById(componentSectionId(name))
|
|
97
|
+
?.scrollIntoView({ block: "start" });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<>
|
|
102
|
+
<Intro title="Components">
|
|
103
|
+
Built-in components exported from{" "}
|
|
104
|
+
<code className="rounded-xs bg-surface px-1 py-0.5 font-mono">
|
|
105
|
+
honeydeck/components
|
|
106
|
+
</code>
|
|
107
|
+
, documented from their exported JSDoc comments and props types.
|
|
108
|
+
</Intro>
|
|
109
|
+
|
|
110
|
+
{componentDocWarnings.length > 0 && (
|
|
111
|
+
<div className="mb-6 rounded-md border border-border bg-surface p-4 text-sm text-foreground/70">
|
|
112
|
+
<div className="font-medium text-foreground">
|
|
113
|
+
Component docs discovery warnings
|
|
114
|
+
</div>
|
|
115
|
+
<ul className="mb-0 mt-2 list-disc pl-5">
|
|
116
|
+
{componentDocWarnings.map((warning) => (
|
|
117
|
+
<li key={warning}>{warning}</li>
|
|
118
|
+
))}
|
|
119
|
+
</ul>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
<div className="grid gap-8 xl:grid-cols-[12rem_minmax(0,1fr)]">
|
|
124
|
+
<aside className="xl:block">
|
|
125
|
+
<nav
|
|
126
|
+
className="sticky top-28 flex gap-1 overflow-x-auto pb-2 xl:block xl:space-y-1 xl:overflow-visible xl:pb-0"
|
|
127
|
+
aria-label="Built-in components"
|
|
128
|
+
>
|
|
129
|
+
{components.map((entry) => (
|
|
130
|
+
<button
|
|
131
|
+
key={entry.name}
|
|
132
|
+
type="button"
|
|
133
|
+
onClick={() => scrollToComponent(entry.name)}
|
|
134
|
+
className="whitespace-nowrap rounded-sm px-2.5 py-2 text-left text-sm font-medium text-foreground/60 hover:bg-surface hover:text-foreground xl:block xl:w-full"
|
|
135
|
+
>
|
|
136
|
+
{entry.name}
|
|
137
|
+
</button>
|
|
138
|
+
))}
|
|
139
|
+
</nav>
|
|
140
|
+
</aside>
|
|
141
|
+
|
|
142
|
+
<div className="min-w-0 ">
|
|
143
|
+
{components.map((entry) => (
|
|
144
|
+
<section
|
|
145
|
+
key={entry.name}
|
|
146
|
+
id={componentSectionId(entry.name)}
|
|
147
|
+
className="scroll-mt-28 py-8 first:pt-0"
|
|
148
|
+
>
|
|
149
|
+
<h2 className="m-0 text-3xl font-semibold tracking-tight text-foreground">
|
|
150
|
+
{entry.name}
|
|
151
|
+
</h2>
|
|
152
|
+
|
|
153
|
+
<div className="mt-5">
|
|
154
|
+
{entry.doc ? (
|
|
155
|
+
<div className="honeydeck-docs-content">
|
|
156
|
+
<entry.doc.Component />
|
|
157
|
+
</div>
|
|
158
|
+
) : (
|
|
159
|
+
<div className="text-sm leading-6 text-surface-foreground/65">
|
|
160
|
+
No documentation comment found for this component.
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div className="mt-6">
|
|
166
|
+
<ComponentPropsTable props={entry.doc?.props ?? []} />
|
|
167
|
+
</div>
|
|
168
|
+
</section>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function componentSectionId(name: string): string {
|
|
177
|
+
return `component-${name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
|
|
178
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ExternalLinkIcon } from "lucide-react";
|
|
2
|
+
import {
|
|
3
|
+
type ColorMode,
|
|
4
|
+
ColorModeCycleButton,
|
|
5
|
+
} from "../../components/ColorModeCycleButton.tsx";
|
|
6
|
+
import { getRememberedSlideRoute } from "../../lastSlideRoute.ts";
|
|
7
|
+
import type { KitTab } from "../../router.ts";
|
|
8
|
+
import { navigate } from "../../router.ts";
|
|
9
|
+
|
|
10
|
+
export type DocsHeaderProps = {
|
|
11
|
+
tab: KitTab;
|
|
12
|
+
colorMode: ColorMode;
|
|
13
|
+
onSetColorMode: (mode: ColorMode) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function tabClass(active: boolean): string {
|
|
17
|
+
return active
|
|
18
|
+
? "border-primary text-foreground"
|
|
19
|
+
: "border-transparent text-foreground/55 hover:text-foreground";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function DocsHeader({
|
|
23
|
+
tab,
|
|
24
|
+
colorMode,
|
|
25
|
+
onSetColorMode,
|
|
26
|
+
}: DocsHeaderProps) {
|
|
27
|
+
return (
|
|
28
|
+
<header className="sticky top-0 z-10 border-b border-border bg-background/95 backdrop-blur">
|
|
29
|
+
<div className="mx-auto flex max-w-7xl flex-col gap-3 px-5 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-8">
|
|
30
|
+
<div>
|
|
31
|
+
<div className="text-lg font-semibold tracking-tight">
|
|
32
|
+
Honeydeck Reference
|
|
33
|
+
</div>
|
|
34
|
+
<div className="text-xs text-foreground/50">/#/theme</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<nav
|
|
38
|
+
className="flex flex-wrap gap-x-6 gap-y-1"
|
|
39
|
+
aria-label="Docs reference sections"
|
|
40
|
+
>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
onClick={() =>
|
|
44
|
+
navigate({ view: "kit", slide: 1, step: 0, kitTab: "theme" })
|
|
45
|
+
}
|
|
46
|
+
className={`border-b-2 py-2 text-sm font-medium ${tabClass(tab === "theme")}`}
|
|
47
|
+
>
|
|
48
|
+
Theme tokens
|
|
49
|
+
</button>
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
onClick={() =>
|
|
53
|
+
navigate({ view: "kit", slide: 1, step: 0, kitTab: "layouts" })
|
|
54
|
+
}
|
|
55
|
+
className={`border-b-2 py-2 text-sm font-medium ${tabClass(tab === "layouts")}`}
|
|
56
|
+
>
|
|
57
|
+
Layouts
|
|
58
|
+
</button>
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
onClick={() =>
|
|
62
|
+
navigate({
|
|
63
|
+
view: "kit",
|
|
64
|
+
slide: 1,
|
|
65
|
+
step: 0,
|
|
66
|
+
kitTab: "components",
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
className={`border-b-2 py-2 text-sm font-medium ${tabClass(tab === "components")}`}
|
|
70
|
+
>
|
|
71
|
+
Components
|
|
72
|
+
</button>
|
|
73
|
+
</nav>
|
|
74
|
+
|
|
75
|
+
<div className="flex items-center gap-4">
|
|
76
|
+
<a
|
|
77
|
+
href="https://honeydeck.dev"
|
|
78
|
+
target="_blank"
|
|
79
|
+
rel="noreferrer"
|
|
80
|
+
className="inline-flex items-center gap-1.5 text-sm font-medium text-foreground/65 underline underline-offset-4 hover:text-foreground"
|
|
81
|
+
>
|
|
82
|
+
Docs
|
|
83
|
+
<ExternalLinkIcon aria-hidden="true" size={14} />
|
|
84
|
+
</a>
|
|
85
|
+
<ColorModeCycleButton
|
|
86
|
+
colorMode={colorMode}
|
|
87
|
+
onSetColorMode={onSetColorMode}
|
|
88
|
+
className="flex h-8 w-8 items-center justify-center rounded-sm border border-border bg-background text-foreground"
|
|
89
|
+
/>
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
onClick={() => navigate(getRememberedSlideRoute())}
|
|
93
|
+
className="rounded-sm border border-border bg-surface px-3 py-1.5 text-sm font-medium text-surface-foreground hover:border-primary/50 hover:bg-primary/10 hover:text-foreground"
|
|
94
|
+
>
|
|
95
|
+
Back to slides
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</header>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export function Intro({
|
|
4
|
+
title,
|
|
5
|
+
children,
|
|
6
|
+
}: {
|
|
7
|
+
title: string;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="mb-8 pb-6">
|
|
12
|
+
<h1 className="m-0 text-4xl font-semibold tracking-tight text-foreground">
|
|
13
|
+
{title}
|
|
14
|
+
</h1>
|
|
15
|
+
<p className="mt-3 max-w-3xl text-md leading-6 text-foreground/70">
|
|
16
|
+
{children}
|
|
17
|
+
</p>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import * as layoutReference from "virtual:honeydeck/layouts";
|
|
2
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { BASE_HEIGHT, BASE_WIDTH, resolveLayout } from "../../slideData.ts";
|
|
5
|
+
import { TimelineProvider } from "../../TimelineContext.tsx";
|
|
6
|
+
import type { CompiledLayoutDemo, LayoutPropDoc } from "../../types.ts";
|
|
7
|
+
import { Intro } from "./Intro.tsx";
|
|
8
|
+
|
|
9
|
+
const { layoutDemos, layoutDemoWarnings, layoutNames, layoutPropDocs } =
|
|
10
|
+
layoutReference;
|
|
11
|
+
|
|
12
|
+
type LayoutEntry = {
|
|
13
|
+
name: string;
|
|
14
|
+
demo?: CompiledLayoutDemo;
|
|
15
|
+
propDocs: LayoutPropDoc[];
|
|
16
|
+
snippet?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function buildLayoutEntries(
|
|
20
|
+
names: string[],
|
|
21
|
+
demos: Record<string, CompiledLayoutDemo>,
|
|
22
|
+
propDocs: Record<string, LayoutPropDoc[]>,
|
|
23
|
+
): LayoutEntry[] {
|
|
24
|
+
const entries: LayoutEntry[] = [];
|
|
25
|
+
|
|
26
|
+
for (const name of names) {
|
|
27
|
+
const demo = demos[name] as CompiledLayoutDemo | undefined;
|
|
28
|
+
const snippet =
|
|
29
|
+
typeof demo?.mdx === "string" && demo.mdx.trim().length > 0
|
|
30
|
+
? demo.mdx.trim()
|
|
31
|
+
: undefined;
|
|
32
|
+
const entry: LayoutEntry = {
|
|
33
|
+
name,
|
|
34
|
+
propDocs: propDocs[name] ?? [],
|
|
35
|
+
};
|
|
36
|
+
if (demo) entry.demo = demo;
|
|
37
|
+
if (snippet) entry.snippet = snippet;
|
|
38
|
+
entries.push(entry);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return entries;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function UsageCode({ code }: { code: string }) {
|
|
45
|
+
return (
|
|
46
|
+
<pre className="m-0 min-h-0 max-w-full flex-1 overflow-x-auto overflow-y-auto p-3 font-mono text-sm leading-6 text-surface-foreground">
|
|
47
|
+
{code}
|
|
48
|
+
</pre>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function PropsTable({ entry }: { entry: LayoutEntry }) {
|
|
53
|
+
const props: LayoutPropDoc[] = [
|
|
54
|
+
{
|
|
55
|
+
name: "layout",
|
|
56
|
+
type: JSON.stringify(entry.name),
|
|
57
|
+
required: true,
|
|
58
|
+
description: `Selects the ${entry.name} layout for this slide.`,
|
|
59
|
+
},
|
|
60
|
+
...entry.propDocs,
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="min-h-0 flex-1 overflow-auto p-3">
|
|
65
|
+
<table className="w-full min-w-0 table-fixed border-collapse text-left text-sm">
|
|
66
|
+
<thead className="text-xs uppercase tracking-wider text-surface-foreground/55">
|
|
67
|
+
<tr className="border-b border-border">
|
|
68
|
+
<th className="w-[30%] px-2 py-2 font-medium">Prop</th>
|
|
69
|
+
<th className="w-[25%] px-2 py-2 font-medium">Type</th>
|
|
70
|
+
<th className="w-[45%] px-2 py-2 font-medium">Description</th>
|
|
71
|
+
</tr>
|
|
72
|
+
</thead>
|
|
73
|
+
<tbody className="divide-y divide-border">
|
|
74
|
+
{props.map((prop) => (
|
|
75
|
+
<tr key={prop.name}>
|
|
76
|
+
<td className="px-2 py-2 align-top">
|
|
77
|
+
<code className="font-mono text-sm font-medium text-surface-foreground">
|
|
78
|
+
{prop.name}
|
|
79
|
+
{prop.required ? "" : "?"}
|
|
80
|
+
</code>
|
|
81
|
+
</td>
|
|
82
|
+
<td className="px-2 py-2 align-top">
|
|
83
|
+
<code className="break-words font-mono text-xs text-surface-foreground/70">
|
|
84
|
+
{prop.type}
|
|
85
|
+
</code>
|
|
86
|
+
</td>
|
|
87
|
+
<td className="px-2 py-2 align-top leading-5 text-surface-foreground/70">
|
|
88
|
+
{prop.description || "—"}
|
|
89
|
+
</td>
|
|
90
|
+
</tr>
|
|
91
|
+
))}
|
|
92
|
+
</tbody>
|
|
93
|
+
</table>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function LayoutReferenceTabs({ entry }: { entry: LayoutEntry }) {
|
|
99
|
+
const [copied, setCopied] = useState(false);
|
|
100
|
+
const [tab, setTab] = useState<"usage" | "props">("usage");
|
|
101
|
+
|
|
102
|
+
async function copy() {
|
|
103
|
+
if (!entry.snippet) return;
|
|
104
|
+
try {
|
|
105
|
+
await navigator.clipboard.writeText(entry.snippet);
|
|
106
|
+
setCopied(true);
|
|
107
|
+
window.setTimeout(() => setCopied(false), 1200);
|
|
108
|
+
} catch {
|
|
109
|
+
// Ignore clipboard failures; the snippet remains selectable.
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div className="flex h-full min-h-0 min-w-0 w-full flex-col rounded-md border border-border bg-background shadow-sm">
|
|
115
|
+
<div className="flex flex-shrink-0 items-center justify-between gap-3 border-b border-border bg-surface/60 px-3 py-2">
|
|
116
|
+
<div
|
|
117
|
+
className="inline-flex rounded-sm border border-border bg-background p-1 shadow-xs"
|
|
118
|
+
role="tablist"
|
|
119
|
+
aria-label={`${entry.name} reference`}
|
|
120
|
+
>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
role="tab"
|
|
124
|
+
aria-selected={tab === "usage"}
|
|
125
|
+
onClick={() => setTab("usage")}
|
|
126
|
+
className={`rounded-xs px-3 py-1.5 text-xs font-semibold uppercase tracking-wider transition ${
|
|
127
|
+
tab === "usage"
|
|
128
|
+
? "bg-primary text-primary-foreground shadow-sm"
|
|
129
|
+
: "text-surface-foreground/60 hover:bg-surface hover:text-surface-foreground"
|
|
130
|
+
}`}
|
|
131
|
+
>
|
|
132
|
+
Usage
|
|
133
|
+
</button>
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
role="tab"
|
|
137
|
+
aria-selected={tab === "props"}
|
|
138
|
+
onClick={() => setTab("props")}
|
|
139
|
+
className={`rounded-xs px-3 py-1.5 text-xs font-semibold uppercase tracking-wider transition ${
|
|
140
|
+
tab === "props"
|
|
141
|
+
? "bg-primary text-primary-foreground shadow-sm"
|
|
142
|
+
: "text-surface-foreground/60 hover:bg-surface hover:text-surface-foreground"
|
|
143
|
+
}`}
|
|
144
|
+
>
|
|
145
|
+
Props
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
{tab === "usage" && entry.snippet && (
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
onClick={copy}
|
|
152
|
+
className="inline-flex items-center gap-1.5 rounded-sm border border-border bg-background px-3 py-1.5 text-xs font-semibold text-surface-foreground shadow-xs transition hover:border-primary/55 hover:bg-primary hover:text-primary-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
|
153
|
+
>
|
|
154
|
+
{copied ? (
|
|
155
|
+
<CheckIcon className="size-3.5" aria-hidden="true" />
|
|
156
|
+
) : (
|
|
157
|
+
<CopyIcon className="size-3.5" aria-hidden="true" />
|
|
158
|
+
)}
|
|
159
|
+
<span>{copied ? "Copied" : "Copy"}</span>
|
|
160
|
+
</button>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
{tab === "usage" ? (
|
|
164
|
+
entry.snippet ? (
|
|
165
|
+
<UsageCode code={entry.snippet} />
|
|
166
|
+
) : (
|
|
167
|
+
<MissingSnippet />
|
|
168
|
+
)
|
|
169
|
+
) : (
|
|
170
|
+
<PropsTable entry={entry} />
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function MissingSnippet() {
|
|
177
|
+
return (
|
|
178
|
+
<div className="h-full min-h-0 min-w-0 w-full overflow-y-auto rounded-md border border-dashed border-border bg-background p-4 text-sm leading-6 text-surface-foreground/65 shadow-sm">
|
|
179
|
+
<div className="font-medium text-surface-foreground">
|
|
180
|
+
No demo MDX provided
|
|
181
|
+
</div>
|
|
182
|
+
<div className="mt-1 max-w-full">
|
|
183
|
+
Add{" "}
|
|
184
|
+
<code className="rounded-xs bg-surface px-1 py-0.5 font-mono">mdx</code>{" "}
|
|
185
|
+
to this layout's{" "}
|
|
186
|
+
<code className="rounded-xs bg-surface px-1 py-0.5 font-mono">
|
|
187
|
+
demo
|
|
188
|
+
</code>{" "}
|
|
189
|
+
export to show copyable usage here.
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function LayoutPreview({ entry }: { entry: LayoutEntry }) {
|
|
196
|
+
const boxRef = useRef<HTMLDivElement>(null);
|
|
197
|
+
const [scale, setScale] = useState(0.2);
|
|
198
|
+
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
const box = boxRef.current;
|
|
201
|
+
if (!box) return;
|
|
202
|
+
|
|
203
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
204
|
+
if (!entry) return;
|
|
205
|
+
const { width, height } = entry.contentRect;
|
|
206
|
+
setScale(Math.min(width / BASE_WIDTH, height / BASE_HEIGHT));
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
observer.observe(box);
|
|
210
|
+
return () => observer.disconnect();
|
|
211
|
+
}, []);
|
|
212
|
+
|
|
213
|
+
if (!entry.demo) {
|
|
214
|
+
return (
|
|
215
|
+
<div className="flex aspect-video w-full min-w-0 items-center justify-center overflow-hidden rounded-md border border-dashed border-border bg-background text-center text-sm text-foreground/55">
|
|
216
|
+
<div>
|
|
217
|
+
<div className="font-medium text-foreground/70">
|
|
218
|
+
No demo MDX provided
|
|
219
|
+
</div>
|
|
220
|
+
<div className="mt-1">
|
|
221
|
+
Export{" "}
|
|
222
|
+
<code className="rounded-xs bg-surface px-1 py-0.5 font-mono">
|
|
223
|
+
demo.mdx
|
|
224
|
+
</code>{" "}
|
|
225
|
+
from this layout to preview it here.
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const DemoComponent = entry.demo.Component;
|
|
233
|
+
const LayoutComponent = resolveLayout(entry.demo.layoutName || entry.name);
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div
|
|
237
|
+
ref={boxRef}
|
|
238
|
+
className="flex aspect-video w-full min-w-0 items-center justify-center overflow-hidden rounded-md"
|
|
239
|
+
>
|
|
240
|
+
<div
|
|
241
|
+
className="relative overflow-hidden bg-background"
|
|
242
|
+
style={{ width: BASE_WIDTH * scale, height: BASE_HEIGHT * scale }}
|
|
243
|
+
>
|
|
244
|
+
<div
|
|
245
|
+
className="honeydeck-slide-canvas absolute left-0 top-0"
|
|
246
|
+
style={{
|
|
247
|
+
width: BASE_WIDTH,
|
|
248
|
+
height: BASE_HEIGHT,
|
|
249
|
+
transform: `scale(${scale})`,
|
|
250
|
+
transformOrigin: "top left",
|
|
251
|
+
}}
|
|
252
|
+
>
|
|
253
|
+
<TimelineProvider
|
|
254
|
+
stepIndex={0}
|
|
255
|
+
stepCount={entry.demo.stepCount}
|
|
256
|
+
showFutureSteps={true}
|
|
257
|
+
>
|
|
258
|
+
<LayoutComponent
|
|
259
|
+
title={entry.demo.title || null}
|
|
260
|
+
frontmatter={entry.demo.frontmatter}
|
|
261
|
+
rawChildren={<DemoComponent />}
|
|
262
|
+
>
|
|
263
|
+
<DemoComponent />
|
|
264
|
+
</LayoutComponent>
|
|
265
|
+
</TimelineProvider>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function LayoutsTab() {
|
|
273
|
+
const layouts = buildLayoutEntries(layoutNames, layoutDemos, layoutPropDocs);
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<>
|
|
277
|
+
<Intro title="Layouts">
|
|
278
|
+
Currently available layouts rendered with their{" "}
|
|
279
|
+
<code className="rounded-xs bg-surface px-1 py-0.5 font-mono">
|
|
280
|
+
demo.mdx
|
|
281
|
+
</code>{" "}
|
|
282
|
+
source and explicit copyable MDX snippets.
|
|
283
|
+
</Intro>
|
|
284
|
+
|
|
285
|
+
{layoutDemoWarnings.length > 0 && (
|
|
286
|
+
<div className="mb-6 rounded-md border border-border bg-surface p-4 text-sm text-foreground/70">
|
|
287
|
+
<div className="font-medium text-foreground">
|
|
288
|
+
Demo discovery warnings
|
|
289
|
+
</div>
|
|
290
|
+
<ul className="mb-0 mt-2 list-disc pl-5">
|
|
291
|
+
{layoutDemoWarnings.map((warning) => (
|
|
292
|
+
<li key={warning}>{warning}</li>
|
|
293
|
+
))}
|
|
294
|
+
</ul>
|
|
295
|
+
</div>
|
|
296
|
+
)}
|
|
297
|
+
|
|
298
|
+
<div className="w-full min-w-0 space-y-6">
|
|
299
|
+
{layouts.map((entry) => (
|
|
300
|
+
<section
|
|
301
|
+
key={entry.name}
|
|
302
|
+
className="w-full min-w-0 overflow-hidden rounded-md border border-border bg-surface"
|
|
303
|
+
>
|
|
304
|
+
<header className="border-b border-border px-4 py-3">
|
|
305
|
+
<h2 className="m-0 text-2xl font-semibold text-foreground">
|
|
306
|
+
{entry.name}
|
|
307
|
+
</h2>
|
|
308
|
+
</header>
|
|
309
|
+
<div className="grid min-w-0 items-stretch gap-6 p-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,0.7fr)]">
|
|
310
|
+
<div className="min-w-0">
|
|
311
|
+
<LayoutPreview entry={entry} />
|
|
312
|
+
</div>
|
|
313
|
+
<div className="relative h-80 min-w-0 lg:h-auto lg:min-h-0">
|
|
314
|
+
<div className="h-full min-h-0 min-w-0 lg:absolute lg:inset-0">
|
|
315
|
+
<LayoutReferenceTabs entry={entry} />
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</section>
|
|
320
|
+
))}
|
|
321
|
+
</div>
|
|
322
|
+
</>
|
|
323
|
+
);
|
|
324
|
+
}
|