create-cocoframe 0.0.1 → 0.0.3
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 +20 -18
- package/package.json +2 -1
- package/src/index.d.ts +5 -0
- package/src/index.js +28 -4
- package/template/app/routes/_layout.tsx +2 -1
- package/template/app/routes/index.page.tsx +8 -5
- package/template/app/styles/app.css +9 -24
- package/template/package.json +5 -4
- package/templates/catalog.json +41 -0
- package/templates/dashboard/README.md +10 -0
- package/templates/dashboard/_gitignore +8 -0
- package/templates/dashboard/app/routes/_layout.tsx +23 -0
- package/templates/dashboard/app/routes/api/health.route.ts +12 -0
- package/templates/dashboard/app/routes/index.page.tsx +29 -0
- package/templates/dashboard/app/styles/app.css +33 -0
- package/templates/dashboard/cocoframe.config.ts +2 -0
- package/templates/dashboard/package.json +26 -0
- package/templates/dashboard/tsconfig.json +17 -0
- package/templates/documentation/README.md +10 -0
- package/templates/documentation/_gitignore +8 -0
- package/templates/documentation/app/routes/_layout.tsx +17 -0
- package/templates/documentation/app/routes/api/health.route.ts +12 -0
- package/templates/documentation/app/routes/index.page.tsx +25 -0
- package/templates/documentation/app/styles/app.css +31 -0
- package/templates/documentation/cocoframe.config.ts +2 -0
- package/templates/documentation/package.json +26 -0
- package/templates/documentation/tsconfig.json +17 -0
- package/templates/marketing/README.md +10 -0
- package/templates/marketing/_gitignore +8 -0
- package/templates/marketing/app/routes/_layout.tsx +13 -0
- package/templates/marketing/app/routes/api/health.route.ts +12 -0
- package/templates/marketing/app/routes/index.page.tsx +39 -0
- package/templates/marketing/app/styles/app.css +32 -0
- package/templates/marketing/cocoframe.config.ts +8 -0
- package/templates/marketing/package.json +26 -0
- package/templates/marketing/tsconfig.json +17 -0
package/README.md
CHANGED
|
@@ -2,27 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
Dependency-free project creator for [CocoFrame](https://github.com/wiryosaputraofficial/cocoframe).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
~~~bash
|
|
5
|
+
```bash
|
|
8
6
|
npm create cocoframe@latest my-app
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
~~~
|
|
12
|
-
|
|
13
|
-
For development from the CocoFrame repository:
|
|
7
|
+
npm create cocoframe@latest my-dashboard -- --template dashboard
|
|
8
|
+
```
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
npm run dev --workspace=my-app
|
|
19
|
-
~~~
|
|
10
|
+
Available templates: `starter`, `marketing`, `dashboard`, and `documentation`.
|
|
11
|
+
Every template uses official `@cocoframe/ui` components and tree-shakeable
|
|
12
|
+
`@cocoframe/icons` imports.
|
|
20
13
|
|
|
21
14
|
## Options
|
|
22
15
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
16
|
+
- `--template starter|marketing|dashboard|documentation` selects the starter.
|
|
17
|
+
- `--package-manager npm|pnpm|yarn|bun` selects the installer.
|
|
18
|
+
- `--skip-install` or `--no-install` only writes the starter files.
|
|
19
|
+
- `--help` prints usage.
|
|
20
|
+
- `--version` prints the creator version.
|
|
21
|
+
|
|
22
|
+
The creator refuses filesystem roots and non-empty target directories. Each
|
|
23
|
+
starter includes a server-rendered page, typed health API, responsive CSS,
|
|
24
|
+
TypeScript configuration, and production scripts.
|
|
25
|
+
|
|
26
|
+
For development from the CocoFrame repository:
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
```bash
|
|
29
|
+
npm run create -- examples/my-app --template marketing --skip-install
|
|
30
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-cocoframe",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Dependency-free project creator for CocoFrame applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cocoframe",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
"src",
|
|
39
39
|
"template",
|
|
40
|
+
"templates",
|
|
40
41
|
"README.md",
|
|
41
42
|
"LICENSE"
|
|
42
43
|
]
|
package/src/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { spawn } from "node:child_process";
|
|
2
2
|
|
|
3
3
|
export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
|
|
4
|
+
export type TemplateName = "starter" | "marketing" | "dashboard" | "documentation";
|
|
4
5
|
|
|
5
6
|
export interface CreatorArguments {
|
|
6
7
|
projectDirectory?: string;
|
|
7
8
|
packageManager: PackageManager;
|
|
9
|
+
template: TemplateName;
|
|
8
10
|
install: boolean;
|
|
9
11
|
help: boolean;
|
|
10
12
|
version: boolean;
|
|
@@ -13,6 +15,7 @@ export interface CreatorArguments {
|
|
|
13
15
|
export interface ScaffoldOptions {
|
|
14
16
|
projectDirectory: string;
|
|
15
17
|
packageManager?: PackageManager;
|
|
18
|
+
template?: TemplateName;
|
|
16
19
|
install?: boolean;
|
|
17
20
|
cwd?: string;
|
|
18
21
|
templateDirectory?: string;
|
|
@@ -23,6 +26,7 @@ export interface ScaffoldResult {
|
|
|
23
26
|
targetDirectory: string;
|
|
24
27
|
packageName: string;
|
|
25
28
|
packageManager: PackageManager;
|
|
29
|
+
template: TemplateName;
|
|
26
30
|
installed: boolean;
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -32,6 +36,7 @@ export interface CreatorOptions extends Omit<ScaffoldOptions, "projectDirectory"
|
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
export const helpText: string;
|
|
39
|
+
export const availableTemplates: readonly TemplateName[];
|
|
35
40
|
export function parseArguments(args: string[], userAgent?: string): CreatorArguments;
|
|
36
41
|
export function scaffoldProject(options: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
37
42
|
export function runCreator(args: string[], options?: CreatorOptions): Promise<ScaffoldResult | undefined>;
|
package/src/index.js
CHANGED
|
@@ -5,7 +5,9 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
|
|
6
6
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
7
|
const defaultTemplate = path.join(packageRoot, "template");
|
|
8
|
+
const templatesRoot = path.join(packageRoot, "templates");
|
|
8
9
|
const supportedPackageManagers = new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
10
|
+
export const availableTemplates = Object.freeze(["starter", "marketing", "dashboard", "documentation"]);
|
|
9
11
|
|
|
10
12
|
export const helpText = [
|
|
11
13
|
"Create a CocoFrame application",
|
|
@@ -14,6 +16,8 @@ export const helpText = [
|
|
|
14
16
|
" npm create cocoframe@latest <project-directory> [options]",
|
|
15
17
|
"",
|
|
16
18
|
"Options:",
|
|
19
|
+
" --template <starter|marketing|dashboard|documentation>",
|
|
20
|
+
" Select a project template",
|
|
17
21
|
" --package-manager <npm|pnpm|yarn|bun> Select the package manager",
|
|
18
22
|
" --skip-install, --no-install Generate files without installing",
|
|
19
23
|
" --help, -h Show this help",
|
|
@@ -23,6 +27,7 @@ export const helpText = [
|
|
|
23
27
|
export function parseArguments(args, userAgent = process.env.npm_config_user_agent) {
|
|
24
28
|
let projectDirectory;
|
|
25
29
|
let packageManager = packageManagerFromUserAgent(userAgent);
|
|
30
|
+
let template = "starter";
|
|
26
31
|
let install = true;
|
|
27
32
|
let help = false;
|
|
28
33
|
let version = false;
|
|
@@ -32,7 +37,13 @@ export function parseArguments(args, userAgent = process.env.npm_config_user_age
|
|
|
32
37
|
if (argument === "--help" || argument === "-h") help = true;
|
|
33
38
|
else if (argument === "--version" || argument === "-v") version = true;
|
|
34
39
|
else if (argument === "--skip-install" || argument === "--no-install") install = false;
|
|
35
|
-
else if (argument === "--
|
|
40
|
+
else if (argument === "--template") {
|
|
41
|
+
const value = args[++index];
|
|
42
|
+
if (!value) throw new Error("--template requires a value.");
|
|
43
|
+
template = validateTemplate(value);
|
|
44
|
+
} else if (argument?.startsWith("--template=")) {
|
|
45
|
+
template = validateTemplate(argument.slice("--template=".length));
|
|
46
|
+
} else if (argument === "--package-manager") {
|
|
36
47
|
const value = args[++index];
|
|
37
48
|
if (!value) throw new Error("--package-manager requires a value.");
|
|
38
49
|
packageManager = validatePackageManager(value);
|
|
@@ -44,7 +55,7 @@ export function parseArguments(args, userAgent = process.env.npm_config_user_age
|
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
if (!help && !version && !projectDirectory) throw new Error("A project directory is required.");
|
|
47
|
-
return { projectDirectory, packageManager, install, help, version };
|
|
58
|
+
return { projectDirectory, packageManager, template, install, help, version };
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
export async function scaffoldProject(options) {
|
|
@@ -52,7 +63,8 @@ export async function scaffoldProject(options) {
|
|
|
52
63
|
const targetDirectory = resolveTargetDirectory(options.projectDirectory, cwd);
|
|
53
64
|
const packageName = normalizePackageName(path.basename(targetDirectory));
|
|
54
65
|
const packageManager = validatePackageManager(options.packageManager ?? "npm");
|
|
55
|
-
const
|
|
66
|
+
const template = validateTemplate(options.template ?? "starter");
|
|
67
|
+
const templateDirectory = path.resolve(options.templateDirectory ?? templatePath(template));
|
|
56
68
|
const entries = await directoryEntries(targetDirectory);
|
|
57
69
|
if (entries.length > 0) throw new Error("Target directory is not empty: " + targetDirectory);
|
|
58
70
|
|
|
@@ -65,7 +77,7 @@ export async function scaffoldProject(options) {
|
|
|
65
77
|
await installDependencies(targetDirectory, packageManager, options.spawnCommand);
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
return { targetDirectory, packageName, packageManager, installed: options.install !== false };
|
|
80
|
+
return { targetDirectory, packageName, packageManager, template, installed: options.install !== false };
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
export async function runCreator(args, options = {}) {
|
|
@@ -84,6 +96,7 @@ export async function runCreator(args, options = {}) {
|
|
|
84
96
|
const result = await scaffoldProject({
|
|
85
97
|
projectDirectory: parsed.projectDirectory,
|
|
86
98
|
packageManager: parsed.packageManager,
|
|
99
|
+
template: parsed.template,
|
|
87
100
|
install: parsed.install,
|
|
88
101
|
cwd: options.cwd,
|
|
89
102
|
templateDirectory: options.templateDirectory,
|
|
@@ -111,6 +124,17 @@ function validatePackageManager(value) {
|
|
|
111
124
|
return value;
|
|
112
125
|
}
|
|
113
126
|
|
|
127
|
+
function validateTemplate(value) {
|
|
128
|
+
if (!availableTemplates.includes(value)) {
|
|
129
|
+
throw new Error("Unknown template: " + value + ". Use " + availableTemplates.join(", ") + ".");
|
|
130
|
+
}
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function templatePath(template) {
|
|
135
|
+
return template === "starter" ? defaultTemplate : path.join(templatesRoot, template);
|
|
136
|
+
}
|
|
137
|
+
|
|
114
138
|
function resolveTargetDirectory(input, cwd) {
|
|
115
139
|
if (!input?.trim()) throw new Error("A project directory is required.");
|
|
116
140
|
const target = path.resolve(cwd, input);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineLayout } from "@cocoframe/core";
|
|
2
|
+
import BoxMinimalisticIcon from "@cocoframe/icons/linear/box-minimalistic";
|
|
2
3
|
|
|
3
4
|
export default defineLayout(({ children }) => (
|
|
4
5
|
<>
|
|
5
6
|
<header class="site-header">
|
|
6
|
-
<a href="/" class="brand">CocoFrame</a>
|
|
7
|
+
<a href="/" class="brand"><BoxMinimalisticIcon size={24} label="CocoFrame" /> <span>CocoFrame</span></a>
|
|
7
8
|
<nav aria-label="Primary navigation">
|
|
8
9
|
<a href="/">Home</a>
|
|
9
10
|
<a href="/api/health">API</a>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { definePage } from "@cocoframe/core";
|
|
2
|
+
import CodeSquareIcon from "@cocoframe/icons/linear/code-square";
|
|
3
|
+
import { Badge, Card, Heading, Stack, Text } from "@cocoframe/ui";
|
|
2
4
|
import Counter from "../islands/counter.island.tsx";
|
|
3
5
|
|
|
4
6
|
export default definePage({
|
|
@@ -8,11 +10,12 @@ export default definePage({
|
|
|
8
10
|
},
|
|
9
11
|
view: () => (
|
|
10
12
|
<main class="hero">
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
<Stack gap="large">
|
|
14
|
+
<Badge variant="success"><CodeSquareIcon size={15} /> Server-first · AI-friendly</Badge>
|
|
15
|
+
<Heading level={1} size="xlarge">Your CocoFrame project is ready.</Heading>
|
|
16
|
+
<Text size="large" tone="muted">Useful HTML is rendered on the server. Browser JavaScript is isolated to the counter below.</Text>
|
|
17
|
+
<Card class="starter-card"><Counter initial={0} /><small>Edit <code>app/routes/index.page.tsx</code> to get started.</small></Card>
|
|
18
|
+
</Stack>
|
|
16
19
|
</main>
|
|
17
20
|
),
|
|
18
21
|
});
|
|
@@ -1,29 +1,14 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
color: #15231c;
|
|
3
|
-
background: #f6faf8;
|
|
4
|
-
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
5
|
-
}
|
|
6
|
-
|
|
1
|
+
:root { color: #15231c; background: #f6faf8; font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
|
|
7
2
|
* { box-sizing: border-box; }
|
|
8
3
|
body { margin: 0; }
|
|
9
4
|
a { color: inherit; }
|
|
10
|
-
|
|
11
|
-
.
|
|
12
|
-
min-height: 68px;
|
|
13
|
-
padding: 0 clamp(20px, 5vw, 72px);
|
|
14
|
-
display: flex;
|
|
15
|
-
align-items: center;
|
|
16
|
-
justify-content: space-between;
|
|
17
|
-
border-bottom: 1px solid #dce9e1;
|
|
18
|
-
background: rgba(255, 255, 255, .9);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.brand { color: #187a55; font-weight: 800; text-decoration: none; }
|
|
5
|
+
.site-header { min-height: 68px; padding: 0 clamp(20px, 5vw, 72px); display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #dce9e1; background: rgba(255, 255, 255, .9); }
|
|
6
|
+
.brand { display: inline-flex; align-items: center; gap: 9px; color: #187a55; font-weight: 800; text-decoration: none; }
|
|
22
7
|
.site-header nav { display: flex; gap: 20px; font-size: 14px; }
|
|
23
8
|
.hero { width: min(760px, calc(100% - 40px)); margin: 0 auto; padding: clamp(72px, 14vw, 150px) 0; }
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
.
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
9
|
+
.hero .coco-heading { max-width: 680px; margin: 0; letter-spacing: -.045em; }
|
|
10
|
+
.hero .coco-text { max-width: 620px; line-height: 1.7; }
|
|
11
|
+
.starter-card { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 24px; }
|
|
12
|
+
.starter-card small { color: #587064; }
|
|
13
|
+
code { padding: 2px 6px; border-radius: 5px; background: #e8f3ed; }
|
|
14
|
+
@media (max-width: 540px) { .site-header nav { gap: 12px; } .starter-card { align-items: flex-start; flex-direction: column; } }
|
package/template/package.json
CHANGED
|
@@ -12,15 +12,16 @@
|
|
|
12
12
|
"generate": "cocoframe generate ."
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@cocoframe/client": "0.0.
|
|
16
|
-
"@cocoframe/core": "0.0.
|
|
15
|
+
"@cocoframe/client": "0.0.2",
|
|
16
|
+
"@cocoframe/core": "0.0.2",
|
|
17
|
+
"@cocoframe/icons": "0.0.1",
|
|
17
18
|
"@cocoframe/jsx": "0.0.1",
|
|
18
19
|
"@cocoframe/schema": "0.0.1",
|
|
19
20
|
"@cocoframe/ui": "0.0.1"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
|
-
"@cocoframe/cli": "0.0.
|
|
23
|
+
"@cocoframe/cli": "0.0.2",
|
|
23
24
|
"@types/node": "^24.0.0",
|
|
24
25
|
"typescript": "^5.9.0"
|
|
25
26
|
}
|
|
26
|
-
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"templates": [
|
|
4
|
+
{
|
|
5
|
+
"id": "starter",
|
|
6
|
+
"name": "CocoFrame Starter",
|
|
7
|
+
"description": "Fondasi minimal server-first dengan typed API dan counter island.",
|
|
8
|
+
"category": "Starter Kits",
|
|
9
|
+
"technology": ["TypeScript", "CocoFrame"],
|
|
10
|
+
"components": ["Badge", "Button", "Card", "Heading", "Stack", "Text"],
|
|
11
|
+
"icons": ["box-minimalistic", "code-square"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "marketing",
|
|
15
|
+
"name": "Marketing Launch",
|
|
16
|
+
"description": "Landing page SEO-ready dengan hero, feature cards, metrics, dan CTA.",
|
|
17
|
+
"category": "Web Applications",
|
|
18
|
+
"technology": ["TypeScript", "CocoFrame", "CSS"],
|
|
19
|
+
"components": ["Badge", "Card", "Container", "Heading", "Inline", "Stack", "Stat", "Text"],
|
|
20
|
+
"icons": ["arrow-right", "bolt", "graph-up", "shield-check", "stars-minimalistic"]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "dashboard",
|
|
24
|
+
"name": "Orbit Dashboard",
|
|
25
|
+
"description": "Dashboard admin responsif dengan sidebar, analytics, data table, dan status.",
|
|
26
|
+
"category": "Dashboards",
|
|
27
|
+
"technology": ["TypeScript", "CocoFrame", "Charts"],
|
|
28
|
+
"components": ["Badge", "Card", "Chart", "DataTable", "Heading", "Inline", "Sidebar", "Stat", "Text"],
|
|
29
|
+
"icons": ["bell", "calendar", "graph-up", "home", "magnifier", "settings", "users-group-rounded", "widget-4"]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "documentation",
|
|
33
|
+
"name": "Atlas Documentation",
|
|
34
|
+
"description": "Dokumentasi server-first dengan sidebar, code samples, alert, dan route table.",
|
|
35
|
+
"category": "Documentation",
|
|
36
|
+
"technology": ["TypeScript", "CocoFrame"],
|
|
37
|
+
"components": ["Alert", "Badge", "Card", "Heading", "Sidebar", "SyntaxHighlighter", "Table", "Text"],
|
|
38
|
+
"icons": ["arrow-right", "book-2", "check-circle", "code-square", "document-text", "magnifier", "routing-2", "settings"]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
Responsive admin dashboard built with CocoFrame `Sidebar`, `Stat`, `Chart`, `DataTable`, `Badge`, and Solar Linear icons.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install
|
|
7
|
+
npm run dev
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Use `npm run inspect` to see the components and icons discovered by CocoFrame.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineLayout } from "@cocoframe/core";
|
|
2
|
+
import ChartIcon from "@cocoframe/icons/linear/graph-up";
|
|
3
|
+
import HomeIcon from "@cocoframe/icons/linear/home";
|
|
4
|
+
import SettingsIcon from "@cocoframe/icons/linear/settings";
|
|
5
|
+
import UsersIcon from "@cocoframe/icons/linear/users-group-rounded";
|
|
6
|
+
import WidgetIcon from "@cocoframe/icons/linear/widget-4";
|
|
7
|
+
import { Badge, Sidebar } from "@cocoframe/ui";
|
|
8
|
+
|
|
9
|
+
export default defineLayout(({ children }) => (
|
|
10
|
+
<div class="dashboard-shell">
|
|
11
|
+
<Sidebar
|
|
12
|
+
brand={<a class="dashboard-brand" href="/"><WidgetIcon size={24} /> <span>Orbit Admin</span></a>}
|
|
13
|
+
groups={[{ label: "Workspace", items: [
|
|
14
|
+
{ label: "Overview", href: "/", current: true, icon: <HomeIcon size={18} /> },
|
|
15
|
+
{ label: "Analytics", href: "#analytics", icon: <ChartIcon size={18} /> },
|
|
16
|
+
{ label: "Customers", href: "#customers", icon: <UsersIcon size={18} />, badge: <Badge variant="success">24</Badge> },
|
|
17
|
+
{ label: "Settings", href: "#settings", icon: <SettingsIcon size={18} /> },
|
|
18
|
+
] }]}
|
|
19
|
+
footer={<small>Server-rendered with CocoFrame</small>}
|
|
20
|
+
/>
|
|
21
|
+
<div class="dashboard-main">{children}</div>
|
|
22
|
+
</div>
|
|
23
|
+
));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineApi } from "@cocoframe/core";
|
|
2
|
+
import { schema } from "@cocoframe/schema";
|
|
3
|
+
|
|
4
|
+
export default defineApi({
|
|
5
|
+
id: "health",
|
|
6
|
+
method: "GET",
|
|
7
|
+
output: schema.object({
|
|
8
|
+
ok: schema.literal(true),
|
|
9
|
+
framework: schema.string(),
|
|
10
|
+
}),
|
|
11
|
+
handle: () => ({ ok: true, framework: "cocoframe" }),
|
|
12
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { definePage } from "@cocoframe/core";
|
|
2
|
+
import BellIcon from "@cocoframe/icons/linear/bell";
|
|
3
|
+
import CalendarIcon from "@cocoframe/icons/linear/calendar";
|
|
4
|
+
import GraphUpIcon from "@cocoframe/icons/linear/graph-up";
|
|
5
|
+
import MagnifierIcon from "@cocoframe/icons/linear/magnifier";
|
|
6
|
+
import { Badge, Card, Chart, DataTable, Heading, Inline, Stat, Text } from "@cocoframe/ui";
|
|
7
|
+
|
|
8
|
+
const rows = [
|
|
9
|
+
{ customer: "Acme Studio", plan: "Growth", status: <Badge variant="success">Active</Badge>, revenue: "$4,800" },
|
|
10
|
+
{ customer: "Northwind", plan: "Starter", status: <Badge variant="warning">Trial</Badge>, revenue: "$1,260" },
|
|
11
|
+
{ customer: "Vertex Labs", plan: "Scale", status: <Badge variant="success">Active</Badge>, revenue: "$8,940" },
|
|
12
|
+
{ customer: "Lumen Works", plan: "Growth", status: <Badge variant="neutral">Paused</Badge>, revenue: "$3,120" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export default definePage({
|
|
16
|
+
meta: { title: "{{PROJECT_NAME}} — Dashboard", description: "Accessible CocoFrame dashboard template with charts, stats, table, and official icons." },
|
|
17
|
+
view: () => <main class="dashboard-page">
|
|
18
|
+
<header class="dashboard-topbar"><div><Text tone="muted" size="small">Tuesday, 22 August</Text><Heading level={1}>Good morning, Alex</Heading></div><Inline gap="small"><button class="icon-action" type="button" aria-label="Search"><MagnifierIcon size={19} /></button><button class="icon-action" type="button" aria-label="Notifications"><BellIcon size={19} /></button><span class="avatar">AX</span></Inline></header>
|
|
19
|
+
|
|
20
|
+
<section class="stats-grid" aria-label="Key metrics"><Stat label="Monthly revenue" value="$42,860" trend="+12.4% this month" tone="positive" /><Stat label="Active customers" value="1,284" trend="+86 this week" tone="positive" /><Stat label="Conversion" value="8.42%" trend="+1.2 percentage points" tone="positive" /><Stat label="Open tickets" value="18" trend="4 need attention" tone="negative" /></section>
|
|
21
|
+
|
|
22
|
+
<section class="dashboard-grid" id="analytics">
|
|
23
|
+
<Card class="chart-card"><div class="card-heading"><div><Badge variant="primary"><GraphUpIcon size={14} /> Analytics</Badge><Heading level={2} size="medium">Revenue overview</Heading></div><button class="period-button" type="button"><CalendarIcon size={16} /> Last 6 months</button></div><Chart type="area" label="Monthly revenue" description="Revenue increased from 24 to 43 thousand dollars." labels={["Mar","Apr","May","Jun","Jul","Aug"]} datasets={[{ label: "Revenue", data: [24,28,27,34,38,43], tone: "primary", fill: true }]} formatValue={(value) => `$${value}k`} /></Card>
|
|
24
|
+
<Card class="goal-card"><Badge variant="success">On track</Badge><Heading level={2} size="medium">Quarter goal</Heading><strong>78%</strong><div class="goal-ring" aria-label="78 percent complete"><span>78%</span></div><Text tone="muted">$31,200 remaining to reach the quarterly target.</Text></Card>
|
|
25
|
+
</section>
|
|
26
|
+
|
|
27
|
+
<section id="customers"><Card class="customers-card"><div class="card-heading"><div><Heading level={2} size="medium">Recent customers</Heading><Text tone="muted" size="small">Latest account activity across the workspace.</Text></div><a href="#customers">View all</a></div><DataTable caption="Recent customers" columns={[{ key: "customer", label: "Customer", sortable: true }, { key: "plan", label: "Plan", sortable: true }, { key: "status", label: "Status" }, { key: "revenue", label: "Revenue", sortable: true }]} rows={rows} sortKey="customer" sortPath="/" /></Card></section>
|
|
28
|
+
</main>,
|
|
29
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
:root { color: #14221b; background: #f4f7f5; font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
|
|
2
|
+
* { box-sizing: border-box; }
|
|
3
|
+
body { margin: 0; }
|
|
4
|
+
a { color: inherit; }
|
|
5
|
+
.dashboard-shell { min-height: 100vh; display: grid; grid-template-columns: 250px minmax(0,1fr); }
|
|
6
|
+
.dashboard-shell > .coco-sidebar { min-height: 100vh; border-right: 1px solid #dce6e0; background: #fff; position: sticky; top: 0; }
|
|
7
|
+
.dashboard-brand { display: flex; align-items: center; gap: 10px; color: #187a55; font-weight: 850; text-decoration: none; }
|
|
8
|
+
.dashboard-main { min-width: 0; }
|
|
9
|
+
.dashboard-page { width: min(1440px,100%); margin: auto; padding: clamp(22px,4vw,54px); }
|
|
10
|
+
.dashboard-topbar, .card-heading { display: flex; align-items: center; justify-content: space-between; gap: 20px; }
|
|
11
|
+
.dashboard-topbar { margin-bottom: 32px; }
|
|
12
|
+
.dashboard-topbar .coco-heading { margin: 4px 0 0; }
|
|
13
|
+
.icon-action, .period-button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 42px; border: 1px solid #d8e4dd; border-radius: 10px; color: #365548; background: white; }
|
|
14
|
+
.icon-action { width: 42px; padding: 0; }
|
|
15
|
+
.period-button { padding: 0 13px; }
|
|
16
|
+
.avatar { width: 42px; height: 42px; display: grid; place-items: center; border-radius: 50%; color: white; background: #187a55; font-size: 13px; font-weight: 800; }
|
|
17
|
+
.stats-grid { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 14px; margin-bottom: 18px; }
|
|
18
|
+
.stats-grid .coco-stat { padding: 22px; border: 1px solid #dce6e0; border-radius: 14px; background: white; }
|
|
19
|
+
.dashboard-grid { display: grid; grid-template-columns: minmax(0,2fr) minmax(260px,.75fr); gap: 18px; margin-bottom: 18px; }
|
|
20
|
+
.chart-card, .goal-card, .customers-card { padding: 24px; }
|
|
21
|
+
.card-heading { margin-bottom: 18px; }
|
|
22
|
+
.card-heading .coco-heading { margin: 8px 0 0; }
|
|
23
|
+
.chart-card .coco-chart { border: 0; padding: 0; box-shadow: none; }
|
|
24
|
+
.goal-card { display: flex; flex-direction: column; align-items: flex-start; }
|
|
25
|
+
.goal-card > strong { margin: 20px 0 6px; font-size: 52px; letter-spacing: -.05em; }
|
|
26
|
+
.goal-ring { width: 150px; height: 150px; display: grid; place-items: center; margin: 18px auto; border-radius: 50%; background: conic-gradient(#187a55 0 78%,#dcebe3 78%); position: relative; }
|
|
27
|
+
.goal-ring::after { content: ""; position: absolute; inset: 15px; border-radius: inherit; background: white; }
|
|
28
|
+
.goal-ring span { position: relative; z-index: 1; font-weight: 800; }
|
|
29
|
+
.customers-card .coco-data-table { border: 0; }
|
|
30
|
+
.customers-card a { color: #187a55; font-weight: 750; }
|
|
31
|
+
@media (max-width: 1080px) { .stats-grid { grid-template-columns: repeat(2,1fr); } .dashboard-grid { grid-template-columns: 1fr; } }
|
|
32
|
+
@media (max-width: 760px) { .dashboard-shell { grid-template-columns: 1fr; } .dashboard-shell > .coco-sidebar { min-height: auto; position: static; } .dashboard-page { padding: 20px 14px 40px; } .dashboard-topbar { align-items: flex-start; } }
|
|
33
|
+
@media (max-width: 520px) { .stats-grid { grid-template-columns: 1fr; } .dashboard-topbar > .coco-inline .icon-action { display: none; } .card-heading { align-items: flex-start; flex-direction: column; } }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "cocoframe dev .",
|
|
8
|
+
"build": "cocoframe build .",
|
|
9
|
+
"start": "cocoframe start .",
|
|
10
|
+
"check": "tsc --noEmit",
|
|
11
|
+
"inspect": "cocoframe inspect .",
|
|
12
|
+
"generate": "cocoframe generate ."
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@cocoframe/core": "0.0.2",
|
|
16
|
+
"@cocoframe/icons": "0.0.1",
|
|
17
|
+
"@cocoframe/jsx": "0.0.1",
|
|
18
|
+
"@cocoframe/schema": "0.0.1",
|
|
19
|
+
"@cocoframe/ui": "0.0.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@cocoframe/cli": "0.0.2",
|
|
23
|
+
"@types/node": "^24.0.0",
|
|
24
|
+
"typescript": "^5.9.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"exactOptionalPropertyTypes": true,
|
|
9
|
+
"verbatimModuleSyntax": true,
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"jsxImportSource": "@cocoframe/jsx",
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"noEmit": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["app/**/*.ts", "app/**/*.tsx", "cocoframe.config.ts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
Documentation template built with CocoFrame `Sidebar`, `SyntaxHighlighter`, `Alert`, `Table`, `Card`, and Solar Linear icons.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install
|
|
7
|
+
npm run dev
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Add new guides as route sections or split them into dedicated `.page.tsx` files.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineLayout } from "@cocoframe/core";
|
|
2
|
+
import BookIcon from "@cocoframe/icons/linear/book-2";
|
|
3
|
+
import CodeIcon from "@cocoframe/icons/linear/code-square";
|
|
4
|
+
import DocumentIcon from "@cocoframe/icons/linear/document-text";
|
|
5
|
+
import RouteIcon from "@cocoframe/icons/linear/routing-2";
|
|
6
|
+
import SettingsIcon from "@cocoframe/icons/linear/settings";
|
|
7
|
+
import { Badge, Sidebar } from "@cocoframe/ui";
|
|
8
|
+
|
|
9
|
+
export default defineLayout(({ children }) => (
|
|
10
|
+
<div class="docs-shell">
|
|
11
|
+
<Sidebar brand={<a class="docs-brand" href="/"><BookIcon size={24} /> <span>Atlas Docs</span></a>} groups={[
|
|
12
|
+
{ label: "Getting started", items: [{ label: "Introduction", href: "#introduction", current: true, icon: <DocumentIcon size={17} /> }, { label: "Installation", href: "#installation", icon: <CodeIcon size={17} /> }] },
|
|
13
|
+
{ label: "Core concepts", items: [{ label: "Routing", href: "#routing", icon: <RouteIcon size={17} /> }, { label: "Configuration", href: "#configuration", icon: <SettingsIcon size={17} />, badge: <Badge variant="success">New</Badge> }] },
|
|
14
|
+
]} footer={<a href="/api/health">API status</a>} />
|
|
15
|
+
<div class="docs-main">{children}</div>
|
|
16
|
+
</div>
|
|
17
|
+
));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineApi } from "@cocoframe/core";
|
|
2
|
+
import { schema } from "@cocoframe/schema";
|
|
3
|
+
|
|
4
|
+
export default defineApi({
|
|
5
|
+
id: "health",
|
|
6
|
+
method: "GET",
|
|
7
|
+
output: schema.object({
|
|
8
|
+
ok: schema.literal(true),
|
|
9
|
+
framework: schema.string(),
|
|
10
|
+
}),
|
|
11
|
+
handle: () => ({ ok: true, framework: "cocoframe" }),
|
|
12
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { definePage } from "@cocoframe/core";
|
|
2
|
+
import ArrowRightIcon from "@cocoframe/icons/linear/arrow-right";
|
|
3
|
+
import CheckIcon from "@cocoframe/icons/linear/check-circle";
|
|
4
|
+
import CodeIcon from "@cocoframe/icons/linear/code-square";
|
|
5
|
+
import MagnifierIcon from "@cocoframe/icons/linear/magnifier";
|
|
6
|
+
import { Alert, Badge, Card, Heading, SyntaxHighlighter, Table, Text } from "@cocoframe/ui";
|
|
7
|
+
|
|
8
|
+
const installCode = `npm create cocoframe@latest my-app\ncd my-app\nnpm run dev`;
|
|
9
|
+
const pageCode = `import { definePage } from "@cocoframe/core";\n\nexport default definePage({\n meta: { title: "Hello" },\n view: () => <h1>Hello from CocoFrame</h1>,\n});`;
|
|
10
|
+
|
|
11
|
+
export default definePage({
|
|
12
|
+
meta: { title: "{{PROJECT_NAME}} — Documentation", description: "A searchable, server-first documentation template using official CocoFrame components and icons." },
|
|
13
|
+
view: () => <main class="docs-page">
|
|
14
|
+
<header class="docs-topbar"><a class="mobile-brand" href="/"><CodeIcon size={21} /> Atlas Docs</a><label class="docs-search"><MagnifierIcon size={17} /><span class="coco-visually-hidden">Search documentation</span><input type="search" placeholder="Search documentation…" /></label><a href="https://github.com/wiryosaputraofficial/cocoframe">GitHub</a></header>
|
|
15
|
+
<article class="docs-article">
|
|
16
|
+
<section id="introduction"><Badge variant="success"><CheckIcon size={14} /> Server-first documentation</Badge><Heading level={1} size="xlarge">Build documentation people can actually navigate.</Heading><Text size="large" tone="muted">Atlas Docs combines semantic navigation, accessible code samples, and a content layout that stays useful without browser JavaScript.</Text><div class="docs-actions"><a class="primary-link" href="#installation">Get started <ArrowRightIcon size={16} /></a><a class="secondary-link" href="#routing">Explore routing</a></div></section>
|
|
17
|
+
|
|
18
|
+
<section id="installation"><Heading level={2}>Installation</Heading><Text tone="muted">Create a project from the public registry and start the local development server.</Text><Card class="code-card"><SyntaxHighlighter code={installCode} language="bash" label="Install CocoFrame" showLineNumbers /></Card><Alert variant="info"><strong>Tip:</strong> Run <code>npm run inspect</code> to give developers and AI assistants a compact project manifest.</Alert></section>
|
|
19
|
+
|
|
20
|
+
<section id="routing"><Heading level={2}>File-based routing</Heading><Text tone="muted">A page owns its loader, metadata, and view. File names remain predictable and easy to scan.</Text><Card class="code-card"><SyntaxHighlighter code={pageCode} language="tsx" label="CocoFrame page" showLineNumbers /></Card><Table caption="Route conventions" headers={["File", "URL", "Purpose"]} rows={[["index.page.tsx", "/", "Static page"],["blog/[slug].page.tsx", "/blog/:slug", "Dynamic page"],["api/health.route.ts", "/api/health", "Typed API route"]]} striped /></section>
|
|
21
|
+
|
|
22
|
+
<section id="configuration"><Heading level={2}>Configuration</Heading><Text tone="muted">Keep cross-cutting behavior explicit in <code>cocoframe.config.ts</code>.</Text><div class="concept-grid"><Card><CodeIcon size={22} /><Heading level={3} size="medium">Typed config</Heading><Text tone="muted">Editors validate middleware, metadata, health checks, and asset options.</Text></Card><Card><CheckIcon size={22} /><Heading level={3} size="medium">Secure defaults</Heading><Text tone="muted">Security behavior stays outside render code and remains easy to inspect.</Text></Card></div></section>
|
|
23
|
+
</article>
|
|
24
|
+
</main>,
|
|
25
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
:root { color: #15231c; background: #fff; font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
|
|
2
|
+
* { box-sizing: border-box; }
|
|
3
|
+
html { scroll-behavior: smooth; }
|
|
4
|
+
body { margin: 0; }
|
|
5
|
+
a { color: inherit; }
|
|
6
|
+
.docs-shell { min-height: 100vh; display: grid; grid-template-columns: 270px minmax(0,1fr); }
|
|
7
|
+
.docs-shell > .coco-sidebar { height: 100vh; border-right: 1px solid #dde7e1; background: #fbfdfc; position: sticky; top: 0; }
|
|
8
|
+
.docs-brand, .mobile-brand { display: flex; align-items: center; gap: 9px; color: #187a55; font-weight: 850; text-decoration: none; }
|
|
9
|
+
.docs-main { min-width: 0; }
|
|
10
|
+
.docs-topbar { min-height: 68px; padding: 0 clamp(20px,4vw,52px); display: flex; align-items: center; justify-content: flex-end; gap: 22px; border-bottom: 1px solid #e2eae5; position: sticky; top: 0; z-index: 5; background: rgba(255,255,255,.94); backdrop-filter: blur(14px); }
|
|
11
|
+
.mobile-brand { display: none; margin-right: auto; }
|
|
12
|
+
.docs-search { width: min(380px,50vw); min-height: 42px; display: flex; align-items: center; gap: 9px; padding: 0 13px; border: 1px solid #d7e2dc; border-radius: 10px; color: #658074; background: #f8fbf9; }
|
|
13
|
+
.docs-search input { width: 100%; border: 0; outline: 0; background: transparent; font: inherit; }
|
|
14
|
+
.docs-topbar > a:last-child { color: #187a55; font-weight: 750; }
|
|
15
|
+
.docs-article { width: min(850px,calc(100% - 40px)); margin: 0 auto; padding: 70px 0 120px; }
|
|
16
|
+
.docs-article section { padding: 42px 0; scroll-margin-top: 80px; border-bottom: 1px solid #e4ece7; }
|
|
17
|
+
.docs-article section:first-child { padding-top: 20px; }
|
|
18
|
+
.docs-article .coco-heading { max-width: 780px; margin: 16px 0; letter-spacing: -.035em; }
|
|
19
|
+
.docs-article .coco-text { max-width: 720px; line-height: 1.75; }
|
|
20
|
+
.docs-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 26px; }
|
|
21
|
+
.primary-link, .secondary-link { min-height: 44px; padding: 0 17px; display: inline-flex; align-items: center; gap: 8px; border-radius: 9px; font-weight: 750; text-decoration: none; }
|
|
22
|
+
.primary-link { color: white; background: #187a55; }.secondary-link { border: 1px solid #cfded6; }
|
|
23
|
+
.code-card { padding: 0; margin: 22px 0; overflow: hidden; }
|
|
24
|
+
.code-card .coco-syntax { border: 0; }
|
|
25
|
+
.docs-article .coco-alert { margin-block: 18px; }
|
|
26
|
+
.docs-article .coco-table-wrap { margin-top: 20px; }
|
|
27
|
+
.concept-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 16px; margin-top: 24px; }
|
|
28
|
+
.concept-grid .coco-card { padding: 24px; }
|
|
29
|
+
.concept-grid svg { color: #187a55; }
|
|
30
|
+
@media (max-width: 820px) { .docs-shell { grid-template-columns: 1fr; } .docs-shell > .coco-sidebar { display: none; } .mobile-brand { display: flex; } .docs-topbar { padding-inline: 16px; } .docs-search { width: min(300px,45vw); } }
|
|
31
|
+
@media (max-width: 520px) { .docs-topbar > a:last-child { display: none; } .docs-search { width: 44px; } .docs-search input { display: none; } .docs-article { width: min(100% - 28px,850px); padding-top: 40px; } .concept-grid { grid-template-columns: 1fr; } }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "cocoframe dev .",
|
|
8
|
+
"build": "cocoframe build .",
|
|
9
|
+
"start": "cocoframe start .",
|
|
10
|
+
"check": "tsc --noEmit",
|
|
11
|
+
"inspect": "cocoframe inspect .",
|
|
12
|
+
"generate": "cocoframe generate ."
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@cocoframe/core": "0.0.2",
|
|
16
|
+
"@cocoframe/icons": "0.0.1",
|
|
17
|
+
"@cocoframe/jsx": "0.0.1",
|
|
18
|
+
"@cocoframe/schema": "0.0.1",
|
|
19
|
+
"@cocoframe/ui": "0.0.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@cocoframe/cli": "0.0.2",
|
|
23
|
+
"@types/node": "^24.0.0",
|
|
24
|
+
"typescript": "^5.9.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"exactOptionalPropertyTypes": true,
|
|
9
|
+
"verbatimModuleSyntax": true,
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"jsxImportSource": "@cocoframe/jsx",
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"noEmit": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["app/**/*.ts", "app/**/*.tsx", "cocoframe.config.ts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineLayout } from "@cocoframe/core";
|
|
2
|
+
import StarsMinimalisticIcon from "@cocoframe/icons/linear/stars-minimalistic";
|
|
3
|
+
|
|
4
|
+
export default defineLayout(({ children }) => (
|
|
5
|
+
<>
|
|
6
|
+
<header class="marketing-header">
|
|
7
|
+
<a class="marketing-brand" href="/"><StarsMinimalisticIcon size={25} label="Northstar" /> <span>Northstar</span></a>
|
|
8
|
+
<nav aria-label="Primary navigation"><a href="#features">Features</a><a href="#results">Results</a><a href="#contact">Contact</a></nav>
|
|
9
|
+
<a class="header-cta" href="#contact">Start free</a>
|
|
10
|
+
</header>
|
|
11
|
+
{children}
|
|
12
|
+
</>
|
|
13
|
+
));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineApi } from "@cocoframe/core";
|
|
2
|
+
import { schema } from "@cocoframe/schema";
|
|
3
|
+
|
|
4
|
+
export default defineApi({
|
|
5
|
+
id: "health",
|
|
6
|
+
method: "GET",
|
|
7
|
+
output: schema.object({
|
|
8
|
+
ok: schema.literal(true),
|
|
9
|
+
framework: schema.string(),
|
|
10
|
+
}),
|
|
11
|
+
handle: () => ({ ok: true, framework: "cocoframe" }),
|
|
12
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { definePage } from "@cocoframe/core";
|
|
2
|
+
import ArrowRightIcon from "@cocoframe/icons/linear/arrow-right";
|
|
3
|
+
import BoltIcon from "@cocoframe/icons/linear/bolt";
|
|
4
|
+
import GraphUpIcon from "@cocoframe/icons/linear/graph-up";
|
|
5
|
+
import ShieldCheckIcon from "@cocoframe/icons/linear/shield-check";
|
|
6
|
+
import { Badge, Card, Container, Heading, Inline, Stack, Stat, Text } from "@cocoframe/ui";
|
|
7
|
+
|
|
8
|
+
const features = [
|
|
9
|
+
{ icon: <BoltIcon size={22} />, title: "Launch faster", copy: "Ship a polished product page with server-rendered HTML and a tiny browser footprint." },
|
|
10
|
+
{ icon: <GraphUpIcon size={22} />, title: "Convert clearly", copy: "Guide visitors with focused sections, measurable proof, and accessible calls to action." },
|
|
11
|
+
{ icon: <ShieldCheckIcon size={22} />, title: "Stay reliable", copy: "Typed routes, secure defaults, and predictable files keep the project easy to maintain." },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export default definePage({
|
|
15
|
+
meta: {
|
|
16
|
+
title: "{{PROJECT_NAME}} — Marketing template",
|
|
17
|
+
description: "A responsive, SEO-ready marketing template built with CocoFrame components.",
|
|
18
|
+
},
|
|
19
|
+
view: () => <main>
|
|
20
|
+
<Container as="section" size="large" class="marketing-hero">
|
|
21
|
+
<Stack gap="large" class="hero-copy">
|
|
22
|
+
<Badge variant="success"><BoltIcon size={14} /> Built with CocoFrame</Badge>
|
|
23
|
+
<Heading level={1} size="xlarge">Turn your next idea into a clear, fast product story.</Heading>
|
|
24
|
+
<Text size="large" tone="muted">Northstar gives modern teams an SEO-ready foundation made from official CocoFrame components and Solar Linear icons.</Text>
|
|
25
|
+
<Inline gap="medium"><a class="primary-link" href="#contact">Start building <ArrowRightIcon size={17} /></a><a class="secondary-link" href="#features">Explore features</a></Inline>
|
|
26
|
+
</Stack>
|
|
27
|
+
<Card class="hero-panel"><span class="panel-label">Weekly momentum</span><strong>+38%</strong><div class="spark-bars" aria-label="Growth chart"><i></i><i></i><i></i><i></i><i></i><i></i></div><small>More qualified product conversations</small></Card>
|
|
28
|
+
</Container>
|
|
29
|
+
|
|
30
|
+
<section id="features"><Container as="div" size="large" class="feature-section">
|
|
31
|
+
<Stack gap="small" class="section-heading"><Badge variant="primary">Product foundation</Badge><Heading level={2}>Everything needed for a confident launch</Heading><Text tone="muted">Composable sections stay readable for developers, search engines, and AI coding assistants.</Text></Stack>
|
|
32
|
+
<div class="feature-grid">{features.map((feature) => <Card class="feature-card"><span class="feature-icon">{feature.icon}</span><Heading level={3} size="medium">{feature.title}</Heading><Text tone="muted">{feature.copy}</Text></Card>)}</div>
|
|
33
|
+
</Container></section>
|
|
34
|
+
|
|
35
|
+
<section id="results"><Container as="div" size="large" class="results"><Stat label="Lighthouse-ready" value="100" trend="Semantic server HTML" tone="positive" /><Stat label="Browser runtime" value="Opt-in" trend="Islands only when needed" tone="positive" /><Stat label="Shared UI" value="80+" trend="Official primitives" tone="positive" /></Container></section>
|
|
36
|
+
|
|
37
|
+
<section id="contact"><Container as="div" size="large" class="final-cta"><Stack gap="medium"><Badge variant="success">Ready when you are</Badge><Heading level={2}>Build the next version of your product today.</Heading><Text tone="muted">Replace this copy with your offer, connect the typed API, and deploy.</Text><a class="primary-link" href="mailto:hello@example.com">Talk to the team <ArrowRightIcon size={17} /></a></Stack></Container></section>
|
|
38
|
+
</main>,
|
|
39
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
:root { color: #10231b; background: #fbfdfc; font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
|
|
2
|
+
* { box-sizing: border-box; }
|
|
3
|
+
html { scroll-behavior: smooth; }
|
|
4
|
+
body { margin: 0; }
|
|
5
|
+
a { color: inherit; }
|
|
6
|
+
.marketing-header { min-height: 72px; padding: 0 clamp(20px, 5vw, 72px); display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 28px; border-bottom: 1px solid #dce9e1; background: rgba(255,255,255,.92); position: sticky; top: 0; z-index: 10; backdrop-filter: blur(16px); }
|
|
7
|
+
.marketing-brand { display: inline-flex; align-items: center; gap: 10px; color: #187a55; font-weight: 850; text-decoration: none; }
|
|
8
|
+
.marketing-header nav { display: flex; gap: 28px; font-size: 14px; }
|
|
9
|
+
.marketing-header nav a { text-decoration: none; }
|
|
10
|
+
.header-cta, .primary-link, .secondary-link { display: inline-flex; align-items: center; justify-content: center; gap: 8px; width: max-content; min-height: 44px; padding: 0 18px; border-radius: 10px; font-weight: 750; text-decoration: none; }
|
|
11
|
+
.header-cta { justify-self: end; color: white; background: #187a55; }
|
|
12
|
+
.primary-link { color: white; background: #187a55; box-shadow: 0 10px 24px rgba(24,122,85,.18); }
|
|
13
|
+
.secondary-link { border: 1px solid #cddfd5; background: white; }
|
|
14
|
+
.marketing-hero { min-height: 690px; display: grid; grid-template-columns: minmax(0,1.15fr) minmax(320px,.85fr); align-items: center; gap: clamp(40px,7vw,100px); padding-block: clamp(72px,10vw,130px); }
|
|
15
|
+
.hero-copy .coco-heading { max-width: 760px; margin: 0; letter-spacing: -.045em; }
|
|
16
|
+
.hero-copy .coco-text { max-width: 650px; line-height: 1.75; }
|
|
17
|
+
.hero-panel { min-height: 410px; padding: 34px; display: flex; flex-direction: column; justify-content: flex-end; overflow: hidden; color: white; background: linear-gradient(145deg,#0d2d22,#187a55); box-shadow: 0 28px 70px rgba(16,52,40,.22); }
|
|
18
|
+
.hero-panel strong { margin: 12px 0 24px; font-size: clamp(58px,8vw,92px); letter-spacing: -.06em; }
|
|
19
|
+
.panel-label, .hero-panel small { color: #c6ead9; }
|
|
20
|
+
.spark-bars { height: 120px; display: flex; align-items: end; gap: 10px; margin-bottom: 20px; }
|
|
21
|
+
.spark-bars i { flex: 1; height: 38%; border-radius: 7px 7px 2px 2px; background: rgba(255,255,255,.76); }
|
|
22
|
+
.spark-bars i:nth-child(2) { height: 48%; }.spark-bars i:nth-child(3) { height: 43%; }.spark-bars i:nth-child(4) { height: 66%; }.spark-bars i:nth-child(5) { height: 76%; }.spark-bars i:nth-child(6) { height: 92%; }
|
|
23
|
+
.feature-section { padding-block: 90px; }
|
|
24
|
+
.section-heading { max-width: 690px; margin-bottom: 36px; }
|
|
25
|
+
.feature-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 18px; }
|
|
26
|
+
.feature-card { padding: 28px; }
|
|
27
|
+
.feature-icon { width: 46px; height: 46px; display: grid; place-items: center; margin-bottom: 28px; border-radius: 12px; color: #187a55; background: #e8f5ee; }
|
|
28
|
+
.results { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 16px; padding-block: 40px 90px; }
|
|
29
|
+
.final-cta { margin-bottom: 80px; padding-block: 70px; text-align: center; border: 1px solid #d7e8df; border-radius: 24px; background: linear-gradient(135deg,#f2faf6,#e4f4eb); }
|
|
30
|
+
.final-cta .coco-stack { align-items: center; max-width: 720px; margin: auto; }
|
|
31
|
+
@media (max-width: 820px) { .marketing-header { grid-template-columns: 1fr auto; } .marketing-header nav { display: none; } .marketing-hero { min-height: auto; grid-template-columns: 1fr; } .feature-grid, .results { grid-template-columns: 1fr; } .hero-panel { min-height: 340px; } }
|
|
32
|
+
@media (max-width: 420px) { .marketing-header { padding-inline: 16px; } .header-cta { padding-inline: 12px; } .marketing-hero { padding-block: 54px; } }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "cocoframe dev .",
|
|
8
|
+
"build": "cocoframe build .",
|
|
9
|
+
"start": "cocoframe start .",
|
|
10
|
+
"check": "tsc --noEmit",
|
|
11
|
+
"inspect": "cocoframe inspect .",
|
|
12
|
+
"generate": "cocoframe generate ."
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@cocoframe/core": "0.0.2",
|
|
16
|
+
"@cocoframe/icons": "0.0.1",
|
|
17
|
+
"@cocoframe/jsx": "0.0.1",
|
|
18
|
+
"@cocoframe/schema": "0.0.1",
|
|
19
|
+
"@cocoframe/ui": "0.0.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@cocoframe/cli": "0.0.2",
|
|
23
|
+
"@types/node": "^24.0.0",
|
|
24
|
+
"typescript": "^5.9.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"exactOptionalPropertyTypes": true,
|
|
9
|
+
"verbatimModuleSyntax": true,
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"jsxImportSource": "@cocoframe/jsx",
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"noEmit": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["app/**/*.ts", "app/**/*.tsx", "cocoframe.config.ts"]
|
|
17
|
+
}
|