create-cartbase 0.1.5 → 0.1.7

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 (32) hide show
  1. package/README.md +13 -8
  2. package/dist/index.js +28 -20
  3. package/package.json +3 -3
  4. package/template/app/AGENTS.md +29 -0
  5. package/template/app/CLAUDE.md +19 -8
  6. package/template/app/docs/BUILD-A-STOREFRONT.md +233 -226
  7. package/template/app/docs/README.md +1 -0
  8. package/template/app/docs/components.md +1134 -1090
  9. package/template/app/docs/deploy.md +15 -10
  10. package/template/app/docs/integrations.md +15 -3
  11. package/template/app/docs/platform.md +1 -1
  12. package/template/app/docs/store.md +47 -0
  13. package/template/app/docs/variables.md +3 -3
  14. package/template/app/next.config.ts +10 -14
  15. package/template/app/package.json +4 -3
  16. package/template/app/src/app/checkout/checkout-page-client.tsx +0 -20
  17. package/template/app/src/app/globals.css +1 -1
  18. package/template/app/src/app/layout.tsx +73 -18
  19. package/template/app/src/app/order/[id]/confirmed/page.tsx +92 -78
  20. package/template/app/src/lib/config.ts +30 -21
  21. package/template/app/next-env.d.ts +0 -6
  22. package/template/app/smoke.mjs +0 -158
  23. package/template/app/src/app/checkout/mypos-demo-tab.tsx +0 -101
  24. package/template/app/src/app/gallery/[slug]/page.tsx +0 -172
  25. package/template/app/src/app/gallery/_components/specimens.tsx +0 -254
  26. package/template/app/src/app/gallery/_components/status.tsx +0 -47
  27. package/template/app/src/app/gallery/_lib/catalog.ts +0 -59
  28. package/template/app/src/app/gallery/_lib/registry.ts +0 -401
  29. package/template/app/src/app/gallery/design-system/page.tsx +0 -353
  30. package/template/app/src/app/gallery/layout.tsx +0 -83
  31. package/template/app/src/app/gallery/page.tsx +0 -81
  32. package/template/app/tsconfig.tsbuildinfo +0 -1
@@ -1,353 +0,0 @@
1
- /**
2
- * The design system on one screen.
3
- *
4
- * Every token the library owns, rendered as the thing it controls rather than
5
- * listed as text: a colour is a swatch, a type style is set in that style, a
6
- * shadow is a raised card, a radius is a corner. You review the system by
7
- * looking at it, not by reading a table of values.
8
- *
9
- * These are NOT components. They are the layer underneath every component, and
10
- * they come first in the nav for that reason: change one value here and all
11
- * hundred and seventeen components repaint at once.
12
- */
13
- export const dynamic = "force-dynamic"
14
-
15
- export default function DesignSystemPage() {
16
- return (
17
- <div className="space-y-16">
18
- <header>
19
- <h1 className="text-h1">Design system</h1>
20
- <p className="text-body text-muted-foreground mt-3 max-w-prose">
21
- The values every component reads. Nothing here is a Cartbase
22
- invention: the namespaces are Tailwind&rsquo;s and the colour names
23
- are shadcn&rsquo;s, so a developer or a coding agent recognises them
24
- without documentation. A theme replaces these values and never these
25
- names.
26
- </p>
27
- </header>
28
-
29
- <Colour />
30
- <Typography />
31
- <Weight />
32
- <Spacing />
33
- <Radius />
34
- <Elevation />
35
- <Motion />
36
- <Layout />
37
- </div>
38
- )
39
- }
40
-
41
- /* ── Colour ─────────────────────────────────────────────────────────────── */
42
-
43
- const SURFACES = [
44
- { name: "background", fg: "foreground", use: "The page itself." },
45
- { name: "card", fg: "card-foreground", use: "Anything raised off the page." },
46
- { name: "popover", fg: "popover-foreground", use: "Menus, dropdowns, tooltips." },
47
- { name: "muted", fg: "muted-foreground", use: "Recessed areas and secondary text." },
48
- ]
49
-
50
- const ROLES = [
51
- { name: "primary", fg: "primary-foreground", use: "The main action. Add to cart is this." },
52
- { name: "secondary", fg: "secondary-foreground", use: "The action beside the main one." },
53
- { name: "accent", fg: "accent-foreground", use: "Highlights and hover grounds." },
54
- { name: "destructive", fg: "destructive-foreground", use: "Remove, cancel, delete." },
55
- { name: "success", fg: "success-foreground", use: "Paid, delivered, in stock." },
56
- { name: "warning", fg: "warning-foreground", use: "Low stock, pending, attention." },
57
- ]
58
-
59
- const LINES = [
60
- { name: "border", use: "Every dividing line." },
61
- { name: "input", use: "Form field outlines." },
62
- { name: "ring", use: "Keyboard focus." },
63
- ]
64
-
65
- function Colour() {
66
- return (
67
- <Section
68
- title="Colour"
69
- blurb="Named by role, never by hue. Every surface carries a paired foreground so text on it is never a guess."
70
- >
71
- <SubTitle>Surfaces</SubTitle>
72
- <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4 mb-10">
73
- {SURFACES.map((s) => (
74
- <Swatch key={s.name} name={s.name} fg={s.fg} use={s.use} />
75
- ))}
76
- </div>
77
-
78
- <SubTitle>Roles</SubTitle>
79
- <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 mb-10">
80
- {ROLES.map((s) => (
81
- <Swatch key={s.name} name={s.name} fg={s.fg} use={s.use} />
82
- ))}
83
- </div>
84
-
85
- <SubTitle>Lines and focus</SubTitle>
86
- <div className="grid gap-4 sm:grid-cols-3">
87
- {LINES.map((l) => (
88
- <div key={l.name} className="rounded-lg border border-border p-4">
89
- <div
90
- className="h-10 rounded-md mb-3"
91
- style={{ backgroundColor: `var(--${l.name})` }}
92
- />
93
- <code className="text-caption font-mono">{l.name}</code>
94
- <p className="text-caption text-muted-foreground mt-1">{l.use}</p>
95
- </div>
96
- ))}
97
- </div>
98
- </Section>
99
- )
100
- }
101
-
102
- function Swatch({ name, fg, use }: { name: string; fg: string; use: string }) {
103
- return (
104
- <div className="rounded-lg border border-border overflow-hidden">
105
- <div
106
- className="p-5"
107
- style={{ backgroundColor: `var(--${name})`, color: `var(--${fg})` }}
108
- >
109
- <span className="text-label">Aa</span>
110
- </div>
111
- <div className="p-3">
112
- <code className="text-caption font-mono">{name}</code>
113
- <p className="text-caption text-muted-foreground mt-1">{use}</p>
114
- </div>
115
- </div>
116
- )
117
- }
118
-
119
- /* ── Typography ─────────────────────────────────────────────────────────── */
120
-
121
- const TYPE = [
122
- { cls: "text-h1", label: "h1", sample: "Everyday Essentials" },
123
- { cls: "text-h2", label: "h2", sample: "New this season" },
124
- { cls: "text-h3", label: "h3", sample: "Merino Crew Neck" },
125
- { cls: "text-h4", label: "h4", sample: "Materials and care" },
126
- { cls: "text-h5", label: "h5", sample: "Shipping" },
127
- { cls: "text-h6", label: "h6", sample: "Returns within 30 days" },
128
- {
129
- cls: "text-body-large",
130
- label: "body-large",
131
- sample: "Knitted from extra-fine merino at a gauge light enough to layer.",
132
- },
133
- {
134
- cls: "text-body",
135
- label: "body",
136
- sample: "Knitted from extra-fine merino at a gauge light enough to layer.",
137
- },
138
- {
139
- cls: "text-body-small",
140
- label: "body-small",
141
- sample: "Knitted from extra-fine merino at a gauge light enough to layer.",
142
- },
143
- { cls: "text-label", label: "label", sample: "Add to cart" },
144
- { cls: "text-caption", label: "caption", sample: "Free delivery over 100 EUR" },
145
- ]
146
-
147
- function Typography() {
148
- return (
149
- <Section
150
- title="Type"
151
- blurb="Eleven styles on a 1.2 ratio from a 16px base. The name is a style, never a tag: put text-h2 on an h1 when the page needs a smaller heading, and the document outline stays correct for search engines and screen readers."
152
- >
153
- <div className="space-y-6">
154
- {TYPE.map((t) => (
155
- <div key={t.cls} className="flex items-baseline gap-6 border-b border-border pb-5">
156
- <code className="text-caption font-mono text-muted-foreground w-28 shrink-0">
157
- {t.label}
158
- </code>
159
- <p className={`${t.cls} min-w-0`}>{t.sample}</p>
160
- </div>
161
- ))}
162
- </div>
163
- </Section>
164
- )
165
- }
166
-
167
- function Weight() {
168
- return (
169
- <Section
170
- title="Weight"
171
- blurb="Three, not four. Bold and semibold were doing the same job in different places, which is how a scale rots."
172
- >
173
- <div className="space-y-4">
174
- {[
175
- ["regular", "font-regular"],
176
- ["medium", "font-medium"],
177
- ["semibold", "font-semibold"],
178
- ].map(([label, cls]) => (
179
- <div key={label} className="flex items-baseline gap-6">
180
- <code className="text-caption font-mono text-muted-foreground w-28 shrink-0">
181
- {label}
182
- </code>
183
- <p className={`text-h5 ${cls}`}>Merino Crew Neck</p>
184
- </div>
185
- ))}
186
- </div>
187
- </Section>
188
- )
189
- }
190
-
191
- /* ── Spacing, radius, elevation, motion, layout ─────────────────────────── */
192
-
193
- const SPACE_STEPS = [1, 2, 3, 4, 6, 8, 12, 16, 24]
194
-
195
- function Spacing() {
196
- return (
197
- <Section
198
- title="Spacing"
199
- blurb="One 4px step, multiplied. Every gap, padding and margin in the library is a multiple of it, so widening the whole rhythm is a single number."
200
- >
201
- <div className="space-y-2">
202
- {SPACE_STEPS.map((n) => (
203
- <div key={n} className="flex items-center gap-4">
204
- <code className="text-caption font-mono text-muted-foreground w-14 shrink-0">
205
- {n}
206
- </code>
207
- <div
208
- className="h-4 bg-primary rounded-sm"
209
- style={{ width: `calc(var(--spacing) * ${n})` }}
210
- />
211
- <span className="text-caption text-muted-foreground">{n * 4}px</span>
212
- </div>
213
- ))}
214
- </div>
215
- </Section>
216
- )
217
- }
218
-
219
- function Radius() {
220
- return (
221
- <Section
222
- title="Radius"
223
- blurb="Derived from one base value, so a theme turns the whole library sharp or soft with one number."
224
- >
225
- {/* Class names are written out, never built from a variable: Tailwind
226
- scans source text, so `rounded-${r}` would generate nothing. */}
227
- <div className="flex flex-wrap gap-6">
228
- {[
229
- ["sm", "rounded-sm"],
230
- ["md", "rounded-md"],
231
- ["lg", "rounded-lg"],
232
- ["xl", "rounded-xl"],
233
- ["2xl", "rounded-2xl"],
234
- ].map(([label, cls]) => (
235
- <div key={label} className="text-center">
236
- <div className={`w-24 h-24 bg-muted border border-border ${cls}`} />
237
- <code className="text-caption font-mono text-muted-foreground mt-2 block">
238
- {label}
239
- </code>
240
- </div>
241
- ))}
242
- </div>
243
- </Section>
244
- )
245
- }
246
-
247
- function Elevation() {
248
- return (
249
- <Section
250
- title="Elevation"
251
- blurb="Named for what is raised, not for how big the blur is. shadow-card says what it is for; shadow-md says nothing and cannot be retuned per theme."
252
- >
253
- <div className="grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
254
- {[
255
- ["card", "shadow-card"],
256
- ["card-hover", "shadow-card-hover"],
257
- ["popover", "shadow-popover"],
258
- ["drawer", "shadow-drawer"],
259
- ].map(([label, cls]) => (
260
- <div key={label}>
261
- <div className={`h-24 bg-card rounded-lg ${cls}`} />
262
- <code className="text-caption font-mono text-muted-foreground mt-3 block">
263
- {label}
264
- </code>
265
- </div>
266
- ))}
267
- </div>
268
- </Section>
269
- )
270
- }
271
-
272
- function Motion() {
273
- return (
274
- <Section
275
- title="Motion"
276
- blurb="Three curves. Standard for things that move within the page, entrance for things arriving, exit for things leaving. Hover a bar to see it."
277
- >
278
- <div className="space-y-4">
279
- {[
280
- ["standard", "ease-standard"],
281
- ["entrance", "ease-entrance"],
282
- ["exit", "ease-exit"],
283
- ].map(([label, cls]) => (
284
- <div key={label} className="flex items-center gap-6 group">
285
- <code className="text-caption font-mono text-muted-foreground w-28 shrink-0">
286
- {label}
287
- </code>
288
- <div className="flex-1 h-8 bg-muted rounded-md relative overflow-hidden">
289
- <div
290
- className={`absolute inset-y-1 left-1 w-8 bg-primary rounded-sm transition-all duration-500 ${cls} group-hover:left-[calc(100%-2.25rem)]`}
291
- />
292
- </div>
293
- </div>
294
- ))}
295
- </div>
296
- </Section>
297
- )
298
- }
299
-
300
- function Layout() {
301
- return (
302
- <Section
303
- title="Layout"
304
- blurb="Two widths. The page holds the whole store, prose holds anything meant to be read as a paragraph."
305
- >
306
- <div className="space-y-4">
307
- {[
308
- ["page", "var(--layout-page-width)", "The store's outer bound."],
309
- ["prose", "var(--layout-prose-width)", "Reading measure for descriptions and policies."],
310
- ].map(([name, val, use]) => (
311
- <div key={name}>
312
- <div className="flex items-baseline gap-3">
313
- <code className="text-caption font-mono">{name}</code>
314
- <span className="text-caption text-muted-foreground">{use}</span>
315
- </div>
316
- <div
317
- className="h-3 bg-primary/20 border border-primary/40 rounded-sm mt-2 max-w-full"
318
- style={{ width: val }}
319
- />
320
- </div>
321
- ))}
322
- </div>
323
- </Section>
324
- )
325
- }
326
-
327
- /* ── Frame ──────────────────────────────────────────────────────────────── */
328
-
329
- function Section({
330
- title,
331
- blurb,
332
- children,
333
- }: {
334
- title: string
335
- blurb: string
336
- children: React.ReactNode
337
- }) {
338
- return (
339
- <section>
340
- <h2 className="text-h3">{title}</h2>
341
- <p className="text-body-small text-muted-foreground mt-2 mb-8 max-w-prose">{blurb}</p>
342
- {children}
343
- </section>
344
- )
345
- }
346
-
347
- function SubTitle({ children }: { children: React.ReactNode }) {
348
- return (
349
- <h3 className="text-caption uppercase tracking-wide text-muted-foreground mb-3">
350
- {children}
351
- </h3>
352
- )
353
- }
@@ -1,83 +0,0 @@
1
- /**
2
- * The workbench.
3
- *
4
- * Everything in the library has an address here, grouped the way a merchant
5
- * thinks about a storefront rather than the way our folders are arranged.
6
- * The nav is always present, so you can move between any two components
7
- * without going back to an index, and every component has a permanent URL so
8
- * it can be named in conversation and opened directly.
9
- */
10
- import Link from "next/link"
11
- import { GROUPS } from "./_lib/registry"
12
- import { StatusDot } from "./_components/status"
13
-
14
- export default function GalleryLayout({ children }: { children: React.ReactNode }) {
15
- return (
16
- <div className="flex min-h-screen">
17
- <aside className="w-64 shrink-0 border-r border-border bg-muted/20">
18
- <div className="sticky top-0 max-h-screen overflow-y-auto px-4 py-6">
19
- <Link href="/gallery" className="block mb-6">
20
- <p className="text-[11px] uppercase tracking-widest text-muted-foreground">
21
- Cartbase
22
- </p>
23
- <p className="font-semibold">Storefront library</p>
24
- </Link>
25
-
26
- <nav className="space-y-6">
27
- {/*
28
- The design system sits above every group because it is a
29
- different kind of thing: not a component, but the values all
30
- 117 of them read. Change one here and everything below repaints.
31
- */}
32
- <div>
33
- <p className="text-[11px] uppercase tracking-widest text-muted-foreground mb-2 px-2">
34
- Foundation
35
- </p>
36
- <Link
37
- href="/gallery/design-system"
38
- className="flex items-center gap-2 px-2 py-1 rounded text-sm text-muted-foreground hover:bg-muted hover:text-foreground"
39
- >
40
- <span className="inline-block w-1.5 h-1.5 rounded-full shrink-0 bg-primary" />
41
- <span className="truncate">Design system</span>
42
- </Link>
43
- </div>
44
-
45
- {GROUPS.map((group) => (
46
- <div key={group.slug}>
47
- <p className="text-[11px] uppercase tracking-widest text-muted-foreground mb-2 px-2">
48
- {group.name}
49
- </p>
50
- <ul className="space-y-0.5">
51
- {group.entries.map((entry) => (
52
- <li key={entry.slug}>
53
- <Link
54
- href={`/gallery/${entry.slug}`}
55
- className="flex items-center gap-2 px-2 py-1 rounded text-sm text-muted-foreground hover:bg-muted hover:text-foreground"
56
- >
57
- <StatusDot status={entry.status} />
58
- <span className="truncate">{entry.name}</span>
59
- </Link>
60
- </li>
61
- ))}
62
- </ul>
63
- </div>
64
- ))}
65
- </nav>
66
-
67
- <div className="mt-8 pt-6 border-t border-border">
68
- <Link
69
- href="/"
70
- className="block px-2 py-1 rounded text-sm text-muted-foreground hover:bg-muted hover:text-foreground"
71
- >
72
- Open the shop
73
- </Link>
74
- </div>
75
- </div>
76
- </aside>
77
-
78
- <div className="min-w-0 flex-1">
79
- <div className="max-w-5xl px-10 py-10">{children}</div>
80
- </div>
81
- </div>
82
- )
83
- }
@@ -1,81 +0,0 @@
1
- /**
2
- * The board. Everything the library has and everything it owes, at a glance,
3
- * so the size of the job is never a surprise.
4
- */
5
- import Link from "next/link"
6
- import { GROUPS, counts } from "./_lib/registry"
7
- import { StatusDot } from "./_components/status"
8
-
9
- export const dynamic = "force-dynamic"
10
-
11
- export default function GalleryIndex() {
12
- const c = counts()
13
-
14
- return (
15
- <div>
16
- <header className="mb-10">
17
- <h1 className="text-3xl font-semibold">The library</h1>
18
- <p className="text-muted-foreground mt-3 max-w-2xl">
19
- Every part of a storefront we ship, or still owe. Open one and we
20
- shape it together; when it is agreed it turns green and stops being
21
- negotiable.
22
- </p>
23
- </header>
24
-
25
- <div className="grid grid-cols-3 gap-4 mb-12">
26
- <Tally label="Agreed" value={c.done} tone="done" />
27
- <Tally label="Built, not reviewed" value={c.built} tone="built" />
28
- <Tally label="Not built" value={c.missing} tone="missing" />
29
- </div>
30
-
31
- <div className="space-y-10">
32
- {GROUPS.map((group) => (
33
- <section key={group.slug}>
34
- <h2 className="text-lg font-semibold">{group.name}</h2>
35
- <p className="text-sm text-muted-foreground mt-1 mb-4 max-w-2xl">{group.blurb}</p>
36
- <ul className="grid gap-2 sm:grid-cols-2">
37
- {group.entries.map((entry) => (
38
- <li key={entry.slug}>
39
- <Link
40
- href={`/gallery/${entry.slug}`}
41
- className="flex items-start gap-3 rounded-lg border border-border p-3 hover:bg-muted/50"
42
- >
43
- <span className="mt-1.5">
44
- <StatusDot status={entry.status} />
45
- </span>
46
- <span className="min-w-0">
47
- <span className="block font-medium text-sm">{entry.name}</span>
48
- <span className="block text-xs text-muted-foreground mt-0.5 line-clamp-2">
49
- {entry.summary}
50
- </span>
51
- </span>
52
- </Link>
53
- </li>
54
- ))}
55
- </ul>
56
- </section>
57
- ))}
58
- </div>
59
- </div>
60
- )
61
- }
62
-
63
- function Tally({
64
- label,
65
- value,
66
- tone,
67
- }: {
68
- label: string
69
- value: number
70
- tone: "done" | "built" | "missing"
71
- }) {
72
- return (
73
- <div className="rounded-lg border border-border p-4">
74
- <div className="flex items-center gap-2">
75
- <StatusDot status={tone} />
76
- <span className="text-xs uppercase tracking-wide text-muted-foreground">{label}</span>
77
- </div>
78
- <p className="text-2xl font-semibold mt-1">{value}</p>
79
- </div>
80
- )
81
- }