create-sanifyfe 0.1.1 → 0.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sanifyfe",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Scaffold project Sanify baru — bun create sanifyfe",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,6 +22,13 @@ bun install
22
22
 
23
23
  ## Struktur
24
24
 
25
- - `src/main.ts` — entry aplikasi (komponen, router, state).
26
- - `index.html` — halaman host, memuat `main.js` hasil bundling.
27
- - `dev-server.ts` server dev Bun yang transpile TS on-the-fly.
25
+ ```
26
+ src/
27
+ main.ts entry: memuat app-root
28
+ app.ts root komponen + definisi router
29
+ state/todos.ts state global (persisted + cross-tab sync)
30
+ components/ komponen UI (todo-item)
31
+ pages/ halaman per-route (todo-page, about-page)
32
+ index.html halaman host, memuat main.js hasil bundling
33
+ dev-server.ts server dev Bun, transpile TS on-the-fly
34
+ ```
@@ -0,0 +1,19 @@
1
+ // app.ts — root komponen + router
2
+
3
+ import { component, html, router } from "@sanify/core";
4
+ import "./pages/todo-page.ts";
5
+ import "./pages/about-page.ts";
6
+
7
+ component("app-root", () => {
8
+ const view = router({
9
+ "/": () => html`<todo-page></todo-page>`,
10
+ "/about": () => html`<about-page></about-page>`,
11
+ "*": () => html`
12
+ <div class="max-w-md mx-auto mt-10 text-center">
13
+ <p class="text-slate-500">Halaman tidak ditemukan.</p>
14
+ <a data-link href="/" class="text-blue-600 hover:underline">Beranda</a>
15
+ </div>
16
+ `,
17
+ });
18
+ return () => html`<div>${view}</div>`;
19
+ });
@@ -0,0 +1,36 @@
1
+ // components/todo-item.ts — satu baris todo (menerima objek lewat property)
2
+
3
+ import { component, html, type ComponentContext } from "@sanify/core";
4
+ import { toggleTodo, removeTodo, type Todo } from "../state/todos.ts";
5
+
6
+ interface TodoItemProps {
7
+ todo: Todo;
8
+ }
9
+
10
+ component<TodoItemProps>(
11
+ "todo-item",
12
+ ({ props }: ComponentContext<TodoItemProps>) => {
13
+ return () => html`
14
+ <li class="flex items-center gap-3 py-2 border-b border-slate-200">
15
+ <input
16
+ type="checkbox"
17
+ class="h-4 w-4"
18
+ .checked=${() => props.todo().done}
19
+ @change=${() => toggleTodo(props.todo().id)}
20
+ />
21
+ <span
22
+ class=${() =>
23
+ props.todo().done ? "flex-1 line-through text-slate-400" : "flex-1"}
24
+ >${() => props.todo().text}</span
25
+ >
26
+ <button
27
+ class="text-red-500 hover:text-red-700 text-sm"
28
+ @click=${() => removeTodo(props.todo().id)}
29
+ >
30
+ hapus
31
+ </button>
32
+ </li>
33
+ `;
34
+ },
35
+ { props: ["todo"] },
36
+ );
@@ -1,156 +1,3 @@
1
- // example/main.ts — aplikasi contoh memakai seluruh API Sanify
2
- // Todo dengan persist + cross-tab sync, routing, props dua arah, list & conditional.
1
+ // main.ts — entry aplikasi: cukup memuat app-root (komponen lain ikut terdaftar)
3
2
 
4
- import {
5
- component,
6
- html,
7
- signal,
8
- computed,
9
- persisted,
10
- router,
11
- navigate,
12
- type ComponentContext,
13
- } from "@sanify/core";
14
-
15
- // ── State global (persisted + cross-tab sync) ───────────────
16
- interface Todo {
17
- id: number;
18
- text: string;
19
- done: boolean;
20
- }
21
-
22
- const [todos, setTodos] = persisted<Todo[]>("sanify:todos", [], { sync: true });
23
-
24
- const remaining = computed(() => todos().filter((t) => !t.done).length);
25
-
26
- function addTodo(text: string): void {
27
- const trimmed = text.trim();
28
- if (!trimmed) return;
29
- setTodos((prev) => [...prev, { id: Date.now(), text: trimmed, done: false }]);
30
- }
31
-
32
- function toggleTodo(id: number): void {
33
- setTodos((prev) => prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
34
- }
35
-
36
- function removeTodo(id: number): void {
37
- setTodos((prev) => prev.filter((t) => t.id !== id));
38
- }
39
-
40
- // ── Komponen: satu baris todo (menerima objek lewat property) ─
41
- interface TodoItemProps {
42
- todo: Todo;
43
- }
44
-
45
- component<TodoItemProps>(
46
- "todo-item",
47
- ({ props }: ComponentContext<TodoItemProps>) => {
48
- return () => html`
49
- <li class="flex items-center gap-3 py-2 border-b border-slate-200">
50
- <input
51
- type="checkbox"
52
- class="h-4 w-4"
53
- .checked=${() => props.todo().done}
54
- @change=${() => toggleTodo(props.todo().id)}
55
- />
56
- <span
57
- class=${() =>
58
- props.todo().done
59
- ? "flex-1 line-through text-slate-400"
60
- : "flex-1"}
61
- >${() => props.todo().text}</span
62
- >
63
- <button
64
- class="text-red-500 hover:text-red-700 text-sm"
65
- @click=${() => removeTodo(props.todo().id)}
66
- >
67
- hapus
68
- </button>
69
- </li>
70
- `;
71
- },
72
- { props: ["todo"] },
73
- );
74
-
75
- // ── Halaman: daftar todo ────────────────────────────────────
76
- component("todo-page", () => {
77
- const [draft, setDraft] = signal("");
78
-
79
- const submit = () => {
80
- addTodo(draft());
81
- setDraft("");
82
- };
83
-
84
- return () => html`
85
- <div class="max-w-md mx-auto mt-10 p-6 bg-white rounded-xl shadow">
86
- <h1 class="text-2xl font-bold mb-1">Todo</h1>
87
- <p class="text-sm text-slate-500 mb-4">
88
- Sisa <strong>${() => remaining()}</strong> belum selesai
89
- — buka di dua tab untuk lihat sync.
90
- </p>
91
-
92
- <div class="flex gap-2 mb-4">
93
- <input
94
- class="flex-1 border border-slate-300 rounded px-3 py-2"
95
- placeholder="Tambah tugas..."
96
- .value=${() => draft()}
97
- @input=${(e: Event) => setDraft((e.target as HTMLInputElement).value)}
98
- @keydown=${(e: KeyboardEvent) => e.key === "Enter" && submit()}
99
- />
100
- <button
101
- class="bg-slate-800 text-white rounded px-4 py-2 hover:bg-slate-700"
102
- @click=${submit}
103
- >
104
- Tambah
105
- </button>
106
- </div>
107
-
108
- <ul>
109
- ${() =>
110
- todos().length === 0
111
- ? html`<li class="text-slate-400 py-2">Belum ada tugas.</li>`
112
- : todos().map((t) => html`<todo-item .todo=${t}></todo-item>`)}
113
- </ul>
114
-
115
- <div class="mt-4 text-sm">
116
- <a data-link href="/about" class="text-blue-600 hover:underline"
117
- >Tentang &rarr;</a
118
- >
119
- </div>
120
- </div>
121
- `;
122
- });
123
-
124
- // ── Halaman: about ──────────────────────────────────────────
125
- component("about-page", () => {
126
- return () => html`
127
- <div class="max-w-md mx-auto mt-10 p-6 bg-white rounded-xl shadow">
128
- <h1 class="text-2xl font-bold mb-2">Tentang</h1>
129
- <p class="text-slate-600 mb-4">
130
- Contoh kecil yang dibangun dengan Sanify: signal fine-grained,
131
- Web Components Light DOM, persist + cross-tab sync, dan router.
132
- </p>
133
- <a data-link href="/" class="text-blue-600 hover:underline"
134
- >&larr; Kembali</a
135
- >
136
- </div>
137
- `;
138
- });
139
-
140
- // ── Root + router ───────────────────────────────────────────
141
- component("app-root", () => {
142
- const view = router({
143
- "/": () => html`<todo-page></todo-page>`,
144
- "/about": () => html`<about-page></about-page>`,
145
- "*": () => html`
146
- <div class="max-w-md mx-auto mt-10 text-center">
147
- <p class="text-slate-500">Halaman tidak ditemukan.</p>
148
- <a data-link href="/" class="text-blue-600 hover:underline">Beranda</a>
149
- </div>
150
- `,
151
- });
152
- return () => html`<div>${view}</div>`;
153
- });
154
-
155
- // hindari unused-import error untuk navigate (tersedia utk pemakaian manual)
156
- void navigate;
3
+ import "./app.ts";
@@ -0,0 +1,16 @@
1
+ // pages/about-page.ts — halaman tentang
2
+
3
+ import { component, html } from "@sanify/core";
4
+
5
+ component("about-page", () => {
6
+ return () => html`
7
+ <div class="max-w-md mx-auto mt-10 p-6 bg-white rounded-xl shadow">
8
+ <h1 class="text-2xl font-bold mb-2">Tentang</h1>
9
+ <p class="text-slate-600 mb-4">
10
+ Contoh kecil yang dibangun dengan Sanify: signal fine-grained, Web
11
+ Components Light DOM, persist + cross-tab sync, dan router.
12
+ </p>
13
+ <a data-link href="/" class="text-blue-600 hover:underline">&larr; Kembali</a>
14
+ </div>
15
+ `;
16
+ });
@@ -0,0 +1,58 @@
1
+ // pages/todo-page.ts — halaman daftar todo
2
+
3
+ import { component, html, signal, For } from "@sanify/core";
4
+ import { todos, remaining, addTodo, type Todo } from "../state/todos.ts";
5
+ import "../components/todo-item.ts";
6
+
7
+ component("todo-page", () => {
8
+ const [draft, setDraft] = signal("");
9
+
10
+ const submit = () => {
11
+ addTodo(draft());
12
+ setDraft("");
13
+ };
14
+
15
+ return () => html`
16
+ <div class="max-w-md mx-auto mt-10 p-6 bg-white rounded-xl shadow">
17
+ <h1 class="text-2xl font-bold mb-1">Todo</h1>
18
+ <p class="text-sm text-slate-500 mb-4">
19
+ Sisa <strong>${() => remaining()}</strong> belum selesai — buka di dua
20
+ tab untuk lihat sync.
21
+ </p>
22
+
23
+ <div class="flex gap-2 mb-4">
24
+ <input
25
+ class="flex-1 border border-slate-300 rounded px-3 py-2"
26
+ placeholder="Tambah tugas..."
27
+ .value=${() => draft()}
28
+ @input=${(e: Event) => setDraft((e.target as HTMLInputElement).value)}
29
+ @keydown=${(e: KeyboardEvent) => e.key === "Enter" && submit()}
30
+ />
31
+ <button
32
+ class="bg-slate-800 text-white rounded px-4 py-2 hover:bg-slate-700"
33
+ @click=${submit}
34
+ >
35
+ Tambah
36
+ </button>
37
+ </div>
38
+
39
+ <ul>
40
+ ${For(
41
+ () => todos(),
42
+ (todo: () => Todo) => html`<todo-item .todo=${todo}></todo-item>`,
43
+ { key: (t: Todo) => t.id },
44
+ )}
45
+ ${() =>
46
+ todos().length === 0
47
+ ? html`<li class="text-slate-400 py-2">Belum ada tugas.</li>`
48
+ : null}
49
+ </ul>
50
+
51
+ <div class="mt-4 text-sm">
52
+ <a data-link href="/about" class="text-blue-600 hover:underline"
53
+ >Tentang &rarr;</a
54
+ >
55
+ </div>
56
+ </div>
57
+ `;
58
+ });
@@ -0,0 +1,32 @@
1
+ // state/todos.ts — state global todo (persisted + cross-tab sync)
2
+
3
+ import { computed, persisted } from "@sanify/core";
4
+
5
+ export interface Todo {
6
+ id: string;
7
+ text: string;
8
+ done: boolean;
9
+ }
10
+
11
+ export const [todos, setTodos] = persisted<Todo[]>("sanify:todos", [], {
12
+ sync: true,
13
+ });
14
+
15
+ export const remaining = computed(() => todos().filter((t) => !t.done).length);
16
+
17
+ export function addTodo(text: string): void {
18
+ const trimmed = text.trim();
19
+ if (!trimmed) return;
20
+ setTodos((prev) => [
21
+ ...prev,
22
+ { id: crypto.randomUUID(), text: trimmed, done: false },
23
+ ]);
24
+ }
25
+
26
+ export function toggleTodo(id: string): void {
27
+ setTodos((prev) => prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
28
+ }
29
+
30
+ export function removeTodo(id: string): void {
31
+ setTodos((prev) => prev.filter((t) => t.id !== id));
32
+ }
@@ -5,6 +5,7 @@
5
5
  "module": "ESNext",
6
6
  "moduleResolution": "bundler",
7
7
  "moduleDetection": "force",
8
+ "allowImportingTsExtensions": true,
8
9
  "verbatimModuleSyntax": true,
9
10
  "noEmit": true,
10
11