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.
Files changed (174) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +21 -0
  3. package/README.md +35 -0
  4. package/dist/cli.js +230 -0
  5. package/package.json +50 -0
  6. package/template/README.md +69 -0
  7. package/template/_env +11 -0
  8. package/template/_env.example +12 -0
  9. package/template/_gitignore +10 -0
  10. package/template/biome.json +43 -0
  11. package/template/bun.lock +1372 -0
  12. package/template/e2e/starter.spec.ts +237 -0
  13. package/template/ecosystem.config.cjs +28 -0
  14. package/template/ecosystem.dev.config.cjs +29 -0
  15. package/template/package.json +55 -0
  16. package/template/packages/backend/package.json +31 -0
  17. package/template/packages/backend/src/cli.ts +12 -0
  18. package/template/packages/backend/src/domain/errors.ts +9 -0
  19. package/template/packages/backend/src/domain/repository/github-cache.test.ts +72 -0
  20. package/template/packages/backend/src/domain/repository/github-cache.ts +205 -0
  21. package/template/packages/backend/src/index.ts +56 -0
  22. package/template/packages/backend/src/lib/db.ts +7 -0
  23. package/template/packages/backend/src/surface.ts +13 -0
  24. package/template/packages/backend/src/tools.ts +21 -0
  25. package/template/packages/backend/src/transport/errors.ts +28 -0
  26. package/template/packages/backend/src/transport/repository-service.ts +18 -0
  27. package/template/packages/backend/tsconfig.json +8 -0
  28. package/template/packages/config/package.json +22 -0
  29. package/template/packages/config/src/server.ts +24 -0
  30. package/template/packages/config/tsconfig.json +5 -0
  31. package/template/packages/db/migrations/20260808000000_init/migration.sql +16 -0
  32. package/template/packages/db/migrations/20260808170000_repository_visibility/migration.sql +4 -0
  33. package/template/packages/db/migrations/migration_lock.toml +1 -0
  34. package/template/packages/db/package.json +29 -0
  35. package/template/packages/db/prisma.config.ts +8 -0
  36. package/template/packages/db/schema.prisma +31 -0
  37. package/template/packages/db/tsconfig.json +5 -0
  38. package/template/packages/frontend/messages/en.json +15 -0
  39. package/template/packages/frontend/messages/ru.json +15 -0
  40. package/template/packages/frontend/next.config.ts +18 -0
  41. package/template/packages/frontend/package.json +62 -0
  42. package/template/packages/frontend/postcss.config.mjs +1 -0
  43. package/template/packages/frontend/public/ASSETS.md +6 -0
  44. package/template/packages/frontend/public/favicon/mascot-stitch.png +0 -0
  45. package/template/packages/frontend/public/icons/biome.svg +1 -0
  46. package/template/packages/frontend/public/icons/bun.svg +19 -0
  47. package/template/packages/frontend/public/icons/next.svg +1 -0
  48. package/template/packages/frontend/public/icons/postgresql.svg +1 -0
  49. package/template/packages/frontend/public/icons/prisma.svg +6 -0
  50. package/template/packages/frontend/public/icons/react-query.svg +9 -0
  51. package/template/packages/frontend/public/icons/react.svg +1 -0
  52. package/template/packages/frontend/public/icons/tailwind.svg +12 -0
  53. package/template/packages/frontend/public/icons/tsconfig.svg +1 -0
  54. package/template/packages/frontend/public/icons/typescript.svg +7 -0
  55. package/template/packages/frontend/public/icons/zod.svg +1 -0
  56. package/template/packages/frontend/public/mascot-stitch.png +0 -0
  57. package/template/packages/frontend/public/theme-dark.svg +8 -0
  58. package/template/packages/frontend/public/theme-light.svg +8 -0
  59. package/template/packages/frontend/src/app/[locale]/error.tsx +19 -0
  60. package/template/packages/frontend/src/app/[locale]/layout.tsx +59 -0
  61. package/template/packages/frontend/src/app/[locale]/loading.tsx +14 -0
  62. package/template/packages/frontend/src/app/[locale]/page.tsx +44 -0
  63. package/template/packages/frontend/src/app/[locale]/starter-page.tsx +151 -0
  64. package/template/packages/frontend/src/app/[locale]/ui/[story]/page.tsx +40 -0
  65. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/account-showcase.tsx +112 -0
  66. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/admin-showcase.tsx +101 -0
  67. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/catalogue-navigation.tsx +56 -0
  68. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/catalogue-shell.tsx +51 -0
  69. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/code-foundation.tsx +35 -0
  70. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/components-story.tsx +54 -0
  71. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/data-story.tsx +126 -0
  72. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/image-preloader.tsx +8 -0
  73. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/index.ts +3 -0
  74. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/landing-sections.tsx +269 -0
  75. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/landing-showcase.tsx +159 -0
  76. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/motion-story.tsx +78 -0
  77. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/overlays-story.tsx +134 -0
  78. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/payment-showcase.tsx +82 -0
  79. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/presentation-story.tsx +68 -0
  80. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/primitives-story.tsx +267 -0
  81. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/story-page.tsx +14 -0
  82. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/theme-story-client.tsx +208 -0
  83. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/theme-story.tsx +13 -0
  84. package/template/packages/frontend/src/app/[locale]/ui/layout.tsx +6 -0
  85. package/template/packages/frontend/src/app/[locale]/ui/page.tsx +10 -0
  86. package/template/packages/frontend/src/app/api/og/[locale]/[page]/route.tsx +53 -0
  87. package/template/packages/frontend/src/app/globals.css +210 -0
  88. package/template/packages/frontend/src/app/robots.ts +9 -0
  89. package/template/packages/frontend/src/app/sitemap.ts +22 -0
  90. package/template/packages/frontend/src/components/brand-mark.tsx +22 -0
  91. package/template/packages/frontend/src/components/repository-summary.tsx +107 -0
  92. package/template/packages/frontend/src/components/system-controls.tsx +92 -0
  93. package/template/packages/frontend/src/components/ui/adaptive-modal.tsx +382 -0
  94. package/template/packages/frontend/src/components/ui/animated-counter.tsx +80 -0
  95. package/template/packages/frontend/src/components/ui/avatar.tsx +46 -0
  96. package/template/packages/frontend/src/components/ui/badge.tsx +94 -0
  97. package/template/packages/frontend/src/components/ui/button-variants.ts +24 -0
  98. package/template/packages/frontend/src/components/ui/button.tsx +81 -0
  99. package/template/packages/frontend/src/components/ui/card-deck.tsx +73 -0
  100. package/template/packages/frontend/src/components/ui/card.tsx +81 -0
  101. package/template/packages/frontend/src/components/ui/charts/area-chart.tsx +167 -0
  102. package/template/packages/frontend/src/components/ui/charts/index.ts +7 -0
  103. package/template/packages/frontend/src/components/ui/checkbox.tsx +133 -0
  104. package/template/packages/frontend/src/components/ui/confirmation-modal.tsx +129 -0
  105. package/template/packages/frontend/src/components/ui/copyable-text.tsx +85 -0
  106. package/template/packages/frontend/src/components/ui/drawer.tsx +136 -0
  107. package/template/packages/frontend/src/components/ui/dropdown-menu.tsx +185 -0
  108. package/template/packages/frontend/src/components/ui/field.tsx +28 -0
  109. package/template/packages/frontend/src/components/ui/hand-drawn-icons.tsx +41 -0
  110. package/template/packages/frontend/src/components/ui/index.ts +36 -0
  111. package/template/packages/frontend/src/components/ui/input.tsx +24 -0
  112. package/template/packages/frontend/src/components/ui/label.tsx +18 -0
  113. package/template/packages/frontend/src/components/ui/otp-input.tsx +140 -0
  114. package/template/packages/frontend/src/components/ui/select.tsx +240 -0
  115. package/template/packages/frontend/src/components/ui/sidebar.tsx +343 -0
  116. package/template/packages/frontend/src/components/ui/skeleton.tsx +8 -0
  117. package/template/packages/frontend/src/components/ui/slider.tsx +31 -0
  118. package/template/packages/frontend/src/components/ui/slot-drum.tsx +50 -0
  119. package/template/packages/frontend/src/components/ui/spinner.tsx +30 -0
  120. package/template/packages/frontend/src/components/ui/switch.tsx +31 -0
  121. package/template/packages/frontend/src/components/ui/table.tsx +106 -0
  122. package/template/packages/frontend/src/components/ui/tabs.tsx +196 -0
  123. package/template/packages/frontend/src/components/ui/textarea.tsx +21 -0
  124. package/template/packages/frontend/src/components/ui/toaster.tsx +72 -0
  125. package/template/packages/frontend/src/components/ui/tooltip.tsx +31 -0
  126. package/template/packages/frontend/src/env.ts +21 -0
  127. package/template/packages/frontend/src/features/data-table/components/column-helpers.tsx +136 -0
  128. package/template/packages/frontend/src/features/data-table/components/data-table.tsx +303 -0
  129. package/template/packages/frontend/src/features/data-table/components/inline-stat.tsx +113 -0
  130. package/template/packages/frontend/src/features/data-table/components/sortable-header.tsx +33 -0
  131. package/template/packages/frontend/src/features/data-table/components/table-error.tsx +24 -0
  132. package/template/packages/frontend/src/features/data-table/components/table-filters.tsx +82 -0
  133. package/template/packages/frontend/src/features/data-table/hooks/useTableState.ts +111 -0
  134. package/template/packages/frontend/src/features/data-table/index.ts +26 -0
  135. package/template/packages/frontend/src/features/data-table/table-features.ts +26 -0
  136. package/template/packages/frontend/src/hooks/index.ts +3 -0
  137. package/template/packages/frontend/src/hooks/useInfiniteScroll.ts +67 -0
  138. package/template/packages/frontend/src/hooks/useIsTouchDevice.ts +25 -0
  139. package/template/packages/frontend/src/hooks/useMediaQuery.ts +30 -0
  140. package/template/packages/frontend/src/i18n/locales.ts +6 -0
  141. package/template/packages/frontend/src/i18n/navigation.ts +4 -0
  142. package/template/packages/frontend/src/i18n/request.ts +9 -0
  143. package/template/packages/frontend/src/i18n/routing.ts +8 -0
  144. package/template/packages/frontend/src/lib/api/client.ts +20 -0
  145. package/template/packages/frontend/src/lib/api/queries.ts +11 -0
  146. package/template/packages/frontend/src/lib/query-client.ts +20 -0
  147. package/template/packages/frontend/src/lib/realtime/repository.ts +21 -0
  148. package/template/packages/frontend/src/lib/seo/metadata.ts +50 -0
  149. package/template/packages/frontend/src/lib/seo/pages.test.ts +25 -0
  150. package/template/packages/frontend/src/lib/seo/pages.ts +107 -0
  151. package/template/packages/frontend/src/lib/utils/cn.ts +6 -0
  152. package/template/packages/frontend/src/lib/utils/format.ts +25 -0
  153. package/template/packages/frontend/src/lib/utils/index.ts +2 -0
  154. package/template/packages/frontend/src/providers/index.tsx +18 -0
  155. package/template/packages/frontend/src/providers/react-query.tsx +9 -0
  156. package/template/packages/frontend/src/providers/realtime.tsx +16 -0
  157. package/template/packages/frontend/src/proxy.ts +6 -0
  158. package/template/packages/frontend/src/theme/config.ts +20 -0
  159. package/template/packages/frontend/src/theme/transition.ts +54 -0
  160. package/template/packages/frontend/tsconfig.json +13 -0
  161. package/template/packages/shared/package.json +23 -0
  162. package/template/packages/shared/src/contracts/repository.ts +28 -0
  163. package/template/packages/shared/src/events/repository.ts +9 -0
  164. package/template/packages/shared/src/index.ts +3 -0
  165. package/template/packages/shared/src/schemas/repository.test.ts +36 -0
  166. package/template/packages/shared/src/schemas/repository.ts +28 -0
  167. package/template/packages/shared/tsconfig.json +5 -0
  168. package/template/playwright.config.ts +15 -0
  169. package/template/scripts/check-authored.ts +81 -0
  170. package/template/scripts/dev.ts +23 -0
  171. package/template/scripts/local-env.ts +12 -0
  172. package/template/scripts/runtime-smoke.ts +67 -0
  173. package/template/scripts/tooling-env.ts +15 -0
  174. package/template/tsconfig.json +13 -0
@@ -0,0 +1,101 @@
1
+ 'use client';
2
+
3
+ import {
4
+ IconActivity,
5
+ IconChartBar,
6
+ IconFileText,
7
+ IconLayoutDashboard,
8
+ IconUsers,
9
+ } from '@tabler/icons-react';
10
+ import { enUS } from 'date-fns/locale';
11
+ import {
12
+ AreaChart,
13
+ Badge,
14
+ Card,
15
+ CardContent,
16
+ CardHeader,
17
+ CardTitle,
18
+ Sidebar,
19
+ } from '@/components/ui';
20
+
21
+ const navigation = [
22
+ { name: 'Overview', href: '/ui/blocks', icon: IconLayoutDashboard },
23
+ { name: 'Content', href: '/ui/blocks', icon: IconFileText },
24
+ { name: 'People', href: '/ui/blocks', icon: IconUsers },
25
+ ];
26
+
27
+ const chartData = [
28
+ { day: '2026-08-01', total: 12 },
29
+ { day: '2026-08-02', total: 18 },
30
+ { day: '2026-08-03', total: 15 },
31
+ { day: '2026-08-04', total: 27 },
32
+ { day: '2026-08-05', total: 32 },
33
+ ];
34
+
35
+ const activity = [
36
+ ['Deployment completed', '2 minutes ago'],
37
+ ['Project contract updated', '18 minutes ago'],
38
+ ['New collaborator joined', '1 hour ago'],
39
+ ];
40
+
41
+ export function AdminShowcase() {
42
+ return (
43
+ <div className='relative flex min-h-[620px] overflow-hidden rounded-2xl border border-border bg-background'>
44
+ <Sidebar navigation={navigation} logo='Workspace' mobileBreakpoint='md' />
45
+ <div className='min-w-0 flex-1 p-5 md:p-8'>
46
+ <div className='mb-8 flex items-center justify-between'>
47
+ <div>
48
+ <p className='text-sm text-muted-foreground'>Dashboard</p>
49
+ <h2 className='text-2xl font-medium'>Overview</h2>
50
+ </div>
51
+ <Badge variant='success'>Live</Badge>
52
+ </div>
53
+ <div className='grid gap-3 sm:grid-cols-3'>
54
+ {[
55
+ { label: 'Requests', value: '12.8k', icon: IconActivity },
56
+ { label: 'Users', value: '1,284', icon: IconUsers },
57
+ { label: 'Conversion', value: '18.4%', icon: IconChartBar },
58
+ ].map((stat) => (
59
+ <Card key={stat.label}>
60
+ <CardContent className='flex items-start justify-between pt-4'>
61
+ <div>
62
+ <p className='text-sm text-muted-foreground'>{stat.label}</p>
63
+ <p className='mt-2 text-2xl font-medium'>{stat.value}</p>
64
+ </div>
65
+ <stat.icon className='text-muted-foreground' />
66
+ </CardContent>
67
+ </Card>
68
+ ))}
69
+ </div>
70
+ <Card className='mt-4'>
71
+ <CardHeader>
72
+ <CardTitle>Activity</CardTitle>
73
+ </CardHeader>
74
+ <CardContent>
75
+ <AreaChart
76
+ data={chartData}
77
+ areas={[{ key: 'total', label: 'Requests', color: 'var(--chart-1)' }]}
78
+ dateLocale={enUS}
79
+ />
80
+ </CardContent>
81
+ </Card>
82
+ <Card className='mt-4'>
83
+ <CardHeader>
84
+ <CardTitle>Activity feed</CardTitle>
85
+ </CardHeader>
86
+ <CardContent className='divide-y divide-border'>
87
+ {activity.map(([title, time]) => (
88
+ <div
89
+ className='flex items-center justify-between gap-4 py-3 first:pt-0 last:pb-0'
90
+ key={title}
91
+ >
92
+ <span className='text-sm'>{title}</span>
93
+ <span className='text-xs text-muted-foreground'>{time}</span>
94
+ </div>
95
+ ))}
96
+ </CardContent>
97
+ </Card>
98
+ </div>
99
+ </div>
100
+ );
101
+ }
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+
3
+ import { IconArrowLeft, IconBox, IconLayoutDashboard, IconPalette } from '@tabler/icons-react';
4
+ import { useLocale } from 'next-intl';
5
+ import { LanguageSwitcher, ThemeToggle } from '@/components/system-controls';
6
+ import { buttonVariants, Sidebar } from '@/components/ui';
7
+ import { LocaleSchema } from '@/i18n/locales';
8
+ import { Link } from '@/i18n/navigation';
9
+ import { getSeoPage } from '@/lib/seo/pages';
10
+
11
+ export function CatalogueNavigation() {
12
+ const locale = LocaleSchema.parse(useLocale());
13
+ const navigation = [
14
+ {
15
+ name: getSeoPage('components', locale).title,
16
+ href: '/ui/components',
17
+ icon: IconBox,
18
+ },
19
+ {
20
+ name: getSeoPage('themes', locale).title,
21
+ href: '/ui/themes',
22
+ icon: IconPalette,
23
+ },
24
+ {
25
+ name: getSeoPage('blocks', locale).title,
26
+ href: '/ui/blocks',
27
+ icon: IconLayoutDashboard,
28
+ },
29
+ ];
30
+ return (
31
+ <Sidebar
32
+ collapsible={false}
33
+ className='top-5 max-h-[calc(100dvh-2.5rem)]'
34
+ footer={
35
+ <div className='flex items-center justify-between gap-2 border-t border-border px-1 pt-3'>
36
+ <Link
37
+ aria-label='Back to starter home'
38
+ className={buttonVariants({ variant: 'ghost', size: 'icon' })}
39
+ href='/'
40
+ title='Back to starter home'
41
+ >
42
+ <IconArrowLeft />
43
+ </Link>
44
+ <div className='flex items-center gap-2'>
45
+ <LanguageSwitcher />
46
+ <ThemeToggle />
47
+ </div>
48
+ </div>
49
+ }
50
+ navigation={navigation}
51
+ logo='UI system'
52
+ logoHref='/ui/components'
53
+ variant='floating'
54
+ />
55
+ );
56
+ }
@@ -0,0 +1,51 @@
1
+ import type { ReactNode } from 'react';
2
+ import { CatalogueNavigation } from './catalogue-navigation';
3
+
4
+ export function CatalogueShell({ children }: { children: ReactNode }) {
5
+ return (
6
+ <div className='relative flex h-dvh flex-col overflow-hidden bg-background'>
7
+ <CatalogueNavigation />
8
+ <main
9
+ className='min-h-0 flex-1 overflow-y-auto overscroll-contain'
10
+ data-testid='catalogue-content'
11
+ >
12
+ <div className='mx-auto w-full max-w-[96rem] px-5 py-7 pt-16 lg:pl-[18rem] lg:pr-8 lg:pt-10'>
13
+ {children}
14
+ </div>
15
+ </main>
16
+ </div>
17
+ );
18
+ }
19
+
20
+ export function StoryHeader({
21
+ title,
22
+ description,
23
+ eyebrow,
24
+ }: {
25
+ title: string;
26
+ description: string;
27
+ eyebrow: string;
28
+ }) {
29
+ return (
30
+ <div className='mb-7 max-w-3xl'>
31
+ <p className='text-sm text-muted-foreground'>{eyebrow}</p>
32
+ <h1 className='mt-2 font-display text-4xl font-medium tracking-tight sm:text-5xl'>
33
+ {title}
34
+ </h1>
35
+ <p className='mt-3 text-muted-foreground'>{description}</p>
36
+ </div>
37
+ );
38
+ }
39
+
40
+ export function StorySection({ title, children }: { title: string; children: ReactNode }) {
41
+ return (
42
+ <details className='group mb-5 rounded-2xl border border-border bg-card/45' open>
43
+ <summary className='flex list-none items-center justify-between px-5 py-4 text-lg font-medium marker:hidden'>
44
+ {title}
45
+ <span className='text-muted-foreground group-open:hidden'>+</span>
46
+ <span className='hidden text-muted-foreground group-open:inline'>−</span>
47
+ </summary>
48
+ <section className='space-y-5 border-t border-border p-5'>{children}</section>
49
+ </details>
50
+ );
51
+ }
@@ -0,0 +1,35 @@
1
+ import { codeToHtml } from 'shiki';
2
+
3
+ const example = `const snapshot = await api.repository.read();
4
+
5
+ await api.repository.refresh();
6
+ // The same operations are available to HTTP, MCP, agents and CLI.`;
7
+
8
+ export async function CodeFoundationSection() {
9
+ const html = await codeToHtml(example, {
10
+ lang: 'ts',
11
+ themes: {
12
+ light: 'github-light-high-contrast',
13
+ dark: 'github-dark-high-contrast',
14
+ },
15
+ defaultColor: false,
16
+ });
17
+
18
+ return (
19
+ <section className='grid gap-8 py-14 md:grid-cols-[0.8fr_1.2fr] md:items-center'>
20
+ <div>
21
+ <p className='text-sm text-muted-foreground'>Inspect the foundation</p>
22
+ <h2 className='mt-2 text-3xl font-medium'>The useful path stays short</h2>
23
+ <p className='mt-3 text-muted-foreground'>
24
+ Application code calls a typed client while validation, transport and observability
25
+ remain framework-owned.
26
+ </p>
27
+ </div>
28
+ <div
29
+ className='overflow-auto rounded-2xl border border-border bg-card p-5 text-sm [&_pre]:bg-transparent!'
30
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki renders a static authored code sample.
31
+ dangerouslySetInnerHTML={{ __html: html }}
32
+ />
33
+ </section>
34
+ );
35
+ }
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui';
4
+ import { DataStory } from './data-story';
5
+ import { MotionStory } from './motion-story';
6
+ import { OverlaysStory } from './overlays-story';
7
+ import { PrimitivesStory } from './primitives-story';
8
+
9
+ const componentTabs = [
10
+ { value: 'actions', label: 'Actions' },
11
+ { value: 'forms', label: 'Forms' },
12
+ { value: 'feedback', label: 'Feedback' },
13
+ { value: 'media', label: 'Media & tables' },
14
+ { value: 'overlays', label: 'Overlays' },
15
+ { value: 'data', label: 'Data' },
16
+ { value: 'motion', label: 'Motion' },
17
+ ];
18
+
19
+ export function ComponentsStory() {
20
+ return (
21
+ <Tabs defaultValue='actions' className='gap-5'>
22
+ <div className='overflow-x-auto overscroll-x-contain pb-1'>
23
+ <TabsList className='min-w-max'>
24
+ {componentTabs.map((tab) => (
25
+ <TabsTrigger key={tab.value} value={tab.value}>
26
+ {tab.label}
27
+ </TabsTrigger>
28
+ ))}
29
+ </TabsList>
30
+ </div>
31
+ <TabsContent value='actions'>
32
+ <PrimitivesStory category='actions' />
33
+ </TabsContent>
34
+ <TabsContent value='forms'>
35
+ <PrimitivesStory category='forms' />
36
+ </TabsContent>
37
+ <TabsContent value='feedback'>
38
+ <PrimitivesStory category='feedback' />
39
+ </TabsContent>
40
+ <TabsContent value='media'>
41
+ <PrimitivesStory category='media' />
42
+ </TabsContent>
43
+ <TabsContent value='overlays'>
44
+ <OverlaysStory />
45
+ </TabsContent>
46
+ <TabsContent value='data'>
47
+ <DataStory />
48
+ </TabsContent>
49
+ <TabsContent value='motion'>
50
+ <MotionStory />
51
+ </TabsContent>
52
+ </Tabs>
53
+ );
54
+ }
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+
3
+ import { IconActivity, IconArchive, IconFolders } from '@tabler/icons-react';
4
+ import { createColumnHelper } from '@tanstack/react-table';
5
+ import { useState } from 'react';
6
+ import { Badge, Button } from '@/components/ui';
7
+ import {
8
+ DataTable,
9
+ DualInlineStat,
10
+ InlineStat,
11
+ SortableHeader,
12
+ TableError,
13
+ TableFilters,
14
+ } from '@/features/data-table';
15
+ import type { dataTableFeatures } from '@/features/data-table/table-features';
16
+ import { StorySection } from './catalogue-shell';
17
+
18
+ interface CatalogueProject {
19
+ name: string;
20
+ stage: string;
21
+ owner: string;
22
+ updatedAt: string;
23
+ }
24
+
25
+ const rows: CatalogueProject[] = [
26
+ { name: 'Stitchkit', stage: 'Shipping', owner: 'Framework', updatedAt: '2026-08-08' },
27
+ { name: 'Agent workspace', stage: 'Building', owner: 'Product', updatedAt: '2026-08-07' },
28
+ { name: 'Device control', stage: 'Idea', owner: 'Research', updatedAt: '2026-08-02' },
29
+ {
30
+ name: 'Release observatory',
31
+ stage: 'Shipping',
32
+ owner: 'Platform',
33
+ updatedAt: '2026-08-01',
34
+ },
35
+ { name: 'Contract explorer', stage: 'Building', owner: 'Framework', updatedAt: '2026-07-31' },
36
+ { name: 'Runtime bench', stage: 'Idea', owner: 'Research', updatedAt: '2026-07-30' },
37
+ ];
38
+
39
+ const columnHelper = createColumnHelper<typeof dataTableFeatures, CatalogueProject>();
40
+ const columns = columnHelper.columns([
41
+ columnHelper.accessor('name', {
42
+ header: ({ column }) => <SortableHeader column={column}>Project</SortableHeader>,
43
+ }),
44
+ columnHelper.accessor('stage', {
45
+ header: 'Stage',
46
+ cell: ({ row }) => <Badge size='sm'>{row.original.stage}</Badge>,
47
+ }),
48
+ columnHelper.accessor('owner', { header: 'Owner' }),
49
+ columnHelper.accessor('updatedAt', { header: 'Updated' }),
50
+ ]);
51
+
52
+ const filters = [
53
+ {
54
+ key: 'stage',
55
+ placeholder: 'Stage',
56
+ allLabel: 'All stages',
57
+ options: [
58
+ { value: 'Building', label: 'Building' },
59
+ { value: 'Shipping', label: 'Shipping' },
60
+ ],
61
+ },
62
+ ];
63
+
64
+ const displayStates: Array<'ready' | 'loading' | 'empty'> = ['ready', 'loading', 'empty'];
65
+
66
+ export function DataStory() {
67
+ const [values, setValues] = useState<Record<string, string | undefined>>({});
68
+ const [state, setState] = useState<'ready' | 'loading' | 'empty'>('ready');
69
+ const filteredRows = rows.filter((row) => !values.stage || row.stage === values.stage);
70
+ return (
71
+ <div>
72
+ <StorySection title='Operational statistics'>
73
+ <div className='grid gap-3 sm:grid-cols-3'>
74
+ <InlineStat icon={IconFolders} label='Projects' value='24' />
75
+ <InlineStat icon={IconActivity} label='Active' value='18' />
76
+ <DualInlineStat
77
+ icon={IconArchive}
78
+ label='Delivery'
79
+ items={[
80
+ { value: '12', suffix: 'shipped', dotColor: 'bg-success' },
81
+ { value: '3', suffix: 'paused', dotColor: 'bg-warning' },
82
+ ]}
83
+ />
84
+ </div>
85
+ </StorySection>
86
+ <StorySection title='Data table'>
87
+ <div className='overflow-hidden rounded-xl border border-border'>
88
+ <div className='flex flex-wrap items-center justify-between gap-3 border-b border-border p-3'>
89
+ <TableFilters
90
+ filters={filters}
91
+ values={values}
92
+ onChange={(key, value) => setValues((current) => ({ ...current, [key]: value }))}
93
+ onClear={() => setValues({})}
94
+ />
95
+ <div className='flex gap-1'>
96
+ {displayStates.map((value) => (
97
+ <Button
98
+ key={value}
99
+ size='sm'
100
+ variant={state === value ? 'default' : 'ghost'}
101
+ onClick={() => setState(value)}
102
+ >
103
+ {value}
104
+ </Button>
105
+ ))}
106
+ </div>
107
+ </div>
108
+ <div className='h-[420px]'>
109
+ <DataTable<CatalogueProject>
110
+ columns={columns}
111
+ data={state === 'empty' ? [] : filteredRows}
112
+ isLoading={state === 'loading'}
113
+ searchKey='name'
114
+ searchPlaceholder='Search projects'
115
+ />
116
+ </div>
117
+ </div>
118
+ </StorySection>
119
+ <StorySection title='Failure state'>
120
+ <div className='h-28 rounded-xl border border-border'>
121
+ <TableError error={new Error('Example typed transport error')} entity='projects' />
122
+ </div>
123
+ </StorySection>
124
+ </div>
125
+ );
126
+ }
@@ -0,0 +1,8 @@
1
+ 'use client';
2
+
3
+ import { preload } from 'react-dom';
4
+
5
+ export function ImagePreloader({ sources }: { sources: string[] }) {
6
+ for (const source of sources) preload(source, { as: 'image' });
7
+ return null;
8
+ }
@@ -0,0 +1,3 @@
1
+ export { isStoryId, type StoryId, storyIds } from '@/lib/seo/pages';
2
+ export { CatalogueShell, StoryHeader, StorySection } from './catalogue-shell';
3
+ export { StoryPage } from './story-page';