shortcut-next 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/README.md +25 -0
- package/bin/quickstart-next.mjs +5 -0
- package/package.json +41 -0
- package/src/run.mjs +130 -0
- package/templates/base/.eslintrc.json +3 -0
- package/templates/base/.prettierrc.js +19 -0
- package/templates/base/.vscode/settings.json +13 -0
- package/templates/base/README.md +1 -0
- package/templates/base/app/favicon.ico +0 -0
- package/templates/base/app/globals.css +2 -0
- package/templates/base/app/layout.tsx +33 -0
- package/templates/base/app/page.tsx +315 -0
- package/templates/base/eslint.config.mjs +16 -0
- package/templates/base/next.config.ts +7 -0
- package/templates/base/package-lock.json +6770 -0
- package/templates/base/package.json +31 -0
- package/templates/base/providers/BaseProvider.tsx +22 -0
- package/templates/base/public/file.svg +1 -0
- package/templates/base/public/globe.svg +1 -0
- package/templates/base/public/next.svg +1 -0
- package/templates/base/public/vercel.svg +1 -0
- package/templates/base/public/window.svg +1 -0
- package/templates/base/tsconfig.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Quickstart Next β‘
|
|
2
|
+
|
|
3
|
+
> Scaffold modern **Next.js 15+ projects** in seconds β with **MUI**, **React Hook Form**, and **TanStack Query** built-in.
|
|
4
|
+
> Optionally add **Tailwind CSS v4** with a single command.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## π Features
|
|
9
|
+
|
|
10
|
+
- **Next.js 15 (App Router)** β modern project structure, ready to go.
|
|
11
|
+
- **MUI (Material UI)** β theming, components, dark mode ready.
|
|
12
|
+
- **React Hook Form** β forms made simple, integrated with MUI inputs.
|
|
13
|
+
- **TanStack Query (React Query)** β powerful data fetching and caching.
|
|
14
|
+
- **Tailwind CSS v4** (optional) β utility-first styling, zero config.
|
|
15
|
+
- **TypeScript by default** β strict mode enabled.
|
|
16
|
+
- **One CLI** β choose your preset: **Base** (MUI stack) or **Tailwind v4**.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## π¦ Installation
|
|
21
|
+
|
|
22
|
+
You donβt need to install globally. Use `npx`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @hadi87s/quickstart-next@latest
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shortcut-next",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold Next.js apps with MUI base or Tailwind v4 preset.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"shortcut-next": "bin/quickstart-next.mjs"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/hadi87s/quickstart-next.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/hadi87s/quickstart-next/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/hadi87s/quickstart-next#readme",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin",
|
|
22
|
+
"src",
|
|
23
|
+
"templates",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [],
|
|
30
|
+
"author": "",
|
|
31
|
+
"license": "ISC",
|
|
32
|
+
"type": "module",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@clack/prompts": "^0.11.0",
|
|
35
|
+
"cac": "^6.7.14",
|
|
36
|
+
"execa": "^9.6.0",
|
|
37
|
+
"fs-extra": "^11.3.1",
|
|
38
|
+
"kolorist": "^1.8.0",
|
|
39
|
+
"ora": "^8.2.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/run.mjs
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { cac } from 'cac';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import { cyan, green, yellow } from 'kolorist';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import { execa } from 'execa';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const TEMPLATE_DIR = path.join(__dirname, '..', 'templates', 'base'); // single base template
|
|
12
|
+
|
|
13
|
+
async function addTailwindV4(dest) {
|
|
14
|
+
// 1) Merge Tailwind v4 devDeps (remove any old ones if present)
|
|
15
|
+
const pkgPath = path.join(dest, 'package.json');
|
|
16
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
17
|
+
|
|
18
|
+
// Ensure devDependencies object exists
|
|
19
|
+
pkg.devDependencies = pkg.devDependencies || {};
|
|
20
|
+
// Remove v3-era keys if they exist
|
|
21
|
+
delete pkg.devDependencies.autoprefixer;
|
|
22
|
+
// Add v4 deps
|
|
23
|
+
pkg.devDependencies.tailwindcss = '^4.0.0';
|
|
24
|
+
pkg.devDependencies['@tailwindcss/postcss'] = '^4.0.0';
|
|
25
|
+
pkg.devDependencies.postcss = '^8.4.47';
|
|
26
|
+
|
|
27
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2));
|
|
28
|
+
|
|
29
|
+
// 2) Write PostCSS config for v4
|
|
30
|
+
await fs.outputFile(
|
|
31
|
+
path.join(dest, 'postcss.config.mjs'),
|
|
32
|
+
`export default { plugins: { '@tailwindcss/postcss': {} } };`
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// 3) Inject the v4 CSS entry at the TOP of globals.css
|
|
36
|
+
const globalsPath = path.join(dest, 'src', 'app', 'globals.css');
|
|
37
|
+
const css = (await fs.pathExists(globalsPath)) ? await fs.readFile(globalsPath, 'utf8') : '';
|
|
38
|
+
const hasImport = /@import\s+["']tailwindcss["'];?/.test(css);
|
|
39
|
+
const withImport = hasImport ? css : `@import "tailwindcss";\n${css}`;
|
|
40
|
+
await fs.outputFile(globalsPath, withImport);
|
|
41
|
+
|
|
42
|
+
// 4) Make sure there is NO tailwind.config.* (v4 is zero-config)
|
|
43
|
+
const tcfgTs = path.join(dest, 'tailwind.config.ts');
|
|
44
|
+
const tcfgJs = path.join(dest, 'tailwind.config.js');
|
|
45
|
+
if (await fs.pathExists(tcfgTs)) await fs.remove(tcfgTs);
|
|
46
|
+
if (await fs.pathExists(tcfgJs)) await fs.remove(tcfgJs);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function installDeps(pm, cwd) {
|
|
50
|
+
const args = ['install']; // install from package.json
|
|
51
|
+
await execa(pm, args, { cwd, stdio: 'inherit' });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function main() {
|
|
55
|
+
const cli = cac('quickstart-next');
|
|
56
|
+
cli
|
|
57
|
+
.option('--preset <name>', 'base | tailwind')
|
|
58
|
+
.option('--pm <pm>', 'npm | pnpm | yarn | bun')
|
|
59
|
+
.option('--no-git', 'Skip git init')
|
|
60
|
+
.option('--no-install', 'Skip dependency install');
|
|
61
|
+
const { options } = cli.parse();
|
|
62
|
+
|
|
63
|
+
p.intro(green('Create Next.js project'));
|
|
64
|
+
|
|
65
|
+
const name = await p.text({
|
|
66
|
+
message: 'Project name?',
|
|
67
|
+
placeholder: 'my-next-app',
|
|
68
|
+
validate: v => (!v ? 'Required' : undefined)
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const preset = options.preset || await p.select({
|
|
72
|
+
message: 'Choose a preset',
|
|
73
|
+
options: [
|
|
74
|
+
{ label: 'Base (MUI, RHF, React Query)', value: 'base' },
|
|
75
|
+
{ label: 'Tailwind v4 (Base + Tailwind)', value: 'tailwind' }
|
|
76
|
+
]
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const pm = options.pm || await p.select({
|
|
80
|
+
message: 'Package manager?',
|
|
81
|
+
options: [
|
|
82
|
+
{ label: 'pnpm', value: 'pnpm' },
|
|
83
|
+
{ label: 'npm', value: 'npm' },
|
|
84
|
+
{ label: 'yarn', value: 'yarn' },
|
|
85
|
+
{ label: 'bun', value: 'bun' }
|
|
86
|
+
]
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const dest = path.resolve(process.cwd(), name);
|
|
90
|
+
if (await fs.pathExists(dest) && (await fs.readdir(dest)).length) {
|
|
91
|
+
p.cancel('Target folder is not empty.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const sp = ora(`Scaffolding ${cyan(name)} with ${yellow(preset)}...`).start();
|
|
96
|
+
await fs.copy(TEMPLATE_DIR, dest);
|
|
97
|
+
|
|
98
|
+
// set package name
|
|
99
|
+
const pkgPath = path.join(dest, 'package.json');
|
|
100
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
101
|
+
pkg.name = name;
|
|
102
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2));
|
|
103
|
+
sp.succeed('Files ready');
|
|
104
|
+
|
|
105
|
+
// Tailwind v4 augmentation
|
|
106
|
+
if (preset === 'tailwind') {
|
|
107
|
+
const tw = ora('Adding Tailwind v4...').start();
|
|
108
|
+
await addTailwindV4(dest);
|
|
109
|
+
tw.succeed('Tailwind v4 wired');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (options.git !== false) {
|
|
113
|
+
await execa('git', ['init'], { cwd: dest });
|
|
114
|
+
await execa('git', ['add', '.'], { cwd: dest });
|
|
115
|
+
await execa('git', ['commit', '-m', 'chore: initial commit'], { cwd: dest });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (options.install !== false) {
|
|
119
|
+
const inst = ora('Installing dependencies...').start();
|
|
120
|
+
await installDeps(pm, dest);
|
|
121
|
+
inst.succeed('Dependencies installed');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
p.outro(`Done! Next steps:
|
|
125
|
+
1) cd ${name}
|
|
126
|
+
2) ${pm} run dev
|
|
127
|
+
`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
main();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// .prettierrc.js
|
|
2
|
+
module.exports = {
|
|
3
|
+
arrowParens: 'avoid',
|
|
4
|
+
bracketSpacing: true,
|
|
5
|
+
htmlWhitespaceSensitivity: 'css',
|
|
6
|
+
insertPragma: false,
|
|
7
|
+
bracketSameLine: false,
|
|
8
|
+
jsxSingleQuote: true,
|
|
9
|
+
printWidth: 120,
|
|
10
|
+
proseWrap: 'preserve',
|
|
11
|
+
quoteProps: 'as-needed',
|
|
12
|
+
requirePragma: false,
|
|
13
|
+
semi: false,
|
|
14
|
+
singleQuote: true,
|
|
15
|
+
tabWidth: 2,
|
|
16
|
+
trailingComma: 'none',
|
|
17
|
+
useTabs: false
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.formatOnSave": true,
|
|
3
|
+
"[javascript]": {
|
|
4
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
5
|
+
},
|
|
6
|
+
"[typescript]": {
|
|
7
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
8
|
+
},
|
|
9
|
+
"[json]": {
|
|
10
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
## Next.js Template
|
|
Binary file
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { Geist, Geist_Mono } from 'next/font/google'
|
|
3
|
+
import './globals.css'
|
|
4
|
+
import BaseProviders from '@/providers/BaseProvider'
|
|
5
|
+
|
|
6
|
+
const geistSans = Geist({
|
|
7
|
+
variable: '--font-geist-sans',
|
|
8
|
+
subsets: ['latin']
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const geistMono = Geist_Mono({
|
|
12
|
+
variable: '--font-geist-mono',
|
|
13
|
+
subsets: ['latin']
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const metadata: Metadata = {
|
|
17
|
+
title: 'Create Next App',
|
|
18
|
+
description: 'Generated by create next app'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function RootLayout({
|
|
22
|
+
children
|
|
23
|
+
}: Readonly<{
|
|
24
|
+
children: React.ReactNode
|
|
25
|
+
}>) {
|
|
26
|
+
return (
|
|
27
|
+
<html lang='en'>
|
|
28
|
+
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
|
|
29
|
+
<BaseProviders>{children}</BaseProviders>
|
|
30
|
+
</body>
|
|
31
|
+
</html>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import Link from 'next/link'
|
|
5
|
+
import { Link as MuiLink } from '@mui/material'
|
|
6
|
+
import {
|
|
7
|
+
Box,
|
|
8
|
+
Button,
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
Chip,
|
|
12
|
+
Container,
|
|
13
|
+
Divider,
|
|
14
|
+
Grid,
|
|
15
|
+
Stack,
|
|
16
|
+
Tooltip,
|
|
17
|
+
Typography
|
|
18
|
+
} from '@mui/material'
|
|
19
|
+
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
|
20
|
+
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
|
21
|
+
import { Github, Package, LayoutDashboard, FormInput } from 'lucide-react'
|
|
22
|
+
import { Icon } from '@iconify/react'
|
|
23
|
+
|
|
24
|
+
const Code = ({ children }: { children: React.ReactNode }) => (
|
|
25
|
+
<Box
|
|
26
|
+
component='code'
|
|
27
|
+
sx={{
|
|
28
|
+
display: 'inline-flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
gap: 1,
|
|
31
|
+
px: 1.25,
|
|
32
|
+
py: 0.75,
|
|
33
|
+
borderRadius: 1,
|
|
34
|
+
bgcolor: 'rgba(255,255,255,0.08)',
|
|
35
|
+
border: '1px solid rgba(255,255,255,0.12)',
|
|
36
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
37
|
+
fontSize: 14
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
</Box>
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
export default function Page() {
|
|
45
|
+
const [copied, setCopied] = React.useState(false)
|
|
46
|
+
|
|
47
|
+
const handleCopy = async (text: string) => {
|
|
48
|
+
try {
|
|
49
|
+
await navigator.clipboard.writeText(text)
|
|
50
|
+
setCopied(true)
|
|
51
|
+
setTimeout(() => setCopied(false), 1200)
|
|
52
|
+
} catch {
|
|
53
|
+
// noop
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Box
|
|
59
|
+
sx={{
|
|
60
|
+
minHeight: '100dvh',
|
|
61
|
+
position: 'relative',
|
|
62
|
+
overflow: 'hidden',
|
|
63
|
+
bgcolor: 'background.default',
|
|
64
|
+
color: 'text.primary'
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{/* --- Glowing blobs --- */}
|
|
68
|
+
<Box
|
|
69
|
+
aria-hidden
|
|
70
|
+
sx={{
|
|
71
|
+
pointerEvents: 'none',
|
|
72
|
+
position: 'absolute',
|
|
73
|
+
inset: 0,
|
|
74
|
+
'&::before, &::after': {
|
|
75
|
+
content: '""',
|
|
76
|
+
position: 'absolute',
|
|
77
|
+
width: 520,
|
|
78
|
+
height: 520,
|
|
79
|
+
borderRadius: '50%',
|
|
80
|
+
filter: 'blur(80px)',
|
|
81
|
+
opacity: 0.22,
|
|
82
|
+
transform: 'translate(-30%, -20%)',
|
|
83
|
+
background: 'radial-gradient(closest-side, #7C4DFF, transparent 70%)',
|
|
84
|
+
animation: 'float1 16s ease-in-out infinite'
|
|
85
|
+
},
|
|
86
|
+
'&::after': {
|
|
87
|
+
right: -120,
|
|
88
|
+
bottom: -120,
|
|
89
|
+
left: 'auto',
|
|
90
|
+
top: 'auto',
|
|
91
|
+
width: 620,
|
|
92
|
+
height: 620,
|
|
93
|
+
opacity: 0.18,
|
|
94
|
+
transform: 'translate(20%, 10%)',
|
|
95
|
+
background: 'radial-gradient(closest-side, #00E5FF, transparent 70%)',
|
|
96
|
+
animation: 'float2 18s ease-in-out infinite'
|
|
97
|
+
},
|
|
98
|
+
'@keyframes float1': {
|
|
99
|
+
'0%, 100%': { transform: 'translate(-30%, -20%) scale(1)' },
|
|
100
|
+
'50%': { transform: 'translate(-10%, -10%) scale(1.08)' }
|
|
101
|
+
},
|
|
102
|
+
'@keyframes float2': {
|
|
103
|
+
'0%, 100%': { transform: 'translate(20%, 10%) scale(1)' },
|
|
104
|
+
'50%': { transform: 'translate(10%, 20%) scale(0.95)' }
|
|
105
|
+
}
|
|
106
|
+
}}
|
|
107
|
+
/>
|
|
108
|
+
|
|
109
|
+
<Container maxWidth='lg' sx={{ position: 'relative', zIndex: 1, py: { xs: 6, md: 10 } }}>
|
|
110
|
+
{/* Hero */}
|
|
111
|
+
<Stack spacing={3} alignItems='center' textAlign='center' sx={{ mb: { xs: 6, md: 10 } }}>
|
|
112
|
+
<Stack direction='row' spacing={1} alignItems='center'>
|
|
113
|
+
<LayoutDashboard size={28} style={{ verticalAlign: 'middle' }} />
|
|
114
|
+
<Typography variant='h4' fontWeight={800} letterSpacing={0.2} sx={{ ml: 1 }}>
|
|
115
|
+
Quickstart Next
|
|
116
|
+
</Typography>
|
|
117
|
+
</Stack>
|
|
118
|
+
|
|
119
|
+
<Typography variant='h6' sx={{ maxWidth: 860, opacity: 0.9 }}>
|
|
120
|
+
A modern Next.js boilerplate powered by <b>MUI</b> with room for <b>React Query</b>, <b>React Hook Form</b>,
|
|
121
|
+
and optional <b>Tailwind v4</b>.
|
|
122
|
+
</Typography>
|
|
123
|
+
|
|
124
|
+
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5} useFlexGap flexWrap='wrap'>
|
|
125
|
+
<Chip
|
|
126
|
+
icon={<Icon icon='simple-icons:mui' width={18} height={18} style={{ borderRadius: 4 }} />}
|
|
127
|
+
label='MUI'
|
|
128
|
+
color='primary'
|
|
129
|
+
variant='filled'
|
|
130
|
+
/>
|
|
131
|
+
<Chip icon={<FormInput size={18} />} label='React Hook Form' variant='outlined' />
|
|
132
|
+
<Chip
|
|
133
|
+
icon={<Icon icon='devicon:tailwindcss' width={18} height={18} />}
|
|
134
|
+
label='Tailwind v4 (optional)'
|
|
135
|
+
variant='outlined'
|
|
136
|
+
/>
|
|
137
|
+
<Chip
|
|
138
|
+
icon={<Icon icon='devicon:typescript' width={18} height={18} style={{ borderRadius: 4 }} />}
|
|
139
|
+
label='TypeScript'
|
|
140
|
+
variant='outlined'
|
|
141
|
+
/>
|
|
142
|
+
<Chip icon={<Icon icon='devicon:nextjs' width={18} height={18} />} label='App Router' variant='outlined' />
|
|
143
|
+
</Stack>
|
|
144
|
+
|
|
145
|
+
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ pt: 1 }}>
|
|
146
|
+
<Button size='large' variant='contained' component={Link} href='/sample-form'>
|
|
147
|
+
Open Sample Form
|
|
148
|
+
</Button>
|
|
149
|
+
<Button
|
|
150
|
+
size='large'
|
|
151
|
+
variant='outlined'
|
|
152
|
+
endIcon={<OpenInNewIcon />}
|
|
153
|
+
component={Link}
|
|
154
|
+
href='#'
|
|
155
|
+
target='_blank'
|
|
156
|
+
rel='noopener'
|
|
157
|
+
>
|
|
158
|
+
View Docs
|
|
159
|
+
</Button>
|
|
160
|
+
</Stack>
|
|
161
|
+
</Stack>
|
|
162
|
+
|
|
163
|
+
{/* Content */}
|
|
164
|
+
<Grid container spacing={3}>
|
|
165
|
+
<Grid size={{ xs: 12, md: 7 }}>
|
|
166
|
+
<Card
|
|
167
|
+
sx={{
|
|
168
|
+
backdropFilter: 'saturate(120%) blur(6px)',
|
|
169
|
+
background: 'rgba(255,255,255,0.04)',
|
|
170
|
+
border: '1px solid',
|
|
171
|
+
borderColor: 'rgba(255,255,255,0.08)'
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
<CardContent>
|
|
175
|
+
<Typography variant='h6' fontWeight={700} gutterBottom>
|
|
176
|
+
Whatβs included
|
|
177
|
+
</Typography>
|
|
178
|
+
<Stack spacing={1.25} sx={{ opacity: 0.9 }}>
|
|
179
|
+
<Typography>β’ Next.js 15 (App Router) + TypeScript</Typography>
|
|
180
|
+
<Typography>β’ MUI ThemeProvider + dark-ready UI</Typography>
|
|
181
|
+
<Typography>
|
|
182
|
+
β’ RHF starter form at <code>/sample-form</code>
|
|
183
|
+
</Typography>
|
|
184
|
+
<Typography>β’ Easy opt-in Tailwind v4 (via CLI preset)</Typography>
|
|
185
|
+
</Stack>
|
|
186
|
+
|
|
187
|
+
<Divider sx={{ my: 3 }} />
|
|
188
|
+
|
|
189
|
+
<Typography variant='subtitle2' gutterBottom>
|
|
190
|
+
Scaffold via npx
|
|
191
|
+
</Typography>
|
|
192
|
+
|
|
193
|
+
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5} alignItems='center'>
|
|
194
|
+
<Code>npx @hadi87s/quickstart-next@latest</Code>
|
|
195
|
+
<Tooltip title={copied ? 'Copied!' : 'Copy'}>
|
|
196
|
+
<Button
|
|
197
|
+
variant='outlined'
|
|
198
|
+
size='small'
|
|
199
|
+
startIcon={<ContentCopyIcon fontSize='small' />}
|
|
200
|
+
onClick={() => handleCopy('npx @hadi87s/quickstart-next@latest')}
|
|
201
|
+
>
|
|
202
|
+
{copied ? 'Copied' : 'Copy'}
|
|
203
|
+
</Button>
|
|
204
|
+
</Tooltip>
|
|
205
|
+
</Stack>
|
|
206
|
+
</CardContent>
|
|
207
|
+
</Card>
|
|
208
|
+
</Grid>
|
|
209
|
+
|
|
210
|
+
<Grid size={{ xs: 12, md: 5 }}>
|
|
211
|
+
<Card
|
|
212
|
+
sx={{
|
|
213
|
+
height: '100%',
|
|
214
|
+
backdropFilter: 'saturate(120%) blur(6px)',
|
|
215
|
+
background: 'rgba(255,255,255,0.04)',
|
|
216
|
+
border: '1px solid',
|
|
217
|
+
borderColor: 'rgba(255,255,255,0.08)'
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
<CardContent>
|
|
221
|
+
<Typography variant='h6' fontWeight={700} gutterBottom>
|
|
222
|
+
Tech Logos
|
|
223
|
+
</Typography>
|
|
224
|
+
<Stack direction='row' spacing={2} alignItems='center' sx={{ pb: 1.5 }}>
|
|
225
|
+
<Tooltip title='MUI' arrow placement='top'>
|
|
226
|
+
<span>
|
|
227
|
+
<Icon icon='simple-icons:mui' width={28} height={28} style={{ borderRadius: 4 }} />
|
|
228
|
+
</span>
|
|
229
|
+
</Tooltip>
|
|
230
|
+
<Tooltip title='Tailwind CSS' arrow placement='top'>
|
|
231
|
+
<span>
|
|
232
|
+
<Icon icon='devicon:tailwindcss' width={28} height={28} />
|
|
233
|
+
</span>
|
|
234
|
+
</Tooltip>
|
|
235
|
+
<Tooltip title='React' arrow placement='top'>
|
|
236
|
+
<span>
|
|
237
|
+
<Icon icon='devicon:react' width={28} height={28} />
|
|
238
|
+
</span>
|
|
239
|
+
</Tooltip>
|
|
240
|
+
<Tooltip title='Next.js' arrow placement='top'>
|
|
241
|
+
<span>
|
|
242
|
+
<Icon icon='devicon:nextjs' width={28} height={28} />
|
|
243
|
+
</span>
|
|
244
|
+
</Tooltip>
|
|
245
|
+
<Tooltip title='React Hook Form' arrow placement='top'>
|
|
246
|
+
<span>
|
|
247
|
+
<Icon icon='simple-icons:reacthookform' width={28} height={28} />
|
|
248
|
+
</span>
|
|
249
|
+
</Tooltip>
|
|
250
|
+
<Tooltip title='TypeScript' arrow placement='top'>
|
|
251
|
+
<span>
|
|
252
|
+
<Icon icon='devicon:typescript' width={28} height={28} style={{ borderRadius: 4 }} />
|
|
253
|
+
</span>
|
|
254
|
+
</Tooltip>
|
|
255
|
+
</Stack>
|
|
256
|
+
|
|
257
|
+
<Typography variant='body2' sx={{ opacity: 0.85 }}>
|
|
258
|
+
This template ships with MUI by default. You can enable Tailwind v4 at scaffold time. React Query and
|
|
259
|
+
other integrations can be added as the stack grows.
|
|
260
|
+
</Typography>
|
|
261
|
+
|
|
262
|
+
<Divider sx={{ my: 2 }} />
|
|
263
|
+
|
|
264
|
+
<Stack direction='row' spacing={1.5}>
|
|
265
|
+
<Button
|
|
266
|
+
variant='outlined'
|
|
267
|
+
size='small'
|
|
268
|
+
startIcon={<Github size={18} />}
|
|
269
|
+
endIcon={<OpenInNewIcon />}
|
|
270
|
+
component={Link}
|
|
271
|
+
href='https://github.com/hadi87s/quickstart-next'
|
|
272
|
+
target='_blank'
|
|
273
|
+
rel='noopener'
|
|
274
|
+
>
|
|
275
|
+
GitHub
|
|
276
|
+
</Button>
|
|
277
|
+
<Button
|
|
278
|
+
variant='outlined'
|
|
279
|
+
size='small'
|
|
280
|
+
startIcon={<Package size={18} />}
|
|
281
|
+
endIcon={<OpenInNewIcon />}
|
|
282
|
+
component={Link}
|
|
283
|
+
href='https://www.npmjs.com/package/@hadi87s/quickstart-next'
|
|
284
|
+
target='_blank'
|
|
285
|
+
rel='noopener'
|
|
286
|
+
>
|
|
287
|
+
npm
|
|
288
|
+
</Button>
|
|
289
|
+
</Stack>
|
|
290
|
+
</CardContent>
|
|
291
|
+
</Card>
|
|
292
|
+
</Grid>
|
|
293
|
+
</Grid>
|
|
294
|
+
|
|
295
|
+
{/* Footer */}
|
|
296
|
+
<Stack alignItems='center' sx={{ mt: 8, opacity: 0.65 }}>
|
|
297
|
+
<Typography variant='body2'>
|
|
298
|
+
Built with β€οΈ by{' '}
|
|
299
|
+
<MuiLink
|
|
300
|
+
href='https://github.com/hadi87s/quickstart-next'
|
|
301
|
+
underline='none'
|
|
302
|
+
color='primary'
|
|
303
|
+
sx={{ fontWeight: 600 }}
|
|
304
|
+
target='_blank'
|
|
305
|
+
rel='noopener'
|
|
306
|
+
>
|
|
307
|
+
Hadi
|
|
308
|
+
</MuiLink>{' '}
|
|
309
|
+
using MUI. Ready for Tailwind v4, React Query, and more.
|
|
310
|
+
</Typography>
|
|
311
|
+
</Stack>
|
|
312
|
+
</Container>
|
|
313
|
+
</Box>
|
|
314
|
+
)
|
|
315
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { dirname } from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const compat = new FlatCompat({
|
|
9
|
+
baseDirectory: __dirname,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const eslintConfig = [
|
|
13
|
+
...compat.extends("next/core-web-vitals", "next/typescript"),
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export default eslintConfig;
|