@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,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean Image layout — image stage with minimal framing.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import type { LayoutDemo, LayoutProps } from "../../../runtime/types.ts";
|
|
7
|
+
import {
|
|
8
|
+
ColorModeImage,
|
|
9
|
+
type DarkModeImageFrontmatter,
|
|
10
|
+
} from "../../ColorModeImage.tsx";
|
|
11
|
+
import { imagePlaceholder, imagePlaceholderDark } from "../../placeholders.ts";
|
|
12
|
+
import CleanDefaultLayout from "../Default.tsx";
|
|
13
|
+
|
|
14
|
+
type ImageFrontmatter = DarkModeImageFrontmatter & {
|
|
15
|
+
/** Accessible text for the image. */
|
|
16
|
+
alt?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function ImageFrame({ children }: { children: ReactNode }) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="max-h-full overflow-hidden rounded-honeydeck border border-border bg-surface">
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function PlaceholderBox() {
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex max-h-full min-h-0 flex-col items-center gap-8">
|
|
30
|
+
<ImageFrame>
|
|
31
|
+
<ColorModeImage
|
|
32
|
+
src={imagePlaceholder}
|
|
33
|
+
darkSrc={imagePlaceholderDark}
|
|
34
|
+
alt=""
|
|
35
|
+
className="max-h-full max-w-full object-contain"
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
/>
|
|
38
|
+
</ImageFrame>
|
|
39
|
+
<p className="text-center text-[length:var(--honeydeck-font-size-small)] leading-snug text-surface-foreground opacity-60">
|
|
40
|
+
Add{" "}
|
|
41
|
+
<code className="rounded-honeydeck bg-background px-4 py-1 font-mono">
|
|
42
|
+
image: /path/to/image.png
|
|
43
|
+
</code>{" "}
|
|
44
|
+
to frontmatter
|
|
45
|
+
</p>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default function CleanImageLayout({
|
|
51
|
+
title,
|
|
52
|
+
children,
|
|
53
|
+
frontmatter,
|
|
54
|
+
rawChildren,
|
|
55
|
+
}: LayoutProps<ImageFrontmatter>) {
|
|
56
|
+
const { image, darkImage, alt = "" } = frontmatter;
|
|
57
|
+
const hasImage = Boolean(image || darkImage);
|
|
58
|
+
const hasCaption = Boolean(children);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<CleanDefaultLayout
|
|
62
|
+
title={title}
|
|
63
|
+
frontmatter={frontmatter}
|
|
64
|
+
rawChildren={rawChildren}
|
|
65
|
+
>
|
|
66
|
+
<figure className="m-0 flex h-full flex-col gap-8 p-0">
|
|
67
|
+
<div className="flex min-h-0 flex-1 flex-col items-center justify-center">
|
|
68
|
+
{hasImage ? (
|
|
69
|
+
<ImageFrame>
|
|
70
|
+
<ColorModeImage
|
|
71
|
+
src={image || imagePlaceholder}
|
|
72
|
+
darkSrc={darkImage}
|
|
73
|
+
alt={alt}
|
|
74
|
+
className="max-h-full max-w-full object-contain"
|
|
75
|
+
/>
|
|
76
|
+
</ImageFrame>
|
|
77
|
+
) : (
|
|
78
|
+
<PlaceholderBox />
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{hasCaption && (
|
|
83
|
+
<figcaption className="flex-shrink-0 pt-5 text-[length:var(--honeydeck-font-size-small)] leading-snug text-surface-foreground">
|
|
84
|
+
{children}
|
|
85
|
+
</figcaption>
|
|
86
|
+
)}
|
|
87
|
+
</figure>
|
|
88
|
+
</CleanDefaultLayout>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const demo: LayoutDemo<ImageFrontmatter> = {
|
|
93
|
+
mdx: `---
|
|
94
|
+
layout: Image
|
|
95
|
+
image: ""
|
|
96
|
+
darkImage: ""
|
|
97
|
+
alt: Architecture diagram
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
# System Architecture
|
|
101
|
+
|
|
102
|
+
Our distributed system in production.`,
|
|
103
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LayoutDemo, LayoutProps } from "../../runtime/types.ts";
|
|
2
|
+
import type { DarkModeImageFrontmatter } from "../ColorModeImage.tsx";
|
|
3
|
+
import { CleanImageSideLayout } from "./ImageSide.tsx";
|
|
4
|
+
|
|
5
|
+
type ImageLeftFrontmatter = DarkModeImageFrontmatter & {
|
|
6
|
+
/** Accessible text for the image. */
|
|
7
|
+
alt?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function CleanImageLeftLayout(
|
|
11
|
+
props: LayoutProps<ImageLeftFrontmatter>,
|
|
12
|
+
) {
|
|
13
|
+
return <CleanImageSideLayout {...props} side="left" />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const demo: LayoutDemo<ImageLeftFrontmatter> = {
|
|
17
|
+
mdx: `---
|
|
18
|
+
layout: ImageLeft
|
|
19
|
+
image: ""
|
|
20
|
+
darkImage: ""
|
|
21
|
+
alt: Product detail
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Product Detail
|
|
25
|
+
|
|
26
|
+
Use the body area for supporting context, bullets, or a short narrative beside the image.`,
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LayoutDemo, LayoutProps } from "../../runtime/types.ts";
|
|
2
|
+
import type { DarkModeImageFrontmatter } from "../ColorModeImage.tsx";
|
|
3
|
+
import { CleanImageSideLayout } from "./ImageSide.tsx";
|
|
4
|
+
|
|
5
|
+
type ImageRightFrontmatter = DarkModeImageFrontmatter & {
|
|
6
|
+
/** Accessible text for the image. */
|
|
7
|
+
alt?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function CleanImageRightLayout(
|
|
11
|
+
props: LayoutProps<ImageRightFrontmatter>,
|
|
12
|
+
) {
|
|
13
|
+
return <CleanImageSideLayout {...props} side="right" />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const demo: LayoutDemo<ImageRightFrontmatter> = {
|
|
17
|
+
mdx: `---
|
|
18
|
+
layout: ImageRight
|
|
19
|
+
image: ""
|
|
20
|
+
darkImage: ""
|
|
21
|
+
alt: Launch moment
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Launch Moment
|
|
25
|
+
|
|
26
|
+
Pair a flush image with focused supporting content.`,
|
|
27
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { LayoutProps } from "../../runtime/types.ts";
|
|
3
|
+
import {
|
|
4
|
+
ColorModeImage,
|
|
5
|
+
type DarkModeImageFrontmatter,
|
|
6
|
+
} from "../ColorModeImage.tsx";
|
|
7
|
+
import {
|
|
8
|
+
verticalImagePlaceholder,
|
|
9
|
+
verticalImagePlaceholderDark,
|
|
10
|
+
} from "../placeholders.ts";
|
|
11
|
+
import { SlideFrame } from "../SlideFrame.tsx";
|
|
12
|
+
import { hasTitle } from "../utils.ts";
|
|
13
|
+
|
|
14
|
+
type CleanImageSideFrontmatter = DarkModeImageFrontmatter & {
|
|
15
|
+
alt?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ImageSide = "left" | "right";
|
|
19
|
+
|
|
20
|
+
type CleanImageSideLayoutProps = LayoutProps<CleanImageSideFrontmatter> & {
|
|
21
|
+
side: ImageSide;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function CleanSideImage({
|
|
25
|
+
image,
|
|
26
|
+
darkImage,
|
|
27
|
+
alt,
|
|
28
|
+
}: {
|
|
29
|
+
image?: string;
|
|
30
|
+
darkImage?: string;
|
|
31
|
+
alt: string;
|
|
32
|
+
}) {
|
|
33
|
+
const hasImage = Boolean(image || darkImage);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="h-full min-h-0 overflow-hidden">
|
|
37
|
+
<ColorModeImage
|
|
38
|
+
src={image || verticalImagePlaceholder}
|
|
39
|
+
darkSrc={
|
|
40
|
+
darkImage || (hasImage ? undefined : verticalImagePlaceholderDark)
|
|
41
|
+
}
|
|
42
|
+
alt={hasImage ? alt : ""}
|
|
43
|
+
className="size-full object-cover"
|
|
44
|
+
aria-hidden={hasImage ? undefined : "true"}
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function CleanSideContent({
|
|
51
|
+
title,
|
|
52
|
+
children,
|
|
53
|
+
side,
|
|
54
|
+
}: {
|
|
55
|
+
title: ReactNode | null;
|
|
56
|
+
children: ReactNode;
|
|
57
|
+
side: ImageSide;
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
className={`flex min-h-0 flex-col overflow-hidden ${
|
|
62
|
+
side === "left"
|
|
63
|
+
? "py-[var(--honeydeck-slide-padding)] pr-[var(--honeydeck-slide-padding)]"
|
|
64
|
+
: "py-[var(--honeydeck-slide-padding)] pl-[var(--honeydeck-slide-padding)]"
|
|
65
|
+
}`}
|
|
66
|
+
>
|
|
67
|
+
{hasTitle(title) && (
|
|
68
|
+
<header className="mb-8 flex-shrink-0">
|
|
69
|
+
<h1 className="font-heading text-[length:var(--honeydeck-font-size-h2)] font-semibold leading-tight text-foreground">
|
|
70
|
+
{title}
|
|
71
|
+
</h1>
|
|
72
|
+
</header>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
<div className="min-h-0 flex-1 overflow-hidden">{children}</div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function CleanImageSideLayout({
|
|
81
|
+
title,
|
|
82
|
+
children,
|
|
83
|
+
frontmatter,
|
|
84
|
+
side,
|
|
85
|
+
}: CleanImageSideLayoutProps) {
|
|
86
|
+
const { image, darkImage, alt = "" } = frontmatter;
|
|
87
|
+
const imagePane = (
|
|
88
|
+
<CleanSideImage image={image} darkImage={darkImage} alt={alt} />
|
|
89
|
+
);
|
|
90
|
+
const contentPane = (
|
|
91
|
+
<CleanSideContent title={title} side={side}>
|
|
92
|
+
{children}
|
|
93
|
+
</CleanSideContent>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<SlideFrame padded={false}>
|
|
98
|
+
<div className="grid size-full grid-cols-2 gap-10">
|
|
99
|
+
{side === "left" ? (
|
|
100
|
+
<>
|
|
101
|
+
{imagePane}
|
|
102
|
+
{contentPane}
|
|
103
|
+
</>
|
|
104
|
+
) : (
|
|
105
|
+
<>
|
|
106
|
+
{contentPane}
|
|
107
|
+
{imagePane}
|
|
108
|
+
</>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
</SlideFrame>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean Section layout — simple centered section divider.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { LayoutDemo, LayoutProps } from "../../runtime/types.ts";
|
|
6
|
+
import { SlideFrame } from "../SlideFrame.tsx";
|
|
7
|
+
import { hasTitle } from "../utils.ts";
|
|
8
|
+
|
|
9
|
+
export default function CleanSectionLayout({ title, children }: LayoutProps) {
|
|
10
|
+
return (
|
|
11
|
+
<SlideFrame className="items-center justify-center text-center">
|
|
12
|
+
{hasTitle(title) && (
|
|
13
|
+
<h1 className="mb-7 max-w-5xl font-heading text-[length:var(--honeydeck-font-size-display)] font-semibold leading-none text-foreground">
|
|
14
|
+
{title}
|
|
15
|
+
</h1>
|
|
16
|
+
)}
|
|
17
|
+
|
|
18
|
+
{children && (
|
|
19
|
+
<div className="max-w-3xl text-[length:var(--honeydeck-font-size-body)] leading-snug text-surface-foreground">
|
|
20
|
+
{children}
|
|
21
|
+
</div>
|
|
22
|
+
)}
|
|
23
|
+
</SlideFrame>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const demo: LayoutDemo = {
|
|
28
|
+
mdx: `---
|
|
29
|
+
layout: Section
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Section Title
|
|
33
|
+
|
|
34
|
+
An optional section subtitle or brief description.`,
|
|
35
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean TwoCol layout — two equal plain columns.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import type { LayoutDemo, LayoutProps } from "../../runtime/types.ts";
|
|
7
|
+
import CleanDefaultLayout from "./Default.tsx";
|
|
8
|
+
|
|
9
|
+
export function Left({ children }: { children?: ReactNode }) {
|
|
10
|
+
return (
|
|
11
|
+
<div data-honeydeck-slot="left" className="col-start-1 overflow-hidden">
|
|
12
|
+
{children}
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Right({ children }: { children?: ReactNode }) {
|
|
18
|
+
return (
|
|
19
|
+
<div data-honeydeck-slot="right" className="col-start-2 overflow-hidden">
|
|
20
|
+
{children}
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function CleanTwoColLayout({
|
|
26
|
+
title,
|
|
27
|
+
children,
|
|
28
|
+
frontmatter,
|
|
29
|
+
rawChildren,
|
|
30
|
+
}: LayoutProps) {
|
|
31
|
+
return (
|
|
32
|
+
<CleanDefaultLayout
|
|
33
|
+
title={title}
|
|
34
|
+
frontmatter={frontmatter}
|
|
35
|
+
rawChildren={rawChildren}
|
|
36
|
+
>
|
|
37
|
+
<div className="grid h-full grid-cols-2 gap-10">{children}</div>
|
|
38
|
+
</CleanDefaultLayout>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const demo: LayoutDemo = {
|
|
43
|
+
mdx: `---
|
|
44
|
+
layout: TwoCol
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
import { Left, Right } from '@honeydeck/honeydeck/layouts/TwoCol'
|
|
48
|
+
|
|
49
|
+
# Pros vs Cons
|
|
50
|
+
|
|
51
|
+
<Left>
|
|
52
|
+
## Pros
|
|
53
|
+
|
|
54
|
+
- Fast
|
|
55
|
+
- Simple
|
|
56
|
+
</Left>
|
|
57
|
+
|
|
58
|
+
<Right>
|
|
59
|
+
## Cons
|
|
60
|
+
|
|
61
|
+
- Limited
|
|
62
|
+
</Right>`,
|
|
63
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean Honeydeck layout map.
|
|
3
|
+
*
|
|
4
|
+
* Same layout names and slot API as `@honeydeck/honeydeck/layouts`, with an explicit
|
|
5
|
+
* import path for decks that want to name the clean kit directly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { LayoutMap } from "../../runtime/types.ts";
|
|
9
|
+
import BlankLayout from "./Blank.tsx";
|
|
10
|
+
import CoverLayout from "./Cover.tsx";
|
|
11
|
+
import DefaultLayout from "./Default.tsx";
|
|
12
|
+
import ImageLayout from "./Image/Image.tsx";
|
|
13
|
+
import ImageLeftLayout from "./ImageLeft.tsx";
|
|
14
|
+
import ImageRightLayout from "./ImageRight.tsx";
|
|
15
|
+
import SectionLayout from "./Section.tsx";
|
|
16
|
+
import TwoColLayout from "./TwoCol.tsx";
|
|
17
|
+
|
|
18
|
+
const cleanLayouts: LayoutMap = {
|
|
19
|
+
Default: DefaultLayout,
|
|
20
|
+
Blank: BlankLayout,
|
|
21
|
+
Cover: CoverLayout,
|
|
22
|
+
Section: SectionLayout,
|
|
23
|
+
TwoCol: TwoColLayout,
|
|
24
|
+
Image: ImageLayout,
|
|
25
|
+
ImageLeft: ImageLeftLayout,
|
|
26
|
+
ImageRight: ImageRightLayout,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default cleanLayouts;
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
BlankLayout,
|
|
33
|
+
CoverLayout,
|
|
34
|
+
DefaultLayout,
|
|
35
|
+
ImageLayout,
|
|
36
|
+
ImageLeftLayout,
|
|
37
|
+
ImageRightLayout,
|
|
38
|
+
SectionLayout,
|
|
39
|
+
TwoColLayout,
|
|
40
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Honeydeck layout map.
|
|
3
|
+
*
|
|
4
|
+
* Exported when no custom `layouts:` path is specified in deck frontmatter.
|
|
5
|
+
* Defaults are intentionally clean and minimal. Use
|
|
6
|
+
* `@honeydeck/honeydeck/layouts/bee` for the original playful Bee layouts.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* // layouts/index.ts — extend the default map
|
|
11
|
+
* import defaultLayouts from '@honeydeck/honeydeck/layouts'
|
|
12
|
+
* import { MyBrandCover } from './BrandCover'
|
|
13
|
+
*
|
|
14
|
+
* export default {
|
|
15
|
+
* ...defaultLayouts,
|
|
16
|
+
* Cover: MyBrandCover,
|
|
17
|
+
* } satisfies LayoutMap
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { LayoutMap } from "../runtime/types.ts";
|
|
22
|
+
import { ColorModeImage } from "./ColorModeImage.tsx";
|
|
23
|
+
import BlankLayout from "./clean/Blank.tsx";
|
|
24
|
+
import CoverLayout from "./clean/Cover.tsx";
|
|
25
|
+
import DefaultLayout from "./clean/Default.tsx";
|
|
26
|
+
import ImageLayout from "./clean/Image/Image.tsx";
|
|
27
|
+
import ImageLeftLayout from "./clean/ImageLeft.tsx";
|
|
28
|
+
import ImageRightLayout from "./clean/ImageRight.tsx";
|
|
29
|
+
import SectionLayout from "./clean/Section.tsx";
|
|
30
|
+
import TwoColLayout from "./clean/TwoCol.tsx";
|
|
31
|
+
|
|
32
|
+
const defaultLayouts: LayoutMap = {
|
|
33
|
+
Default: DefaultLayout,
|
|
34
|
+
Blank: BlankLayout,
|
|
35
|
+
Cover: CoverLayout,
|
|
36
|
+
Section: SectionLayout,
|
|
37
|
+
TwoCol: TwoColLayout,
|
|
38
|
+
Image: ImageLayout,
|
|
39
|
+
ImageLeft: ImageLeftLayout,
|
|
40
|
+
ImageRight: ImageRightLayout,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default defaultLayouts;
|
|
44
|
+
|
|
45
|
+
export type {
|
|
46
|
+
ColorModeImageProps,
|
|
47
|
+
DarkModeImageFrontmatter,
|
|
48
|
+
} from "./ColorModeImage.tsx";
|
|
49
|
+
// Re-export individual layouts for named imports
|
|
50
|
+
export {
|
|
51
|
+
BlankLayout,
|
|
52
|
+
ColorModeImage,
|
|
53
|
+
CoverLayout,
|
|
54
|
+
DefaultLayout,
|
|
55
|
+
ImageLayout,
|
|
56
|
+
ImageLeftLayout,
|
|
57
|
+
ImageRightLayout,
|
|
58
|
+
SectionLayout,
|
|
59
|
+
TwoColLayout,
|
|
60
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import imagePlaceholderUrl from "./bee/Image/placeholder.webp";
|
|
2
|
+
import imagePlaceholderDarkUrl from "./bee/Image/placeholder-dark.webp";
|
|
3
|
+
import verticalImagePlaceholderUrl from "./bee/Image/placeholder-vertical.webp";
|
|
4
|
+
import verticalImagePlaceholderDarkUrl from "./bee/Image/placeholder-vertical-dark.webp";
|
|
5
|
+
|
|
6
|
+
export const imagePlaceholder = imagePlaceholderUrl;
|
|
7
|
+
export const imagePlaceholderDark = imagePlaceholderDarkUrl;
|
|
8
|
+
export const verticalImagePlaceholder = verticalImagePlaceholderUrl;
|
|
9
|
+
export const verticalImagePlaceholderDark = verticalImagePlaceholderDarkUrl;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility helpers for Honeydeck layout components.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Joins class names, filtering falsy values. No external dependencies. */
|
|
6
|
+
export function cn(...classes: (string | undefined | null | false)[]): string {
|
|
7
|
+
return classes.filter(Boolean).join(" ");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Returns true when `title` is a non-empty string. */
|
|
11
|
+
export function hasTitle(title: unknown): title is string {
|
|
12
|
+
return title != null && title !== "";
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Honeydeck Remark Transform Specification
|
|
2
|
+
|
|
3
|
+
> Observable behavior for code highlighting transforms.
|
|
4
|
+
|
|
5
|
+
## Code Highlighting
|
|
6
|
+
|
|
7
|
+
### Syntax Highlighting
|
|
8
|
+
|
|
9
|
+
Honeydeck highlights fenced code blocks without author configuration. Unsupported or failed languages render as plain code using Honeydeck CSS tokens.
|
|
10
|
+
|
|
11
|
+
### Step-Through Syntax
|
|
12
|
+
|
|
13
|
+
````mdx
|
|
14
|
+
```ts {2|4|all}
|
|
15
|
+
const a = 1
|
|
16
|
+
const b = 2
|
|
17
|
+
console.log(a + b)
|
|
18
|
+
```
|
|
19
|
+
````
|
|
20
|
+
|
|
21
|
+
Syntax:
|
|
22
|
+
|
|
23
|
+
- `{2}` — highlight line 2 immediately; adds 0 timeline steps
|
|
24
|
+
- `{2-3}` — highlight lines 2 through 3 immediately; adds 0 timeline steps
|
|
25
|
+
- `{1,3}` — highlight lines 1 and 3 immediately; adds 0 timeline steps
|
|
26
|
+
- `{2-3|5|all}` — start with lines 2-3 active, then step to line 5, then all
|
|
27
|
+
- `|` separates code highlight groups; every group after the first adds one timeline step
|
|
28
|
+
|
|
29
|
+
### Behavior
|
|
30
|
+
|
|
31
|
+
- Stepped code blocks show the first metadata group immediately, including at slide step 0 when the block is visible
|
|
32
|
+
- Each later code group adds one timeline step
|
|
33
|
+
- `{1|3-4|6-7|all}` starts with line 1 active, then steps to lines 3-4, then lines 6-7, then all lines
|
|
34
|
+
- Each active code group highlights only the specified lines (non-cumulative)
|
|
35
|
+
- Non-highlighted lines are dimmed (`--honeydeck-code-line-dim-opacity`)
|
|
36
|
+
- Dimming is applied by Honeydeck runtime markup/styles independently of Tailwind utility generation
|
|
37
|
+
- Code steps participate in the same slide timeline as `Reveal` components
|
|
38
|
+
- Unsupported/failed languages render as plain code using Honeydeck CSS tokens
|
|
39
|
+
- Code blocks expose a hover/focus copy control that copies the original fenced code text, not the highlighted HTML
|
|
40
|
+
|
|
41
|
+
### Visual Style
|
|
42
|
+
|
|
43
|
+
- Highlighted lines: normal brightness
|
|
44
|
+
- Non-highlighted lines: dimmed (reduced opacity)
|
|
45
|
+
- No background stripe
|
|
46
|
+
|
|
47
|
+
### Theme
|
|
48
|
+
|
|
49
|
+
Code highlighting colors come from Honeydeck's built-in light/dark syntax themes, not from Honeydeck CSS tokens. Honeydeck does not expose syntax theme configuration. The `--honeydeck-code-line-dim-opacity` token controls dimming of non-highlighted lines.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remark plugin: extract the first h1 from each slide and parse YAML frontmatter.
|
|
3
|
+
*
|
|
4
|
+
* ### What it does
|
|
5
|
+
* 1. **Frontmatter extraction** — visits the `yaml` node created by
|
|
6
|
+
* `remark-frontmatter`, parses the YAML string into a plain object, and
|
|
7
|
+
* stores it as `vfile.data.frontmatter`. This makes per-slide frontmatter
|
|
8
|
+
* (e.g. `layout: Cover`) available to the virtual-modules plugin without
|
|
9
|
+
* an extra YAML parsing library.
|
|
10
|
+
*
|
|
11
|
+
* 2. **H1 extraction** — finds the first `heading[depth=1]` node in the AST,
|
|
12
|
+
* reads its plain-text content via `mdast-util-to-string`, removes the node
|
|
13
|
+
* from the tree, and stores the text as `vfile.data.title`. Layouts can
|
|
14
|
+
* then render the title independently from the body, keeping its position
|
|
15
|
+
* stable regardless of how many steps have been revealed.
|
|
16
|
+
*
|
|
17
|
+
* ### Plugin ordering
|
|
18
|
+
* This plugin must run AFTER `remark-frontmatter` (which creates the `yaml`
|
|
19
|
+
* node) and BEFORE any plugins that rely on a cleaned-up AST.
|
|
20
|
+
*
|
|
21
|
+
* Recommended order:
|
|
22
|
+
* ```ts
|
|
23
|
+
* remarkPlugins: [remarkFrontmatter, remarkH1Extract, remarkStepNumbering]
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { Heading, Parent, Root } from "mdast";
|
|
28
|
+
import { toString as mdastToString } from "mdast-util-to-string";
|
|
29
|
+
import type { Plugin } from "unified";
|
|
30
|
+
import { visit } from "unist-util-visit";
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Simple YAML parser — handles flat key: value pairs.
|
|
34
|
+
// Coerces booleans and numbers; everything else stays as a string.
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function parseFlatYaml(yaml: string): Record<string, unknown> {
|
|
38
|
+
const result: Record<string, unknown> = {};
|
|
39
|
+
|
|
40
|
+
for (const line of yaml.split("\n")) {
|
|
41
|
+
const colonIdx = line.indexOf(":");
|
|
42
|
+
if (colonIdx === -1) continue;
|
|
43
|
+
|
|
44
|
+
const key = line.slice(0, colonIdx).trim();
|
|
45
|
+
const raw = line.slice(colonIdx + 1).trim();
|
|
46
|
+
|
|
47
|
+
if (!key) continue;
|
|
48
|
+
|
|
49
|
+
if (raw === "true") {
|
|
50
|
+
result[key] = true;
|
|
51
|
+
} else if (raw === "false") {
|
|
52
|
+
result[key] = false;
|
|
53
|
+
} else if (raw !== "" && !Number.isNaN(Number(raw))) {
|
|
54
|
+
result[key] = Number(raw);
|
|
55
|
+
} else if (
|
|
56
|
+
(raw.startsWith('"') && raw.endsWith('"')) ||
|
|
57
|
+
(raw.startsWith("'") && raw.endsWith("'"))
|
|
58
|
+
) {
|
|
59
|
+
result[key] = raw.slice(1, -1);
|
|
60
|
+
} else {
|
|
61
|
+
result[key] = raw;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Plugin
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Remark plugin that:
|
|
74
|
+
* 1. Parses `yaml` nodes (from remark-frontmatter) into `vfile.data.frontmatter`
|
|
75
|
+
* 2. Removes the first `h1` from the tree and stores its text in `vfile.data.title`
|
|
76
|
+
*/
|
|
77
|
+
export const remarkH1Extract: Plugin<[], Root> = () => (tree, vfile) => {
|
|
78
|
+
// ── Step 1: parse YAML frontmatter ──────────────────────────────────────
|
|
79
|
+
visit(tree, "yaml", (node) => {
|
|
80
|
+
const yamlNode = node as unknown as { value: string };
|
|
81
|
+
vfile.data.frontmatter = parseFlatYaml(yamlNode.value);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ── Step 2: find and remove first h1 ────────────────────────────────────
|
|
85
|
+
// We collect the location during the visit and mutate after, to avoid
|
|
86
|
+
// iterator invalidation while unist-util-visit walks the tree.
|
|
87
|
+
|
|
88
|
+
type H1Location = {
|
|
89
|
+
index: number;
|
|
90
|
+
parent: Parent;
|
|
91
|
+
text: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
let firstH1: H1Location | null = null;
|
|
95
|
+
|
|
96
|
+
visit(tree, "heading", (node, index, parent) => {
|
|
97
|
+
if (firstH1) return; // already found one — stop collecting
|
|
98
|
+
const heading = node as unknown as Heading;
|
|
99
|
+
if (
|
|
100
|
+
heading.depth === 1 &&
|
|
101
|
+
parent &&
|
|
102
|
+
index !== null &&
|
|
103
|
+
index !== undefined
|
|
104
|
+
) {
|
|
105
|
+
firstH1 = {
|
|
106
|
+
index: index as number,
|
|
107
|
+
parent: parent as unknown as Parent,
|
|
108
|
+
text: mdastToString(heading),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (firstH1) {
|
|
114
|
+
// Remove the h1 node from its parent
|
|
115
|
+
(firstH1 as H1Location).parent.children.splice(
|
|
116
|
+
(firstH1 as H1Location).index,
|
|
117
|
+
1,
|
|
118
|
+
);
|
|
119
|
+
vfile.data.title = (firstH1 as H1Location).text;
|
|
120
|
+
} else {
|
|
121
|
+
// No h1 found — title is empty string
|
|
122
|
+
vfile.data.title = "";
|
|
123
|
+
}
|
|
124
|
+
};
|