create-stitchkit 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 +46 -0
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/dist/cli.js +230 -0
- package/package.json +50 -0
- package/template/README.md +69 -0
- package/template/_env +11 -0
- package/template/_env.example +12 -0
- package/template/_gitignore +10 -0
- package/template/biome.json +43 -0
- package/template/bun.lock +1372 -0
- package/template/e2e/starter.spec.ts +237 -0
- package/template/ecosystem.config.cjs +28 -0
- package/template/ecosystem.dev.config.cjs +29 -0
- package/template/package.json +55 -0
- package/template/packages/backend/package.json +31 -0
- package/template/packages/backend/src/cli.ts +12 -0
- package/template/packages/backend/src/domain/errors.ts +9 -0
- package/template/packages/backend/src/domain/repository/github-cache.test.ts +72 -0
- package/template/packages/backend/src/domain/repository/github-cache.ts +205 -0
- package/template/packages/backend/src/index.ts +56 -0
- package/template/packages/backend/src/lib/db.ts +7 -0
- package/template/packages/backend/src/surface.ts +13 -0
- package/template/packages/backend/src/tools.ts +21 -0
- package/template/packages/backend/src/transport/errors.ts +28 -0
- package/template/packages/backend/src/transport/repository-service.ts +18 -0
- package/template/packages/backend/tsconfig.json +8 -0
- package/template/packages/config/package.json +22 -0
- package/template/packages/config/src/server.ts +24 -0
- package/template/packages/config/tsconfig.json +5 -0
- package/template/packages/db/migrations/20260808000000_init/migration.sql +16 -0
- package/template/packages/db/migrations/20260808170000_repository_visibility/migration.sql +4 -0
- package/template/packages/db/migrations/migration_lock.toml +1 -0
- package/template/packages/db/package.json +29 -0
- package/template/packages/db/prisma.config.ts +8 -0
- package/template/packages/db/schema.prisma +31 -0
- package/template/packages/db/tsconfig.json +5 -0
- package/template/packages/frontend/messages/en.json +15 -0
- package/template/packages/frontend/messages/ru.json +15 -0
- package/template/packages/frontend/next.config.ts +18 -0
- package/template/packages/frontend/package.json +62 -0
- package/template/packages/frontend/postcss.config.mjs +1 -0
- package/template/packages/frontend/public/ASSETS.md +6 -0
- package/template/packages/frontend/public/favicon/mascot-stitch.png +0 -0
- package/template/packages/frontend/public/icons/biome.svg +1 -0
- package/template/packages/frontend/public/icons/bun.svg +19 -0
- package/template/packages/frontend/public/icons/next.svg +1 -0
- package/template/packages/frontend/public/icons/postgresql.svg +1 -0
- package/template/packages/frontend/public/icons/prisma.svg +6 -0
- package/template/packages/frontend/public/icons/react-query.svg +9 -0
- package/template/packages/frontend/public/icons/react.svg +1 -0
- package/template/packages/frontend/public/icons/tailwind.svg +12 -0
- package/template/packages/frontend/public/icons/tsconfig.svg +1 -0
- package/template/packages/frontend/public/icons/typescript.svg +7 -0
- package/template/packages/frontend/public/icons/zod.svg +1 -0
- package/template/packages/frontend/public/mascot-stitch.png +0 -0
- package/template/packages/frontend/public/theme-dark.svg +8 -0
- package/template/packages/frontend/public/theme-light.svg +8 -0
- package/template/packages/frontend/src/app/[locale]/error.tsx +19 -0
- package/template/packages/frontend/src/app/[locale]/layout.tsx +59 -0
- package/template/packages/frontend/src/app/[locale]/loading.tsx +14 -0
- package/template/packages/frontend/src/app/[locale]/page.tsx +44 -0
- package/template/packages/frontend/src/app/[locale]/starter-page.tsx +151 -0
- package/template/packages/frontend/src/app/[locale]/ui/[story]/page.tsx +40 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/account-showcase.tsx +112 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/admin-showcase.tsx +101 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/catalogue-navigation.tsx +56 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/catalogue-shell.tsx +51 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/code-foundation.tsx +35 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/components-story.tsx +54 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/data-story.tsx +126 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/image-preloader.tsx +8 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/index.ts +3 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/landing-sections.tsx +269 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/landing-showcase.tsx +159 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/motion-story.tsx +78 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/overlays-story.tsx +134 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/payment-showcase.tsx +82 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/presentation-story.tsx +68 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/primitives-story.tsx +267 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/story-page.tsx +14 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/theme-story-client.tsx +208 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/theme-story.tsx +13 -0
- package/template/packages/frontend/src/app/[locale]/ui/layout.tsx +6 -0
- package/template/packages/frontend/src/app/[locale]/ui/page.tsx +10 -0
- package/template/packages/frontend/src/app/api/og/[locale]/[page]/route.tsx +53 -0
- package/template/packages/frontend/src/app/globals.css +210 -0
- package/template/packages/frontend/src/app/robots.ts +9 -0
- package/template/packages/frontend/src/app/sitemap.ts +22 -0
- package/template/packages/frontend/src/components/brand-mark.tsx +22 -0
- package/template/packages/frontend/src/components/repository-summary.tsx +107 -0
- package/template/packages/frontend/src/components/system-controls.tsx +92 -0
- package/template/packages/frontend/src/components/ui/adaptive-modal.tsx +382 -0
- package/template/packages/frontend/src/components/ui/animated-counter.tsx +80 -0
- package/template/packages/frontend/src/components/ui/avatar.tsx +46 -0
- package/template/packages/frontend/src/components/ui/badge.tsx +94 -0
- package/template/packages/frontend/src/components/ui/button-variants.ts +24 -0
- package/template/packages/frontend/src/components/ui/button.tsx +81 -0
- package/template/packages/frontend/src/components/ui/card-deck.tsx +73 -0
- package/template/packages/frontend/src/components/ui/card.tsx +81 -0
- package/template/packages/frontend/src/components/ui/charts/area-chart.tsx +167 -0
- package/template/packages/frontend/src/components/ui/charts/index.ts +7 -0
- package/template/packages/frontend/src/components/ui/checkbox.tsx +133 -0
- package/template/packages/frontend/src/components/ui/confirmation-modal.tsx +129 -0
- package/template/packages/frontend/src/components/ui/copyable-text.tsx +85 -0
- package/template/packages/frontend/src/components/ui/drawer.tsx +136 -0
- package/template/packages/frontend/src/components/ui/dropdown-menu.tsx +185 -0
- package/template/packages/frontend/src/components/ui/field.tsx +28 -0
- package/template/packages/frontend/src/components/ui/hand-drawn-icons.tsx +41 -0
- package/template/packages/frontend/src/components/ui/index.ts +36 -0
- package/template/packages/frontend/src/components/ui/input.tsx +24 -0
- package/template/packages/frontend/src/components/ui/label.tsx +18 -0
- package/template/packages/frontend/src/components/ui/otp-input.tsx +140 -0
- package/template/packages/frontend/src/components/ui/select.tsx +240 -0
- package/template/packages/frontend/src/components/ui/sidebar.tsx +343 -0
- package/template/packages/frontend/src/components/ui/skeleton.tsx +8 -0
- package/template/packages/frontend/src/components/ui/slider.tsx +31 -0
- package/template/packages/frontend/src/components/ui/slot-drum.tsx +50 -0
- package/template/packages/frontend/src/components/ui/spinner.tsx +30 -0
- package/template/packages/frontend/src/components/ui/switch.tsx +31 -0
- package/template/packages/frontend/src/components/ui/table.tsx +106 -0
- package/template/packages/frontend/src/components/ui/tabs.tsx +196 -0
- package/template/packages/frontend/src/components/ui/textarea.tsx +21 -0
- package/template/packages/frontend/src/components/ui/toaster.tsx +72 -0
- package/template/packages/frontend/src/components/ui/tooltip.tsx +31 -0
- package/template/packages/frontend/src/env.ts +21 -0
- package/template/packages/frontend/src/features/data-table/components/column-helpers.tsx +136 -0
- package/template/packages/frontend/src/features/data-table/components/data-table.tsx +303 -0
- package/template/packages/frontend/src/features/data-table/components/inline-stat.tsx +113 -0
- package/template/packages/frontend/src/features/data-table/components/sortable-header.tsx +33 -0
- package/template/packages/frontend/src/features/data-table/components/table-error.tsx +24 -0
- package/template/packages/frontend/src/features/data-table/components/table-filters.tsx +82 -0
- package/template/packages/frontend/src/features/data-table/hooks/useTableState.ts +111 -0
- package/template/packages/frontend/src/features/data-table/index.ts +26 -0
- package/template/packages/frontend/src/features/data-table/table-features.ts +26 -0
- package/template/packages/frontend/src/hooks/index.ts +3 -0
- package/template/packages/frontend/src/hooks/useInfiniteScroll.ts +67 -0
- package/template/packages/frontend/src/hooks/useIsTouchDevice.ts +25 -0
- package/template/packages/frontend/src/hooks/useMediaQuery.ts +30 -0
- package/template/packages/frontend/src/i18n/locales.ts +6 -0
- package/template/packages/frontend/src/i18n/navigation.ts +4 -0
- package/template/packages/frontend/src/i18n/request.ts +9 -0
- package/template/packages/frontend/src/i18n/routing.ts +8 -0
- package/template/packages/frontend/src/lib/api/client.ts +20 -0
- package/template/packages/frontend/src/lib/api/queries.ts +11 -0
- package/template/packages/frontend/src/lib/query-client.ts +20 -0
- package/template/packages/frontend/src/lib/realtime/repository.ts +21 -0
- package/template/packages/frontend/src/lib/seo/metadata.ts +50 -0
- package/template/packages/frontend/src/lib/seo/pages.test.ts +25 -0
- package/template/packages/frontend/src/lib/seo/pages.ts +107 -0
- package/template/packages/frontend/src/lib/utils/cn.ts +6 -0
- package/template/packages/frontend/src/lib/utils/format.ts +25 -0
- package/template/packages/frontend/src/lib/utils/index.ts +2 -0
- package/template/packages/frontend/src/providers/index.tsx +18 -0
- package/template/packages/frontend/src/providers/react-query.tsx +9 -0
- package/template/packages/frontend/src/providers/realtime.tsx +16 -0
- package/template/packages/frontend/src/proxy.ts +6 -0
- package/template/packages/frontend/src/theme/config.ts +20 -0
- package/template/packages/frontend/src/theme/transition.ts +54 -0
- package/template/packages/frontend/tsconfig.json +13 -0
- package/template/packages/shared/package.json +23 -0
- package/template/packages/shared/src/contracts/repository.ts +28 -0
- package/template/packages/shared/src/events/repository.ts +9 -0
- package/template/packages/shared/src/index.ts +3 -0
- package/template/packages/shared/src/schemas/repository.test.ts +36 -0
- package/template/packages/shared/src/schemas/repository.ts +28 -0
- package/template/packages/shared/tsconfig.json +5 -0
- package/template/playwright.config.ts +15 -0
- package/template/scripts/check-authored.ts +81 -0
- package/template/scripts/dev.ts +23 -0
- package/template/scripts/local-env.ts +12 -0
- package/template/scripts/runtime-smoke.ts +67 -0
- package/template/scripts/tooling-env.ts +15 -0
- package/template/tsconfig.json +13 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
IconArrowUpRight,
|
|
5
|
+
IconBooks,
|
|
6
|
+
IconBox,
|
|
7
|
+
IconBrandReact,
|
|
8
|
+
IconCode,
|
|
9
|
+
IconDatabase,
|
|
10
|
+
IconDeviceDesktop,
|
|
11
|
+
IconRoute,
|
|
12
|
+
IconServer,
|
|
13
|
+
IconShieldCheck,
|
|
14
|
+
IconUsers,
|
|
15
|
+
} from '@tabler/icons-react';
|
|
16
|
+
import { ThemedImage } from '@wrksz/themes/client/themed-image';
|
|
17
|
+
import { useState } from 'react';
|
|
18
|
+
import {
|
|
19
|
+
AdaptiveModal,
|
|
20
|
+
Badge,
|
|
21
|
+
Button,
|
|
22
|
+
Card,
|
|
23
|
+
CardContent,
|
|
24
|
+
CardHeader,
|
|
25
|
+
CardTitle,
|
|
26
|
+
Tabs,
|
|
27
|
+
TabsContent,
|
|
28
|
+
TabsList,
|
|
29
|
+
TabsTrigger,
|
|
30
|
+
} from '@/components/ui';
|
|
31
|
+
|
|
32
|
+
const problems = [
|
|
33
|
+
['Schema drift', 'One named Zod schema drives server validation and every client.'],
|
|
34
|
+
['Transport duplication', 'HTTP, tools and CLI share the same implementation.'],
|
|
35
|
+
['Frontend plumbing', 'Queries, realtime cache updates and errors arrive composed.'],
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export function ProblemsSection() {
|
|
39
|
+
return (
|
|
40
|
+
<section className='space-y-6 py-14'>
|
|
41
|
+
<div className='max-w-2xl'>
|
|
42
|
+
<p className='text-sm text-muted-foreground'>The repetitive work</p>
|
|
43
|
+
<h2 className='text-3xl font-medium'>Keep infrastructure from becoming the product</h2>
|
|
44
|
+
</div>
|
|
45
|
+
<div className='grid gap-3 md:grid-cols-3'>
|
|
46
|
+
{problems.map(([title, copy], index) => (
|
|
47
|
+
<Card key={title}>
|
|
48
|
+
<CardHeader>
|
|
49
|
+
<Badge className='w-fit'>{String(index + 1).padStart(2, '0')}</Badge>
|
|
50
|
+
<CardTitle className='pt-4'>{title}</CardTitle>
|
|
51
|
+
</CardHeader>
|
|
52
|
+
<CardContent className='text-sm text-muted-foreground'>{copy}</CardContent>
|
|
53
|
+
</Card>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function BuilderSection() {
|
|
61
|
+
return (
|
|
62
|
+
<section className='grid gap-8 border-y border-border py-14 md:grid-cols-[0.8fr_1.2fr] md:items-center'>
|
|
63
|
+
<div className='grid aspect-square max-w-sm place-items-center rounded-3xl bg-muted'>
|
|
64
|
+
<IconCode className='size-20 text-muted-foreground' stroke={1.25} />
|
|
65
|
+
</div>
|
|
66
|
+
<div className='space-y-4'>
|
|
67
|
+
<Badge variant='secondary'>Built for product teams</Badge>
|
|
68
|
+
<h2 className='text-3xl font-medium'>A neutral foundation, not a sample business</h2>
|
|
69
|
+
<p className='text-muted-foreground'>
|
|
70
|
+
Replace the example domain while retaining contracts, runtime boundaries, operational
|
|
71
|
+
tooling and the complete component vocabulary.
|
|
72
|
+
</p>
|
|
73
|
+
<div className='flex items-center gap-2 text-sm'>
|
|
74
|
+
<IconUsers /> Solo developers and teams
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</section>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const foundations = [
|
|
82
|
+
{
|
|
83
|
+
icon: IconShieldCheck,
|
|
84
|
+
title: 'Validated boundaries',
|
|
85
|
+
copy: 'Fail-first environment and data parsing.',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
icon: IconDatabase,
|
|
89
|
+
title: 'Persistent by default',
|
|
90
|
+
copy: 'Prisma and PostgreSQL with a real migration.',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
icon: IconServer,
|
|
94
|
+
title: 'Independent backend',
|
|
95
|
+
copy: 'Long-running work, sockets and MCP stay outside Next.',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
icon: IconRoute,
|
|
99
|
+
title: 'Typed navigation',
|
|
100
|
+
copy: 'Contracts build calls and URLs without repeated strings.',
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
export function WhySection() {
|
|
105
|
+
return (
|
|
106
|
+
<section className='space-y-6 py-14'>
|
|
107
|
+
<div>
|
|
108
|
+
<p className='text-sm text-muted-foreground'>Why this topology</p>
|
|
109
|
+
<h2 className='text-3xl font-medium'>Clear ownership at every boundary</h2>
|
|
110
|
+
</div>
|
|
111
|
+
<div className='grid gap-3 sm:grid-cols-2'>
|
|
112
|
+
{foundations.map((item) => (
|
|
113
|
+
<Card key={item.title}>
|
|
114
|
+
<CardContent className='flex gap-4 pt-4'>
|
|
115
|
+
<div className='grid size-10 shrink-0 place-items-center rounded-xl bg-muted'>
|
|
116
|
+
<item.icon />
|
|
117
|
+
</div>
|
|
118
|
+
<div>
|
|
119
|
+
<h3 className='font-medium'>{item.title}</h3>
|
|
120
|
+
<p className='mt-1 text-sm text-muted-foreground'>{item.copy}</p>
|
|
121
|
+
</div>
|
|
122
|
+
</CardContent>
|
|
123
|
+
</Card>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
</section>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function AudienceSection() {
|
|
131
|
+
return (
|
|
132
|
+
<section className='grid gap-3 py-14 md:grid-cols-3'>
|
|
133
|
+
{[
|
|
134
|
+
['Start', 'Generate a complete system and learn it through one bounded domain.'],
|
|
135
|
+
['Adapt', 'Keep the boundaries and replace fixtures with your own product model.'],
|
|
136
|
+
['Scale', 'Split deployments without rewriting the contract or transport surface.'],
|
|
137
|
+
].map(([title, copy]) => (
|
|
138
|
+
<div className='rounded-2xl border border-border p-5' key={title}>
|
|
139
|
+
<h3 className='text-xl font-medium'>{title}</h3>
|
|
140
|
+
<p className='mt-2 text-sm text-muted-foreground'>{copy}</p>
|
|
141
|
+
</div>
|
|
142
|
+
))}
|
|
143
|
+
</section>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function LearningSection() {
|
|
148
|
+
return (
|
|
149
|
+
<section className='grid gap-8 py-14 md:grid-cols-[0.8fr_1.2fr]'>
|
|
150
|
+
<div>
|
|
151
|
+
<IconBooks className='mb-4 size-10' />
|
|
152
|
+
<h2 className='text-3xl font-medium'>Learn by tracing one operation</h2>
|
|
153
|
+
<p className='mt-3 text-muted-foreground'>
|
|
154
|
+
Follow a project mutation from schema to contract, service, HTTP client, query cache,
|
|
155
|
+
Socket.IO event, MCP tool and CLI command.
|
|
156
|
+
</p>
|
|
157
|
+
</div>
|
|
158
|
+
<ol className='space-y-3'>
|
|
159
|
+
{[
|
|
160
|
+
'Define a named schema',
|
|
161
|
+
'Reference it from a thin contract',
|
|
162
|
+
'Implement the domain once',
|
|
163
|
+
'Consume the generated surfaces',
|
|
164
|
+
].map((step, index) => (
|
|
165
|
+
<li
|
|
166
|
+
className='flex items-center gap-4 rounded-xl border border-border p-4'
|
|
167
|
+
key={step}
|
|
168
|
+
>
|
|
169
|
+
<Badge>{index + 1}</Badge>
|
|
170
|
+
<span>{step}</span>
|
|
171
|
+
</li>
|
|
172
|
+
))}
|
|
173
|
+
</ol>
|
|
174
|
+
</section>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function TemplatePreviewSection() {
|
|
179
|
+
return (
|
|
180
|
+
<section className='space-y-6 py-14'>
|
|
181
|
+
<div className='flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between'>
|
|
182
|
+
<div>
|
|
183
|
+
<p className='text-sm text-muted-foreground'>Theme-aware media</p>
|
|
184
|
+
<h2 className='text-3xl font-medium'>A project shell ready to become yours</h2>
|
|
185
|
+
</div>
|
|
186
|
+
<Badge variant='success'>Light + dark</Badge>
|
|
187
|
+
</div>
|
|
188
|
+
<ThemedImage
|
|
189
|
+
src={{ light: '/theme-light.svg', dark: '/theme-dark.svg' }}
|
|
190
|
+
alt='Theme-aware generic application preview'
|
|
191
|
+
width={1280}
|
|
192
|
+
height={720}
|
|
193
|
+
className='w-full rounded-2xl border border-border'
|
|
194
|
+
/>
|
|
195
|
+
</section>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function FrontendSection() {
|
|
200
|
+
return (
|
|
201
|
+
<section className='space-y-6 py-14'>
|
|
202
|
+
<h2 className='text-3xl font-medium'>Frontend foundations included</h2>
|
|
203
|
+
<div className='grid gap-3 sm:grid-cols-3'>
|
|
204
|
+
{[
|
|
205
|
+
{
|
|
206
|
+
icon: IconBrandReact,
|
|
207
|
+
title: 'React 19',
|
|
208
|
+
copy: 'Server-first composition and isolated client islands.',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
icon: IconDeviceDesktop,
|
|
212
|
+
title: 'Responsive UI',
|
|
213
|
+
copy: 'Touch, keyboard and desktop interaction patterns.',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
icon: IconBox,
|
|
217
|
+
title: 'Source components',
|
|
218
|
+
copy: 'Own and adapt every primitive without a black box.',
|
|
219
|
+
},
|
|
220
|
+
].map((item) => (
|
|
221
|
+
<Card key={item.title}>
|
|
222
|
+
<CardContent className='pt-4'>
|
|
223
|
+
<item.icon className='mb-5 size-8' />
|
|
224
|
+
<h3 className='font-medium'>{item.title}</h3>
|
|
225
|
+
<p className='mt-2 text-sm text-muted-foreground'>{item.copy}</p>
|
|
226
|
+
</CardContent>
|
|
227
|
+
</Card>
|
|
228
|
+
))}
|
|
229
|
+
</div>
|
|
230
|
+
</section>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function DemoModal() {
|
|
235
|
+
const [open, setOpen] = useState(false);
|
|
236
|
+
return (
|
|
237
|
+
<>
|
|
238
|
+
<Button variant='outline' onClick={() => setOpen(true)}>
|
|
239
|
+
Explore starter <IconArrowUpRight />
|
|
240
|
+
</Button>
|
|
241
|
+
<AdaptiveModal
|
|
242
|
+
isOpen={open}
|
|
243
|
+
onClose={() => setOpen(false)}
|
|
244
|
+
title='Choose a surface'
|
|
245
|
+
desktopWidth='2xl'
|
|
246
|
+
mobileVariant='sheet'
|
|
247
|
+
>
|
|
248
|
+
<div className='p-4 pt-1'>
|
|
249
|
+
<Tabs defaultValue='http'>
|
|
250
|
+
<TabsList>
|
|
251
|
+
<TabsTrigger value='http'>HTTP</TabsTrigger>
|
|
252
|
+
<TabsTrigger value='tools'>Tools</TabsTrigger>
|
|
253
|
+
<TabsTrigger value='realtime'>Realtime</TabsTrigger>
|
|
254
|
+
</TabsList>
|
|
255
|
+
<TabsContent className='pt-5 text-sm text-muted-foreground' value='http'>
|
|
256
|
+
Typed browser and server clients consume the same contract.
|
|
257
|
+
</TabsContent>
|
|
258
|
+
<TabsContent className='pt-5 text-sm text-muted-foreground' value='tools'>
|
|
259
|
+
MCP, Agent and CLI surfaces reuse the service runner and validation.
|
|
260
|
+
</TabsContent>
|
|
261
|
+
<TabsContent className='pt-5 text-sm text-muted-foreground' value='realtime'>
|
|
262
|
+
Socket.IO events update the TanStack Query cache through Stitchkit.
|
|
263
|
+
</TabsContent>
|
|
264
|
+
</Tabs>
|
|
265
|
+
</div>
|
|
266
|
+
</AdaptiveModal>
|
|
267
|
+
</>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { IconArrowRight, IconBraces, IconDatabase, IconWorld } from '@tabler/icons-react';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
import { BrandMark } from '@/components/brand-mark';
|
|
6
|
+
import {
|
|
7
|
+
Badge,
|
|
8
|
+
Button,
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
CardDeck,
|
|
12
|
+
CardHeader,
|
|
13
|
+
CardTitle,
|
|
14
|
+
} from '@/components/ui';
|
|
15
|
+
|
|
16
|
+
const features = [
|
|
17
|
+
{
|
|
18
|
+
icon: IconBraces,
|
|
19
|
+
title: 'Contract first',
|
|
20
|
+
copy: 'Schemas, clients and transports stay aligned.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
icon: IconDatabase,
|
|
24
|
+
title: 'Production data',
|
|
25
|
+
copy: 'Prisma and PostgreSQL are ready from day one.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
icon: IconWorld,
|
|
29
|
+
title: 'Every surface',
|
|
30
|
+
copy: 'HTTP, realtime, MCP, agents and CLI share one service.',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export function LandingHeader({ actions }: { actions?: ReactNode }) {
|
|
35
|
+
return (
|
|
36
|
+
<header className='flex items-center justify-between border-b border-border py-4'>
|
|
37
|
+
<div className='flex items-center gap-2 font-medium'>
|
|
38
|
+
<BrandMark priority />
|
|
39
|
+
Stitchkit Starter
|
|
40
|
+
</div>
|
|
41
|
+
<div className='flex items-center gap-2'>{actions}</div>
|
|
42
|
+
</header>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function HeroSection() {
|
|
47
|
+
return (
|
|
48
|
+
<section className='grid gap-8 py-20 lg:grid-cols-[1.2fr_0.8fr] lg:items-end'>
|
|
49
|
+
<div className='space-y-6'>
|
|
50
|
+
<Badge variant='primary'>Production starter</Badge>
|
|
51
|
+
<h1 className='max-w-3xl font-display text-5xl font-medium tracking-tight sm:text-7xl'>
|
|
52
|
+
Build the product, not its plumbing.
|
|
53
|
+
</h1>
|
|
54
|
+
<p className='max-w-2xl text-lg text-muted-foreground'>
|
|
55
|
+
A precise fullstack base with a complete UI system and one contract across every
|
|
56
|
+
interface.
|
|
57
|
+
</p>
|
|
58
|
+
<div className='flex flex-wrap gap-3'>
|
|
59
|
+
<Button variant='primary'>
|
|
60
|
+
Start building <IconArrowRight />
|
|
61
|
+
</Button>
|
|
62
|
+
<Button variant='outline'>Read the architecture</Button>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<CardDeck
|
|
66
|
+
items={features}
|
|
67
|
+
overlap={-28}
|
|
68
|
+
rotationStep={4}
|
|
69
|
+
keyExtractor={(feature) => feature.title}
|
|
70
|
+
renderCard={(feature) => (
|
|
71
|
+
<Card className='w-48 bg-card/95 backdrop-blur'>
|
|
72
|
+
<CardHeader>
|
|
73
|
+
<feature.icon />
|
|
74
|
+
<CardTitle>{feature.title}</CardTitle>
|
|
75
|
+
</CardHeader>
|
|
76
|
+
<CardContent className='text-sm text-muted-foreground'>{feature.copy}</CardContent>
|
|
77
|
+
</Card>
|
|
78
|
+
)}
|
|
79
|
+
/>
|
|
80
|
+
</section>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function FeatureGrid() {
|
|
85
|
+
return (
|
|
86
|
+
<section className='grid gap-3 py-10 md:grid-cols-3'>
|
|
87
|
+
{features.map((feature) => (
|
|
88
|
+
<Card key={feature.title}>
|
|
89
|
+
<CardHeader>
|
|
90
|
+
<div className='mb-5 grid size-10 place-items-center rounded-xl bg-muted'>
|
|
91
|
+
<feature.icon />
|
|
92
|
+
</div>
|
|
93
|
+
<CardTitle>{feature.title}</CardTitle>
|
|
94
|
+
</CardHeader>
|
|
95
|
+
<CardContent className='text-sm text-muted-foreground'>{feature.copy}</CardContent>
|
|
96
|
+
</Card>
|
|
97
|
+
))}
|
|
98
|
+
</section>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function PortfolioSection() {
|
|
103
|
+
return (
|
|
104
|
+
<section className='space-y-6 py-16'>
|
|
105
|
+
<div>
|
|
106
|
+
<p className='text-sm text-muted-foreground'>Composable sections</p>
|
|
107
|
+
<h2 className='text-3xl font-medium'>From landing page to application shell</h2>
|
|
108
|
+
</div>
|
|
109
|
+
<div className='grid gap-4 md:grid-cols-2'>
|
|
110
|
+
{[
|
|
111
|
+
'Editorial landing',
|
|
112
|
+
'Operational dashboard',
|
|
113
|
+
'Account workspace',
|
|
114
|
+
'Data-heavy admin',
|
|
115
|
+
].map((title, index) => (
|
|
116
|
+
<div
|
|
117
|
+
className='aspect-[16/9] rounded-2xl border border-border bg-gradient-to-br from-muted to-background p-5'
|
|
118
|
+
key={title}
|
|
119
|
+
>
|
|
120
|
+
<Badge>{String(index + 1).padStart(2, '0')}</Badge>
|
|
121
|
+
<p className='mt-auto pt-20 text-lg font-medium'>{title}</p>
|
|
122
|
+
</div>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
</section>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function PricingSection() {
|
|
130
|
+
return (
|
|
131
|
+
<section className='grid gap-4 py-16 md:grid-cols-3'>
|
|
132
|
+
{['Foundation', 'Product', 'Scale'].map((name, index) => (
|
|
133
|
+
<Card className={index === 1 ? 'border-primary' : undefined} key={name}>
|
|
134
|
+
<CardHeader>
|
|
135
|
+
<Badge variant={index === 1 ? 'primary' : 'default'}>
|
|
136
|
+
{index === 1 ? 'Recommended' : 'Flexible'}
|
|
137
|
+
</Badge>
|
|
138
|
+
<CardTitle className='pt-4 text-2xl'>{name}</CardTitle>
|
|
139
|
+
</CardHeader>
|
|
140
|
+
<CardContent className='space-y-5 text-sm text-muted-foreground'>
|
|
141
|
+
<p>A reusable layout demonstrating pricing, comparison and conversion patterns.</p>
|
|
142
|
+
<Button className='w-full' variant={index === 1 ? 'primary' : 'outline'}>
|
|
143
|
+
Choose {name}
|
|
144
|
+
</Button>
|
|
145
|
+
</CardContent>
|
|
146
|
+
</Card>
|
|
147
|
+
))}
|
|
148
|
+
</section>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function LandingFooter() {
|
|
153
|
+
return (
|
|
154
|
+
<footer className='flex flex-col gap-3 border-t border-border py-8 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between'>
|
|
155
|
+
<span>Stitchkit Starter · Built with Stitchkit</span>
|
|
156
|
+
<span>Contracts · UI · Realtime · Tools</span>
|
|
157
|
+
</footer>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
AnimatedCounter,
|
|
6
|
+
Button,
|
|
7
|
+
Card,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardDeck,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
SlotDrum,
|
|
13
|
+
} from '@/components/ui';
|
|
14
|
+
import { StorySection } from './catalogue-shell';
|
|
15
|
+
|
|
16
|
+
const deckItems = [
|
|
17
|
+
{ title: 'Contracts', copy: 'One source of truth' },
|
|
18
|
+
{ title: 'Transport', copy: 'HTTP, MCP and agents' },
|
|
19
|
+
{ title: 'Interface', copy: 'Typed data to polished UI' },
|
|
20
|
+
];
|
|
21
|
+
const stages = ['Idea', 'Building', 'Shipping'];
|
|
22
|
+
|
|
23
|
+
export function MotionStory() {
|
|
24
|
+
const [count, setCount] = useState(24);
|
|
25
|
+
const [stage, setStage] = useState(0);
|
|
26
|
+
return (
|
|
27
|
+
<div>
|
|
28
|
+
<StorySection title='Meaningful motion'>
|
|
29
|
+
<div className='grid gap-4 md:grid-cols-2'>
|
|
30
|
+
<Card>
|
|
31
|
+
<CardHeader>
|
|
32
|
+
<CardTitle>Delivery count</CardTitle>
|
|
33
|
+
</CardHeader>
|
|
34
|
+
<CardContent className='space-y-4'>
|
|
35
|
+
<AnimatedCounter className='text-4xl font-medium' value={count} suffix='%' />
|
|
36
|
+
<Button onClick={() => setCount((value) => (value + 13) % 101)}>Advance</Button>
|
|
37
|
+
</CardContent>
|
|
38
|
+
</Card>
|
|
39
|
+
<Card>
|
|
40
|
+
<CardHeader>
|
|
41
|
+
<CardTitle>Current stage</CardTitle>
|
|
42
|
+
</CardHeader>
|
|
43
|
+
<CardContent className='space-y-4'>
|
|
44
|
+
<SlotDrum
|
|
45
|
+
className='text-2xl font-medium'
|
|
46
|
+
items={stages}
|
|
47
|
+
activeIndex={stage}
|
|
48
|
+
getItemKey={(item) => item}
|
|
49
|
+
renderItem={(item) => item}
|
|
50
|
+
/>
|
|
51
|
+
<Button onClick={() => setStage((value) => (value + 1) % stages.length)}>
|
|
52
|
+
Next stage
|
|
53
|
+
</Button>
|
|
54
|
+
</CardContent>
|
|
55
|
+
</Card>
|
|
56
|
+
</div>
|
|
57
|
+
</StorySection>
|
|
58
|
+
<StorySection title='Composed card deck'>
|
|
59
|
+
<div className='py-8'>
|
|
60
|
+
<CardDeck
|
|
61
|
+
items={deckItems}
|
|
62
|
+
overlap={-24}
|
|
63
|
+
rotationStep={4}
|
|
64
|
+
keyExtractor={(item) => item.title}
|
|
65
|
+
renderCard={(item) => (
|
|
66
|
+
<Card className='w-52 bg-card'>
|
|
67
|
+
<CardHeader>
|
|
68
|
+
<CardTitle>{item.title}</CardTitle>
|
|
69
|
+
</CardHeader>
|
|
70
|
+
<CardContent className='text-sm text-muted-foreground'>{item.copy}</CardContent>
|
|
71
|
+
</Card>
|
|
72
|
+
)}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</StorySection>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { IconDots, IconSettings, IconTrash } from '@tabler/icons-react';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import {
|
|
6
|
+
AdaptiveModal,
|
|
7
|
+
Button,
|
|
8
|
+
ConfirmationModal,
|
|
9
|
+
Drawer,
|
|
10
|
+
DrawerClose,
|
|
11
|
+
DrawerContent,
|
|
12
|
+
DrawerDescription,
|
|
13
|
+
DrawerFooter,
|
|
14
|
+
DrawerHeader,
|
|
15
|
+
DrawerTitle,
|
|
16
|
+
DrawerTrigger,
|
|
17
|
+
DropdownMenu,
|
|
18
|
+
DropdownMenuCheckboxItem,
|
|
19
|
+
DropdownMenuContent,
|
|
20
|
+
DropdownMenuItem,
|
|
21
|
+
DropdownMenuLabel,
|
|
22
|
+
DropdownMenuSeparator,
|
|
23
|
+
DropdownMenuShortcut,
|
|
24
|
+
DropdownMenuTrigger,
|
|
25
|
+
Tooltip,
|
|
26
|
+
TooltipContent,
|
|
27
|
+
TooltipProvider,
|
|
28
|
+
TooltipTrigger,
|
|
29
|
+
toast,
|
|
30
|
+
} from '@/components/ui';
|
|
31
|
+
import { StorySection } from './catalogue-shell';
|
|
32
|
+
|
|
33
|
+
export function OverlaysStory() {
|
|
34
|
+
const [modalOpen, setModalOpen] = useState(false);
|
|
35
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
36
|
+
const [notifications, setNotifications] = useState(true);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div>
|
|
40
|
+
<StorySection title='Adaptive surfaces'>
|
|
41
|
+
<div className='flex flex-wrap gap-3'>
|
|
42
|
+
<Button onClick={() => setModalOpen(true)}>Adaptive modal</Button>
|
|
43
|
+
<Button variant='destructive' onClick={() => setConfirmOpen(true)}>
|
|
44
|
+
Confirmation
|
|
45
|
+
</Button>
|
|
46
|
+
<Drawer>
|
|
47
|
+
<DrawerTrigger asChild>
|
|
48
|
+
<Button variant='outline'>Drawer</Button>
|
|
49
|
+
</DrawerTrigger>
|
|
50
|
+
<DrawerContent>
|
|
51
|
+
<DrawerHeader>
|
|
52
|
+
<DrawerTitle>Project settings</DrawerTitle>
|
|
53
|
+
<DrawerDescription>A composable mobile-first surface.</DrawerDescription>
|
|
54
|
+
</DrawerHeader>
|
|
55
|
+
<div className='px-4 text-sm text-muted-foreground'>
|
|
56
|
+
Use drawers for contextual actions without replacing the current page.
|
|
57
|
+
</div>
|
|
58
|
+
<DrawerFooter>
|
|
59
|
+
<DrawerClose asChild>
|
|
60
|
+
<Button>Done</Button>
|
|
61
|
+
</DrawerClose>
|
|
62
|
+
</DrawerFooter>
|
|
63
|
+
</DrawerContent>
|
|
64
|
+
</Drawer>
|
|
65
|
+
</div>
|
|
66
|
+
<AdaptiveModal
|
|
67
|
+
isOpen={modalOpen}
|
|
68
|
+
onClose={() => setModalOpen(false)}
|
|
69
|
+
title='Adaptive modal'
|
|
70
|
+
mobileVariant='sheet'
|
|
71
|
+
footer={
|
|
72
|
+
<Button className='w-full' onClick={() => setModalOpen(false)}>
|
|
73
|
+
Save changes
|
|
74
|
+
</Button>
|
|
75
|
+
}
|
|
76
|
+
>
|
|
77
|
+
<div className='px-4 pb-6 text-sm text-muted-foreground'>
|
|
78
|
+
Centered on desktop, touch-friendly sheet on compact screens.
|
|
79
|
+
</div>
|
|
80
|
+
</AdaptiveModal>
|
|
81
|
+
<ConfirmationModal
|
|
82
|
+
isOpen={confirmOpen}
|
|
83
|
+
onClose={() => setConfirmOpen(false)}
|
|
84
|
+
onConfirm={() => {
|
|
85
|
+
setConfirmOpen(false);
|
|
86
|
+
toast.success('Action confirmed');
|
|
87
|
+
}}
|
|
88
|
+
title='Archive project?'
|
|
89
|
+
description='The project remains available in your history.'
|
|
90
|
+
confirmText='Archive'
|
|
91
|
+
variant='destructive'
|
|
92
|
+
/>
|
|
93
|
+
</StorySection>
|
|
94
|
+
|
|
95
|
+
<StorySection title='Menus and contextual help'>
|
|
96
|
+
<div className='flex items-center gap-3'>
|
|
97
|
+
<DropdownMenu>
|
|
98
|
+
<DropdownMenuTrigger asChild>
|
|
99
|
+
<Button size='icon' variant='outline' aria-label='Open project menu'>
|
|
100
|
+
<IconDots />
|
|
101
|
+
</Button>
|
|
102
|
+
</DropdownMenuTrigger>
|
|
103
|
+
<DropdownMenuContent align='start'>
|
|
104
|
+
<DropdownMenuLabel>Project</DropdownMenuLabel>
|
|
105
|
+
<DropdownMenuItem>
|
|
106
|
+
<IconSettings /> Settings <DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
|
|
107
|
+
</DropdownMenuItem>
|
|
108
|
+
<DropdownMenuCheckboxItem
|
|
109
|
+
checked={notifications}
|
|
110
|
+
onCheckedChange={setNotifications}
|
|
111
|
+
>
|
|
112
|
+
Notifications
|
|
113
|
+
</DropdownMenuCheckboxItem>
|
|
114
|
+
<DropdownMenuSeparator />
|
|
115
|
+
<DropdownMenuItem className='text-destructive'>
|
|
116
|
+
<IconTrash /> Archive
|
|
117
|
+
</DropdownMenuItem>
|
|
118
|
+
</DropdownMenuContent>
|
|
119
|
+
</DropdownMenu>
|
|
120
|
+
<TooltipProvider>
|
|
121
|
+
<Tooltip>
|
|
122
|
+
<TooltipTrigger asChild>
|
|
123
|
+
<Button variant='ghost'>Why server cache?</Button>
|
|
124
|
+
</TooltipTrigger>
|
|
125
|
+
<TooltipContent>
|
|
126
|
+
It protects external rate limits and keeps rendering predictable.
|
|
127
|
+
</TooltipContent>
|
|
128
|
+
</Tooltip>
|
|
129
|
+
</TooltipProvider>
|
|
130
|
+
</div>
|
|
131
|
+
</StorySection>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|