@draekien/create-d9-app 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +9 -2
- package/dist/index.js +53 -0
- package/index.js +11 -0
- package/package.json +36 -3
- package/templates/base/.claude/settings.json +36 -0
- package/templates/base/.codex/hooks.json +30 -0
- package/templates/base/.intent/hooks/intent-claude-gate.mjs +278 -0
- package/templates/base/.intent/hooks/intent-codex-gate.mjs +278 -0
- package/templates/base/AGENTS.md +28 -0
- package/templates/base/CLAUDE.md +1 -0
- package/templates/base/_gitignore +7 -0
- package/templates/base/apps/web/package.json +38 -0
- package/templates/base/apps/web/src/examples/form.test.tsx +10 -0
- package/templates/base/apps/web/src/examples/form.tsx +35 -0
- package/templates/base/apps/web/src/examples/greeting.ts +11 -0
- package/templates/base/apps/web/src/examples/hotkeys.test.tsx +7 -0
- package/templates/base/apps/web/src/examples/hotkeys.tsx +10 -0
- package/templates/base/apps/web/src/examples/query.test.tsx +18 -0
- package/templates/base/apps/web/src/examples/query.tsx +8 -0
- package/templates/base/apps/web/src/examples/table.test.tsx +7 -0
- package/templates/base/apps/web/src/examples/table.tsx +47 -0
- package/templates/base/apps/web/src/home.test.tsx +18 -0
- package/templates/base/apps/web/src/home.tsx +21 -0
- package/templates/base/apps/web/src/routeTree.gen.ts +68 -0
- package/templates/base/apps/web/src/router.tsx +20 -0
- package/templates/base/apps/web/src/routes/__root.tsx +26 -0
- package/templates/base/apps/web/src/routes/index.tsx +10 -0
- package/templates/base/apps/web/tsconfig.json +12 -0
- package/templates/base/apps/web/vite.config.ts +20 -0
- package/templates/base/package.json +28 -0
- package/templates/base/pnpm-workspace.yaml +7 -0
- package/templates/base/scripts/check-comments.ts +100 -0
- package/templates/base/tsconfig.json +13 -0
- package/templates/base/turbo.json +23 -0
- package/templates/tools/biome/.biome/effect-cleanup.grit +8 -0
- package/templates/tools/biome/.biome/handler-naming.grit +9 -0
- package/templates/tools/biome/.biome/no-default-props.grit +5 -0
- package/templates/tools/biome/.biome/no-derived-state-effect.grit +7 -0
- package/templates/tools/biome/.biome/no-double-assertion.grit +5 -0
- package/templates/tools/biome/.biome/no-fetch-in-effect.grit +6 -0
- package/templates/tools/biome/.biome/no-impure-render.grit +8 -0
- package/templates/tools/biome/.biome/no-inline-query-key.grit +7 -0
- package/templates/tools/biome/.biome/no-query-client-in-render.grit +7 -0
- package/templates/tools/biome/.biome/no-refetch-on-success.grit +7 -0
- package/templates/tools/biome/.biome/no-try-around-jsx.grit +9 -0
- package/templates/tools/biome/.biome/query-client-stale-time.grit +8 -0
- package/templates/tools/biome/biome.json +156 -0
- package/templates/tools/biome/package.json +8 -0
- package/templates/tools/lefthook/lefthook.yml +6 -0
- package/templates/tools/lefthook/package.json +5 -0
- package/templates/tools/shadcn/packages/ui/components.json +22 -0
- package/templates/tools/shadcn/packages/ui/package.json +33 -0
- package/templates/tools/shadcn/packages/ui/src/components/button.tsx +56 -0
- package/templates/tools/shadcn/packages/ui/src/lib/utils.ts +1 -0
- package/templates/tools/shadcn/packages/ui/src/styles/globals.css +87 -0
- package/templates/tools/shadcn/packages/ui/tsconfig.json +10 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { QueryClient } from "@tanstack/react-query"
|
|
2
|
+
import { createRootRouteWithContext, HeadContent, Outlet, Scripts } from "@tanstack/react-router"
|
|
3
|
+
// biome-ignore lint/correctness/noUnresolvedImports: vite ?url query resolves to the stylesheet url, which Biome cannot resolve
|
|
4
|
+
import appCss from "@workspace/ui/globals.css?url"
|
|
5
|
+
|
|
6
|
+
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
|
7
|
+
head: () => ({
|
|
8
|
+
meta: [
|
|
9
|
+
{ charSet: "utf-8" },
|
|
10
|
+
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
|
11
|
+
{ title: "App" },
|
|
12
|
+
],
|
|
13
|
+
links: [{ rel: "stylesheet", href: appCss }],
|
|
14
|
+
}),
|
|
15
|
+
component: () => (
|
|
16
|
+
<html lang="en">
|
|
17
|
+
<head>
|
|
18
|
+
<HeadContent />
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<Outlet />
|
|
22
|
+
<Scripts />
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|
|
25
|
+
),
|
|
26
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router"
|
|
2
|
+
import { greetingOptions } from "../examples/greeting.ts"
|
|
3
|
+
import { Home } from "../home.tsx"
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/")({
|
|
6
|
+
loader: async ({ context }) => {
|
|
7
|
+
await context.queryClient.query(greetingOptions)
|
|
8
|
+
},
|
|
9
|
+
component: Home,
|
|
10
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"include": ["src", "vite.config.ts", "vitest.config.ts"],
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"types": ["vite/client", "node"],
|
|
6
|
+
"paths": {
|
|
7
|
+
"@workspace/ui/*": ["../../packages/ui/src/*"]
|
|
8
|
+
},
|
|
9
|
+
"noUnusedLocals": true,
|
|
10
|
+
"noUnusedParameters": true
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/// <reference types="vitest/config" />
|
|
2
|
+
import babel from "@rolldown/plugin-babel"
|
|
3
|
+
import tailwindcss from "@tailwindcss/vite"
|
|
4
|
+
import { tanstackStart } from "@tanstack/react-start/plugin/vite"
|
|
5
|
+
import viteReact, { reactCompilerPreset } from "@vitejs/plugin-react"
|
|
6
|
+
import { defineConfig } from "vite"
|
|
7
|
+
|
|
8
|
+
const isTest = process.env.VITEST === "true"
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
server: { port: 3000 },
|
|
12
|
+
resolve: { tsconfigPaths: true },
|
|
13
|
+
plugins: [
|
|
14
|
+
!isTest && tailwindcss(),
|
|
15
|
+
!isTest && tanstackStart(),
|
|
16
|
+
viteReact(),
|
|
17
|
+
babel({ presets: [reactCompilerPreset()] }),
|
|
18
|
+
],
|
|
19
|
+
test: { environment: "node" },
|
|
20
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"packageManager": "pnpm@11.25.0",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.18.0"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "turbo run build",
|
|
11
|
+
"check": "turbo run check lint:comments lint:biome",
|
|
12
|
+
"lint:comments": "node scripts/check-comments.ts",
|
|
13
|
+
"typecheck": "turbo run typecheck",
|
|
14
|
+
"test": "turbo run test",
|
|
15
|
+
"dev": "turbo run dev"
|
|
16
|
+
},
|
|
17
|
+
"intent": {
|
|
18
|
+
"skills": [
|
|
19
|
+
"*"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@babel/parser": "8.0.7",
|
|
24
|
+
"@tanstack/intent": "0.5.5",
|
|
25
|
+
"turbo": "2.11.7",
|
|
26
|
+
"typescript": "7.0.2"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process"
|
|
2
|
+
import { readdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { parse } from "@babel/parser"
|
|
4
|
+
|
|
5
|
+
const directive = /^(\/|\s*biome-ignore|\s*@ts-)/
|
|
6
|
+
const scopeRoots = ["apps/", "packages/"]
|
|
7
|
+
const ignoredDirs = new Set(["node_modules", "dist", ".output", ".nitro", ".tanstack", ".turbo"])
|
|
8
|
+
const generated = /(^|\/)[^/]*\.gen\.tsx?$/
|
|
9
|
+
|
|
10
|
+
const inScope = (file: string) =>
|
|
11
|
+
/\.tsx?$/.test(file) &&
|
|
12
|
+
scopeRoots.some((root) => file.startsWith(root)) &&
|
|
13
|
+
!generated.test(file) &&
|
|
14
|
+
!file.split("/").some((part) => ignoredDirs.has(part))
|
|
15
|
+
|
|
16
|
+
const walk = (dir: string): string[] =>
|
|
17
|
+
readdirSync(dir, { withFileTypes: true }).flatMap((entry) =>
|
|
18
|
+
ignoredDirs.has(entry.name)
|
|
19
|
+
? []
|
|
20
|
+
: entry.isDirectory()
|
|
21
|
+
? walk(`${dir}/${entry.name}`)
|
|
22
|
+
: [`${dir}/${entry.name}`]
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const listFiles = () => {
|
|
26
|
+
try {
|
|
27
|
+
return execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard"], {
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
30
|
+
}).split("\n")
|
|
31
|
+
} catch {
|
|
32
|
+
return scopeRoots.flatMap((root) => {
|
|
33
|
+
try {
|
|
34
|
+
return walk(root.slice(0, -1))
|
|
35
|
+
} catch {
|
|
36
|
+
return []
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const fix = process.argv.includes("--fix")
|
|
43
|
+
const requested = process.argv.slice(2).filter((arg) => arg !== "--fix")
|
|
44
|
+
|
|
45
|
+
const files = (requested.length > 0 ? requested : listFiles())
|
|
46
|
+
.map((file) => file.replaceAll("\\", "/"))
|
|
47
|
+
.filter(inScope)
|
|
48
|
+
|
|
49
|
+
const lineComments = (file: string, source: string) =>
|
|
50
|
+
(
|
|
51
|
+
parse(source, {
|
|
52
|
+
sourceType: "module",
|
|
53
|
+
errorRecovery: true,
|
|
54
|
+
plugins: file.endsWith(".tsx") ? ["typescript", "jsx"] : ["typescript"],
|
|
55
|
+
}).comments ?? []
|
|
56
|
+
).flatMap(({ type, value, start, end, loc }) =>
|
|
57
|
+
type === "CommentLine" && !directive.test(value) && start != null && end != null && loc
|
|
58
|
+
? [{ start, end, line: loc.start.line, column: loc.start.column }]
|
|
59
|
+
: []
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const removeComment = (source: string, start: number, end: number) => {
|
|
63
|
+
const lineStart = source.lastIndexOf("\n", start - 1) + 1
|
|
64
|
+
const before = source.slice(lineStart, start)
|
|
65
|
+
if (before.trim() === "") {
|
|
66
|
+
const lineEnd = source.indexOf("\n", end)
|
|
67
|
+
return source.slice(0, lineStart) + (lineEnd === -1 ? "" : source.slice(lineEnd + 1))
|
|
68
|
+
}
|
|
69
|
+
return source.slice(0, lineStart) + before.trimEnd() + source.slice(end)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let violations = 0
|
|
73
|
+
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
const source = readFileSync(file, "utf8")
|
|
76
|
+
const comments = lineComments(file, source)
|
|
77
|
+
if (comments.length === 0) continue
|
|
78
|
+
violations += comments.length
|
|
79
|
+
|
|
80
|
+
if (fix) {
|
|
81
|
+
const fixed = comments.reduceRight(
|
|
82
|
+
(text, comment) => removeComment(text, comment.start, comment.end),
|
|
83
|
+
source
|
|
84
|
+
)
|
|
85
|
+
writeFileSync(file, fixed)
|
|
86
|
+
console.log(`${file}: removed ${comments.length} comment(s)`)
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const { line, column } of comments) {
|
|
91
|
+
console.log(
|
|
92
|
+
`${file}:${line}:${column + 1} line comment; rename or document the public API with JSDoc instead`
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!fix && violations > 0) {
|
|
98
|
+
console.error(`\n${violations} line comment(s). Run \`pnpm lint:comments --fix\` to remove them.`)
|
|
99
|
+
process.exit(1)
|
|
100
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"allowImportingTsExtensions": true
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://turborepo.dev/schema.json",
|
|
3
|
+
"agentGuidance": false,
|
|
4
|
+
"tasks": {
|
|
5
|
+
"build": {
|
|
6
|
+
"dependsOn": ["^build"],
|
|
7
|
+
"outputs": ["dist/**"]
|
|
8
|
+
},
|
|
9
|
+
"check": {},
|
|
10
|
+
"//#lint:biome": {},
|
|
11
|
+
"//#lint:comments": {
|
|
12
|
+
"inputs": ["apps/**", "packages/**", "scripts/check-comments.ts"]
|
|
13
|
+
},
|
|
14
|
+
"typecheck": {
|
|
15
|
+
"dependsOn": ["^typecheck"]
|
|
16
|
+
},
|
|
17
|
+
"test": {},
|
|
18
|
+
"dev": {
|
|
19
|
+
"cache": false,
|
|
20
|
+
"persistent": true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
`useEffect($callback, $deps)` as $effect where {
|
|
4
|
+
$callback <: contains or { `$_.addEventListener($_, $_)`, `setInterval($_, $_)`, `setTimeout($_, $_)`, `$_.subscribe($_)` },
|
|
5
|
+
$callback <: JsArrowFunctionExpression(body = JsFunctionBody(statements = $statements)),
|
|
6
|
+
$statements <: not some `return $cleanup`,
|
|
7
|
+
register_diagnostic(span = $effect, message = "Return a cleanup function from an effect that sets up a listener, timer, or subscription.")
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
JsxAttribute() as $attr where {
|
|
4
|
+
$attr <: r"on[A-Z]\w*=\{\w+\}",
|
|
5
|
+
$attr <: not r"on[A-Z]\w*=\{handle[A-Z]\w*\}",
|
|
6
|
+
$attr <: not r"on[A-Z]\w*=\{on[A-Z]\w*\}",
|
|
7
|
+
$attr <: not r"on[A-Z]\w*=\{set[A-Z]\w*\}",
|
|
8
|
+
register_diagnostic(span = $attr, message = "Name the function passed to an on<Event> prop handle<Action>.", severity = "error")
|
|
9
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
`useEffect($callback, $deps)` as $effect where {
|
|
4
|
+
$callback <: contains `$setter($_)` where { $setter <: r"set[A-Z]\w*" },
|
|
5
|
+
$callback <: not contains or { `$_.then($_)`, `await $_`, `setTimeout($_, $_)`, `setInterval($_, $_)`, `$_.addEventListener($_, $_)`, `$_.subscribe($_)` },
|
|
6
|
+
register_diagnostic(span = $effect, message = "Compute derived values during render instead of syncing them with useEffect.", severity = "error")
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
or { `Math.random()`, `Date.now()`, `new Date()`, `DateTime.now()` } as $call where {
|
|
4
|
+
$call <: within JsFunctionDeclaration(id = $component) where { $component <: r"[A-Z]\w*" },
|
|
5
|
+
$call <: not within JsArrowFunctionExpression(),
|
|
6
|
+
$call <: not within JsFunctionDeclaration(id = $handler) where { $handler <: r"handle[A-Z]\w*" },
|
|
7
|
+
register_diagnostic(span = $call, message = "Non-deterministic value during render; move it into an effect, handler, or useState initialiser.", severity = "error")
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
`new QueryClient($_)` as $client where {
|
|
4
|
+
$client <: within JsFunctionDeclaration(id = $component) where { $component <: r"[A-Z]\w*" },
|
|
5
|
+
$client <: not within `useState($_)`,
|
|
6
|
+
register_diagnostic(span = $client, message = "Create the QueryClient once at module scope or in a useState initialiser.")
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
JsPropertyObjectMember(name = $name, value = $callback) as $member where {
|
|
4
|
+
$name <: or { `onSuccess`, `onSettled` },
|
|
5
|
+
$callback <: contains or { `refetch()`, `$_.refetch()` },
|
|
6
|
+
register_diagnostic(span = $member, message = "Invalidate the affected queries with queryClient.invalidateQueries instead of calling refetch.")
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
JsTryStatement(body = $block) as $try where {
|
|
4
|
+
$block <: contains or { JsxElement(), JsxSelfClosingElement(), JsxFragment() },
|
|
5
|
+
$try <: within JsFunctionDeclaration(id = $component) where { $component <: r"[A-Z]\w*" },
|
|
6
|
+
$try <: not within JsArrowFunctionExpression(),
|
|
7
|
+
$try <: not within JsFunctionDeclaration(id = $handler) where { $handler <: r"handle[A-Z]\w*" },
|
|
8
|
+
register_diagnostic(span = $try, message = "try/catch cannot catch render errors; wrap the subtree in an ErrorBoundary.")
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
`new QueryClient()`,
|
|
5
|
+
`new QueryClient($options)` where { $options <: not contains `staleTime: $_` }
|
|
6
|
+
} as $client where {
|
|
7
|
+
register_diagnostic(span = $client, message = "Set a default staleTime on the QueryClient; the built-in 0 refetches on every mount.")
|
|
8
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.5.15/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"includes": ["**", "!**/routeTree.gen.ts", "!.biome", "!.intent", "!.claude", "!.codex"]
|
|
10
|
+
},
|
|
11
|
+
"formatter": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"indentStyle": "space",
|
|
14
|
+
"indentWidth": 2,
|
|
15
|
+
"lineWidth": 100,
|
|
16
|
+
"lineEnding": "lf",
|
|
17
|
+
"trailingNewline": true
|
|
18
|
+
},
|
|
19
|
+
"linter": {
|
|
20
|
+
"enabled": true,
|
|
21
|
+
"rules": {
|
|
22
|
+
"preset": "recommended",
|
|
23
|
+
"correctness": {
|
|
24
|
+
"noReactPropAssignments": "error"
|
|
25
|
+
},
|
|
26
|
+
"style": {
|
|
27
|
+
"noInferrableTypes": "error",
|
|
28
|
+
"useReadonlyClassProperties": "error",
|
|
29
|
+
"useThrowOnlyError": "error",
|
|
30
|
+
"useConsistentTypeDefinitions": {
|
|
31
|
+
"level": "error",
|
|
32
|
+
"options": {
|
|
33
|
+
"style": "type"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"useFilenamingConvention": {
|
|
37
|
+
"level": "error",
|
|
38
|
+
"options": {
|
|
39
|
+
"filenameCases": ["kebab-case"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"suspicious": {
|
|
44
|
+
"noVar": "error"
|
|
45
|
+
},
|
|
46
|
+
"nursery": {
|
|
47
|
+
"noBaseToString": "error",
|
|
48
|
+
"noIdenticalTestTitle": "error",
|
|
49
|
+
"noImpliedEval": "error",
|
|
50
|
+
"noLoopFunc": "error",
|
|
51
|
+
"noMisusedPromises": "error",
|
|
52
|
+
"noReturnInFinally": "error",
|
|
53
|
+
"noUnmodifiedLoopCondition": "error",
|
|
54
|
+
"noUnnecessaryTemplateExpression": "error",
|
|
55
|
+
"noUnsafePlusOperands": "error",
|
|
56
|
+
"noUnsafeTypeAssertion": "error",
|
|
57
|
+
"noUselessTypeConversion": "error",
|
|
58
|
+
"useArraySome": "error",
|
|
59
|
+
"useAwaitThenable": "error",
|
|
60
|
+
"useConsistentTestIt": "error",
|
|
61
|
+
"useExhaustiveSwitchCases": "error",
|
|
62
|
+
"useExpect": "error",
|
|
63
|
+
"useIncludes": "error",
|
|
64
|
+
"useNullishCoalescing": "error",
|
|
65
|
+
"useReactCompiler": "error",
|
|
66
|
+
"useReduceTypeParameter": "error",
|
|
67
|
+
"useRegexpExec": "error",
|
|
68
|
+
"useRegexpTest": "error",
|
|
69
|
+
"useStringStartsEndsWith": "error",
|
|
70
|
+
"useTestHooksOnTop": "error"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"domains": {
|
|
74
|
+
"project": "all",
|
|
75
|
+
"react": "all",
|
|
76
|
+
"test": "all",
|
|
77
|
+
"turborepo": "all",
|
|
78
|
+
"types": "all"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"javascript": {
|
|
82
|
+
"formatter": {
|
|
83
|
+
"quoteStyle": "double",
|
|
84
|
+
"trailingCommas": "es5",
|
|
85
|
+
"semicolons": "asNeeded"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"assist": {
|
|
89
|
+
"actions": {
|
|
90
|
+
"source": {
|
|
91
|
+
"organizeImports": "on",
|
|
92
|
+
"noDuplicateClasses": "on",
|
|
93
|
+
"useSortedProperties": "on"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"plugins": [
|
|
98
|
+
".biome/no-derived-state-effect.grit",
|
|
99
|
+
".biome/no-double-assertion.grit",
|
|
100
|
+
".biome/no-fetch-in-effect.grit",
|
|
101
|
+
{
|
|
102
|
+
"path": ".biome/no-inline-query-key.grit",
|
|
103
|
+
"includes": ["**/*.{ts,tsx}", "!**/*.test.{ts,tsx}"]
|
|
104
|
+
},
|
|
105
|
+
".biome/no-query-client-in-render.grit",
|
|
106
|
+
".biome/effect-cleanup.grit",
|
|
107
|
+
".biome/no-refetch-on-success.grit",
|
|
108
|
+
{
|
|
109
|
+
"path": ".biome/query-client-stale-time.grit",
|
|
110
|
+
"includes": ["**/*.{ts,tsx}", "!**/*.test.{ts,tsx}"]
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"path": ".biome/no-try-around-jsx.grit",
|
|
114
|
+
"includes": ["**/*.tsx"]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"path": ".biome/handler-naming.grit",
|
|
118
|
+
"includes": ["**/*.tsx"]
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"path": ".biome/no-default-props.grit",
|
|
122
|
+
"includes": ["**/*.tsx"]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"path": ".biome/no-impure-render.grit",
|
|
126
|
+
"includes": ["**/*.tsx"]
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"css": {
|
|
130
|
+
"parser": {
|
|
131
|
+
"tailwindDirectives": true
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"overrides": [
|
|
135
|
+
{
|
|
136
|
+
"includes": ["**/*.tsx"],
|
|
137
|
+
"linter": {
|
|
138
|
+
"rules": {
|
|
139
|
+
"style": {
|
|
140
|
+
"noDefaultExport": "error"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"includes": ["**/src/routes/**/$*.tsx", "**/src/routes/**/*.$.tsx"],
|
|
147
|
+
"linter": {
|
|
148
|
+
"rules": {
|
|
149
|
+
"style": {
|
|
150
|
+
"useFilenamingConvention": "off"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-mira",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/styles/globals.css",
|
|
9
|
+
"baseColor": "zinc",
|
|
10
|
+
"cssVariables": true
|
|
11
|
+
},
|
|
12
|
+
"iconLibrary": "hugeicons",
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@workspace/ui/components",
|
|
15
|
+
"utils": "@workspace/ui/lib/utils",
|
|
16
|
+
"hooks": "@workspace/ui/hooks",
|
|
17
|
+
"lib": "@workspace/ui/lib",
|
|
18
|
+
"ui": "@workspace/ui/components"
|
|
19
|
+
},
|
|
20
|
+
"menuColor": "default",
|
|
21
|
+
"menuAccent": "bold"
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@workspace/ui",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"typecheck": "tsc --noEmit"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@base-ui/react": "1.8.0",
|
|
11
|
+
"@hugeicons/core-free-icons": "4.3.5",
|
|
12
|
+
"@hugeicons/react": "1.1.10",
|
|
13
|
+
"class-variance-authority": "0.7.1",
|
|
14
|
+
"cn": "0.4.0",
|
|
15
|
+
"react": "19.3.0",
|
|
16
|
+
"react-dom": "19.3.0",
|
|
17
|
+
"tw-animate-css": "1.4.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "22.20.5",
|
|
21
|
+
"@types/react": "19.3.0",
|
|
22
|
+
"@types/react-dom": "19.3.0",
|
|
23
|
+
"shadcn": "4.21.4",
|
|
24
|
+
"tailwindcss": "4.3.3",
|
|
25
|
+
"typescript": "7.0.2"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
"./globals.css": "./src/styles/globals.css",
|
|
29
|
+
"./lib/*": "./src/lib/*",
|
|
30
|
+
"./components/*": "./src/components/*",
|
|
31
|
+
"./hooks/*": "./src/hooks/*"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "cn"
|
|
4
|
+
|
|
5
|
+
const buttonVariants = cva(
|
|
6
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
11
|
+
outline:
|
|
12
|
+
"border-border hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-input/30",
|
|
13
|
+
secondary:
|
|
14
|
+
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
15
|
+
ghost:
|
|
16
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
17
|
+
destructive:
|
|
18
|
+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
19
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
default:
|
|
23
|
+
"h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
24
|
+
xs: "h-5 gap-1 rounded-sm px-2 text-[0.625rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-2.5",
|
|
25
|
+
sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
26
|
+
lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-4",
|
|
27
|
+
icon: "size-7 [&_svg:not([class*='size-'])]:size-3.5",
|
|
28
|
+
"icon-xs": "size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5",
|
|
29
|
+
"icon-sm": "size-6 [&_svg:not([class*='size-'])]:size-3",
|
|
30
|
+
"icon-lg": "size-8 [&_svg:not([class*='size-'])]:size-4",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "default",
|
|
35
|
+
size: "default",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
function Button({
|
|
41
|
+
className,
|
|
42
|
+
variant = "default",
|
|
43
|
+
size = "default",
|
|
44
|
+
...props
|
|
45
|
+
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
|
46
|
+
return (
|
|
47
|
+
<ButtonPrimitive
|
|
48
|
+
data-slot="button"
|
|
49
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// biome-ignore lint/style/useComponentExportOnlyModules: buttonVariants is the shadcn-generated companion export of Button
|
|
56
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { cn } from "cn"
|