bsmnt 0.2.0 → 0.2.5
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 +1 -1
- package/package.json +22 -5
- package/src/templates/next-default/.biome/plugins/no-anchor-element.grit +12 -0
- package/src/templates/next-default/.biome/plugins/no-relative-parent-imports.grit +10 -0
- package/src/templates/next-default/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
- package/src/templates/next-default/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- package/src/templates/next-default/.vscode/extensions.json +20 -0
- package/src/templates/next-default/.vscode/settings.json +105 -0
- package/src/templates/next-default/app/favicon.ico +0 -0
- package/src/templates/next-default/app/layout.tsx +104 -0
- package/src/templates/next-default/app/page.tsx +11 -0
- package/src/templates/next-default/app/robots.ts +15 -0
- package/src/templates/next-default/app/sitemap.ts +16 -0
- package/src/templates/next-default/biome.json +2 -2
- package/src/templates/next-default/components/layout/footer/index.tsx +31 -0
- package/src/templates/next-default/components/layout/header/index.tsx +9 -0
- package/src/templates/next-default/components/layout/theme/index.tsx +66 -0
- package/src/templates/next-default/components/layout/wrapper/index.tsx +63 -0
- package/src/templates/next-default/components/ui/README.md +77 -0
- package/src/templates/next-default/components/ui/image/README.md +37 -0
- package/src/templates/next-default/components/ui/image/index.tsx +219 -0
- package/src/templates/next-default/components/ui/link/index.tsx +152 -0
- package/src/templates/next-default/lib/utils/metadata.ts +26 -26
- package/src/templates/next-default/package.json +4 -4
- package/src/templates/next-default/public/fonts/geist/Geist-Mono.woff2 +0 -0
- package/src/templates/next-experiments/app/layout.tsx +18 -18
- package/src/templates/next-experiments/app/robots.ts +3 -3
- package/src/templates/next-experiments/app/sitemap.ts +4 -4
- package/src/templates/next-experiments/biome.json +2 -2
- package/src/templates/next-experiments/components/layout/theme/index.tsx +1 -1
- package/src/templates/next-experiments/components/layout/wrapper/index.tsx +7 -9
- package/src/templates/next-experiments/components/ui/image/index.tsx +39 -46
- package/src/templates/next-experiments/components/ui/link/index.tsx +6 -0
- package/src/templates/next-experiments/lib/utils/metadata.ts +26 -26
- package/src/templates/next-experiments/package.json +4 -4
- package/src/templates/next-webgl/app/layout.tsx +18 -18
- package/src/templates/next-webgl/app/robots.ts +3 -3
- package/src/templates/next-webgl/app/sitemap.ts +4 -4
- package/src/templates/next-webgl/biome.json +2 -2
- package/src/templates/next-webgl/components/layout/theme/index.tsx +1 -1
- package/src/templates/next-webgl/components/layout/wrapper/index.tsx +0 -2
- package/src/templates/next-webgl/components/ui/image/index.tsx +6 -11
- package/src/templates/next-webgl/components/ui/link/index.tsx +9 -2
- package/src/templates/next-webgl/components/webgl/components/scene/index.tsx +1 -0
- package/src/templates/next-webgl/lib/utils/metadata.ts +26 -26
- package/src/templates/next-webgl/package.json +4 -4
- package/src/templates/next-experiments/.cursor/rules/README.md +0 -184
- package/src/templates/next-experiments/.cursor/rules/architecture.mdc +0 -437
- package/src/templates/next-experiments/.cursor/rules/components.mdc +0 -436
- package/src/templates/next-experiments/.cursor/rules/integrations.mdc +0 -447
- package/src/templates/next-experiments/.cursor/rules/main.mdc +0 -278
- package/src/templates/next-experiments/.cursor/rules/styling.mdc +0 -433
- package/src/templates/next-experiments/.github/workflows/lighthouse-to-slack.yml +0 -136
- package/src/templates/next-webgl/.cursor/rules/README.md +0 -184
- package/src/templates/next-webgl/.cursor/rules/architecture.mdc +0 -437
- package/src/templates/next-webgl/.cursor/rules/components.mdc +0 -436
- package/src/templates/next-webgl/.cursor/rules/integrations.mdc +0 -447
- package/src/templates/next-webgl/.cursor/rules/main.mdc +0 -278
- package/src/templates/next-webgl/.cursor/rules/styling.mdc +0 -433
- package/src/templates/next-webgl/.github/workflows/lighthouse-to-slack.yml +0 -136
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bsmnt",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"packageManager": "bun@1.2.20",
|
|
5
5
|
"description": "CLI to scaffold basement projects and add integrations",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"workspaces": [
|
|
8
|
+
".",
|
|
9
|
+
"src/templates/*"
|
|
10
|
+
],
|
|
7
11
|
"bin": {
|
|
8
|
-
"bsmnt": "
|
|
12
|
+
"bsmnt": "index.js"
|
|
9
13
|
},
|
|
10
14
|
"main": "./dist/index.js",
|
|
11
15
|
"files": [
|
|
12
16
|
"dist",
|
|
13
17
|
"index.js",
|
|
14
18
|
"src/templates",
|
|
19
|
+
"!src/templates/*/node_modules",
|
|
20
|
+
"!src/templates/*/.next",
|
|
21
|
+
"!src/templates/*/bun.lock",
|
|
15
22
|
"src/template-hooks",
|
|
16
23
|
"plugins"
|
|
17
24
|
],
|
|
@@ -22,10 +29,20 @@
|
|
|
22
29
|
"lint": "biome check .",
|
|
23
30
|
"lint:fix": "biome check --write .",
|
|
24
31
|
"changeset": "changeset",
|
|
25
|
-
"version-packages": "changeset version",
|
|
32
|
+
"version-packages": "changeset version && node scripts/sync-readme-version.js",
|
|
26
33
|
"release": "changeset publish",
|
|
27
|
-
"prepublishOnly": "bun run build",
|
|
28
|
-
"default": "turbo run dev --filter=
|
|
34
|
+
"prepublishOnly": "rm -rf dist && bun run build",
|
|
35
|
+
"default": "turbo run dev --filter=bsmnt-next-starter",
|
|
36
|
+
"webgpu": "turbo run dev --filter=bsmnt-webgpu-starter",
|
|
37
|
+
"experiments": "turbo run dev --filter=bsmnt-experiments-starter",
|
|
38
|
+
"build:default": "turbo run build --filter=bsmnt-next-starter",
|
|
39
|
+
"build:webgpu": "turbo run build --filter=bsmnt-webgpu-starter",
|
|
40
|
+
"build:experiments": "turbo run build --filter=bsmnt-experiments-starter",
|
|
41
|
+
"build:all": "turbo run build --filter=bsmnt-next-starter --filter=bsmnt-webgpu-starter --filter=bsmnt-experiments-starter",
|
|
42
|
+
"lint:default": "turbo run lint --filter=bsmnt-next-starter",
|
|
43
|
+
"lint:webgpu": "turbo run lint --filter=bsmnt-webgpu-starter",
|
|
44
|
+
"lint:experiments": "turbo run lint --filter=bsmnt-experiments-starter",
|
|
45
|
+
"lint:all": "turbo run lint --filter=bsmnt-next-starter --filter=bsmnt-webgpu-starter --filter=bsmnt-experiments-starter"
|
|
29
46
|
},
|
|
30
47
|
"keywords": [
|
|
31
48
|
"webgpu",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
language js;
|
|
2
|
+
|
|
3
|
+
`<a $attrs>$content</a>` as $anchor where {
|
|
4
|
+
!$anchor <: within `if ($condition) { return ($jsx) }` where {
|
|
5
|
+
$condition <: contains or { `isExternal`, `isExternalSSR` }
|
|
6
|
+
},
|
|
7
|
+
register_diagnostic(
|
|
8
|
+
span = $anchor,
|
|
9
|
+
message = "Use custom Link component instead of <a> element. The Link component handles both internal and external links automatically.",
|
|
10
|
+
severity = "error"
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
language js;
|
|
2
|
+
|
|
3
|
+
`import $imports from $source` as $import where {
|
|
4
|
+
$source <: r"['\"]\.\.\/\.\.\/.*['\"]",
|
|
5
|
+
register_diagnostic(
|
|
6
|
+
span = $import,
|
|
7
|
+
message = "Use alias imports (~/dir/) instead of deep relative imports (../../). Single level imports (../) are allowed for colocated files.",
|
|
8
|
+
severity = "error"
|
|
9
|
+
)
|
|
10
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"recommendations": [
|
|
3
|
+
// Formatting & Linting
|
|
4
|
+
"biomejs.biome",
|
|
5
|
+
|
|
6
|
+
// CSS & Styling
|
|
7
|
+
"bradlc.vscode-tailwindcss",
|
|
8
|
+
"csstools.postcss",
|
|
9
|
+
|
|
10
|
+
// GraphQL (Shopify, etc.)
|
|
11
|
+
"graphql.vscode-graphql-syntax",
|
|
12
|
+
|
|
13
|
+
// Sanity CMS (GROQ syntax + validation)
|
|
14
|
+
"sanity-io.vscode-sanity",
|
|
15
|
+
|
|
16
|
+
// DX Enhancements
|
|
17
|
+
"yoavbls.pretty-ts-errors",
|
|
18
|
+
"waderyan.gitblame"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Language-specific formatters (explicit per-language ensures no fallback to other formatters)
|
|
3
|
+
"[css]": {
|
|
4
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
5
|
+
},
|
|
6
|
+
"[javascript]": {
|
|
7
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
8
|
+
},
|
|
9
|
+
"[javascriptreact]": {
|
|
10
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
11
|
+
},
|
|
12
|
+
"[json]": {
|
|
13
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
14
|
+
},
|
|
15
|
+
"[jsonc]": {
|
|
16
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
17
|
+
},
|
|
18
|
+
"[postcss]": {
|
|
19
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
20
|
+
},
|
|
21
|
+
"[typescript]": {
|
|
22
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
23
|
+
},
|
|
24
|
+
"[typescriptreact]": {
|
|
25
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
26
|
+
},
|
|
27
|
+
"colorize.languages": [
|
|
28
|
+
"javascript",
|
|
29
|
+
"typescript",
|
|
30
|
+
"css",
|
|
31
|
+
"scss",
|
|
32
|
+
"less",
|
|
33
|
+
"html",
|
|
34
|
+
"json",
|
|
35
|
+
"yaml",
|
|
36
|
+
"markdown",
|
|
37
|
+
"glsl"
|
|
38
|
+
],
|
|
39
|
+
"css.lint.unknownAtRules": "ignore",
|
|
40
|
+
|
|
41
|
+
// CSS (Tailwind v4)
|
|
42
|
+
"css.lint.validProperties": ["user-drag"],
|
|
43
|
+
"editor.codeActionsOnSave": {
|
|
44
|
+
"source.fixAll.biome": "always",
|
|
45
|
+
"source.organizeImports.biome": "always"
|
|
46
|
+
},
|
|
47
|
+
"editor.defaultFormatter": "biomejs.biome",
|
|
48
|
+
|
|
49
|
+
// Formatting
|
|
50
|
+
"editor.formatOnSave": true,
|
|
51
|
+
"editor.formatOnSaveMode": "file",
|
|
52
|
+
|
|
53
|
+
// File associations
|
|
54
|
+
"files.associations": {
|
|
55
|
+
"*.css": "css",
|
|
56
|
+
"*.json": "jsonc"
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// General
|
|
60
|
+
"files.eol": "\n",
|
|
61
|
+
// "typescript.experimental.useTsgo": true, // Disabled: tsgo doesn't support Next.js plugin
|
|
62
|
+
"javascript.suggest.autoImports": true,
|
|
63
|
+
// Disable Prettier to prevent silent formatter conflicts
|
|
64
|
+
"prettier.enable": false,
|
|
65
|
+
|
|
66
|
+
// Search exclusions (performance)
|
|
67
|
+
"search.exclude": {
|
|
68
|
+
"**/.next": true,
|
|
69
|
+
"**/.vercel": true,
|
|
70
|
+
"**/bun.lock": true,
|
|
71
|
+
"**/lib/integrations/sanity/sanity.types.ts": true,
|
|
72
|
+
"**/node_modules": true
|
|
73
|
+
},
|
|
74
|
+
"tailwindCSS.classAttributes": [
|
|
75
|
+
"class",
|
|
76
|
+
"className",
|
|
77
|
+
".*ClassName",
|
|
78
|
+
".*Styles"
|
|
79
|
+
],
|
|
80
|
+
"tailwindCSS.classFunctions": ["cn", "cva", "twMerge", "cx"],
|
|
81
|
+
"tailwindCSS.experimental.classRegex": [
|
|
82
|
+
["cn\\(([^)]*)\\)", "(?:'|\"|`)([^\"'`]*)(?:'|\"|`)"],
|
|
83
|
+
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
|
84
|
+
],
|
|
85
|
+
"tailwindCSS.experimental.configFile": "./lib/styles/css/tailwind.css",
|
|
86
|
+
"tailwindCSS.includeLanguages": {
|
|
87
|
+
"typescriptreact": "html"
|
|
88
|
+
},
|
|
89
|
+
"typescript.preferences.importModuleSpecifier": "non-relative",
|
|
90
|
+
|
|
91
|
+
// TypeScript
|
|
92
|
+
"typescript.suggest.autoImports": true,
|
|
93
|
+
"typescript.tsdk": "node_modules/typescript/lib",
|
|
94
|
+
|
|
95
|
+
// Editor labels (fixed for .tsx files)
|
|
96
|
+
"workbench.editor.customLabels.patterns": {
|
|
97
|
+
"**/app/**/layout.tsx": "${dirname(1)}/${dirname} <layout>",
|
|
98
|
+
"**/app/**/page.tsx": "${dirname(1)}/${dirname} <page>",
|
|
99
|
+
"**/app/api/**/route.ts": "${dirname(1)}/${dirname} <route>",
|
|
100
|
+
"**/components/**/index.tsx": "${dirname} <component>"
|
|
101
|
+
},
|
|
102
|
+
"workbench.editorAssociations": {
|
|
103
|
+
"*.svg": "default"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Metadata, Viewport } from "next";
|
|
2
|
+
import { Geist } from "next/font/google";
|
|
3
|
+
import { type PropsWithChildren, Suspense } from "react";
|
|
4
|
+
import { Link } from "@/components/ui/link";
|
|
5
|
+
import { themes } from "@/lib/styles/colors";
|
|
6
|
+
import { fontsVariable } from "@/lib/styles/fonts";
|
|
7
|
+
import AppData from "@/package.json";
|
|
8
|
+
import "@/lib/styles/css/index.css";
|
|
9
|
+
import { cn } from "@/lib/styles/cn";
|
|
10
|
+
|
|
11
|
+
const APP_NAME = AppData.name;
|
|
12
|
+
const APP_DEFAULT_TITLE = "Basement Starter";
|
|
13
|
+
const APP_TITLE_TEMPLATE = "%s - Basement Starter";
|
|
14
|
+
const APP_DESCRIPTION = AppData.description;
|
|
15
|
+
const APP_BASE_URL =
|
|
16
|
+
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
|
|
17
|
+
|
|
18
|
+
const geist = Geist({
|
|
19
|
+
subsets: ["latin"],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const metadata: Metadata = {
|
|
23
|
+
alternates: {
|
|
24
|
+
canonical: "/",
|
|
25
|
+
languages: {
|
|
26
|
+
"en-US": "/en-US",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
appleWebApp: {
|
|
30
|
+
capable: true,
|
|
31
|
+
statusBarStyle: "default",
|
|
32
|
+
title: APP_DEFAULT_TITLE,
|
|
33
|
+
},
|
|
34
|
+
applicationName: APP_NAME,
|
|
35
|
+
authors: [{ name: "basement.studio", url: "https://basement.studio" }],
|
|
36
|
+
description: APP_DESCRIPTION,
|
|
37
|
+
formatDetection: { telephone: false },
|
|
38
|
+
metadataBase: new URL(APP_BASE_URL),
|
|
39
|
+
openGraph: {
|
|
40
|
+
description: APP_DESCRIPTION,
|
|
41
|
+
images: [
|
|
42
|
+
{
|
|
43
|
+
alt: APP_DEFAULT_TITLE,
|
|
44
|
+
height: 630,
|
|
45
|
+
url: "/opengraph-image.jpg",
|
|
46
|
+
width: 1200,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
locale: "en_US",
|
|
50
|
+
siteName: APP_NAME,
|
|
51
|
+
title: {
|
|
52
|
+
default: APP_DEFAULT_TITLE,
|
|
53
|
+
template: APP_TITLE_TEMPLATE,
|
|
54
|
+
},
|
|
55
|
+
type: "website",
|
|
56
|
+
url: APP_BASE_URL,
|
|
57
|
+
},
|
|
58
|
+
other: {
|
|
59
|
+
"fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "",
|
|
60
|
+
},
|
|
61
|
+
title: {
|
|
62
|
+
default: APP_DEFAULT_TITLE,
|
|
63
|
+
template: APP_TITLE_TEMPLATE,
|
|
64
|
+
},
|
|
65
|
+
twitter: {
|
|
66
|
+
card: "summary_large_image",
|
|
67
|
+
description: APP_DESCRIPTION,
|
|
68
|
+
title: {
|
|
69
|
+
default: APP_DEFAULT_TITLE,
|
|
70
|
+
template: APP_TITLE_TEMPLATE,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const viewport: Viewport = {
|
|
76
|
+
colorScheme: "normal",
|
|
77
|
+
themeColor: themes.dark.primary,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default async function Layout({ children }: PropsWithChildren) {
|
|
81
|
+
return (
|
|
82
|
+
<html
|
|
83
|
+
lang="en"
|
|
84
|
+
dir="ltr"
|
|
85
|
+
className={cn(fontsVariable, geist.className)}
|
|
86
|
+
// NOTE: This is due to the data-theme attribute being set which causes hydration errors
|
|
87
|
+
suppressHydrationWarning
|
|
88
|
+
>
|
|
89
|
+
<body>
|
|
90
|
+
{/* Skip link for keyboard navigation accessibility */}
|
|
91
|
+
<Suspense fallback={null}>
|
|
92
|
+
<Link
|
|
93
|
+
href="#main-content"
|
|
94
|
+
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-9999 focus:rounded focus:bg-black focus:px-4 focus:py-2 focus:text-white focus:outline-none focus:ring-2 focus:ring-white"
|
|
95
|
+
>
|
|
96
|
+
Skip to main content
|
|
97
|
+
</Link>
|
|
98
|
+
</Suspense>
|
|
99
|
+
|
|
100
|
+
{children}
|
|
101
|
+
</body>
|
|
102
|
+
</html>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Wrapper } from "@/components/layout/wrapper"
|
|
2
|
+
|
|
3
|
+
export default function Home() {
|
|
4
|
+
return (
|
|
5
|
+
<Wrapper theme="dark">
|
|
6
|
+
<section className="flex grow items-center justify-center">
|
|
7
|
+
<h1 className="font-mono text-xl uppercase">Basement Next Starter</h1>
|
|
8
|
+
</section>
|
|
9
|
+
</Wrapper>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next";
|
|
2
|
+
|
|
3
|
+
const APP_BASE_URL =
|
|
4
|
+
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
|
|
5
|
+
|
|
6
|
+
export default function robots(): MetadataRoute.Robots {
|
|
7
|
+
return {
|
|
8
|
+
rules: {
|
|
9
|
+
userAgent: "*",
|
|
10
|
+
allow: "/",
|
|
11
|
+
disallow: [],
|
|
12
|
+
},
|
|
13
|
+
sitemap: `${APP_BASE_URL}/sitemap.xml`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next";
|
|
2
|
+
|
|
3
|
+
const APP_BASE_URL =
|
|
4
|
+
process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
|
|
5
|
+
|
|
6
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
7
|
+
const baseRoutes: MetadataRoute.Sitemap = [
|
|
8
|
+
{
|
|
9
|
+
url: APP_BASE_URL,
|
|
10
|
+
lastModified: new Date(),
|
|
11
|
+
changeFrequency: "daily",
|
|
12
|
+
priority: 1,
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
return baseRoutes;
|
|
16
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"root":
|
|
2
|
+
"root": true,
|
|
3
3
|
"$schema": "node_modules/@biomejs/biome/configuration_schema.json",
|
|
4
4
|
"assist": {
|
|
5
5
|
"actions": {
|
|
@@ -245,6 +245,6 @@
|
|
|
245
245
|
"vcs": {
|
|
246
246
|
"clientKind": "git",
|
|
247
247
|
"enabled": true,
|
|
248
|
-
"useIgnoreFile":
|
|
248
|
+
"useIgnoreFile": false
|
|
249
249
|
}
|
|
250
250
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import Logo from "@/components/basement.svg"
|
|
2
|
+
import { Link } from "@/components/ui/link"
|
|
3
|
+
|
|
4
|
+
export function Footer() {
|
|
5
|
+
return (
|
|
6
|
+
<footer className="flex flex-col items-center justify-between p-safe font-mono uppercase 2xl:flex-row 2xl:items-end">
|
|
7
|
+
<Link
|
|
8
|
+
href="https://basement.studio/"
|
|
9
|
+
className="link"
|
|
10
|
+
aria-label="Basement Studio"
|
|
11
|
+
>
|
|
12
|
+
<Logo className="w-148 text-secondary" />
|
|
13
|
+
</Link>
|
|
14
|
+
<div>
|
|
15
|
+
<Link
|
|
16
|
+
href="https://github.com/basementstudio/next-starter/generate"
|
|
17
|
+
className="link"
|
|
18
|
+
>
|
|
19
|
+
use this template
|
|
20
|
+
</Link>
|
|
21
|
+
{" / "}
|
|
22
|
+
<Link
|
|
23
|
+
href="https://github.com/basementstudio/next-starter"
|
|
24
|
+
className="link"
|
|
25
|
+
>
|
|
26
|
+
github
|
|
27
|
+
</Link>
|
|
28
|
+
</div>
|
|
29
|
+
</footer>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { usePathname } from "next/navigation"
|
|
4
|
+
import { createContext, useContext, useEffect, useState } from "react"
|
|
5
|
+
import type { Themes } from "@/lib/styles/colors"
|
|
6
|
+
import { type ThemeName, themes } from "@/lib/styles/config"
|
|
7
|
+
|
|
8
|
+
export const ThemeContext = createContext<{
|
|
9
|
+
name: ThemeName
|
|
10
|
+
theme: Themes[ThemeName]
|
|
11
|
+
setThemeName: (theme: ThemeName) => void
|
|
12
|
+
}>({
|
|
13
|
+
name: "light",
|
|
14
|
+
theme: themes.light,
|
|
15
|
+
setThemeName: () => {
|
|
16
|
+
void 0
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export function useTheme() {
|
|
21
|
+
return useContext(ThemeContext)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function Theme({
|
|
25
|
+
children,
|
|
26
|
+
theme,
|
|
27
|
+
global,
|
|
28
|
+
}: {
|
|
29
|
+
children: React.ReactNode
|
|
30
|
+
theme: ThemeName
|
|
31
|
+
global?: boolean
|
|
32
|
+
}) {
|
|
33
|
+
const pathname = usePathname()
|
|
34
|
+
|
|
35
|
+
const [currentTheme, setCurrentTheme] = useState(theme)
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setCurrentTheme(theme)
|
|
39
|
+
}, [theme])
|
|
40
|
+
|
|
41
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: we need to trigger on path change
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (global) {
|
|
44
|
+
document.documentElement.setAttribute("data-theme", currentTheme)
|
|
45
|
+
}
|
|
46
|
+
}, [pathname, currentTheme, global])
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
{global && /^[a-z]+$/.test(currentTheme) && (
|
|
51
|
+
<script>
|
|
52
|
+
{`document.documentElement.setAttribute('data-theme', '${currentTheme}');`}
|
|
53
|
+
</script>
|
|
54
|
+
)}
|
|
55
|
+
<ThemeContext.Provider
|
|
56
|
+
value={{
|
|
57
|
+
name: currentTheme,
|
|
58
|
+
theme: themes[currentTheme],
|
|
59
|
+
setThemeName: setCurrentTheme,
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
</ThemeContext.Provider>
|
|
64
|
+
</>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main page wrapper providing theme, smooth scrolling, and WebGL context.
|
|
3
|
+
*
|
|
4
|
+
* Customize the Header and Footer components for your project needs.
|
|
5
|
+
*/
|
|
6
|
+
import cn from "clsx"
|
|
7
|
+
import { Footer } from "@/components/layout/footer"
|
|
8
|
+
import { Header } from "@/components/layout/header"
|
|
9
|
+
import { Theme } from "@/components/layout/theme"
|
|
10
|
+
import type { ThemeName } from "@/lib/styles/config"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Props for the Wrapper component.
|
|
14
|
+
*/
|
|
15
|
+
interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
16
|
+
/** Theme to apply ('dark' | 'light'). Defaults to 'dark'. */
|
|
17
|
+
theme?: ThemeName
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Main page wrapper component providing theme.
|
|
22
|
+
*
|
|
23
|
+
* This component serves as the root container for pages, automatically handling
|
|
24
|
+
* theme application and layout structure.
|
|
25
|
+
* It includes navigation and footer.
|
|
26
|
+
*
|
|
27
|
+
* @param props - Component props
|
|
28
|
+
* @param props.theme - Color theme to apply to the page
|
|
29
|
+
* @param props.children - Page content
|
|
30
|
+
* @param props.className - Additional CSS classes
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* // Basic usage with theme
|
|
35
|
+
* export default function Page() {
|
|
36
|
+
* return (
|
|
37
|
+
* <Wrapper theme="dark">
|
|
38
|
+
* <section>My page content</section>
|
|
39
|
+
* </Wrapper>
|
|
40
|
+
* )
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function Wrapper({
|
|
45
|
+
children,
|
|
46
|
+
theme = "dark",
|
|
47
|
+
className,
|
|
48
|
+
...props
|
|
49
|
+
}: WrapperProps) {
|
|
50
|
+
return (
|
|
51
|
+
<Theme theme={theme} global>
|
|
52
|
+
<Header />
|
|
53
|
+
<main
|
|
54
|
+
id="main-content"
|
|
55
|
+
className={cn("relative flex grow flex-col", className)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</main>
|
|
60
|
+
<Footer />
|
|
61
|
+
</Theme>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Components
|
|
2
|
+
|
|
3
|
+
UI components organized by purpose.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
components/
|
|
7
|
+
├── ui/ → Primitives (reusable across projects)
|
|
8
|
+
├── layout/ → Site chrome (customize per project)
|
|
9
|
+
└── magic/ → Animation & visual enhancements
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Direct Imports (Recommended)
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
// UI Components - import directly for better clarity
|
|
16
|
+
import { Image } from '@/components/ui/image'
|
|
17
|
+
import { Link } from '@/components/ui/link'
|
|
18
|
+
|
|
19
|
+
// Layout Components
|
|
20
|
+
import { Wrapper } from '@/components/layout/wrapper'
|
|
21
|
+
import { Header } from '@/components/layout/header'
|
|
22
|
+
import { Footer } from '@/components/layout/footer'
|
|
23
|
+
|
|
24
|
+
// Magic Components
|
|
25
|
+
import { Marquee } from '@/components/magic/marquee'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## UI Components
|
|
29
|
+
|
|
30
|
+
| Component | Purpose |
|
|
31
|
+
|-----------|---------|
|
|
32
|
+
| `image/` | Optimized images (always use this, never `next/image`) |
|
|
33
|
+
| `link/` | Smart navigation (auto-detects external) |
|
|
34
|
+
|
|
35
|
+
## Layout Components
|
|
36
|
+
|
|
37
|
+
| Component | Purpose |
|
|
38
|
+
|-----------|---------|
|
|
39
|
+
| `wrapper/` | Page wrapper (theme, WebGL, Lenis) |
|
|
40
|
+
| `header/` | Site header/navigation |
|
|
41
|
+
| `footer/` | Site footer |
|
|
42
|
+
| `lenis/` | Smooth scrolling |
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<Wrapper theme="dark" webgl lenis>
|
|
46
|
+
<section>Your content</section>
|
|
47
|
+
</Wrapper>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Effects Components
|
|
51
|
+
|
|
52
|
+
| Component | Purpose |
|
|
53
|
+
|-----------|---------|
|
|
54
|
+
| `marquee/` | Infinite scrolling text |
|
|
55
|
+
|
|
56
|
+
## Best Practices
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// ✅ Always use custom Image
|
|
60
|
+
import { Image } from '@/components/ui/image'
|
|
61
|
+
<Image src="/photo.jpg" alt="Photo" aspectRatio={16/9} />
|
|
62
|
+
|
|
63
|
+
// ✅ Always use custom Link
|
|
64
|
+
import { Link } from '@/components/ui/link'
|
|
65
|
+
<Link href="/about">Internal</Link>
|
|
66
|
+
<Link href="https://example.com">External</Link>
|
|
67
|
+
|
|
68
|
+
// ✅ CSS Modules + Tailwind
|
|
69
|
+
import s from './component.module.css'
|
|
70
|
+
<div className={cn(s.wrapper, 'flex items-center')} />
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Related
|
|
74
|
+
|
|
75
|
+
- [Image docs](ui/image/README.md)
|
|
76
|
+
- [Real Viewport docs](ui/real-viewport/README.md)
|
|
77
|
+
- [WebGL Components](../lib/webgl/README.md)
|