create-reactivite 1.4.0 → 1.7.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/README.md +326 -290
- package/index.js +77 -2
- package/package.json +4 -2
- package/template/.storybook/main.ts +20 -0
- package/template/.storybook/preview.tsx +55 -0
- package/template/_gitignore +3 -0
- package/template/index.html +1 -1
- package/template/package.json +29 -23
- package/template/src/components/author-credit.tsx +25 -25
- package/template/src/components/ui-lib/auth-form.stories.tsx +26 -0
- package/template/src/components/ui-lib/auth-form.tsx +116 -0
- package/template/src/components/ui-lib/empty-state.stories.tsx +32 -0
- package/template/src/components/ui-lib/empty-state.tsx +54 -0
- package/template/src/components/ui-lib/feature-card.stories.tsx +45 -0
- package/template/src/components/ui-lib/feature-card.tsx +52 -0
- package/template/src/components/ui-lib/index.ts +13 -0
- package/template/src/components/ui-lib/page-header.stories.tsx +49 -0
- package/template/src/components/ui-lib/page-header.tsx +46 -0
- package/template/src/components/ui-lib/pricing-card.stories.tsx +58 -0
- package/template/src/components/ui-lib/pricing-card.tsx +86 -0
- package/template/src/components/ui-lib/stat-card.stories.tsx +55 -0
- package/template/src/components/ui-lib/stat-card.tsx +73 -0
- package/template/src/components/ui-lib/themes.stories.tsx +68 -0
- package/template/src/global.css +122 -0
- package/template2/.env.example +8 -8
- package/template2/.husky/pre-commit +4 -4
- package/template2/.prettierrc +5 -5
- package/template2/README.md +73 -73
- package/template2/__tests__/example.test.ts +20 -20
- package/template2/_gitignore +37 -37
- package/template2/app/[locale]/(private)/dashboard/page.tsx +52 -52
- package/template2/app/[locale]/(public)/login/page.tsx +83 -83
- package/template2/app/[locale]/layout.tsx +58 -58
- package/template2/app/[locale]/locales.ts +10 -10
- package/template2/app/[locale]/page.tsx +38 -38
- package/template2/app/api/clear-session/route.ts +10 -10
- package/template2/app/globals.css +127 -127
- package/template2/app/layout.tsx +7 -7
- package/template2/app/page.tsx +6 -6
- package/template2/components/AuthEventListener.tsx +22 -22
- package/template2/components/author-credit.tsx +25 -25
- package/template2/components/theme-provider.tsx +78 -78
- package/template2/components/ui/button.tsx +60 -60
- package/template2/components/ui/card.tsx +92 -92
- package/template2/components/ui/input.tsx +21 -21
- package/template2/components/ui/label.tsx +24 -24
- package/template2/components/ui/sonner.tsx +40 -40
- package/template2/components.json +22 -22
- package/template2/config/constants.ts +7 -7
- package/template2/config/env.ts +5 -5
- package/template2/contexts/translation-context.tsx +70 -70
- package/template2/eslint.config.mjs +18 -18
- package/template2/hoc/provider.tsx +27 -27
- package/template2/lib/paramsSerializer.ts +40 -40
- package/template2/lib/utils.ts +6 -6
- package/template2/locales/az.json +20 -20
- package/template2/locales/en.json +20 -20
- package/template2/next-env.d.ts +1 -1
- package/template2/next.config.ts +17 -17
- package/template2/orval.config.ts +66 -66
- package/template2/package.json +62 -62
- package/template2/postcss.config.mjs +7 -7
- package/template2/scripts/fix-generated-types.mjs +13 -13
- package/template2/services/generated/.gitkeep +2 -2
- package/template2/services/httpClient/httpClient.ts +70 -70
- package/template2/services/httpClient/orvalMutator.ts +10 -10
- package/template2/store/example-store.tsx +16 -16
- package/template2/store/user-store.tsx +29 -29
- package/template2/testing/msw/handlers/index.ts +6 -6
- package/template2/testing/msw/server.ts +4 -4
- package/template2/tsconfig.json +34 -34
- package/template2/vitest.config.ts +17 -17
- package/template2/vitest.setup.ts +7 -7
- package/template3/README.md +34 -34
- package/template3/_gitignore +16 -16
- package/template3/components.json +21 -0
- package/template3/index.html +8 -2
- package/template3/package.json +48 -22
- package/template3/postcss.config.mjs +5 -0
- package/template3/rspack.config.mjs +59 -51
- package/template3/src/App.tsx +16 -11
- package/template3/src/components/author-credit.tsx +42 -42
- package/template3/src/components/layout.tsx +59 -0
- package/template3/src/components/matrix-rain.tsx +71 -0
- package/template3/src/components/ui/accordion.tsx +64 -0
- package/template3/src/components/ui/alert-dialog.tsx +196 -0
- package/template3/src/components/ui/alert.tsx +66 -0
- package/template3/src/components/ui/aspect-ratio.tsx +11 -0
- package/template3/src/components/ui/avatar.tsx +107 -0
- package/template3/src/components/ui/badge.tsx +48 -0
- package/template3/src/components/ui/breadcrumb.tsx +109 -0
- package/template3/src/components/ui/button-group.tsx +83 -0
- package/template3/src/components/ui/button.tsx +64 -0
- package/template3/src/components/ui/calendar.tsx +218 -0
- package/template3/src/components/ui/card.tsx +92 -0
- package/template3/src/components/ui/carousel.tsx +241 -0
- package/template3/src/components/ui/chart.tsx +372 -0
- package/template3/src/components/ui/checkbox.tsx +32 -0
- package/template3/src/components/ui/collapsible.tsx +31 -0
- package/template3/src/components/ui/combobox.tsx +310 -0
- package/template3/src/components/ui/command.tsx +184 -0
- package/template3/src/components/ui/context-menu.tsx +252 -0
- package/template3/src/components/ui/dialog.tsx +156 -0
- package/template3/src/components/ui/direction.tsx +22 -0
- package/template3/src/components/ui/drawer.tsx +133 -0
- package/template3/src/components/ui/dropdown-menu.tsx +257 -0
- package/template3/src/components/ui/empty.tsx +104 -0
- package/template3/src/components/ui/field.tsx +248 -0
- package/template3/src/components/ui/form.tsx +165 -0
- package/template3/src/components/ui/hover-card.tsx +42 -0
- package/template3/src/components/ui/input-group.tsx +168 -0
- package/template3/src/components/ui/input-otp.tsx +77 -0
- package/template3/src/components/ui/input.tsx +21 -0
- package/template3/src/components/ui/item.tsx +193 -0
- package/template3/src/components/ui/kbd.tsx +28 -0
- package/template3/src/components/ui/label.tsx +22 -0
- package/template3/src/components/ui/menubar.tsx +276 -0
- package/template3/src/components/ui/native-select.tsx +62 -0
- package/template3/src/components/ui/navigation-menu.tsx +168 -0
- package/template3/src/components/ui/pagination.tsx +127 -0
- package/template3/src/components/ui/popover.tsx +87 -0
- package/template3/src/components/ui/progress.tsx +31 -0
- package/template3/src/components/ui/radio-group.tsx +43 -0
- package/template3/src/components/ui/resizable.tsx +53 -0
- package/template3/src/components/ui/scroll-area.tsx +56 -0
- package/template3/src/components/ui/select.tsx +190 -0
- package/template3/src/components/ui/separator.tsx +26 -0
- package/template3/src/components/ui/sheet.tsx +143 -0
- package/template3/src/components/ui/sidebar.tsx +724 -0
- package/template3/src/components/ui/skeleton.tsx +13 -0
- package/template3/src/components/ui/slider.tsx +61 -0
- package/template3/src/components/ui/sonner.tsx +40 -0
- package/template3/src/components/ui/spinner.tsx +16 -0
- package/template3/src/components/ui/switch.tsx +33 -0
- package/template3/src/components/ui/table.tsx +116 -0
- package/template3/src/components/ui/tabs.tsx +89 -0
- package/template3/src/components/ui/textarea.tsx +18 -0
- package/template3/src/components/ui/toggle-group.tsx +83 -0
- package/template3/src/components/ui/toggle.tsx +47 -0
- package/template3/src/components/ui/tooltip.tsx +55 -0
- package/template3/src/hooks/use-mobile.ts +19 -0
- package/template3/src/index.css +175 -32
- package/template3/src/lib/utils.ts +6 -0
- package/template3/src/main.tsx +10 -10
- package/template3/src/pages/about.tsx +113 -0
- package/template3/src/pages/contact.tsx +111 -0
- package/template3/src/pages/home.tsx +81 -0
- package/template3/tsconfig.json +24 -20
- package/template/pnpm-lock.yaml +0 -3274
- package/template2/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { type LucideIcon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from "@/components/ui/card"
|
|
12
|
+
|
|
13
|
+
export interface FeatureCardProps extends React.ComponentProps<typeof Card> {
|
|
14
|
+
icon: LucideIcon
|
|
15
|
+
title: string
|
|
16
|
+
description: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* FeatureCard — marketing/feature tile composed from Card.
|
|
21
|
+
* The icon chip uses primary/accent vars so it re-skins per theme.
|
|
22
|
+
*/
|
|
23
|
+
export function FeatureCard({
|
|
24
|
+
icon: Icon,
|
|
25
|
+
title,
|
|
26
|
+
description,
|
|
27
|
+
className,
|
|
28
|
+
...props
|
|
29
|
+
}: FeatureCardProps) {
|
|
30
|
+
return (
|
|
31
|
+
<Card
|
|
32
|
+
className={cn(
|
|
33
|
+
"gap-4 transition-shadow hover:shadow-md",
|
|
34
|
+
className
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
<CardHeader>
|
|
39
|
+
<span className="bg-primary text-primary-foreground mb-2 flex size-11 items-center justify-center rounded-lg">
|
|
40
|
+
<Icon className="size-5" />
|
|
41
|
+
</span>
|
|
42
|
+
<CardTitle>{title}</CardTitle>
|
|
43
|
+
<CardDescription>{description}</CardDescription>
|
|
44
|
+
</CardHeader>
|
|
45
|
+
<CardContent className="text-muted-foreground text-sm">
|
|
46
|
+
<span className="text-foreground font-medium underline-offset-4 hover:underline cursor-pointer">
|
|
47
|
+
Learn more →
|
|
48
|
+
</span>
|
|
49
|
+
</CardContent>
|
|
50
|
+
</Card>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ui-lib — composed components built on top of shadcn/ui primitives
|
|
3
|
+
* (src/components/ui). These are the building blocks meant for use
|
|
4
|
+
* inside the template's pages. Every one is var-driven, so it adapts
|
|
5
|
+
* to the active `data-theme` (minimal | brutalist | aurora) with no
|
|
6
|
+
* per-theme code. See each component's Storybook story for variants.
|
|
7
|
+
*/
|
|
8
|
+
export { StatCard, type StatCardProps } from "./stat-card"
|
|
9
|
+
export { FeatureCard, type FeatureCardProps } from "./feature-card"
|
|
10
|
+
export { PricingCard, type PricingCardProps } from "./pricing-card"
|
|
11
|
+
export { AuthForm, type AuthFormProps } from "./auth-form"
|
|
12
|
+
export { EmptyState, type EmptyStateProps } from "./empty-state"
|
|
13
|
+
export { PageHeader, type PageHeaderProps } from "./page-header"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { Plus, Download } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { PageHeader } from "./page-header";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "UI Library/PageHeader",
|
|
9
|
+
component: PageHeader,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
parameters: { layout: "padded" },
|
|
12
|
+
} satisfies Meta<typeof PageHeader>;
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {
|
|
19
|
+
eyebrow: "Workspace",
|
|
20
|
+
title: "Projects",
|
|
21
|
+
description: "Manage and organize all of your team's projects.",
|
|
22
|
+
},
|
|
23
|
+
render: (args) => (
|
|
24
|
+
<div className="w-full max-w-3xl">
|
|
25
|
+
<PageHeader
|
|
26
|
+
{...args}
|
|
27
|
+
actions={
|
|
28
|
+
<>
|
|
29
|
+
<Button variant="outline">
|
|
30
|
+
<Download /> Export
|
|
31
|
+
</Button>
|
|
32
|
+
<Button>
|
|
33
|
+
<Plus /> New
|
|
34
|
+
</Button>
|
|
35
|
+
</>
|
|
36
|
+
}
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const TitleOnly: Story = {
|
|
43
|
+
args: { title: "Settings" },
|
|
44
|
+
render: (args) => (
|
|
45
|
+
<div className="w-full max-w-3xl">
|
|
46
|
+
<PageHeader {...args} />
|
|
47
|
+
</div>
|
|
48
|
+
),
|
|
49
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
import { Separator } from "@/components/ui/separator"
|
|
5
|
+
|
|
6
|
+
export interface PageHeaderProps extends React.ComponentProps<"div"> {
|
|
7
|
+
title: string
|
|
8
|
+
description?: string
|
|
9
|
+
/** Right-aligned action slot, e.g. buttons. */
|
|
10
|
+
actions?: React.ReactNode
|
|
11
|
+
/** Optional eyebrow / breadcrumb line above the title. */
|
|
12
|
+
eyebrow?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* PageHeader — the title block at the top of a page/section.
|
|
17
|
+
* title + description on the left, actions on the right, then a rule.
|
|
18
|
+
*/
|
|
19
|
+
export function PageHeader({
|
|
20
|
+
title,
|
|
21
|
+
description,
|
|
22
|
+
actions,
|
|
23
|
+
eyebrow,
|
|
24
|
+
className,
|
|
25
|
+
...props
|
|
26
|
+
}: PageHeaderProps) {
|
|
27
|
+
return (
|
|
28
|
+
<div className={cn("space-y-4", className)} {...props}>
|
|
29
|
+
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
|
30
|
+
<div className="space-y-1">
|
|
31
|
+
{eyebrow && (
|
|
32
|
+
<p className="text-muted-foreground text-xs font-medium tracking-wide uppercase">
|
|
33
|
+
{eyebrow}
|
|
34
|
+
</p>
|
|
35
|
+
)}
|
|
36
|
+
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
|
|
37
|
+
{description && (
|
|
38
|
+
<p className="text-muted-foreground text-sm">{description}</p>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
{actions && <div className="flex items-center gap-2">{actions}</div>}
|
|
42
|
+
</div>
|
|
43
|
+
<Separator />
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { PricingCard } from "./pricing-card";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "UI Library/PricingCard",
|
|
7
|
+
component: PricingCard,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
} satisfies Meta<typeof PricingCard>;
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
args: {
|
|
16
|
+
plan: "Pro",
|
|
17
|
+
price: "$29",
|
|
18
|
+
description: "For growing teams that need more power.",
|
|
19
|
+
features: ["Unlimited projects", "Priority support", "Advanced analytics", "Custom domains"],
|
|
20
|
+
featured: false,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const Featured: Story = {
|
|
25
|
+
args: {
|
|
26
|
+
...Default.args,
|
|
27
|
+
featured: true,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Tiers: Story = {
|
|
32
|
+
args: { plan: "Pro", price: "$29", features: [] },
|
|
33
|
+
render: () => (
|
|
34
|
+
<div className="grid w-full max-w-4xl grid-cols-1 gap-6 sm:grid-cols-3">
|
|
35
|
+
<PricingCard
|
|
36
|
+
plan="Starter"
|
|
37
|
+
price="$0"
|
|
38
|
+
description="For side projects."
|
|
39
|
+
features={["1 project", "Community support", "1 GB storage"]}
|
|
40
|
+
cta="Start free"
|
|
41
|
+
/>
|
|
42
|
+
<PricingCard
|
|
43
|
+
plan="Pro"
|
|
44
|
+
price="$29"
|
|
45
|
+
description="For growing teams."
|
|
46
|
+
features={["Unlimited projects", "Priority support", "Analytics"]}
|
|
47
|
+
featured
|
|
48
|
+
/>
|
|
49
|
+
<PricingCard
|
|
50
|
+
plan="Enterprise"
|
|
51
|
+
price="$99"
|
|
52
|
+
description="For large orgs."
|
|
53
|
+
features={["SSO + SAML", "Dedicated support", "SLA", "Audit logs"]}
|
|
54
|
+
cta="Contact sales"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
),
|
|
58
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Check } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import { Badge } from "@/components/ui/badge"
|
|
6
|
+
import { Button } from "@/components/ui/button"
|
|
7
|
+
import {
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardFooter,
|
|
12
|
+
CardHeader,
|
|
13
|
+
CardTitle,
|
|
14
|
+
} from "@/components/ui/card"
|
|
15
|
+
|
|
16
|
+
export interface PricingCardProps extends React.ComponentProps<typeof Card> {
|
|
17
|
+
plan: string
|
|
18
|
+
price: string
|
|
19
|
+
period?: string
|
|
20
|
+
description?: string
|
|
21
|
+
features: string[]
|
|
22
|
+
cta?: string
|
|
23
|
+
/** Highlights the card with a ring + badge — the "recommended" plan. */
|
|
24
|
+
featured?: boolean
|
|
25
|
+
onSelect?: () => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* PricingCard — pricing tier composed from Card + Badge + Button + check list.
|
|
30
|
+
*/
|
|
31
|
+
export function PricingCard({
|
|
32
|
+
plan,
|
|
33
|
+
price,
|
|
34
|
+
period = "/mo",
|
|
35
|
+
description,
|
|
36
|
+
features,
|
|
37
|
+
cta = "Get started",
|
|
38
|
+
featured = false,
|
|
39
|
+
onSelect,
|
|
40
|
+
className,
|
|
41
|
+
...props
|
|
42
|
+
}: PricingCardProps) {
|
|
43
|
+
return (
|
|
44
|
+
<Card
|
|
45
|
+
className={cn(
|
|
46
|
+
"relative gap-6",
|
|
47
|
+
featured && "ring-primary ring-2",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
{featured && (
|
|
53
|
+
<Badge className="absolute -top-3 left-6">Most popular</Badge>
|
|
54
|
+
)}
|
|
55
|
+
<CardHeader>
|
|
56
|
+
<CardDescription>{plan}</CardDescription>
|
|
57
|
+
<CardTitle className="flex items-baseline gap-1">
|
|
58
|
+
<span className="text-4xl font-bold tracking-tight">{price}</span>
|
|
59
|
+
<span className="text-muted-foreground text-sm font-normal">
|
|
60
|
+
{period}
|
|
61
|
+
</span>
|
|
62
|
+
</CardTitle>
|
|
63
|
+
{description && <CardDescription>{description}</CardDescription>}
|
|
64
|
+
</CardHeader>
|
|
65
|
+
<CardContent>
|
|
66
|
+
<ul className="space-y-2.5 text-sm">
|
|
67
|
+
{features.map((f) => (
|
|
68
|
+
<li key={f} className="flex items-center gap-2">
|
|
69
|
+
<Check className="text-primary size-4 shrink-0" />
|
|
70
|
+
<span>{f}</span>
|
|
71
|
+
</li>
|
|
72
|
+
))}
|
|
73
|
+
</ul>
|
|
74
|
+
</CardContent>
|
|
75
|
+
<CardFooter>
|
|
76
|
+
<Button
|
|
77
|
+
className="w-full"
|
|
78
|
+
variant={featured ? "default" : "outline"}
|
|
79
|
+
onClick={onSelect}
|
|
80
|
+
>
|
|
81
|
+
{cta}
|
|
82
|
+
</Button>
|
|
83
|
+
</CardFooter>
|
|
84
|
+
</Card>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { DollarSign, Users, Activity } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { StatCard } from "./stat-card";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "UI Library/StatCard",
|
|
8
|
+
component: StatCard,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
argTypes: {
|
|
11
|
+
delta: { control: { type: "number" } },
|
|
12
|
+
},
|
|
13
|
+
} satisfies Meta<typeof StatCard>;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof meta>;
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
label: "Total revenue",
|
|
21
|
+
value: "$48,120",
|
|
22
|
+
delta: 12.4,
|
|
23
|
+
hint: "vs last month",
|
|
24
|
+
icon: DollarSign,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Negative: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
label: "Churn rate",
|
|
31
|
+
value: "3.8%",
|
|
32
|
+
delta: -2.1,
|
|
33
|
+
hint: "vs last month",
|
|
34
|
+
icon: Activity,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const NoDelta: Story = {
|
|
39
|
+
args: {
|
|
40
|
+
label: "Active users",
|
|
41
|
+
value: "12,840",
|
|
42
|
+
icon: Users,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Grid: Story = {
|
|
47
|
+
args: { label: "Total revenue", value: "$48,120", delta: 12.4, icon: DollarSign },
|
|
48
|
+
render: () => (
|
|
49
|
+
<div className="grid w-full max-w-3xl grid-cols-1 gap-4 sm:grid-cols-3">
|
|
50
|
+
<StatCard label="Revenue" value="$48,120" delta={12.4} icon={DollarSign} />
|
|
51
|
+
<StatCard label="Users" value="12,840" delta={4.2} icon={Users} />
|
|
52
|
+
<StatCard label="Churn" value="3.8%" delta={-2.1} icon={Activity} />
|
|
53
|
+
</div>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { type LucideIcon, TrendingDown, TrendingUp } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import { Badge } from "@/components/ui/badge"
|
|
6
|
+
import {
|
|
7
|
+
Card,
|
|
8
|
+
CardAction,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
} from "@/components/ui/card"
|
|
14
|
+
|
|
15
|
+
export interface StatCardProps extends React.ComponentProps<typeof Card> {
|
|
16
|
+
/** Metric label, e.g. "Total revenue". */
|
|
17
|
+
label: string
|
|
18
|
+
/** Big value, e.g. "$48,120". */
|
|
19
|
+
value: React.ReactNode
|
|
20
|
+
/** Optional period / context line under the value. */
|
|
21
|
+
hint?: string
|
|
22
|
+
/** Signed percentage change. Positive renders up + accent, negative down + destructive. */
|
|
23
|
+
delta?: number
|
|
24
|
+
/** Icon shown top-right. */
|
|
25
|
+
icon?: LucideIcon
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* StatCard — a dashboard KPI tile composed from Card + Badge.
|
|
30
|
+
* Purely var-driven, so it adopts whatever `data-theme` is active.
|
|
31
|
+
*/
|
|
32
|
+
export function StatCard({
|
|
33
|
+
label,
|
|
34
|
+
value,
|
|
35
|
+
hint,
|
|
36
|
+
delta,
|
|
37
|
+
icon: Icon,
|
|
38
|
+
className,
|
|
39
|
+
...props
|
|
40
|
+
}: StatCardProps) {
|
|
41
|
+
const up = (delta ?? 0) >= 0
|
|
42
|
+
return (
|
|
43
|
+
<Card className={cn("gap-3", className)} {...props}>
|
|
44
|
+
<CardHeader>
|
|
45
|
+
<CardDescription>{label}</CardDescription>
|
|
46
|
+
<CardTitle className="text-3xl font-semibold tabular-nums tracking-tight">
|
|
47
|
+
{value}
|
|
48
|
+
</CardTitle>
|
|
49
|
+
{Icon ? (
|
|
50
|
+
<CardAction>
|
|
51
|
+
<span className="bg-muted text-muted-foreground flex size-9 items-center justify-center rounded-md">
|
|
52
|
+
<Icon className="size-4" />
|
|
53
|
+
</span>
|
|
54
|
+
</CardAction>
|
|
55
|
+
) : null}
|
|
56
|
+
</CardHeader>
|
|
57
|
+
{(delta !== undefined || hint) && (
|
|
58
|
+
<CardContent className="flex items-center gap-2">
|
|
59
|
+
{delta !== undefined && (
|
|
60
|
+
<Badge variant={up ? "default" : "destructive"}>
|
|
61
|
+
{up ? <TrendingUp /> : <TrendingDown />}
|
|
62
|
+
{up ? "+" : ""}
|
|
63
|
+
{delta}%
|
|
64
|
+
</Badge>
|
|
65
|
+
)}
|
|
66
|
+
{hint && (
|
|
67
|
+
<span className="text-muted-foreground text-xs">{hint}</span>
|
|
68
|
+
)}
|
|
69
|
+
</CardContent>
|
|
70
|
+
)}
|
|
71
|
+
</Card>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { DollarSign, Zap } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { StatCard } from "./stat-card";
|
|
5
|
+
import { FeatureCard } from "./feature-card";
|
|
6
|
+
import { PricingCard } from "./pricing-card";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { Badge } from "@/components/ui/badge";
|
|
9
|
+
|
|
10
|
+
const THEMES = ["minimal", "brutalist", "aurora"] as const;
|
|
11
|
+
|
|
12
|
+
/** A compact sampler rendered once per theme, side by side. */
|
|
13
|
+
function Sampler() {
|
|
14
|
+
return (
|
|
15
|
+
<div className="grid w-72 gap-4">
|
|
16
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
17
|
+
<Button size="sm">Primary</Button>
|
|
18
|
+
<Button size="sm" variant="outline">
|
|
19
|
+
Outline
|
|
20
|
+
</Button>
|
|
21
|
+
<Badge>New</Badge>
|
|
22
|
+
</div>
|
|
23
|
+
<StatCard label="Revenue" value="$48,120" delta={12.4} icon={DollarSign} />
|
|
24
|
+
<FeatureCard
|
|
25
|
+
icon={Zap}
|
|
26
|
+
title="Themeable"
|
|
27
|
+
description="One attribute re-skins everything."
|
|
28
|
+
/>
|
|
29
|
+
<PricingCard
|
|
30
|
+
plan="Pro"
|
|
31
|
+
price="$29"
|
|
32
|
+
features={["Unlimited", "Support", "Analytics"]}
|
|
33
|
+
featured
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const meta = {
|
|
40
|
+
title: "UI Library/Themes",
|
|
41
|
+
parameters: { layout: "fullscreen" },
|
|
42
|
+
} satisfies Meta;
|
|
43
|
+
|
|
44
|
+
export default meta;
|
|
45
|
+
type Story = StoryObj<typeof meta>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* All three themes at once. Each column hard-sets its own `data-theme`,
|
|
49
|
+
* overriding the toolbar selection — this is the "3 variants" overview.
|
|
50
|
+
*/
|
|
51
|
+
export const AllThemes: Story = {
|
|
52
|
+
render: () => (
|
|
53
|
+
<div className="flex flex-wrap gap-6 p-2">
|
|
54
|
+
{THEMES.map((t) => (
|
|
55
|
+
<div
|
|
56
|
+
key={t}
|
|
57
|
+
data-theme={t}
|
|
58
|
+
className="bg-background text-foreground rounded-xl border p-5"
|
|
59
|
+
>
|
|
60
|
+
<p className="text-muted-foreground mb-4 text-xs font-medium tracking-wide uppercase">
|
|
61
|
+
{t}
|
|
62
|
+
</p>
|
|
63
|
+
<Sampler />
|
|
64
|
+
</div>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
),
|
|
68
|
+
};
|
package/template/src/global.css
CHANGED
|
@@ -74,6 +74,128 @@
|
|
|
74
74
|
--sidebar-ring: oklch(0.439 0 0);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/* ──────────────────────────────────────────────────────────────
|
|
78
|
+
UI LIBRARY THEMES
|
|
79
|
+
3 distinct, var-driven looks. Switch by setting `data-theme` on
|
|
80
|
+
<html>: "minimal" | "brutalist" | "aurora". Every shadcn + ui-lib
|
|
81
|
+
component reads these vars, so themes need ZERO component changes.
|
|
82
|
+
Default theme is set in index.html. The base :root above stays the
|
|
83
|
+
neutral-dark fallback when no data-theme is present.
|
|
84
|
+
────────────────────────────────────────────────────────────── */
|
|
85
|
+
|
|
86
|
+
/* Theme 1 — MINIMAL: light, calm neutrals, soft radius, editorial. */
|
|
87
|
+
[data-theme="minimal"] {
|
|
88
|
+
--background: oklch(1 0 0);
|
|
89
|
+
--foreground: oklch(0.18 0 0);
|
|
90
|
+
--card: oklch(1 0 0);
|
|
91
|
+
--card-foreground: oklch(0.18 0 0);
|
|
92
|
+
--popover: oklch(1 0 0);
|
|
93
|
+
--popover-foreground: oklch(0.18 0 0);
|
|
94
|
+
--primary: oklch(0.21 0 0);
|
|
95
|
+
--primary-foreground: oklch(0.98 0 0);
|
|
96
|
+
--secondary: oklch(0.97 0 0);
|
|
97
|
+
--secondary-foreground: oklch(0.21 0 0);
|
|
98
|
+
--muted: oklch(0.97 0 0);
|
|
99
|
+
--muted-foreground: oklch(0.52 0 0);
|
|
100
|
+
--accent: oklch(0.96 0 0);
|
|
101
|
+
--accent-foreground: oklch(0.21 0 0);
|
|
102
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
103
|
+
--destructive-foreground: oklch(0.98 0 0);
|
|
104
|
+
--border: oklch(0.92 0 0);
|
|
105
|
+
--input: oklch(0.92 0 0);
|
|
106
|
+
--ring: oklch(0.7 0 0);
|
|
107
|
+
--radius: 0.625rem;
|
|
108
|
+
--sidebar: oklch(0.985 0 0);
|
|
109
|
+
--sidebar-foreground: oklch(0.18 0 0);
|
|
110
|
+
--sidebar-primary: oklch(0.21 0 0);
|
|
111
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
112
|
+
--sidebar-accent: oklch(0.96 0 0);
|
|
113
|
+
--sidebar-accent-foreground: oklch(0.21 0 0);
|
|
114
|
+
--sidebar-border: oklch(0.92 0 0);
|
|
115
|
+
--sidebar-ring: oklch(0.7 0 0);
|
|
116
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
117
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
118
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
119
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
120
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Theme 2 — BRUTALIST: stark black/white, acid-yellow accent, 0 radius,
|
|
124
|
+
pure-black borders. High-contrast, raw, intentionally loud. */
|
|
125
|
+
[data-theme="brutalist"] {
|
|
126
|
+
--background: oklch(0.98 0 0);
|
|
127
|
+
--foreground: oklch(0 0 0);
|
|
128
|
+
--card: oklch(1 0 0);
|
|
129
|
+
--card-foreground: oklch(0 0 0);
|
|
130
|
+
--popover: oklch(1 0 0);
|
|
131
|
+
--popover-foreground: oklch(0 0 0);
|
|
132
|
+
--primary: oklch(0 0 0);
|
|
133
|
+
--primary-foreground: oklch(1 0 0);
|
|
134
|
+
--secondary: oklch(0.93 0.19 102);
|
|
135
|
+
--secondary-foreground: oklch(0 0 0);
|
|
136
|
+
--muted: oklch(0.95 0 0);
|
|
137
|
+
--muted-foreground: oklch(0.35 0 0);
|
|
138
|
+
--accent: oklch(0.93 0.19 102);
|
|
139
|
+
--accent-foreground: oklch(0 0 0);
|
|
140
|
+
--destructive: oklch(0.55 0.24 27);
|
|
141
|
+
--destructive-foreground: oklch(1 0 0);
|
|
142
|
+
--border: oklch(0 0 0);
|
|
143
|
+
--input: oklch(0 0 0);
|
|
144
|
+
--ring: oklch(0 0 0);
|
|
145
|
+
--radius: 0rem;
|
|
146
|
+
--sidebar: oklch(1 0 0);
|
|
147
|
+
--sidebar-foreground: oklch(0 0 0);
|
|
148
|
+
--sidebar-primary: oklch(0 0 0);
|
|
149
|
+
--sidebar-primary-foreground: oklch(1 0 0);
|
|
150
|
+
--sidebar-accent: oklch(0.93 0.19 102);
|
|
151
|
+
--sidebar-accent-foreground: oklch(0 0 0);
|
|
152
|
+
--sidebar-border: oklch(0 0 0);
|
|
153
|
+
--sidebar-ring: oklch(0 0 0);
|
|
154
|
+
--chart-1: oklch(0 0 0);
|
|
155
|
+
--chart-2: oklch(0.93 0.19 102);
|
|
156
|
+
--chart-3: oklch(0.55 0.24 27);
|
|
157
|
+
--chart-4: oklch(0.5 0 0);
|
|
158
|
+
--chart-5: oklch(0.75 0 0);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Theme 3 — AURORA: deep violet dark, neon violet/cyan accents,
|
|
162
|
+
generous radius. Glowy, premium, night-mode product feel. */
|
|
163
|
+
[data-theme="aurora"] {
|
|
164
|
+
--background: oklch(0.16 0.02 280);
|
|
165
|
+
--foreground: oklch(0.96 0.01 280);
|
|
166
|
+
--card: oklch(0.2 0.03 280);
|
|
167
|
+
--card-foreground: oklch(0.96 0.01 280);
|
|
168
|
+
--popover: oklch(0.2 0.03 280);
|
|
169
|
+
--popover-foreground: oklch(0.96 0.01 280);
|
|
170
|
+
--primary: oklch(0.7 0.18 295);
|
|
171
|
+
--primary-foreground: oklch(0.16 0.02 280);
|
|
172
|
+
--secondary: oklch(0.28 0.04 280);
|
|
173
|
+
--secondary-foreground: oklch(0.96 0.01 280);
|
|
174
|
+
--muted: oklch(0.26 0.03 280);
|
|
175
|
+
--muted-foreground: oklch(0.72 0.03 280);
|
|
176
|
+
--accent: oklch(0.55 0.16 230);
|
|
177
|
+
--accent-foreground: oklch(0.98 0 0);
|
|
178
|
+
--destructive: oklch(0.62 0.24 18);
|
|
179
|
+
--destructive-foreground: oklch(0.98 0 0);
|
|
180
|
+
--border: oklch(0.32 0.04 280);
|
|
181
|
+
--input: oklch(0.3 0.04 280);
|
|
182
|
+
--ring: oklch(0.7 0.18 295);
|
|
183
|
+
--radius: 1rem;
|
|
184
|
+
--sidebar: oklch(0.18 0.025 280);
|
|
185
|
+
--sidebar-foreground: oklch(0.96 0.01 280);
|
|
186
|
+
--sidebar-primary: oklch(0.7 0.18 295);
|
|
187
|
+
--sidebar-primary-foreground: oklch(0.16 0.02 280);
|
|
188
|
+
--sidebar-accent: oklch(0.28 0.04 280);
|
|
189
|
+
--sidebar-accent-foreground: oklch(0.96 0.01 280);
|
|
190
|
+
--sidebar-border: oklch(0.32 0.04 280);
|
|
191
|
+
--sidebar-ring: oklch(0.7 0.18 295);
|
|
192
|
+
--chart-1: oklch(0.7 0.18 295);
|
|
193
|
+
--chart-2: oklch(0.6 0.16 230);
|
|
194
|
+
--chart-3: oklch(0.75 0.17 160);
|
|
195
|
+
--chart-4: oklch(0.7 0.19 20);
|
|
196
|
+
--chart-5: oklch(0.8 0.16 90);
|
|
197
|
+
}
|
|
198
|
+
|
|
77
199
|
@theme inline {
|
|
78
200
|
--font-sans: var(--font-geist-sans);
|
|
79
201
|
--font-mono: var(--font-geist-mono);
|
package/template2/.env.example
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Public API base URL used by the browser (axios httpClient).
|
|
2
|
-
NEXT_PUBLIC_API_BASE_URL=/api/backend
|
|
3
|
-
|
|
4
|
-
# Server-side backend URL proxied via next.config.ts rewrites.
|
|
5
|
-
API_BASE_URL=http://localhost:8080/api
|
|
6
|
-
|
|
7
|
-
# OpenAPI schema used by orval (npm run generate:api).
|
|
8
|
-
OPENAPI_TARGET=http://localhost:8080/v3/api-docs
|
|
1
|
+
# Public API base URL used by the browser (axios httpClient).
|
|
2
|
+
NEXT_PUBLIC_API_BASE_URL=/api/backend
|
|
3
|
+
|
|
4
|
+
# Server-side backend URL proxied via next.config.ts rewrites.
|
|
5
|
+
API_BASE_URL=http://localhost:8080/api
|
|
6
|
+
|
|
7
|
+
# OpenAPI schema used by orval (npm run generate:api).
|
|
8
|
+
OPENAPI_TARGET=http://localhost:8080/v3/api-docs
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
npx figlet "Nextivite"
|
|
2
|
-
echo "Pre-commit: lint + format + tests"
|
|
3
|
-
|
|
4
|
-
npm run lint && npm run format && npm run test:run
|
|
1
|
+
npx figlet "Nextivite"
|
|
2
|
+
echo "Pre-commit: lint + format + tests"
|
|
3
|
+
|
|
4
|
+
npm run lint && npm run format && npm run test:run
|
package/template2/.prettierrc
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
{
|
|
2
|
-
"singleQuote": true,
|
|
3
|
-
"trailingComma": "all",
|
|
4
|
-
"printWidth": 80
|
|
5
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"singleQuote": true,
|
|
3
|
+
"trailingComma": "all",
|
|
4
|
+
"printWidth": 80
|
|
5
|
+
}
|