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
package/index.js
CHANGED
|
@@ -20,18 +20,65 @@ const TEMPLATES = ["template", "template2", "template3"];
|
|
|
20
20
|
|
|
21
21
|
// 🧩 argv parse — `name` positional + `--template/-t <val>` + `--help/--version`
|
|
22
22
|
const parseArgs = (argv) => {
|
|
23
|
-
|
|
23
|
+
// storybook: undefined = soruş, false = --no-storybook ilə açıq imtina
|
|
24
|
+
const args = {
|
|
25
|
+
_: [],
|
|
26
|
+
template: undefined,
|
|
27
|
+
help: false,
|
|
28
|
+
version: false,
|
|
29
|
+
storybook: undefined,
|
|
30
|
+
};
|
|
24
31
|
for (let i = 0; i < argv.length; i++) {
|
|
25
32
|
const a = argv[i];
|
|
26
33
|
if (a === "--help" || a === "-h") args.help = true;
|
|
27
34
|
else if (a === "--version" || a === "-v") args.version = true;
|
|
28
35
|
else if (a === "--template" || a === "-t") args.template = argv[++i];
|
|
29
36
|
else if (a.startsWith("--template=")) args.template = a.split("=")[1];
|
|
37
|
+
else if (a === "--no-storybook") args.storybook = false;
|
|
38
|
+
else if (a === "--storybook") args.storybook = true;
|
|
30
39
|
else if (!a.startsWith("-")) args._.push(a);
|
|
31
40
|
}
|
|
32
41
|
return args;
|
|
33
42
|
};
|
|
34
43
|
|
|
44
|
+
// 🧩 Storybook-u target layihədən tamamilə çıxarır (user istəməyəndə).
|
|
45
|
+
// Yalnız `template`-də Storybook var. Strip mexaniki: config qovluğu,
|
|
46
|
+
// bütün *.stories.* faylları, package.json-dakı dep + skriptlər.
|
|
47
|
+
const stripStorybook = (targetPath) => {
|
|
48
|
+
// .storybook config qovluğu
|
|
49
|
+
fs.rmSync(path.join(targetPath, ".storybook"), {
|
|
50
|
+
recursive: true,
|
|
51
|
+
force: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// src altındakı bütün story fayllarını rekursiv sil
|
|
55
|
+
const walk = (dir) => {
|
|
56
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
57
|
+
const full = path.join(dir, entry.name);
|
|
58
|
+
if (entry.isDirectory()) walk(full);
|
|
59
|
+
else if (/\.stories\.(t|j)sx?$/.test(entry.name)) fs.rmSync(full);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const srcDir = path.join(targetPath, "src");
|
|
63
|
+
if (fs.existsSync(srcDir)) walk(srcDir);
|
|
64
|
+
|
|
65
|
+
// package.json-dan storybook dep + skriptlərini təmizlə
|
|
66
|
+
const pkgPath = path.join(targetPath, "package.json");
|
|
67
|
+
try {
|
|
68
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
69
|
+
delete pkg.scripts?.storybook;
|
|
70
|
+
delete pkg.scripts?.["build-storybook"];
|
|
71
|
+
for (const dep of Object.keys(pkg.devDependencies || {})) {
|
|
72
|
+
if (dep === "storybook" || dep.startsWith("@storybook/")) {
|
|
73
|
+
delete pkg.devDependencies[dep];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
77
|
+
} catch {
|
|
78
|
+
// package.json oxunmadı — keç
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
35
82
|
const printHelp = () => {
|
|
36
83
|
console.log(`
|
|
37
84
|
${cyan("create-reactivite")} — scaffold React / Next.js / Rspack apps
|
|
@@ -41,6 +88,7 @@ const printHelp = () => {
|
|
|
41
88
|
|
|
42
89
|
${dim("Options:")}
|
|
43
90
|
-t, --template <name> ${TEMPLATES.join(" | ")}
|
|
91
|
+
--no-storybook Skip Storybook (template only)
|
|
44
92
|
-h, --help Show this help
|
|
45
93
|
-v, --version Show version
|
|
46
94
|
|
|
@@ -148,7 +196,7 @@ const printBanner = () => {
|
|
|
148
196
|
value: "template2",
|
|
149
197
|
},
|
|
150
198
|
{
|
|
151
|
-
title: "Rspack (
|
|
199
|
+
title: "Rspack (Tailwind v4, shadcn/ui, React Router, Matrix theme)",
|
|
152
200
|
value: "template3",
|
|
153
201
|
},
|
|
154
202
|
],
|
|
@@ -162,6 +210,27 @@ const printBanner = () => {
|
|
|
162
210
|
}
|
|
163
211
|
}
|
|
164
212
|
|
|
213
|
+
// 🧩 0a. Storybook — yalnız `template`-də mövcuddur. argv ilə qərar
|
|
214
|
+
// verilməyibsə soruş (default: bəli). Digər template-lərdə bu addım yoxdur.
|
|
215
|
+
let wantStorybook = true;
|
|
216
|
+
if (template === "template") {
|
|
217
|
+
if (argv.storybook !== undefined) {
|
|
218
|
+
wantStorybook = argv.storybook;
|
|
219
|
+
} else {
|
|
220
|
+
const res = await prompts({
|
|
221
|
+
type: "confirm",
|
|
222
|
+
name: "sb",
|
|
223
|
+
message: "Include Storybook (UI library workbench + stories)?",
|
|
224
|
+
initial: true,
|
|
225
|
+
});
|
|
226
|
+
if (res.sb === undefined) {
|
|
227
|
+
console.log("❌ Operation cancelled.");
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
wantStorybook = res.sb;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
165
234
|
// 🧩 1. Target folderı müəyyən edirik
|
|
166
235
|
const isCurrentDir = projectName === "." || projectName === "./";
|
|
167
236
|
const targetPath = isCurrentDir
|
|
@@ -205,6 +274,12 @@ const printBanner = () => {
|
|
|
205
274
|
fs.renameSync(gitignoreSrc, path.join(targetPath, ".gitignore"));
|
|
206
275
|
}
|
|
207
276
|
|
|
277
|
+
// Storybook istənilmirsə — config, story faylları və dep-ləri çıxar
|
|
278
|
+
if (template === "template" && !wantStorybook) {
|
|
279
|
+
console.log("🧹 Removing Storybook...");
|
|
280
|
+
stripStorybook(targetPath);
|
|
281
|
+
}
|
|
282
|
+
|
|
208
283
|
// 🧩 3a. Layihənin adını target package.json-a yazırıq
|
|
209
284
|
// (template-dən gələn ad `template`/`template2`/`template3` olur)
|
|
210
285
|
const pkgPath = path.join(targetPath, "package.json");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-reactivite",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "⚡ Scaffold modern frontend projects in seconds — pick a template: React + Vite
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "⚡ Scaffold modern frontend projects in seconds — pick a template: React + Vite, Next.js 16 (App Router), or Rspack. All ship Tailwind v4, shadcn/ui, TypeScript and a clean, production-ready structure. The Next.js template adds i18n, TanStack Query, axios/orval, Zustand, husky and Vitest. Zero setup hassle.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-reactivite": "./index.js"
|
|
7
7
|
},
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"keywords": [
|
|
30
30
|
"react",
|
|
31
31
|
"vite",
|
|
32
|
+
"nextjs",
|
|
33
|
+
"rspack",
|
|
32
34
|
"tailwind",
|
|
33
35
|
"tailwindcss",
|
|
34
36
|
"shadcn",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { StorybookConfig } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Storybook is OPTIONAL. The generator (create-reactivite) asks whether
|
|
5
|
+
* to keep it; if you said no, this folder, the *.stories.tsx files and
|
|
6
|
+
* the storybook devDependencies/scripts were stripped out.
|
|
7
|
+
*
|
|
8
|
+
* Stories live next to the components in src/. The @ alias and Tailwind
|
|
9
|
+
* are inherited from vite.config.ts automatically.
|
|
10
|
+
*/
|
|
11
|
+
const config: StorybookConfig = {
|
|
12
|
+
stories: ["../src/**/*.stories.@(ts|tsx)"],
|
|
13
|
+
addons: ["@storybook/addon-docs", "@storybook/addon-a11y"],
|
|
14
|
+
framework: {
|
|
15
|
+
name: "@storybook/react-vite",
|
|
16
|
+
options: {},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default config;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { Decorator, Preview } from "@storybook/react-vite";
|
|
3
|
+
|
|
4
|
+
import "../src/global.css";
|
|
5
|
+
|
|
6
|
+
/** The three UI-library themes, exposed in the Storybook toolbar. */
|
|
7
|
+
const THEMES = [
|
|
8
|
+
{ value: "minimal", title: "Minimal", left: "⬜" },
|
|
9
|
+
{ value: "brutalist", title: "Brutalist", left: "🟨" },
|
|
10
|
+
{ value: "aurora", title: "Aurora", left: "🟪" },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Applies the selected theme by setting `data-theme` on both the
|
|
15
|
+
* <html> element (so the iframe body background matches) and the story
|
|
16
|
+
* wrapper (so the rendered subtree picks up the CSS vars).
|
|
17
|
+
*/
|
|
18
|
+
const withTheme: Decorator = (Story, context) => {
|
|
19
|
+
const theme = (context.globals.theme as string) ?? "minimal";
|
|
20
|
+
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
document.documentElement.setAttribute("data-theme", theme);
|
|
23
|
+
}, [theme]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
data-theme={theme}
|
|
28
|
+
className="bg-background text-foreground flex min-h-screen items-center justify-center p-8"
|
|
29
|
+
>
|
|
30
|
+
<Story />
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const preview: Preview = {
|
|
36
|
+
parameters: {
|
|
37
|
+
layout: "fullscreen",
|
|
38
|
+
controls: { expanded: true },
|
|
39
|
+
},
|
|
40
|
+
globalTypes: {
|
|
41
|
+
theme: {
|
|
42
|
+
description: "UI library theme",
|
|
43
|
+
defaultValue: "minimal",
|
|
44
|
+
toolbar: {
|
|
45
|
+
title: "Theme",
|
|
46
|
+
icon: "paintbrush",
|
|
47
|
+
items: THEMES,
|
|
48
|
+
dynamicTitle: true,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
decorators: [withTheme],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default preview;
|
package/template/_gitignore
CHANGED
package/template/index.html
CHANGED
package/template/package.json
CHANGED
|
@@ -7,47 +7,53 @@
|
|
|
7
7
|
"dev": "vite",
|
|
8
8
|
"build": "tsc -b && vite build",
|
|
9
9
|
"lint": "eslint .",
|
|
10
|
-
"preview": "vite preview"
|
|
10
|
+
"preview": "vite preview",
|
|
11
|
+
"storybook": "storybook dev -p 6006",
|
|
12
|
+
"build-storybook": "storybook build"
|
|
11
13
|
},
|
|
12
14
|
"dependencies": {
|
|
13
|
-
"@radix-ui/react-accordion": "^1.2.
|
|
14
|
-
"@radix-ui/react-avatar": "^1.
|
|
15
|
-
"@radix-ui/react-checkbox": "^1.3.
|
|
16
|
-
"@radix-ui/react-collapsible": "^1.1.
|
|
17
|
-
"@radix-ui/react-dialog": "^1.1.
|
|
18
|
-
"@radix-ui/react-select": "^2.3.
|
|
19
|
-
"@radix-ui/react-separator": "^1.1.
|
|
20
|
-
"@radix-ui/react-slot": "^1.
|
|
21
|
-
"@radix-ui/react-toggle": "^1.1.
|
|
22
|
-
"@radix-ui/react-tooltip": "^1.2.
|
|
23
|
-
"@tailwindcss/vite": "^4.3.
|
|
15
|
+
"@radix-ui/react-accordion": "^1.2.14",
|
|
16
|
+
"@radix-ui/react-avatar": "^1.2.0",
|
|
17
|
+
"@radix-ui/react-checkbox": "^1.3.5",
|
|
18
|
+
"@radix-ui/react-collapsible": "^1.1.14",
|
|
19
|
+
"@radix-ui/react-dialog": "^1.1.17",
|
|
20
|
+
"@radix-ui/react-select": "^2.3.1",
|
|
21
|
+
"@radix-ui/react-separator": "^1.1.10",
|
|
22
|
+
"@radix-ui/react-slot": "^1.3.0",
|
|
23
|
+
"@radix-ui/react-toggle": "^1.1.12",
|
|
24
|
+
"@radix-ui/react-tooltip": "^1.2.10",
|
|
25
|
+
"@tailwindcss/vite": "^4.3.1",
|
|
24
26
|
"class-variance-authority": "^0.7.1",
|
|
25
27
|
"clsx": "^2.1.1",
|
|
26
28
|
"date-fns": "^4.4.0",
|
|
27
|
-
"lucide-react": "^1.
|
|
29
|
+
"lucide-react": "^1.21.0",
|
|
28
30
|
"next-themes": "^0.4.6",
|
|
29
31
|
"react": "^19.2.7",
|
|
30
32
|
"react-day-picker": "^10.0.1",
|
|
31
33
|
"react-dom": "^19.2.7",
|
|
32
|
-
"react-router": "^
|
|
33
|
-
"recharts": "^3.
|
|
34
|
+
"react-router": "^8.0.1",
|
|
35
|
+
"recharts": "^3.9.0",
|
|
34
36
|
"sonner": "^2.0.7",
|
|
35
37
|
"tailwind-merge": "^3.6.0",
|
|
36
|
-
"tailwindcss": "^4.3.
|
|
38
|
+
"tailwindcss": "^4.3.1"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
39
41
|
"@eslint/js": "^10.0.1",
|
|
40
|
-
"@
|
|
42
|
+
"@storybook/addon-a11y": "^10.4.6",
|
|
43
|
+
"@storybook/addon-docs": "^10.4.6",
|
|
44
|
+
"@storybook/react-vite": "^10.4.6",
|
|
45
|
+
"storybook": "^10.4.6",
|
|
46
|
+
"@types/node": "^26.0.0",
|
|
41
47
|
"@types/react": "^19.2.17",
|
|
42
48
|
"@types/react-dom": "^19.2.3",
|
|
43
|
-
"@vitejs/plugin-react": "^6.0.
|
|
44
|
-
"eslint": "^10.
|
|
49
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
50
|
+
"eslint": "^10.5.0",
|
|
45
51
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
46
|
-
"eslint-plugin-react-refresh": "^0.5.
|
|
47
|
-
"globals": "^17.
|
|
52
|
+
"eslint-plugin-react-refresh": "^0.5.3",
|
|
53
|
+
"globals": "^17.7.0",
|
|
48
54
|
"tw-animate-css": "^1.4.0",
|
|
49
55
|
"typescript": "~6.0.3",
|
|
50
|
-
"typescript-eslint": "^8.
|
|
51
|
-
"vite": "^8.0
|
|
56
|
+
"typescript-eslint": "^8.62.0",
|
|
57
|
+
"vite": "^8.1.0"
|
|
52
58
|
}
|
|
53
59
|
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
const links = [
|
|
2
|
-
{ label: "GitHub", href: "https://github.com/javidselimov" },
|
|
3
|
-
{ label: "LinkedIn", href: "https://www.linkedin.com/in/javidsalim/" },
|
|
4
|
-
{ label: "npm", href: "https://www.npmjs.com/~ubuligan" },
|
|
5
|
-
];
|
|
6
|
-
|
|
7
|
-
export function AuthorCredit() {
|
|
8
|
-
return (
|
|
9
|
-
<div className="fixed bottom-4 right-4 z-50 flex items-center gap-3 rounded-full border bg-background/80 px-4 py-2 text-sm shadow-lg backdrop-blur">
|
|
10
|
-
<span className="font-medium">Javid Salimov</span>
|
|
11
|
-
<span className="text-muted-foreground">·</span>
|
|
12
|
-
{links.map((l) => (
|
|
13
|
-
<a
|
|
14
|
-
key={l.label}
|
|
15
|
-
href={l.href}
|
|
16
|
-
target="_blank"
|
|
17
|
-
rel="noreferrer"
|
|
18
|
-
className="text-muted-foreground transition-colors hover:text-foreground"
|
|
19
|
-
>
|
|
20
|
-
{l.label}
|
|
21
|
-
</a>
|
|
22
|
-
))}
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
1
|
+
const links = [
|
|
2
|
+
{ label: "GitHub", href: "https://github.com/javidselimov" },
|
|
3
|
+
{ label: "LinkedIn", href: "https://www.linkedin.com/in/javidsalim/" },
|
|
4
|
+
{ label: "npm", href: "https://www.npmjs.com/~ubuligan" },
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
export function AuthorCredit() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="fixed bottom-4 right-4 z-50 flex items-center gap-3 rounded-full border bg-background/80 px-4 py-2 text-sm shadow-lg backdrop-blur">
|
|
10
|
+
<span className="font-medium">Javid Salimov</span>
|
|
11
|
+
<span className="text-muted-foreground">·</span>
|
|
12
|
+
{links.map((l) => (
|
|
13
|
+
<a
|
|
14
|
+
key={l.label}
|
|
15
|
+
href={l.href}
|
|
16
|
+
target="_blank"
|
|
17
|
+
rel="noreferrer"
|
|
18
|
+
className="text-muted-foreground transition-colors hover:text-foreground"
|
|
19
|
+
>
|
|
20
|
+
{l.label}
|
|
21
|
+
</a>
|
|
22
|
+
))}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { fn } from "storybook/test";
|
|
3
|
+
|
|
4
|
+
import { AuthForm } from "./auth-form";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "UI Library/AuthForm",
|
|
8
|
+
component: AuthForm,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
args: {
|
|
11
|
+
onSubmit: fn(),
|
|
12
|
+
},
|
|
13
|
+
} satisfies Meta<typeof AuthForm>;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof meta>;
|
|
17
|
+
|
|
18
|
+
export const SignIn: Story = {};
|
|
19
|
+
|
|
20
|
+
export const Custom: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
title: "Log in to Acme",
|
|
23
|
+
description: "Enter your credentials below",
|
|
24
|
+
submitLabel: "Continue",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
5
|
+
import { Checkbox } from "@/components/ui/checkbox"
|
|
6
|
+
import { Input } from "@/components/ui/input"
|
|
7
|
+
import { Separator } from "@/components/ui/separator"
|
|
8
|
+
import {
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
CardDescription,
|
|
12
|
+
CardFooter,
|
|
13
|
+
CardHeader,
|
|
14
|
+
CardTitle,
|
|
15
|
+
} from "@/components/ui/card"
|
|
16
|
+
|
|
17
|
+
export interface AuthFormProps
|
|
18
|
+
extends Omit<React.ComponentProps<typeof Card>, "onSubmit"> {
|
|
19
|
+
title?: string
|
|
20
|
+
description?: string
|
|
21
|
+
submitLabel?: string
|
|
22
|
+
/** Receives the entered email + password. */
|
|
23
|
+
onSubmit?: (data: { email: string; password: string }) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* AuthForm — sign-in card composed from Card + Input + Checkbox +
|
|
28
|
+
* Button + Separator. Self-contained controlled inputs.
|
|
29
|
+
*/
|
|
30
|
+
export function AuthForm({
|
|
31
|
+
title = "Welcome back",
|
|
32
|
+
description = "Sign in to your account to continue",
|
|
33
|
+
submitLabel = "Sign in",
|
|
34
|
+
onSubmit,
|
|
35
|
+
className,
|
|
36
|
+
...props
|
|
37
|
+
}: AuthFormProps) {
|
|
38
|
+
const [email, setEmail] = React.useState("")
|
|
39
|
+
const [password, setPassword] = React.useState("")
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Card className={cn("w-full max-w-sm", className)} {...props}>
|
|
43
|
+
<CardHeader className="text-center">
|
|
44
|
+
<CardTitle className="text-xl">{title}</CardTitle>
|
|
45
|
+
<CardDescription>{description}</CardDescription>
|
|
46
|
+
</CardHeader>
|
|
47
|
+
<CardContent>
|
|
48
|
+
<form
|
|
49
|
+
className="grid gap-4"
|
|
50
|
+
onSubmit={(e) => {
|
|
51
|
+
e.preventDefault()
|
|
52
|
+
onSubmit?.({ email, password })
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<div className="grid gap-2">
|
|
56
|
+
<label htmlFor="auth-email" className="text-sm font-medium">
|
|
57
|
+
Email
|
|
58
|
+
</label>
|
|
59
|
+
<Input
|
|
60
|
+
id="auth-email"
|
|
61
|
+
type="email"
|
|
62
|
+
placeholder="you@example.com"
|
|
63
|
+
value={email}
|
|
64
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
65
|
+
autoComplete="email"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
<div className="grid gap-2">
|
|
69
|
+
<div className="flex items-center justify-between">
|
|
70
|
+
<label htmlFor="auth-pass" className="text-sm font-medium">
|
|
71
|
+
Password
|
|
72
|
+
</label>
|
|
73
|
+
<a
|
|
74
|
+
href="#"
|
|
75
|
+
className="text-muted-foreground text-xs underline-offset-4 hover:underline"
|
|
76
|
+
>
|
|
77
|
+
Forgot?
|
|
78
|
+
</a>
|
|
79
|
+
</div>
|
|
80
|
+
<Input
|
|
81
|
+
id="auth-pass"
|
|
82
|
+
type="password"
|
|
83
|
+
placeholder="••••••••"
|
|
84
|
+
value={password}
|
|
85
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
86
|
+
autoComplete="current-password"
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
<label className="flex items-center gap-2 text-sm">
|
|
90
|
+
<Checkbox id="auth-remember" defaultChecked />
|
|
91
|
+
Remember me for 30 days
|
|
92
|
+
</label>
|
|
93
|
+
<Button type="submit" className="w-full">
|
|
94
|
+
{submitLabel}
|
|
95
|
+
</Button>
|
|
96
|
+
</form>
|
|
97
|
+
<div className="my-4 flex items-center gap-3">
|
|
98
|
+
<Separator className="flex-1" />
|
|
99
|
+
<span className="text-muted-foreground text-xs">OR</span>
|
|
100
|
+
<Separator className="flex-1" />
|
|
101
|
+
</div>
|
|
102
|
+
<Button variant="outline" className="w-full">
|
|
103
|
+
Continue with GitHub
|
|
104
|
+
</Button>
|
|
105
|
+
</CardContent>
|
|
106
|
+
<CardFooter className="justify-center">
|
|
107
|
+
<span className="text-muted-foreground text-sm">
|
|
108
|
+
No account?{" "}
|
|
109
|
+
<a href="#" className="text-foreground font-medium underline-offset-4 hover:underline">
|
|
110
|
+
Sign up
|
|
111
|
+
</a>
|
|
112
|
+
</span>
|
|
113
|
+
</CardFooter>
|
|
114
|
+
</Card>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { fn } from "storybook/test";
|
|
3
|
+
import { SearchX, FolderPlus } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { EmptyState } from "./empty-state";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "UI Library/EmptyState",
|
|
9
|
+
component: EmptyState,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
args: { onAction: fn() },
|
|
12
|
+
} satisfies Meta<typeof EmptyState>;
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {
|
|
19
|
+
title: "No projects yet",
|
|
20
|
+
description: "Create your first project to get started.",
|
|
21
|
+
actionLabel: "New project",
|
|
22
|
+
icon: FolderPlus,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const NoResults: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
title: "No results found",
|
|
29
|
+
description: "Try adjusting your search or filters.",
|
|
30
|
+
icon: SearchX,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { type LucideIcon, Inbox } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import { Button } from "@/components/ui/button"
|
|
6
|
+
|
|
7
|
+
export interface EmptyStateProps extends React.ComponentProps<"div"> {
|
|
8
|
+
icon?: LucideIcon
|
|
9
|
+
title: string
|
|
10
|
+
description?: string
|
|
11
|
+
actionLabel?: string
|
|
12
|
+
onAction?: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* EmptyState — zero-data placeholder composed from a dashed frame +
|
|
17
|
+
* icon chip + Button. Used for empty lists, tables, search results.
|
|
18
|
+
*/
|
|
19
|
+
export function EmptyState({
|
|
20
|
+
icon: Icon = Inbox,
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
|
+
actionLabel,
|
|
24
|
+
onAction,
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}: EmptyStateProps) {
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
className={cn(
|
|
31
|
+
"border-border bg-card flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed px-6 py-14 text-center",
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
<span className="bg-muted text-muted-foreground flex size-12 items-center justify-center rounded-full">
|
|
37
|
+
<Icon className="size-6" />
|
|
38
|
+
</span>
|
|
39
|
+
<div className="space-y-1">
|
|
40
|
+
<h3 className="text-base font-semibold">{title}</h3>
|
|
41
|
+
{description && (
|
|
42
|
+
<p className="text-muted-foreground mx-auto max-w-xs text-sm">
|
|
43
|
+
{description}
|
|
44
|
+
</p>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
{actionLabel && (
|
|
48
|
+
<Button onClick={onAction} className="mt-1">
|
|
49
|
+
{actionLabel}
|
|
50
|
+
</Button>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { Zap, ShieldCheck, Sparkles } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { FeatureCard } from "./feature-card";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "UI Library/FeatureCard",
|
|
8
|
+
component: FeatureCard,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
} satisfies Meta<typeof FeatureCard>;
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof meta>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
args: {
|
|
17
|
+
icon: Zap,
|
|
18
|
+
title: "Blazing fast",
|
|
19
|
+
description:
|
|
20
|
+
"Vite + SWC give you instant HMR and sub-second cold starts out of the box.",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const Grid: Story = {
|
|
25
|
+
args: { icon: Zap, title: "Blazing fast", description: "..." },
|
|
26
|
+
render: () => (
|
|
27
|
+
<div className="grid w-full max-w-4xl grid-cols-1 gap-4 sm:grid-cols-3">
|
|
28
|
+
<FeatureCard
|
|
29
|
+
icon={Zap}
|
|
30
|
+
title="Blazing fast"
|
|
31
|
+
description="Instant HMR and sub-second cold starts out of the box."
|
|
32
|
+
/>
|
|
33
|
+
<FeatureCard
|
|
34
|
+
icon={ShieldCheck}
|
|
35
|
+
title="Type safe"
|
|
36
|
+
description="End-to-end TypeScript with strict mode enabled by default."
|
|
37
|
+
/>
|
|
38
|
+
<FeatureCard
|
|
39
|
+
icon={Sparkles}
|
|
40
|
+
title="Themeable"
|
|
41
|
+
description="Swap the whole look with a single data-theme attribute."
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
),
|
|
45
|
+
};
|