Compare commits
8 Commits
462866b8f2
...
v0.0.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7923514c5c | ||
|
|
7cd943cced | ||
|
|
0016e2104c | ||
|
|
752ca147ff | ||
|
|
56b57aa8de | ||
|
|
ae823b755e | ||
|
|
73dc856821 | ||
|
|
e35a038f09 |
39
.gitea/workflows/publish.yml
Normal file
39
.gitea/workflows/publish.yml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Publish npm package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: x86
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Check tag matches package version
|
||||||
|
run: |
|
||||||
|
PACKAGE_VERSION="v$(node -p "require('./package.json').version")"
|
||||||
|
TAG_NAME="${GITHUB_REF_NAME:-${GITHUB_REF#refs/tags/}}"
|
||||||
|
test "$PACKAGE_VERSION" = "$TAG_NAME"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: bun run build
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
run: npm publish --access public
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
15
README.md
15
README.md
@@ -1,8 +1,11 @@
|
|||||||
# create-glstack
|
# create-glstack
|
||||||
|
|
||||||
Scaffold a fullstack application with a Go backend, SvelteKit frontend, and PostgreSQL database, connected via Connect RPC.
|
Scaffold a fullstack application with a Go backend, PostgreSQL database, and your choice of frontend framework, connected via Connect RPC.
|
||||||
|
|
||||||
> **Work in progress** -- additional frontend frameworks and features are planned for future releases.
|
Supported frontends:
|
||||||
|
|
||||||
|
- [SvelteKit](https://kit.svelte.dev/)
|
||||||
|
- [Solid Start](https://start.solidjs.com/)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -15,7 +18,7 @@ bun create glstack
|
|||||||
A monorepo with the following structure:
|
A monorepo with the following structure:
|
||||||
|
|
||||||
- **`services/api`** -- Go backend using [Cobra](https://github.com/spf13/cobra) for CLI, [pgx](https://github.com/jackc/pgx) for Postgres, and [Connect RPC](https://connectrpc.com/) for the API layer
|
- **`services/api`** -- Go backend using [Cobra](https://github.com/spf13/cobra) for CLI, [pgx](https://github.com/jackc/pgx) for Postgres, and [Connect RPC](https://connectrpc.com/) for the API layer
|
||||||
- **`apps/web`** -- SvelteKit frontend with [shadcn-svelte](https://shadcn-svelte.com/), TailwindCSS, [TanStack Query](https://tanstack.com/query), and [Paraglide](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) for i18n
|
- **`apps/web`** -- Frontend app (SvelteKit or Solid Start) with TailwindCSS and [TanStack Query](https://tanstack.com/query)
|
||||||
- **`packages/proto`** -- Protobuf service definitions with [Buf](https://buf.build/) for codegen (Go + TypeScript)
|
- **`packages/proto`** -- Protobuf service definitions with [Buf](https://buf.build/) for codegen (Go + TypeScript)
|
||||||
- **`packages/rpc`** -- Generated TypeScript Connect RPC client shared across frontend apps
|
- **`packages/rpc`** -- Generated TypeScript Connect RPC client shared across frontend apps
|
||||||
|
|
||||||
@@ -23,16 +26,16 @@ A monorepo with the following structure:
|
|||||||
|
|
||||||
The template includes a [devenv.nix](https://devenv.sh/) configuration that sets up:
|
The template includes a [devenv.nix](https://devenv.sh/) configuration that sets up:
|
||||||
|
|
||||||
- Go, TypeScript, Bun
|
- Go, TypeScript, Bun, Node.js
|
||||||
- PostgreSQL with an auto-provisioned database
|
- PostgreSQL with an auto-provisioned database
|
||||||
- File watchers for protobuf, SQL, and frontend hot reload
|
- File watchers for protobuf, SQL, and frontend hot reload with proper process dependency ordering
|
||||||
- [Air](https://github.com/air-verse/air) for Go live reload
|
- [Air](https://github.com/air-verse/air) for Go live reload
|
||||||
- [sqlc](https://sqlc.dev/) for type-safe SQL
|
- [sqlc](https://sqlc.dev/) for type-safe SQL
|
||||||
- [dbmate](https://github.com/amacneil/dbmate) for database migrations
|
- [dbmate](https://github.com/amacneil/dbmate) for database migrations
|
||||||
|
|
||||||
### Starter example
|
### Starter example
|
||||||
|
|
||||||
The generated project includes a working Todo CRUD example wired end-to-end: protobuf schema, Go service implementation, SQL queries, and a SvelteKit UI.
|
The generated project includes a working Todo CRUD example wired end-to-end: protobuf schema, Go service implementation, SQL queries, and frontend UI.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
4
bun.lock
4
bun.lock
@@ -6,7 +6,7 @@
|
|||||||
"name": "glstack",
|
"name": "glstack",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^1.2.0",
|
"@clack/prompts": "^1.2.0",
|
||||||
"@gregorlohaus/tdir": "^0.1.1",
|
"@gregorlohaus/tdir": "^0.2.0",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
"@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="],
|
"@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="],
|
||||||
|
|
||||||
"@gregorlohaus/tdir": ["@gregorlohaus/tdir@0.1.1", "", { "peerDependencies": { "zod": "^4" } }, "sha512-4NlHif5Pn6Vh1TzCj8B1d+pz8ab5/CodC2Cq9HVr1wHdFlgXM/yjtZEDLdBMtwqz1n1oCCEjzV7XYbin2ywsjQ=="],
|
"@gregorlohaus/tdir": ["@gregorlohaus/tdir@0.2.0", "", { "peerDependencies": { "zod": "^4" }, "bin": { "tdir": "dist/cli.js" } }, "sha512-LwISQC7iARezu7SQZJk9TV8Sqp8I/fVMssH18lWL1KD/ETpWp2LwwTzm/j9aWcs+Eo5OBQctbqFTafo9Nfue+A=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
||||||
|
|
||||||
|
|||||||
26
index.ts
26
index.ts
@@ -5,7 +5,7 @@ import { initRenderer } from '@gregorlohaus/tdir'
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
p.intro("create-glstack");
|
p.intro(`create-glstack ${process.env.GLSTACK_DEV && 'isDev'}`);
|
||||||
|
|
||||||
const project = await p.group(
|
const project = await p.group(
|
||||||
{
|
{
|
||||||
@@ -18,6 +18,16 @@ const project = await p.group(
|
|||||||
return undefined
|
return undefined
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
frontend: async () =>
|
||||||
|
await p.select({
|
||||||
|
message: 'Pick a frontend framework.',
|
||||||
|
options: [
|
||||||
|
{value: "svelte-kit", label:"SvelteKit"},
|
||||||
|
{value: "solid-start", label:"SolidStart"},
|
||||||
|
{value: "none", label:"None"}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
,
|
||||||
goprefix: () =>
|
goprefix: () =>
|
||||||
p.text({
|
p.text({
|
||||||
message: "What would you like to use as a go package prefix?",
|
message: "What would you like to use as a go package prefix?",
|
||||||
@@ -35,22 +45,26 @@ const project = await p.group(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const createRenderer = initRenderer(path.join(import.meta.dir, '..', 'template'))
|
|
||||||
|
const templateDir = (process.env.GLSTACK_DEV == 'true' ? './template' : path.join(import.meta.dir, '..', 'template'))
|
||||||
|
const createRenderer = initRenderer(templateDir)
|
||||||
const render = createRenderer(z.object({
|
const render = createRenderer(z.object({
|
||||||
project: z.object({
|
project: z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
goprefix: z.string()
|
goprefix: z.string(),
|
||||||
|
frontend: z.string(),
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
const destDir = path.join("./",project.name);
|
const destDir = path.join("./",project.name);
|
||||||
render(destDir,{project})
|
render(destDir,{project},{reverseMap: true})
|
||||||
// TODO: template rendering — copy/generate files into destDir
|
|
||||||
|
|
||||||
const s = p.spinner();
|
const s = p.spinner();
|
||||||
s.start("Installing dependencies");
|
s.start("Installing dependencies");
|
||||||
await Bun.$`bun install`.cwd(path.join(destDir)).quiet();
|
await Bun.$`bun install`.cwd(path.join(destDir)).quiet();
|
||||||
await Bun.$`bun install`.cwd(path.join(destDir,'packages','rpc')).quiet();
|
await Bun.$`bun install`.cwd(path.join(destDir,'packages','rpc')).quiet();
|
||||||
await Bun.$`bun install`.cwd(path.join(destDir,'apps','web')).quiet();
|
if (project.frontend !== "none") {
|
||||||
|
await Bun.$`bun install`.cwd(path.join(destDir,'apps','web')).quiet();
|
||||||
|
}
|
||||||
s.stop("Dependencies installed.");
|
s.stop("Dependencies installed.");
|
||||||
|
|
||||||
p.outro("You're all set!");
|
p.outro("You're all set!");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "create-glstack",
|
"name": "create-glstack",
|
||||||
"version": "0.0.3",
|
"version": "0.0.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^1.2.0",
|
"@clack/prompts": "^1.2.0",
|
||||||
"@gregorlohaus/tdir": "^0.1.1",
|
"@gregorlohaus/tdir": "^0.2.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
template/apps/<@if(eq(context.project.frontend,"solid-start"))>web/.gitignore
vendored
Normal file
28
template/apps/<@if(eq(context.project.frontend,"solid-start"))>web/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
dist
|
||||||
|
.wrangler
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
.netlify
|
||||||
|
.vinxi
|
||||||
|
app.config.timestamp_*.js
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Temp
|
||||||
|
gitignore
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "example-basic",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"start": "vite start",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@<@var(context.project.name)>/rpc": "workspace:*",
|
||||||
|
"@solidjs/meta": "^0.29.4",
|
||||||
|
"@solidjs/router": "^0.15.0",
|
||||||
|
"@solidjs/start": "2.0.0-alpha.2",
|
||||||
|
"@solidjs/vite-plugin-nitro-2": "^0.1.0",
|
||||||
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
|
"@tanstack/query-db-collection": "^1.0.35",
|
||||||
|
"@tanstack/solid-db": "^0.2.18",
|
||||||
|
"@tanstack/solid-query": "^5.96.2",
|
||||||
|
"solid-js": "^1.9.5",
|
||||||
|
"tailwindcss": "^4.2.2",
|
||||||
|
"vite": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"devDependencies": {}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 664 B |
@@ -0,0 +1,5 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { MetaProvider, Title } from "@solidjs/meta";
|
||||||
|
import { Router } from "@solidjs/router";
|
||||||
|
import { FileRoutes } from "@solidjs/start/router";
|
||||||
|
import { Suspense } from "solid-js";
|
||||||
|
import { createCollection } from '@tanstack/solid-db'
|
||||||
|
import { queryCollectionOptions } from '@tanstack/query-db-collection'
|
||||||
|
import type { Todo, ExtractPayload } from '@<@var(context.project.name)>/rpc'
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
|
||||||
|
import { getRouter } from "./lib/getRouter"
|
||||||
|
import "./app.css";
|
||||||
|
import { TodoCollectionProvider } from "./context/todocollection/provider";
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
|
||||||
|
})
|
||||||
|
const router = getRouter();
|
||||||
|
export default function App() {
|
||||||
|
const todosCollection = createCollection(
|
||||||
|
queryCollectionOptions({
|
||||||
|
queryKey: ["todos"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const todos = await router.todos.listTodos({})
|
||||||
|
return todos.todos as ExtractPayload<Todo>[];
|
||||||
|
},
|
||||||
|
queryClient,
|
||||||
|
getKey: (item) => item.id ? item.id : crypto.randomUUID(),
|
||||||
|
onInsert: async ({ transaction }) => {
|
||||||
|
Promise.all(transaction.mutations.map((m) => {
|
||||||
|
router.todos.createTodo({ todo: m.modified })
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
onDelete: async ({ transaction }) => {
|
||||||
|
Promise.all(transaction.mutations.map((m) => {
|
||||||
|
router.todos.deleteTodo({ todo: m.modified })
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
onUpdate: async ({ transaction }) => {
|
||||||
|
Promise.all(transaction.mutations.map((m) => {
|
||||||
|
router.todos.updateTodo({ todo: m.modified })
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<Router
|
||||||
|
root={props => (
|
||||||
|
<MetaProvider>
|
||||||
|
<QueryClientProvider client={queryClient} >
|
||||||
|
<TodoCollectionProvider value={todosCollection}>
|
||||||
|
<Suspense>{props.children}</Suspense>
|
||||||
|
</TodoCollectionProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</MetaProvider>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FileRoutes/>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { ExtractPayload, type Todo } from '@<@var(context.project.name)>/rpc'
|
||||||
|
import { createSignal } from 'solid-js';
|
||||||
|
import { useTodoCollection } from '~/context/todocollection/create';
|
||||||
|
|
||||||
|
export const CreateTodo = () => {
|
||||||
|
const [todoState, setTodoState] = createSignal<ExtractPayload<Todo>>({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
task: "",
|
||||||
|
done: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
let inputRef!: HTMLInputElement
|
||||||
|
|
||||||
|
const setTask = () => {
|
||||||
|
const todo = {...todoState()}
|
||||||
|
todo.task = inputRef.value;
|
||||||
|
setTodoState(todo)
|
||||||
|
}
|
||||||
|
|
||||||
|
const todoCollection = useTodoCollection()
|
||||||
|
|
||||||
|
const insertTodo = () => {
|
||||||
|
todoCollection.insert(todoState())
|
||||||
|
setTodoState({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
task: "",
|
||||||
|
done: false
|
||||||
|
})
|
||||||
|
inputRef.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input class="border rounded-md px-3 py-1" ref={inputRef} type='text' onInput={setTask} />
|
||||||
|
<button class="bg-teal-800 text-teal-100 p-1 rounded-md cursor-pointer" onClick={insertTodo}> create </button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useLiveQuery } from "@tanstack/solid-db";
|
||||||
|
import { useTodoCollection } from "~/context/todocollection/create";
|
||||||
|
import { Todo } from './Todo'
|
||||||
|
import { For } from 'solid-js'
|
||||||
|
|
||||||
|
export const ListTodos = () => {
|
||||||
|
const todoCollection = useTodoCollection()
|
||||||
|
const data = useLiveQuery((q) =>
|
||||||
|
q.from({todos: todoCollection})
|
||||||
|
)
|
||||||
|
const todos = () => {
|
||||||
|
const items = Array.from(data.state.values());
|
||||||
|
return items.sort((a: any, b: any) => {
|
||||||
|
if (a.done != b.done) return a.done ? -1 : 1
|
||||||
|
if (!a.done && !b.done) {
|
||||||
|
const adate = a.createdAt ? new Date(a.createdAt) : new Date()
|
||||||
|
const bdate = b.createdAt ? new Date(b.createdAt) : new Date()
|
||||||
|
return bdate.getTime() - adate.getTime()
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<For each={todos()} fallback={<div>Loading...</div>}>
|
||||||
|
{(item) => <Todo todo={item} />}
|
||||||
|
</For>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { ExtractPayload, type Todo as RpcTodo } from '@<@var(context.project.name)>/rpc'
|
||||||
|
import { createSignal, type JSX } from 'solid-js';
|
||||||
|
import { useTodoCollection } from '~/context/todocollection/create';
|
||||||
|
|
||||||
|
export const Todo = ({ todo }: { todo: ExtractPayload<RpcTodo> }) => {
|
||||||
|
const [todoState, setTodoState] = createSignal(todo)
|
||||||
|
const todoCollection = useTodoCollection()
|
||||||
|
let commitUpdateTimeoutId: NodeJS.Timeout|null = null;
|
||||||
|
const updateTask: JSX.EventHandlerUnion<HTMLInputElement, Event> = (e) => {
|
||||||
|
if (commitUpdateTimeoutId != null) {
|
||||||
|
clearTimeout(commitUpdateTimeoutId)
|
||||||
|
}
|
||||||
|
const todo = { ...todoState() };
|
||||||
|
todo.task = e.currentTarget.value
|
||||||
|
setTodoState(todo)
|
||||||
|
commitUpdateTimeoutId = setTimeout(() => {
|
||||||
|
todoCollection.update(todoState().id, (draft) => {
|
||||||
|
draft.task = todoState().task
|
||||||
|
})
|
||||||
|
},3000)
|
||||||
|
|
||||||
|
}
|
||||||
|
const updateDone: JSX.EventHandlerUnion<HTMLInputElement, Event> = (e) => {
|
||||||
|
const todo = { ...todoState() }
|
||||||
|
todo.done = e.currentTarget.checked
|
||||||
|
setTodoState(todo)
|
||||||
|
todoCollection.update(todoState().id, (draft) => {
|
||||||
|
draft.done = todoState().done
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const del = () => {
|
||||||
|
todoCollection.delete(todoState().id || "")
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col items-center border rounded-md p-5 gap-2">
|
||||||
|
<input class="text-center border rounded-md px-3 py-1" oninput={updateTask} type="text" value={todoState().task} />
|
||||||
|
<span> {(new Date(todoState().createdAt || "")).toLocaleString()}</span>
|
||||||
|
<span> {(new Date(todoState().updatesAt || "")).toLocaleString()}</span>
|
||||||
|
<input type="checkbox" onchange={updateDone} checked={todoState().done} />
|
||||||
|
<button class="bg-amber-800 text-amber-100 p-1 rounded-md cursor-pointer" onclick={del} class=""> delete </button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { createContext,useContext } from 'solid-js'
|
||||||
|
import { type CollectionImpl, type CollectionLike } from '@tanstack/solid-db'
|
||||||
|
import type { ExtractPayload, Todo } from '@<@var(context.project.name)>/rpc';
|
||||||
|
export type TodoCollection = CollectionImpl<ExtractPayload<Todo>>
|
||||||
|
export const TodoCollectionContext = createContext<TodoCollection>()
|
||||||
|
export const useTodoCollection = () => {
|
||||||
|
const col = useContext(TodoCollectionContext)
|
||||||
|
if (col === undefined) {
|
||||||
|
throw new Error("Todo collection has not been initialized")
|
||||||
|
}
|
||||||
|
return col;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { JSXElement } from 'solid-js';
|
||||||
|
import { TodoCollection, TodoCollectionContext } from './create'
|
||||||
|
|
||||||
|
export const TodoCollectionProvider = (props : {children:JSXElement, value: TodoCollection}) => {
|
||||||
|
return (
|
||||||
|
<TodoCollectionContext.Provider value={props.value}> {props.children} </TodoCollectionContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// @refresh reload
|
||||||
|
import { mount, StartClient } from "@solidjs/start/client";
|
||||||
|
|
||||||
|
mount(() => <StartClient />, document.getElementById("app")!);
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// @refresh reload
|
||||||
|
import { createHandler, StartServer } from "@solidjs/start/server";
|
||||||
|
|
||||||
|
export default createHandler(() => (
|
||||||
|
<StartServer
|
||||||
|
document={({ assets, children, scripts }) => (
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
{assets}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">{children}</div>
|
||||||
|
{scripts}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
1
template/apps/<@if(eq(context.project.frontend,"solid-start"))>web/src/global.d.ts
vendored
Normal file
1
template/apps/<@if(eq(context.project.frontend,"solid-start"))>web/src/global.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="@solidjs/start/env" />
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { createRouter } from "@<@var(context.project.name)>/rpc"
|
||||||
|
|
||||||
|
const router = createRouter("http://127.0.0.1:8080")
|
||||||
|
|
||||||
|
export const getRouter = () => {
|
||||||
|
return router;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Title } from "@solidjs/meta";
|
||||||
|
import { HttpStatusCode } from "@solidjs/start";
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<Title>Not Found</Title>
|
||||||
|
<HttpStatusCode code={404} />
|
||||||
|
<h1>Page Not Found</h1>
|
||||||
|
<p>
|
||||||
|
Visit{" "}
|
||||||
|
<a href="https://start.solidjs.com" target="_blank">
|
||||||
|
start.solidjs.com
|
||||||
|
</a>{" "}
|
||||||
|
to learn how to build SolidStart apps.
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Title } from "@solidjs/meta";
|
||||||
|
import { CreateTodo } from "~/components/CreateTodo";
|
||||||
|
import { ListTodos } from "~/components/ListTodos"
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<main class="flex flex-col items-center gap-2 py-5">
|
||||||
|
<Title>Todos</Title>
|
||||||
|
<h1 class="text-5xl"> Todos </h1>
|
||||||
|
<CreateTodo/>
|
||||||
|
<ListTodos/>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js",
|
||||||
|
"allowJs": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"isolatedModules": true,
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { nitroV2Plugin as nitro } from "@solidjs/vite-plugin-nitro-2";
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
import { solidStart } from "@solidjs/start/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
solidStart(),
|
||||||
|
nitro(),
|
||||||
|
tailwindcss()
|
||||||
|
]
|
||||||
|
});
|
||||||
@@ -21,6 +21,3 @@ Thumbs.db
|
|||||||
# Vite
|
# Vite
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
# Paraglide
|
|
||||||
src/lib/paraglide
|
|
||||||
project.inlang/cache/
|
|
||||||
3
template/apps/<@if(eq(context.project.frontend,"svelte-kit"))>web/.vscode/extensions.json
vendored
Normal file
3
template/apps/<@if(eq(context.project.frontend,"svelte-kit"))>web/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["svelte.svelte-vscode"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@sveltejs/adapter-auto": "^7.0.1",
|
||||||
|
"@sveltejs/kit": "^2.57.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
||||||
|
"svelte": "^5.55.2",
|
||||||
|
"svelte-check": "^4.4.6",
|
||||||
|
"typescript": "^6.0.2",
|
||||||
|
"vite": "^8.0.7"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@bufbuild/protobuf": "^2.11.0",
|
||||||
|
"@connectrpc/connect": "^2.1.1",
|
||||||
|
"@connectrpc/connect-web": "^2.1.1",
|
||||||
|
"@<@var(context.project.name)>/rpc": "workspace:*",
|
||||||
|
"@tailwindcss/vite": "^4.3.0",
|
||||||
|
"@tanstack/query-db-collection": "^1.0.33",
|
||||||
|
"@tanstack/svelte-db": "^0.1.79",
|
||||||
|
"@tanstack/svelte-query": "^6.1.13",
|
||||||
|
"tailwindcss": "^4.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import @tailwindcss;
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
<html lang="%paraglide.lang%" dir="%paraglide.dir%">
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
<meta name="text-scale" content="scale" />
|
<meta name="text-scale" content="scale" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { ExtractPayload, Todo } from '@<@var(context.project.name)>/rpc';
|
||||||
|
import { getTodoCollection } from '$lib/todocollectionscontext';
|
||||||
|
|
||||||
|
const todoCollection = getTodoCollection();
|
||||||
|
let todo = $state<ExtractPayload<Todo>>({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
task: '',
|
||||||
|
done: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const insertTodo = () => {
|
||||||
|
todoCollection.insert({ ...todo });
|
||||||
|
todo = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
task: '',
|
||||||
|
done: false
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input class="rounded-md border px-3 py-1" type="text" bind:value={todo.task} />
|
||||||
|
<button class="cursor-pointer rounded-md bg-teal-800 p-1 text-teal-100" onclick={insertTodo}>
|
||||||
|
create
|
||||||
|
</button>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getTodoCollection } from '$lib/todocollectionscontext';
|
||||||
|
import { useLiveQuery } from '@tanstack/svelte-db';
|
||||||
|
import Todo from './Todo.svelte';
|
||||||
|
|
||||||
|
const todoCollection = getTodoCollection();
|
||||||
|
const data = useLiveQuery((q) => q.from({ todos: todoCollection }));
|
||||||
|
const todos = $derived.by(() => {
|
||||||
|
return data.data.toSorted((a, b) => {
|
||||||
|
if (a.done != b.done) {
|
||||||
|
return a.done ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (!a.done && !b.done) {
|
||||||
|
const adate = a.createdAt ? new Date(a.createdAt) : new Date();
|
||||||
|
const bdate = b.createdAt ? new Date(b.createdAt) : new Date();
|
||||||
|
return bdate.getTime() - adate.getTime();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if data.isLoading}
|
||||||
|
<div>Loading...</div>
|
||||||
|
{:else}
|
||||||
|
{#each todos as todo (todo.id)}
|
||||||
|
<Todo {todo} />
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { ExtractPayload, Todo as RpcTodo } from '@<@var(context.project.name)>/rpc';
|
||||||
|
import { getTodoCollection } from '$lib/todocollectionscontext';
|
||||||
|
|
||||||
|
let { todo }: { todo: ExtractPayload<RpcTodo> } = $props();
|
||||||
|
const getInitialTodo = () => todo;
|
||||||
|
let todoState = $state<ExtractPayload<RpcTodo>>({ ...getInitialTodo() });
|
||||||
|
let commitUpdateTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
const todoCollection = getTodoCollection();
|
||||||
|
|
||||||
|
const updateTask = () => {
|
||||||
|
if (commitUpdateTimeoutId != null) {
|
||||||
|
clearTimeout(commitUpdateTimeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
commitUpdateTimeoutId = setTimeout(() => {
|
||||||
|
todoCollection.update(todoState.id, (draft) => {
|
||||||
|
draft.task = todoState.task;
|
||||||
|
});
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDone = () => {
|
||||||
|
todoCollection.update(todoState.id, (draft) => {
|
||||||
|
draft.done = todoState.done;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const del = () => {
|
||||||
|
todoCollection.delete(todoState.id || '');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col items-center gap-2 rounded-md border p-5">
|
||||||
|
<input
|
||||||
|
class="rounded-md border px-3 py-1 text-center"
|
||||||
|
type="text"
|
||||||
|
bind:value={todoState.task}
|
||||||
|
oninput={updateTask}
|
||||||
|
/>
|
||||||
|
<span>{new Date(todoState.createdAt || '').toLocaleString()}</span>
|
||||||
|
<span>{new Date(todoState.updatesAt || '').toLocaleString()}</span>
|
||||||
|
<input type="checkbox" bind:checked={todoState.done} onchange={updateDone} />
|
||||||
|
<button class="cursor-pointer rounded-md bg-amber-800 p-1 text-amber-100" onclick={del}>
|
||||||
|
delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { createContext } from "svelte";
|
import { createContext } from "svelte";
|
||||||
import { type CollectionImpl } from '@tanstack/svelte-db'
|
import { type CollectionImpl } from '@tanstack/svelte-db'
|
||||||
import type { Todo } from "@<@var(context.project.name)>/rpc";
|
import type { Todo, ExtractPayload } from "@<@var(context.project.name)>/rpc";
|
||||||
import { type ExtractPayload } from "./utils";
|
|
||||||
export const [getTodoCollection,setTodoCollection] = createContext<CollectionImpl<ExtractPayload<Todo>,string>>()
|
export const [getTodoCollection,setTodoCollection] = createContext<CollectionImpl<ExtractPayload<Todo>,string>>()
|
||||||
@@ -1,26 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/state';
|
|
||||||
import { locales, localizeHref } from '$lib/paraglide/runtime';
|
|
||||||
import './layout.css';
|
|
||||||
import favicon from '$lib/assets/favicon.svg';
|
|
||||||
import { setTodoCollection } from '$lib/todocollectionscontext';
|
|
||||||
import { QueryClient,QueryClientProvider } from '@tanstack/svelte-query';
|
|
||||||
import { type Todo } from '@<@var(context.project.name)>/rpc';
|
|
||||||
import { browser } from '$app/environment';
|
|
||||||
import { createCollection } from '@tanstack/svelte-db';
|
import { createCollection } from '@tanstack/svelte-db';
|
||||||
import { queryCollectionOptions } from '@tanstack/query-db-collection';
|
import { queryCollectionOptions } from '@tanstack/query-db-collection';
|
||||||
import { getRouter } from "$lib/getconnectrouter"
|
import { getRouter } from "$lib/getconnectrouter"
|
||||||
import type { ExtractPayload } from '$lib/utils';
|
import favicon from '$lib/assets/favicon.svg';
|
||||||
|
import "../app.css"
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query';
|
||||||
|
import type { ExtractPayload, Todo } from '@<@var(context.project.name)>/rpc';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
import { setTodoCollection } from '$lib/todocollectionscontext';
|
||||||
|
|
||||||
|
let { children } = $props();
|
||||||
|
const router = getRouter()
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
enabled: browser
|
enabled: browser
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
})
|
})
|
||||||
let { children } = $props();
|
|
||||||
const router = getRouter()
|
|
||||||
const todosCollection = createCollection(
|
const todosCollection = createCollection(
|
||||||
queryCollectionOptions({
|
queryCollectionOptions({
|
||||||
queryKey: ["todos"],
|
queryKey: ["todos"],
|
||||||
@@ -50,14 +47,7 @@
|
|||||||
setTodoCollection(todosCollection)
|
setTodoCollection(todosCollection)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
|
|
||||||
|
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|
||||||
<div style="display:none">
|
|
||||||
{#each locales as locale}
|
|
||||||
<a href={localizeHref(page.url.pathname, { locale })}>{locale}</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<script>
|
||||||
|
import CreateTodo from "$lib/components/CreateTodo.svelte";
|
||||||
|
import ListTodos from "$lib/components/ListTodos.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="flex flex-col items-center gap-2 py-5">
|
||||||
|
<title>Todos</title>
|
||||||
|
<h1 class="text-5xl"> Todos </h1>
|
||||||
|
<CreateTodo/>
|
||||||
|
<ListTodos/>
|
||||||
|
</main>
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
import adapter from '@sveltejs/adapter-auto';
|
import adapter from '@sveltejs/adapter-auto';
|
||||||
import { relative, sep } from 'node:path';
|
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
// defaults to rune mode for the project, except for `node_modules`. Can be removed in svelte 6.
|
// Force runes mode for the project, except for libraries. Can be removed in svelte 6.
|
||||||
runes: ({ filename }) => {
|
runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true)
|
||||||
const relativePath = relative(import.meta.dirname, filename);
|
|
||||||
const pathSegments = relativePath.toLowerCase().split(sep);
|
|
||||||
const isExternalLibrary = pathSegments.includes('node_modules');
|
|
||||||
|
|
||||||
return isExternalLibrary ? undefined : true;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
kit: {
|
kit: {
|
||||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
tailwindcss(),
|
||||||
|
sveltekit(),
|
||||||
|
]
|
||||||
|
});
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
node_modules
|
|
||||||
|
|
||||||
# Output
|
|
||||||
.output
|
|
||||||
.vercel
|
|
||||||
.netlify
|
|
||||||
.wrangler
|
|
||||||
/.svelte-kit
|
|
||||||
/build
|
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Env
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
!.env.test
|
|
||||||
|
|
||||||
# Vite
|
|
||||||
vite.config.js.timestamp-*
|
|
||||||
vite.config.ts.timestamp-*
|
|
||||||
# Paraglide
|
|
||||||
src/lib/paraglide
|
|
||||||
project.inlang/cache/
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# Package Managers
|
|
||||||
package-lock.json
|
|
||||||
pnpm-lock.yaml
|
|
||||||
yarn.lock
|
|
||||||
bun.lock
|
|
||||||
bun.lockb
|
|
||||||
|
|
||||||
# Miscellaneous
|
|
||||||
/static/
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"useTabs": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"printWidth": 100,
|
|
||||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "*.svelte",
|
|
||||||
"options": {
|
|
||||||
"parser": "svelte"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tailwindStylesheet": "./src/routes/layout.css"
|
|
||||||
}
|
|
||||||
3
template/apps/web/.vscode/extensions.json
vendored
3
template/apps/web/.vscode/extensions.json
vendored
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["svelte.svelte-vscode", "esbenp.prettier-vscode", "bradlc.vscode-tailwindcss"]
|
|
||||||
}
|
|
||||||
5
template/apps/web/.vscode/settings.json
vendored
5
template/apps/web/.vscode/settings.json
vendored
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"files.associations": {
|
|
||||||
"*.css": "tailwindcss"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# sv
|
|
||||||
|
|
||||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
|
||||||
|
|
||||||
## Creating a project
|
|
||||||
|
|
||||||
If you're seeing this, you've probably already done this step. Congrats!
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# create a new project
|
|
||||||
npx sv create my-app
|
|
||||||
```
|
|
||||||
|
|
||||||
To recreate this project with the same configuration:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# recreate this project
|
|
||||||
bun x sv@0.14.0 create --template minimal --types ts --add prettier tailwindcss="plugins:typography" paraglide="languageTags:en, de-De+demo:no" --install bun web
|
|
||||||
```
|
|
||||||
|
|
||||||
## Developing
|
|
||||||
|
|
||||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# or start the server and open the app in a new browser tab
|
|
||||||
npm run dev -- --open
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
To create a production version of your app:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
You can preview the production build with `npm run preview`.
|
|
||||||
|
|
||||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
|
||||||
"tailwind": {
|
|
||||||
"css": "src/routes/layout.css",
|
|
||||||
"baseColor": "mist"
|
|
||||||
},
|
|
||||||
"aliases": {
|
|
||||||
"components": "$lib/components",
|
|
||||||
"utils": "$lib/utils",
|
|
||||||
"ui": "$lib/components/ui",
|
|
||||||
"hooks": "$lib/hooks",
|
|
||||||
"lib": "$lib"
|
|
||||||
},
|
|
||||||
"typescript": true,
|
|
||||||
"registry": "https://shadcn-svelte.com/registry",
|
|
||||||
"style": "lyra",
|
|
||||||
"iconLibrary": "lucide",
|
|
||||||
"menuColor": "default",
|
|
||||||
"menuAccent": "subtle"
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
|
||||||
"hello_world": "Hello, {name} from de-de!"
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
|
||||||
"hello_world": "Hello, {name} from en!"
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "web",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.0.1",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite dev",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"prepare": "svelte-kit sync || echo ''",
|
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
||||||
"lint": "prettier --check .",
|
|
||||||
"format": "prettier --write ."
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@fontsource-variable/roboto": "^5.2.10",
|
|
||||||
"@inlang/paraglide-js": "^2.10.0",
|
|
||||||
"@internationalized/date": "^3.12.0",
|
|
||||||
"@lucide/svelte": "^0.577.0",
|
|
||||||
"@sveltejs/adapter-auto": "^7.0.0",
|
|
||||||
"@sveltejs/kit": "^2.50.2",
|
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
|
||||||
"@tanstack/table-core": "^8.21.3",
|
|
||||||
"bits-ui": "^2.16.3",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"embla-carousel-svelte": "^8.6.0",
|
|
||||||
"formsnap": "^2.0.1",
|
|
||||||
"layerchart": "2.0.0-next.48",
|
|
||||||
"mode-watcher": "^1.1.0",
|
|
||||||
"paneforge": "^1.0.2",
|
|
||||||
"prettier": "^3.8.1",
|
|
||||||
"prettier-plugin-svelte": "^3.4.1",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
||||||
"shadcn-svelte": "^1.2.7",
|
|
||||||
"svelte": "^5.54.0",
|
|
||||||
"svelte-check": "^4.4.2",
|
|
||||||
"svelte-sonner": "^1.1.0",
|
|
||||||
"sveltekit-superforms": "^2.30.0",
|
|
||||||
"tailwind-merge": "^3.5.0",
|
|
||||||
"tailwind-variants": "^3.2.2",
|
|
||||||
"tailwindcss": "^4.1.18",
|
|
||||||
"tw-animate-css": "^1.4.0",
|
|
||||||
"typescript": "^5.9.3",
|
|
||||||
"vaul-svelte": "^1.0.0-next.7",
|
|
||||||
"vite": "^7.3.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@bufbuild/protobuf": "^2.11.0",
|
|
||||||
"@connectrpc/connect": "^2.1.1",
|
|
||||||
"@connectrpc/connect-web": "^2.1.1",
|
|
||||||
"@<@var(context.project.name)>/rpc": "workspace:*",
|
|
||||||
"@tanstack/query-db-collection": "^1.0.33",
|
|
||||||
"@tanstack/svelte-db": "^0.1.79",
|
|
||||||
"@tanstack/svelte-query": "^6.1.13"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://inlang.com/schema/project-settings",
|
|
||||||
"modules": [
|
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
|
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
|
|
||||||
],
|
|
||||||
"plugin.inlang.messageFormat": {
|
|
||||||
"pathPattern": "./messages/{locale}.json"
|
|
||||||
},
|
|
||||||
"baseLocale": "en",
|
|
||||||
"locales": ["en", "de-de"]
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import type { Handle } from '@sveltejs/kit';
|
|
||||||
import { getTextDirection } from '$lib/paraglide/runtime';
|
|
||||||
import { paraglideMiddleware } from '$lib/paraglide/server';
|
|
||||||
|
|
||||||
const handleParaglide: Handle = ({ event, resolve }) =>
|
|
||||||
paraglideMiddleware(event.request, ({ request, locale }) => {
|
|
||||||
event.request = request;
|
|
||||||
|
|
||||||
return resolve(event, {
|
|
||||||
transformPageChunk: ({ html }) =>
|
|
||||||
html
|
|
||||||
.replace('%paraglide.lang%', locale)
|
|
||||||
.replace('%paraglide.dir%', getTextDirection(locale))
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const handle: Handle = handleParaglide;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import type { Reroute } from '@sveltejs/kit';
|
|
||||||
import { deLocalizeUrl } from '$lib/paraglide/runtime';
|
|
||||||
|
|
||||||
export const reroute: Reroute = (request) => deLocalizeUrl(request.url).pathname;
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Todo } from "@<@var(context.project.name)>/rpc";
|
|
||||||
import type { ExtractPayload } from "$lib/utils"
|
|
||||||
import Input from "$lib/components/ui/input/input.svelte";
|
|
||||||
import Button from "$lib/components/ui/button/button.svelte"
|
|
||||||
import * as Field from "$lib/components/ui/field/index"
|
|
||||||
import { getTodoCollection } from "$lib/todocollectionscontext";
|
|
||||||
const todoCollection = getTodoCollection();
|
|
||||||
let todo = $state<ExtractPayload<Todo>>({id: crypto.randomUUID() ,task: "",done:false})
|
|
||||||
let create = () => {
|
|
||||||
todoCollection.insert(todo)
|
|
||||||
todo = { id: crypto.randomUUID(), task: "", done:false }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex flex-col items-center justify-center">
|
|
||||||
<Field.FieldGroup class="w-50">
|
|
||||||
<Field.Field>
|
|
||||||
<Field.Label>
|
|
||||||
Todo
|
|
||||||
</Field.Label>
|
|
||||||
<Input bind:value={todo.task}/>
|
|
||||||
</Field.Field>
|
|
||||||
<Button variant="outline" onclick={create} > Create </Button>
|
|
||||||
</Field.FieldGroup>
|
|
||||||
</div>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { getTodoCollection } from '$lib/todocollectionscontext';
|
|
||||||
import { useLiveQuery } from '@tanstack/svelte-db';
|
|
||||||
import { tick } from 'svelte';
|
|
||||||
import { ScrollArea } from '../ui/scroll-area/index';
|
|
||||||
import type { Todo as TodoRpc } from '@<@var(context.project.name)>/rpc';
|
|
||||||
import { browser } from '$app/environment';
|
|
||||||
import Todo from './Todo.svelte';
|
|
||||||
|
|
||||||
let { initialTodos }: { initialTodos: TodoRpc[] } = $props();
|
|
||||||
const todoCollection = getTodoCollection();
|
|
||||||
const data = useLiveQuery((q) => q.from({ todos: todoCollection }));
|
|
||||||
const todos = $derived.by(() => {
|
|
||||||
const res = data.data.toSorted((a, b) => {
|
|
||||||
if (a.done != b.done) {
|
|
||||||
return a.done ? -1 : 1;
|
|
||||||
}
|
|
||||||
if (!a.done && !b.done) {
|
|
||||||
let adate = a.createdAt ? new Date(a.createdAt) : new Date(Date.now());
|
|
||||||
let bdate = b.createdAt ? new Date(b.createdAt) : new Date(Date.now());
|
|
||||||
return bdate.getTime() - adate.getTime();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
let scrollerRef = $state<HTMLElement | null>(null);
|
|
||||||
$effect(() => {
|
|
||||||
todos;
|
|
||||||
tick().then(() => {
|
|
||||||
scrollerRef?.scrollTo({ top: scrollerRef.scrollHeight, behavior: 'smooth' });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ScrollArea
|
|
||||||
bind:viewportRef={scrollerRef}
|
|
||||||
id="scroller"
|
|
||||||
class="flex h-[80%] w-full flex-col items-center justify-center lg:w-[30%]"
|
|
||||||
>
|
|
||||||
{#if browser}
|
|
||||||
{#each todos as todo (todo.id)}
|
|
||||||
<Todo {todo} />
|
|
||||||
{/each}
|
|
||||||
{:else}
|
|
||||||
{#each initialTodos as todo}
|
|
||||||
<Todo {todo} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</ScrollArea>
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { type Todo } from "@<@var(context.project.name)>/rpc";
|
|
||||||
import { getTodoCollection } from "$lib/todocollectionscontext";
|
|
||||||
import * as Card from "../ui/card/index";
|
|
||||||
import * as Field from "../ui/field/index";
|
|
||||||
import Input from "../ui/input/input.svelte";
|
|
||||||
import type { ExtractPayload } from "$lib/utils";
|
|
||||||
import Checkbox from "../ui/checkbox/checkbox.svelte";
|
|
||||||
import Button from "../ui/button/button.svelte";
|
|
||||||
import { DeleteIcon } from "@lucide/svelte"
|
|
||||||
let { todo } : { todo:ExtractPayload<Todo> } = $props();
|
|
||||||
let todoState = $state(todo)
|
|
||||||
const todoCollection = getTodoCollection();
|
|
||||||
const update = () => {
|
|
||||||
todoCollection.update(todoState.id,(draft) => {
|
|
||||||
draft.done = todoState.done;
|
|
||||||
draft.task = todoState.task;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const del = () => {
|
|
||||||
if (todoState.id) {
|
|
||||||
todoCollection.delete(todoState.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Card.Card class="p-5" onkeydown={()=>update()}>
|
|
||||||
<Card.CardContent>
|
|
||||||
<Field.FieldSet>
|
|
||||||
<Field.Field>
|
|
||||||
<Field.Label>
|
|
||||||
task
|
|
||||||
</Field.Label>
|
|
||||||
<Field.FieldContent>
|
|
||||||
<Input bind:value={todoState.task}/>
|
|
||||||
</Field.FieldContent>
|
|
||||||
</Field.Field>
|
|
||||||
<Field.Field>
|
|
||||||
<Field.Label>
|
|
||||||
done
|
|
||||||
</Field.Label>
|
|
||||||
<Field.FieldContent onclick={()=>update()}>
|
|
||||||
<Checkbox bind:checked={todoState.done}/>
|
|
||||||
</Field.FieldContent>
|
|
||||||
</Field.Field>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<Field.Field>
|
|
||||||
<Field.Label>
|
|
||||||
Created
|
|
||||||
</Field.Label>
|
|
||||||
<Field.FieldContent>
|
|
||||||
{(new Date(todoState.createdAt||"now")).toLocaleString()}
|
|
||||||
</Field.FieldContent>
|
|
||||||
</Field.Field>
|
|
||||||
<Field.Field>
|
|
||||||
<Field.Label>
|
|
||||||
Updated
|
|
||||||
</Field.Label>
|
|
||||||
<Field.FieldContent>
|
|
||||||
{(new Date(todoState.updatesAt||"now")).toLocaleString()}
|
|
||||||
</Field.FieldContent>
|
|
||||||
</Field.Field>
|
|
||||||
<Button variant='destructive' onclick={del}>
|
|
||||||
<DeleteIcon/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Field.FieldSet>
|
|
||||||
</Card.CardContent>
|
|
||||||
</Card.Card>
|
|
||||||
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
|
||||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithoutChild<AccordionPrimitive.ContentProps> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AccordionPrimitive.Content
|
|
||||||
bind:ref
|
|
||||||
data-slot="accordion-content"
|
|
||||||
class="data-open:animate-accordion-down data-closed:animate-accordion-up text-xs overflow-hidden"
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class={cn(
|
|
||||||
"pt-0 pb-2.5 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
</AccordionPrimitive.Content>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: AccordionPrimitive.ItemProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AccordionPrimitive.Item
|
|
||||||
bind:ref
|
|
||||||
data-slot="accordion-item"
|
|
||||||
class={cn("not-last:border-b", className)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
|
||||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
|
||||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
|
||||||
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
level = 3,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithoutChild<AccordionPrimitive.TriggerProps> & {
|
|
||||||
level?: AccordionPrimitive.HeaderProps["level"];
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AccordionPrimitive.Header {level} class="flex">
|
|
||||||
<AccordionPrimitive.Trigger
|
|
||||||
data-slot="accordion-trigger"
|
|
||||||
bind:ref
|
|
||||||
class={cn(
|
|
||||||
"focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground rounded-none py-2.5 text-left text-xs font-medium hover:underline focus-visible:ring-1 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none disabled:pointer-events-none disabled:opacity-50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
<ChevronDownIcon data-slot="accordion-trigger-icon" class="cn-accordion-trigger-icon pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden" />
|
|
||||||
<ChevronUpIcon data-slot="accordion-trigger-icon" class="cn-accordion-trigger-icon pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline" />
|
|
||||||
</AccordionPrimitive.Trigger>
|
|
||||||
</AccordionPrimitive.Header>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
value = $bindable(),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: AccordionPrimitive.RootProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AccordionPrimitive.Root
|
|
||||||
bind:ref
|
|
||||||
bind:value={value as never}
|
|
||||||
data-slot="accordion"
|
|
||||||
class={cn("cn-accordion flex w-full flex-col", className)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import Root from "./accordion.svelte";
|
|
||||||
import Content from "./accordion-content.svelte";
|
|
||||||
import Item from "./accordion-item.svelte";
|
|
||||||
import Trigger from "./accordion-trigger.svelte";
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
Content,
|
|
||||||
Item,
|
|
||||||
Trigger,
|
|
||||||
//
|
|
||||||
Root as Accordion,
|
|
||||||
Content as AccordionContent,
|
|
||||||
Item as AccordionItem,
|
|
||||||
Trigger as AccordionTrigger,
|
|
||||||
};
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
|
||||||
import {
|
|
||||||
buttonVariants,
|
|
||||||
type ButtonVariant,
|
|
||||||
type ButtonSize,
|
|
||||||
} from "$lib/components/ui/button/index.js";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
variant = "default",
|
|
||||||
size = "default",
|
|
||||||
...restProps
|
|
||||||
}: AlertDialogPrimitive.ActionProps & {
|
|
||||||
variant?: ButtonVariant;
|
|
||||||
size?: ButtonSize;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AlertDialogPrimitive.Action
|
|
||||||
bind:ref
|
|
||||||
data-slot="alert-dialog-action"
|
|
||||||
class={cn(buttonVariants({ variant, size }), "cn-alert-dialog-action", className)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
|
||||||
import {
|
|
||||||
buttonVariants,
|
|
||||||
type ButtonVariant,
|
|
||||||
type ButtonSize,
|
|
||||||
} from "$lib/components/ui/button/index.js";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
variant = "outline",
|
|
||||||
size = "default",
|
|
||||||
...restProps
|
|
||||||
}: AlertDialogPrimitive.CancelProps & {
|
|
||||||
variant?: ButtonVariant;
|
|
||||||
size?: ButtonSize;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AlertDialogPrimitive.Cancel
|
|
||||||
bind:ref
|
|
||||||
data-slot="alert-dialog-cancel"
|
|
||||||
class={cn(buttonVariants({ variant, size }), "cn-alert-dialog-cancel", className)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
|
||||||
import AlertDialogPortal from "./alert-dialog-portal.svelte";
|
|
||||||
import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
|
|
||||||
import { cn, type WithoutChild, type WithoutChildrenOrChild } from "$lib/utils.js";
|
|
||||||
import type { ComponentProps } from "svelte";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
size = "default",
|
|
||||||
portalProps,
|
|
||||||
...restProps
|
|
||||||
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
|
|
||||||
size?: "default" | "sm";
|
|
||||||
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof AlertDialogPortal>>;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AlertDialogPortal {...portalProps}>
|
|
||||||
<AlertDialogOverlay />
|
|
||||||
<AlertDialogPrimitive.Content
|
|
||||||
bind:ref
|
|
||||||
data-slot="alert-dialog-content"
|
|
||||||
data-size={size}
|
|
||||||
class={cn(
|
|
||||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-popover text-popover-foreground ring-foreground/10 gap-4 rounded-none p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
</AlertDialogPortal>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: AlertDialogPrimitive.DescriptionProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AlertDialogPrimitive.Description
|
|
||||||
bind:ref
|
|
||||||
data-slot="alert-dialog-description"
|
|
||||||
class={cn("text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="alert-dialog-footer"
|
|
||||||
class={cn(
|
|
||||||
"cn-alert-dialog-footer flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="alert-dialog-header"
|
|
||||||
class={cn("grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="alert-dialog-media"
|
|
||||||
class={cn("bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-none sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6", className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: AlertDialogPrimitive.OverlayProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AlertDialogPrimitive.Overlay
|
|
||||||
bind:ref
|
|
||||||
data-slot="alert-dialog-overlay"
|
|
||||||
class={cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50", className)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
|
||||||
|
|
||||||
let { ...restProps }: AlertDialogPrimitive.PortalProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AlertDialogPrimitive.Portal {...restProps} />
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: AlertDialogPrimitive.TitleProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AlertDialogPrimitive.Title
|
|
||||||
bind:ref
|
|
||||||
data-slot="alert-dialog-title"
|
|
||||||
class={cn("text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
|
||||||
|
|
||||||
let { ref = $bindable(null), ...restProps }: AlertDialogPrimitive.TriggerProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AlertDialogPrimitive.Trigger bind:ref data-slot="alert-dialog-trigger" {...restProps} />
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
|
||||||
|
|
||||||
let { open = $bindable(false), ...restProps }: AlertDialogPrimitive.RootProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AlertDialogPrimitive.Root bind:open {...restProps} />
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import Root from "./alert-dialog.svelte";
|
|
||||||
import Portal from "./alert-dialog-portal.svelte";
|
|
||||||
import Trigger from "./alert-dialog-trigger.svelte";
|
|
||||||
import Title from "./alert-dialog-title.svelte";
|
|
||||||
import Action from "./alert-dialog-action.svelte";
|
|
||||||
import Cancel from "./alert-dialog-cancel.svelte";
|
|
||||||
import Footer from "./alert-dialog-footer.svelte";
|
|
||||||
import Header from "./alert-dialog-header.svelte";
|
|
||||||
import Overlay from "./alert-dialog-overlay.svelte";
|
|
||||||
import Content from "./alert-dialog-content.svelte";
|
|
||||||
import Description from "./alert-dialog-description.svelte";
|
|
||||||
import Media from "./alert-dialog-media.svelte";
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
Title,
|
|
||||||
Action,
|
|
||||||
Cancel,
|
|
||||||
Portal,
|
|
||||||
Footer,
|
|
||||||
Header,
|
|
||||||
Trigger,
|
|
||||||
Overlay,
|
|
||||||
Content,
|
|
||||||
Description,
|
|
||||||
Media,
|
|
||||||
//
|
|
||||||
Root as AlertDialog,
|
|
||||||
Title as AlertDialogTitle,
|
|
||||||
Action as AlertDialogAction,
|
|
||||||
Cancel as AlertDialogCancel,
|
|
||||||
Portal as AlertDialogPortal,
|
|
||||||
Footer as AlertDialogFooter,
|
|
||||||
Header as AlertDialogHeader,
|
|
||||||
Trigger as AlertDialogTrigger,
|
|
||||||
Overlay as AlertDialogOverlay,
|
|
||||||
Content as AlertDialogContent,
|
|
||||||
Description as AlertDialogDescription,
|
|
||||||
Media as AlertDialogMedia,
|
|
||||||
};
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="alert-action"
|
|
||||||
class={cn("absolute top-[calc(--spacing(1.25))] right-[calc(--spacing(1.25))]", className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="alert-description"
|
|
||||||
class={cn(
|
|
||||||
"text-muted-foreground text-xs/relaxed text-balance md:text-pretty [&_p:not(:last-child)]:mb-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="alert-title"
|
|
||||||
class={cn(
|
|
||||||
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<script lang="ts" module>
|
|
||||||
import { type VariantProps, tv } from "tailwind-variants";
|
|
||||||
|
|
||||||
export const alertVariants = tv({
|
|
||||||
base: "grid gap-0.5 rounded-none border px-2.5 py-2 text-left text-xs has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 group/alert relative w-full",
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "bg-card text-card-foreground",
|
|
||||||
destructive: "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AlertVariant = VariantProps<typeof alertVariants>["variant"];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
variant = "default",
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
|
||||||
variant?: AlertVariant;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="alert"
|
|
||||||
role="alert"
|
|
||||||
class={cn(alertVariants({ variant }), className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import Root from "./alert.svelte";
|
|
||||||
import Description from "./alert-description.svelte";
|
|
||||||
import Title from "./alert-title.svelte";
|
|
||||||
import Action from "./alert-action.svelte";
|
|
||||||
export { alertVariants, type AlertVariant } from "./alert.svelte";
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
Description,
|
|
||||||
Title,
|
|
||||||
Action,
|
|
||||||
//
|
|
||||||
Root as Alert,
|
|
||||||
Description as AlertDescription,
|
|
||||||
Title as AlertTitle,
|
|
||||||
Action as AlertAction,
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { AspectRatio as AspectRatioPrimitive } from "bits-ui";
|
|
||||||
|
|
||||||
let { ref = $bindable(null), ...restProps }: AspectRatioPrimitive.RootProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AspectRatioPrimitive.Root bind:ref data-slot="aspect-ratio" {...restProps} />
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Root from "./aspect-ratio.svelte";
|
|
||||||
|
|
||||||
export { Root, Root as AspectRatio };
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<span
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="avatar-badge"
|
|
||||||
class={cn(
|
|
||||||
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
|
|
||||||
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
|
||||||
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
|
||||||
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</span>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: AvatarPrimitive.FallbackProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AvatarPrimitive.Fallback
|
|
||||||
bind:ref
|
|
||||||
data-slot="avatar-fallback"
|
|
||||||
class={cn(
|
|
||||||
"bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="avatar-group-count"
|
|
||||||
class={cn(
|
|
||||||
"bg-muted text-muted-foreground size-8 rounded-full text-xs group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="avatar-group"
|
|
||||||
class={cn(
|
|
||||||
"cn-avatar-group *:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: AvatarPrimitive.ImageProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AvatarPrimitive.Image
|
|
||||||
bind:ref
|
|
||||||
data-slot="avatar-image"
|
|
||||||
class={cn("rounded-full aspect-square size-full object-cover", className)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
loadingStatus = $bindable("loading"),
|
|
||||||
size = "default",
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: AvatarPrimitive.RootProps & {
|
|
||||||
size?: "default" | "sm" | "lg";
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AvatarPrimitive.Root
|
|
||||||
bind:ref
|
|
||||||
bind:loadingStatus
|
|
||||||
data-slot="avatar"
|
|
||||||
data-size={size}
|
|
||||||
class={cn(
|
|
||||||
"size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import Root from "./avatar.svelte";
|
|
||||||
import Image from "./avatar-image.svelte";
|
|
||||||
import Fallback from "./avatar-fallback.svelte";
|
|
||||||
import Badge from "./avatar-badge.svelte";
|
|
||||||
import Group from "./avatar-group.svelte";
|
|
||||||
import GroupCount from "./avatar-group-count.svelte";
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
Image,
|
|
||||||
Fallback,
|
|
||||||
Badge,
|
|
||||||
Group,
|
|
||||||
GroupCount,
|
|
||||||
//
|
|
||||||
Root as Avatar,
|
|
||||||
Image as AvatarImage,
|
|
||||||
Fallback as AvatarFallback,
|
|
||||||
Badge as AvatarBadge,
|
|
||||||
Group as AvatarGroup,
|
|
||||||
GroupCount as AvatarGroupCount,
|
|
||||||
};
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<script lang="ts" module>
|
|
||||||
import { type VariantProps, tv } from "tailwind-variants";
|
|
||||||
|
|
||||||
export const badgeVariants = tv({
|
|
||||||
base: "h-5 gap-1 rounded-none border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-colors focus-visible:ring-[3px] [&>svg]:pointer-events-none",
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
||||||
secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
||||||
destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
|
|
||||||
outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
|
||||||
ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import type { HTMLAnchorAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
href,
|
|
||||||
class: className,
|
|
||||||
variant = "default",
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAnchorAttributes> & {
|
|
||||||
variant?: BadgeVariant;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:element
|
|
||||||
this={href ? "a" : "span"}
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="badge"
|
|
||||||
{href}
|
|
||||||
class={cn(badgeVariants({ variant }), className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</svelte:element>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { default as Badge } from "./badge.svelte";
|
|
||||||
export { badgeVariants, type BadgeVariant } from "./badge.svelte";
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js";
|
|
||||||
import MoreHorizontalIcon from '@lucide/svelte/icons/more-horizontal';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLSpanElement>>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<span
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="breadcrumb-ellipsis"
|
|
||||||
role="presentation"
|
|
||||||
aria-hidden="true"
|
|
||||||
class={cn("size-5 [&>svg]:size-4 flex items-center justify-center", className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
<MoreHorizontalIcon />
|
|
||||||
<span class="sr-only">More</span>
|
|
||||||
</span>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLLiAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLLiAttributes> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<li
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="breadcrumb-item"
|
|
||||||
class={cn("gap-1 inline-flex items-center", className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</li>
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAnchorAttributes } from "svelte/elements";
|
|
||||||
import type { Snippet } from "svelte";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
href = undefined,
|
|
||||||
child,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAnchorAttributes> & {
|
|
||||||
child?: Snippet<[{ props: HTMLAnchorAttributes }]>;
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
const attrs = $derived({
|
|
||||||
"data-slot": "breadcrumb-link",
|
|
||||||
class: cn("hover:text-foreground transition-colors", className),
|
|
||||||
href,
|
|
||||||
...restProps,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if child}
|
|
||||||
{@render child({ props: attrs })}
|
|
||||||
{:else}
|
|
||||||
<a bind:this={ref} {...attrs}>
|
|
||||||
{@render children?.()}
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLOlAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLOlAttributes> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ol
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="breadcrumb-list"
|
|
||||||
class={cn("text-muted-foreground gap-1.5 text-xs flex flex-wrap items-center wrap-break-word", className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</ol>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<span
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="breadcrumb-page"
|
|
||||||
role="link"
|
|
||||||
aria-disabled="true"
|
|
||||||
aria-current="page"
|
|
||||||
class={cn("text-foreground font-normal", className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</span>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
||||||
import type { HTMLLiAttributes } from "svelte/elements";
|
|
||||||
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLLiAttributes> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<li
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="breadcrumb-separator"
|
|
||||||
role="presentation"
|
|
||||||
aria-hidden="true"
|
|
||||||
class={cn("[&>svg]:size-3.5", className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{#if children}
|
|
||||||
{@render children?.()}
|
|
||||||
{:else}
|
|
||||||
<ChevronRightIcon />
|
|
||||||
{/if}
|
|
||||||
</li>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { WithElementRef } from "$lib/utils.js";
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<nav
|
|
||||||
bind:this={ref}
|
|
||||||
data-slot="breadcrumb"
|
|
||||||
aria-label="breadcrumb"
|
|
||||||
class={cn("cn-breadcrumb", className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
{@render children?.()}
|
|
||||||
</nav>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user