mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-04 04:48:17 +08:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
771f438677 | ||
|
|
2005fb0aae | ||
|
|
51b813c107 | ||
|
|
0716b1075c | ||
|
|
6f3f07deb9 | ||
|
|
5177ae8472 | ||
|
|
72174a0453 | ||
|
|
39149594e0 | ||
|
|
c034e2dbcb | ||
|
|
2b86d50b57 | ||
|
|
fc841d7bb9 | ||
|
|
0770687bbd | ||
|
|
282fe6138f | ||
|
|
0a5cbb4529 | ||
|
|
6a48ad8608 | ||
|
|
3aa8b392dc | ||
|
|
1de2b380ef | ||
|
|
4afbb433a1 |
@@ -27,7 +27,6 @@ out/
|
||||
build
|
||||
dist
|
||||
*.tsbuildinfo
|
||||
.svelte-kit/
|
||||
|
||||
|
||||
# Debug
|
||||
|
||||
@@ -462,9 +462,8 @@ pnpm dev
|
||||
- http://react-email-demo.json-render.localhost:1355 - React Email Example
|
||||
- http://remotion-demo.json-render.localhost:1355 - Remotion Video Example
|
||||
- Chat Example: run `pnpm dev` in `examples/chat`
|
||||
- Svelte Example: run `pnpm dev` in `examples/svelte` or `examples/svelte-chat`
|
||||
- Vue Example: run `pnpm dev` in `examples/vue`
|
||||
- Vite Renderers (React + Vue + Svelte): run `pnpm dev` in `examples/vite-renderers`
|
||||
- Vite Renderers (React + Vue): run `pnpm dev` in `examples/vite-renderers`
|
||||
- React Native example: run `npx expo start` in `examples/react-native`
|
||||
|
||||
## How It Works
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import { pageMetadata } from "@/lib/page-metadata"
|
||||
export const metadata = pageMetadata("docs/api/svelte")
|
||||
|
||||
# @json-render/svelte
|
||||
|
||||
Svelte 5 components, providers, and helpers for rendering json-render specs.
|
||||
|
||||
## Installation
|
||||
|
||||
<PackageInstall packages="@json-render/core @json-render/svelte" />
|
||||
|
||||
Peer dependencies: `svelte ^5.0.0` and `zod ^4.0.0`.
|
||||
|
||||
<PackageInstall packages="svelte zod" />
|
||||
|
||||
## Components
|
||||
|
||||
### Renderer
|
||||
|
||||
```svelte
|
||||
<Renderer
|
||||
spec={spec} // Spec | null
|
||||
registry={registry}
|
||||
loading={false}
|
||||
/>
|
||||
```
|
||||
|
||||
Renders a spec with your component registry. If `spec` is `null`, it renders nothing.
|
||||
|
||||
### JsonUIProvider
|
||||
|
||||
Convenience wrapper around `StateProvider`, `VisibilityProvider`, `ValidationProvider`, and `ActionProvider`.
|
||||
|
||||
```svelte
|
||||
<JsonUIProvider
|
||||
initialState={{}}
|
||||
handlers={handlers}
|
||||
validationFunctions={validationFunctions}
|
||||
>
|
||||
<Renderer {spec} {registry} />
|
||||
</JsonUIProvider>
|
||||
```
|
||||
|
||||
## defineRegistry
|
||||
|
||||
Create a typed component registry and action handlers from a catalog.
|
||||
|
||||
```typescript
|
||||
import { defineRegistry } from "@json-render/svelte";
|
||||
|
||||
const { registry, handlers, executeAction } = defineRegistry(catalog, {
|
||||
components: {
|
||||
Card,
|
||||
Button,
|
||||
},
|
||||
actions: {
|
||||
submit: async (params, setState, state) => {
|
||||
// custom action logic
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`handlers` is designed for `JsonUIProvider`/`ActionProvider`. `executeAction` is an imperative helper.
|
||||
|
||||
## Component Props
|
||||
|
||||
Registry components receive `BaseComponentProps<TProps>`:
|
||||
|
||||
```typescript
|
||||
interface BaseComponentProps<TProps> {
|
||||
props: TProps;
|
||||
children?: Snippet;
|
||||
emit: (event: string) => void;
|
||||
bindings?: Record<string, string>;
|
||||
loading?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
Use `emit("eventName")` to trigger handlers declared in the spec `on` bindings.
|
||||
|
||||
## Context Helpers
|
||||
|
||||
Use these helpers inside Svelte components:
|
||||
|
||||
- `getStateValue(path)` - read/write state via `.current`
|
||||
- `getBoundProp(() => value, () => bindingPath)` - write back resolved `$bindState` / `$bindItem` values
|
||||
- `isVisible(condition)` - evaluate visibility via `.current`
|
||||
- `getAction(name)` - read a registered action handler via `.current`
|
||||
- `getFieldValidation(ctx, path, config)` - get field validation state + actions
|
||||
|
||||
For advanced usage, access full contexts:
|
||||
|
||||
- `getStateContext()`
|
||||
- `getActionContext()`
|
||||
- `getVisibilityContext()`
|
||||
- `getValidationContext()`
|
||||
- `getOptionalValidationContext()`
|
||||
|
||||
## Streaming
|
||||
|
||||
### createUIStream
|
||||
|
||||
```typescript
|
||||
const stream = createUIStream({
|
||||
api: "/api/generate-ui",
|
||||
onComplete: (spec) => console.log(spec),
|
||||
});
|
||||
|
||||
await stream.send("Create a login form");
|
||||
|
||||
console.log(stream.spec);
|
||||
console.log(stream.isStreaming);
|
||||
```
|
||||
|
||||
### createChatUI
|
||||
|
||||
```typescript
|
||||
const chat = createChatUI({ api: "/api/chat-ui" });
|
||||
await chat.send("Build a settings panel");
|
||||
console.log(chat.messages, chat.isStreaming);
|
||||
```
|
||||
|
||||
## Schema Export
|
||||
|
||||
Use `schema` from `@json-render/svelte` when defining catalogs for Svelte specs.
|
||||
@@ -21,14 +21,6 @@ Peer dependencies: `vue ^3.5.0` and `zod ^4.0.0`.
|
||||
|
||||
<PackageInstall packages="vue zod" />
|
||||
|
||||
## For Svelte
|
||||
|
||||
<PackageInstall packages="@json-render/core @json-render/svelte" />
|
||||
|
||||
Peer dependencies: `svelte ^5.0.0` and `zod ^4.0.0`.
|
||||
|
||||
<PackageInstall packages="svelte zod" />
|
||||
|
||||
## For React UI with shadcn/ui
|
||||
|
||||
Pre-built components for fast prototyping and production use:
|
||||
|
||||
@@ -80,18 +80,13 @@ export const docsNavigation: NavSection[] = [
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/image",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
title: "Svelte",
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/svelte",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
title: "Vue",
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/vue",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
title: "Renders with Vite (Vue / React / Svelte)",
|
||||
title: "Renders with Vite (Vue / React)",
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/vite-renderers",
|
||||
external: true,
|
||||
},
|
||||
@@ -126,7 +121,6 @@ export const docsNavigation: NavSection[] = [
|
||||
{ title: "@json-render/image", href: "/docs/api/image" },
|
||||
{ title: "@json-render/remotion", href: "/docs/api/remotion" },
|
||||
{ title: "@json-render/vue", href: "/docs/api/vue" },
|
||||
{ title: "@json-render/svelte", href: "/docs/api/svelte" },
|
||||
{ title: "@json-render/codegen", href: "/docs/api/codegen" },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -45,7 +45,6 @@ export const PAGE_TITLES: Record<string, string> = {
|
||||
"docs/api/react-pdf": "@json-render/react-pdf API",
|
||||
"docs/api/react-email": "@json-render/react-email API",
|
||||
"docs/api/react-native": "@json-render/react-native API",
|
||||
"docs/api/svelte": "@json-render/svelte API",
|
||||
"docs/api/codegen": "@json-render/codegen API",
|
||||
"docs/api/image": "@json-render/image API",
|
||||
"docs/api/remotion": "@json-render/remotion API",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"react-resizable-panels": "^4.4.1",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"zod": "4.3.6"
|
||||
"zod": "4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@internal/typescript-config": "workspace:*",
|
||||
|
||||
@@ -1,23 +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-*
|
||||
@@ -1 +0,0 @@
|
||||
engine-strict=true
|
||||
@@ -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
|
||||
npx sv create --template minimal --types ts --no-install svelte-chat
|
||||
```
|
||||
|
||||
## 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,16 +0,0 @@
|
||||
{
|
||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||
"tailwind": {
|
||||
"css": "src/app.css",
|
||||
"baseColor": "zinc"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils",
|
||||
"ui": "$lib/components/ui",
|
||||
"hooks": "$lib/hooks",
|
||||
"lib": "$lib"
|
||||
},
|
||||
"typescript": true,
|
||||
"registry": "https://shadcn-svelte.com/registry"
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "svelte-chat",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check-types": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@internationalized/date": "^3.10.0",
|
||||
"@lucide/svelte": "^0.561.0",
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/kit": "^2.50.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"bits-ui": "^2.14.4",
|
||||
"svelte": "^5.49.2",
|
||||
"svelte-check": "^4.3.6",
|
||||
"tailwind-variants": "^3.2.2",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/gateway": "^3.0.46",
|
||||
"@ai-sdk/svelte": "^4.0.96",
|
||||
"@json-render/core": "workspace:*",
|
||||
"@json-render/svelte": "workspace:*",
|
||||
"ai": "^6.0.86",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-svelte": "^0.500.0",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"zod": "4.3.6"
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-2xl: calc(var(--radius) + 8px);
|
||||
--radius-3xl: calc(var(--radius) + 12px);
|
||||
--radius-4xl: calc(var(--radius) + 16px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-shimmer {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
currentColor 25%,
|
||||
hsl(0 0% 64%) 50%,
|
||||
currentColor 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: shimmer 2s ease-in-out infinite;
|
||||
}
|
||||
Vendored
-13
@@ -1,13 +0,0 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -1,11 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,92 +0,0 @@
|
||||
import { ToolLoopAgent, stepCountIs } from "ai";
|
||||
import { createGateway } from "@ai-sdk/gateway";
|
||||
import { explorerCatalog } from "./render/catalog";
|
||||
import { getWeather } from "./tools/weather";
|
||||
import { getGitHubRepo, getGitHubPullRequests } from "./tools/github";
|
||||
import { getCryptoPrice, getCryptoPriceHistory } from "./tools/crypto";
|
||||
import { getHackerNewsTop } from "./tools/hackernews";
|
||||
import { webSearch } from "./tools/search";
|
||||
import { env } from "$env/dynamic/private";
|
||||
|
||||
const DEFAULT_MODEL = "anthropic/claude-haiku-4.5";
|
||||
|
||||
const AGENT_INSTRUCTIONS = `You are a knowledgeable assistant that helps users explore data and learn about any topic. You look up real-time information, build visual dashboards, and create rich educational content.
|
||||
|
||||
WORKFLOW:
|
||||
1. Call the appropriate tools to gather relevant data. Use webSearch for general topics not covered by specialized tools.
|
||||
2. Respond with a brief, conversational summary of what you found.
|
||||
3. Then output the JSONL UI spec wrapped in a \`\`\`spec fence to render a rich visual experience.
|
||||
|
||||
RULES:
|
||||
- Always call tools FIRST to get real data. Never make up data.
|
||||
- Embed the fetched data directly in /state paths so components can reference it.
|
||||
- Use Card components to group related information.
|
||||
- NEVER nest a Card inside another Card. If you need sub-sections inside a Card, use Stack, Separator, Heading, or Accordion instead.
|
||||
- Use Grid for multi-column layouts.
|
||||
- Use Metric for key numeric values (temperature, stars, price, etc.).
|
||||
- Use Table for lists of items (stories, forecasts, languages, etc.).
|
||||
- Use BarChart or LineChart for numeric trends and time-series data.
|
||||
- Use PieChart for compositional/proportional data (market share, breakdowns, distributions).
|
||||
- Use Tabs when showing multiple categories of data side by side.
|
||||
- Use Badge for status indicators.
|
||||
- Use Callout for key facts, tips, warnings, or important takeaways.
|
||||
- Use Accordion to organize detailed sections the user can expand for deeper reading.
|
||||
- Use Timeline for historical events, processes, step-by-step explanations, or milestones.
|
||||
- When teaching about a topic, combine multiple component types to create a rich, engaging experience.
|
||||
|
||||
DATA BINDING:
|
||||
- The state model is the single source of truth. Put fetched data in /state, then reference it with { "$state": "/json/pointer" } in any prop.
|
||||
- $state works on ANY prop at ANY nesting level. The renderer resolves expressions before components receive props.
|
||||
- Scalar binding: "title": { "$state": "/quiz/title" }
|
||||
- Array binding: "items": { "$state": "/quiz/questions" } (for Accordion, Timeline, etc.)
|
||||
- For Table, BarChart, LineChart, and PieChart, use { "$state": "/path" } on the data prop to bind read-only data from state.
|
||||
- Always emit /state patches BEFORE the elements that reference them, so data is available when the UI renders.
|
||||
- Always use the { "$state": "/foo" } object syntax for data binding.
|
||||
|
||||
INTERACTIVITY:
|
||||
- You can use visible, repeat, on.press, and $cond/$then/$else freely.
|
||||
- visible: Conditionally show/hide elements based on state. e.g. "visible": { "$state": "/q1/answer", "eq": "a" }
|
||||
- repeat: Iterate over state arrays. e.g. "repeat": { "statePath": "/items" }
|
||||
- on.press: Trigger actions on button clicks. e.g. "on": { "press": { "action": "setState", "params": { "statePath": "/submitted", "value": true } } }
|
||||
- $cond/$then/$else: Conditional prop values. e.g. { "$cond": { "$state": "/correct" }, "$then": "Correct!", "$else": "Try again" }
|
||||
|
||||
BUILT-IN ACTIONS (use with on.press):
|
||||
- setState: Set a value at a state path. params: { statePath: "/foo", value: "bar" }
|
||||
- pushState: Append to an array. params: { statePath: "/items", value: { ... } }
|
||||
- removeState: Remove by index. params: { statePath: "/items", index: 0 }
|
||||
|
||||
INPUT COMPONENTS:
|
||||
- RadioGroup: Renders radio buttons. Writes selected value to statePath automatically.
|
||||
- SelectInput: Dropdown select. Writes selected value to statePath automatically.
|
||||
- TextInput: Text input field. Writes entered value to statePath automatically.
|
||||
- Button: Clickable button. Use on.press to trigger actions.
|
||||
|
||||
${explorerCatalog.prompt({
|
||||
mode: "chat",
|
||||
customRules: [
|
||||
"NEVER use viewport height classes (min-h-screen, h-screen) — the UI renders inside a fixed-size container.",
|
||||
"Prefer Grid with columns='2' or columns='3' for side-by-side layouts.",
|
||||
"Use Metric components for key numbers instead of plain Text.",
|
||||
"Put chart data arrays in /state and reference them with { $state: '/path' } on the data prop.",
|
||||
"Keep the UI clean and information-dense — no excessive padding or empty space.",
|
||||
"For educational prompts ('teach me about', 'explain', 'what is'), use a mix of Callout, Accordion, Timeline, and charts to make the content visually rich.",
|
||||
],
|
||||
})}`;
|
||||
|
||||
export const gateway = createGateway({ apiKey: env.AI_GATEWAY_API_KEY });
|
||||
|
||||
export const agent = new ToolLoopAgent({
|
||||
model: gateway(env.AI_GATEWAY_MODEL || DEFAULT_MODEL),
|
||||
instructions: AGENT_INSTRUCTIONS,
|
||||
tools: {
|
||||
getWeather,
|
||||
getGitHubRepo,
|
||||
getGitHubPullRequests,
|
||||
getCryptoPrice,
|
||||
getCryptoPriceHistory,
|
||||
getHackerNewsTop,
|
||||
webSearch,
|
||||
},
|
||||
stopWhen: stepCountIs(5),
|
||||
temperature: 0.7,
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,22 +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-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
||||
{...restProps}
|
||||
>
|
||||
<div class={cn("pt-0 pb-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("border-b last:border-b-0", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,32 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
|
||||
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:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-start text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronDownIcon
|
||||
class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200"
|
||||
/>
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
@@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
}: AccordionPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<AccordionPrimitive.Root
|
||||
bind:ref
|
||||
bind:value={value as never}
|
||||
data-slot="accordion"
|
||||
{...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,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 col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
||||
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-title"
|
||||
class={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -1,44 +0,0 @@
|
||||
<script lang="ts" module>
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
|
||||
export const alertVariants = tv({
|
||||
base: "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||
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"
|
||||
class={cn(alertVariants({ variant }), className)}
|
||||
{...restProps}
|
||||
role="alert"
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -1,14 +0,0 @@
|
||||
import Root from "./alert.svelte";
|
||||
import Description from "./alert-description.svelte";
|
||||
import Title from "./alert-title.svelte";
|
||||
export { alertVariants, type AlertVariant } from "./alert.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Description,
|
||||
Title,
|
||||
//
|
||||
Root as Alert,
|
||||
Description as AlertDescription,
|
||||
Title as AlertTitle,
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
<script lang="ts" module>
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
|
||||
export const badgeVariants = tv({
|
||||
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3",
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent",
|
||||
destructive:
|
||||
"bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white",
|
||||
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||
},
|
||||
},
|
||||
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,82 +0,0 @@
|
||||
<script lang="ts" module>
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
|
||||
export const buttonVariants = tv({
|
||||
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs",
|
||||
destructive:
|
||||
"bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white shadow-xs",
|
||||
outline:
|
||||
"bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
"icon-sm": "size-8",
|
||||
"icon-lg": "size-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
|
||||
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
|
||||
|
||||
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
||||
WithElementRef<HTMLAnchorAttributes> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
let {
|
||||
class: className,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
ref = $bindable(null),
|
||||
href = undefined,
|
||||
type = "button",
|
||||
disabled,
|
||||
children,
|
||||
...restProps
|
||||
}: ButtonProps = $props();
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
href={disabled ? undefined : href}
|
||||
aria-disabled={disabled}
|
||||
role={disabled ? "link" : undefined}
|
||||
tabindex={disabled ? -1 : undefined}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
{:else}
|
||||
<button
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
{type}
|
||||
{disabled}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
{/if}
|
||||
@@ -1,17 +0,0 @@
|
||||
import Root, {
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
buttonVariants,
|
||||
} from "./button.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
type ButtonProps as Props,
|
||||
//
|
||||
Root as Button,
|
||||
buttonVariants,
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
};
|
||||
@@ -1,20 +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="card-action"
|
||||
class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -1,15 +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="card-content" class={cn("px-6", 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<HTMLParagraphElement>> = $props();
|
||||
</script>
|
||||
|
||||
<p
|
||||
bind:this={ref}
|
||||
data-slot="card-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</p>
|
||||
@@ -1,20 +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="card-footer"
|
||||
class={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -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="card-header"
|
||||
class={cn(
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
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="card-title"
|
||||
class={cn("leading-none font-semibold", 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="card"
|
||||
class={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -1,25 +0,0 @@
|
||||
import Root from "./card.svelte";
|
||||
import Content from "./card-content.svelte";
|
||||
import Description from "./card-description.svelte";
|
||||
import Footer from "./card-footer.svelte";
|
||||
import Header from "./card-header.svelte";
|
||||
import Title from "./card-title.svelte";
|
||||
import Action from "./card-action.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
Action,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
Action as CardAction,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import Root from "./input.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
|
||||
|
||||
type Props = WithElementRef<
|
||||
Omit<HTMLInputAttributes, "type"> &
|
||||
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
|
||||
>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
type,
|
||||
files = $bindable(),
|
||||
class: className,
|
||||
"data-slot": dataSlot = "input",
|
||||
...restProps
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if type === "file"}
|
||||
<input
|
||||
bind:this={ref}
|
||||
data-slot={dataSlot}
|
||||
class={cn(
|
||||
"selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 pt-1.5 text-sm font-medium shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className
|
||||
)}
|
||||
type="file"
|
||||
bind:files
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
bind:this={ref}
|
||||
data-slot={dataSlot}
|
||||
class={cn(
|
||||
"border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className
|
||||
)}
|
||||
{type}
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{/if}
|
||||
@@ -1,7 +0,0 @@
|
||||
import Root from "./label.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Label,
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Label as LabelPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: LabelPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<LabelPrimitive.Root
|
||||
bind:ref
|
||||
data-slot="label"
|
||||
class={cn(
|
||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,7 +0,0 @@
|
||||
import Root from "./progress.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Progress,
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Progress as ProgressPrimitive } from "bits-ui";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
max = 100,
|
||||
value,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<ProgressPrimitive.RootProps> = $props();
|
||||
</script>
|
||||
|
||||
<ProgressPrimitive.Root
|
||||
bind:ref
|
||||
data-slot="progress"
|
||||
class={cn("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", className)}
|
||||
{value}
|
||||
{max}
|
||||
{...restProps}
|
||||
>
|
||||
<div
|
||||
data-slot="progress-indicator"
|
||||
class="bg-primary h-full w-full flex-1 transition-all"
|
||||
style="transform: translateX(-{100 - (100 * (value ?? 0)) / (max ?? 1)}%)"
|
||||
></div>
|
||||
</ProgressPrimitive.Root>
|
||||
@@ -1,10 +0,0 @@
|
||||
import Root from "./radio-group.svelte";
|
||||
import Item from "./radio-group-item.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Item,
|
||||
//
|
||||
Root as RadioGroup,
|
||||
Item as RadioGroupItem,
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<RadioGroupPrimitive.ItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<RadioGroupPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="radio-group-item"
|
||||
class={cn(
|
||||
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked })}
|
||||
<div data-slot="radio-group-indicator" class="relative flex items-center justify-center">
|
||||
{#if checked}
|
||||
<CircleIcon
|
||||
class="fill-primary absolute start-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</RadioGroupPrimitive.Item>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value = $bindable(""),
|
||||
...restProps
|
||||
}: RadioGroupPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<RadioGroupPrimitive.Root
|
||||
bind:ref
|
||||
bind:value
|
||||
data-slot="radio-group"
|
||||
class={cn("grid gap-3", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,37 +0,0 @@
|
||||
import Root from "./select.svelte";
|
||||
import Group from "./select-group.svelte";
|
||||
import Label from "./select-label.svelte";
|
||||
import Item from "./select-item.svelte";
|
||||
import Content from "./select-content.svelte";
|
||||
import Trigger from "./select-trigger.svelte";
|
||||
import Separator from "./select-separator.svelte";
|
||||
import ScrollDownButton from "./select-scroll-down-button.svelte";
|
||||
import ScrollUpButton from "./select-scroll-up-button.svelte";
|
||||
import GroupHeading from "./select-group-heading.svelte";
|
||||
import Portal from "./select-portal.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Group,
|
||||
Label,
|
||||
Item,
|
||||
Content,
|
||||
Trigger,
|
||||
Separator,
|
||||
ScrollDownButton,
|
||||
ScrollUpButton,
|
||||
GroupHeading,
|
||||
Portal,
|
||||
//
|
||||
Root as Select,
|
||||
Group as SelectGroup,
|
||||
Label as SelectLabel,
|
||||
Item as SelectItem,
|
||||
Content as SelectContent,
|
||||
Trigger as SelectTrigger,
|
||||
Separator as SelectSeparator,
|
||||
ScrollDownButton as SelectScrollDownButton,
|
||||
ScrollUpButton as SelectScrollUpButton,
|
||||
GroupHeading as SelectGroupHeading,
|
||||
Portal as SelectPortal,
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import SelectPortal from "./select-portal.svelte";
|
||||
import SelectScrollUpButton from "./select-scroll-up-button.svelte";
|
||||
import SelectScrollDownButton from "./select-scroll-down-button.svelte";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
import type { ComponentProps } from "svelte";
|
||||
import type { WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
children,
|
||||
preventScroll = true,
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.ContentProps> & {
|
||||
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof SelectPortal>>;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<SelectPortal {...portalProps}>
|
||||
<SelectPrimitive.Content
|
||||
bind:ref
|
||||
{sideOffset}
|
||||
{preventScroll}
|
||||
data-slot="select-content"
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--bits-select-content-available-height) min-w-[8rem] origin-(--bits-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
class={cn(
|
||||
"h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1 p-1"
|
||||
)}
|
||||
>
|
||||
{@render children?.()}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPortal>
|
||||
@@ -1,21 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: ComponentProps<typeof SelectPrimitive.GroupHeading> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.GroupHeading
|
||||
bind:ref
|
||||
data-slot="select-group-heading"
|
||||
class={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</SelectPrimitive.GroupHeading>
|
||||
@@ -1,7 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Group bind:ref data-slot="select-group" {...restProps} />
|
||||
@@ -1,38 +0,0 @@
|
||||
<script lang="ts">
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value,
|
||||
label,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.ItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Item
|
||||
bind:ref
|
||||
{value}
|
||||
data-slot="select-item"
|
||||
class={cn(
|
||||
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 ps-2 pe-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ selected, highlighted })}
|
||||
<span class="absolute end-2 flex size-3.5 items-center justify-center">
|
||||
{#if selected}
|
||||
<CheckIcon class="size-4" />
|
||||
{/if}
|
||||
</span>
|
||||
{#if childrenProp}
|
||||
{@render childrenProp({ selected, highlighted })}
|
||||
{:else}
|
||||
{label || value}
|
||||
{/if}
|
||||
{/snippet}
|
||||
</SelectPrimitive.Item>
|
||||
@@ -1,20 +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="select-label"
|
||||
class={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -1,7 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
let { ...restProps }: SelectPrimitive.PortalProps = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Portal {...restProps} />
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
bind:ref
|
||||
data-slot="select-scroll-down-button"
|
||||
class={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChevronDownIcon class="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import ChevronUpIcon from "@lucide/svelte/icons/chevron-up";
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<SelectPrimitive.ScrollUpButtonProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
bind:ref
|
||||
data-slot="select-scroll-up-button"
|
||||
class={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChevronUpIcon class="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
@@ -1,18 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Separator as SeparatorPrimitive } from "bits-ui";
|
||||
import { Separator } from "$lib/components/ui/separator/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: SeparatorPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<Separator
|
||||
bind:ref
|
||||
data-slot="select-separator"
|
||||
class={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,29 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
size = "default",
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.TriggerProps> & {
|
||||
size?: "sm" | "default";
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Trigger
|
||||
bind:ref
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
class={cn(
|
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none select-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronDownIcon class="size-4 opacity-50" />
|
||||
</SelectPrimitive.Trigger>
|
||||
@@ -1,11 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
}: SelectPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Root bind:open bind:value={value as never} {...restProps} />
|
||||
@@ -1,7 +0,0 @@
|
||||
import Root from "./separator.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Separator as SeparatorPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
"data-slot": dataSlot = "separator",
|
||||
...restProps
|
||||
}: SeparatorPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<SeparatorPrimitive.Root
|
||||
bind:ref
|
||||
data-slot={dataSlot}
|
||||
class={cn(
|
||||
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:min-h-full data-[orientation=vertical]:w-px",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,28 +0,0 @@
|
||||
import Root from "./table.svelte";
|
||||
import Body from "./table-body.svelte";
|
||||
import Caption from "./table-caption.svelte";
|
||||
import Cell from "./table-cell.svelte";
|
||||
import Footer from "./table-footer.svelte";
|
||||
import Head from "./table-head.svelte";
|
||||
import Header from "./table-header.svelte";
|
||||
import Row from "./table-row.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Body,
|
||||
Caption,
|
||||
Cell,
|
||||
Footer,
|
||||
Head,
|
||||
Header,
|
||||
Row,
|
||||
//
|
||||
Root as Table,
|
||||
Body as TableBody,
|
||||
Caption as TableCaption,
|
||||
Cell as TableCell,
|
||||
Footer as TableFooter,
|
||||
Head as TableHead,
|
||||
Header as TableHeader,
|
||||
Row as TableRow,
|
||||
};
|
||||
@@ -1,20 +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<HTMLTableSectionElement>> = $props();
|
||||
</script>
|
||||
|
||||
<tbody
|
||||
bind:this={ref}
|
||||
data-slot="table-body"
|
||||
class={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</tbody>
|
||||
@@ -1,20 +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<HTMLElement>> = $props();
|
||||
</script>
|
||||
|
||||
<caption
|
||||
bind:this={ref}
|
||||
data-slot="table-caption"
|
||||
class={cn("text-muted-foreground mt-4 text-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</caption>
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLTdAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLTdAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<td
|
||||
bind:this={ref}
|
||||
data-slot="table-cell"
|
||||
class={cn(
|
||||
"bg-clip-padding p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</td>
|
||||
@@ -1,20 +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<HTMLTableSectionElement>> = $props();
|
||||
</script>
|
||||
|
||||
<tfoot
|
||||
bind:this={ref}
|
||||
data-slot="table-footer"
|
||||
class={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</tfoot>
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLThAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLThAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<th
|
||||
bind:this={ref}
|
||||
data-slot="table-head"
|
||||
class={cn(
|
||||
"text-foreground h-10 bg-clip-padding px-2 text-start align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pe-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</th>
|
||||
@@ -1,20 +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<HTMLTableSectionElement>> = $props();
|
||||
</script>
|
||||
|
||||
<thead
|
||||
bind:this={ref}
|
||||
data-slot="table-header"
|
||||
class={cn("[&_tr]:border-b", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</thead>
|
||||
@@ -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<HTMLTableRowElement>> = $props();
|
||||
</script>
|
||||
|
||||
<tr
|
||||
bind:this={ref}
|
||||
data-slot="table-row"
|
||||
class={cn(
|
||||
"hover:[&,&>svelte-css-wrapper]:[&>th,td]:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</tr>
|
||||
@@ -1,22 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLTableAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLTableAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<div data-slot="table-container" class="relative w-full overflow-x-auto">
|
||||
<table
|
||||
bind:this={ref}
|
||||
data-slot="table"
|
||||
class={cn("w-full caption-bottom text-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</table>
|
||||
</div>
|
||||
@@ -1,16 +0,0 @@
|
||||
import Root from "./tabs.svelte";
|
||||
import Content from "./tabs-content.svelte";
|
||||
import List from "./tabs-list.svelte";
|
||||
import Trigger from "./tabs-trigger.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
List,
|
||||
Trigger,
|
||||
//
|
||||
Root as Tabs,
|
||||
Content as TabsContent,
|
||||
List as TabsList,
|
||||
Trigger as TabsTrigger,
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.ContentProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="tabs-content"
|
||||
class={cn("flex-1 outline-none", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.ListProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.List
|
||||
bind:ref
|
||||
data-slot="tabs-list"
|
||||
class={cn(
|
||||
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.Trigger
|
||||
bind:ref
|
||||
data-slot="tabs-trigger"
|
||||
class={cn(
|
||||
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(""),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.Root
|
||||
bind:ref
|
||||
bind:value
|
||||
data-slot="tabs"
|
||||
class={cn("flex flex-col gap-2", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,4 +0,0 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
export { explorerCatalog } from "./render/catalog";
|
||||
export { registry } from "./render/registry";
|
||||
export { agent } from "./agent";
|
||||
@@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Renderer, JsonUIProvider } from "@json-render/svelte";
|
||||
import type { Spec } from "@json-render/svelte";
|
||||
import { registry } from "./registry";
|
||||
|
||||
interface Props {
|
||||
spec: Spec;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
let { spec, loading = false }: Props = $props();
|
||||
</script>
|
||||
|
||||
<JsonUIProvider initialState={spec.state}>
|
||||
<Renderer {spec} {registry} {loading} />
|
||||
</JsonUIProvider>
|
||||
@@ -1,363 +0,0 @@
|
||||
import { schema } from "@json-render/svelte/schema";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* json-render + AI SDK Example Catalog (Svelte)
|
||||
*
|
||||
* Components for rendering data dashboards generated by the ToolLoopAgent.
|
||||
* Data flows in through tools (weather, GitHub, crypto, HN), not user actions.
|
||||
*/
|
||||
export const explorerCatalog = schema.createCatalog({
|
||||
components: {
|
||||
// Layout
|
||||
Stack: {
|
||||
props: z.object({
|
||||
direction: z.enum(["horizontal", "vertical"]).nullable(),
|
||||
gap: z.enum(["sm", "md", "lg"]).nullable(),
|
||||
wrap: z.boolean().nullable(),
|
||||
}),
|
||||
slots: ["default"],
|
||||
description: "Flex layout container",
|
||||
example: { direction: "vertical", gap: "md", wrap: null },
|
||||
},
|
||||
|
||||
Card: {
|
||||
props: z.object({
|
||||
title: z.string().nullable(),
|
||||
description: z.string().nullable(),
|
||||
}),
|
||||
slots: ["default"],
|
||||
description: "Card container with optional title and description",
|
||||
example: { title: "Weather", description: "Current conditions" },
|
||||
},
|
||||
|
||||
Grid: {
|
||||
props: z.object({
|
||||
columns: z.enum(["1", "2", "3", "4"]).nullable(),
|
||||
gap: z.enum(["sm", "md", "lg"]).nullable(),
|
||||
}),
|
||||
slots: ["default"],
|
||||
description: "Responsive grid layout container",
|
||||
example: { columns: "3", gap: "md" },
|
||||
},
|
||||
|
||||
// Typography
|
||||
Heading: {
|
||||
props: z.object({
|
||||
text: z.string(),
|
||||
level: z.enum(["h1", "h2", "h3", "h4"]).nullable(),
|
||||
}),
|
||||
description: "Section heading",
|
||||
example: { text: "Data Explorer", level: "h1" },
|
||||
},
|
||||
|
||||
Text: {
|
||||
props: z.object({
|
||||
content: z.string(),
|
||||
muted: z.boolean().nullable(),
|
||||
}),
|
||||
description: "Text content",
|
||||
example: { content: "Here is your data overview." },
|
||||
},
|
||||
|
||||
// Data display
|
||||
Badge: {
|
||||
props: z.object({
|
||||
text: z.string(),
|
||||
variant: z
|
||||
.enum(["default", "secondary", "destructive", "outline"])
|
||||
.nullable(),
|
||||
}),
|
||||
description: "Status badge",
|
||||
example: { text: "Live", variant: "default" },
|
||||
},
|
||||
|
||||
Alert: {
|
||||
props: z.object({
|
||||
variant: z.enum(["default", "destructive"]).nullable(),
|
||||
title: z.string(),
|
||||
description: z.string().nullable(),
|
||||
}),
|
||||
description: "Alert or info message",
|
||||
},
|
||||
|
||||
Separator: {
|
||||
props: z.object({}),
|
||||
description: "Visual divider",
|
||||
},
|
||||
|
||||
Metric: {
|
||||
props: z.object({
|
||||
label: z.string(),
|
||||
value: z.string(),
|
||||
detail: z.string().nullable(),
|
||||
trend: z.enum(["up", "down", "neutral"]).nullable(),
|
||||
}),
|
||||
description:
|
||||
"Single metric display with label, value, and optional trend indicator",
|
||||
example: {
|
||||
label: "Temperature",
|
||||
value: "72F",
|
||||
detail: "Feels like 68F",
|
||||
trend: "up",
|
||||
},
|
||||
},
|
||||
|
||||
Table: {
|
||||
props: z.object({
|
||||
data: z.array(z.record(z.string(), z.unknown())),
|
||||
columns: z.array(
|
||||
z.object({
|
||||
key: z.string(),
|
||||
label: z.string(),
|
||||
}),
|
||||
),
|
||||
emptyMessage: z.string().nullable(),
|
||||
}),
|
||||
description:
|
||||
'Data table. Use { "$state": "/path" } to bind read-only data from state.',
|
||||
example: {
|
||||
data: { $state: "/stories" },
|
||||
columns: [
|
||||
{ key: "title", label: "Title" },
|
||||
{ key: "score", label: "Score" },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
Link: {
|
||||
props: z.object({
|
||||
text: z.string(),
|
||||
href: z.string(),
|
||||
}),
|
||||
description: "External link that opens in a new tab",
|
||||
example: { text: "View on GitHub", href: "https://github.com" },
|
||||
},
|
||||
|
||||
// Charts
|
||||
BarChart: {
|
||||
props: z.object({
|
||||
title: z.string().nullable(),
|
||||
data: z.array(z.record(z.string(), z.unknown())),
|
||||
xKey: z.string(),
|
||||
yKey: z.string(),
|
||||
aggregate: z.enum(["sum", "count", "avg"]).nullable(),
|
||||
color: z.string().nullable(),
|
||||
height: z.number().nullable(),
|
||||
}),
|
||||
description:
|
||||
'Bar chart visualization. Use { "$state": "/path" } to bind read-only data. xKey is the category field, yKey is the numeric value field.',
|
||||
},
|
||||
|
||||
LineChart: {
|
||||
props: z.object({
|
||||
title: z.string().nullable(),
|
||||
data: z.array(z.record(z.string(), z.unknown())),
|
||||
xKey: z.string(),
|
||||
yKey: z.string(),
|
||||
aggregate: z.enum(["sum", "count", "avg"]).nullable(),
|
||||
color: z.string().nullable(),
|
||||
height: z.number().nullable(),
|
||||
}),
|
||||
description:
|
||||
'Line chart visualization. Use { "$state": "/path" } to bind read-only data. xKey is the x-axis field, yKey is the numeric value field.',
|
||||
},
|
||||
|
||||
// Interactive
|
||||
Tabs: {
|
||||
props: z.object({
|
||||
defaultValue: z.string().nullable(),
|
||||
tabs: z.array(
|
||||
z.object({
|
||||
value: z.string(),
|
||||
label: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
slots: ["default"],
|
||||
description: "Tabbed content container",
|
||||
},
|
||||
|
||||
TabContent: {
|
||||
props: z.object({
|
||||
value: z.string(),
|
||||
}),
|
||||
slots: ["default"],
|
||||
description: "Content for a specific tab",
|
||||
},
|
||||
|
||||
Progress: {
|
||||
props: z.object({
|
||||
value: z.number(),
|
||||
max: z.number().nullable(),
|
||||
}),
|
||||
description: "Progress bar",
|
||||
},
|
||||
|
||||
Skeleton: {
|
||||
props: z.object({
|
||||
width: z.string().nullable(),
|
||||
height: z.string().nullable(),
|
||||
}),
|
||||
description: "Loading placeholder",
|
||||
},
|
||||
|
||||
// Educational / Rich content
|
||||
Callout: {
|
||||
props: z.object({
|
||||
type: z.enum(["info", "tip", "warning", "important"]).nullable(),
|
||||
title: z.string().nullable(),
|
||||
content: z.string(),
|
||||
}),
|
||||
description:
|
||||
"Highlighted callout box for tips, warnings, notes, or key information",
|
||||
example: {
|
||||
type: "tip",
|
||||
title: "Did you know?",
|
||||
content: "The sun is about 93 million miles from Earth.",
|
||||
},
|
||||
},
|
||||
|
||||
Accordion: {
|
||||
props: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
title: z.string(),
|
||||
content: z.string(),
|
||||
}),
|
||||
),
|
||||
type: z.enum(["single", "multiple"]).nullable(),
|
||||
}),
|
||||
description:
|
||||
"Collapsible accordion sections for organizing detailed content",
|
||||
example: {
|
||||
items: [{ title: "Overview", content: "A brief introduction." }],
|
||||
type: "multiple",
|
||||
},
|
||||
},
|
||||
|
||||
Timeline: {
|
||||
props: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
title: z.string(),
|
||||
description: z.string().nullable(),
|
||||
date: z.string().nullable(),
|
||||
status: z.enum(["completed", "current", "upcoming"]).nullable(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
description:
|
||||
"Vertical timeline showing ordered events, steps, or historical milestones",
|
||||
example: {
|
||||
items: [
|
||||
{
|
||||
title: "Discovery",
|
||||
description: "Initial breakthrough",
|
||||
date: "1905",
|
||||
status: "completed",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
PieChart: {
|
||||
props: z.object({
|
||||
title: z.string().nullable(),
|
||||
data: z.array(z.record(z.string(), z.unknown())),
|
||||
nameKey: z.string(),
|
||||
valueKey: z.string(),
|
||||
height: z.number().nullable(),
|
||||
}),
|
||||
description:
|
||||
'Pie/donut chart for proportional data. Use { "$state": "/path" } to bind read-only data. nameKey is the label field, valueKey is the numeric value field.',
|
||||
},
|
||||
|
||||
// Interactive / Input
|
||||
RadioGroup: {
|
||||
props: z.object({
|
||||
label: z.string().nullable(),
|
||||
value: z.string().nullable(),
|
||||
options: z.array(
|
||||
z.object({
|
||||
value: z.string(),
|
||||
label: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
description:
|
||||
'Radio button group for single selection. Use { "$bindState": "/path" } for two-way binding.',
|
||||
example: {
|
||||
label: "Choose one",
|
||||
value: { $bindState: "/answer" },
|
||||
options: [
|
||||
{ value: "a", label: "Option A" },
|
||||
{ value: "b", label: "Option B" },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
SelectInput: {
|
||||
props: z.object({
|
||||
label: z.string().nullable(),
|
||||
value: z.string().nullable(),
|
||||
placeholder: z.string().nullable(),
|
||||
options: z.array(
|
||||
z.object({
|
||||
value: z.string(),
|
||||
label: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
description:
|
||||
'Dropdown select input. Use { "$bindState": "/path" } for two-way binding.',
|
||||
example: {
|
||||
label: "Country",
|
||||
value: { $bindState: "/selectedCountry" },
|
||||
placeholder: "Select a country",
|
||||
options: [
|
||||
{ value: "us", label: "United States" },
|
||||
{ value: "uk", label: "United Kingdom" },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
TextInput: {
|
||||
props: z.object({
|
||||
label: z.string().nullable(),
|
||||
value: z.string().nullable(),
|
||||
placeholder: z.string().nullable(),
|
||||
type: z.enum(["text", "email", "number", "password", "url"]).nullable(),
|
||||
}),
|
||||
description:
|
||||
'Text input field. Use { "$bindState": "/path" } for two-way binding.',
|
||||
example: {
|
||||
label: "Your name",
|
||||
value: { $bindState: "/userName" },
|
||||
placeholder: "Enter your name",
|
||||
type: "text",
|
||||
},
|
||||
},
|
||||
|
||||
Button: {
|
||||
props: z.object({
|
||||
label: z.string(),
|
||||
variant: z
|
||||
.enum(["default", "secondary", "destructive", "outline", "ghost"])
|
||||
.nullable(),
|
||||
size: z.enum(["default", "sm", "lg"]).nullable(),
|
||||
disabled: z.boolean().nullable(),
|
||||
}),
|
||||
description:
|
||||
"Clickable button. Use with on.press to trigger actions like setState.",
|
||||
example: {
|
||||
label: "Submit",
|
||||
variant: "default",
|
||||
size: "default",
|
||||
disabled: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
actions: {},
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import * as Accordion from "$lib/components/ui/accordion";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
items: Array<{ title: string; content: string }>;
|
||||
type?: "single" | "multiple" | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Accordion.Root type={props.type === "single" ? "single" : "multiple"} class="w-full">
|
||||
{#each props.items ?? [] as item, i}
|
||||
<Accordion.Item value="item-{i}">
|
||||
<Accordion.Trigger>{item.title}</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
<p class="text-muted-foreground">{item.content}</p>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
{/each}
|
||||
</Accordion.Root>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import * as Alert from "$lib/components/ui/alert";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
variant?: "default" | "destructive" | null;
|
||||
title: string;
|
||||
description?: string | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Alert.Root variant={props.variant ?? "default"}>
|
||||
<Alert.Title>{props.title}</Alert.Title>
|
||||
{#if props.description}
|
||||
<Alert.Description>{props.description}</Alert.Description>
|
||||
{/if}
|
||||
</Alert.Root>
|
||||
@@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import { Badge } from "$lib/components/ui/badge";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
text: string;
|
||||
variant?: "default" | "secondary" | "destructive" | "outline" | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Badge variant={props.variant ?? "default"}>{props.text}</Badge>
|
||||
@@ -1,99 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
title?: string | null;
|
||||
data: Array<Record<string, unknown>>;
|
||||
xKey: string;
|
||||
yKey: string;
|
||||
aggregate?: "sum" | "count" | "avg" | null;
|
||||
color?: string | null;
|
||||
height?: number | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
|
||||
const rawData = $derived(props.data);
|
||||
const rawItems = $derived<Array<Record<string, unknown>>>(
|
||||
Array.isArray(rawData)
|
||||
? rawData
|
||||
: Array.isArray((rawData as Record<string, unknown>)?.data)
|
||||
? ((rawData as Record<string, unknown>).data as Array<Record<string, unknown>>)
|
||||
: []
|
||||
);
|
||||
|
||||
const processedData = $derived(() => {
|
||||
if (rawItems.length === 0) return { items: [], valueKey: props.yKey };
|
||||
|
||||
const { xKey, yKey, aggregate } = props;
|
||||
|
||||
if (!aggregate) {
|
||||
const formatted = rawItems.map((item) => ({
|
||||
label: String(item[xKey] ?? ""),
|
||||
value: typeof item[yKey] === "number" ? item[yKey] : parseFloat(String(item[yKey])) || 0,
|
||||
}));
|
||||
return { items: formatted, valueKey: yKey };
|
||||
}
|
||||
|
||||
const groups = new Map<string, Array<Record<string, unknown>>>();
|
||||
for (const item of rawItems) {
|
||||
const groupKey = String(item[xKey] ?? "unknown");
|
||||
const group = groups.get(groupKey) ?? [];
|
||||
group.push(item);
|
||||
groups.set(groupKey, group);
|
||||
}
|
||||
|
||||
const valueKey = aggregate === "count" ? "count" : yKey;
|
||||
const aggregated: Array<{ label: string; value: number }> = [];
|
||||
|
||||
for (const [key, group] of Array.from(groups.entries()).sort((a, b) => a[0].localeCompare(b[0]))) {
|
||||
let value: number;
|
||||
if (aggregate === "count") {
|
||||
value = group.length;
|
||||
} else if (aggregate === "sum") {
|
||||
value = group.reduce((sum, item) => {
|
||||
const v = item[yKey];
|
||||
return sum + (typeof v === "number" ? v : parseFloat(String(v)) || 0);
|
||||
}, 0);
|
||||
} else {
|
||||
const sum = group.reduce((s, item) => {
|
||||
const v = item[yKey];
|
||||
return s + (typeof v === "number" ? v : parseFloat(String(v)) || 0);
|
||||
}, 0);
|
||||
value = group.length > 0 ? sum / group.length : 0;
|
||||
}
|
||||
aggregated.push({ label: key, value });
|
||||
}
|
||||
|
||||
return { items: aggregated, valueKey };
|
||||
});
|
||||
|
||||
const chartData = $derived(processedData());
|
||||
const maxValue = $derived(Math.max(...chartData.items.map(d => d.value), 1));
|
||||
const chartColor = $derived(props.color ?? "var(--chart-1)");
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
{#if props.title}
|
||||
<p class="text-sm font-medium mb-2">{props.title}</p>
|
||||
{/if}
|
||||
|
||||
{#if chartData.items.length === 0}
|
||||
<div class="text-center py-4 text-muted-foreground">No data available</div>
|
||||
{:else}
|
||||
<div class="space-y-2" style="height: {props.height ?? 200}px">
|
||||
{#each chartData.items as item}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-muted-foreground w-16 truncate">{item.label}</span>
|
||||
<div class="flex-1 h-6 bg-muted rounded overflow-hidden">
|
||||
<div
|
||||
class="h-full rounded transition-all"
|
||||
style="width: {(item.value / maxValue) * 100}%; background-color: {chartColor}"
|
||||
></div>
|
||||
</div>
|
||||
<span class="text-xs font-medium w-12 text-right">{item.value.toLocaleString()}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,22 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
label: string;
|
||||
variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | null;
|
||||
size?: "default" | "sm" | "lg" | null;
|
||||
disabled?: boolean | null;
|
||||
}> {}
|
||||
|
||||
let { props, emit }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Button
|
||||
variant={props.variant ?? "default"}
|
||||
size={props.size ?? "default"}
|
||||
disabled={props.disabled ?? false}
|
||||
onclick={() => emit("press")}
|
||||
>
|
||||
{props.label}
|
||||
</Button>
|
||||
@@ -1,57 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import { Info, Lightbulb, AlertTriangle, Star } from "lucide-svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
type?: "info" | "tip" | "warning" | "important" | null;
|
||||
title?: string | null;
|
||||
content: string;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
|
||||
const configs = {
|
||||
info: {
|
||||
border: "border-l-blue-500",
|
||||
bg: "bg-blue-500/5",
|
||||
iconColor: "text-blue-500",
|
||||
},
|
||||
tip: {
|
||||
border: "border-l-emerald-500",
|
||||
bg: "bg-emerald-500/5",
|
||||
iconColor: "text-emerald-500",
|
||||
},
|
||||
warning: {
|
||||
border: "border-l-amber-500",
|
||||
bg: "bg-amber-500/5",
|
||||
iconColor: "text-amber-500",
|
||||
},
|
||||
important: {
|
||||
border: "border-l-purple-500",
|
||||
bg: "bg-purple-500/5",
|
||||
iconColor: "text-purple-500",
|
||||
},
|
||||
};
|
||||
|
||||
const config = $derived(configs[props.type ?? "info"] ?? configs.info);
|
||||
</script>
|
||||
|
||||
<div class="border-l-4 {config.border} {config.bg} rounded-r-lg p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
{#if props.type === "tip"}
|
||||
<Lightbulb class="h-5 w-5 mt-0.5 shrink-0 {config.iconColor}" />
|
||||
{:else if props.type === "warning"}
|
||||
<AlertTriangle class="h-5 w-5 mt-0.5 shrink-0 {config.iconColor}" />
|
||||
{:else if props.type === "important"}
|
||||
<Star class="h-5 w-5 mt-0.5 shrink-0 {config.iconColor}" />
|
||||
{:else}
|
||||
<Info class="h-5 w-5 mt-0.5 shrink-0 {config.iconColor}" />
|
||||
{/if}
|
||||
<div class="flex-1 min-w-0">
|
||||
{#if props.title}
|
||||
<p class="font-semibold text-sm mb-1">{props.title}</p>
|
||||
{/if}
|
||||
<p class="text-sm text-muted-foreground">{props.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,32 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import * as Card from "$lib/components/ui/card";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
title?: string | null;
|
||||
description?: string | null;
|
||||
}> {
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { props, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
{#if props.title || props.description}
|
||||
<Card.Header>
|
||||
{#if props.title}
|
||||
<Card.Title>{props.title}</Card.Title>
|
||||
{/if}
|
||||
{#if props.description}
|
||||
<Card.Description>{props.description}</Card.Description>
|
||||
{/if}
|
||||
</Card.Header>
|
||||
{/if}
|
||||
<Card.Content class="flex flex-col gap-4">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -1,31 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
columns?: "1" | "2" | "3" | "4" | null;
|
||||
gap?: "sm" | "md" | "lg" | null;
|
||||
}> {
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { props, children }: Props = $props();
|
||||
|
||||
const colsClass = $derived(
|
||||
{
|
||||
"1": "grid-cols-1",
|
||||
"2": "grid-cols-1 md:grid-cols-2",
|
||||
"3": "grid-cols-1 md:grid-cols-2 lg:grid-cols-3",
|
||||
"4": "grid-cols-1 md:grid-cols-2 lg:grid-cols-4",
|
||||
}[props.columns ?? "3"] ?? "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
|
||||
);
|
||||
const gapClass = $derived(
|
||||
{ sm: "gap-2", md: "gap-4", lg: "gap-6" }[props.gap ?? "md"] ?? "gap-4"
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="grid {colsClass} {gapClass}">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,22 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
text: string;
|
||||
level?: "h1" | "h2" | "h3" | "h4" | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
|
||||
const level = $derived(props.level ?? "h2");
|
||||
</script>
|
||||
|
||||
{#if level === "h1"}
|
||||
<h1 class="text-3xl font-bold tracking-tight">{props.text}</h1>
|
||||
{:else if level === "h2"}
|
||||
<h2 class="text-2xl font-semibold tracking-tight">{props.text}</h2>
|
||||
{:else if level === "h3"}
|
||||
<h3 class="text-xl font-semibold">{props.text}</h3>
|
||||
{:else}
|
||||
<h4 class="text-lg font-medium">{props.text}</h4>
|
||||
{/if}
|
||||
@@ -1,109 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
title?: string | null;
|
||||
data: Array<Record<string, unknown>>;
|
||||
xKey: string;
|
||||
yKey: string;
|
||||
aggregate?: "sum" | "count" | "avg" | null;
|
||||
color?: string | null;
|
||||
height?: number | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
|
||||
const rawData = $derived(props.data);
|
||||
const rawItems = $derived<Array<Record<string, unknown>>>(
|
||||
Array.isArray(rawData)
|
||||
? rawData
|
||||
: Array.isArray((rawData as Record<string, unknown>)?.data)
|
||||
? ((rawData as Record<string, unknown>).data as Array<Record<string, unknown>>)
|
||||
: []
|
||||
);
|
||||
|
||||
const processedData = $derived(() => {
|
||||
if (rawItems.length === 0) return [];
|
||||
|
||||
const { xKey, yKey, aggregate } = props;
|
||||
|
||||
if (!aggregate) {
|
||||
return rawItems.map((item) => ({
|
||||
label: String(item[xKey] ?? ""),
|
||||
value: typeof item[yKey] === "number" ? item[yKey] : parseFloat(String(item[yKey])) || 0,
|
||||
}));
|
||||
}
|
||||
|
||||
const groups = new Map<string, Array<Record<string, unknown>>>();
|
||||
for (const item of rawItems) {
|
||||
const groupKey = String(item[xKey] ?? "unknown");
|
||||
const group = groups.get(groupKey) ?? [];
|
||||
group.push(item);
|
||||
groups.set(groupKey, group);
|
||||
}
|
||||
|
||||
const aggregated: Array<{ label: string; value: number }> = [];
|
||||
for (const [key, group] of Array.from(groups.entries()).sort((a, b) => a[0].localeCompare(b[0]))) {
|
||||
let value: number;
|
||||
if (aggregate === "count") {
|
||||
value = group.length;
|
||||
} else if (aggregate === "sum") {
|
||||
value = group.reduce((sum, item) => {
|
||||
const v = item[yKey];
|
||||
return sum + (typeof v === "number" ? v : parseFloat(String(v)) || 0);
|
||||
}, 0);
|
||||
} else {
|
||||
const sum = group.reduce((s, item) => {
|
||||
const v = item[yKey];
|
||||
return s + (typeof v === "number" ? v : parseFloat(String(v)) || 0);
|
||||
}, 0);
|
||||
value = group.length > 0 ? sum / group.length : 0;
|
||||
}
|
||||
aggregated.push({ label: key, value });
|
||||
}
|
||||
return aggregated;
|
||||
});
|
||||
|
||||
const chartData = $derived(processedData());
|
||||
const maxValue = $derived(Math.max(...chartData.map(d => d.value), 1));
|
||||
const minValue = $derived(Math.min(...chartData.map(d => d.value), 0));
|
||||
const chartColor = $derived(props.color ?? "var(--chart-1)");
|
||||
const height = $derived(props.height ?? 200);
|
||||
|
||||
const points = $derived(() => {
|
||||
if (chartData.length === 0) return "";
|
||||
const range = maxValue - minValue || 1;
|
||||
return chartData.map((d, i) => {
|
||||
const x = (i / (chartData.length - 1 || 1)) * 100;
|
||||
const y = 100 - ((d.value - minValue) / range) * 100;
|
||||
return `${x},${y}`;
|
||||
}).join(" ");
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
{#if props.title}
|
||||
<p class="text-sm font-medium mb-2">{props.title}</p>
|
||||
{/if}
|
||||
|
||||
{#if chartData.length === 0}
|
||||
<div class="text-center py-4 text-muted-foreground">No data available</div>
|
||||
{:else}
|
||||
<div class="relative" style="height: {height}px">
|
||||
<svg viewBox="0 0 100 100" preserveAspectRatio="none" class="w-full h-full">
|
||||
<polyline
|
||||
points={points()}
|
||||
fill="none"
|
||||
stroke={chartColor}
|
||||
stroke-width="2"
|
||||
vector-effect="non-scaling-stroke"
|
||||
/>
|
||||
</svg>
|
||||
<div class="flex justify-between text-xs text-muted-foreground mt-1">
|
||||
{#each chartData.filter((_, i) => i === 0 || i === chartData.length - 1 || i === Math.floor(chartData.length / 2)) as item}
|
||||
<span>{item.label}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
text: string;
|
||||
href: string;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={props.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary underline underline-offset-4 hover:text-primary/80"
|
||||
>
|
||||
{props.text}
|
||||
</a>
|
||||
@@ -1,40 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import { TrendingUp, TrendingDown, Minus } from "lucide-svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
label: string;
|
||||
value: string;
|
||||
detail?: string | null;
|
||||
trend?: "up" | "down" | "neutral" | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
|
||||
const trendColor = $derived(
|
||||
props.trend === "up"
|
||||
? "text-green-500"
|
||||
: props.trend === "down"
|
||||
? "text-red-500"
|
||||
: "text-muted-foreground"
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-sm text-muted-foreground">{props.label}</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-2xl font-bold">{props.value}</span>
|
||||
{#if props.trend}
|
||||
{#if props.trend === "up"}
|
||||
<TrendingUp class="h-4 w-4 {trendColor}" />
|
||||
{:else if props.trend === "down"}
|
||||
<TrendingDown class="h-4 w-4 {trendColor}" />
|
||||
{:else}
|
||||
<Minus class="h-4 w-4 {trendColor}" />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{#if props.detail}
|
||||
<p class="text-xs text-muted-foreground">{props.detail}</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,106 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
title?: string | null;
|
||||
data: Array<Record<string, unknown>>;
|
||||
nameKey: string;
|
||||
valueKey: string;
|
||||
height?: number | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
|
||||
const PIE_COLORS = [
|
||||
"var(--chart-1)",
|
||||
"var(--chart-2)",
|
||||
"var(--chart-3)",
|
||||
"var(--chart-4)",
|
||||
"var(--chart-5)",
|
||||
];
|
||||
|
||||
const rawData = $derived(props.data);
|
||||
const items = $derived<Array<Record<string, unknown>>>(
|
||||
Array.isArray(rawData)
|
||||
? rawData
|
||||
: Array.isArray((rawData as Record<string, unknown>)?.data)
|
||||
? ((rawData as Record<string, unknown>).data as Array<Record<string, unknown>>)
|
||||
: []
|
||||
);
|
||||
|
||||
const chartData = $derived(
|
||||
items.map((item, i) => ({
|
||||
name: String(item[props.nameKey] ?? `Segment ${i + 1}`),
|
||||
value: typeof item[props.valueKey] === "number"
|
||||
? item[props.valueKey] as number
|
||||
: parseFloat(String(item[props.valueKey])) || 0,
|
||||
color: PIE_COLORS[i % PIE_COLORS.length],
|
||||
}))
|
||||
);
|
||||
|
||||
const total = $derived(chartData.reduce((sum, d) => sum + d.value, 0));
|
||||
|
||||
const segments = $derived(() => {
|
||||
let startAngle = 0;
|
||||
return chartData.map((d) => {
|
||||
const angle = (d.value / (total || 1)) * 360;
|
||||
const segment = {
|
||||
...d,
|
||||
startAngle,
|
||||
endAngle: startAngle + angle,
|
||||
percentage: Math.round((d.value / (total || 1)) * 100),
|
||||
};
|
||||
startAngle += angle;
|
||||
return segment;
|
||||
});
|
||||
});
|
||||
|
||||
function polarToCartesian(cx: number, cy: number, r: number, angle: number) {
|
||||
const rad = ((angle - 90) * Math.PI) / 180;
|
||||
return {
|
||||
x: cx + r * Math.cos(rad),
|
||||
y: cy + r * Math.sin(rad),
|
||||
};
|
||||
}
|
||||
|
||||
function describeArc(cx: number, cy: number, r: number, startAngle: number, endAngle: number) {
|
||||
const start = polarToCartesian(cx, cy, r, endAngle);
|
||||
const end = polarToCartesian(cx, cy, r, startAngle);
|
||||
const largeArc = endAngle - startAngle <= 180 ? 0 : 1;
|
||||
return `M ${start.x} ${start.y} A ${r} ${r} 0 ${largeArc} 0 ${end.x} ${end.y}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
{#if props.title}
|
||||
<p class="text-sm font-medium mb-2">{props.title}</p>
|
||||
{/if}
|
||||
|
||||
{#if items.length === 0}
|
||||
<div class="text-center py-4 text-muted-foreground">No data available</div>
|
||||
{:else}
|
||||
<div class="flex items-center gap-4" style="height: {props.height ?? 200}px">
|
||||
<svg viewBox="0 0 100 100" class="h-full aspect-square">
|
||||
{#each segments() as seg}
|
||||
{#if seg.endAngle - seg.startAngle >= 1}
|
||||
<path
|
||||
d={describeArc(50, 50, 35, seg.startAngle, seg.endAngle)}
|
||||
fill="none"
|
||||
stroke={seg.color}
|
||||
stroke-width="15"
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</svg>
|
||||
<div class="flex flex-col gap-1 text-sm">
|
||||
{#each segments() as seg}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-full" style="background-color: {seg.color}"></span>
|
||||
<span class="text-muted-foreground">{seg.name}</span>
|
||||
<span class="font-medium">{seg.percentage}%</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import { Progress } from "$lib/components/ui/progress";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
value: number;
|
||||
max?: number | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Progress value={props.value} max={props.max ?? 100} />
|
||||
@@ -1,45 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import { getBoundProp } from "@json-render/svelte";
|
||||
import * as RadioGroup from "$lib/components/ui/radio-group";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
label?: string | null;
|
||||
value?: string | null;
|
||||
options: Array<{ value: string; label: string }>;
|
||||
}> {}
|
||||
|
||||
let { props, bindings }: Props = $props();
|
||||
|
||||
function valueBinding() {
|
||||
return getBoundProp<string>(
|
||||
() => (props.value ?? undefined) as string | undefined,
|
||||
() => bindings?.value,
|
||||
);
|
||||
}
|
||||
|
||||
let value = $derived(
|
||||
valueBinding().current ?? ""
|
||||
);
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
valueBinding().current = newValue;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if props.label}
|
||||
<Label class="text-sm font-medium">{props.label}</Label>
|
||||
{/if}
|
||||
<RadioGroup.Root value={value} onValueChange={handleChange}>
|
||||
{#each props.options ?? [] as opt}
|
||||
<div class="flex items-center gap-2">
|
||||
<RadioGroup.Item value={opt.value} id="rg-{opt.value}" />
|
||||
<Label for="rg-{opt.value}" class="font-normal cursor-pointer">
|
||||
{opt.label}
|
||||
</Label>
|
||||
</div>
|
||||
{/each}
|
||||
</RadioGroup.Root>
|
||||
</div>
|
||||
@@ -1,56 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import { getBoundProp } from "@json-render/svelte";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
label?: string | null;
|
||||
value?: string | null;
|
||||
placeholder?: string | null;
|
||||
options: Array<{ value: string; label: string }>;
|
||||
}> {}
|
||||
|
||||
let { props, bindings }: Props = $props();
|
||||
|
||||
function valueBinding() {
|
||||
return getBoundProp<string>(
|
||||
() => (props.value ?? undefined) as string | undefined,
|
||||
() => bindings?.value,
|
||||
);
|
||||
}
|
||||
|
||||
let value = $derived(
|
||||
valueBinding().current ?? ""
|
||||
);
|
||||
|
||||
const selectedOption = $derived(
|
||||
props.options?.find(o => o.value === value)
|
||||
);
|
||||
|
||||
function handleChange(newValue: string | undefined) {
|
||||
if (newValue) {
|
||||
valueBinding().current = newValue;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if props.label}
|
||||
<Label class="text-sm font-medium">{props.label}</Label>
|
||||
{/if}
|
||||
<Select.Root type="single" value={value} onValueChange={handleChange}>
|
||||
<Select.Trigger>
|
||||
{#if selectedOption}
|
||||
{selectedOption.label}
|
||||
{:else}
|
||||
<span class="text-muted-foreground">{props.placeholder ?? "Select..."}</span>
|
||||
{/if}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each props.options ?? [] as opt}
|
||||
<Select.Item value={opt.value}>{opt.label}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
@@ -1,10 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
|
||||
interface Props extends BaseComponentProps<Record<string, never>> {}
|
||||
|
||||
let {}: Props = $props();
|
||||
</script>
|
||||
|
||||
<Separator />
|
||||
@@ -1,14 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
width?: string | null;
|
||||
height?: string | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="animate-pulse rounded-md bg-muted {props.width ?? 'w-full'} {props.height ?? 'h-4'}"
|
||||
></div>
|
||||
@@ -1,28 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
direction?: "horizontal" | "vertical" | null;
|
||||
gap?: "sm" | "md" | "lg" | null;
|
||||
wrap?: boolean | null;
|
||||
}> {
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { props, children }: Props = $props();
|
||||
|
||||
const gapClass = $derived(
|
||||
{ sm: "gap-2", md: "gap-4", lg: "gap-6" }[props.gap ?? "md"] ?? "gap-4"
|
||||
);
|
||||
const dirClass = $derived(
|
||||
props.direction === "horizontal" ? "flex-row" : "flex-col"
|
||||
);
|
||||
const wrapClass = $derived(props.wrap ? "flex-wrap" : "");
|
||||
</script>
|
||||
|
||||
<div class="flex {dirClass} {wrapClass} {gapClass}">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import * as Tabs from "$lib/components/ui/tabs";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
value: string;
|
||||
}> {
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { props, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Tabs.Content value={props.value}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</Tabs.Content>
|
||||
@@ -1,91 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import * as Table from "$lib/components/ui/table";
|
||||
import { ArrowUpDown, ArrowUp, ArrowDown } from "lucide-svelte";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
data: Array<Record<string, unknown>>;
|
||||
columns: Array<{ key: string; label: string }>;
|
||||
emptyMessage?: string | null;
|
||||
}> {}
|
||||
|
||||
let { props }: Props = $props();
|
||||
|
||||
let sortKey = $state<string | null>(null);
|
||||
let sortDir = $state<"asc" | "desc">("asc");
|
||||
|
||||
const rawData = $derived(props.data);
|
||||
const items = $derived<Array<Record<string, unknown>>>(
|
||||
Array.isArray(rawData)
|
||||
? rawData
|
||||
: Array.isArray((rawData as Record<string, unknown>)?.data)
|
||||
? ((rawData as Record<string, unknown>).data as Array<Record<string, unknown>>)
|
||||
: []
|
||||
);
|
||||
|
||||
const sorted = $derived(
|
||||
sortKey
|
||||
? [...items].sort((a, b) => {
|
||||
const av = a[sortKey!];
|
||||
const bv = b[sortKey!];
|
||||
if (typeof av === "number" && typeof bv === "number") {
|
||||
return sortDir === "asc" ? av - bv : bv - av;
|
||||
}
|
||||
const as = String(av ?? "");
|
||||
const bs = String(bv ?? "");
|
||||
return sortDir === "asc" ? as.localeCompare(bs) : bs.localeCompare(as);
|
||||
})
|
||||
: items
|
||||
);
|
||||
|
||||
function handleSort(key: string) {
|
||||
if (sortKey === key) {
|
||||
sortDir = sortDir === "asc" ? "desc" : "asc";
|
||||
} else {
|
||||
sortKey = key;
|
||||
sortDir = "asc";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if items.length === 0}
|
||||
<div class="text-center py-4 text-muted-foreground">
|
||||
{props.emptyMessage ?? "No data"}
|
||||
</div>
|
||||
{:else}
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
{#each props.columns as col}
|
||||
<Table.Head>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center gap-1 hover:text-foreground transition-colors"
|
||||
onclick={() => handleSort(col.key)}
|
||||
>
|
||||
{col.label}
|
||||
{#if sortKey === col.key}
|
||||
{#if sortDir === "asc"}
|
||||
<ArrowUp class="h-3 w-3 text-muted-foreground" />
|
||||
{:else}
|
||||
<ArrowDown class="h-3 w-3 text-muted-foreground" />
|
||||
{/if}
|
||||
{:else}
|
||||
<ArrowUpDown class="h-3 w-3 text-muted-foreground" />
|
||||
{/if}
|
||||
</button>
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each sorted as item, i}
|
||||
<Table.Row>
|
||||
{#each props.columns as col}
|
||||
<Table.Cell>{String(item[col.key] ?? "")}</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
{/if}
|
||||
@@ -1,29 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import type { BaseComponentProps } from "@json-render/svelte";
|
||||
import * as Tabs from "$lib/components/ui/tabs";
|
||||
|
||||
interface Props extends BaseComponentProps<{
|
||||
defaultValue?: string | null;
|
||||
tabs: Array<{ value: string; label: string }>;
|
||||
}> {
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { props, children }: Props = $props();
|
||||
|
||||
const defaultVal = $derived(
|
||||
props.defaultValue ?? (props.tabs ?? [])[0]?.value ?? ""
|
||||
);
|
||||
</script>
|
||||
|
||||
<Tabs.Root value={defaultVal}>
|
||||
<Tabs.List>
|
||||
{#each props.tabs ?? [] as tab}
|
||||
<Tabs.Trigger value={tab.value}>{tab.label}</Tabs.Trigger>
|
||||
{/each}
|
||||
</Tabs.List>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</Tabs.Root>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user