create-adjacent 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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/index.js +2498 -0
- package/package.json +52 -0
- package/templates/adjacent/app/manifest.ts +5 -0
- package/templates/adjacent/app/sw.js/route.ts +5 -0
- package/templates/adjacent/app.config.ts +21 -0
- package/templates/adjacent/components/navigation.tsx +46 -0
- package/templates/adjacent/public/apple-touch-icon.png +0 -0
- package/templates/adjacent/public/favicon-96x96.png +0 -0
- package/templates/adjacent/public/favicon.ico +0 -0
- package/templates/adjacent/public/favicon.svg +4 -0
- package/templates/adjacent/public/web-app-manifest-192x192.png +0 -0
- package/templates/adjacent/public/web-app-manifest-512x512.png +0 -0
- package/templates/adjacent/public/web-app-manifest-maskable-192x192.png +0 -0
- package/templates/adjacent/public/web-app-manifest-maskable-512x512.png +0 -0
- package/templates/starter/app/(app)/layout.tsx +12 -0
- package/templates/starter/app/(app)/page.tsx +75 -0
- package/templates/starter/app/(app)/settings/page.tsx +24 -0
- package/templates/starter/app/(app)/settings/settings-panel.tsx +54 -0
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-adjacent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create an Adjacent application: npm create adjacent@latest my-app",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"create",
|
|
7
|
+
"adjacent",
|
|
8
|
+
"pwa",
|
|
9
|
+
"nextjs",
|
|
10
|
+
"scaffold"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "Eric Kweyunga",
|
|
14
|
+
"homepage": "https://github.com/erickweyunga/adjacent-framework/blob/main/docs/create.md",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/erickweyunga/adjacent-framework.git",
|
|
18
|
+
"directory": "packages/create-adjacent"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/erickweyunga/adjacent-framework/issues"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"bin": {
|
|
25
|
+
"create-adjacent": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"templates",
|
|
30
|
+
"README.md",
|
|
31
|
+
"CHANGELOG.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20.12.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "node scripts/copy-templates.mjs && bun build src/index.ts --target node --format esm --outfile dist/index.js",
|
|
42
|
+
"prepublishOnly": "npm run build",
|
|
43
|
+
"test": "bun test",
|
|
44
|
+
"typecheck": "tsc --noEmit"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/bun": "^1.3.13",
|
|
48
|
+
"@types/node": "^20",
|
|
49
|
+
"typescript": "^5",
|
|
50
|
+
"adjacent-cli": "workspace:~"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig } from "adjacent-core";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
app: {
|
|
5
|
+
description: "A mobile-first progressive web application built with Adjacent.",
|
|
6
|
+
name: "{{appName}}",
|
|
7
|
+
shortName: "{{shortName}}",
|
|
8
|
+
slug: "{{slug}}",
|
|
9
|
+
},
|
|
10
|
+
appearance: {
|
|
11
|
+
backgroundColor: "#ffffff",
|
|
12
|
+
themeColor: "#171717",
|
|
13
|
+
},
|
|
14
|
+
// The Android wrapper reads these through the generated web manifest.
|
|
15
|
+
android: {
|
|
16
|
+
navigationColor: "#ffffff",
|
|
17
|
+
navigationColorDark: "#0c0c0e",
|
|
18
|
+
themeColorDark: "#0c0c0e",
|
|
19
|
+
},
|
|
20
|
+
offline: true,
|
|
21
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Tab, Tabs } from "adjacent-ui/navigation";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { usePathname } from "next/navigation";
|
|
6
|
+
|
|
7
|
+
function HomeIcon() {
|
|
8
|
+
return (
|
|
9
|
+
<svg aria-hidden="true" viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
|
|
10
|
+
<path d="M12 3 3 10.5V21h6v-6h6v6h6V10.5L12 3Z" />
|
|
11
|
+
</svg>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function SettingsIcon() {
|
|
16
|
+
return (
|
|
17
|
+
<svg aria-hidden="true" viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
|
|
18
|
+
<path d="M12 15.5A3.5 3.5 0 1 1 12 8.5a3.5 3.5 0 0 1 0 7Zm7.4-2.5a7.6 7.6 0 0 0 0-2l2-1.6-2-3.4-2.4 1a7.6 7.6 0 0 0-1.7-1L14.9 3H9.1l-.4 2.6a7.6 7.6 0 0 0-1.7 1l-2.4-1-2 3.4L4.6 11a7.6 7.6 0 0 0 0 2l-2 1.6 2 3.4 2.4-1a7.6 7.6 0 0 0 1.7 1l.4 2.6h5.8l.4-2.6a7.6 7.6 0 0 0 1.7-1l2.4 1 2-3.4-2-1.6Z" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* One navigation, two presentations: a bottom bar on a phone and a left rail
|
|
25
|
+
* from 768px. Which one shows is decided in CSS, so there is no hydration flash.
|
|
26
|
+
*/
|
|
27
|
+
export function Navigation() {
|
|
28
|
+
const pathname = usePathname();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Tabs>
|
|
32
|
+
<Tab
|
|
33
|
+
active={pathname === "/"}
|
|
34
|
+
icon={<HomeIcon />}
|
|
35
|
+
label="Home"
|
|
36
|
+
render={<Link href="/" />}
|
|
37
|
+
/>
|
|
38
|
+
<Tab
|
|
39
|
+
active={pathname.startsWith("/settings")}
|
|
40
|
+
icon={<SettingsIcon />}
|
|
41
|
+
label="Settings"
|
|
42
|
+
render={<Link href="/settings" />}
|
|
43
|
+
/>
|
|
44
|
+
</Tabs>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img" aria-label="App icon">
|
|
2
|
+
<rect width="100" height="100" fill="#171717" />
|
|
3
|
+
<path fill="#ffffff" fill-rule="evenodd" d="M 50.0 3.0 L 98.5 97.0 L 1.5 97.0 Z M 50.0 25.29 L 71.01 66.0 L 28.99 66.0 Z M 23.06 77.5 L 76.94 77.5 L 87.0 97.0 L 13.0 97.0 Z" />
|
|
4
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NavigationShell } from "adjacent-ui/navigation";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import { Navigation } from "@/components/navigation";
|
|
5
|
+
|
|
6
|
+
export default function AppLayout({ children }: { readonly children: ReactNode }) {
|
|
7
|
+
return (
|
|
8
|
+
<NavigationShell className="bg-background" navigation={<Navigation />}>
|
|
9
|
+
{children}
|
|
10
|
+
</NavigationShell>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
Card,
|
|
4
|
+
CardContent,
|
|
5
|
+
CardDescription,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
Divider,
|
|
9
|
+
List,
|
|
10
|
+
ListItem,
|
|
11
|
+
Page,
|
|
12
|
+
Text,
|
|
13
|
+
} from "adjacent-ui";
|
|
14
|
+
|
|
15
|
+
export default function Home() {
|
|
16
|
+
return (
|
|
17
|
+
<Page
|
|
18
|
+
options={{
|
|
19
|
+
headerVariant: "large",
|
|
20
|
+
subtitle: "A mobile-first progressive web app",
|
|
21
|
+
title: "{{appName}}",
|
|
22
|
+
}}
|
|
23
|
+
>
|
|
24
|
+
<Card variant="outlined">
|
|
25
|
+
<CardHeader>
|
|
26
|
+
<CardTitle>Start here</CardTitle>
|
|
27
|
+
<CardDescription>
|
|
28
|
+
Edit <code>app/(app)/page.tsx</code> and this page reloads as you save.
|
|
29
|
+
</CardDescription>
|
|
30
|
+
</CardHeader>
|
|
31
|
+
<CardContent>
|
|
32
|
+
<Text variant="body-medium">
|
|
33
|
+
Every component reads the design tokens in{" "}
|
|
34
|
+
<code>app/globals.css</code>, so changing one there changes the whole
|
|
35
|
+
application.
|
|
36
|
+
</Text>
|
|
37
|
+
</CardContent>
|
|
38
|
+
</Card>
|
|
39
|
+
|
|
40
|
+
<Card variant="outlined">
|
|
41
|
+
<CardHeader>
|
|
42
|
+
<CardTitle>What is configured</CardTitle>
|
|
43
|
+
</CardHeader>
|
|
44
|
+
<List divided dividerInset="none" lastDivider={false}>
|
|
45
|
+
<ListItem
|
|
46
|
+
headline="Offline support"
|
|
47
|
+
supportingText="A service worker is served from /sw.js in production builds."
|
|
48
|
+
/>
|
|
49
|
+
<ListItem
|
|
50
|
+
headline="Web app manifest"
|
|
51
|
+
supportingText="Generated from app.config.ts at /manifest.webmanifest."
|
|
52
|
+
/>
|
|
53
|
+
<ListItem
|
|
54
|
+
headline="Install prompt"
|
|
55
|
+
supportingText="Installation state is reported on the Settings tab."
|
|
56
|
+
/>
|
|
57
|
+
</List>
|
|
58
|
+
</Card>
|
|
59
|
+
|
|
60
|
+
<Divider />
|
|
61
|
+
|
|
62
|
+
<Text variant="label-large">Run it on a phone</Text>
|
|
63
|
+
<Text variant="body-small">
|
|
64
|
+
With a device connected over ADB, <code>adj dev --android</code> serves
|
|
65
|
+
this app over a trusted HTTPS tunnel and opens it fullscreen.
|
|
66
|
+
</Text>
|
|
67
|
+
|
|
68
|
+
<div>
|
|
69
|
+
<Button size="sm" variant="filled">
|
|
70
|
+
A filled button
|
|
71
|
+
</Button>
|
|
72
|
+
</div>
|
|
73
|
+
</Page>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Card, CardContent, CardHeader, CardTitle, Page, Text } from "adjacent-ui";
|
|
2
|
+
|
|
3
|
+
import { SettingsPanel } from "./settings-panel";
|
|
4
|
+
|
|
5
|
+
export default function Settings() {
|
|
6
|
+
return (
|
|
7
|
+
<Page options={{ title: "Settings" }}>
|
|
8
|
+
<Card variant="outlined">
|
|
9
|
+
<CardHeader>
|
|
10
|
+
<CardTitle>Runtime</CardTitle>
|
|
11
|
+
</CardHeader>
|
|
12
|
+
<CardContent>
|
|
13
|
+
<Text variant="body-small">
|
|
14
|
+
Reported by <code>useAdjacent()</code> from{" "}
|
|
15
|
+
<code>adjacent-react</code>. The service worker registers in
|
|
16
|
+
production builds only, so it stays unavailable while developing.
|
|
17
|
+
</Text>
|
|
18
|
+
</CardContent>
|
|
19
|
+
</Card>
|
|
20
|
+
|
|
21
|
+
<SettingsPanel />
|
|
22
|
+
</Page>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useAdjacent } from "adjacent-react";
|
|
4
|
+
import { Button, List, ListItem, Text, useDisplayMode } from "adjacent-ui";
|
|
5
|
+
|
|
6
|
+
const INSTALL_HINT: Readonly<Record<string, string>> = {
|
|
7
|
+
android: "Open the browser menu and choose Install app.",
|
|
8
|
+
browser: "This browser cannot install the app.",
|
|
9
|
+
ios: "Open the Share sheet and choose Add to Home Screen.",
|
|
10
|
+
macos: "Open the Share menu and choose Add to Dock.",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Adjacent reports installation and runtime state and renders no UI of its own,
|
|
15
|
+
* so what a prompt looks like is entirely this component's decision.
|
|
16
|
+
*/
|
|
17
|
+
export function SettingsPanel() {
|
|
18
|
+
const adjacent = useAdjacent();
|
|
19
|
+
const displayMode = useDisplayMode();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<List divided lastDivider={false}>
|
|
24
|
+
<ListItem headline="Connectivity" trailingSupportingText={adjacent.connectivity} />
|
|
25
|
+
<ListItem headline="Display mode" trailingSupportingText={displayMode} />
|
|
26
|
+
<ListItem
|
|
27
|
+
headline="Service worker"
|
|
28
|
+
trailingSupportingText={adjacent.serviceWorker.status}
|
|
29
|
+
/>
|
|
30
|
+
<ListItem headline="Installation" trailingSupportingText={adjacent.state.status} />
|
|
31
|
+
</List>
|
|
32
|
+
|
|
33
|
+
{adjacent.state.status === "available" ? (
|
|
34
|
+
<div>
|
|
35
|
+
<Button onClick={() => void adjacent.install()} variant="filled">
|
|
36
|
+
Install app
|
|
37
|
+
</Button>
|
|
38
|
+
</div>
|
|
39
|
+
) : null}
|
|
40
|
+
|
|
41
|
+
{adjacent.state.status === "manual" ? (
|
|
42
|
+
<Text variant="body-small">{INSTALL_HINT[adjacent.state.platform]}</Text>
|
|
43
|
+
) : null}
|
|
44
|
+
|
|
45
|
+
{adjacent.serviceWorker.status === "update-available" ? (
|
|
46
|
+
<div>
|
|
47
|
+
<Button onClick={() => void adjacent.activateUpdate()} variant="tonal">
|
|
48
|
+
Update app
|
|
49
|
+
</Button>
|
|
50
|
+
</div>
|
|
51
|
+
) : null}
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
}
|