@opendocsdev/cli 0.2.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.
Files changed (78) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +300 -0
  3. package/dist/bin/opendocs.js +712 -0
  4. package/dist/bin/opendocs.js.map +1 -0
  5. package/dist/templates/api-reference.mdx +308 -0
  6. package/dist/templates/components.mdx +286 -0
  7. package/dist/templates/configuration.mdx +190 -0
  8. package/dist/templates/docs.json +27 -0
  9. package/dist/templates/introduction.mdx +25 -0
  10. package/dist/templates/logo.svg +4 -0
  11. package/dist/templates/quickstart.mdx +59 -0
  12. package/dist/templates/writing-content.mdx +236 -0
  13. package/package.json +92 -0
  14. package/src/engine/astro.config.ts +75 -0
  15. package/src/engine/src/components/Analytics.astro +57 -0
  16. package/src/engine/src/components/ApiPlayground.astro +24 -0
  17. package/src/engine/src/components/Callout.astro +66 -0
  18. package/src/engine/src/components/Card.astro +75 -0
  19. package/src/engine/src/components/CardGroup.astro +29 -0
  20. package/src/engine/src/components/CodeGroup.astro +231 -0
  21. package/src/engine/src/components/CopyButton.astro +179 -0
  22. package/src/engine/src/components/Steps.astro +27 -0
  23. package/src/engine/src/components/Tab.astro +21 -0
  24. package/src/engine/src/components/TableOfContents.astro +119 -0
  25. package/src/engine/src/components/Tabs.astro +135 -0
  26. package/src/engine/src/components/index.ts +107 -0
  27. package/src/engine/src/components/react/ApiPlayground/AuthSection.tsx +91 -0
  28. package/src/engine/src/components/react/ApiPlayground/CodeBlock.tsx +66 -0
  29. package/src/engine/src/components/react/ApiPlayground/CodeSnippets.tsx +66 -0
  30. package/src/engine/src/components/react/ApiPlayground/CollapsibleSection.tsx +26 -0
  31. package/src/engine/src/components/react/ApiPlayground/KeyValueEditor.tsx +58 -0
  32. package/src/engine/src/components/react/ApiPlayground/ResponseDisplay.tsx +109 -0
  33. package/src/engine/src/components/react/ApiPlayground/Spinner.tsx +6 -0
  34. package/src/engine/src/components/react/ApiPlayground/constants.ts +16 -0
  35. package/src/engine/src/components/react/ApiPlayground/generators.test.ts +130 -0
  36. package/src/engine/src/components/react/ApiPlayground/generators.ts +75 -0
  37. package/src/engine/src/components/react/ApiPlayground/index.tsx +490 -0
  38. package/src/engine/src/components/react/ApiPlayground/types.ts +35 -0
  39. package/src/engine/src/components/react/Callout.tsx +54 -0
  40. package/src/engine/src/components/react/Card.tsx +48 -0
  41. package/src/engine/src/components/react/CardGroup.tsx +24 -0
  42. package/src/engine/src/components/react/FeedbackWidget.tsx +166 -0
  43. package/src/engine/src/components/react/GitHubLink.tsx +28 -0
  44. package/src/engine/src/components/react/NavigationCard.tsx +53 -0
  45. package/src/engine/src/components/react/PageActions.tsx +124 -0
  46. package/src/engine/src/components/react/PageFooter.tsx +91 -0
  47. package/src/engine/src/components/react/SearchModal.tsx +358 -0
  48. package/src/engine/src/components/react/SearchProvider.tsx +37 -0
  49. package/src/engine/src/components/react/Sidebar.tsx +369 -0
  50. package/src/engine/src/components/react/SidebarSearchTrigger.tsx +57 -0
  51. package/src/engine/src/components/react/Steps.tsx +25 -0
  52. package/src/engine/src/components/react/ThemeToggle.tsx +72 -0
  53. package/src/engine/src/components/react/index.ts +14 -0
  54. package/src/engine/src/env.d.ts +10 -0
  55. package/src/engine/src/layouts/DocsLayout.astro +357 -0
  56. package/src/engine/src/lib/__tests__/markdown.test.ts +124 -0
  57. package/src/engine/src/lib/__tests__/mdx-loader.test.ts +205 -0
  58. package/src/engine/src/lib/config.ts +79 -0
  59. package/src/engine/src/lib/markdown.ts +54 -0
  60. package/src/engine/src/lib/mdx-loader.ts +143 -0
  61. package/src/engine/src/lib/mdx-utils.ts +72 -0
  62. package/src/engine/src/lib/remark-opendocs.ts +195 -0
  63. package/src/engine/src/lib/utils.ts +221 -0
  64. package/src/engine/src/pages/[...slug].astro +115 -0
  65. package/src/engine/src/pages/index.astro +71 -0
  66. package/src/engine/src/scripts/mobile-sidebar.ts +56 -0
  67. package/src/engine/src/scripts/theme-init.ts +25 -0
  68. package/src/engine/src/styles/global.css +703 -0
  69. package/src/engine/tailwind.config.mjs +60 -0
  70. package/src/engine/tsconfig.json +15 -0
  71. package/src/templates/api-reference.mdx +308 -0
  72. package/src/templates/components.mdx +286 -0
  73. package/src/templates/configuration.mdx +190 -0
  74. package/src/templates/docs.json +27 -0
  75. package/src/templates/introduction.mdx +25 -0
  76. package/src/templates/logo.svg +4 -0
  77. package/src/templates/quickstart.mdx +59 -0
  78. package/src/templates/writing-content.mdx +236 -0
@@ -0,0 +1,236 @@
1
+ ---
2
+ title: Writing Content
3
+ description: Learn how to write effective documentation with MDX
4
+ ---
5
+
6
+ # Writing Content
7
+
8
+ opendocs uses MDX, which combines Markdown with JSX components for rich documentation.
9
+
10
+ ## Frontmatter
11
+
12
+ Every page should start with frontmatter that defines metadata:
13
+
14
+ ```mdx
15
+ ---
16
+ title: Page Title
17
+ description: A brief description for SEO and previews
18
+ ---
19
+
20
+ # Your Content Here
21
+ ```
22
+
23
+ ### Frontmatter Fields
24
+
25
+ | Field | Required | Description |
26
+ |-------|----------|-------------|
27
+ | `title` | Yes | Page title (shown in browser tab and navigation) |
28
+ | `description` | No | Short description for SEO and social sharing |
29
+
30
+ ## Markdown Basics
31
+
32
+ opendocs supports all standard Markdown syntax.
33
+
34
+ ### Headings
35
+
36
+ ```markdown
37
+ # Heading 1
38
+ ## Heading 2
39
+ ### Heading 3
40
+ #### Heading 4
41
+ ```
42
+
43
+ <Callout type="tip">
44
+ Use `##` (H2) for main sections. These appear in the "On this page" table of contents.
45
+ </Callout>
46
+
47
+ ### Text Formatting
48
+
49
+ | Syntax | Result |
50
+ |--------|--------|
51
+ | `**bold**` | **bold** |
52
+ | `*italic*` | *italic* |
53
+ | `~~strikethrough~~` | ~~strikethrough~~ |
54
+ | `` `inline code` `` | `inline code` |
55
+
56
+ ### Lists
57
+
58
+ **Unordered lists:**
59
+
60
+ ```markdown
61
+ - First item
62
+ - Second item
63
+ - Nested item
64
+ - Another nested item
65
+ - Third item
66
+ ```
67
+
68
+ - First item
69
+ - Second item
70
+ - Nested item
71
+ - Another nested item
72
+ - Third item
73
+
74
+ **Ordered lists:**
75
+
76
+ ```markdown
77
+ 1. First step
78
+ 2. Second step
79
+ 3. Third step
80
+ ```
81
+
82
+ 1. First step
83
+ 2. Second step
84
+ 3. Third step
85
+
86
+ ### Links
87
+
88
+ ```markdown
89
+ [Link text](https://example.com)
90
+ [Internal link](/quickstart)
91
+ ```
92
+
93
+ [Link text](https://example.com) | [Internal link](/quickstart)
94
+
95
+ ### Images
96
+
97
+ Place images in the `public` directory and reference them:
98
+
99
+ ```markdown
100
+ ![Alt text](/images/screenshot.png)
101
+ ```
102
+
103
+ ### Tables
104
+
105
+ ```markdown
106
+ | Header 1 | Header 2 | Header 3 |
107
+ |----------|----------|----------|
108
+ | Cell 1 | Cell 2 | Cell 3 |
109
+ | Cell 4 | Cell 5 | Cell 6 |
110
+ ```
111
+
112
+ | Header 1 | Header 2 | Header 3 |
113
+ |----------|----------|----------|
114
+ | Cell 1 | Cell 2 | Cell 3 |
115
+ | Cell 4 | Cell 5 | Cell 6 |
116
+
117
+ ### Blockquotes
118
+
119
+ ```markdown
120
+ > This is a blockquote. Use it to highlight quotes or important notes.
121
+ ```
122
+
123
+ > This is a blockquote. Use it to highlight quotes or important notes.
124
+
125
+ ## Code Blocks
126
+
127
+ ### Basic Code Blocks
128
+
129
+ Use triple backticks with a language identifier:
130
+
131
+ ```javascript
132
+ function greet(name) {
133
+ return `Hello, ${name}!`;
134
+ }
135
+ ```
136
+
137
+ ```javascript
138
+ function greet(name) {
139
+ return `Hello, ${name}!`;
140
+ }
141
+ ```
142
+
143
+ ### Supported Languages
144
+
145
+ opendocs supports syntax highlighting for 100+ languages including:
146
+
147
+ - JavaScript, TypeScript, JSX, TSX
148
+ - Python, Ruby, Go, Rust
149
+ - HTML, CSS, SCSS
150
+ - JSON, YAML, TOML
151
+ - Bash, Shell
152
+ - SQL, GraphQL
153
+ - And many more...
154
+
155
+ ### Code Groups for Multiple Languages
156
+
157
+ Use the `CodeGroup` component to show code in multiple languages:
158
+
159
+ <CodeGroup>
160
+ ```javascript Node.js
161
+ const express = require('express');
162
+ const app = express();
163
+
164
+ app.get('/', (req, res) => {
165
+ res.send('Hello World!');
166
+ });
167
+ ```
168
+
169
+ ```python Flask
170
+ from flask import Flask
171
+ app = Flask(__name__)
172
+
173
+ @app.route('/')
174
+ def hello():
175
+ return 'Hello World!'
176
+ ```
177
+
178
+ ```go Go
179
+ package main
180
+
181
+ import "net/http"
182
+
183
+ func main() {
184
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
185
+ w.Write([]byte("Hello World!"))
186
+ })
187
+ http.ListenAndServe(":8080", nil)
188
+ }
189
+ ```
190
+ </CodeGroup>
191
+
192
+ ## Using Components
193
+
194
+ Import and use built-in components directly in your MDX:
195
+
196
+ ```mdx
197
+ <Callout type="info">
198
+ This is an informational callout.
199
+ </Callout>
200
+
201
+ <CardGroup cols={2}>
202
+ <Card title="Card 1" description="Description" href="/page1" />
203
+ <Card title="Card 2" description="Description" href="/page2" />
204
+ </CardGroup>
205
+ ```
206
+
207
+ See the [Components](/components) page for all available components.
208
+
209
+ ## Best Practices
210
+
211
+ <CardGroup cols={2}>
212
+ <Card
213
+ title="Keep it concise"
214
+ description="Write short paragraphs. Use lists and tables for clarity."
215
+ icon="✂️"
216
+ />
217
+ <Card
218
+ title="Use examples"
219
+ description="Show, don't tell. Include code samples and screenshots."
220
+ icon="💻"
221
+ />
222
+ <Card
223
+ title="Structure content"
224
+ description="Use headings to create a clear hierarchy."
225
+ icon="📋"
226
+ />
227
+ <Card
228
+ title="Add callouts"
229
+ description="Highlight important info, warnings, and tips."
230
+ icon="💡"
231
+ />
232
+ </CardGroup>
233
+
234
+ <Callout type="tip">
235
+ Write documentation as if the reader is in a hurry. Get to the point quickly and make information easy to scan.
236
+ </Callout>
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@opendocsdev/cli",
3
+ "version": "0.2.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Beautiful docs with zero config. Open-source Mintlify alternative.",
8
+ "license": "AGPL-3.0",
9
+ "author": "Chigala",
10
+ "homepage": "https://github.com/chigala/opendocs#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/chigala/opendocs/issues"
13
+ },
14
+ "keywords": [
15
+ "documentation",
16
+ "docs",
17
+ "mdx",
18
+ "astro",
19
+ "cli",
20
+ "static-site",
21
+ "mintlify",
22
+ "opendocs"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/chigala/opendocs.git",
27
+ "directory": "packages/cli"
28
+ },
29
+ "engines": {
30
+ "node": ">=20.0.0"
31
+ },
32
+ "type": "module",
33
+ "bin": {
34
+ "opendocs": "./dist/bin/opendocs.js"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "src/engine/astro.config.ts",
39
+ "src/engine/tailwind.config.mjs",
40
+ "src/engine/tsconfig.json",
41
+ "src/engine/src",
42
+ "src/templates"
43
+ ],
44
+ "dependencies": {
45
+ "@astrojs/mdx": "^3.1.0",
46
+ "@astrojs/react": "^4.4.2",
47
+ "@astrojs/tailwind": "^5.1.0",
48
+ "@fontsource-variable/geist": "^5.2.8",
49
+ "@fontsource-variable/geist-mono": "^5.2.7",
50
+ "@inquirer/prompts": "^8.2.0",
51
+ "@lucide/astro": "^0.563.0",
52
+ "@mdx-js/mdx": "^3.1.1",
53
+ "@types/react": "^18.2.0",
54
+ "@types/react-dom": "^18.2.0",
55
+ "astro": "^4.16.0",
56
+ "chalk": "^5.3.0",
57
+ "clsx": "^2.0.0",
58
+ "commander": "^12.1.0",
59
+ "fast-glob": "^3.3.2",
60
+ "fs-extra": "^11.2.0",
61
+ "lucide-react": "^0.563.0",
62
+ "ora": "^8.1.1",
63
+ "pagefind": "^1.2.0",
64
+ "react": "^18.2.0",
65
+ "react-dom": "^18.2.0",
66
+ "rehype-shiki": "^0.0.9",
67
+ "rehype-stringify": "^10.0.1",
68
+ "remark-gfm": "^4.0.1",
69
+ "remark-parse": "^11.0.0",
70
+ "remark-rehype": "^11.1.2",
71
+ "shiki": "^1.29.2",
72
+ "tailwind-merge": "^3.4.0",
73
+ "tar-stream": "^3.1.7",
74
+ "unified": "^11.0.5",
75
+ "unist-util-visit": "^5.1.0",
76
+ "zod": "^3.24.0"
77
+ },
78
+ "devDependencies": {
79
+ "@types/fs-extra": "^11.0.4",
80
+ "@types/node": "^22.0.0",
81
+ "@types/tar-stream": "^3.1.4",
82
+ "tsup": "^8.3.5",
83
+ "typescript": "^5.7.0",
84
+ "vitest": "^1.6.0"
85
+ },
86
+ "scripts": {
87
+ "build": "tsup",
88
+ "typecheck": "tsc --noEmit",
89
+ "test": "vitest run",
90
+ "test:watch": "vitest"
91
+ }
92
+ }
@@ -0,0 +1,75 @@
1
+ import { defineConfig } from "astro/config";
2
+ import mdx from "@astrojs/mdx";
3
+ import tailwind from "@astrojs/tailwind";
4
+ import react from "@astrojs/react";
5
+ import remarkGfm from "remark-gfm";
6
+ import { remarkOpendocs } from "./src/lib/remark-opendocs";
7
+ import path from "node:path";
8
+ import type { ShikiTransformer } from "shiki";
9
+
10
+ // Get the user's project directory from environment variable
11
+ const projectDir = process.env.OPENDOCS_PROJECT_DIR || process.cwd();
12
+
13
+ // Get the engine directory (where this config file lives)
14
+ const engineDir = new URL(".", import.meta.url).pathname;
15
+
16
+ // Custom Shiki transformer to expose meta string as data-title attribute
17
+ // This allows CodeGroup to extract tab labels from code fences like: ```bash npm
18
+ const metaTransformer: ShikiTransformer = {
19
+ name: "meta-to-data-title",
20
+ pre(node) {
21
+ const meta = this.options.meta?.__raw;
22
+ if (meta) {
23
+ node.properties["data-title"] = meta;
24
+ }
25
+ },
26
+ };
27
+
28
+ export default defineConfig({
29
+ prefetch: {
30
+ prefetchAll: true, // Prefetch all links for instant navigation
31
+ },
32
+ integrations: [
33
+ react(),
34
+ mdx(),
35
+ tailwind({
36
+ applyBaseStyles: false,
37
+ }),
38
+ ],
39
+ markdown: {
40
+ remarkPlugins: [remarkGfm, remarkOpendocs],
41
+ shikiConfig: {
42
+ themes: {
43
+ light: "github-light",
44
+ dark: "github-dark",
45
+ },
46
+ defaultColor: false,
47
+ langs: [],
48
+ wrap: true,
49
+ transformers: [metaTransformer],
50
+ },
51
+ },
52
+ // Allow importing MDX files from the user's project directory
53
+ vite: {
54
+ resolve: {
55
+ alias: [
56
+ // @content alias points to the user's project directory
57
+ // Used by mdx-loader.ts to glob all MDX/MD files
58
+ { find: "@content", replacement: projectDir },
59
+ {
60
+ find: "@opendocs/components",
61
+ replacement: `${engineDir}src/components/react/index.ts`,
62
+ },
63
+ ],
64
+ },
65
+ server: {
66
+ watch: {
67
+ ignored: [`!${projectDir}/**`],
68
+ },
69
+ },
70
+ },
71
+ // Output directory relative to the engine
72
+ outDir: path.join(projectDir, "dist"),
73
+ // Serve content from the user's project directory
74
+ publicDir: path.join(projectDir, "public"),
75
+ });
@@ -0,0 +1,57 @@
1
+ ---
2
+ /**
3
+ * Analytics - Pageview tracking component
4
+ * Sends analytics to backend if configured in docs.json
5
+ */
6
+ import { loadConfig } from "../lib/config.js";
7
+
8
+ interface Props {
9
+ path?: string;
10
+ }
11
+
12
+ const { path: propPath } = Astro.props;
13
+
14
+ // Load config and extract analytics settings
15
+ const config = await loadConfig();
16
+ const backendConfig = config.backend as { apiUrl?: string; siteId?: string } | undefined;
17
+ const backend = backendConfig?.apiUrl;
18
+ const siteId = backendConfig?.siteId;
19
+ const analyticsEnabled = config.features?.analytics !== false;
20
+
21
+ // Only render script if backend and siteId are configured
22
+ const shouldTrack = backend && siteId && analyticsEnabled;
23
+ ---
24
+
25
+ {shouldTrack && (
26
+ <script
27
+ data-backend={backend}
28
+ data-site-id={siteId}
29
+ data-path={propPath}
30
+ is:inline
31
+ >
32
+ (function() {
33
+ // Get configuration from script tag
34
+ const script = document.currentScript;
35
+ const backend = script.getAttribute('data-backend');
36
+ const siteId = script.getAttribute('data-site-id');
37
+ const pathFromProp = script.getAttribute('data-path');
38
+
39
+ // Use provided path or current pathname
40
+ const path = pathFromProp || window.location.pathname;
41
+
42
+ // Send pageview to backend (short path to avoid ad blockers)
43
+ fetch(`${backend}/api/analytics/pv`, {
44
+ method: 'POST',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ },
48
+ body: JSON.stringify({
49
+ siteId: siteId,
50
+ path: path,
51
+ }),
52
+ }).catch(function(err) {
53
+ console.debug('[opendocs] Analytics error:', err);
54
+ });
55
+ })();
56
+ </script>
57
+ )}
@@ -0,0 +1,24 @@
1
+ ---
2
+ import ApiPlaygroundReact from "./react/ApiPlayground/index";
3
+
4
+ export interface Props {
5
+ endpoint: string;
6
+ method: string;
7
+ baseUrl: string;
8
+ defaultHeaders?: Record<string, string>;
9
+ defaultBody?: Record<string, unknown> | string;
10
+ defaultParams?: Record<string, string>;
11
+ }
12
+
13
+ const { endpoint, method, baseUrl, defaultHeaders, defaultBody, defaultParams } = Astro.props;
14
+ ---
15
+
16
+ <ApiPlaygroundReact
17
+ client:load
18
+ endpoint={endpoint}
19
+ method={method}
20
+ baseUrl={baseUrl}
21
+ defaultHeaders={defaultHeaders}
22
+ defaultBody={defaultBody}
23
+ defaultParams={defaultParams}
24
+ />
@@ -0,0 +1,66 @@
1
+ ---
2
+ /**
3
+ * Callout - Informational alert boxes
4
+ * Pure Astro component - no client-side JavaScript needed
5
+ */
6
+ import { Info, AlertTriangle, XCircle, Lightbulb } from '@lucide/astro';
7
+
8
+ interface Props {
9
+ type?: "info" | "warning" | "error" | "tip";
10
+ title?: string;
11
+ class?: string;
12
+ }
13
+
14
+ const { type = "info", title, class: className } = Astro.props;
15
+
16
+ const variants = {
17
+ info: {
18
+ border: "border-l-[var(--color-info)]",
19
+ iconColor: "text-[var(--color-info)]",
20
+ icon: Info,
21
+ },
22
+ warning: {
23
+ border: "border-l-[var(--color-warning)]",
24
+ iconColor: "text-[var(--color-warning)]",
25
+ icon: AlertTriangle,
26
+ },
27
+ error: {
28
+ border: "border-l-[var(--color-error)]",
29
+ iconColor: "text-[var(--color-error)]",
30
+ icon: XCircle,
31
+ },
32
+ tip: {
33
+ border: "border-l-[var(--color-success)]",
34
+ iconColor: "text-[var(--color-success)]",
35
+ icon: Lightbulb,
36
+ },
37
+ };
38
+
39
+ const variant = variants[type] || variants.info;
40
+ const Icon = variant.icon;
41
+ ---
42
+
43
+ <div
44
+ class:list={[
45
+ "callout rounded-lg border-l-4 my-6 p-4",
46
+ "bg-[var(--color-surface-raised)]",
47
+ variant.border,
48
+ className,
49
+ ]}
50
+ role="alert"
51
+ data-type={type}
52
+ >
53
+ <div class="flex gap-3">
54
+ <span class:list={["flex-shrink-0 mt-0.5", variant.iconColor]}>
55
+ <Icon class="w-5 h-5" aria-hidden="true" />
56
+ </span>
57
+ <div class="flex-1 min-w-0">
58
+ {title && (
59
+ <p class="font-semibold text-[var(--color-foreground)] mb-1">{title}</p>
60
+ )}
61
+ <div class="callout-content text-[var(--color-muted)] text-sm [&>p]:my-0 [&>p+p]:mt-2 [&>ul]:my-2 [&>ol]:my-2 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0">
62
+ <slot />
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
@@ -0,0 +1,75 @@
1
+ ---
2
+ /**
3
+ * Card - Content card with optional link
4
+ * Pure Astro component - no client-side JavaScript needed
5
+ */
6
+ interface Props {
7
+ title: string;
8
+ description?: string;
9
+ href?: string;
10
+ icon?: string;
11
+ class?: string;
12
+ }
13
+
14
+ const { title, description, href, icon, class: className } = Astro.props;
15
+ const isLink = !!href;
16
+ ---
17
+
18
+ {isLink ? (
19
+ <a
20
+ href={href}
21
+ class:list={[
22
+ "card group block p-6 rounded-xl cursor-pointer no-underline",
23
+ "bg-[var(--color-surface-raised)]",
24
+ className,
25
+ ]}
26
+ >
27
+ {icon && (
28
+ <span
29
+ class="card-icon flex items-center justify-center w-10 h-10 mb-4 rounded-lg bg-[var(--color-surface-sunken)] text-xl"
30
+ aria-hidden="true"
31
+ >
32
+ {icon}
33
+ </span>
34
+ )}
35
+ <h3 class="card-title text-base font-semibold m-0 text-[var(--color-primary)]">
36
+ {title}
37
+ </h3>
38
+ {description && (
39
+ <p class="card-description text-sm text-[var(--color-muted)] mt-2 mb-0">
40
+ {description}
41
+ </p>
42
+ )}
43
+ <div class="card-content text-sm text-[var(--color-muted)] mt-2">
44
+ <slot />
45
+ </div>
46
+ </a>
47
+ ) : (
48
+ <div
49
+ class:list={[
50
+ "card group block p-6 rounded-xl",
51
+ "bg-[var(--color-surface-raised)]",
52
+ className,
53
+ ]}
54
+ >
55
+ {icon && (
56
+ <span
57
+ class="card-icon flex items-center justify-center w-10 h-10 mb-4 rounded-lg bg-[var(--color-surface-sunken)] text-xl"
58
+ aria-hidden="true"
59
+ >
60
+ {icon}
61
+ </span>
62
+ )}
63
+ <h3 class="card-title text-base font-semibold m-0 text-[var(--color-foreground)]">
64
+ {title}
65
+ </h3>
66
+ {description && (
67
+ <p class="card-description text-sm text-[var(--color-muted)] mt-2 mb-0">
68
+ {description}
69
+ </p>
70
+ )}
71
+ <div class="card-content text-sm text-[var(--color-muted)] mt-2">
72
+ <slot />
73
+ </div>
74
+ </div>
75
+ )}
@@ -0,0 +1,29 @@
1
+ ---
2
+ /**
3
+ * CardGroup - Grid layout for Card components
4
+ * Pure Astro component - no client-side JavaScript needed
5
+ */
6
+ interface Props {
7
+ cols?: 1 | 2 | 3 | 4;
8
+ class?: string;
9
+ }
10
+
11
+ const { cols = 2, class: className } = Astro.props;
12
+
13
+ const colVariants: Record<number, string> = {
14
+ 1: "grid-cols-1",
15
+ 2: "grid-cols-1 sm:grid-cols-2",
16
+ 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
17
+ 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
18
+ };
19
+ ---
20
+
21
+ <div
22
+ class:list={[
23
+ "card-group grid gap-4 my-6 not-prose",
24
+ colVariants[cols],
25
+ className,
26
+ ]}
27
+ >
28
+ <slot />
29
+ </div>