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,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { IconCheck, IconCreditCard } from '@tabler/icons-react';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import {
|
|
6
|
+
AdaptiveModal,
|
|
7
|
+
Badge,
|
|
8
|
+
Button,
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
Field,
|
|
14
|
+
Input,
|
|
15
|
+
} from '@/components/ui';
|
|
16
|
+
|
|
17
|
+
export function PaymentShowcase() {
|
|
18
|
+
const [open, setOpen] = useState(false);
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<Card className='max-w-lg'>
|
|
22
|
+
<CardHeader>
|
|
23
|
+
<div className='flex items-center justify-between'>
|
|
24
|
+
<div className='grid size-10 place-items-center rounded-xl bg-muted'>
|
|
25
|
+
<IconCreditCard />
|
|
26
|
+
</div>
|
|
27
|
+
<Badge>Secure checkout</Badge>
|
|
28
|
+
</div>
|
|
29
|
+
<CardTitle className='pt-4 text-2xl'>Complete your order</CardTitle>
|
|
30
|
+
</CardHeader>
|
|
31
|
+
<CardContent className='space-y-5'>
|
|
32
|
+
<div className='rounded-xl bg-muted p-4'>
|
|
33
|
+
<div className='flex justify-between'>
|
|
34
|
+
<span className='text-muted-foreground'>Product foundation</span>
|
|
35
|
+
<span className='font-medium'>$99</span>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<div className='space-y-2 text-sm'>
|
|
39
|
+
{[
|
|
40
|
+
'Typed contract surface',
|
|
41
|
+
'Complete UI source',
|
|
42
|
+
'Production deployment profiles',
|
|
43
|
+
].map((item) => (
|
|
44
|
+
<div className='flex items-center gap-2' key={item}>
|
|
45
|
+
<IconCheck className='text-success' size={17} />
|
|
46
|
+
{item}
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
<Button className='w-full' variant='primary' onClick={() => setOpen(true)}>
|
|
51
|
+
Continue to payment
|
|
52
|
+
</Button>
|
|
53
|
+
<p className='text-center text-xs text-muted-foreground'>
|
|
54
|
+
Presentation only. Connect your own provider contract.
|
|
55
|
+
</p>
|
|
56
|
+
</CardContent>
|
|
57
|
+
</Card>
|
|
58
|
+
<AdaptiveModal
|
|
59
|
+
isOpen={open}
|
|
60
|
+
onClose={() => setOpen(false)}
|
|
61
|
+
title='Checkout details'
|
|
62
|
+
desktopWidth='md'
|
|
63
|
+
mobileVariant='sheet'
|
|
64
|
+
footer={
|
|
65
|
+
<Button className='w-full' variant='primary' onClick={() => setOpen(false)}>
|
|
66
|
+
Confirm example order
|
|
67
|
+
</Button>
|
|
68
|
+
}
|
|
69
|
+
>
|
|
70
|
+
<div className='grid gap-4 p-4 pt-1'>
|
|
71
|
+
<Field label='Billing email' htmlFor='checkout-email'>
|
|
72
|
+
<Input id='checkout-email' type='email' placeholder='builder@example.com' />
|
|
73
|
+
</Field>
|
|
74
|
+
<div className='flex justify-between rounded-xl bg-muted p-4 text-sm'>
|
|
75
|
+
<span>Product foundation</span>
|
|
76
|
+
<strong>$99</strong>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</AdaptiveModal>
|
|
80
|
+
</>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { LanguageSwitcher, ThemeToggle } from '@/components/system-controls';
|
|
2
|
+
import { Button } from '@/components/ui';
|
|
3
|
+
import { AccountShowcase } from './account-showcase';
|
|
4
|
+
import { AdminShowcase } from './admin-showcase';
|
|
5
|
+
import { StorySection } from './catalogue-shell';
|
|
6
|
+
import { CodeFoundationSection } from './code-foundation';
|
|
7
|
+
import { ImagePreloader } from './image-preloader';
|
|
8
|
+
import {
|
|
9
|
+
AudienceSection,
|
|
10
|
+
BuilderSection,
|
|
11
|
+
DemoModal,
|
|
12
|
+
FrontendSection,
|
|
13
|
+
LearningSection,
|
|
14
|
+
ProblemsSection,
|
|
15
|
+
TemplatePreviewSection,
|
|
16
|
+
WhySection,
|
|
17
|
+
} from './landing-sections';
|
|
18
|
+
import {
|
|
19
|
+
FeatureGrid,
|
|
20
|
+
HeroSection,
|
|
21
|
+
LandingFooter,
|
|
22
|
+
LandingHeader,
|
|
23
|
+
PortfolioSection,
|
|
24
|
+
PricingSection,
|
|
25
|
+
} from './landing-showcase';
|
|
26
|
+
import { PaymentShowcase } from './payment-showcase';
|
|
27
|
+
|
|
28
|
+
export function BlocksStory() {
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
<StorySection title='Editorial landing system'>
|
|
32
|
+
<LandingHeader
|
|
33
|
+
actions={
|
|
34
|
+
<>
|
|
35
|
+
<LanguageSwitcher />
|
|
36
|
+
<ThemeToggle />
|
|
37
|
+
<Button size='sm'>Open app</Button>
|
|
38
|
+
</>
|
|
39
|
+
}
|
|
40
|
+
/>
|
|
41
|
+
<HeroSection />
|
|
42
|
+
<ProblemsSection />
|
|
43
|
+
<BuilderSection />
|
|
44
|
+
<WhySection />
|
|
45
|
+
<CodeFoundationSection />
|
|
46
|
+
<FeatureGrid />
|
|
47
|
+
<AudienceSection />
|
|
48
|
+
<LearningSection />
|
|
49
|
+
<TemplatePreviewSection />
|
|
50
|
+
<FrontendSection />
|
|
51
|
+
<PortfolioSection />
|
|
52
|
+
<PricingSection />
|
|
53
|
+
<DemoModal />
|
|
54
|
+
<LandingFooter />
|
|
55
|
+
<ImagePreloader sources={['/theme-light.svg', '/theme-dark.svg']} />
|
|
56
|
+
</StorySection>
|
|
57
|
+
<StorySection title='Application shell'>
|
|
58
|
+
<AdminShowcase />
|
|
59
|
+
</StorySection>
|
|
60
|
+
<StorySection title='Account flows'>
|
|
61
|
+
<AccountShowcase />
|
|
62
|
+
</StorySection>
|
|
63
|
+
<StorySection title='Checkout presentation'>
|
|
64
|
+
<PaymentShowcase />
|
|
65
|
+
</StorySection>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { IconCheck, IconMail } from '@tabler/icons-react';
|
|
4
|
+
import { ThemedImage } from '@wrksz/themes/client/themed-image';
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
import { BrandMark } from '@/components/brand-mark';
|
|
7
|
+
import {
|
|
8
|
+
Avatar,
|
|
9
|
+
AvatarFallback,
|
|
10
|
+
AvatarImage,
|
|
11
|
+
Badge,
|
|
12
|
+
Button,
|
|
13
|
+
Card,
|
|
14
|
+
CardContent,
|
|
15
|
+
CardDescription,
|
|
16
|
+
CardFooter,
|
|
17
|
+
CardHeader,
|
|
18
|
+
CardTitle,
|
|
19
|
+
Checkbox,
|
|
20
|
+
CopyableText,
|
|
21
|
+
Field,
|
|
22
|
+
HandDrawnCheck,
|
|
23
|
+
HandDrawnX,
|
|
24
|
+
Input,
|
|
25
|
+
Label,
|
|
26
|
+
OtpInput,
|
|
27
|
+
Select,
|
|
28
|
+
SelectContent,
|
|
29
|
+
SelectGroup,
|
|
30
|
+
SelectItem,
|
|
31
|
+
SelectLabel,
|
|
32
|
+
SelectSeparator,
|
|
33
|
+
SelectTrigger,
|
|
34
|
+
SelectValue,
|
|
35
|
+
Skeleton,
|
|
36
|
+
Slider,
|
|
37
|
+
Spinner,
|
|
38
|
+
Switch,
|
|
39
|
+
Table,
|
|
40
|
+
TableBody,
|
|
41
|
+
TableCell,
|
|
42
|
+
TableHead,
|
|
43
|
+
TableHeader,
|
|
44
|
+
TableRow,
|
|
45
|
+
Tabs,
|
|
46
|
+
TabsContent,
|
|
47
|
+
TabsList,
|
|
48
|
+
TabsTrigger,
|
|
49
|
+
Textarea,
|
|
50
|
+
TriStateCheckbox,
|
|
51
|
+
toast,
|
|
52
|
+
} from '@/components/ui';
|
|
53
|
+
import { StorySection } from './catalogue-shell';
|
|
54
|
+
|
|
55
|
+
type PrimitiveCategory = 'actions' | 'forms' | 'feedback' | 'media';
|
|
56
|
+
|
|
57
|
+
export function PrimitivesStory({ category }: { category: PrimitiveCategory }) {
|
|
58
|
+
const [otp, setOtp] = useState('');
|
|
59
|
+
const [enabled, setEnabled] = useState(true);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div>
|
|
63
|
+
{category === 'actions' ? (
|
|
64
|
+
<StorySection title='Actions and status'>
|
|
65
|
+
<div className='flex flex-wrap gap-3'>
|
|
66
|
+
<Button>default</Button>
|
|
67
|
+
<Button variant='primary'>primary</Button>
|
|
68
|
+
<Button variant='outline'>outline</Button>
|
|
69
|
+
<Button variant='ghost'>ghost</Button>
|
|
70
|
+
<Button variant='destructive'>destructive</Button>
|
|
71
|
+
<Button loading>Working</Button>
|
|
72
|
+
<Button size='icon' aria-label='Quick action'>
|
|
73
|
+
<BrandMark className='size-5' />
|
|
74
|
+
</Button>
|
|
75
|
+
</div>
|
|
76
|
+
<div className='flex flex-wrap gap-2'>
|
|
77
|
+
<Badge icon={<IconCheck />}>default</Badge>
|
|
78
|
+
<Badge icon={<IconCheck />} variant='primary'>
|
|
79
|
+
primary
|
|
80
|
+
</Badge>
|
|
81
|
+
<Badge icon={<IconCheck />} variant='secondary'>
|
|
82
|
+
secondary
|
|
83
|
+
</Badge>
|
|
84
|
+
<Badge icon={<IconCheck />} variant='outline'>
|
|
85
|
+
outline
|
|
86
|
+
</Badge>
|
|
87
|
+
<Badge icon={<IconCheck />} variant='success'>
|
|
88
|
+
success
|
|
89
|
+
</Badge>
|
|
90
|
+
<Badge icon={<IconCheck />} variant='warning'>
|
|
91
|
+
warning
|
|
92
|
+
</Badge>
|
|
93
|
+
<Badge icon={<IconCheck />} variant='destructive'>
|
|
94
|
+
destructive
|
|
95
|
+
</Badge>
|
|
96
|
+
</div>
|
|
97
|
+
</StorySection>
|
|
98
|
+
) : null}
|
|
99
|
+
|
|
100
|
+
{category === 'forms' ? (
|
|
101
|
+
<StorySection title='Forms'>
|
|
102
|
+
<Card className='w-full'>
|
|
103
|
+
<CardHeader>
|
|
104
|
+
<CardTitle>Project details</CardTitle>
|
|
105
|
+
<CardDescription>Reusable accessible form primitives.</CardDescription>
|
|
106
|
+
</CardHeader>
|
|
107
|
+
<CardContent className='grid gap-4 sm:grid-cols-2'>
|
|
108
|
+
<Field label='Project name' htmlFor='story-name'>
|
|
109
|
+
<Input id='story-name' placeholder='Project atlas' />
|
|
110
|
+
</Field>
|
|
111
|
+
<Field label='Contact' htmlFor='story-email'>
|
|
112
|
+
<Input id='story-email' type='email' placeholder='you@example.com' />
|
|
113
|
+
</Field>
|
|
114
|
+
<div className='sm:col-span-2'>
|
|
115
|
+
<Field label='Description' htmlFor='story-description'>
|
|
116
|
+
<Textarea id='story-description' placeholder='What are you building?' />
|
|
117
|
+
</Field>
|
|
118
|
+
</div>
|
|
119
|
+
<div className='space-y-2'>
|
|
120
|
+
<Label htmlFor='story-select'>Stage</Label>
|
|
121
|
+
<Select defaultValue='building'>
|
|
122
|
+
<SelectTrigger id='story-select'>
|
|
123
|
+
<SelectValue />
|
|
124
|
+
</SelectTrigger>
|
|
125
|
+
<SelectContent>
|
|
126
|
+
<SelectGroup>
|
|
127
|
+
<SelectLabel>Planning</SelectLabel>
|
|
128
|
+
<SelectItem value='idea'>Idea</SelectItem>
|
|
129
|
+
</SelectGroup>
|
|
130
|
+
<SelectSeparator />
|
|
131
|
+
<SelectGroup>
|
|
132
|
+
<SelectLabel>Delivery</SelectLabel>
|
|
133
|
+
<SelectItem value='building'>Building</SelectItem>
|
|
134
|
+
<SelectItem value='shipping'>Shipping</SelectItem>
|
|
135
|
+
</SelectGroup>
|
|
136
|
+
</SelectContent>
|
|
137
|
+
</Select>
|
|
138
|
+
</div>
|
|
139
|
+
<div className='space-y-3'>
|
|
140
|
+
<Label htmlFor='story-progress'>Progress</Label>
|
|
141
|
+
<Slider
|
|
142
|
+
id='story-progress'
|
|
143
|
+
aria-label='Project progress'
|
|
144
|
+
defaultValue={[68]}
|
|
145
|
+
max={100}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
<Checkbox defaultChecked label='Public roadmap' />
|
|
149
|
+
<div className='flex items-center gap-2 text-sm'>
|
|
150
|
+
<Switch id='story-realtime' checked={enabled} onCheckedChange={setEnabled} />
|
|
151
|
+
<Label htmlFor='story-realtime'>Realtime updates</Label>
|
|
152
|
+
</div>
|
|
153
|
+
<TriStateCheckbox checked='indeterminate' label='Select visible rows' />
|
|
154
|
+
</CardContent>
|
|
155
|
+
</Card>
|
|
156
|
+
</StorySection>
|
|
157
|
+
) : null}
|
|
158
|
+
|
|
159
|
+
{category === 'feedback' ? (
|
|
160
|
+
<StorySection title='Identity, verification and feedback'>
|
|
161
|
+
<div className='grid gap-6 md:grid-cols-2'>
|
|
162
|
+
<Card>
|
|
163
|
+
<CardContent className='flex items-center gap-4 pt-4'>
|
|
164
|
+
<Avatar>
|
|
165
|
+
<AvatarImage src='https://github.com/github.png' alt='Example user avatar' />
|
|
166
|
+
<AvatarFallback>PB</AvatarFallback>
|
|
167
|
+
</Avatar>
|
|
168
|
+
<div>
|
|
169
|
+
<p className='font-medium'>Product builder</p>
|
|
170
|
+
<CopyableText value='builder@example.com'>
|
|
171
|
+
<span className='flex items-center gap-1'>
|
|
172
|
+
<IconMail size={14} /> builder@example.com
|
|
173
|
+
</span>
|
|
174
|
+
</CopyableText>
|
|
175
|
+
</div>
|
|
176
|
+
</CardContent>
|
|
177
|
+
</Card>
|
|
178
|
+
<Card>
|
|
179
|
+
<CardContent className='pt-4 text-sm text-muted-foreground'>
|
|
180
|
+
Card content
|
|
181
|
+
</CardContent>
|
|
182
|
+
<CardFooter>
|
|
183
|
+
<Button
|
|
184
|
+
size='sm'
|
|
185
|
+
variant='outline'
|
|
186
|
+
onClick={() => toast.info('Project opened')}
|
|
187
|
+
>
|
|
188
|
+
Open project
|
|
189
|
+
</Button>
|
|
190
|
+
</CardFooter>
|
|
191
|
+
</Card>
|
|
192
|
+
<Card>
|
|
193
|
+
<CardContent className='space-y-4 pt-4'>
|
|
194
|
+
<OtpInput value={otp} onChange={setOtp} />
|
|
195
|
+
<div className='grid grid-cols-2 gap-2'>
|
|
196
|
+
<Button onClick={() => toast.success('Changes saved')}>Success toast</Button>
|
|
197
|
+
<Button variant='outline' onClick={() => toast.info('Sync is running')}>
|
|
198
|
+
Info toast
|
|
199
|
+
</Button>
|
|
200
|
+
<Button variant='outline' onClick={() => toast.warning('Review required')}>
|
|
201
|
+
Warning toast
|
|
202
|
+
</Button>
|
|
203
|
+
<Button variant='destructive' onClick={() => toast.error('Action failed')}>
|
|
204
|
+
Error toast
|
|
205
|
+
</Button>
|
|
206
|
+
</div>
|
|
207
|
+
</CardContent>
|
|
208
|
+
</Card>
|
|
209
|
+
</div>
|
|
210
|
+
<div className='flex items-center gap-5'>
|
|
211
|
+
<HandDrawnCheck className='size-12 text-success' />
|
|
212
|
+
<HandDrawnX className='size-12 text-destructive' />
|
|
213
|
+
<Spinner />
|
|
214
|
+
<Skeleton className='h-10 w-40' />
|
|
215
|
+
</div>
|
|
216
|
+
</StorySection>
|
|
217
|
+
) : null}
|
|
218
|
+
|
|
219
|
+
{category === 'media' ? (
|
|
220
|
+
<StorySection title='Tabs and theme-aware media'>
|
|
221
|
+
<Tabs defaultValue='preview'>
|
|
222
|
+
<TabsList>
|
|
223
|
+
<TabsTrigger value='preview'>Preview</TabsTrigger>
|
|
224
|
+
<TabsTrigger value='details'>Details</TabsTrigger>
|
|
225
|
+
</TabsList>
|
|
226
|
+
<TabsContent value='preview' className='pt-4'>
|
|
227
|
+
<ThemedImage
|
|
228
|
+
src={{ light: '/theme-light.svg', dark: '/theme-dark.svg' }}
|
|
229
|
+
alt='Theme-aware application shell preview'
|
|
230
|
+
width={640}
|
|
231
|
+
height={360}
|
|
232
|
+
className='w-full max-w-2xl rounded-2xl'
|
|
233
|
+
/>
|
|
234
|
+
</TabsContent>
|
|
235
|
+
<TabsContent value='details' className='pt-4 text-sm text-muted-foreground'>
|
|
236
|
+
The same component switches assets with semantic theme state.
|
|
237
|
+
</TabsContent>
|
|
238
|
+
</Tabs>
|
|
239
|
+
</StorySection>
|
|
240
|
+
) : null}
|
|
241
|
+
{category === 'media' ? (
|
|
242
|
+
<StorySection title='Semantic table primitives'>
|
|
243
|
+
<div className='overflow-hidden rounded-xl border border-border'>
|
|
244
|
+
<Table>
|
|
245
|
+
<TableHeader>
|
|
246
|
+
<TableRow>
|
|
247
|
+
<TableHead>Surface</TableHead>
|
|
248
|
+
<TableHead>Status</TableHead>
|
|
249
|
+
</TableRow>
|
|
250
|
+
</TableHeader>
|
|
251
|
+
<TableBody>
|
|
252
|
+
<TableRow>
|
|
253
|
+
<TableCell>HTTP</TableCell>
|
|
254
|
+
<TableCell>Ready</TableCell>
|
|
255
|
+
</TableRow>
|
|
256
|
+
<TableRow>
|
|
257
|
+
<TableCell>MCP</TableCell>
|
|
258
|
+
<TableCell>Ready</TableCell>
|
|
259
|
+
</TableRow>
|
|
260
|
+
</TableBody>
|
|
261
|
+
</Table>
|
|
262
|
+
</div>
|
|
263
|
+
</StorySection>
|
|
264
|
+
) : null}
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { StoryId } from '@/lib/seo/pages';
|
|
2
|
+
import { ComponentsStory } from './components-story';
|
|
3
|
+
import { BlocksStory } from './presentation-story';
|
|
4
|
+
import { ThemeStory } from './theme-story';
|
|
5
|
+
|
|
6
|
+
export function StoryPage({ story }: { story: StoryId }) {
|
|
7
|
+
if (story === 'components') {
|
|
8
|
+
return <ComponentsStory />;
|
|
9
|
+
}
|
|
10
|
+
if (story === 'themes') {
|
|
11
|
+
return <ThemeStory />;
|
|
12
|
+
}
|
|
13
|
+
return <BlocksStory />;
|
|
14
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { IconCheck, IconDeviceDesktop, IconMoon, IconSun } from '@tabler/icons-react';
|
|
4
|
+
import { ClientThemeProvider } from '@wrksz/themes/client/provider';
|
|
5
|
+
import { ThemedImage } from '@wrksz/themes/client/themed-image';
|
|
6
|
+
import { useHydrated } from '@wrksz/themes/client/use-hydrated';
|
|
7
|
+
import { useTheme } from '@wrksz/themes/client/use-theme';
|
|
8
|
+
import { useThemeEffect } from '@wrksz/themes/client/use-theme-effect';
|
|
9
|
+
import { useThemeValue } from '@wrksz/themes/client/use-theme-value';
|
|
10
|
+
import { useCallback, useState } from 'react';
|
|
11
|
+
import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from '@/components/ui';
|
|
12
|
+
import { type AppTheme, appThemes } from '@/theme/config';
|
|
13
|
+
import {
|
|
14
|
+
runThemeTransition,
|
|
15
|
+
type ThemeTransitionConfig,
|
|
16
|
+
type ThemeTransitionStyle,
|
|
17
|
+
} from '@/theme/transition';
|
|
18
|
+
import { StorySection } from './catalogue-shell';
|
|
19
|
+
|
|
20
|
+
type ThemeSelection = AppTheme | 'system';
|
|
21
|
+
|
|
22
|
+
const themeChoices = [
|
|
23
|
+
{ value: 'light', label: 'Light', icon: IconSun },
|
|
24
|
+
{ value: 'dark', label: 'Dark', icon: IconMoon },
|
|
25
|
+
{ value: 'system', label: 'System', icon: IconDeviceDesktop },
|
|
26
|
+
] satisfies ReadonlyArray<{
|
|
27
|
+
value: ThemeSelection;
|
|
28
|
+
label: string;
|
|
29
|
+
icon: typeof IconSun;
|
|
30
|
+
}>;
|
|
31
|
+
|
|
32
|
+
const themeImages = {
|
|
33
|
+
light: '/theme-light.svg',
|
|
34
|
+
dark: '/theme-dark.svg',
|
|
35
|
+
} satisfies Record<AppTheme, string>;
|
|
36
|
+
|
|
37
|
+
const transitionStyles = [
|
|
38
|
+
{ value: 'crossfade', label: 'Crossfade' },
|
|
39
|
+
{ value: 'radial', label: 'Radial reveal' },
|
|
40
|
+
] satisfies ReadonlyArray<{ value: ThemeTransitionStyle; label: string }>;
|
|
41
|
+
|
|
42
|
+
const transitionDurations = [150, 250, 400];
|
|
43
|
+
|
|
44
|
+
function ScopedThemeCard({ theme }: { theme: AppTheme }) {
|
|
45
|
+
const { resolvedTheme } = useTheme<AppTheme>();
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
id={`theme-scope-${theme}`}
|
|
50
|
+
className='rounded-2xl border border-border bg-background p-5 text-foreground'
|
|
51
|
+
>
|
|
52
|
+
<div className='flex items-center justify-between gap-4'>
|
|
53
|
+
<div>
|
|
54
|
+
<p className='font-medium'>{theme === 'light' ? 'Forced light' : 'Forced dark'}</p>
|
|
55
|
+
<p className='mt-1 text-sm text-muted-foreground'>Independent client provider</p>
|
|
56
|
+
</div>
|
|
57
|
+
<Badge variant='secondary'>{resolvedTheme ?? 'hydrating'}</Badge>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function ThemeStoryClient({ serverTheme }: { serverTheme: ThemeSelection }) {
|
|
64
|
+
const hydrated = useHydrated();
|
|
65
|
+
const { theme, resolvedTheme, systemTheme, setTheme } = useTheme<AppTheme>();
|
|
66
|
+
const surfaceLabel = useThemeValue({
|
|
67
|
+
light: 'Paper surface',
|
|
68
|
+
dark: 'Midnight surface',
|
|
69
|
+
default: 'Resolving surface',
|
|
70
|
+
});
|
|
71
|
+
const [lastChange, setLastChange] = useState('No client change yet');
|
|
72
|
+
const [transitionStyle, setTransitionStyle] = useState<ThemeTransitionStyle>('crossfade');
|
|
73
|
+
const [transitionDuration, setTransitionDuration] = useState(250);
|
|
74
|
+
const transitionConfig = {
|
|
75
|
+
style: transitionStyle,
|
|
76
|
+
durationMs: transitionDuration,
|
|
77
|
+
easing: 'ease-out',
|
|
78
|
+
} satisfies ThemeTransitionConfig;
|
|
79
|
+
const captureThemeChange = useCallback(
|
|
80
|
+
(selected: string | undefined, resolved: string | undefined) => {
|
|
81
|
+
setLastChange(`${selected ?? 'unknown'} → ${resolved ?? 'unknown'}`);
|
|
82
|
+
},
|
|
83
|
+
[],
|
|
84
|
+
);
|
|
85
|
+
useThemeEffect(captureThemeChange);
|
|
86
|
+
|
|
87
|
+
const selectTheme = (selection: ThemeSelection, trigger: HTMLButtonElement) => {
|
|
88
|
+
const bounds = trigger.getBoundingClientRect();
|
|
89
|
+
runThemeTransition(() => setTheme(selection), transitionConfig, {
|
|
90
|
+
x: bounds.left + bounds.width / 2,
|
|
91
|
+
y: bounds.top + bounds.height / 2,
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div>
|
|
97
|
+
<StorySection title='Native transition lab'>
|
|
98
|
+
<div className='grid gap-5 rounded-2xl border border-border bg-card p-5 sm:grid-cols-2'>
|
|
99
|
+
<div>
|
|
100
|
+
<p className='text-sm font-medium'>Effect</p>
|
|
101
|
+
<div className='mt-3 flex flex-wrap gap-2'>
|
|
102
|
+
{transitionStyles.map((option) => (
|
|
103
|
+
<Button
|
|
104
|
+
key={option.value}
|
|
105
|
+
size='sm'
|
|
106
|
+
variant={transitionStyle === option.value ? 'primary' : 'outline'}
|
|
107
|
+
aria-pressed={transitionStyle === option.value}
|
|
108
|
+
onClick={() => setTransitionStyle(option.value)}
|
|
109
|
+
>
|
|
110
|
+
{option.label}
|
|
111
|
+
</Button>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div>
|
|
116
|
+
<p className='text-sm font-medium'>Duration</p>
|
|
117
|
+
<div className='mt-3 flex flex-wrap gap-2'>
|
|
118
|
+
{transitionDurations.map((duration) => (
|
|
119
|
+
<Button
|
|
120
|
+
key={duration}
|
|
121
|
+
size='sm'
|
|
122
|
+
variant={transitionDuration === duration ? 'primary' : 'outline'}
|
|
123
|
+
aria-pressed={transitionDuration === duration}
|
|
124
|
+
onClick={() => setTransitionDuration(duration)}
|
|
125
|
+
>
|
|
126
|
+
{duration} ms
|
|
127
|
+
</Button>
|
|
128
|
+
))}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<p className='text-sm text-muted-foreground'>
|
|
133
|
+
Theme state still belongs to @wrksz/themes. This page only configures the native
|
|
134
|
+
snapshot animation used by the controls below.
|
|
135
|
+
</p>
|
|
136
|
+
</StorySection>
|
|
137
|
+
|
|
138
|
+
<StorySection title='Global typed theme state'>
|
|
139
|
+
<div className='grid gap-3 sm:grid-cols-3'>
|
|
140
|
+
{themeChoices.map((choice) => (
|
|
141
|
+
<Button
|
|
142
|
+
key={choice.value}
|
|
143
|
+
variant={theme === choice.value ? 'primary' : 'outline'}
|
|
144
|
+
aria-pressed={theme === choice.value}
|
|
145
|
+
onClick={(event) => selectTheme(choice.value, event.currentTarget)}
|
|
146
|
+
>
|
|
147
|
+
<choice.icon />
|
|
148
|
+
{choice.label}
|
|
149
|
+
{theme === choice.value ? <IconCheck aria-hidden /> : null}
|
|
150
|
+
</Button>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
<div className='grid gap-3 sm:grid-cols-2 lg:grid-cols-4'>
|
|
154
|
+
{[
|
|
155
|
+
['Selected', hydrated ? (theme ?? 'unknown') : 'hydrating'],
|
|
156
|
+
['Resolved', hydrated ? (resolvedTheme ?? 'unknown') : 'hydrating'],
|
|
157
|
+
['Operating system', hydrated ? (systemTheme ?? 'unknown') : 'hydrating'],
|
|
158
|
+
['Server cookie', serverTheme],
|
|
159
|
+
].map(([label, value]) => (
|
|
160
|
+
<Card
|
|
161
|
+
key={label}
|
|
162
|
+
data-testid={`theme-state-${label.toLowerCase().replaceAll(' ', '-')}`}
|
|
163
|
+
>
|
|
164
|
+
<CardHeader>
|
|
165
|
+
<CardTitle className='text-sm text-muted-foreground'>{label}</CardTitle>
|
|
166
|
+
</CardHeader>
|
|
167
|
+
<CardContent className='font-medium'>{value}</CardContent>
|
|
168
|
+
</Card>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
<p className='text-sm text-muted-foreground'>
|
|
172
|
+
{surfaceLabel} · Last observed change: {lastChange}
|
|
173
|
+
</p>
|
|
174
|
+
</StorySection>
|
|
175
|
+
|
|
176
|
+
<StorySection title='Theme-aware media'>
|
|
177
|
+
<ThemedImage
|
|
178
|
+
src={themeImages}
|
|
179
|
+
alt='Application shell selected by the resolved theme'
|
|
180
|
+
width={1280}
|
|
181
|
+
height={720}
|
|
182
|
+
className='w-full rounded-2xl border border-border'
|
|
183
|
+
/>
|
|
184
|
+
</StorySection>
|
|
185
|
+
|
|
186
|
+
<StorySection title='Independent scoped themes'>
|
|
187
|
+
<div className='grid gap-4 md:grid-cols-2'>
|
|
188
|
+
<ClientThemeProvider<AppTheme>
|
|
189
|
+
themes={appThemes}
|
|
190
|
+
forcedTheme='light'
|
|
191
|
+
target='#theme-scope-light'
|
|
192
|
+
storage='none'
|
|
193
|
+
>
|
|
194
|
+
<ScopedThemeCard theme='light' />
|
|
195
|
+
</ClientThemeProvider>
|
|
196
|
+
<ClientThemeProvider<AppTheme>
|
|
197
|
+
themes={appThemes}
|
|
198
|
+
forcedTheme='dark'
|
|
199
|
+
target='#theme-scope-dark'
|
|
200
|
+
storage='none'
|
|
201
|
+
>
|
|
202
|
+
<ScopedThemeCard theme='dark' />
|
|
203
|
+
</ClientThemeProvider>
|
|
204
|
+
</div>
|
|
205
|
+
</StorySection>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getTheme } from '@wrksz/themes/next';
|
|
2
|
+
import { appThemes, themeStorageKey } from '@/theme/config';
|
|
3
|
+
import { ThemeStoryClient } from './theme-story-client';
|
|
4
|
+
|
|
5
|
+
export async function ThemeStory() {
|
|
6
|
+
const serverTheme = await getTheme({
|
|
7
|
+
themes: appThemes,
|
|
8
|
+
defaultTheme: 'system',
|
|
9
|
+
storageKey: themeStorageKey,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return <ThemeStoryClient serverTheme={serverTheme} />;
|
|
13
|
+
}
|