Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
e64086b7cf | |||
08946059ad |
@ -78,12 +78,25 @@ impl App {
|
||||
.await;
|
||||
};
|
||||
|
||||
#[cfg(all(unix, debug_assertions))]
|
||||
let quit = async {
|
||||
signal::unix::signal(signal::unix::SignalKind::quit())
|
||||
.expect("Failed to install SIGQUIT handler")
|
||||
.recv()
|
||||
.await;
|
||||
println!("Received SIGQUIT");
|
||||
};
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let terminate = std::future::pending::<()>();
|
||||
|
||||
#[cfg(all(not(unix), debug_assertions))]
|
||||
let quit = std::future::pending::<()>();
|
||||
|
||||
tokio::select! {
|
||||
() = ctrl_c => {},
|
||||
() = terminate => {},
|
||||
() = quit => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
apps/webui/components.json
Normal file
20
apps/webui/components.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/app.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/styles/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
@ -4,6 +4,20 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script>
|
||||
(function() {
|
||||
try {
|
||||
const theme = localStorage.getItem('prefers-color-scheme');
|
||||
document.documentElement.classList.remove('dark', 'light');
|
||||
if (theme === 'dark' || theme === 'light') {
|
||||
document.documentElement.classList.add(theme);
|
||||
} else {
|
||||
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
document.documentElement.classList.add(systemTheme);
|
||||
}
|
||||
} catch(e) {}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -10,49 +10,79 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@abraham/reflection": "^0.12.0",
|
||||
"@ark-ui/solid": "^4.10.2",
|
||||
"@codemirror/language": "6.0.0",
|
||||
"@corvu/drawer": "^0.2.3",
|
||||
"@corvu/otp-field": "^0.1.4",
|
||||
"@corvu/resizable": "^0.2.4",
|
||||
"@graphiql/toolkit": "^0.11.1",
|
||||
"@kobalte/core": "^0.13.9",
|
||||
"@kobalte/tailwindcss": "^0.9.0",
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@outposts/injection-js": "^2.5.1",
|
||||
"@solid-primitives/graphql": "^2.2.0",
|
||||
"@solid-primitives/refs": "^1.1.0",
|
||||
"@tailwindcss/postcss": "^4.0.9",
|
||||
"@radix-ui/react-accordion": "^1.2.7",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.10",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.4",
|
||||
"@radix-ui/react-avatar": "^1.1.6",
|
||||
"@radix-ui/react-checkbox": "^1.2.2",
|
||||
"@radix-ui/react-collapsible": "^1.1.7",
|
||||
"@radix-ui/react-context-menu": "^2.2.11",
|
||||
"@radix-ui/react-dialog": "^1.1.10",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.11",
|
||||
"@radix-ui/react-hover-card": "^1.1.10",
|
||||
"@radix-ui/react-label": "^2.1.4",
|
||||
"@radix-ui/react-menubar": "^1.1.11",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.9",
|
||||
"@radix-ui/react-popover": "^1.1.10",
|
||||
"@radix-ui/react-progress": "^1.1.4",
|
||||
"@radix-ui/react-radio-group": "^1.3.3",
|
||||
"@radix-ui/react-scroll-area": "^1.2.5",
|
||||
"@radix-ui/react-select": "^2.2.2",
|
||||
"@radix-ui/react-separator": "^1.1.4",
|
||||
"@radix-ui/react-slider": "^1.3.2",
|
||||
"@radix-ui/react-slot": "^1.2.0",
|
||||
"@radix-ui/react-switch": "^1.2.2",
|
||||
"@radix-ui/react-tabs": "^1.1.8",
|
||||
"@radix-ui/react-toggle": "^1.1.6",
|
||||
"@radix-ui/react-toggle-group": "^1.1.7",
|
||||
"@radix-ui/react-tooltip": "^1.2.3",
|
||||
"@rsbuild/plugin-react": "^1.2.0",
|
||||
"@tanstack/react-router": "^1.112.13",
|
||||
"@tanstack/router-devtools": "^1.112.13",
|
||||
"@tanstack/solid-router": "^1.112.12",
|
||||
"arktype": "^2.1.6",
|
||||
"chart.js": "^4.4.8",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk-solid": "^1.1.2",
|
||||
"embla-carousel-solid": "^8.5.2",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"graphiql": "^3.8.3",
|
||||
"lucide-solid": "^0.477.0",
|
||||
"input-otp": "^1.4.2",
|
||||
"jotai": "^2.12.3",
|
||||
"jotai-signal": "^0.9.0",
|
||||
"lucide-react": "^0.503.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"oidc-client-rx": "0.1.0-alpha.9",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react": "^19.1.0",
|
||||
"react-day-picker": "8.10.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hook-form": "^7.56.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"recharts": "^2.15.3",
|
||||
"rxjs": "^7.8.2",
|
||||
"solid-js": "^1.9.5",
|
||||
"solid-sonner": "^0.2.8",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
"sonner": "^2.0.3",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwindcss": "^4.0.6",
|
||||
"tw-animate-css": "^1.2.7",
|
||||
"type-fest": "^4.40.0",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "^3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rsbuild/core": "^1.2.15",
|
||||
"@rsbuild/plugin-babel": "^1.0.4",
|
||||
"@rsbuild/plugin-solid": "^1.0.5",
|
||||
"@tailwindcss/postcss": "^4.0.9",
|
||||
"@tanstack/react-router": "^1.112.0",
|
||||
"@tanstack/router-devtools": "^1.112.6",
|
||||
"@tanstack/router-plugin": "^1.112.13",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"chalk": "^5.4.1",
|
||||
"commander": "^13.1.0",
|
||||
"postcss": "^8.5.3"
|
||||
|
@ -1,5 +1,5 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'tailwindcss': {},
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
};
|
||||
};
|
@ -1,6 +1,5 @@
|
||||
import { defineConfig } from '@rsbuild/core';
|
||||
import { pluginBabel } from '@rsbuild/plugin-babel';
|
||||
import { pluginSolid } from '@rsbuild/plugin-solid';
|
||||
import { pluginReact } from '@rsbuild/plugin-react';
|
||||
import { TanStackRouterRspack } from '@tanstack/router-plugin/rspack';
|
||||
|
||||
export default defineConfig({
|
||||
@ -8,16 +7,11 @@ export default defineConfig({
|
||||
title: 'Konobangu',
|
||||
favicon: './public/assets/favicon.ico',
|
||||
},
|
||||
plugins: [
|
||||
pluginBabel({
|
||||
include: /\.(?:jsx|tsx)$/,
|
||||
}),
|
||||
pluginSolid(),
|
||||
],
|
||||
plugins: [pluginReact()],
|
||||
tools: {
|
||||
rspack: {
|
||||
plugins: [
|
||||
TanStackRouterRspack({ target: 'solid', autoCodeSplitting: true }),
|
||||
TanStackRouterRspack({ target: 'react', autoCodeSplitting: true }),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -1,149 +1,146 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
: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);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
.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);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
@theme inline {
|
||||
--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-destructive-foreground: var(--destructive-foreground);
|
||||
--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);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--info: 204 94% 94%;
|
||||
--info-foreground: 199 89% 48%;
|
||||
|
||||
--success: 149 80% 90%;
|
||||
--success-foreground: 160 84% 39%;
|
||||
|
||||
--warning: 48 96% 89%;
|
||||
--warning-foreground: 25 95% 53%;
|
||||
|
||||
--error: 0 93% 94%;
|
||||
--error-foreground: 0 84% 60%;
|
||||
|
||||
--ring: 240 5.9% 10%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
@keyframes accordion-down {
|
||||
from {
|
||||
height: 0;
|
||||
}
|
||||
to {
|
||||
height: var(--radix-accordion-content-height);
|
||||
}
|
||||
}
|
||||
|
||||
.dark,
|
||||
[data-kb-theme="dark"] {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--info: 204 94% 94%;
|
||||
--info-foreground: 199 89% 48%;
|
||||
|
||||
--success: 149 80% 90%;
|
||||
--success-foreground: 160 84% 39%;
|
||||
|
||||
--warning: 48 96% 89%;
|
||||
--warning-foreground: 25 95% 53%;
|
||||
|
||||
--error: 0 93% 94%;
|
||||
--error-foreground: 0 84% 60%;
|
||||
|
||||
--ring: 240 4.9% 83.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-primary: 224.3 76.3% 48%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 240 3.7% 15.9%;
|
||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
@keyframes accordion-up {
|
||||
from {
|
||||
height: var(--radix-accordion-content-height);
|
||||
}
|
||||
to {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* custom start */
|
||||
::-webkit-scrollbar {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 9999px;
|
||||
border: 4px solid transparent;
|
||||
background-clip: content-box;
|
||||
@apply bg-accent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
display: none;
|
||||
}
|
||||
/* custom end */
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings:
|
||||
"rlig" 1,
|
||||
"calt" 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.container {
|
||||
@apply px-4;
|
||||
button:not(:disabled),
|
||||
[role="button"]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,14 @@
|
||||
import { LogLevel, type OpenIdConfiguration } from 'oidc-client-rx';
|
||||
import type { ValueOf } from 'type-fest';
|
||||
|
||||
export const isBasicAuth = process.env.AUTH_TYPE === 'basic';
|
||||
export const isOidcAuth = process.env.AUTH_TYPE === 'oidc';
|
||||
export const AuthMethodEnum = {
|
||||
BASIC: 'basic',
|
||||
OIDC: 'oidc',
|
||||
} as const;
|
||||
|
||||
export type AuthMethodType = ValueOf<typeof AuthMethodEnum>;
|
||||
|
||||
export const AppAuthMethod = process.env.AUTH_TYPE as AuthMethodType;
|
||||
|
||||
export function buildOidcConfig(): OpenIdConfiguration {
|
||||
const origin = window.location.origin;
|
||||
|
@ -1,4 +1,85 @@
|
||||
import { createSignal } from 'solid-js';
|
||||
import { isBasicAuth } from './config';
|
||||
import { UnreachableError } from '@/errors/common';
|
||||
import type { Injector, Provider } from '@outposts/injection-js';
|
||||
import type { AnyRouter } from '@tanstack/react-router';
|
||||
import { atomSignal } from 'jotai-signal';
|
||||
import type { Atom } from 'jotai/vanilla';
|
||||
import {
|
||||
CHECK_AUTH_RESULT_EVENT,
|
||||
type CheckAuthResultEventType,
|
||||
OidcSecurityService,
|
||||
provideAuth as provideOidcAuth,
|
||||
withCheckAuthResultEvent,
|
||||
withDefaultFeatures,
|
||||
} from 'oidc-client-rx';
|
||||
import { withTanstackRouter } from 'oidc-client-rx/adapters/@tanstack/react-router';
|
||||
import { NEVER, type Observable, map, of } from 'rxjs';
|
||||
import { AppAuthMethod, AuthMethodEnum, buildOidcConfig } from './config';
|
||||
|
||||
export const [isAuthenticated, setIsAuthenticated] = createSignal(isBasicAuth);
|
||||
export function provideAuth(router: AnyRouter): Provider[] {
|
||||
if (AppAuthMethod === AuthMethodEnum.OIDC) {
|
||||
return provideOidcAuth(
|
||||
{
|
||||
config: buildOidcConfig(),
|
||||
},
|
||||
withDefaultFeatures({
|
||||
router: { enabled: false },
|
||||
securityStorage: { type: 'local-storage' },
|
||||
}),
|
||||
withTanstackRouter(router),
|
||||
withCheckAuthResultEvent()
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function setupAuthContext(injector: Injector) {
|
||||
if (AppAuthMethod === AuthMethodEnum.OIDC) {
|
||||
const oidcSecurityService = injector.get(OidcSecurityService);
|
||||
oidcSecurityService.checkAuth().subscribe();
|
||||
}
|
||||
}
|
||||
export interface OidcAuthContext {
|
||||
type: typeof AuthMethodEnum.OIDC;
|
||||
oidcSecurityService: OidcSecurityService;
|
||||
isAuthenticated$: Observable<boolean>;
|
||||
userData$: Observable<any>;
|
||||
checkAuthResultEvent$: Observable<CheckAuthResultEventType>;
|
||||
}
|
||||
|
||||
export interface BasicAuthContext {
|
||||
type: typeof AuthMethodEnum.BASIC;
|
||||
isAuthenticated$: Observable<true>;
|
||||
userData$: Observable<{}>;
|
||||
checkAuthResultEvent$: Observable<CheckAuthResultEventType>;
|
||||
}
|
||||
|
||||
export type AuthContext = OidcAuthContext | BasicAuthContext;
|
||||
|
||||
const BASIC_AUTH_IS_AUTHENTICATED$ = of(true) as Observable<true>;
|
||||
|
||||
const BASIC_AUTH_USER_DATA$ = of({});
|
||||
|
||||
export function authContextFromInjector(injector: Injector): AuthContext {
|
||||
if (AppAuthMethod === AuthMethodEnum.OIDC) {
|
||||
const oidcSecurityService = injector.get(OidcSecurityService)!;
|
||||
|
||||
return {
|
||||
type: AuthMethodEnum.OIDC,
|
||||
oidcSecurityService: injector.get(OidcSecurityService),
|
||||
isAuthenticated$: oidcSecurityService.isAuthenticated$.pipe(
|
||||
map((s) => s.isAuthenticated)
|
||||
),
|
||||
userData$: oidcSecurityService.userData$.pipe(map((s) => s.userData)),
|
||||
checkAuthResultEvent$: injector.get(CHECK_AUTH_RESULT_EVENT),
|
||||
};
|
||||
}
|
||||
if (AppAuthMethod === AuthMethodEnum.BASIC) {
|
||||
return {
|
||||
type: AuthMethodEnum.BASIC,
|
||||
isAuthenticated$: BASIC_AUTH_IS_AUTHENTICATED$,
|
||||
userData$: BASIC_AUTH_USER_DATA$,
|
||||
checkAuthResultEvent$: NEVER,
|
||||
};
|
||||
}
|
||||
throw new UnreachableError('Invalid auth method');
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
import type { RouterContext } from '@/traits/router';
|
||||
import { runInInjectionContext } from '@outposts/injection-js';
|
||||
import { autoLoginPartialRoutesGuard } from 'oidc-client-rx';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import type { RouterContext } from '~/traits/router';
|
||||
import { authContextFromInjector } from './context';
|
||||
|
||||
export const beforeLoadGuard = async ({
|
||||
context,
|
||||
}: { context: RouterContext }) => {
|
||||
if (!context.isAuthenticated()) {
|
||||
const { isAuthenticated$ } = authContextFromInjector(context.injector);
|
||||
if (!(await firstValueFrom(isAuthenticated$))) {
|
||||
const guard$ = runInInjectionContext(context.injector, () =>
|
||||
autoLoginPartialRoutesGuard()
|
||||
);
|
||||
|
@ -1,55 +1,34 @@
|
||||
import { CHECK_AUTH_RESULT_EVENT } from 'oidc-client-rx';
|
||||
import {
|
||||
InjectorContextVoidInjector,
|
||||
useOidcClient,
|
||||
} from 'oidc-client-rx/adapters/solid-js';
|
||||
import { NEVER, map, of } from 'rxjs';
|
||||
import { from } from 'solid-js';
|
||||
import { isBasicAuth, isOidcAuth } from './config';
|
||||
|
||||
const BASIC_AUTH_IS_AUTHENTICATED$ = of({
|
||||
isAuthenticated: true,
|
||||
allConfigsAuthenticated: [],
|
||||
});
|
||||
|
||||
const BASIC_AUTH_USER_DATA$ = of({
|
||||
userData: {},
|
||||
allUserData: [],
|
||||
});
|
||||
|
||||
const useOidcClientExt = isOidcAuth
|
||||
? useOidcClient
|
||||
: () => ({
|
||||
oidcSecurityService: undefined,
|
||||
injector: InjectorContextVoidInjector,
|
||||
});
|
||||
import { atomWithObservable } from 'jotai/utils';
|
||||
import { useInjector } from 'oidc-client-rx/adapters/react';
|
||||
import { useMemo } from 'react';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { authContextFromInjector } from './context';
|
||||
|
||||
export function useAuth() {
|
||||
const { oidcSecurityService, injector } = useOidcClientExt();
|
||||
const injector = useInjector();
|
||||
|
||||
const isAuthenticated$ = (
|
||||
oidcSecurityService?.isAuthenticated$ ?? BASIC_AUTH_IS_AUTHENTICATED$
|
||||
).pipe(map((s) => s.isAuthenticated));
|
||||
const authContext = useMemo(
|
||||
() => authContextFromInjector(injector),
|
||||
[injector]
|
||||
);
|
||||
|
||||
const userData$ = (
|
||||
oidcSecurityService?.userData$ ?? BASIC_AUTH_USER_DATA$
|
||||
).pipe(map((s) => s.userData));
|
||||
const isAuthenticated = useMemo(
|
||||
() =>
|
||||
atomWithObservable(
|
||||
() => authContext.isAuthenticated$ as Observable<boolean>
|
||||
),
|
||||
[authContext.isAuthenticated$]
|
||||
);
|
||||
|
||||
const isAuthenticated = from(isAuthenticated$);
|
||||
|
||||
const userData = from(userData$);
|
||||
|
||||
const checkAuthResultEvent$ = isBasicAuth
|
||||
? NEVER
|
||||
: injector.get(CHECK_AUTH_RESULT_EVENT);
|
||||
const userData = useMemo(
|
||||
() => atomWithObservable(() => authContext.userData$ as Observable<any>),
|
||||
[authContext]
|
||||
);
|
||||
|
||||
return {
|
||||
oidcSecurityService,
|
||||
isAuthenticated$,
|
||||
isAuthenticated,
|
||||
userData$,
|
||||
userData,
|
||||
injector,
|
||||
checkAuthResultEvent$,
|
||||
isAuthenticated,
|
||||
authContext,
|
||||
};
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import {Image} from '@kobalte/core/image';
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from '~/components/ui/sidebar';
|
||||
} from '@/components/ui/sidebar';
|
||||
|
||||
import { Image } from '@/components/ui/image';
|
||||
|
||||
export function AppIcon() {
|
||||
return (
|
||||
@ -11,21 +12,20 @@ export function AppIcon() {
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<div
|
||||
class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
||||
<Image fallbackDelay={1000}>
|
||||
<Image.Img
|
||||
<div className="flex size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
||||
<div className="relative size-8">
|
||||
<Image
|
||||
src="/assets/favicon.png"
|
||||
class="size-8 object-cover"
|
||||
alt="App Logo"
|
||||
className="object-cover"
|
||||
/>
|
||||
<Image.Fallback>KO</Image.Fallback>
|
||||
</Image>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid flex-1 gap-1 py-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-semibold">Konobangu</span>
|
||||
<span class="mt-1 truncate">@dumtruck</span>
|
||||
<div className="grid flex-1 gap-1 py-1 text-left text-xs leading-tight">
|
||||
<span className="truncate font-semibold">Konobangu</span>
|
||||
<span className="mt-1 truncate">@dumtruck</span>
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
@ -1,54 +1,53 @@
|
||||
import { useMatches } from '@tanstack/solid-router';
|
||||
import {
|
||||
type ComponentProps,
|
||||
type FlowProps,
|
||||
For,
|
||||
Show,
|
||||
createMemo,
|
||||
splitProps,
|
||||
} from 'solid-js';
|
||||
import { Dynamic } from 'solid-js/web';
|
||||
import { AppSidebar } from '~/components/layout/app-sidebar';
|
||||
import { AppSidebar } from '@/components/layout/app-sidebar';
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '~/components/ui/breadcrumb';
|
||||
import { Separator } from '~/components/ui/separator';
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from '~/components/ui/sidebar';
|
||||
import type { RouteStateDataOption } from '~/traits/router';
|
||||
import type { RouteBreadcrumbItem } from '~/traits/router';
|
||||
import { cn } from '~/utils/styles';
|
||||
} from '@/components/ui/sidebar';
|
||||
import { cn } from '@/styles/utils';
|
||||
import type { RouteStateDataOption } from '@/traits/router';
|
||||
import type { RouteBreadcrumbItem } from '@/traits/router';
|
||||
import { useMatches } from '@tanstack/react-router';
|
||||
import {
|
||||
type DetailedHTMLProps,
|
||||
Fragment,
|
||||
type HTMLAttributes,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { ProLink } from '../ui/pro-link';
|
||||
|
||||
export type AppAsideBreadcrumbItem = RouteBreadcrumbItem;
|
||||
export interface AppAsideProps extends FlowProps<ComponentProps<'div'>> {
|
||||
|
||||
export interface AppAsideProps
|
||||
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
||||
breadcrumb?: AppAsideBreadcrumbItem[];
|
||||
extractBreadcrumbFromRoutes?: boolean;
|
||||
}
|
||||
|
||||
export function AppAside(props: AppAsideProps) {
|
||||
const [local, other] = splitProps(props, [
|
||||
'children',
|
||||
'class',
|
||||
'breadcrumb',
|
||||
'extractBreadcrumbFromRoutes',
|
||||
]);
|
||||
|
||||
export function AppAside({
|
||||
children,
|
||||
className,
|
||||
breadcrumb: propBreadcrumb,
|
||||
extractBreadcrumbFromRoutes,
|
||||
...other
|
||||
}: AppAsideProps) {
|
||||
const matches = useMatches();
|
||||
|
||||
const breadcrumb = createMemo(() => {
|
||||
if (local.breadcrumb) {
|
||||
return local.breadcrumb;
|
||||
const breadcrumb = useMemo(() => {
|
||||
if (propBreadcrumb) {
|
||||
return propBreadcrumb;
|
||||
}
|
||||
if (local.extractBreadcrumbFromRoutes) {
|
||||
return matches()
|
||||
if (extractBreadcrumbFromRoutes) {
|
||||
return matches
|
||||
.map((m, i, arr) => {
|
||||
const staticData = m.staticData as RouteStateDataOption;
|
||||
if (staticData.breadcrumb) {
|
||||
@ -67,61 +66,69 @@ export function AppAside(props: AppAsideProps) {
|
||||
.filter((b): b is AppAsideBreadcrumbItem => !!b);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}, [matches, propBreadcrumb, extractBreadcrumbFromRoutes]);
|
||||
|
||||
const breadcrumbLength = breadcrumb().length;
|
||||
const breadcrumbLength = breadcrumb.length;
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppSidebar />
|
||||
<SidebarInset>
|
||||
<header class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
||||
<div class="flex items-center gap-2 px-4">
|
||||
<SidebarTrigger class="-ml-1" />
|
||||
<Show when={breadcrumbLength}>
|
||||
<Separator orientation="vertical" class="mr-2 h-4" />
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<For each={breadcrumb()}>
|
||||
{(item, index) => {
|
||||
const iconEl = (
|
||||
<Show when={!!item.icon}>
|
||||
<Dynamic component={item.icon} class=" size-4" />
|
||||
</Show>
|
||||
);
|
||||
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
||||
<div className="flex items-center gap-2 px-4">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
{breadcrumbLength > 0 && (
|
||||
<>
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
{breadcrumb.map((item, index) => {
|
||||
const iconEl = item.icon ? (
|
||||
<item.icon className="size-4" />
|
||||
) : null;
|
||||
|
||||
const isCurrent = index() + 1 === breadcrumbLength;
|
||||
const isCurrent = index + 1 === breadcrumbLength;
|
||||
|
||||
const LinkChild = (
|
||||
item.link ? ProLink : Fragment
|
||||
) as typeof ProLink;
|
||||
return (
|
||||
<>
|
||||
<Show when={index() > 0}>
|
||||
<BreadcrumbSeparator class="hidden md:block" />
|
||||
</Show>
|
||||
<BreadcrumbItem class="hidden md:block">
|
||||
<BreadcrumbLink
|
||||
class="text-[var(--foreground)] hover:text-inherit"
|
||||
as={item.link ? ProLink : undefined}
|
||||
current={isCurrent}
|
||||
{...item?.link}
|
||||
>
|
||||
{iconEl}
|
||||
{item.label}
|
||||
</BreadcrumbLink>
|
||||
<Fragment key={index}>
|
||||
{index > 0 && (
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
)}
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
{isCurrent ? (
|
||||
<BreadcrumbPage>
|
||||
{iconEl}
|
||||
{item.label}
|
||||
</BreadcrumbPage>
|
||||
) : (
|
||||
<BreadcrumbLink
|
||||
className="text-[var(--foreground)] hover:text-inherit"
|
||||
asChild={!!item.link}
|
||||
>
|
||||
<LinkChild {...item?.link}>
|
||||
{iconEl}
|
||||
{item.label}
|
||||
</LinkChild>
|
||||
</BreadcrumbLink>
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</Show>
|
||||
})}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
<div
|
||||
{...other}
|
||||
class={cn('flex min-h-0 flex-1 flex-col p-4 pt-0', local.class)}
|
||||
className={cn('flex min-h-0 flex-1 flex-col p-4 pt-0', className)}
|
||||
>
|
||||
{local.children}
|
||||
{children}
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
|
@ -2,7 +2,7 @@ import {
|
||||
type AnyRoute,
|
||||
type ParsedLocation,
|
||||
redirect,
|
||||
} from '@tanstack/solid-router';
|
||||
} from '@tanstack/react-router';
|
||||
import { ProLink } from '../ui/pro-link';
|
||||
|
||||
export function guardRouteIndexAsNotFound(
|
||||
@ -21,19 +21,19 @@ export function guardRouteIndexAsNotFound(
|
||||
|
||||
export function AppNotFoundComponent() {
|
||||
return (
|
||||
<div class="flex h-svh items-center px-4 py-12 sm:px-6 md:px-8 lg:px-12 xl:px-16">
|
||||
<div class="w-full space-y-6 text-center">
|
||||
<div class="space-y-3">
|
||||
<h1 class="font-bold text-4xl tracking-tighter sm:text-5xl">
|
||||
<div className="flex h-svh items-center px-4 py-12 sm:px-6 md:px-8 lg:px-12 xl:px-16">
|
||||
<div className="w-full space-y-6 text-center">
|
||||
<div className="space-y-3">
|
||||
<h1 className="font-bold text-4xl tracking-tighter sm:text-5xl">
|
||||
404 Page Not Found
|
||||
</h1>
|
||||
<p class="text-gray-500">
|
||||
<p className="text-gray-500">
|
||||
Sorry, we couldn't find the page you're looking for.
|
||||
</p>
|
||||
</div>
|
||||
<ProLink
|
||||
to="/"
|
||||
class="inline-flex h-10 items-center rounded-md border border-gray-200 border-gray-200 bg-white px-8 font-medium text-sm shadow-sm transition-colors hover:bg-gray-100 hover:text-gray-900 dark:border-gray-800 dark:border-gray-800 dark:bg-gray-950 dark:focus-visible:ring-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-50"
|
||||
className="inline-flex h-10 items-center rounded-md border border-gray-20 bg-white px-8 font-medium text-xs shadow-xs transition-colors hover:bg-gray-100 hover:text-gray-900 dark:border-gray-800 dark:bg-gray-950 dark:focus-visible:ring-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-50"
|
||||
>
|
||||
Return to website
|
||||
</ProLink>
|
||||
|
@ -1,12 +1,12 @@
|
||||
import type { ComponentProps } from 'solid-js';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarRail,
|
||||
} from '~/components/ui/sidebar';
|
||||
import { AppNavMainData } from '~/config/app-layout';
|
||||
} from '@/components/ui/sidebar';
|
||||
import { AppNavMainData } from '@/config/app-layout';
|
||||
import type { ComponentPropsWithoutRef } from 'react';
|
||||
import { AppIcon } from './app-icon';
|
||||
import { NavMain } from './nav-main';
|
||||
import { NavUser } from './nav-user';
|
||||
@ -19,7 +19,10 @@ const data = {
|
||||
},
|
||||
};
|
||||
|
||||
type AppSidebarRootProps = Omit<ComponentProps<typeof Sidebar>, 'collapsible'>;
|
||||
type AppSidebarRootProps = Omit<
|
||||
ComponentPropsWithoutRef<typeof Sidebar>,
|
||||
'collapsible'
|
||||
>;
|
||||
|
||||
export const AppSidebar = (props: AppSidebarRootProps) => {
|
||||
return (
|
||||
|
@ -1,12 +1,12 @@
|
||||
export function AppSkeleton() {
|
||||
return (
|
||||
<div class="flex min-h-svh flex-1 flex-col gap-4 md:min-h-0">
|
||||
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||
<div class="aspect-video rounded-xl bg-muted/50" />
|
||||
<div class="aspect-video rounded-xl bg-muted/50" />
|
||||
<div class="aspect-video rounded-xl bg-muted/50" />
|
||||
<div className="flex min-h-svh flex-1 flex-col gap-4 md:min-h-0">
|
||||
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||
<div className="aspect-video rounded-xl bg-muted/50" />
|
||||
<div className="aspect-video rounded-xl bg-muted/50" />
|
||||
<div className="aspect-video rounded-xl bg-muted/50" />
|
||||
</div>
|
||||
<div class="min-h-svh flex-1 rounded-xl bg-muted/50 md:min-h-0" />
|
||||
<div className="min-h-svh flex-1 rounded-xl bg-muted/50 md:min-h-0" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ChevronRight, type LucideIcon } from 'lucide-solid';
|
||||
import { For, Show, createSignal } from 'solid-js';
|
||||
'use client';
|
||||
|
||||
import { ChevronRight, type LucideIcon } from 'lucide-react';
|
||||
|
||||
import { useMatch, useMatches } from '@tanstack/solid-router';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '~/components/ui/collapsible';
|
||||
} from '@/components/ui/collapsible';
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
@ -16,7 +16,8 @@ import {
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from '~/components/ui/sidebar';
|
||||
} from '@/components/ui/sidebar';
|
||||
import { useMatches } from '@tanstack/react-router';
|
||||
import { ProLink, type ProLinkProps } from '../ui/pro-link';
|
||||
|
||||
export interface NavMainItem {
|
||||
@ -43,7 +44,7 @@ export function NavMain({
|
||||
if (!linkTo) {
|
||||
return false;
|
||||
}
|
||||
return matches().some((match) => match.pathname.startsWith(linkTo));
|
||||
return matches.some((match) => match.pathname.startsWith(linkTo));
|
||||
};
|
||||
|
||||
const renderSidebarMenuItemButton = (item: NavMainItem) => {
|
||||
@ -51,71 +52,68 @@ export function NavMain({
|
||||
<>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
<ChevronRight class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]:rotate-90" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<For each={groups}>
|
||||
{(group) => (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>{group.group}</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
<For each={group.items}>
|
||||
{(item) => {
|
||||
return (
|
||||
<Show
|
||||
when={!!item.children?.length}
|
||||
fallback={
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
as={ProLink}
|
||||
{...item.link}
|
||||
tooltip={item.title}
|
||||
>
|
||||
{renderSidebarMenuItemButton(item)}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
}
|
||||
>
|
||||
<Collapsible
|
||||
as={SidebarMenuItem}
|
||||
class="group/collapsible"
|
||||
defaultOpen={isMenuMatch(item.link)}
|
||||
>
|
||||
<CollapsibleTrigger
|
||||
as={SidebarMenuButton}
|
||||
tooltip={item.title}
|
||||
>
|
||||
{renderSidebarMenuItemButton(item)}
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
<For each={item.children || []}>
|
||||
{(subItem) => (
|
||||
<SidebarMenuSubItem>
|
||||
<SidebarMenuSubButton
|
||||
as={ProLink}
|
||||
{...subItem.link}
|
||||
isActive={isMenuMatch(subItem.link)}
|
||||
activeProps={{ class: '' }}
|
||||
>
|
||||
<span>{subItem.title}</span>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
)}
|
||||
</For>
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</Show>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)}
|
||||
</For>
|
||||
);
|
||||
return groups.map((group, groupIndex) => {
|
||||
return (
|
||||
<SidebarGroup key={groupIndex}>
|
||||
<SidebarGroupLabel>{group.group}</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{group.items.map((item, itemIndex) => {
|
||||
if (!item.children?.length) {
|
||||
return (
|
||||
<SidebarMenuItem key={itemIndex}>
|
||||
<SidebarMenuButton asChild tooltip={item.title}>
|
||||
<ProLink {...item.link}>
|
||||
{renderSidebarMenuItemButton(item)}
|
||||
</ProLink>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Collapsible
|
||||
key={itemIndex}
|
||||
asChild
|
||||
className="group/collapsible"
|
||||
defaultOpen={isMenuMatch(item.link)}
|
||||
>
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuButton tooltip={item.title}>
|
||||
{renderSidebarMenuItemButton(item)}
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{(item.children || []).map((subItem, subItemIndex) => {
|
||||
return (
|
||||
<SidebarMenuSubItem key={subItemIndex}>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
isActive={isMenuMatch(subItem.link)}
|
||||
>
|
||||
<ProLink
|
||||
{...subItem.link}
|
||||
activeProps={{ className: '' }}
|
||||
>
|
||||
<span>{subItem.title}</span>
|
||||
</ProLink>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
);
|
||||
})}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
);
|
||||
})}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { Link } from '@tanstack/solid-router';
|
||||
'use client';
|
||||
|
||||
import { Link } from '@tanstack/react-router';
|
||||
|
||||
import {
|
||||
Folder,
|
||||
Forward,
|
||||
type LucideIcon,
|
||||
MoreHorizontal,
|
||||
Trash2,
|
||||
} from 'lucide-solid';
|
||||
import { type ComponentProps, For } from 'solid-js';
|
||||
} from 'lucide-react';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -14,7 +16,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '~/components/ui/dropdown-menu';
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
@ -22,7 +24,9 @@ import {
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from '~/components/ui/sidebar';
|
||||
useSidebar,
|
||||
} from '@/components/ui/sidebar';
|
||||
import type { ComponentProps } from 'react';
|
||||
|
||||
export function NavProjects({
|
||||
projects,
|
||||
@ -33,44 +37,52 @@ export function NavProjects({
|
||||
link: ComponentProps<typeof Link>;
|
||||
}[];
|
||||
}) {
|
||||
const { isMobile } = useSidebar();
|
||||
|
||||
return (
|
||||
<SidebarGroup class="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Projects</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
<For each={projects}>
|
||||
{(item) => (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton as={Link} {...item?.link}>
|
||||
{projects.map((item) => (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link {...item.link}>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as={SidebarMenuAction} showOnHover>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction showOnHover>
|
||||
<MoreHorizontal />
|
||||
<span class="sr-only">More</span>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-48 rounded-lg">
|
||||
<DropdownMenuItem>
|
||||
<Folder class="text-muted-foreground" />
|
||||
<span>View Project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Forward class="text-muted-foreground" />
|
||||
<span>Share Project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Trash2 class="text-muted-foreground" />
|
||||
<span>Delete Project</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
)}
|
||||
</For>
|
||||
<span className="sr-only">More</span>
|
||||
</SidebarMenuAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-48 rounded-lg"
|
||||
side={isMobile ? 'bottom' : 'right'}
|
||||
align={isMobile ? 'end' : 'start'}
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
<Folder className="text-muted-foreground" />
|
||||
<span>View Project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Forward className="text-muted-foreground" />
|
||||
<span>Share Project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Trash2 className="text-muted-foreground" />
|
||||
<span>Delete Project</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton class="text-sidebar-foreground/70">
|
||||
<MoreHorizontal class="text-sidebar-foreground/70" />
|
||||
<SidebarMenuButton className="text-sidebar-foreground/70">
|
||||
<MoreHorizontal className="text-sidebar-foreground/70" />
|
||||
<span>More</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
BadgeCheck,
|
||||
Bell,
|
||||
@ -5,9 +7,9 @@ import {
|
||||
CreditCard,
|
||||
LogOut,
|
||||
Sparkles,
|
||||
} from 'lucide-solid';
|
||||
} from 'lucide-react';
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '~/components/ui/avatar';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@ -16,12 +18,13 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '~/components/ui/dropdown-menu';
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from '~/components/ui/sidebar';
|
||||
useSidebar,
|
||||
} from '@/components/ui/sidebar';
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
@ -32,35 +35,43 @@ export function NavUser({
|
||||
avatar: string;
|
||||
};
|
||||
}) {
|
||||
const { isMobile } = useSidebar();
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
as={SidebarMenuButton}
|
||||
size="lg"
|
||||
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar class="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback class="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-semibold">{user.name}</span>
|
||||
<span class="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
<ChevronsUpDown class="ml-auto size-4" />
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-xs leading-tight">
|
||||
<span className="truncate font-semibold">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg">
|
||||
<DropdownMenuLabel class="p-0 font-normal">
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar class="h-8 w-8 rounded-lg">
|
||||
<DropdownMenuContent
|
||||
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
||||
side={isMobile ? 'bottom' : 'right'}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-xs">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback class="rounded-lg">CN</AvatarFallback>
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-semibold">{user.name}</span>
|
||||
<span class="truncate text-xs">{user.email}</span>
|
||||
<div className="grid flex-1 text-left text-xs leading-tight">
|
||||
<span className="truncate font-semibold">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
|
@ -1,91 +1,64 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import * as AccordionPrimitive from '@kobalte/core/accordion';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
function Accordion({
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
||||
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
||||
}
|
||||
|
||||
const Accordion = AccordionPrimitive.Root;
|
||||
|
||||
type AccordionItemProps<T extends ValidComponent = 'div'> =
|
||||
AccordionPrimitive.AccordionItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const AccordionItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, AccordionItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AccordionItemProps, ['class']);
|
||||
function AccordionItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
||||
return (
|
||||
<AccordionPrimitive.Item class={cn('border-b', local.class)} {...others} />
|
||||
);
|
||||
};
|
||||
<AccordionPrimitive.Item
|
||||
data-slot="accordion-item"
|
||||
className={cn("border-b last:border-b-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type AccordionTriggerProps<T extends ValidComponent = 'button'> =
|
||||
AccordionPrimitive.AccordionTriggerProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const AccordionTrigger = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, AccordionTriggerProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AccordionTriggerProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function AccordionTrigger({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
||||
return (
|
||||
<AccordionPrimitive.Header class="flex">
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
class={cn(
|
||||
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-expanded]>svg]:rotate-180',
|
||||
local.class
|
||||
data-slot="accordion-trigger"
|
||||
className={cn(
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left 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
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
>
|
||||
{local.children}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4 shrink-0 transition-transform duration-200"
|
||||
>
|
||||
<path d="M6 9l6 6l6 -6" />
|
||||
</svg>
|
||||
{children}
|
||||
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type AccordionContentProps<T extends ValidComponent = 'div'> =
|
||||
AccordionPrimitive.AccordionContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const AccordionContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, AccordionContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AccordionContentProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function AccordionContent({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
||||
return (
|
||||
<AccordionPrimitive.Content
|
||||
class={cn(
|
||||
'animate-accordion-up overflow-hidden text-sm transition-all data-[expanded]:animate-accordion-down',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
data-slot="accordion-content"
|
||||
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
||||
{...props}
|
||||
>
|
||||
<div class="pt-0 pb-4">{local.children}</div>
|
||||
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||
|
@ -1,117 +1,146 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import * as AlertDialogPrimitive from '@kobalte/core/alert-dialog';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as React from "react"
|
||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
|
||||
const AlertDialog = AlertDialogPrimitive.Root;
|
||||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
||||
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
||||
function AlertDialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
||||
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
||||
}
|
||||
|
||||
type AlertDialogOverlayProps<T extends ValidComponent = 'div'> =
|
||||
AlertDialogPrimitive.AlertDialogOverlayProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
function AlertDialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
const AlertDialogOverlay = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, AlertDialogOverlayProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AlertDialogOverlayProps, [
|
||||
'class',
|
||||
]);
|
||||
function AlertDialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
class={cn(
|
||||
'data-[closed]:fade-out-0 data-[expanded]:fade-in-0 fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[closed]:animate-out data-[expanded]:animate-in',
|
||||
local.class
|
||||
data-slot="alert-dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type AlertDialogContentProps<T extends ValidComponent = 'div'> =
|
||||
AlertDialogPrimitive.AlertDialogContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const AlertDialogContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, AlertDialogContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AlertDialogContentProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function AlertDialogContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
|
||||
return (
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
class={cn(
|
||||
'-translate-x-1/2 -translate-y-1/2 data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95 data-[closed]:slide-out-to-left-1/2 data-[closed]:slide-out-to-top-[48%] data-[expanded]:slide-in-from-left-1/2 data-[expanded]:slide-in-from-top-[48%] fixed top-1/2 left-1/2 z-50 grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg duration-200 data-[closed]:animate-out data-[expanded]:animate-in sm:rounded-lg md:w-full',
|
||||
local.class
|
||||
data-slot="alert-dialog-content"
|
||||
className={cn(
|
||||
"bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
{local.children}
|
||||
<AlertDialogPrimitive.CloseButton class="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[expanded]:bg-accent data-[expanded]:text-muted-foreground">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M18 6l-12 12" />
|
||||
<path d="M6 6l12 12" />
|
||||
</svg>
|
||||
<span class="sr-only">Close</span>
|
||||
</AlertDialogPrimitive.CloseButton>
|
||||
</AlertDialogPrimitive.Content>
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type AlertDialogTitleProps<T extends ValidComponent = 'h2'> =
|
||||
AlertDialogPrimitive.AlertDialogTitleProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
function AlertDialogHeader({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="alert-dialog-header"
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const AlertDialogTitle = <T extends ValidComponent = 'h2'>(
|
||||
props: PolymorphicProps<T, AlertDialogTitleProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AlertDialogTitleProps, ['class']);
|
||||
function AlertDialogFooter({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="alert-dialog-footer"
|
||||
className={cn(
|
||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Title
|
||||
class={cn('font-semibold text-lg', local.class)}
|
||||
{...others}
|
||||
data-slot="alert-dialog-title"
|
||||
className={cn("text-lg font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type AlertDialogDescriptionProps<T extends ValidComponent = 'p'> =
|
||||
AlertDialogPrimitive.AlertDialogDescriptionProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const AlertDialogDescription = <T extends ValidComponent = 'p'>(
|
||||
props: PolymorphicProps<T, AlertDialogDescriptionProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AlertDialogDescriptionProps, [
|
||||
'class',
|
||||
]);
|
||||
function AlertDialogDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Description
|
||||
class={cn('text-muted-foreground text-sm', local.class)}
|
||||
{...others}
|
||||
data-slot="alert-dialog-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogAction({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Action
|
||||
className={cn(buttonVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AlertDialogCancel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
|
||||
return (
|
||||
<AlertDialogPrimitive.Cancel
|
||||
className={cn(buttonVariants({ variant: "outline" }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
@ -119,6 +148,10 @@ export {
|
||||
AlertDialogOverlay,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
};
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
}
|
||||
|
@ -1,63 +1,66 @@
|
||||
import type { Component, ComponentProps, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import * as AlertPrimitive from '@kobalte/core/alert';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
const alertVariants = cva(
|
||||
'relative w-full rounded-lg border p-4 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:top-4 [&>svg]:left-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
|
||||
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-background text-foreground',
|
||||
default: "bg-card text-card-foreground",
|
||||
destructive:
|
||||
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
|
||||
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
type AlertRootProps<T extends ValidComponent = 'div'> =
|
||||
AlertPrimitive.AlertRootProps<T> &
|
||||
VariantProps<typeof alertVariants> & { class?: string | undefined };
|
||||
|
||||
const Alert = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, AlertRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AlertRootProps, [
|
||||
'class',
|
||||
'variant',
|
||||
]);
|
||||
function Alert({
|
||||
className,
|
||||
variant,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
||||
return (
|
||||
<AlertPrimitive.Root
|
||||
class={cn(alertVariants({ variant: props.variant }), local.class)}
|
||||
{...others}
|
||||
<div
|
||||
data-slot="alert"
|
||||
role="alert"
|
||||
className={cn(alertVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const AlertTitle: Component<ComponentProps<'h5'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<h5
|
||||
class={cn('mb-1 font-medium leading-none tracking-tight', local.class)}
|
||||
{...others}
|
||||
<div
|
||||
data-slot="alert-title"
|
||||
className={cn(
|
||||
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const AlertDescription: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function AlertDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div class={cn('text-sm [&_p]:leading-relaxed', local.class)} {...others} />
|
||||
);
|
||||
};
|
||||
<div
|
||||
data-slot="alert-description"
|
||||
className={cn(
|
||||
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription };
|
||||
export { Alert, AlertTitle, AlertDescription }
|
||||
|
@ -1,31 +1,9 @@
|
||||
import type { Component, ComponentProps } from "solid-js"
|
||||
import { mergeProps, splitProps } from "solid-js"
|
||||
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
|
||||
|
||||
type AspectRatioProps = ComponentProps<"div"> & { ratio?: number }
|
||||
|
||||
const AspectRatio: Component<AspectRatioProps> = (rawProps) => {
|
||||
const props = mergeProps({ ratio: 1 / 1 }, rawProps)
|
||||
const [local, others] = splitProps(props, ["ratio"])
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
"padding-bottom": `${100 / local.ratio}%`
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
}}
|
||||
{...others}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
function AspectRatio({
|
||||
...props
|
||||
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
|
||||
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
|
||||
}
|
||||
|
||||
export { AspectRatio }
|
||||
|
@ -1,64 +1,53 @@
|
||||
import type { ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import * as ImagePrimitive from '@kobalte/core/image';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
type AvatarRootProps<T extends ValidComponent = 'span'> =
|
||||
ImagePrimitive.ImageRootProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const Avatar = <T extends ValidComponent = 'span'>(
|
||||
props: PolymorphicProps<T, AvatarRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AvatarRootProps, ['class']);
|
||||
function Avatar({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||
return (
|
||||
<ImagePrimitive.Root
|
||||
class={cn(
|
||||
'relative flex size-10 shrink-0 overflow-hidden rounded-full',
|
||||
local.class
|
||||
<AvatarPrimitive.Root
|
||||
data-slot="avatar"
|
||||
className={cn(
|
||||
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type AvatarImageProps<T extends ValidComponent = 'img'> =
|
||||
ImagePrimitive.ImageImgProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const AvatarImage = <T extends ValidComponent = 'img'>(
|
||||
props: PolymorphicProps<T, AvatarImageProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AvatarImageProps, ['class']);
|
||||
function AvatarImage({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
||||
return (
|
||||
<ImagePrimitive.Img
|
||||
class={cn('aspect-square size-full', local.class)}
|
||||
{...others}
|
||||
<AvatarPrimitive.Image
|
||||
data-slot="avatar-image"
|
||||
className={cn("aspect-square size-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type AvatarFallbackProps<T extends ValidComponent = 'span'> =
|
||||
ImagePrimitive.ImageFallbackProps<T> & { class?: string | undefined };
|
||||
|
||||
const AvatarFallback = <T extends ValidComponent = 'span'>(
|
||||
props: PolymorphicProps<T, AvatarFallbackProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as AvatarFallbackProps, ['class']);
|
||||
function AvatarFallback({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
||||
return (
|
||||
<ImagePrimitive.Fallback
|
||||
class={cn(
|
||||
'flex size-full items-center justify-center bg-muted',
|
||||
local.class
|
||||
<AvatarPrimitive.Fallback
|
||||
data-slot="avatar-fallback"
|
||||
className={cn(
|
||||
"bg-muted flex size-full items-center justify-center rounded-full",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback };
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
||||
|
@ -1,156 +0,0 @@
|
||||
import type { Component, JSXElement } from 'solid-js';
|
||||
import { createEffect, on, splitProps } from 'solid-js';
|
||||
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import type { BadgeProps } from '~/components/ui/badge';
|
||||
import { Badge } from '~/components/ui/badge';
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
type DeltaType =
|
||||
| 'increase'
|
||||
| 'moderateIncrease'
|
||||
| 'unchanged'
|
||||
| 'moderateDecrease'
|
||||
| 'decrease';
|
||||
|
||||
const badgeDeltaVariants = cva('', {
|
||||
variants: {
|
||||
variant: {
|
||||
success: 'bg-success text-success-foreground hover:bg-success',
|
||||
warning: 'bg-warning text-warning-foreground hover:bg-warning',
|
||||
error: 'bg-error text-error-foreground hover:bg-error',
|
||||
},
|
||||
},
|
||||
});
|
||||
type DeltaVariant = NonNullable<
|
||||
VariantProps<typeof badgeDeltaVariants>['variant']
|
||||
>;
|
||||
|
||||
const iconMap: {
|
||||
[key in DeltaType]: (props: { class?: string }) => JSXElement;
|
||||
} = {
|
||||
increase: (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class={props.class}
|
||||
>
|
||||
<path d="M12 5l0 14" />
|
||||
<path d="M18 11l-6 -6" />
|
||||
<path d="M6 11l6 -6" />
|
||||
</svg>
|
||||
),
|
||||
moderateIncrease: (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class={props.class}
|
||||
>
|
||||
<path d="M17 7l-10 10" />
|
||||
<path d="M8 7l9 0l0 9" />
|
||||
</svg>
|
||||
),
|
||||
unchanged: (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class={props.class}
|
||||
>
|
||||
<path d="M5 12l14 0" />
|
||||
<path d="M13 18l6 -6" />
|
||||
<path d="M13 6l6 6" />
|
||||
</svg>
|
||||
),
|
||||
moderateDecrease: (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class={props.class}
|
||||
>
|
||||
<path d="M7 7l10 10" />
|
||||
<path d="M17 8l0 9l-9 0" />
|
||||
</svg>
|
||||
),
|
||||
decrease: (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class={props.class}
|
||||
>
|
||||
<path d="M12 5l0 14" />
|
||||
<path d="M18 13l-6 6" />
|
||||
<path d="M6 13l6 6" />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
const variantMap: { [key in DeltaType]: DeltaVariant } = {
|
||||
increase: 'success',
|
||||
moderateIncrease: 'success',
|
||||
unchanged: 'warning',
|
||||
moderateDecrease: 'error',
|
||||
decrease: 'error',
|
||||
};
|
||||
|
||||
type BadgeDeltaProps = Omit<BadgeProps, 'variant'> & {
|
||||
deltaType: DeltaType;
|
||||
};
|
||||
|
||||
const BadgeDelta: Component<BadgeDeltaProps> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class', 'children', 'deltaType']);
|
||||
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
let Icon = iconMap[local.deltaType];
|
||||
createEffect(
|
||||
on(
|
||||
() => local.deltaType,
|
||||
() => {
|
||||
Icon = iconMap[local.deltaType];
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<Badge
|
||||
class={cn(
|
||||
badgeDeltaVariants({ variant: variantMap[local.deltaType] }),
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<span class="flex gap-1">
|
||||
<Icon class="size-4" />
|
||||
{local.children}
|
||||
</span>
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
export { BadgeDelta };
|
@ -1,48 +1,46 @@
|
||||
import type { Component, ComponentProps } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
'inline-flex items-center rounded-md border px-2.5 py-0.5 font-semibold text-xs transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none 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 transition-[color,box-shadow] overflow-hidden",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'border-transparent bg-primary text-primary-foreground',
|
||||
secondary: 'border-transparent bg-secondary text-secondary-foreground',
|
||||
outline: 'text-foreground',
|
||||
success: 'border-success-foreground bg-success text-success-foreground',
|
||||
warning: 'border-warning-foreground bg-warning text-warning-foreground',
|
||||
error: 'border-error-foreground bg-error text-error-foreground',
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
type BadgeProps = ComponentProps<'div'> &
|
||||
VariantProps<typeof badgeVariants> & {
|
||||
round?: boolean;
|
||||
};
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : "span"
|
||||
|
||||
const Badge: Component<BadgeProps> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class', 'variant', 'round']);
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
badgeVariants({ variant: local.variant }),
|
||||
local.round && 'rounded-full',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export type { BadgeProps };
|
||||
export { Badge, badgeVariants };
|
||||
export { Badge, badgeVariants }
|
||||
|
@ -1,111 +0,0 @@
|
||||
import type { ComponentProps, JSX } from 'solid-js';
|
||||
import { For, Show, mergeProps, splitProps } from 'solid-js';
|
||||
import { Dynamic } from 'solid-js/web';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
type Bar<T> = T & {
|
||||
value: number;
|
||||
name: JSX.Element;
|
||||
icon?: (props: ComponentProps<'svg'>) => JSX.Element;
|
||||
href?: string;
|
||||
target?: string;
|
||||
};
|
||||
|
||||
type SortOrder = 'ascending' | 'descending' | 'none';
|
||||
|
||||
type ValueFormatter = (value: number) => string;
|
||||
|
||||
const defaultValueFormatter: ValueFormatter = (value: number) =>
|
||||
value.toString();
|
||||
|
||||
type BarListProps<T> = ComponentProps<'div'> & {
|
||||
data: Bar<T>[];
|
||||
valueFormatter?: ValueFormatter;
|
||||
sortOrder?: SortOrder;
|
||||
};
|
||||
|
||||
const BarList = <T,>(rawProps: BarListProps<T>) => {
|
||||
const props = mergeProps(
|
||||
{
|
||||
valueFormatter: defaultValueFormatter,
|
||||
sortOrder: 'descending' as SortOrder,
|
||||
},
|
||||
rawProps
|
||||
);
|
||||
const [local, others] = splitProps(props, [
|
||||
'class',
|
||||
'data',
|
||||
'valueFormatter',
|
||||
'sortOrder',
|
||||
]);
|
||||
|
||||
const sortedData = () => {
|
||||
if (local.sortOrder === 'none') {
|
||||
return local.data;
|
||||
}
|
||||
return local.data.sort((a, b) =>
|
||||
local.sortOrder === 'ascending' ? a.value - b.value : b.value - a.value
|
||||
);
|
||||
};
|
||||
|
||||
const widths = () => {
|
||||
const maxValue = Math.max(...sortedData().map((item) => item.value), 0);
|
||||
return sortedData().map((item) =>
|
||||
item.value === 0 ? 0 : Math.max((item.value / maxValue) * 100, 2)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn('flex flex-col space-y-1.5', local.class)}
|
||||
aria-sort={local.sortOrder}
|
||||
{...others}
|
||||
>
|
||||
<For each={sortedData()}>
|
||||
{(bar, idx) => {
|
||||
return (
|
||||
<div class="row flex w-full justify-between space-x-6">
|
||||
<div class="grow">
|
||||
<div
|
||||
class={cn(
|
||||
'flex h-8 items-center rounded-md bg-secondary px-2'
|
||||
)}
|
||||
style={{
|
||||
width: `${widths()[idx()]}%`,
|
||||
}}
|
||||
>
|
||||
<Show when={bar.icon}>
|
||||
{(icon) => (
|
||||
<Dynamic
|
||||
component={icon()}
|
||||
class="mr-2 size-5 flex-none"
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={bar.href} fallback={<p>{bar.name}</p>}>
|
||||
{(href) => (
|
||||
<a
|
||||
href={href()}
|
||||
target={bar.target ?? '_blank'}
|
||||
rel="noreferrer"
|
||||
class="hover:underline"
|
||||
>
|
||||
{bar.name}
|
||||
</a>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
{local.valueFormatter(bar.value)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { BarList };
|
@ -1,125 +1,109 @@
|
||||
import type { Component, ComponentProps, JSX, ValidComponent } from 'solid-js';
|
||||
import { Show, splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core';
|
||||
import * as BreadcrumbPrimitive from '@kobalte/core/breadcrumbs';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
||||
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
|
||||
}
|
||||
|
||||
const Breadcrumb = BreadcrumbPrimitive.Root;
|
||||
|
||||
const BreadcrumbList: Component<ComponentProps<'ol'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||
return (
|
||||
<ol
|
||||
class={cn(
|
||||
'flex flex-wrap items-center gap-1.5 break-words text-muted-foreground text-sm sm:gap-2.5',
|
||||
local.class
|
||||
data-slot="breadcrumb-list"
|
||||
className={cn(
|
||||
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const BreadcrumbItem: Component<ComponentProps<'li'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||
return (
|
||||
<li
|
||||
class={cn('inline-flex items-center gap-1.5', local.class)}
|
||||
{...others}
|
||||
data-slot="breadcrumb-item"
|
||||
className={cn("inline-flex items-center gap-1.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type BreadcrumbLinkProps<T extends ValidComponent = 'a'> =
|
||||
BreadcrumbPrimitive.BreadcrumbsLinkProps<T> & { class?: string | undefined };
|
||||
function BreadcrumbLink({
|
||||
asChild,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"a"> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "a"
|
||||
|
||||
const BreadcrumbLink = <T extends ValidComponent = 'a'>(
|
||||
props: PolymorphicProps<T, BreadcrumbLinkProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as BreadcrumbLinkProps, ['class']);
|
||||
return (
|
||||
<BreadcrumbPrimitive.Link
|
||||
class={cn(
|
||||
'transition-colors hover:text-foreground data-[current]:font-normal data-[current]:text-foreground',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
<Comp
|
||||
data-slot="breadcrumb-link"
|
||||
className={cn("hover:text-foreground transition-colors", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type BreadcrumbSeparatorProps<T extends ValidComponent = 'span'> =
|
||||
BreadcrumbPrimitive.BreadcrumbsSeparatorProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const BreadcrumbSeparator = <T extends ValidComponent = 'span'>(
|
||||
props: PolymorphicProps<T, BreadcrumbSeparatorProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as BreadcrumbSeparatorProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
return (
|
||||
<BreadcrumbPrimitive.Separator
|
||||
class={cn('[&>svg]:size-3.5', local.class)}
|
||||
{...others}
|
||||
>
|
||||
<Show
|
||||
when={local.children}
|
||||
fallback={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M9 6l6 6l-6 6" />
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
{local.children}
|
||||
</Show>
|
||||
</BreadcrumbPrimitive.Separator>
|
||||
);
|
||||
};
|
||||
|
||||
const BreadcrumbEllipsis: Component<ComponentProps<'span'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
class={cn('flex size-9 items-center justify-center', local.class)}
|
||||
{...others}
|
||||
data-slot="breadcrumb-page"
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
className={cn("text-foreground font-normal", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function BreadcrumbSeparator({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"li">) {
|
||||
return (
|
||||
<li
|
||||
data-slot="breadcrumb-separator"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("[&>svg]:size-3.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
<path d="M19 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
</svg>
|
||||
<span class="sr-only">More</span>
|
||||
{children ?? <ChevronRight />}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function BreadcrumbEllipsis({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="breadcrumb-ellipsis"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("flex size-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="size-4" />
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis,
|
||||
};
|
||||
}
|
||||
|
@ -1,67 +1,59 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import * as ButtonPrimitive from '@kobalte/core/button';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none 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",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||
destructive:
|
||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
'border border-input hover:bg-accent hover:text-accent-foreground',
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||
ghost:
|
||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 px-3 text-xs',
|
||||
lg: 'h-11 px-8',
|
||||
icon: 'size-10',
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
type ButtonProps<T extends ValidComponent = 'button'> =
|
||||
ButtonPrimitive.ButtonRootProps<T> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
const Button = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, ButtonProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ButtonProps, [
|
||||
'variant',
|
||||
'size',
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<ButtonPrimitive.Root
|
||||
class={cn(
|
||||
buttonVariants({ variant: local.variant, size: local.size }),
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
<Comp
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export type { ButtonProps };
|
||||
export { Button, buttonVariants };
|
||||
export { Button, buttonVariants }
|
||||
|
73
apps/webui/src/components/ui/calendar.tsx
Normal file
73
apps/webui/src/components/ui/calendar.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import * as React from "react"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { DayPicker } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/styles/utils"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DayPicker>) {
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col sm:flex-row gap-2",
|
||||
month: "flex flex-col gap-4",
|
||||
caption: "flex justify-center pt-1 relative items-center w-full",
|
||||
caption_label: "text-sm font-medium",
|
||||
nav: "flex items-center gap-1",
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"size-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||
),
|
||||
nav_button_previous: "absolute left-1",
|
||||
nav_button_next: "absolute right-1",
|
||||
table: "w-full border-collapse space-x-1",
|
||||
head_row: "flex",
|
||||
head_cell:
|
||||
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
|
||||
row: "flex w-full mt-2",
|
||||
cell: cn(
|
||||
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md",
|
||||
props.mode === "range"
|
||||
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
|
||||
: "[&:has([aria-selected])]:rounded-md"
|
||||
),
|
||||
day: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"size-8 p-0 font-normal aria-selected:opacity-100"
|
||||
),
|
||||
day_range_start:
|
||||
"day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
|
||||
day_range_end:
|
||||
"day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
|
||||
day_selected:
|
||||
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
||||
day_today: "bg-accent text-accent-foreground",
|
||||
day_outside:
|
||||
"day-outside text-muted-foreground aria-selected:text-muted-foreground",
|
||||
day_disabled: "text-muted-foreground opacity-50",
|
||||
day_range_middle:
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
||||
day_hidden: "invisible",
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ className, ...props }) => (
|
||||
<ChevronLeft className={cn("size-4", className)} {...props} />
|
||||
),
|
||||
IconRight: ({ className, ...props }) => (
|
||||
<ChevronRight className={cn("size-4", className)} {...props} />
|
||||
),
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Calendar }
|
@ -1,46 +0,0 @@
|
||||
import type { Component, ComponentProps } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
const calloutVariants = cva('rounded-md border-l-4 p-2 pl-4', {
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'border-info-foreground bg-info text-info-foreground',
|
||||
success: 'border-success-foreground bg-success text-success-foreground',
|
||||
warning: 'border-warning-foreground bg-warning text-warning-foreground',
|
||||
error: 'border-error-foreground bg-error text-error-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
type CalloutProps = ComponentProps<'div'> &
|
||||
VariantProps<typeof calloutVariants>;
|
||||
|
||||
const Callout: Component<CalloutProps> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class', 'variant']);
|
||||
return (
|
||||
<div
|
||||
class={cn(calloutVariants({ variant: local.variant }), local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const CalloutTitle: Component<ComponentProps<'h3'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return <h3 class={cn('font-semibold', local.class)} {...others} />;
|
||||
};
|
||||
|
||||
const CalloutContent: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return <div class={cn('mt-2', local.class)} {...others} />;
|
||||
};
|
||||
|
||||
export { Callout, CalloutTitle, CalloutContent };
|
@ -1,65 +1,92 @@
|
||||
import type { Component, ComponentProps } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import type * as React from 'react';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from '@/styles/utils';
|
||||
|
||||
const Card: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function Card({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'rounded-lg border bg-card text-card-foreground shadow-sm',
|
||||
local.class
|
||||
data-slot="card"
|
||||
className={cn(
|
||||
'flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm',
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const CardHeader: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div class={cn('flex flex-col space-y-1.5 p-6', local.class)} {...others} />
|
||||
);
|
||||
};
|
||||
|
||||
const CardTitle: Component<ComponentProps<'h3'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<h3
|
||||
class={cn(
|
||||
'font-semibold text-lg leading-none tracking-tight',
|
||||
local.class
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={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
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const CardDescription: Component<ComponentProps<'p'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<p class={cn('text-muted-foreground text-sm', local.class)} {...others} />
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn('font-semibold leading-none', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const CardContent: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return <div class={cn('p-6 pt-0', local.class)} {...others} />;
|
||||
};
|
||||
|
||||
const CardFooter: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div class={cn('flex items-center p-6 pt-0', local.class)} {...others} />
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn('text-muted-foreground text-sm', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-action"
|
||||
className={cn(
|
||||
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn('px-6', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
};
|
||||
|
@ -1,285 +1,241 @@
|
||||
import type { Accessor, Component, ComponentProps, VoidProps } from 'solid-js';
|
||||
import {
|
||||
createContext,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
mergeProps,
|
||||
splitProps,
|
||||
useContext,
|
||||
} from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import type { CreateEmblaCarouselType } from 'embla-carousel-solid';
|
||||
import createEmblaCarousel from 'embla-carousel-solid';
|
||||
import * as React from "react"
|
||||
import useEmblaCarousel, {
|
||||
type UseEmblaCarouselType,
|
||||
} from "embla-carousel-react"
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react"
|
||||
|
||||
import type { ButtonProps } from '~/components/ui/button';
|
||||
import { Button } from '~/components/ui/button';
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export type CarouselApi = CreateEmblaCarouselType[1];
|
||||
|
||||
type UseCarouselParameters = Parameters<typeof createEmblaCarousel>;
|
||||
type CarouselOptions = NonNullable<UseCarouselParameters[0]>;
|
||||
type CarouselPlugin = NonNullable<UseCarouselParameters[1]>;
|
||||
type CarouselApi = UseEmblaCarouselType[1]
|
||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
||||
type CarouselOptions = UseCarouselParameters[0]
|
||||
type CarouselPlugin = UseCarouselParameters[1]
|
||||
|
||||
type CarouselProps = {
|
||||
opts?: ReturnType<CarouselOptions>;
|
||||
plugins?: ReturnType<CarouselPlugin>;
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
setApi?: (api: CarouselApi) => void;
|
||||
};
|
||||
opts?: CarouselOptions
|
||||
plugins?: CarouselPlugin
|
||||
orientation?: "horizontal" | "vertical"
|
||||
setApi?: (api: CarouselApi) => void
|
||||
}
|
||||
|
||||
type CarouselContextProps = {
|
||||
carouselRef: ReturnType<typeof createEmblaCarousel>[0];
|
||||
api: ReturnType<typeof createEmblaCarousel>[1];
|
||||
scrollPrev: () => void;
|
||||
scrollNext: () => void;
|
||||
canScrollPrev: Accessor<boolean>;
|
||||
canScrollNext: Accessor<boolean>;
|
||||
} & CarouselProps;
|
||||
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
||||
api: ReturnType<typeof useEmblaCarousel>[1]
|
||||
scrollPrev: () => void
|
||||
scrollNext: () => void
|
||||
canScrollPrev: boolean
|
||||
canScrollNext: boolean
|
||||
} & CarouselProps
|
||||
|
||||
const CarouselContext = createContext<Accessor<CarouselContextProps> | null>(
|
||||
null
|
||||
);
|
||||
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
||||
|
||||
const useCarousel = () => {
|
||||
const context = useContext(CarouselContext);
|
||||
function useCarousel() {
|
||||
const context = React.useContext(CarouselContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useCarousel must be used within a <Carousel />');
|
||||
throw new Error("useCarousel must be used within a <Carousel />")
|
||||
}
|
||||
|
||||
return context();
|
||||
};
|
||||
return context
|
||||
}
|
||||
|
||||
const Carousel: Component<CarouselProps & ComponentProps<'div'>> = (
|
||||
rawProps
|
||||
) => {
|
||||
const props = mergeProps<(CarouselProps & ComponentProps<'div'>)[]>(
|
||||
{ orientation: 'horizontal' },
|
||||
rawProps
|
||||
);
|
||||
function Carousel({
|
||||
orientation = "horizontal",
|
||||
opts,
|
||||
setApi,
|
||||
plugins,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & CarouselProps) {
|
||||
const [carouselRef, api] = useEmblaCarousel(
|
||||
{
|
||||
...opts,
|
||||
axis: orientation === "horizontal" ? "x" : "y",
|
||||
},
|
||||
plugins
|
||||
)
|
||||
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
||||
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
||||
|
||||
const [local, others] = splitProps(props, [
|
||||
'orientation',
|
||||
'opts',
|
||||
'setApi',
|
||||
'plugins',
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||
if (!api) return
|
||||
setCanScrollPrev(api.canScrollPrev())
|
||||
setCanScrollNext(api.canScrollNext())
|
||||
}, [])
|
||||
|
||||
const [carouselRef, api] = createEmblaCarousel(
|
||||
() => ({
|
||||
...local.opts,
|
||||
axis: local.orientation === 'horizontal' ? 'x' : 'y',
|
||||
}),
|
||||
() => (local.plugins === undefined ? [] : local.plugins)
|
||||
);
|
||||
const [canScrollPrev, setCanScrollPrev] = createSignal(false);
|
||||
const [canScrollNext, setCanScrollNext] = createSignal(false);
|
||||
const scrollPrev = React.useCallback(() => {
|
||||
api?.scrollPrev()
|
||||
}, [api])
|
||||
|
||||
const onSelect = (api: NonNullable<ReturnType<CarouselApi>>) => {
|
||||
setCanScrollPrev(api.canScrollPrev());
|
||||
setCanScrollNext(api.canScrollNext());
|
||||
};
|
||||
const scrollNext = React.useCallback(() => {
|
||||
api?.scrollNext()
|
||||
}, [api])
|
||||
|
||||
const scrollPrev = () => {
|
||||
api()?.scrollPrev();
|
||||
};
|
||||
const handleKeyDown = React.useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === "ArrowLeft") {
|
||||
event.preventDefault()
|
||||
scrollPrev()
|
||||
} else if (event.key === "ArrowRight") {
|
||||
event.preventDefault()
|
||||
scrollNext()
|
||||
}
|
||||
},
|
||||
[scrollPrev, scrollNext]
|
||||
)
|
||||
|
||||
const scrollNext = () => {
|
||||
api()?.scrollNext();
|
||||
};
|
||||
React.useEffect(() => {
|
||||
if (!api || !setApi) return
|
||||
setApi(api)
|
||||
}, [api, setApi])
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault();
|
||||
scrollPrev();
|
||||
} else if (event.key === 'ArrowRight') {
|
||||
event.preventDefault();
|
||||
scrollNext();
|
||||
}
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
if (!api() || !local.setApi) {
|
||||
return;
|
||||
}
|
||||
local.setApi(api);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (!api()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSelect(api()!);
|
||||
api()!.on('reInit', onSelect);
|
||||
api()!.on('select', onSelect);
|
||||
React.useEffect(() => {
|
||||
if (!api) return
|
||||
onSelect(api)
|
||||
api.on("reInit", onSelect)
|
||||
api.on("select", onSelect)
|
||||
|
||||
return () => {
|
||||
api()?.off('select', onSelect);
|
||||
};
|
||||
});
|
||||
api?.off("select", onSelect)
|
||||
}
|
||||
}, [api, onSelect])
|
||||
|
||||
const value = createMemo(
|
||||
() =>
|
||||
({
|
||||
return (
|
||||
<CarouselContext.Provider
|
||||
value={{
|
||||
carouselRef,
|
||||
api,
|
||||
opts: local.opts,
|
||||
api: api,
|
||||
opts,
|
||||
orientation:
|
||||
local.orientation ||
|
||||
(local.opts?.axis === 'y' ? 'vertical' : 'horizontal'),
|
||||
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
||||
scrollPrev,
|
||||
scrollNext,
|
||||
canScrollPrev,
|
||||
canScrollNext,
|
||||
}) satisfies CarouselContextProps
|
||||
);
|
||||
|
||||
return (
|
||||
<CarouselContext.Provider value={value}>
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onKeyDown={handleKeyDown}
|
||||
class={cn('relative', local.class)}
|
||||
onKeyDownCapture={handleKeyDown}
|
||||
className={cn("relative", className)}
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
{...others}
|
||||
data-slot="carousel"
|
||||
{...props}
|
||||
>
|
||||
{local.children}
|
||||
{children}
|
||||
</div>
|
||||
</CarouselContext.Provider>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const CarouselContent: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
const { carouselRef, orientation } = useCarousel();
|
||||
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
const { carouselRef, orientation } = useCarousel()
|
||||
|
||||
return (
|
||||
<div ref={carouselRef} class="overflow-hidden">
|
||||
<div
|
||||
ref={carouselRef}
|
||||
className="overflow-hidden"
|
||||
data-slot="carousel-content"
|
||||
>
|
||||
<div
|
||||
class={cn(
|
||||
'flex',
|
||||
orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
|
||||
local.class
|
||||
className={cn(
|
||||
"flex",
|
||||
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const CarouselItem: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
const { orientation } = useCarousel();
|
||||
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||
const { orientation } = useCarousel()
|
||||
|
||||
return (
|
||||
<div
|
||||
role="group"
|
||||
aria-roledescription="slide"
|
||||
class={cn(
|
||||
'min-w-0 shrink-0 grow-0 basis-full',
|
||||
orientation === 'horizontal' ? 'pl-4' : 'pt-4',
|
||||
local.class
|
||||
data-slot="carousel-item"
|
||||
className={cn(
|
||||
"min-w-0 shrink-0 grow-0 basis-full",
|
||||
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type CarouselButtonProps = VoidProps<ButtonProps>;
|
||||
|
||||
const CarouselPrevious: Component<CarouselButtonProps> = (rawProps) => {
|
||||
const props = mergeProps<CarouselButtonProps[]>(
|
||||
{ variant: 'outline', size: 'icon' },
|
||||
rawProps
|
||||
);
|
||||
const [local, others] = splitProps(props, ['class', 'variant', 'size']);
|
||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
|
||||
function CarouselPrevious({
|
||||
className,
|
||||
variant = "outline",
|
||||
size = "icon",
|
||||
...props
|
||||
}: React.ComponentProps<typeof Button>) {
|
||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={local.variant}
|
||||
size={local.size}
|
||||
class={cn(
|
||||
'absolute size-8 touch-manipulation rounded-full',
|
||||
orientation === 'horizontal'
|
||||
? '-left-12 -translate-y-1/2 top-1/2'
|
||||
: '-top-12 -translate-x-1/2 left-1/2 rotate-90',
|
||||
local.class
|
||||
data-slot="carousel-previous"
|
||||
variant={variant}
|
||||
size={size}
|
||||
className={cn(
|
||||
"absolute size-8 rounded-full",
|
||||
orientation === "horizontal"
|
||||
? "top-1/2 -left-12 -translate-y-1/2"
|
||||
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className
|
||||
)}
|
||||
disabled={!canScrollPrev()}
|
||||
disabled={!canScrollPrev}
|
||||
onClick={scrollPrev}
|
||||
{...others}
|
||||
{...props}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12l14 0" />
|
||||
<path d="M5 12l6 6" />
|
||||
<path d="M5 12l6 -6" />
|
||||
</svg>
|
||||
<span class="sr-only">Previous slide</span>
|
||||
<ArrowLeft />
|
||||
<span className="sr-only">Previous slide</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const CarouselNext: Component<CarouselButtonProps> = (rawProps) => {
|
||||
const props = mergeProps<CarouselButtonProps[]>(
|
||||
{ variant: 'outline', size: 'icon' },
|
||||
rawProps
|
||||
);
|
||||
const [local, others] = splitProps(props, ['class', 'variant', 'size']);
|
||||
const { orientation, scrollNext, canScrollNext } = useCarousel();
|
||||
function CarouselNext({
|
||||
className,
|
||||
variant = "outline",
|
||||
size = "icon",
|
||||
...props
|
||||
}: React.ComponentProps<typeof Button>) {
|
||||
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={local.variant}
|
||||
size={local.size}
|
||||
class={cn(
|
||||
'absolute size-8 touch-manipulation rounded-full',
|
||||
orientation === 'horizontal'
|
||||
? '-right-12 -translate-y-1/2 top-1/2'
|
||||
: '-bottom-12 -translate-x-1/2 left-1/2 rotate-90',
|
||||
local.class
|
||||
data-slot="carousel-next"
|
||||
variant={variant}
|
||||
size={size}
|
||||
className={cn(
|
||||
"absolute size-8 rounded-full",
|
||||
orientation === "horizontal"
|
||||
? "top-1/2 -right-12 -translate-y-1/2"
|
||||
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className
|
||||
)}
|
||||
disabled={!canScrollNext()}
|
||||
disabled={!canScrollNext}
|
||||
onClick={scrollNext}
|
||||
{...others}
|
||||
{...props}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12l14 0" />
|
||||
<path d="M13 18l6 -6" />
|
||||
<path d="M13 6l6 6" />
|
||||
</svg>
|
||||
<span class="sr-only">Next slide</span>
|
||||
<ArrowRight />
|
||||
<span className="sr-only">Next slide</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
type CarouselApi,
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselPrevious,
|
||||
CarouselNext,
|
||||
};
|
||||
}
|
||||
|
351
apps/webui/src/components/ui/chart.tsx
Normal file
351
apps/webui/src/components/ui/chart.tsx
Normal file
@ -0,0 +1,351 @@
|
||||
import * as React from "react"
|
||||
import * as RechartsPrimitive from "recharts"
|
||||
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||
const THEMES = { light: "", dark: ".dark" } as const
|
||||
|
||||
export type ChartConfig = {
|
||||
[k in string]: {
|
||||
label?: React.ReactNode
|
||||
icon?: React.ComponentType
|
||||
} & (
|
||||
| { color?: string; theme?: never }
|
||||
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||
)
|
||||
}
|
||||
|
||||
type ChartContextProps = {
|
||||
config: ChartConfig
|
||||
}
|
||||
|
||||
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
||||
|
||||
function useChart() {
|
||||
const context = React.useContext(ChartContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useChart must be used within a <ChartContainer />")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
function ChartContainer({
|
||||
id,
|
||||
className,
|
||||
children,
|
||||
config,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
config: ChartConfig
|
||||
children: React.ComponentProps<
|
||||
typeof RechartsPrimitive.ResponsiveContainer
|
||||
>["children"]
|
||||
}) {
|
||||
const uniqueId = React.useId()
|
||||
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
||||
|
||||
return (
|
||||
<ChartContext.Provider value={{ config }}>
|
||||
<div
|
||||
data-slot="chart"
|
||||
data-chart={chartId}
|
||||
className={cn(
|
||||
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChartStyle id={chartId} config={config} />
|
||||
<RechartsPrimitive.ResponsiveContainer>
|
||||
{children}
|
||||
</RechartsPrimitive.ResponsiveContainer>
|
||||
</div>
|
||||
</ChartContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||
const colorConfig = Object.entries(config).filter(
|
||||
([, config]) => config.theme || config.color
|
||||
)
|
||||
|
||||
if (!colorConfig.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: Object.entries(THEMES)
|
||||
.map(
|
||||
([theme, prefix]) => `
|
||||
${prefix} [data-chart=${id}] {
|
||||
${colorConfig
|
||||
.map(([key, itemConfig]) => {
|
||||
const color =
|
||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||
itemConfig.color
|
||||
return color ? ` --color-${key}: ${color};` : null
|
||||
})
|
||||
.join("\n")}
|
||||
}
|
||||
`
|
||||
)
|
||||
.join("\n"),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ChartTooltip = RechartsPrimitive.Tooltip
|
||||
|
||||
function ChartTooltipContent({
|
||||
active,
|
||||
payload,
|
||||
className,
|
||||
indicator = "dot",
|
||||
hideLabel = false,
|
||||
hideIndicator = false,
|
||||
label,
|
||||
labelFormatter,
|
||||
labelClassName,
|
||||
formatter,
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||
React.ComponentProps<"div"> & {
|
||||
hideLabel?: boolean
|
||||
hideIndicator?: boolean
|
||||
indicator?: "line" | "dot" | "dashed"
|
||||
nameKey?: string
|
||||
labelKey?: string
|
||||
}) {
|
||||
const { config } = useChart()
|
||||
|
||||
const tooltipLabel = React.useMemo(() => {
|
||||
if (hideLabel || !payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [item] = payload
|
||||
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const value =
|
||||
!labelKey && typeof label === "string"
|
||||
? config[label as keyof typeof config]?.label || label
|
||||
: itemConfig?.label
|
||||
|
||||
if (labelFormatter) {
|
||||
return (
|
||||
<div className={cn("font-medium", labelClassName)}>
|
||||
{labelFormatter(value, payload)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
||||
}, [
|
||||
label,
|
||||
labelFormatter,
|
||||
payload,
|
||||
hideLabel,
|
||||
labelClassName,
|
||||
config,
|
||||
labelKey,
|
||||
])
|
||||
|
||||
if (!active || !payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const nestLabel = payload.length === 1 && indicator !== "dot"
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="text-foreground font-mono font-medium tabular-nums">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ChartLegend = RechartsPrimitive.Legend
|
||||
|
||||
function ChartLegendContent({
|
||||
className,
|
||||
hideIcon = false,
|
||||
payload,
|
||||
verticalAlign = "bottom",
|
||||
nameKey,
|
||||
}: React.ComponentProps<"div"> &
|
||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||
hideIcon?: boolean
|
||||
nameKey?: string
|
||||
}) {
|
||||
const { config } = useChart()
|
||||
|
||||
if (!payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-4",
|
||||
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Helper to extract item config from a payload.
|
||||
function getPayloadConfigFromPayload(
|
||||
config: ChartConfig,
|
||||
payload: unknown,
|
||||
key: string
|
||||
) {
|
||||
if (typeof payload !== "object" || payload === null) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const payloadPayload =
|
||||
"payload" in payload &&
|
||||
typeof payload.payload === "object" &&
|
||||
payload.payload !== null
|
||||
? payload.payload
|
||||
: undefined
|
||||
|
||||
let configLabelKey: string = key
|
||||
|
||||
if (
|
||||
key in payload &&
|
||||
typeof payload[key as keyof typeof payload] === "string"
|
||||
) {
|
||||
configLabelKey = payload[key as keyof typeof payload] as string
|
||||
} else if (
|
||||
payloadPayload &&
|
||||
key in payloadPayload &&
|
||||
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||
) {
|
||||
configLabelKey = payloadPayload[
|
||||
key as keyof typeof payloadPayload
|
||||
] as string
|
||||
}
|
||||
|
||||
return configLabelKey in config
|
||||
? config[configLabelKey]
|
||||
: config[key as keyof typeof config]
|
||||
}
|
||||
|
||||
export {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartStyle,
|
||||
}
|
@ -1,292 +0,0 @@
|
||||
import type { Component } from "solid-js"
|
||||
import { createEffect, createSignal, mergeProps, on, onCleanup, onMount } from "solid-js"
|
||||
import { unwrap } from "solid-js/store"
|
||||
|
||||
import type { Ref } from "@solid-primitives/refs"
|
||||
import { mergeRefs } from "@solid-primitives/refs"
|
||||
import type {
|
||||
ChartComponent,
|
||||
ChartData,
|
||||
ChartItem,
|
||||
ChartOptions,
|
||||
Plugin as ChartPlugin,
|
||||
ChartType,
|
||||
ChartTypeRegistry,
|
||||
TooltipModel
|
||||
} from "chart.js"
|
||||
import {
|
||||
ArcElement,
|
||||
BarController,
|
||||
BarElement,
|
||||
BubbleController,
|
||||
CategoryScale,
|
||||
Chart,
|
||||
Colors,
|
||||
DoughnutController,
|
||||
Filler,
|
||||
Legend,
|
||||
LinearScale,
|
||||
LineController,
|
||||
LineElement,
|
||||
PieController,
|
||||
PointElement,
|
||||
PolarAreaController,
|
||||
RadarController,
|
||||
RadialLinearScale,
|
||||
ScatterController,
|
||||
Tooltip
|
||||
} from "chart.js"
|
||||
|
||||
type TypedChartProps = {
|
||||
data: ChartData
|
||||
options?: ChartOptions
|
||||
plugins?: ChartPlugin[]
|
||||
ref?: Ref<HTMLCanvasElement | null>
|
||||
width?: number | undefined
|
||||
height?: number | undefined
|
||||
}
|
||||
|
||||
type ChartProps = TypedChartProps & {
|
||||
type: ChartType
|
||||
}
|
||||
|
||||
type ChartContext = {
|
||||
chart: Chart
|
||||
tooltip: TooltipModel<keyof ChartTypeRegistry>
|
||||
}
|
||||
|
||||
const BaseChart: Component<ChartProps> = (rawProps) => {
|
||||
const [canvasRef, setCanvasRef] = createSignal<HTMLCanvasElement | null>()
|
||||
const [chart, setChart] = createSignal<Chart>()
|
||||
|
||||
const props = mergeProps(
|
||||
{
|
||||
width: 512,
|
||||
height: 512,
|
||||
options: { responsive: true } as ChartOptions,
|
||||
plugins: [] as ChartPlugin[]
|
||||
},
|
||||
rawProps
|
||||
)
|
||||
|
||||
const init = () => {
|
||||
const ctx = canvasRef()?.getContext("2d") as ChartItem
|
||||
const config = unwrap(props)
|
||||
const chart = new Chart(ctx, {
|
||||
type: config.type,
|
||||
data: config.data,
|
||||
options: config.options,
|
||||
plugins: config.plugins
|
||||
})
|
||||
setChart(chart)
|
||||
}
|
||||
|
||||
onMount(() => init())
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.data,
|
||||
() => {
|
||||
chart()!.data = props.data
|
||||
chart()!.update()
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.options,
|
||||
() => {
|
||||
chart()!.options = props.options
|
||||
chart()!.update()
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
[() => props.width, () => props.height],
|
||||
() => {
|
||||
chart()!.resize(props.width, props.height)
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.type,
|
||||
() => {
|
||||
const dimensions = [chart()!.width, chart()!.height]
|
||||
chart()!.destroy()
|
||||
init()
|
||||
chart()!.resize(...dimensions)
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
onCleanup(() => {
|
||||
chart()?.destroy()
|
||||
mergeRefs(props.ref, null)
|
||||
})
|
||||
|
||||
Chart.register(Colors, Filler, Legend, Tooltip)
|
||||
return (
|
||||
<canvas
|
||||
ref={mergeRefs(props.ref, (el) => setCanvasRef(el))}
|
||||
height={props.height}
|
||||
width={props.width}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function showTooltip(context: ChartContext) {
|
||||
let el = document.getElementById("chartjs-tooltip")
|
||||
if (!el) {
|
||||
el = document.createElement("div")
|
||||
el.id = "chartjs-tooltip"
|
||||
document.body.appendChild(el)
|
||||
}
|
||||
|
||||
const model = context.tooltip
|
||||
if (model.opacity === 0 || !model.body) {
|
||||
el.style.opacity = "0"
|
||||
return
|
||||
}
|
||||
|
||||
el.className = `p-2 bg-card text-card-foreground rounded-lg border shadow-sm text-sm ${
|
||||
model.yAlign ?? `no-transform`
|
||||
}`
|
||||
|
||||
let content = ""
|
||||
|
||||
model.title.forEach((title) => {
|
||||
content += `<h3 class="font-semibold leading-none tracking-tight">${title}</h3>`
|
||||
})
|
||||
|
||||
content += `<div class="mt-1 text-muted-foreground">`
|
||||
const body = model.body.flatMap((body) => body.lines)
|
||||
body.forEach((line, i) => {
|
||||
const colors = model.labelColors[i]
|
||||
content += `
|
||||
<div class="flex items-center">
|
||||
<span class="inline-block h-2 w-2 mr-1 rounded-full border" style="background: ${colors.backgroundColor}; border-color: ${colors.borderColor}"></span>
|
||||
${line}
|
||||
</div>`
|
||||
})
|
||||
content += `</div>`
|
||||
|
||||
el.innerHTML = content
|
||||
|
||||
const pos = context.chart.canvas.getBoundingClientRect()
|
||||
el.style.opacity = "1"
|
||||
el.style.position = "absolute"
|
||||
el.style.left = `${pos.left + window.scrollX + model.caretX}px`
|
||||
el.style.top = `${pos.top + window.scrollY + model.caretY}px`
|
||||
el.style.pointerEvents = "none"
|
||||
}
|
||||
|
||||
function createTypedChart(
|
||||
type: ChartType,
|
||||
components: ChartComponent[]
|
||||
): Component<TypedChartProps> {
|
||||
const chartsWithScales: ChartType[] = ["bar", "line", "scatter"]
|
||||
const chartsWithLegends: ChartType[] = ["bar", "line"]
|
||||
|
||||
const options: ChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: chartsWithScales.includes(type)
|
||||
? {
|
||||
x: {
|
||||
border: { display: false },
|
||||
grid: { display: false }
|
||||
},
|
||||
y: {
|
||||
border: {
|
||||
dash: [3],
|
||||
dashOffset: 3,
|
||||
display: false
|
||||
},
|
||||
grid: {
|
||||
color: "hsla(240, 3.8%, 46.1%, 0.4)"
|
||||
}
|
||||
}
|
||||
}
|
||||
: {},
|
||||
plugins: {
|
||||
legend: chartsWithLegends.includes(type)
|
||||
? {
|
||||
display: true,
|
||||
align: "end",
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
boxWidth: 6,
|
||||
boxHeight: 6,
|
||||
color: "hsl(240, 3.8%, 46.1%)",
|
||||
font: { size: 14 }
|
||||
}
|
||||
}
|
||||
: { display: false },
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
external: (context) => showTooltip(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Chart.register(...components)
|
||||
return (props) => <BaseChart type={type} options={options} {...props} />
|
||||
}
|
||||
|
||||
const BarChart = /* #__PURE__ */ createTypedChart("bar", [
|
||||
BarController,
|
||||
BarElement,
|
||||
CategoryScale,
|
||||
LinearScale
|
||||
])
|
||||
const BubbleChart = /* #__PURE__ */ createTypedChart("bubble", [
|
||||
BubbleController,
|
||||
PointElement,
|
||||
LinearScale
|
||||
])
|
||||
const DonutChart = /* #__PURE__ */ createTypedChart("doughnut", [DoughnutController, ArcElement])
|
||||
const LineChart = /* #__PURE__ */ createTypedChart("line", [
|
||||
LineController,
|
||||
LineElement,
|
||||
PointElement,
|
||||
CategoryScale,
|
||||
LinearScale
|
||||
])
|
||||
const PieChart = /* #__PURE__ */ createTypedChart("pie", [PieController, ArcElement])
|
||||
const PolarAreaChart = /* #__PURE__ */ createTypedChart("polarArea", [
|
||||
PolarAreaController,
|
||||
ArcElement,
|
||||
RadialLinearScale
|
||||
])
|
||||
const RadarChart = /* #__PURE__ */ createTypedChart("radar", [
|
||||
RadarController,
|
||||
LineElement,
|
||||
PointElement,
|
||||
RadialLinearScale
|
||||
])
|
||||
const ScatterChart = /* #__PURE__ */ createTypedChart("scatter", [
|
||||
ScatterController,
|
||||
PointElement,
|
||||
LinearScale
|
||||
])
|
||||
|
||||
export {
|
||||
BaseChart as Chart,
|
||||
BarChart,
|
||||
BubbleChart,
|
||||
DonutChart,
|
||||
LineChart,
|
||||
PieChart,
|
||||
PolarAreaChart,
|
||||
RadarChart,
|
||||
ScatterChart
|
||||
}
|
@ -1,60 +1,32 @@
|
||||
import type { ValidComponent } from 'solid-js';
|
||||
import { Match, Switch, splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import * as CheckboxPrimitive from '@kobalte/core/checkbox';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { CheckIcon } from "lucide-react"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
type CheckboxRootProps<T extends ValidComponent = 'div'> =
|
||||
CheckboxPrimitive.CheckboxRootProps<T> & { class?: string | undefined };
|
||||
|
||||
const Checkbox = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, CheckboxRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as CheckboxRootProps, ['class']);
|
||||
function Checkbox({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
||||
return (
|
||||
<CheckboxPrimitive.Root
|
||||
class={cn('items-top group relative flex space-x-2', local.class)}
|
||||
{...others}
|
||||
data-slot="checkbox"
|
||||
className={cn(
|
||||
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-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 size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Input class="peer" />
|
||||
<CheckboxPrimitive.Control class="size-4 shrink-0 rounded-sm border border-primary ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 data-[checked]:border-none data-[indeterminate]:border-none data-[checked]:bg-primary data-[indeterminate]:bg-primary data-[checked]:text-primary-foreground data-[indeterminate]:text-primary-foreground">
|
||||
<CheckboxPrimitive.Indicator>
|
||||
<Switch>
|
||||
<Match when={!others.indeterminate}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>
|
||||
</Match>
|
||||
<Match when={others.indeterminate}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12l14 0" />
|
||||
</svg>
|
||||
</Match>
|
||||
</Switch>
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Control>
|
||||
<CheckboxPrimitive.Indicator
|
||||
data-slot="checkbox-indicator"
|
||||
className="flex items-center justify-center text-current transition-none"
|
||||
>
|
||||
<CheckIcon className="size-3.5" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export { Checkbox };
|
||||
export { Checkbox }
|
||||
|
@ -1,9 +1,31 @@
|
||||
import * as CollapsiblePrimitive from "@kobalte/core/collapsible"
|
||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||
|
||||
const Collapsible = CollapsiblePrimitive.Root
|
||||
function Collapsible({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
||||
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
||||
}
|
||||
|
||||
const CollapsibleTrigger = CollapsiblePrimitive.Trigger
|
||||
function CollapsibleTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
|
||||
return (
|
||||
<CollapsiblePrimitive.CollapsibleTrigger
|
||||
data-slot="collapsible-trigger"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const CollapsibleContent = CollapsiblePrimitive.Content
|
||||
function CollapsibleContent({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
|
||||
return (
|
||||
<CollapsiblePrimitive.CollapsibleContent
|
||||
data-slot="collapsible-content"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
||||
|
@ -1,203 +0,0 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { Show, splitProps } from 'solid-js';
|
||||
|
||||
import * as ComboboxPrimitive from '@kobalte/core/combobox';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
const Combobox = ComboboxPrimitive.Root;
|
||||
const ComboboxItemLabel = ComboboxPrimitive.ItemLabel;
|
||||
const ComboboxHiddenSelect = ComboboxPrimitive.HiddenSelect;
|
||||
|
||||
type ComboboxItemProps<T extends ValidComponent = 'li'> =
|
||||
ComboboxPrimitive.ComboboxItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const ComboboxItem = <T extends ValidComponent = 'li'>(
|
||||
props: PolymorphicProps<T, ComboboxItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ComboboxItemProps, ['class']);
|
||||
return (
|
||||
<ComboboxPrimitive.Item
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center justify-between rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ComboboxItemIndicatorProps<T extends ValidComponent = 'div'> =
|
||||
ComboboxPrimitive.ComboboxItemIndicatorProps<T> & {
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const ComboboxItemIndicator = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ComboboxItemIndicatorProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ComboboxItemIndicatorProps, [
|
||||
'children',
|
||||
]);
|
||||
return (
|
||||
<ComboboxPrimitive.ItemIndicator {...others}>
|
||||
<Show
|
||||
when={local.children}
|
||||
fallback={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
{(children) => children()}
|
||||
</Show>
|
||||
</ComboboxPrimitive.ItemIndicator>
|
||||
);
|
||||
};
|
||||
|
||||
type ComboboxSectionProps<T extends ValidComponent = 'li'> =
|
||||
ComboboxPrimitive.ComboboxSectionProps<T> & { class?: string | undefined };
|
||||
|
||||
const ComboboxSection = <T extends ValidComponent = 'li'>(
|
||||
props: PolymorphicProps<T, ComboboxSectionProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ComboboxSectionProps, ['class']);
|
||||
return (
|
||||
<ComboboxPrimitive.Section
|
||||
class={cn(
|
||||
'overflow-hidden p-1 px-2 py-1.5 font-medium text-muted-foreground text-xs ',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ComboboxControlProps<
|
||||
U,
|
||||
T extends ValidComponent = 'div',
|
||||
> = ComboboxPrimitive.ComboboxControlProps<U, T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const ComboboxControl = <T, U extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<U, ComboboxControlProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ComboboxControlProps<T>, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<ComboboxPrimitive.Control
|
||||
class={cn('flex h-10 items-center rounded-md border px-3', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ComboboxInputProps<T extends ValidComponent = 'input'> =
|
||||
ComboboxPrimitive.ComboboxInputProps<T> & { class?: string | undefined };
|
||||
|
||||
const ComboboxInput = <T extends ValidComponent = 'input'>(
|
||||
props: PolymorphicProps<T, ComboboxInputProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ComboboxInputProps, ['class']);
|
||||
return (
|
||||
<ComboboxPrimitive.Input
|
||||
class={cn(
|
||||
'flex size-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ComboboxTriggerProps<T extends ValidComponent = 'button'> =
|
||||
ComboboxPrimitive.ComboboxTriggerProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const ComboboxTrigger = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, ComboboxTriggerProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ComboboxTriggerProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
return (
|
||||
<ComboboxPrimitive.Trigger
|
||||
class={cn('size-4 opacity-50', local.class)}
|
||||
{...others}
|
||||
>
|
||||
<ComboboxPrimitive.Icon>
|
||||
<Show
|
||||
when={local.children}
|
||||
fallback={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M8 9l4 -4l4 4" />
|
||||
<path d="M16 15l-4 4l-4 -4" />
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
{(children) => children()}
|
||||
</Show>
|
||||
</ComboboxPrimitive.Icon>
|
||||
</ComboboxPrimitive.Trigger>
|
||||
);
|
||||
};
|
||||
|
||||
type ComboboxContentProps<T extends ValidComponent = 'div'> =
|
||||
ComboboxPrimitive.ComboboxContentProps<T> & { class?: string | undefined };
|
||||
|
||||
const ComboboxContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ComboboxContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ComboboxContentProps, ['class']);
|
||||
return (
|
||||
<ComboboxPrimitive.Portal>
|
||||
<ComboboxPrimitive.Content
|
||||
class={cn(
|
||||
'fade-in-80 relative z-50 min-w-32 animate-in overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<ComboboxPrimitive.Listbox class="m-0 p-1" />
|
||||
</ComboboxPrimitive.Content>
|
||||
</ComboboxPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
Combobox,
|
||||
ComboboxItem,
|
||||
ComboboxItemLabel,
|
||||
ComboboxItemIndicator,
|
||||
ComboboxSection,
|
||||
ComboboxControl,
|
||||
ComboboxTrigger,
|
||||
ComboboxInput,
|
||||
ComboboxHiddenSelect,
|
||||
ComboboxContent,
|
||||
};
|
@ -1,163 +1,168 @@
|
||||
import type {
|
||||
Component,
|
||||
ComponentProps,
|
||||
ParentProps,
|
||||
VoidProps,
|
||||
} from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import type { DialogRootProps } from '@kobalte/core/dialog';
|
||||
import * as CommandPrimitive from 'cmdk-solid';
|
||||
import * as React from "react"
|
||||
import { Command as CommandPrimitive } from "cmdk"
|
||||
import { SearchIcon } from "lucide-react"
|
||||
|
||||
import { Dialog, DialogContent } from '~/components/ui/dialog';
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
const Command: Component<ParentProps<CommandPrimitive.CommandRootProps>> = (
|
||||
props
|
||||
) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
import { cn } from "@/styles/utils"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
|
||||
function Command({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive>) {
|
||||
return (
|
||||
<CommandPrimitive.CommandRoot
|
||||
class={cn(
|
||||
'flex size-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground blur-none',
|
||||
local.class
|
||||
<CommandPrimitive
|
||||
data-slot="command"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandDialog: Component<ParentProps<DialogRootProps>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['children']);
|
||||
)
|
||||
}
|
||||
|
||||
function CommandDialog({
|
||||
title = "Command Palette",
|
||||
description = "Search for a command to run...",
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Dialog> & {
|
||||
title?: string
|
||||
description?: string
|
||||
}) {
|
||||
return (
|
||||
<Dialog {...others}>
|
||||
<DialogContent class="overflow-hidden p-0">
|
||||
<Command class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:size-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:size-5">
|
||||
{local.children}
|
||||
<Dialog {...props}>
|
||||
<DialogHeader className="sr-only">
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogContent className="overflow-hidden p-0">
|
||||
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||
{children}
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandInput: Component<VoidProps<CommandPrimitive.CommandInputProps>> = (
|
||||
props
|
||||
) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
)
|
||||
}
|
||||
|
||||
function CommandInput({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||
return (
|
||||
<div class="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-2 size-4 shrink-0 opacity-50"
|
||||
>
|
||||
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" />
|
||||
<path d="M21 21l-6 -6" />
|
||||
</svg>
|
||||
<CommandPrimitive.CommandInput
|
||||
class={cn(
|
||||
'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
|
||||
local.class
|
||||
<div
|
||||
data-slot="command-input-wrapper"
|
||||
className="flex h-9 items-center gap-2 border-b px-3"
|
||||
>
|
||||
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
data-slot="command-input"
|
||||
className={cn(
|
||||
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandList: Component<ParentProps<CommandPrimitive.CommandListProps>> = (
|
||||
props
|
||||
) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
)
|
||||
}
|
||||
|
||||
function CommandList({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
||||
return (
|
||||
<CommandPrimitive.CommandList
|
||||
class={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandEmpty: Component<
|
||||
ParentProps<CommandPrimitive.CommandEmptyProps>
|
||||
> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<CommandPrimitive.CommandEmpty
|
||||
class={cn('py-6 text-center text-sm', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandGroup: Component<
|
||||
ParentProps<CommandPrimitive.CommandGroupProps>
|
||||
> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
|
||||
return (
|
||||
<CommandPrimitive.CommandGroup
|
||||
class={cn(
|
||||
'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:text-xs',
|
||||
local.class
|
||||
<CommandPrimitive.List
|
||||
data-slot="command-list"
|
||||
className={cn(
|
||||
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandSeparator: Component<
|
||||
VoidProps<CommandPrimitive.CommandSeparatorProps>
|
||||
> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
)
|
||||
}
|
||||
|
||||
function CommandEmpty({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||
return (
|
||||
<CommandPrimitive.CommandSeparator
|
||||
class={cn('h-px bg-border', local.class)}
|
||||
{...others}
|
||||
<CommandPrimitive.Empty
|
||||
data-slot="command-empty"
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandItem: Component<ParentProps<CommandPrimitive.CommandItemProps>> = (
|
||||
props
|
||||
) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
)
|
||||
}
|
||||
|
||||
function CommandGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
||||
return (
|
||||
<CommandPrimitive.CommandItem
|
||||
cmdk-item=""
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',
|
||||
local.class
|
||||
<CommandPrimitive.Group
|
||||
data-slot="command-group"
|
||||
className={cn(
|
||||
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const CommandShortcut: Component<ComponentProps<'span'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function CommandSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
||||
return (
|
||||
<CommandPrimitive.Separator
|
||||
data-slot="command-separator"
|
||||
className={cn("bg-border -mx-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||
return (
|
||||
<CommandPrimitive.Item
|
||||
data-slot="command-item"
|
||||
className={cn(
|
||||
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
class={cn(
|
||||
'ml-auto text-muted-foreground text-xs tracking-widest',
|
||||
local.class
|
||||
data-slot="command-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Command,
|
||||
@ -169,4 +174,4 @@ export {
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandSeparator,
|
||||
};
|
||||
}
|
||||
|
@ -1,273 +1,252 @@
|
||||
import type { Component, ComponentProps, JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import * as ContextMenuPrimitive from '@kobalte/core/context-menu';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as React from "react"
|
||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
|
||||
const ContextMenuPortal = ContextMenuPrimitive.Portal;
|
||||
const ContextMenuSub = ContextMenuPrimitive.Sub;
|
||||
const ContextMenuGroup = ContextMenuPrimitive.Group;
|
||||
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
|
||||
function ContextMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
|
||||
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
|
||||
}
|
||||
|
||||
const ContextMenu: Component<ContextMenuPrimitive.ContextMenuRootProps> = (
|
||||
props
|
||||
) => {
|
||||
return <ContextMenuPrimitive.Root gutter={4} {...props} />;
|
||||
};
|
||||
function ContextMenuTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuContentProps<T extends ValidComponent = 'div'> =
|
||||
ContextMenuPrimitive.ContextMenuContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
function ContextMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
const ContextMenuContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ContextMenuContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ContextMenuContentProps, [
|
||||
'class',
|
||||
]);
|
||||
function ContextMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
|
||||
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
|
||||
}
|
||||
|
||||
function ContextMenuRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.RadioGroup
|
||||
data-slot="context-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.SubTrigger
|
||||
data-slot="context-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto" />
|
||||
</ContextMenuPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.SubContent
|
||||
data-slot="context-menu-sub-content"
|
||||
className={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-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Portal>
|
||||
<ContextMenuPrimitive.Content
|
||||
class={cn(
|
||||
'z-50 min-w-32 origin-[var(--kb-menu-content-transform-origin)] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
|
||||
local.class
|
||||
data-slot="context-menu-content"
|
||||
className={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-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
</ContextMenuPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuItemProps<T extends ValidComponent = 'div'> =
|
||||
ContextMenuPrimitive.ContextMenuItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const ContextMenuItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ContextMenuItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ContextMenuItemProps, ['class']);
|
||||
function ContextMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: "default" | "destructive"
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Item
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
local.class
|
||||
data-slot="context-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const ContextMenuShortcut: Component<ComponentProps<'span'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<span
|
||||
class={cn('ml-auto text-xs tracking-widest opacity-60', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ContextMenuSeparatorProps<T extends ValidComponent = 'hr'> =
|
||||
ContextMenuPrimitive.ContextMenuSeparatorProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const ContextMenuSeparator = <T extends ValidComponent = 'hr'>(
|
||||
props: PolymorphicProps<T, ContextMenuSeparatorProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ContextMenuSeparatorProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<ContextMenuPrimitive.Separator
|
||||
class={cn('-mx-1 my-1 h-px bg-muted', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ContextMenuSubTriggerProps<T extends ValidComponent = 'div'> =
|
||||
ContextMenuPrimitive.ContextMenuSubTriggerProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const ContextMenuSubTrigger = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ContextMenuSubTriggerProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ContextMenuSubTriggerProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
return (
|
||||
<ContextMenuPrimitive.SubTrigger
|
||||
class={cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
{local.children}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="ml-auto size-4"
|
||||
>
|
||||
<path d="M9 6l6 6l-6 6" />
|
||||
</svg>
|
||||
</ContextMenuPrimitive.SubTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
type ContextMenuSubContentProps<T extends ValidComponent = 'div'> =
|
||||
ContextMenuPrimitive.ContextMenuSubContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const ContextMenuSubContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ContextMenuSubContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ContextMenuSubContentProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<ContextMenuPrimitive.SubContent
|
||||
class={cn(
|
||||
'z-50 min-w-32 origin-[var(--kb-menu-content-transform-origin)] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ContextMenuCheckboxItemProps<T extends ValidComponent = 'div'> =
|
||||
ContextMenuPrimitive.ContextMenuCheckboxItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const ContextMenuCheckboxItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ContextMenuCheckboxItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ContextMenuCheckboxItemProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function ContextMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.CheckboxItem
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
local.class
|
||||
data-slot="context-menu-checkbox-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-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",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>
|
||||
<CheckIcon className="size-4" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{local.children}
|
||||
{children}
|
||||
</ContextMenuPrimitive.CheckboxItem>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type ContextMenuGroupLabelProps<T extends ValidComponent = 'span'> =
|
||||
ContextMenuPrimitive.ContextMenuGroupLabelProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const ContextMenuGroupLabel = <T extends ValidComponent = 'span'>(
|
||||
props: PolymorphicProps<T, ContextMenuGroupLabelProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ContextMenuGroupLabelProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<ContextMenuPrimitive.GroupLabel
|
||||
class={cn('px-2 py-1.5 font-semibold text-sm', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ContextMenuRadioItemProps<T extends ValidComponent = 'div'> =
|
||||
ContextMenuPrimitive.ContextMenuRadioItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const ContextMenuRadioItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ContextMenuRadioItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ContextMenuRadioItemProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function ContextMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.RadioItem
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
local.class
|
||||
data-slot="context-menu-radio-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-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",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
>
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-2 fill-current"
|
||||
>
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
||||
</svg>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{local.children}
|
||||
{children}
|
||||
</ContextMenuPrimitive.RadioItem>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Label
|
||||
data-slot="context-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Separator
|
||||
data-slot="context-menu-separator"
|
||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="context-menu-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
ContextMenu,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuPortal,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuGroup,
|
||||
ContextMenuGroupLabel,
|
||||
ContextMenuRadioGroup,
|
||||
ContextMenuRadioItem,
|
||||
};
|
||||
ContextMenuLabel,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuGroup,
|
||||
ContextMenuPortal,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuRadioGroup,
|
||||
}
|
||||
|
@ -1,301 +0,0 @@
|
||||
import { Show, children, splitProps } from 'solid-js';
|
||||
|
||||
import { DatePicker as DatePickerPrimitive } from '@ark-ui/solid';
|
||||
|
||||
import { buttonVariants } from '~/components/ui/button';
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
const DatePicker = DatePickerPrimitive.Root;
|
||||
const DatePickerLabel = DatePickerPrimitive.Label;
|
||||
const DatePickerContext = DatePickerPrimitive.Context;
|
||||
const DatePickerTableHead = DatePickerPrimitive.TableHead;
|
||||
const DatePickerTableBody = DatePickerPrimitive.TableBody;
|
||||
const DatePickerYearSelect = DatePickerPrimitive.YearSelect;
|
||||
const DatePickerMonthSelect = DatePickerPrimitive.MonthSelect;
|
||||
const DatePickerPositioner = DatePickerPrimitive.Positioner;
|
||||
|
||||
const DatePickerControl = (props: DatePickerPrimitive.ControlProps) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.Control
|
||||
class={cn('inline-flex items-center gap-1', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerInput = (props: DatePickerPrimitive.InputProps) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.Input
|
||||
class={cn(
|
||||
'h-9 w-full rounded-md border border-border bg-background px-3 py-1 text-sm shadow-sm transition-shadow placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerTrigger = (props: DatePickerPrimitive.TriggerProps) => {
|
||||
const [local, others] = splitProps(props, ['class', 'children']);
|
||||
|
||||
// prevents rendering children twice
|
||||
const resolvedChildren = children(() => local.children);
|
||||
const hasChildren = () => resolvedChildren.toArray().length !== 0;
|
||||
|
||||
return (
|
||||
<DatePickerPrimitive.Trigger
|
||||
class={cn(
|
||||
'flex min-h-9 min-w-9 items-center justify-center rounded-md border border-border bg-background transition-[box-shadow,background-color] hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:size-4',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<Show when={!hasChildren()} fallback={resolvedChildren()}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12z" />
|
||||
<path d="M16 3v4" />
|
||||
<path d="M8 3v4" />
|
||||
<path d="M4 11h16" />
|
||||
<path d="M11 15h1" />
|
||||
<path d="M12 15v3" />
|
||||
<title>Calendar</title>
|
||||
</svg>
|
||||
</Show>
|
||||
</DatePickerPrimitive.Trigger>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerContent = (props: DatePickerPrimitive.ContentProps) => {
|
||||
const [local, others] = splitProps(props, ['class', 'children']);
|
||||
|
||||
return (
|
||||
<DatePickerPrimitive.Content
|
||||
class={cn(
|
||||
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 z-50 rounded-md border bg-popover p-3 text-popover-foreground shadow-md outline-none data-[state=closed]:animate-out data-[state=open]:animate-in',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
{local.children}
|
||||
</DatePickerPrimitive.Content>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerView = (props: DatePickerPrimitive.ViewProps) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.View
|
||||
class={cn('space-y-4', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerViewControl = (props: DatePickerPrimitive.ViewControlProps) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.ViewControl
|
||||
class={cn('flex items-center justify-between gap-4', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerPrevTrigger = (props: DatePickerPrimitive.PrevTriggerProps) => {
|
||||
const [local, others] = splitProps(props, ['class', 'children']);
|
||||
|
||||
// prevents rendering children twice
|
||||
const resolvedChildren = children(() => local.children);
|
||||
const hasChildren = () => resolvedChildren.toArray().length !== 0;
|
||||
|
||||
return (
|
||||
<DatePickerPrimitive.PrevTrigger
|
||||
class={cn(
|
||||
buttonVariants({
|
||||
variant: 'outline',
|
||||
}),
|
||||
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<Show when={!hasChildren()} fallback={resolvedChildren()}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M15 6l-6 6l6 6" />
|
||||
<title>Previous</title>
|
||||
</svg>
|
||||
</Show>
|
||||
</DatePickerPrimitive.PrevTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerNextTrigger = (props: DatePickerPrimitive.NextTriggerProps) => {
|
||||
const [local, others] = splitProps(props, ['class', 'children']);
|
||||
|
||||
// prevents rendering children twice
|
||||
const resolvedChildren = children(() => local.children);
|
||||
const hasChildren = () => resolvedChildren.toArray().length !== 0;
|
||||
|
||||
return (
|
||||
<DatePickerPrimitive.NextTrigger
|
||||
class={cn(
|
||||
buttonVariants({
|
||||
variant: 'outline',
|
||||
}),
|
||||
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<Show when={!hasChildren()} fallback={resolvedChildren()}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M9 6l6 6l-6 6" />
|
||||
<title>Next</title>
|
||||
</svg>
|
||||
</Show>
|
||||
</DatePickerPrimitive.NextTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerViewTrigger = (props: DatePickerPrimitive.ViewTriggerProps) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.ViewTrigger
|
||||
class={cn(buttonVariants({ variant: 'ghost' }), 'h-7', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerRangeText = (props: DatePickerPrimitive.RangeTextProps) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.RangeText
|
||||
class={cn('font-medium text-sm', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerTable = (props: DatePickerPrimitive.TableProps) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.Table
|
||||
class={cn('w-full border-collapse space-y-1', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerTableRow = (props: DatePickerPrimitive.TableRowProps) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.TableRow
|
||||
class={cn('mt-2 flex w-full', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerTableHeader = (props: DatePickerPrimitive.TableHeaderProps) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.TableHeader
|
||||
class={cn(
|
||||
'w-8 flex-1 font-normal text-[0.8rem] text-muted-foreground',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerTableCell = (props: DatePickerPrimitive.TableCellProps) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.TableCell
|
||||
class={cn(
|
||||
'flex-1 p-0 text-center text-sm has-[[data-range-end]]:rounded-r-md has-[[data-range-start]]:rounded-l-md has-[[data-in-range]]:bg-accent has-[[data-outside-range][data-in-range]]:bg-accent/50 has-[[data-in-range]]:last-of-type:rounded-r-md has-[[data-in-range]]:first-of-type:rounded-l-md',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePickerTableCellTrigger = (
|
||||
props: DatePickerPrimitive.TableCellTriggerProps
|
||||
) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<DatePickerPrimitive.TableCellTrigger
|
||||
class={cn(
|
||||
buttonVariants({ variant: 'ghost' }),
|
||||
'size-8 w-full p-0 font-normal data-[selected]:opacity-100',
|
||||
'data-[today]:bg-accent data-[today]:text-accent-foreground',
|
||||
'[&:is([data-today][data-selected])]:bg-primary [&:is([data-today][data-selected])]:text-primary-foreground',
|
||||
'data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground',
|
||||
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
|
||||
'data-[outside-range]:text-muted-foreground data-[outside-range]:opacity-50',
|
||||
'[&:is([data-outside-range][data-in-range])]:bg-accent/50 [&:is([data-outside-range][data-in-range])]:text-muted-foreground [&:is([data-outside-range][data-in-range])]:opacity-30',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
DatePicker,
|
||||
DatePickerLabel,
|
||||
DatePickerControl,
|
||||
DatePickerInput,
|
||||
DatePickerTrigger,
|
||||
DatePickerContent,
|
||||
DatePickerView,
|
||||
DatePickerViewControl,
|
||||
DatePickerPrevTrigger,
|
||||
DatePickerNextTrigger,
|
||||
DatePickerViewTrigger,
|
||||
DatePickerRangeText,
|
||||
DatePickerContext,
|
||||
DatePickerTable,
|
||||
DatePickerTableHead,
|
||||
DatePickerTableBody,
|
||||
DatePickerTableRow,
|
||||
DatePickerTableHeader,
|
||||
DatePickerTableCell,
|
||||
DatePickerTableCellTrigger,
|
||||
DatePickerYearSelect,
|
||||
DatePickerMonthSelect,
|
||||
DatePickerPositioner,
|
||||
};
|
@ -1,75 +0,0 @@
|
||||
import {
|
||||
type Component,
|
||||
type ComponentProps,
|
||||
Show,
|
||||
mergeProps,
|
||||
splitProps,
|
||||
} from 'solid-js';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
type DeltaBarProps = ComponentProps<'div'> & {
|
||||
value: number;
|
||||
isIncreasePositive?: boolean;
|
||||
};
|
||||
|
||||
const DeltaBar: Component<DeltaBarProps> = (rawProps) => {
|
||||
const props = mergeProps(
|
||||
{
|
||||
isIncreasePositive: true,
|
||||
},
|
||||
rawProps
|
||||
);
|
||||
const [local, others] = splitProps(props, [
|
||||
'value',
|
||||
'isIncreasePositive',
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'relative flex h-2 w-full items-center rounded-full bg-secondary',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<div class="flex h-full w-1/2 justify-end">
|
||||
<Show when={local.value < 0}>
|
||||
<div
|
||||
class={cn(
|
||||
'rounded-l-full',
|
||||
(local.value > 0 && local.isIncreasePositive) ||
|
||||
(local.value < 0 && !local.isIncreasePositive)
|
||||
? 'bg-success-foreground'
|
||||
: 'bg-error-foreground'
|
||||
)}
|
||||
style={{ width: `${Math.abs(local.value)}%` }}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
<div
|
||||
class={cn(
|
||||
'z-10 h-4 w-1 rounded-full bg-primary ring-2 ring-background'
|
||||
)}
|
||||
/>
|
||||
<div class="flex h-full w-1/2 justify-start">
|
||||
<Show when={local.value > 0}>
|
||||
<div
|
||||
class={cn(
|
||||
'rounded-r-full',
|
||||
(local.value > 0 && local.isIncreasePositive) ||
|
||||
(local.value < 0 && !local.isIncreasePositive)
|
||||
? 'bg-success-foreground'
|
||||
: 'bg-error-foreground'
|
||||
)}
|
||||
style={{ width: `${Math.abs(local.value)}%` }}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export type { DeltaBarProps };
|
||||
export { DeltaBar };
|
@ -1,157 +1,133 @@
|
||||
import type { Component, ComponentProps, JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
|
||||
import * as DialogPrimitive from '@kobalte/core/dialog';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
function Dialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||
}
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||
}
|
||||
|
||||
const DialogPortal: Component<DialogPrimitive.DialogPortalProps> = (props) => {
|
||||
const [, rest] = splitProps(props, ['children']);
|
||||
return (
|
||||
<DialogPrimitive.Portal {...rest}>
|
||||
<div class="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
|
||||
{props.children}
|
||||
</div>
|
||||
</DialogPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
function DialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||
}
|
||||
|
||||
type DialogOverlayProps<T extends ValidComponent = 'div'> =
|
||||
DialogPrimitive.DialogOverlayProps<T> & { class?: string | undefined };
|
||||
function DialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||
}
|
||||
|
||||
const DialogOverlay = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, DialogOverlayProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DialogOverlayProps, ['class']);
|
||||
function DialogOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||
return (
|
||||
<DialogPrimitive.Overlay
|
||||
class={cn(
|
||||
'data-[closed]:fade-out-0 data-[expanded]:fade-in-0 fixed inset-0 z-50 bg-background/80 data-[closed]:animate-out data-[expanded]:animate-in',
|
||||
props.class
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type DialogContentProps<T extends ValidComponent = 'div'> =
|
||||
DialogPrimitive.DialogContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const DialogContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, DialogContentProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DialogContentProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function DialogContent({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
|
||||
return (
|
||||
<DialogPortal>
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
class={cn(
|
||||
'-translate-x-1/2 -translate-y-1/2 data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95 data-[closed]:slide-out-to-left-1/2 data-[closed]:slide-out-to-top-[48%] data-[expanded]:slide-in-from-left-1/2 data-[expanded]:slide-in-from-top-[48%] fixed top-1/2 left-1/2 z-50 grid max-h-screen w-full max-w-lg gap-4 overflow-y-auto border bg-background p-6 shadow-lg duration-200 data-[closed]:animate-out data-[expanded]:animate-in sm:rounded-lg',
|
||||
props.class
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
{...props}
|
||||
>
|
||||
{props.children}
|
||||
<DialogPrimitive.CloseButton class="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[expanded]:bg-accent data-[expanded]:text-muted-foreground">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M18 6l-12 12" />
|
||||
<path d="M6 6l12 12" />
|
||||
</svg>
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogPrimitive.CloseButton>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const DialogHeader: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [, rest] = splitProps(props, ['class']);
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'flex flex-col space-y-1.5 text-center sm:text-left',
|
||||
props.class
|
||||
)}
|
||||
{...rest}
|
||||
data-slot="dialog-header"
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const DialogFooter: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [, rest] = splitProps(props, ['class']);
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
props.class
|
||||
data-slot="dialog-footer"
|
||||
className={cn(
|
||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type DialogTitleProps<T extends ValidComponent = 'h2'> =
|
||||
DialogPrimitive.DialogTitleProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const DialogTitle = <T extends ValidComponent = 'h2'>(
|
||||
props: PolymorphicProps<T, DialogTitleProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DialogTitleProps, ['class']);
|
||||
function DialogTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
class={cn(
|
||||
'font-semibold text-lg leading-none tracking-tight',
|
||||
props.class
|
||||
)}
|
||||
{...rest}
|
||||
data-slot="dialog-title"
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type DialogDescriptionProps<T extends ValidComponent = 'p'> =
|
||||
DialogPrimitive.DialogDescriptionProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const DialogDescription = <T extends ValidComponent = 'p'>(
|
||||
props: PolymorphicProps<T, DialogDescriptionProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DialogDescriptionProps, ['class']);
|
||||
function DialogDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
class={cn('text-muted-foreground text-sm', props.class)}
|
||||
{...rest}
|
||||
data-slot="dialog-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
};
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
}
|
||||
|
@ -1,129 +1,120 @@
|
||||
import type { Component, ComponentProps, JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import { Drawer as DrawerPrimitive } from "vaul"
|
||||
|
||||
import type {
|
||||
ContentProps,
|
||||
DescriptionProps,
|
||||
DynamicProps,
|
||||
LabelProps,
|
||||
OverlayProps,
|
||||
} from '@corvu/drawer';
|
||||
import DrawerPrimitive from '@corvu/drawer';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
function Drawer({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
||||
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
|
||||
}
|
||||
|
||||
const Drawer = DrawerPrimitive;
|
||||
function DrawerTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
||||
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
|
||||
}
|
||||
|
||||
const DrawerTrigger = DrawerPrimitive.Trigger;
|
||||
function DrawerPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
||||
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
|
||||
}
|
||||
|
||||
const DrawerPortal = DrawerPrimitive.Portal;
|
||||
function DrawerClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
||||
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
|
||||
}
|
||||
|
||||
const DrawerClose = DrawerPrimitive.Close;
|
||||
|
||||
type DrawerOverlayProps<T extends ValidComponent = 'div'> = OverlayProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
const DrawerOverlay = <T extends ValidComponent = 'div'>(
|
||||
props: DynamicProps<T, DrawerOverlayProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DrawerOverlayProps, ['class']);
|
||||
const drawerContext = DrawerPrimitive.useContext();
|
||||
function DrawerOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
||||
return (
|
||||
<DrawerPrimitive.Overlay
|
||||
class={cn(
|
||||
'fixed inset-0 z-50 data-[transitioning]:transition-colors data-[transitioning]:duration-300',
|
||||
props.class
|
||||
data-slot="drawer-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
'background-color': `rgb(0 0 0 / ${0.8 * drawerContext.openPercentage()})`,
|
||||
}}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type DrawerContentProps<T extends ValidComponent = 'div'> = ContentProps<T> & {
|
||||
class?: string;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const DrawerContent = <T extends ValidComponent = 'div'>(
|
||||
props: DynamicProps<T, DrawerContentProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DrawerContentProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function DrawerContent({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
|
||||
return (
|
||||
<DrawerPortal>
|
||||
<DrawerPortal data-slot="drawer-portal">
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
class={cn(
|
||||
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background after:absolute after:inset-x-0 after:top-full after:h-1/2 after:bg-inherit data-[transitioning]:transition-transform data-[transitioning]:duration-300 md:select-none',
|
||||
props.class
|
||||
data-slot="drawer-content"
|
||||
className={cn(
|
||||
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
|
||||
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
|
||||
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
|
||||
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
||||
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
{...props}
|
||||
>
|
||||
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||
{props.children}
|
||||
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
||||
{children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const DrawerHeader: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [, rest] = splitProps(props, ['class']);
|
||||
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
class={cn('grid gap-1.5 p-4 text-center sm:text-left', props.class)}
|
||||
{...rest}
|
||||
data-slot="drawer-header"
|
||||
className={cn("flex flex-col gap-1.5 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const DrawerFooter: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [, rest] = splitProps(props, ['class']);
|
||||
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div class={cn('t-auto flex flex-col gap-2 p-4', props.class)} {...rest} />
|
||||
);
|
||||
};
|
||||
|
||||
type DrawerTitleProps<T extends ValidComponent = 'div'> = LabelProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
const DrawerTitle = <T extends ValidComponent = 'div'>(
|
||||
props: DynamicProps<T, DrawerTitleProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DrawerTitleProps, ['class']);
|
||||
return (
|
||||
<DrawerPrimitive.Label
|
||||
class={cn(
|
||||
'font-semibold text-lg leading-none tracking-tight',
|
||||
props.class
|
||||
)}
|
||||
{...rest}
|
||||
<div
|
||||
data-slot="drawer-footer"
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type DrawerDescriptionProps<T extends ValidComponent = 'div'> =
|
||||
DescriptionProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
function DrawerTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
||||
return (
|
||||
<DrawerPrimitive.Title
|
||||
data-slot="drawer-title"
|
||||
className={cn("text-foreground font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const DrawerDescription = <T extends ValidComponent = 'div'>(
|
||||
props: DynamicProps<T, DrawerDescriptionProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DrawerDescriptionProps, ['class']);
|
||||
function DrawerDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
||||
return (
|
||||
<DrawerPrimitive.Description
|
||||
class={cn('text-muted-foreground text-sm', props.class)}
|
||||
{...rest}
|
||||
data-slot="drawer-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Drawer,
|
||||
@ -136,4 +127,4 @@ export {
|
||||
DrawerFooter,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
};
|
||||
}
|
||||
|
@ -1,282 +1,257 @@
|
||||
import type { Component, ComponentProps, JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import * as DropdownMenuPrimitive from '@kobalte/core/dropdown-menu';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||
function DropdownMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
||||
}
|
||||
|
||||
const DropdownMenu: Component<DropdownMenuPrimitive.DropdownMenuRootProps> = (
|
||||
props
|
||||
) => {
|
||||
return <DropdownMenuPrimitive.Root gutter={4} {...props} />;
|
||||
};
|
||||
function DropdownMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuContentProps<T extends ValidComponent = 'div'> =
|
||||
DropdownMenuPrimitive.DropdownMenuContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
function DropdownMenuTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Trigger
|
||||
data-slot="dropdown-menu-trigger"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const DropdownMenuContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, DropdownMenuContentProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DropdownMenuContentProps, ['class']);
|
||||
function DropdownMenuContent({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
class={cn(
|
||||
'z-50 min-w-32 origin-[var(--kb-menu-content-transform-origin)] animate-content-hide overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:animate-content-show',
|
||||
props.class
|
||||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
className={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-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuItemProps<T extends ValidComponent = 'div'> =
|
||||
DropdownMenuPrimitive.DropdownMenuItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
function DropdownMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
const DropdownMenuItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, DropdownMenuItemProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DropdownMenuItemProps, ['class']);
|
||||
function DropdownMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: "default" | "destructive"
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const DropdownMenuShortcut: Component<ComponentProps<'span'>> = (props) => {
|
||||
const [, rest] = splitProps(props, ['class']);
|
||||
return (
|
||||
<span
|
||||
class={cn('ml-auto text-xs tracking-widest opacity-60', props.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DropdownMenuLabel: Component<
|
||||
ComponentProps<'div'> & { inset?: boolean }
|
||||
> = (props) => {
|
||||
const [, rest] = splitProps(props, ['class', 'inset']);
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'px-2 py-1.5 font-semibold text-sm',
|
||||
props.inset && 'pl-8',
|
||||
props.class
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuSeparatorProps<T extends ValidComponent = 'hr'> =
|
||||
DropdownMenuPrimitive.DropdownMenuSeparatorProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const DropdownMenuSeparator = <T extends ValidComponent = 'hr'>(
|
||||
props: PolymorphicProps<T, DropdownMenuSeparatorProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DropdownMenuSeparatorProps, ['class']);
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
class={cn('-mx-1 my-1 h-px bg-muted', props.class)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuSubTriggerProps<T extends ValidComponent = 'div'> =
|
||||
DropdownMenuPrimitive.DropdownMenuSubTriggerProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const DropdownMenuSubTrigger = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, DropdownMenuSubTriggerProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DropdownMenuSubTriggerProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
class={cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
|
||||
props.class
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{props.children}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="ml-auto size-4"
|
||||
>
|
||||
<path d="M9 6l6 6l-6 6" />
|
||||
</svg>
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuSubContentProps<T extends ValidComponent = 'div'> =
|
||||
DropdownMenuPrimitive.DropdownMenuSubContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const DropdownMenuSubContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, DropdownMenuSubContentProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DropdownMenuSubContentProps, ['class']);
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
class={cn(
|
||||
'z-50 min-w-32 origin-[var(--kb-menu-content-transform-origin)] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
|
||||
props.class
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuCheckboxItemProps<T extends ValidComponent = 'div'> =
|
||||
DropdownMenuPrimitive.DropdownMenuCheckboxItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const DropdownMenuCheckboxItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, DropdownMenuCheckboxItemProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DropdownMenuCheckboxItemProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function DropdownMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-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",
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>
|
||||
<CheckIcon className="size-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{props.children}
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuGroupLabelProps<T extends ValidComponent = 'span'> =
|
||||
DropdownMenuPrimitive.DropdownMenuGroupLabelProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const DropdownMenuGroupLabel = <T extends ValidComponent = 'span'>(
|
||||
props: PolymorphicProps<T, DropdownMenuGroupLabelProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DropdownMenuGroupLabelProps, ['class']);
|
||||
function DropdownMenuRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.GroupLabel
|
||||
class={cn('px-2 py-1.5 font-semibold text-sm', props.class)}
|
||||
{...rest}
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type DropdownMenuRadioItemProps<T extends ValidComponent = 'div'> =
|
||||
DropdownMenuPrimitive.DropdownMenuRadioItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const DropdownMenuRadioItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, DropdownMenuRadioItemProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as DropdownMenuRadioItemProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function DropdownMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
props.class
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-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",
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
{...props}
|
||||
>
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-2 fill-current"
|
||||
>
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
||||
</svg>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{props.children}
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Label
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
data-slot="dropdown-menu-separator"
|
||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
className={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-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuGroupLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
};
|
||||
}
|
||||
|
@ -1,76 +0,0 @@
|
||||
import type { Component, ComponentProps } from 'solid-js';
|
||||
import { mergeProps, splitProps } from 'solid-js';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
type JustifyContent =
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'center'
|
||||
| 'between'
|
||||
| 'around'
|
||||
| 'evenly';
|
||||
type AlignItems = 'start' | 'end' | 'center' | 'baseline' | 'stretch';
|
||||
type FlexDirection = 'row' | 'col' | 'row-reverse' | 'col-reverse';
|
||||
|
||||
type FlexProps = ComponentProps<'div'> & {
|
||||
flexDirection?: FlexDirection;
|
||||
justifyContent?: JustifyContent;
|
||||
alignItems?: AlignItems;
|
||||
};
|
||||
|
||||
const Flex: Component<FlexProps> = (rawProps) => {
|
||||
const props = mergeProps(
|
||||
{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'between',
|
||||
alignItems: 'center',
|
||||
} satisfies FlexProps,
|
||||
rawProps
|
||||
);
|
||||
const [local, others] = splitProps(props, [
|
||||
'flexDirection',
|
||||
'justifyContent',
|
||||
'alignItems',
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'flex w-full',
|
||||
flexDirectionClassNames[local.flexDirection],
|
||||
justifyContentClassNames[local.justifyContent],
|
||||
alignItemsClassNames[local.alignItems],
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Flex };
|
||||
|
||||
const justifyContentClassNames: { [key in JustifyContent]: string } = {
|
||||
start: 'justify-start',
|
||||
end: 'justify-end',
|
||||
center: 'justify-center',
|
||||
between: 'justify-between',
|
||||
around: 'justify-around',
|
||||
evenly: 'justify-evenly',
|
||||
};
|
||||
|
||||
const alignItemsClassNames: { [key in AlignItems]: string } = {
|
||||
start: 'items-start',
|
||||
end: 'items-end',
|
||||
center: 'items-center',
|
||||
baseline: 'items-baseline',
|
||||
stretch: 'items-stretch',
|
||||
};
|
||||
|
||||
const flexDirectionClassNames: { [key in FlexDirection]: string } = {
|
||||
row: 'flex-row',
|
||||
col: 'flex-col',
|
||||
'row-reverse': 'flex-row-reverse',
|
||||
'col-reverse': 'flex-col-reverse',
|
||||
};
|
165
apps/webui/src/components/ui/form.tsx
Normal file
165
apps/webui/src/components/ui/form.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
useFormState,
|
||||
type ControllerProps,
|
||||
type FieldPath,
|
||||
type FieldValues,
|
||||
} from "react-hook-form"
|
||||
|
||||
import { cn } from "@/styles/utils"
|
||||
import { Label } from "@/components/ui/label"
|
||||
|
||||
const Form = FormProvider
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName
|
||||
}
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
)
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext)
|
||||
const itemContext = React.useContext(FormItemContext)
|
||||
const { getFieldState } = useFormContext()
|
||||
const formState = useFormState({ name: fieldContext.name })
|
||||
const fieldState = getFieldState(fieldContext.name, formState)
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
}
|
||||
|
||||
const { id } = itemContext
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
}
|
||||
}
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string
|
||||
}
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
)
|
||||
|
||||
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div
|
||||
data-slot="form-item"
|
||||
className={cn("grid gap-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function FormLabel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
const { error, formItemId } = useFormField()
|
||||
|
||||
return (
|
||||
<Label
|
||||
data-slot="form-label"
|
||||
data-error={!!error}
|
||||
className={cn("data-[error=true]:text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||
|
||||
return (
|
||||
<Slot
|
||||
data-slot="form-control"
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||
const { formDescriptionId } = useFormField()
|
||||
|
||||
return (
|
||||
<p
|
||||
data-slot="form-description"
|
||||
id={formDescriptionId}
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
||||
const { error, formMessageId } = useFormField()
|
||||
const body = error ? String(error?.message ?? "") : props.children
|
||||
|
||||
if (!body) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
data-slot="form-message"
|
||||
id={formMessageId}
|
||||
className={cn("text-destructive text-sm", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
}
|
@ -1,200 +0,0 @@
|
||||
import type { Component, ComponentProps } from 'solid-js';
|
||||
import { mergeProps, splitProps } from 'solid-js';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
type Cols = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||
type Span = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
|
||||
|
||||
type GridProps = ComponentProps<'div'> & {
|
||||
cols?: Cols;
|
||||
colsSm?: Cols;
|
||||
colsMd?: Cols;
|
||||
colsLg?: Cols;
|
||||
};
|
||||
|
||||
const Grid: Component<GridProps> = (rawProps) => {
|
||||
const props = mergeProps({ cols: 1 } satisfies GridProps, rawProps);
|
||||
const [local, others] = splitProps(props, [
|
||||
'cols',
|
||||
'colsSm',
|
||||
'colsMd',
|
||||
'colsLg',
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'grid',
|
||||
gridCols[local.cols],
|
||||
local.colsSm && gridColsSm[local.colsSm],
|
||||
local.colsMd && gridColsMd[local.colsMd],
|
||||
local.colsLg && gridColsLg[local.colsLg],
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ColProps = ComponentProps<'div'> & {
|
||||
span?: Span;
|
||||
spanSm?: Span;
|
||||
spanMd?: Span;
|
||||
spanLg?: Span;
|
||||
};
|
||||
|
||||
const Col: Component<ColProps> = (rawProps) => {
|
||||
const props = mergeProps({ span: 1 as Span }, rawProps);
|
||||
const [local, others] = splitProps(props, [
|
||||
'span',
|
||||
'spanSm',
|
||||
'spanMd',
|
||||
'spanLg',
|
||||
'class',
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
colSpan[local.span],
|
||||
local.spanSm && colSpanSm[local.spanSm],
|
||||
local.spanMd && colSpanMd[local.spanMd],
|
||||
local.spanLg && colSpanLg[local.spanLg],
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Grid, Col };
|
||||
|
||||
const gridCols: { [key in Cols]: string } = {
|
||||
0: 'grid-cols-none',
|
||||
1: 'grid-cols-1',
|
||||
2: 'grid-cols-2',
|
||||
3: 'grid-cols-3',
|
||||
4: 'grid-cols-4',
|
||||
5: 'grid-cols-5',
|
||||
6: 'grid-cols-6',
|
||||
7: 'grid-cols-7',
|
||||
8: 'grid-cols-8',
|
||||
9: 'grid-cols-9',
|
||||
10: 'grid-cols-10',
|
||||
11: 'grid-cols-11',
|
||||
12: 'grid-cols-12',
|
||||
};
|
||||
|
||||
const gridColsSm: { [key in Cols]: string } = {
|
||||
0: 'sm:grid-cols-none',
|
||||
1: 'sm:grid-cols-1',
|
||||
2: 'sm:grid-cols-2',
|
||||
3: 'sm:grid-cols-3',
|
||||
4: 'sm:grid-cols-4',
|
||||
5: 'sm:grid-cols-5',
|
||||
6: 'sm:grid-cols-6',
|
||||
7: 'sm:grid-cols-7',
|
||||
8: 'sm:grid-cols-8',
|
||||
9: 'sm:grid-cols-9',
|
||||
10: 'sm:grid-cols-10',
|
||||
11: 'sm:grid-cols-11',
|
||||
12: 'sm:grid-cols-12',
|
||||
};
|
||||
|
||||
const gridColsMd: { [key in Cols]: string } = {
|
||||
0: 'md:grid-cols-none',
|
||||
1: 'md:grid-cols-1',
|
||||
2: 'md:grid-cols-2',
|
||||
3: 'md:grid-cols-3',
|
||||
4: 'md:grid-cols-4',
|
||||
5: 'md:grid-cols-5',
|
||||
6: 'md:grid-cols-6',
|
||||
7: 'md:grid-cols-7',
|
||||
8: 'md:grid-cols-8',
|
||||
9: 'md:grid-cols-9',
|
||||
10: 'md:grid-cols-10',
|
||||
11: 'md:grid-cols-11',
|
||||
12: 'md:grid-cols-12',
|
||||
};
|
||||
|
||||
const gridColsLg: { [key in Cols]: string } = {
|
||||
0: 'lg:grid-cols-none',
|
||||
1: 'lg:grid-cols-1',
|
||||
2: 'lg:grid-cols-2',
|
||||
3: 'lg:grid-cols-3',
|
||||
4: 'lg:grid-cols-4',
|
||||
5: 'lg:grid-cols-5',
|
||||
6: 'lg:grid-cols-6',
|
||||
7: 'lg:grid-cols-7',
|
||||
8: 'lg:grid-cols-8',
|
||||
9: 'lg:grid-cols-9',
|
||||
10: 'lg:grid-cols-10',
|
||||
11: 'lg:grid-cols-11',
|
||||
12: 'lg:grid-cols-12',
|
||||
};
|
||||
|
||||
const colSpan: { [key in Span]: string } = {
|
||||
1: 'col-span-1',
|
||||
2: 'col-span-2',
|
||||
3: 'col-span-3',
|
||||
4: 'col-span-4',
|
||||
5: 'col-span-5',
|
||||
6: 'col-span-6',
|
||||
7: 'col-span-7',
|
||||
8: 'col-span-8',
|
||||
9: 'col-span-9',
|
||||
10: 'col-span-10',
|
||||
11: 'col-span-11',
|
||||
12: 'col-span-12',
|
||||
13: 'col-span-13',
|
||||
};
|
||||
|
||||
const colSpanSm: { [key in Span]: string } = {
|
||||
1: 'sm:col-span-1',
|
||||
2: 'sm:col-span-2',
|
||||
3: 'sm:col-span-3',
|
||||
4: 'sm:col-span-4',
|
||||
5: 'sm:col-span-5',
|
||||
6: 'sm:col-span-6',
|
||||
7: 'sm:col-span-7',
|
||||
8: 'sm:col-span-8',
|
||||
9: 'sm:col-span-9',
|
||||
10: 'sm:col-span-10',
|
||||
11: 'sm:col-span-11',
|
||||
12: 'sm:col-span-12',
|
||||
13: 'sm:col-span-13',
|
||||
};
|
||||
|
||||
const colSpanMd: { [key in Span]: string } = {
|
||||
1: 'md:col-span-1',
|
||||
2: 'md:col-span-2',
|
||||
3: 'md:col-span-3',
|
||||
4: 'md:col-span-4',
|
||||
5: 'md:col-span-5',
|
||||
6: 'md:col-span-6',
|
||||
7: 'md:col-span-7',
|
||||
8: 'md:col-span-8',
|
||||
9: 'md:col-span-9',
|
||||
10: 'md:col-span-10',
|
||||
11: 'md:col-span-11',
|
||||
12: 'md:col-span-12',
|
||||
13: 'md:col-span-13',
|
||||
};
|
||||
|
||||
const colSpanLg: { [key in Span]: string } = {
|
||||
1: 'lg:col-span-1',
|
||||
2: 'lg:col-span-2',
|
||||
3: 'lg:col-span-3',
|
||||
4: 'lg:col-span-4',
|
||||
5: 'lg:col-span-5',
|
||||
6: 'lg:col-span-6',
|
||||
7: 'lg:col-span-7',
|
||||
8: 'lg:col-span-8',
|
||||
9: 'lg:col-span-9',
|
||||
10: 'lg:col-span-10',
|
||||
11: 'lg:col-span-11',
|
||||
12: 'lg:col-span-12',
|
||||
13: 'lg:col-span-13',
|
||||
};
|
@ -1,37 +1,42 @@
|
||||
import type { Component, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
|
||||
|
||||
import * as HoverCardPrimitive from '@kobalte/core/hover-card';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
function HoverCard({
|
||||
...props
|
||||
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
|
||||
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
|
||||
}
|
||||
|
||||
const HoverCardTrigger = HoverCardPrimitive.Trigger;
|
||||
|
||||
const HoverCard: Component<HoverCardPrimitive.HoverCardRootProps> = (props) => {
|
||||
return <HoverCardPrimitive.Root gutter={4} {...props} />;
|
||||
};
|
||||
|
||||
type HoverCardContentProps<T extends ValidComponent = 'div'> =
|
||||
HoverCardPrimitive.HoverCardContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const HoverCardContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, HoverCardContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as HoverCardContentProps, ['class']);
|
||||
function HoverCardTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
|
||||
return (
|
||||
<HoverCardPrimitive.Portal>
|
||||
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function HoverCardContent({
|
||||
className,
|
||||
align = "center",
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
|
||||
return (
|
||||
<HoverCardPrimitive.Portal data-slot="hover-card-portal">
|
||||
<HoverCardPrimitive.Content
|
||||
class={cn(
|
||||
'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-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=closed]:animate-out data-[state=open]:animate-in',
|
||||
local.class
|
||||
data-slot="hover-card-content"
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={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-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
</HoverCardPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export { HoverCard, HoverCardTrigger, HoverCardContent };
|
||||
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
||||
|
9
apps/webui/src/components/ui/image.tsx
Normal file
9
apps/webui/src/components/ui/image.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
|
||||
export type ImageProps = Omit<ComponentProps<'img'>, 'alt'> &
|
||||
Required<Pick<ComponentProps<'img'>, 'alt'>>;
|
||||
|
||||
export const Image = (props: ImageProps) => {
|
||||
// biome-ignore lint/nursery/noImgElement: <explanation>
|
||||
return <img {...props} alt={props.alt} />;
|
||||
};
|
77
apps/webui/src/components/ui/input-otp.tsx
Normal file
77
apps/webui/src/components/ui/input-otp.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { OTPInput, OTPInputContext } from "input-otp"
|
||||
import { MinusIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
function InputOTP({
|
||||
className,
|
||||
containerClassName,
|
||||
...props
|
||||
}: React.ComponentProps<typeof OTPInput> & {
|
||||
containerClassName?: string
|
||||
}) {
|
||||
return (
|
||||
<OTPInput
|
||||
data-slot="input-otp"
|
||||
containerClassName={cn(
|
||||
"flex items-center gap-2 has-disabled:opacity-50",
|
||||
containerClassName
|
||||
)}
|
||||
className={cn("disabled:cursor-not-allowed", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="input-otp-group"
|
||||
className={cn("flex items-center", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function InputOTPSlot({
|
||||
index,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
index: number
|
||||
}) {
|
||||
const inputOTPContext = React.useContext(OTPInputContext)
|
||||
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="input-otp-slot"
|
||||
data-active={isActive}
|
||||
className={cn(
|
||||
"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{char}
|
||||
{hasFakeCaret && (
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div data-slot="input-otp-separator" role="separator" {...props}>
|
||||
<MinusIcon />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
|
21
apps/webui/src/components/ui/input.tsx
Normal file
21
apps/webui/src/components/ui/input.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
data-slot="input"
|
||||
className={cn(
|
||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-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
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Input }
|
@ -1,19 +1,24 @@
|
||||
import type { Component, ComponentProps } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
|
||||
const Label: Component<ComponentProps<'label'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
function Label({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
return (
|
||||
<label
|
||||
class={cn(
|
||||
'font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
local.class
|
||||
<LabelPrimitive.Root
|
||||
data-slot="label"
|
||||
className={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
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export { Label };
|
||||
export { Label }
|
||||
|
@ -1,346 +1,274 @@
|
||||
import type { Component, ComponentProps, JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import * as MenubarPrimitive from "@radix-ui/react-menubar"
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||
|
||||
import * as MenubarPrimitive from '@kobalte/core/menubar';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
const MenubarGroup = MenubarPrimitive.Group;
|
||||
const MenubarPortal = MenubarPrimitive.Portal;
|
||||
const MenubarSub = MenubarPrimitive.Sub;
|
||||
const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
|
||||
|
||||
type MenubarRootProps<T extends ValidComponent = 'div'> =
|
||||
MenubarPrimitive.MenubarRootProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const Menubar = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, MenubarRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarRootProps, ['class']);
|
||||
function Menubar({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.Root>) {
|
||||
return (
|
||||
<MenubarPrimitive.Root
|
||||
class={cn(
|
||||
'flex h-10 items-center space-x-1 rounded-md border bg-background p-1',
|
||||
local.class
|
||||
data-slot="menubar"
|
||||
className={cn(
|
||||
"bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const MenubarMenu: Component<MenubarPrimitive.MenubarMenuProps> = (props) => {
|
||||
return <MenubarPrimitive.Menu gutter={8} {...props} />;
|
||||
};
|
||||
function MenubarMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
|
||||
return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />
|
||||
}
|
||||
|
||||
type MenubarTriggerProps<T extends ValidComponent = 'button'> =
|
||||
MenubarPrimitive.MenubarTriggerProps<T> & { class?: string | undefined };
|
||||
function MenubarGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
|
||||
return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />
|
||||
}
|
||||
|
||||
const MenubarTrigger = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, MenubarTriggerProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarTriggerProps, ['class']);
|
||||
function MenubarPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
|
||||
return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />
|
||||
}
|
||||
|
||||
function MenubarRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function MenubarTrigger({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
|
||||
return (
|
||||
<MenubarPrimitive.Trigger
|
||||
class={cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-3 py-1.5 font-medium text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
|
||||
local.class
|
||||
data-slot="menubar-trigger"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type MenubarContentProps<T extends ValidComponent = 'div'> =
|
||||
MenubarPrimitive.MenubarContentProps<T> & { class?: string | undefined };
|
||||
|
||||
const MenubarContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, MenubarContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarContentProps, ['class']);
|
||||
function MenubarContent({
|
||||
className,
|
||||
align = "start",
|
||||
alignOffset = -4,
|
||||
sideOffset = 8,
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.Content>) {
|
||||
return (
|
||||
<MenubarPrimitive.Portal>
|
||||
<MenubarPortal>
|
||||
<MenubarPrimitive.Content
|
||||
class={cn(
|
||||
'z-50 min-w-48 origin-[var(--kb-menu-content-transform-origin)] animate-content-hide overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:animate-content-show',
|
||||
local.class
|
||||
data-slot="menubar-content"
|
||||
align={align}
|
||||
alignOffset={alignOffset}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in 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-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
</MenubarPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
</MenubarPortal>
|
||||
)
|
||||
}
|
||||
|
||||
type MenubarSubTriggerProps<T extends ValidComponent = 'div'> =
|
||||
MenubarPrimitive.MenubarSubTriggerProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
inset?: boolean;
|
||||
};
|
||||
|
||||
const MenubarSubTrigger = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, MenubarSubTriggerProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarSubTriggerProps, [
|
||||
'class',
|
||||
'children',
|
||||
'inset',
|
||||
]);
|
||||
return (
|
||||
<MenubarPrimitive.SubTrigger
|
||||
class={cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
|
||||
local.inset && 'pl-8',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
{local.children}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="ml-auto size-4"
|
||||
>
|
||||
<path d="M9 6l6 6l-6 6" />
|
||||
</svg>
|
||||
</MenubarPrimitive.SubTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
type MenubarSubContentProps<T extends ValidComponent = 'div'> =
|
||||
MenubarPrimitive.MenubarSubContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const MenubarSubContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, MenubarSubContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarSubContentProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<MenubarPrimitive.Portal>
|
||||
<MenubarPrimitive.SubContent
|
||||
class={cn(
|
||||
'z-50 min-w-32 origin-[var(--kb-menu-content-transform-origin)] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
</MenubarPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
|
||||
type MenubarItemProps<T extends ValidComponent = 'div'> =
|
||||
MenubarPrimitive.MenubarItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
inset?: boolean;
|
||||
};
|
||||
|
||||
const MenubarItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, MenubarItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarItemProps, [
|
||||
'class',
|
||||
'inset',
|
||||
]);
|
||||
function MenubarItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: "default" | "destructive"
|
||||
}) {
|
||||
return (
|
||||
<MenubarPrimitive.Item
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
local.inset && 'pl-8',
|
||||
local.class
|
||||
data-slot="menubar-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type MenubarCheckboxItemProps<T extends ValidComponent = 'div'> =
|
||||
MenubarPrimitive.MenubarCheckboxItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const MenubarCheckboxItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, MenubarCheckboxItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarCheckboxItemProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function MenubarCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<MenubarPrimitive.CheckboxItem
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
local.class
|
||||
data-slot="menubar-checkbox-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-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",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<MenubarPrimitive.ItemIndicator>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>
|
||||
<CheckIcon className="size-4" />
|
||||
</MenubarPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{local.children}
|
||||
{children}
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type MenubarRadioItemProps<T extends ValidComponent = 'div'> =
|
||||
MenubarPrimitive.MenubarRadioItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const MenubarRadioItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, MenubarRadioItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarRadioItemProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function MenubarRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {
|
||||
return (
|
||||
<MenubarPrimitive.RadioItem
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
local.class
|
||||
data-slot="menubar-radio-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-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",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
>
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<MenubarPrimitive.ItemIndicator>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-2 fill-current"
|
||||
>
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
||||
</svg>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</MenubarPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{local.children}
|
||||
{children}
|
||||
</MenubarPrimitive.RadioItem>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type MenubarItemLabelProps<T extends ValidComponent = 'div'> =
|
||||
MenubarPrimitive.MenubarItemLabelProps<T> & {
|
||||
class?: string | undefined;
|
||||
inset?: boolean;
|
||||
};
|
||||
|
||||
const MenubarItemLabel = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, MenubarItemLabelProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarItemLabelProps, [
|
||||
'class',
|
||||
'inset',
|
||||
]);
|
||||
function MenubarLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<MenubarPrimitive.ItemLabel
|
||||
class={cn(
|
||||
'px-2 py-1.5 font-semibold text-sm',
|
||||
local.inset && 'pl-8',
|
||||
local.class
|
||||
<MenubarPrimitive.Label
|
||||
data-slot="menubar-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type MenubarGroupLabelProps<T extends ValidComponent = 'span'> =
|
||||
MenubarPrimitive.MenubarGroupLabelProps<T> & {
|
||||
class?: string | undefined;
|
||||
inset?: boolean;
|
||||
};
|
||||
|
||||
const MenubarGroupLabel = <T extends ValidComponent = 'span'>(
|
||||
props: PolymorphicProps<T, MenubarGroupLabelProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarGroupLabelProps, [
|
||||
'class',
|
||||
'inset',
|
||||
]);
|
||||
return (
|
||||
<MenubarPrimitive.GroupLabel
|
||||
class={cn(
|
||||
'px-2 py-1.5 font-semibold text-sm',
|
||||
local.inset && 'pl-8',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type MenubarSeparatorProps<T extends ValidComponent = 'hr'> =
|
||||
MenubarPrimitive.MenubarSeparatorProps<T> & { class?: string | undefined };
|
||||
|
||||
const MenubarSeparator = <T extends ValidComponent = 'hr'>(
|
||||
props: PolymorphicProps<T, MenubarSeparatorProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as MenubarSeparatorProps, ['class']);
|
||||
function MenubarSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
|
||||
return (
|
||||
<MenubarPrimitive.Separator
|
||||
class={cn('-mx-1 my-1 h-px bg-muted', local.class)}
|
||||
{...others}
|
||||
data-slot="menubar-separator"
|
||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const MenubarShortcut: Component<ComponentProps<'span'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function MenubarShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
class={cn(
|
||||
'ml-auto text-muted-foreground text-xs tracking-widest',
|
||||
local.class
|
||||
data-slot="menubar-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
function MenubarSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
|
||||
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
|
||||
}
|
||||
|
||||
function MenubarSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<MenubarPrimitive.SubTrigger
|
||||
data-slot="menubar-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||
</MenubarPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
function MenubarSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
|
||||
return (
|
||||
<MenubarPrimitive.SubContent
|
||||
data-slot="menubar-sub-content"
|
||||
className={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-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Menubar,
|
||||
MenubarPortal,
|
||||
MenubarMenu,
|
||||
MenubarTrigger,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarGroup,
|
||||
MenubarSeparator,
|
||||
MenubarItemLabel,
|
||||
MenubarGroupLabel,
|
||||
MenubarLabel,
|
||||
MenubarItem,
|
||||
MenubarShortcut,
|
||||
MenubarCheckboxItem,
|
||||
MenubarRadioGroup,
|
||||
MenubarRadioItem,
|
||||
MenubarPortal,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarGroup,
|
||||
MenubarSub,
|
||||
MenubarShortcut,
|
||||
};
|
||||
MenubarSubTrigger,
|
||||
MenubarSubContent,
|
||||
}
|
||||
|
@ -1,209 +1,168 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||
import { cva } from "class-variance-authority"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core';
|
||||
import * as NavigationMenuPrimitive from '@kobalte/core/navigation-menu';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Menu;
|
||||
|
||||
type NavigationMenuProps<T extends ValidComponent = 'ul'> =
|
||||
NavigationMenuPrimitive.NavigationMenuRootProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const NavigationMenu = <T extends ValidComponent = 'ul'>(
|
||||
props: PolymorphicProps<T, NavigationMenuProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NavigationMenuProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function NavigationMenu({
|
||||
className,
|
||||
children,
|
||||
viewport = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
|
||||
viewport?: boolean
|
||||
}) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Root
|
||||
gutter={6}
|
||||
class={cn(
|
||||
'group/menu flex w-max flex-1 list-none items-center justify-center data-[orientation=vertical]:flex-col [&>li]:w-full',
|
||||
local.class
|
||||
data-slot="navigation-menu"
|
||||
data-viewport={viewport}
|
||||
className={cn(
|
||||
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
>
|
||||
{local.children}
|
||||
<NavigationMenuViewport />
|
||||
{children}
|
||||
{viewport && <NavigationMenuViewport />}
|
||||
</NavigationMenuPrimitive.Root>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type NavigationMenuTriggerProps<T extends ValidComponent = 'button'> =
|
||||
NavigationMenuPrimitive.NavigationMenuTriggerProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const NavigationMenuTrigger = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, NavigationMenuTriggerProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NavigationMenuTriggerProps, [
|
||||
'class',
|
||||
]);
|
||||
function NavigationMenuList({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
class={cn(
|
||||
'group/trigger inline-flex h-10 w-full items-center justify-center whitespace-nowrap rounded-md bg-background px-4 py-2 font-medium text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[expanded]:bg-accent/50',
|
||||
local.class
|
||||
<NavigationMenuPrimitive.List
|
||||
data-slot="navigation-menu-list"
|
||||
className={cn(
|
||||
"group flex flex-1 list-none items-center justify-center gap-1",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const NavigationMenuIcon = () => {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Icon aria-hidden="true">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="group-data-[orientation=vertical]/menu:-rotate-90 relative top-px ml-1 size-3 transition duration-200 group-data-[orientation=vertical]/menu:group-data-[expanded]/trigger:rotate-90 group-data-[expanded]/trigger:rotate-180"
|
||||
>
|
||||
<path d="M6 9l6 6l6 -6" />
|
||||
</svg>
|
||||
</NavigationMenuPrimitive.Icon>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type NavigationMenuViewportProps<T extends ValidComponent = 'li'> =
|
||||
NavigationMenuPrimitive.NavigationMenuViewportProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const NavigationMenuViewport = <T extends ValidComponent = 'li'>(
|
||||
props: PolymorphicProps<T, NavigationMenuViewportProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NavigationMenuViewportProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
class={cn(
|
||||
// base settings
|
||||
'pointer-events-none z-[1000] flex h-[var(--kb-navigation-menu-viewport-height)] w-[var(--kb-navigation-menu-viewport-width)] origin-[var(--kb-menu-content-transform-origin)] items-center justify-center overflow-x-clip overflow-y-visible rounded-md border bg-popover opacity-0 shadow-lg data-[expanded]:pointer-events-auto data-[orientation=vertical]:overflow-y-clip data-[orientation=vertical]:overflow-x-visible data-[expanded]:rounded-md',
|
||||
// animate
|
||||
'animate-content-hide transition-[width,height] duration-200 ease-in data-[expanded]:animate-content-show data-[expanded]:opacity-100 data-[expanded]:ease-out',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type NavigationMenuContentProps<T extends ValidComponent = 'ul'> =
|
||||
NavigationMenuPrimitive.NavigationMenuContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const NavigationMenuContent = <T extends ValidComponent = 'ul'>(
|
||||
props: PolymorphicProps<T, NavigationMenuContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NavigationMenuContentProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<NavigationMenuPrimitive.Portal>
|
||||
<NavigationMenuPrimitive.Content
|
||||
class={cn(
|
||||
// base settings
|
||||
'pointer-events-none absolute top-0 left-0 box-border p-4 focus:outline-none data-[expanded]:pointer-events-auto',
|
||||
// base animation settings
|
||||
'data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out',
|
||||
// left to right
|
||||
'data-[orientation=horizontal]:data-[motion=from-start]:slide-in-from-left-52 data-[orientation=horizontal]:data-[motion=to-end]:slide-out-to-right-52',
|
||||
// right to left
|
||||
'data-[orientation=horizontal]:data-[motion=from-end]:slide-in-from-right-52 data-[orientation=horizontal]:data-[motion=to-start]:slide-out-to-left-52',
|
||||
// top to bottom
|
||||
'data-[orientation=vertical]:data-[motion=from-start]:slide-in-from-top-52 data-[orientation=vertical]:data-[motion=to-end]:slide-out-to-bottom-52',
|
||||
//bottom to top
|
||||
'data-[orientation=vertical]:data-[motion=from-end]:slide-in-from-bottom-52 data-[orientation=vertical]:data-[motion=to-start]:slide-out-to-bottom-52',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
</NavigationMenuPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
|
||||
type NavigationMenuLinkProps<T extends ValidComponent = 'a'> =
|
||||
NavigationMenuPrimitive.NavigationMenuItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const NavigationMenuLink = <T extends ValidComponent = 'a'>(
|
||||
props: PolymorphicProps<T, NavigationMenuLinkProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NavigationMenuLinkProps, [
|
||||
'class',
|
||||
]);
|
||||
function NavigationMenuItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Item
|
||||
class={cn(
|
||||
'block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
|
||||
local.class
|
||||
data-slot="navigation-menu-item"
|
||||
className={cn("relative", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
|
||||
)
|
||||
|
||||
function NavigationMenuTrigger({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
data-slot="navigation-menu-trigger"
|
||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}{" "}
|
||||
<ChevronDownIcon
|
||||
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
)
|
||||
}
|
||||
|
||||
function NavigationMenuContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Content
|
||||
data-slot="navigation-menu-content"
|
||||
className={cn(
|
||||
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
|
||||
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type NavigationMenuLabelProps<T extends ValidComponent = 'div'> =
|
||||
NavigationMenuPrimitive.NavigationMenuItemLabelProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const NavigationMenuLabel = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, NavigationMenuLabelProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NavigationMenuLabelProps, [
|
||||
'class',
|
||||
]);
|
||||
function NavigationMenuViewport({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.ItemLabel
|
||||
class={cn('font-medium text-sm leading-none', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
<div
|
||||
className={cn(
|
||||
"absolute top-full left-0 isolate z-50 flex justify-center"
|
||||
)}
|
||||
>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
data-slot="navigation-menu-viewport"
|
||||
className={cn(
|
||||
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type NavigationMenuDescriptionProps<T extends ValidComponent = 'div'> =
|
||||
NavigationMenuPrimitive.NavigationMenuItemDescriptionProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const NavigationMenuDescription = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, NavigationMenuDescriptionProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NavigationMenuDescriptionProps, [
|
||||
'class',
|
||||
]);
|
||||
function NavigationMenuLink({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.ItemDescription
|
||||
class={cn('text-muted-foreground text-sm leading-snug', local.class)}
|
||||
{...others}
|
||||
<NavigationMenuPrimitive.Link
|
||||
data-slot="navigation-menu-link"
|
||||
className={cn(
|
||||
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
function NavigationMenuIndicator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
data-slot="navigation-menu-indicator"
|
||||
className={cn(
|
||||
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuIcon,
|
||||
NavigationMenuViewport,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuLabel,
|
||||
NavigationMenuDescription,
|
||||
};
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
navigationMenuTriggerStyle,
|
||||
}
|
||||
|
@ -1,199 +0,0 @@
|
||||
import type { Component, ComponentProps, JSX, ValidComponent } from 'solid-js';
|
||||
import { Show, splitProps } from 'solid-js';
|
||||
|
||||
import * as NumberFieldPrimitive from '@kobalte/core/number-field';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
const NumberField = NumberFieldPrimitive.Root;
|
||||
|
||||
const NumberFieldGroup: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'relative rounded-md focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type NumberFieldLabelProps<T extends ValidComponent = 'label'> =
|
||||
NumberFieldPrimitive.NumberFieldLabelProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const NumberFieldLabel = <T extends ValidComponent = 'label'>(
|
||||
props: PolymorphicProps<T, NumberFieldLabelProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NumberFieldLabelProps, ['class']);
|
||||
return (
|
||||
<NumberFieldPrimitive.Label
|
||||
class={cn(
|
||||
'font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type NumberFieldInputProps<T extends ValidComponent = 'input'> =
|
||||
NumberFieldPrimitive.NumberFieldInputProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const NumberFieldInput = <T extends ValidComponent = 'input'>(
|
||||
props: PolymorphicProps<T, NumberFieldInputProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NumberFieldInputProps, ['class']);
|
||||
return (
|
||||
<NumberFieldPrimitive.Input
|
||||
class={cn(
|
||||
'flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[invalid]:border-error-foreground data-[invalid]:text-error-foreground',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type NumberFieldIncrementTriggerProps<T extends ValidComponent = 'button'> =
|
||||
NumberFieldPrimitive.NumberFieldIncrementTriggerProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const NumberFieldIncrementTrigger = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, NumberFieldIncrementTriggerProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(
|
||||
props as NumberFieldIncrementTriggerProps,
|
||||
['class', 'children']
|
||||
);
|
||||
return (
|
||||
<NumberFieldPrimitive.IncrementTrigger
|
||||
class={cn(
|
||||
'absolute top-1 right-1 inline-flex size-4 items-center justify-center',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<Show
|
||||
when={local.children}
|
||||
fallback={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M6 15l6 -6l6 6" />
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
{(children) => children()}
|
||||
</Show>
|
||||
</NumberFieldPrimitive.IncrementTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
type NumberFieldDecrementTriggerProps<T extends ValidComponent = 'button'> =
|
||||
NumberFieldPrimitive.NumberFieldDecrementTriggerProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const NumberFieldDecrementTrigger = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, NumberFieldDecrementTriggerProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(
|
||||
props as NumberFieldDecrementTriggerProps,
|
||||
['class', 'children']
|
||||
);
|
||||
return (
|
||||
<NumberFieldPrimitive.DecrementTrigger
|
||||
class={cn(
|
||||
'absolute right-1 bottom-1 inline-flex size-4 items-center justify-center',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<Show
|
||||
when={local.children}
|
||||
fallback={
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M6 9l6 6l6 -6" />
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
{(children) => children()}
|
||||
</Show>
|
||||
</NumberFieldPrimitive.DecrementTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
type NumberFieldDescriptionProps<T extends ValidComponent = 'div'> =
|
||||
NumberFieldPrimitive.NumberFieldDescriptionProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const NumberFieldDescription = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, NumberFieldDescriptionProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NumberFieldDescriptionProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<NumberFieldPrimitive.Description
|
||||
class={cn('text-muted-foreground text-sm', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type NumberFieldErrorMessageProps<T extends ValidComponent = 'div'> =
|
||||
NumberFieldPrimitive.NumberFieldErrorMessageProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const NumberFieldErrorMessage = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, NumberFieldErrorMessageProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as NumberFieldErrorMessageProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<NumberFieldPrimitive.ErrorMessage
|
||||
class={cn('text-error-foreground text-sm', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
NumberField,
|
||||
NumberFieldGroup,
|
||||
NumberFieldLabel,
|
||||
NumberFieldInput,
|
||||
NumberFieldIncrementTrigger,
|
||||
NumberFieldDecrementTrigger,
|
||||
NumberFieldDescription,
|
||||
NumberFieldErrorMessage,
|
||||
};
|
@ -1,98 +0,0 @@
|
||||
import type { Component, ComponentProps, ValidComponent } from 'solid-js';
|
||||
import { Show, splitProps } from 'solid-js';
|
||||
|
||||
import type { DynamicProps, RootProps } from '@corvu/otp-field';
|
||||
import OtpField from '@corvu/otp-field';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
export const REGEXP_ONLY_DIGITS = '^\\d*$';
|
||||
export const REGEXP_ONLY_CHARS = '^[a-zA-Z]*$';
|
||||
export const REGEXP_ONLY_DIGITS_AND_CHARS = '^[a-zA-Z0-9]*$';
|
||||
|
||||
type OTPFieldProps<T extends ValidComponent = 'div'> = RootProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
const OTPField = <T extends ValidComponent = 'div'>(
|
||||
props: DynamicProps<T, OTPFieldProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as OTPFieldProps, ['class']);
|
||||
return (
|
||||
<OtpField
|
||||
class={cn(
|
||||
'flex items-center gap-2 disabled:cursor-not-allowed has-[:disabled]:opacity-50',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const OTPFieldInput = OtpField.Input;
|
||||
|
||||
const OTPFieldGroup: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return <div class={cn('flex items-center', local.class)} {...others} />;
|
||||
};
|
||||
|
||||
const OTPFieldSlot: Component<ComponentProps<'div'> & { index: number }> = (
|
||||
props
|
||||
) => {
|
||||
const [local, others] = splitProps(props, ['class', 'index']);
|
||||
const context = OtpField.useContext();
|
||||
const char = () => context.value()[local.index];
|
||||
const showFakeCaret = () =>
|
||||
context.value().length === local.index && context.isInserting();
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'group relative flex size-10 items-center justify-center border-input border-y border-r text-sm first:rounded-l-md first:border-l last:rounded-r-md',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<div
|
||||
class={cn(
|
||||
'absolute inset-0 z-10 transition-all group-first:rounded-l-md group-last:rounded-r-md',
|
||||
context.activeSlots().includes(local.index) &&
|
||||
'ring-2 ring-ring ring-offset-background'
|
||||
)}
|
||||
/>
|
||||
{char()}
|
||||
<Show when={showFakeCaret()}>
|
||||
<div class="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<div class="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OTPFieldSeparator: Component<ComponentProps<'div'>> = (props) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-6"
|
||||
>
|
||||
<circle cx="12.1" cy="12.1" r="1" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
OTPField,
|
||||
OTPFieldInput,
|
||||
OTPFieldGroup,
|
||||
OTPFieldSlot,
|
||||
OTPFieldSeparator,
|
||||
};
|
@ -1,193 +1,127 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { Show, splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
MoreHorizontalIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import * as PaginationPrimitive from '@kobalte/core/pagination';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import { cn } from "@/styles/utils"
|
||||
import { Button, buttonVariants } from "@/components/ui/button"
|
||||
|
||||
import { buttonVariants } from '~/components/ui/button';
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
const PaginationItems = PaginationPrimitive.Items;
|
||||
|
||||
type PaginationRootProps<T extends ValidComponent = 'nav'> =
|
||||
PaginationPrimitive.PaginationRootProps<T> & { class?: string | undefined };
|
||||
|
||||
const Pagination = <T extends ValidComponent = 'nav'>(
|
||||
props: PolymorphicProps<T, PaginationRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as PaginationRootProps, ['class']);
|
||||
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
||||
return (
|
||||
<PaginationPrimitive.Root
|
||||
class={cn(
|
||||
'[&>*]:flex [&>*]:flex-row [&>*]:items-center [&>*]:gap-1',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
<nav
|
||||
role="navigation"
|
||||
aria-label="pagination"
|
||||
data-slot="pagination"
|
||||
className={cn("mx-auto flex w-full justify-center", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type PaginationItemProps<T extends ValidComponent = 'button'> =
|
||||
PaginationPrimitive.PaginationItemProps<T> & { class?: string | undefined };
|
||||
|
||||
const PaginationItem = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, PaginationItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as PaginationItemProps, ['class']);
|
||||
function PaginationContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"ul">) {
|
||||
return (
|
||||
<PaginationPrimitive.Item
|
||||
class={cn(
|
||||
buttonVariants({
|
||||
variant: 'ghost',
|
||||
}),
|
||||
'size-10 data-[current]:border',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
<ul
|
||||
data-slot="pagination-content"
|
||||
className={cn("flex flex-row items-center gap-1", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type PaginationEllipsisProps<T extends ValidComponent = 'div'> =
|
||||
PaginationPrimitive.PaginationEllipsisProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
||||
return <li data-slot="pagination-item" {...props} />
|
||||
}
|
||||
|
||||
const PaginationEllipsis = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, PaginationEllipsisProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as PaginationEllipsisProps, [
|
||||
'class',
|
||||
]);
|
||||
type PaginationLinkProps = {
|
||||
isActive?: boolean
|
||||
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
||||
React.ComponentProps<"a">
|
||||
|
||||
function PaginationLink({
|
||||
className,
|
||||
isActive,
|
||||
size = "icon",
|
||||
...props
|
||||
}: PaginationLinkProps) {
|
||||
return (
|
||||
<PaginationPrimitive.Ellipsis
|
||||
class={cn('flex size-10 items-center justify-center', local.class)}
|
||||
{...others}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<circle cx="12" cy="12" r="1" />
|
||||
<circle cx="19" cy="12" r="1" />
|
||||
<circle cx="5" cy="12" r="1" />
|
||||
</svg>
|
||||
<span class="sr-only">More pages</span>
|
||||
</PaginationPrimitive.Ellipsis>
|
||||
);
|
||||
};
|
||||
|
||||
type PaginationPreviousProps<T extends ValidComponent = 'button'> =
|
||||
PaginationPrimitive.PaginationPreviousProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const PaginationPrevious = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, PaginationPreviousProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as PaginationPreviousProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
return (
|
||||
<PaginationPrimitive.Previous
|
||||
class={cn(
|
||||
<a
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
data-slot="pagination-link"
|
||||
data-active={isActive}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: 'ghost',
|
||||
variant: isActive ? "outline" : "ghost",
|
||||
size,
|
||||
}),
|
||||
'gap-1 pl-2.5',
|
||||
local.class
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<Show
|
||||
when={local.children}
|
||||
fallback={
|
||||
<>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M15 6l-6 6l6 6" />
|
||||
</svg>
|
||||
<span>Previous</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{(children) => children()}
|
||||
</Show>
|
||||
</PaginationPrimitive.Previous>
|
||||
);
|
||||
};
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type PaginationNextProps<T extends ValidComponent = 'button'> =
|
||||
PaginationPrimitive.PaginationNextProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const PaginationNext = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, PaginationNextProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as PaginationNextProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function PaginationPrevious({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) {
|
||||
return (
|
||||
<PaginationPrimitive.Next
|
||||
class={cn(
|
||||
buttonVariants({
|
||||
variant: 'ghost',
|
||||
}),
|
||||
'gap-1 pl-2.5',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
<PaginationLink
|
||||
aria-label="Go to previous page"
|
||||
size="default"
|
||||
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<Show
|
||||
when={local.children}
|
||||
fallback={
|
||||
<>
|
||||
<span>Next</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M9 6l6 6l-6 6" />
|
||||
</svg>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{(children) => children()}
|
||||
</Show>
|
||||
</PaginationPrimitive.Next>
|
||||
);
|
||||
};
|
||||
<ChevronLeftIcon />
|
||||
<span className="hidden sm:block">Previous</span>
|
||||
</PaginationLink>
|
||||
)
|
||||
}
|
||||
|
||||
function PaginationNext({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) {
|
||||
return (
|
||||
<PaginationLink
|
||||
aria-label="Go to next page"
|
||||
size="default"
|
||||
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<span className="hidden sm:block">Next</span>
|
||||
<ChevronRightIcon />
|
||||
</PaginationLink>
|
||||
)
|
||||
}
|
||||
|
||||
function PaginationEllipsis({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
aria-hidden
|
||||
data-slot="pagination-ellipsis"
|
||||
className={cn("flex size-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontalIcon className="size-4" />
|
||||
<span className="sr-only">More pages</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Pagination,
|
||||
PaginationItems,
|
||||
PaginationContent,
|
||||
PaginationLink,
|
||||
PaginationItem,
|
||||
PaginationEllipsis,
|
||||
PaginationPrevious,
|
||||
PaginationNext,
|
||||
};
|
||||
PaginationEllipsis,
|
||||
}
|
||||
|
@ -1,35 +1,48 @@
|
||||
import type { Component, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as PopoverPrimitive from '@kobalte/core/popover';
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||
function Popover({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
||||
}
|
||||
|
||||
const Popover: Component<PopoverPrimitive.PopoverRootProps> = (props) => {
|
||||
return <PopoverPrimitive.Root gutter={4} {...props} />;
|
||||
};
|
||||
function PopoverTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
||||
}
|
||||
|
||||
type PopoverContentProps<T extends ValidComponent = 'div'> =
|
||||
PopoverPrimitive.PopoverContentProps<T> & { class?: string | undefined };
|
||||
|
||||
const PopoverContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, PopoverContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as PopoverContentProps, ['class']);
|
||||
function PopoverContent({
|
||||
className,
|
||||
align = "center",
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
class={cn(
|
||||
'data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95 z-50 w-72 origin-[var(--kb-popover-content-transform-origin)] rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[closed]:animate-out data-[expanded]:animate-in',
|
||||
local.class
|
||||
data-slot="popover-content"
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={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-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent };
|
||||
function PopoverAnchor({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||
|
@ -1,30 +1,26 @@
|
||||
import { type LinkComponent, createLink } from '@tanstack/solid-router';
|
||||
import {
|
||||
type Component,
|
||||
type ComponentProps,
|
||||
type JSX,
|
||||
Show,
|
||||
splitProps,
|
||||
} from 'solid-js';
|
||||
import { cn } from '@/styles/utils';
|
||||
import { type LinkComponent, createLink } from '@tanstack/react-router';
|
||||
import type { AnchorHTMLAttributes, ComponentProps } from 'react';
|
||||
|
||||
type BasicLinkProps = JSX.IntrinsicElements['a'];
|
||||
export interface BasicLinkProps
|
||||
extends AnchorHTMLAttributes<HTMLAnchorElement> {}
|
||||
|
||||
const BasicLinkComponent: Component<BasicLinkProps> = (props) => (
|
||||
<a {...props}>{props.children}</a>
|
||||
);
|
||||
const BasicLinkComponent = (props: ComponentProps<'a'>) => {
|
||||
return (
|
||||
<a
|
||||
{...props}
|
||||
className={cn('block px-3 py-2 text-blue-700', props.className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const CreatedLinkComponent = createLink(BasicLinkComponent);
|
||||
|
||||
export const ProLink: LinkComponent<typeof BasicLinkComponent> = (props) => {
|
||||
const [local, other] = splitProps(props, ['href']);
|
||||
return (
|
||||
<Show
|
||||
when={!props.href}
|
||||
fallback={<BasicLinkComponent {...(other as any)} href={local.href} />}
|
||||
>
|
||||
<CreatedLinkComponent preload={'intent'} {...(other as typeof props)} />
|
||||
</Show>
|
||||
);
|
||||
if (props.href) {
|
||||
return <BasicLinkComponent {...(props as any)} />;
|
||||
}
|
||||
return <CreatedLinkComponent preload={'intent'} {...props} />;
|
||||
};
|
||||
|
||||
export type ProLinkProps = ComponentProps<typeof ProLink>;
|
||||
|
@ -1,7 +1,5 @@
|
||||
import type { Component, ComponentProps } from 'solid-js';
|
||||
import { mergeProps, splitProps } from 'solid-js';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from '@/styles/utils';
|
||||
import type { FC, HTMLAttributes } from 'react';
|
||||
|
||||
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
|
||||
@ -13,87 +11,81 @@ const sizes: Record<Size, { radius: number; strokeWidth: number }> = {
|
||||
xl: { radius: 80, strokeWidth: 10 },
|
||||
};
|
||||
|
||||
type ProgressCircleProps = ComponentProps<'div'> & {
|
||||
interface ProgressCircleProps extends HTMLAttributes<HTMLDivElement> {
|
||||
value?: number;
|
||||
size?: Size;
|
||||
radius?: number;
|
||||
strokeWidth?: number;
|
||||
showAnimation?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const ProgressCircle: Component<ProgressCircleProps> = (rawProps) => {
|
||||
const props = mergeProps(
|
||||
{ size: 'md' as Size, showAnimation: true },
|
||||
rawProps
|
||||
);
|
||||
const [local, others] = splitProps(props, [
|
||||
'class',
|
||||
'children',
|
||||
'value',
|
||||
'size',
|
||||
'radius',
|
||||
'strokeWidth',
|
||||
'showAnimation',
|
||||
]);
|
||||
|
||||
const value = () => getLimitedValue(local.value);
|
||||
const radius = () => local.radius ?? sizes[local.size].radius;
|
||||
const strokeWidth = () => local.strokeWidth ?? sizes[local.size].strokeWidth;
|
||||
const normalizedRadius = () => radius() - strokeWidth() / 2;
|
||||
const circumference = () => normalizedRadius() * 2 * Math.PI;
|
||||
const strokeDashoffset = () => (value() / 100) * circumference();
|
||||
const offset = () => circumference() - strokeDashoffset();
|
||||
const ProgressCircle: FC<ProgressCircleProps> = ({
|
||||
className,
|
||||
children,
|
||||
value = 0,
|
||||
size = 'md',
|
||||
radius,
|
||||
strokeWidth,
|
||||
showAnimation = true,
|
||||
...props
|
||||
}) => {
|
||||
const currentValue = getLimitedValue(value);
|
||||
const currentRadius = radius ?? sizes[size].radius;
|
||||
const currentStrokeWidth = strokeWidth ?? sizes[size].strokeWidth;
|
||||
const normalizedRadius = currentRadius - currentStrokeWidth / 2;
|
||||
const circumference = normalizedRadius * 2 * Math.PI;
|
||||
const strokeDashoffset = (currentValue / 100) * circumference;
|
||||
const offset = circumference - strokeDashoffset;
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn('flex flex-col items-center justify-center', local.class)}
|
||||
{...others}
|
||||
className={cn('flex flex-col items-center justify-center', className)}
|
||||
{...props}
|
||||
>
|
||||
<svg
|
||||
width={radius() * 2}
|
||||
height={radius() * 2}
|
||||
viewBox={`0 0 ${radius() * 2} ${radius() * 2}`}
|
||||
class="-rotate-90"
|
||||
width={currentRadius * 2}
|
||||
height={currentRadius * 2}
|
||||
viewBox={`0 0 ${currentRadius * 2} ${currentRadius * 2}`}
|
||||
className="-rotate-90"
|
||||
>
|
||||
<circle
|
||||
r={normalizedRadius()}
|
||||
cx={radius()}
|
||||
cy={radius()}
|
||||
stroke-width={strokeWidth()}
|
||||
r={normalizedRadius}
|
||||
cx={currentRadius}
|
||||
cy={currentRadius}
|
||||
strokeWidth={currentStrokeWidth}
|
||||
fill="transparent"
|
||||
stroke=""
|
||||
stroke-linecap="round"
|
||||
class={cn('stroke-secondary transition-colors ease-linear')}
|
||||
strokeLinecap="round"
|
||||
className={cn('stroke-secondary transition-colors ease-linear')}
|
||||
/>
|
||||
{value() >= 0 ? (
|
||||
{currentValue >= 0 && (
|
||||
<circle
|
||||
r={normalizedRadius()}
|
||||
cx={radius()}
|
||||
cy={radius()}
|
||||
stroke-width={strokeWidth()}
|
||||
stroke-dasharray={circumference() + ' ' + circumference()}
|
||||
stroke-dashoffset={offset()}
|
||||
r={normalizedRadius}
|
||||
cx={currentRadius}
|
||||
cy={currentRadius}
|
||||
strokeWidth={currentStrokeWidth}
|
||||
strokeDasharray={`${circumference} ${circumference}`}
|
||||
strokeDashoffset={offset}
|
||||
fill="transparent"
|
||||
stroke=""
|
||||
stroke-linecap="round"
|
||||
class={cn(
|
||||
strokeLinecap="round"
|
||||
className={cn(
|
||||
'stroke-primary transition-colors ease-linear',
|
||||
local.showAnimation
|
||||
? 'transition-all duration-300 ease-in-out'
|
||||
: ''
|
||||
showAnimation ? 'transition-all duration-300 ease-in-out' : ''
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
</svg>
|
||||
<div class={cn('absolute flex')}>{local.children}</div>
|
||||
<div className={cn('absolute flex')}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function getLimitedValue(input: number | undefined) {
|
||||
function getLimitedValue(input: number | undefined): number {
|
||||
if (input === undefined) {
|
||||
return 0;
|
||||
} else if (input > 100) {
|
||||
}
|
||||
if (input > 100) {
|
||||
return 100;
|
||||
}
|
||||
return input;
|
||||
|
@ -1,34 +1,29 @@
|
||||
import type { Component, JSX, ValidComponent } from "solid-js"
|
||||
import { splitProps } from "solid-js"
|
||||
import * as React from "react"
|
||||
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic"
|
||||
import * as ProgressPrimitive from "@kobalte/core/progress"
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { Label } from "~/components/ui/label"
|
||||
|
||||
type ProgressRootProps<T extends ValidComponent = "div"> =
|
||||
ProgressPrimitive.ProgressRootProps<T> & { children?: JSX.Element }
|
||||
|
||||
const Progress = <T extends ValidComponent = "div">(
|
||||
props: PolymorphicProps<T, ProgressRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ProgressRootProps, ["children"])
|
||||
function Progress({
|
||||
className,
|
||||
value,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
||||
return (
|
||||
<ProgressPrimitive.Root {...others}>
|
||||
{local.children}
|
||||
<ProgressPrimitive.Track class="relative h-2 w-full overflow-hidden rounded-full bg-secondary">
|
||||
<ProgressPrimitive.Fill class="h-full w-[var(--kb-progress-fill-width)] flex-1 bg-primary transition-all" />
|
||||
</ProgressPrimitive.Track>
|
||||
<ProgressPrimitive.Root
|
||||
data-slot="progress"
|
||||
className={cn(
|
||||
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ProgressPrimitive.Indicator
|
||||
data-slot="progress-indicator"
|
||||
className="bg-primary h-full w-full flex-1 transition-all"
|
||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const ProgressLabel: Component<ProgressPrimitive.ProgressLabelProps> = (props) => {
|
||||
return <ProgressPrimitive.Label as={Label} {...props} />
|
||||
}
|
||||
|
||||
const ProgressValueLabel: Component<ProgressPrimitive.ProgressValueLabelProps> = (props) => {
|
||||
return <ProgressPrimitive.ValueLabel as={Label} {...props} />
|
||||
}
|
||||
|
||||
export { Progress, ProgressLabel, ProgressValueLabel }
|
||||
export { Progress }
|
||||
|
@ -1,84 +1,45 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as RadioGroupPrimitive from '@kobalte/core/radio-group';
|
||||
import * as React from "react"
|
||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
||||
import { CircleIcon } from "lucide-react"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
type RadioGroupRootProps<T extends ValidComponent = 'div'> =
|
||||
RadioGroupPrimitive.RadioGroupRootProps<T> & { class?: string | undefined };
|
||||
|
||||
const RadioGroup = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, RadioGroupRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as RadioGroupRootProps, ['class']);
|
||||
function RadioGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
||||
return (
|
||||
<RadioGroupPrimitive.Root
|
||||
class={cn('grid gap-2', local.class)}
|
||||
{...others}
|
||||
data-slot="radio-group"
|
||||
className={cn("grid gap-3", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type RadioGroupItemProps<T extends ValidComponent = 'div'> =
|
||||
RadioGroupPrimitive.RadioGroupItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const RadioGroupItem = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, RadioGroupItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as RadioGroupItemProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function RadioGroupItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
||||
return (
|
||||
<RadioGroupPrimitive.Item
|
||||
class={cn('flex items-center space-x-2', local.class)}
|
||||
{...others}
|
||||
>
|
||||
<RadioGroupPrimitive.ItemInput />
|
||||
<RadioGroupPrimitive.ItemControl class="aspect-square size-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50">
|
||||
<RadioGroupPrimitive.ItemIndicator class="flex h-full items-center justify-center ">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-2.5 fill-current text-current"
|
||||
>
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
||||
</svg>
|
||||
</RadioGroupPrimitive.ItemIndicator>
|
||||
</RadioGroupPrimitive.ItemControl>
|
||||
{local.children}
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
};
|
||||
|
||||
type RadioGroupLabelProps<T extends ValidComponent = 'label'> =
|
||||
RadioGroupPrimitive.RadioGroupLabelProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const RadioGroupItemLabel = <T extends ValidComponent = 'label'>(
|
||||
props: PolymorphicProps<T, RadioGroupLabelProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as RadioGroupLabelProps, ['class']);
|
||||
return (
|
||||
<RadioGroupPrimitive.ItemLabel
|
||||
class={cn(
|
||||
'font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
local.class
|
||||
data-slot="radio-group-item"
|
||||
className={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
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator
|
||||
data-slot="radio-group-indicator"
|
||||
className="relative flex items-center justify-center"
|
||||
>
|
||||
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
export { RadioGroup, RadioGroupItem, RadioGroupItemLabel };
|
||||
export { RadioGroup, RadioGroupItem }
|
||||
|
@ -1,76 +1,54 @@
|
||||
import type { ValidComponent } from 'solid-js';
|
||||
import { Show, splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import { GripVerticalIcon } from "lucide-react"
|
||||
import * as ResizablePrimitive from "react-resizable-panels"
|
||||
|
||||
import type { DynamicProps, HandleProps, RootProps } from '@corvu/resizable';
|
||||
import ResizablePrimitive from '@corvu/resizable';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
type ResizableProps<T extends ValidComponent = 'div'> = RootProps<T> & {
|
||||
class?: string;
|
||||
};
|
||||
|
||||
const Resizable = <T extends ValidComponent = 'div'>(
|
||||
props: DynamicProps<T, ResizableProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as ResizableProps, ['class']);
|
||||
function ResizablePanelGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
|
||||
return (
|
||||
<ResizablePrimitive
|
||||
class={cn(
|
||||
'flex size-full data-[orientation=vertical]:flex-col',
|
||||
props.class
|
||||
<ResizablePrimitive.PanelGroup
|
||||
data-slot="resizable-panel-group"
|
||||
className={cn(
|
||||
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const ResizablePanel = ResizablePrimitive.Panel;
|
||||
function ResizablePanel({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
|
||||
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
|
||||
}
|
||||
|
||||
type ResizableHandleProps<T extends ValidComponent = 'button'> =
|
||||
HandleProps<T> & {
|
||||
class?: string;
|
||||
withHandle?: boolean;
|
||||
};
|
||||
|
||||
const ResizableHandle = <T extends ValidComponent = 'button'>(
|
||||
props: DynamicProps<T, ResizableHandleProps<T>>
|
||||
) => {
|
||||
const [, rest] = splitProps(props as ResizableHandleProps, [
|
||||
'class',
|
||||
'withHandle',
|
||||
]);
|
||||
function ResizableHandle({
|
||||
withHandle,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
||||
withHandle?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ResizablePrimitive.Handle
|
||||
class={cn(
|
||||
'after:-translate-x-1/2 data-[orientation=vertical]:after:-translate-y-1/2 relative flex w-px shrink-0 items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[orientation=vertical]:h-px data-[orientation=vertical]:w-full data-[orientation=vertical]:after:left-0 data-[orientation=vertical]:after:h-1 data-[orientation=vertical]:after:w-full data-[orientation=vertical]:after:translate-x-0 [&[data-orientation=vertical]>div]:rotate-90',
|
||||
props.class
|
||||
<ResizablePrimitive.PanelResizeHandle
|
||||
data-slot="resizable-handle"
|
||||
className={cn(
|
||||
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
{...props}
|
||||
>
|
||||
<Show when={props.withHandle}>
|
||||
<div class="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-2.5"
|
||||
>
|
||||
<path d="M9 5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
<path d="M9 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
<path d="M9 19m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
<path d="M15 5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
<path d="M15 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
<path d="M15 19m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
</svg>
|
||||
{withHandle && (
|
||||
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
|
||||
<GripVerticalIcon className="size-2.5" />
|
||||
</div>
|
||||
</Show>
|
||||
</ResizablePrimitive.Handle>
|
||||
);
|
||||
};
|
||||
)}
|
||||
</ResizablePrimitive.PanelResizeHandle>
|
||||
)
|
||||
}
|
||||
|
||||
export { Resizable, ResizablePanel, ResizableHandle };
|
||||
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
||||
|
58
apps/webui/src/components/ui/scroll-area.tsx
Normal file
58
apps/webui/src/components/ui/scroll-area.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
function ScrollArea({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root
|
||||
data-slot="scroll-area"
|
||||
className={cn("relative", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
data-slot="scroll-area-viewport"
|
||||
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
||||
>
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
function ScrollBar({
|
||||
className,
|
||||
orientation = "vertical",
|
||||
...props
|
||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
||||
return (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
data-slot="scroll-area-scrollbar"
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none p-px transition-colors select-none",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||
data-slot="scroll-area-thumb"
|
||||
className="bg-border relative flex-1 rounded-full"
|
||||
/>
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
)
|
||||
}
|
||||
|
||||
export { ScrollArea, ScrollBar }
|
@ -1,198 +1,183 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as SelectPrimitive from '@kobalte/core/select';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
function Select({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||
}
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
const SelectValue = SelectPrimitive.Value;
|
||||
const SelectHiddenSelect = SelectPrimitive.HiddenSelect;
|
||||
function SelectGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
||||
}
|
||||
|
||||
type SelectTriggerProps<T extends ValidComponent = 'button'> =
|
||||
SelectPrimitive.SelectTriggerProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
function SelectValue({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
||||
}
|
||||
|
||||
const SelectTrigger = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, SelectTriggerProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SelectTriggerProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function SelectTrigger({
|
||||
className,
|
||||
size = "default",
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||
size?: "sm" | "default"
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
class={cn(
|
||||
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
local.class
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={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 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
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
>
|
||||
{local.children}
|
||||
<SelectPrimitive.Icon
|
||||
as="svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4 opacity-50"
|
||||
>
|
||||
<path d="M8 9l4 -4l4 4" />
|
||||
<path d="M16 15l-4 4l-4 -4" />
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type SelectContentProps<T extends ValidComponent = 'div'> =
|
||||
SelectPrimitive.SelectContentProps<T> & { class?: string | undefined };
|
||||
|
||||
const SelectContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, SelectContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SelectContentProps, ['class']);
|
||||
function SelectContent({
|
||||
className,
|
||||
children,
|
||||
position = "popper",
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
class={cn(
|
||||
'fade-in-80 relative z-50 min-w-32 animate-in overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md',
|
||||
local.class
|
||||
data-slot="select-content"
|
||||
className={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-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||
position === "popper" &&
|
||||
"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
|
||||
)}
|
||||
{...others}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.Listbox class="m-0 p-1" />
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type SelectItemProps<T extends ValidComponent = 'li'> =
|
||||
SelectPrimitive.SelectItemProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const SelectItem = <T extends ValidComponent = 'li'>(
|
||||
props: PolymorphicProps<T, SelectItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SelectItemProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
class={cn(
|
||||
'relative mt-0 flex w-full cursor-default select-none items-center rounded-sm py-1.5 pr-8 pl-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<SelectPrimitive.ItemIndicator class="absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M5 12l5 5l10 -10" />
|
||||
</svg>
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
<SelectPrimitive.ItemLabel>{local.children}</SelectPrimitive.ItemLabel>
|
||||
</SelectPrimitive.Item>
|
||||
);
|
||||
};
|
||||
|
||||
const labelVariants = cva(
|
||||
'font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
label: 'data-[invalid]:text-destructive',
|
||||
description: 'font-normal text-muted-foreground',
|
||||
error: 'text-destructive text-xs',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'label',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type SelectLabelProps<T extends ValidComponent = 'label'> =
|
||||
SelectPrimitive.SelectLabelProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const SelectLabel = <T extends ValidComponent = 'label'>(
|
||||
props: PolymorphicProps<T, SelectLabelProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SelectLabelProps, ['class']);
|
||||
function SelectLabel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||
return (
|
||||
<SelectPrimitive.Label
|
||||
class={cn(labelVariants(), local.class)}
|
||||
{...others}
|
||||
data-slot="select-label"
|
||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type SelectDescriptionProps<T extends ValidComponent = 'div'> =
|
||||
SelectPrimitive.SelectDescriptionProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const SelectDescription = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, SelectDescriptionProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SelectDescriptionProps, [
|
||||
'class',
|
||||
]);
|
||||
function SelectItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
||||
return (
|
||||
<SelectPrimitive.Description
|
||||
class={cn(labelVariants({ variant: 'description' }), local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
<SelectPrimitive.Item
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus: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 pr-8 pl-2 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
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
type SelectErrorMessageProps<T extends ValidComponent = 'div'> =
|
||||
SelectPrimitive.SelectErrorMessageProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const SelectErrorMessage = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, SelectErrorMessageProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SelectErrorMessageProps, [
|
||||
'class',
|
||||
]);
|
||||
function SelectSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||
return (
|
||||
<SelectPrimitive.ErrorMessage
|
||||
class={cn(labelVariants({ variant: 'error' }), local.class)}
|
||||
{...others}
|
||||
<SelectPrimitive.Separator
|
||||
data-slot="select-separator"
|
||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollUpButton({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollDownButton({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectValue,
|
||||
SelectHiddenSelect,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectDescription,
|
||||
SelectErrorMessage,
|
||||
};
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
}
|
||||
|
@ -1,32 +1,28 @@
|
||||
import type { ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as SeparatorPrimitive from '@kobalte/core/separator';
|
||||
import * as React from "react"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
type SeparatorRootProps<T extends ValidComponent = 'hr'> =
|
||||
SeparatorPrimitive.SeparatorRootProps<T> & { class?: string | undefined };
|
||||
|
||||
const Separator = <T extends ValidComponent = 'hr'>(
|
||||
props: PolymorphicProps<T, SeparatorRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SeparatorRootProps, [
|
||||
'class',
|
||||
'orientation',
|
||||
]);
|
||||
function Separator({
|
||||
className,
|
||||
orientation = "horizontal",
|
||||
decorative = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||
return (
|
||||
<SeparatorPrimitive.Root
|
||||
orientation={local.orientation ?? 'horizontal'}
|
||||
class={cn(
|
||||
'shrink-0 bg-border',
|
||||
local.orientation === 'vertical' ? 'h-full w-px' : 'h-px w-full',
|
||||
local.class
|
||||
data-slot="separator-root"
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export { Separator };
|
||||
export { Separator }
|
||||
|
@ -1,188 +1,129 @@
|
||||
import type { Component, ComponentProps, JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
|
||||
import * as SheetPrimitive from '@kobalte/core/dialog';
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import { type VariantProps, cva } from 'class-variance-authority';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
||||
}
|
||||
|
||||
const Sheet = SheetPrimitive.Root;
|
||||
const SheetTrigger = SheetPrimitive.Trigger;
|
||||
const SheetClose = SheetPrimitive.CloseButton;
|
||||
function SheetTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
||||
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
||||
}
|
||||
|
||||
const portalVariants = cva('fixed inset-0 z-50 flex', {
|
||||
variants: {
|
||||
position: {
|
||||
top: 'items-start',
|
||||
bottom: 'items-end',
|
||||
left: 'justify-start',
|
||||
right: 'justify-end',
|
||||
},
|
||||
},
|
||||
defaultVariants: { position: 'right' },
|
||||
});
|
||||
function SheetClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
||||
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
||||
}
|
||||
|
||||
type PortalProps = SheetPrimitive.DialogPortalProps &
|
||||
VariantProps<typeof portalVariants>;
|
||||
function SheetPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
||||
}
|
||||
|
||||
const SheetPortal: Component<PortalProps> = (props) => {
|
||||
const [local, others] = splitProps(props, ['position', 'children']);
|
||||
return (
|
||||
<SheetPrimitive.Portal {...others}>
|
||||
<div class={portalVariants({ position: local.position })}>
|
||||
{local.children}
|
||||
</div>
|
||||
</SheetPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
|
||||
type DialogOverlayProps<T extends ValidComponent = 'div'> =
|
||||
SheetPrimitive.DialogOverlayProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const SheetOverlay = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, DialogOverlayProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DialogOverlayProps, ['class']);
|
||||
function SheetOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
||||
return (
|
||||
<SheetPrimitive.Overlay
|
||||
class={cn(
|
||||
'data-[closed=]:fade-out-0 data-[expanded=]:fade-in-0 fixed inset-0 z-50 bg-black/80 data-[closed=]:animate-out data-[expanded=]:animate-in',
|
||||
local.class
|
||||
data-slot="sheet-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const sheetVariants = cva(
|
||||
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[closed=]:animate-out data-[expanded=]:animate-in data-[closed=]:duration-300 data-[expanded=]:duration-500',
|
||||
{
|
||||
variants: {
|
||||
position: {
|
||||
top: 'data-[closed=]:slide-out-to-top data-[expanded=]:slide-in-from-top inset-x-0 top-0 border-b',
|
||||
bottom:
|
||||
'data-[closed=]:slide-out-to-bottom data-[expanded=]:slide-in-from-bottom inset-x-0 bottom-0 border-t',
|
||||
left: 'data-[closed=]:slide-out-to-left data-[expanded=]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
|
||||
right:
|
||||
'data-[closed=]:slide-out-to-right data-[expanded=]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
position: 'right',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type DialogContentProps<T extends ValidComponent = 'div'> =
|
||||
SheetPrimitive.DialogContentProps<T> &
|
||||
VariantProps<typeof sheetVariants> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const SheetContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, DialogContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DialogContentProps, [
|
||||
'position',
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function SheetContent({
|
||||
className,
|
||||
children,
|
||||
side = "right",
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||
side?: "top" | "right" | "bottom" | "left"
|
||||
}) {
|
||||
return (
|
||||
<SheetPortal position={local.position}>
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
class={cn(
|
||||
sheetVariants({ position: local.position }),
|
||||
local.class,
|
||||
'max-h-screen overflow-y-auto'
|
||||
data-slot="sheet-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
side === "right" &&
|
||||
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
||||
side === "left" &&
|
||||
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
||||
side === "top" &&
|
||||
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
||||
side === "bottom" &&
|
||||
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
>
|
||||
{local.children}
|
||||
<SheetPrimitive.CloseButton class="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M18 6l-12 12" />
|
||||
<path d="M6 6l12 12" />
|
||||
</svg>
|
||||
<span class="sr-only">Close</span>
|
||||
</SheetPrimitive.CloseButton>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
||||
<XIcon className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const SheetHeader: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'flex flex-col space-y-2 text-center sm:text-left',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
data-slot="sheet-header"
|
||||
className={cn("flex flex-col gap-1.5 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const SheetFooter: Component<ComponentProps<'div'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
data-slot="sheet-footer"
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type DialogTitleProps<T extends ValidComponent = 'h2'> =
|
||||
SheetPrimitive.DialogTitleProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const SheetTitle = <T extends ValidComponent = 'h2'>(
|
||||
props: PolymorphicProps<T, DialogTitleProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DialogTitleProps, ['class']);
|
||||
function SheetTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
||||
return (
|
||||
<SheetPrimitive.Title
|
||||
class={cn('font-semibold text-foreground text-lg', local.class)}
|
||||
{...others}
|
||||
data-slot="sheet-title"
|
||||
className={cn("text-foreground font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type DialogDescriptionProps<T extends ValidComponent = 'p'> =
|
||||
SheetPrimitive.DialogDescriptionProps<T> & { class?: string | undefined };
|
||||
|
||||
const SheetDescription = <T extends ValidComponent = 'p'>(
|
||||
props: PolymorphicProps<T, DialogDescriptionProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DialogDescriptionProps, [
|
||||
'class',
|
||||
]);
|
||||
function SheetDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
||||
return (
|
||||
<SheetPrimitive.Description
|
||||
class={cn('text-muted-foreground text-sm', local.class)}
|
||||
{...others}
|
||||
data-slot="sheet-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
@ -193,4 +134,4 @@ export {
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
};
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,27 +1,13 @@
|
||||
import type { ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as SkeletonPrimitive from '@kobalte/core/skeleton';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
type SkeletonRootProps<T extends ValidComponent = 'div'> =
|
||||
SkeletonPrimitive.SkeletonRootProps<T> & { class?: string | undefined };
|
||||
|
||||
const Skeleton = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, SkeletonRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SkeletonRootProps, ['class']);
|
||||
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<SkeletonPrimitive.Root
|
||||
class={cn(
|
||||
"bg-primary/10 data-[animate='true']:animate-pulse",
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
<div
|
||||
data-slot="skeleton"
|
||||
className={cn("bg-accent animate-pulse rounded-md", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export { Skeleton };
|
||||
export { Skeleton }
|
||||
|
@ -1,112 +1,63 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as SliderPrimitive from '@kobalte/core/slider';
|
||||
import * as React from "react"
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider"
|
||||
|
||||
import { Label } from '~/components/ui/label';
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
type SliderRootProps<T extends ValidComponent = 'div'> =
|
||||
SliderPrimitive.SliderRootProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
function Slider({
|
||||
className,
|
||||
defaultValue,
|
||||
value,
|
||||
min = 0,
|
||||
max = 100,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
|
||||
const _values = React.useMemo(
|
||||
() =>
|
||||
Array.isArray(value)
|
||||
? value
|
||||
: Array.isArray(defaultValue)
|
||||
? defaultValue
|
||||
: [min, max],
|
||||
[value, defaultValue, min, max]
|
||||
)
|
||||
|
||||
const Slider = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, SliderRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SliderRootProps, ['class']);
|
||||
return (
|
||||
<SliderPrimitive.Root
|
||||
class={cn(
|
||||
'relative flex w-full touch-none select-none flex-col items-center',
|
||||
local.class
|
||||
data-slot="slider"
|
||||
defaultValue={defaultValue}
|
||||
value={value}
|
||||
min={min}
|
||||
max={max}
|
||||
className={cn(
|
||||
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type SliderTrackProps<T extends ValidComponent = 'div'> =
|
||||
SliderPrimitive.SliderTrackProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const SliderTrack = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, SliderTrackProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SliderTrackProps, ['class']);
|
||||
return (
|
||||
<SliderPrimitive.Track
|
||||
class={cn(
|
||||
'relative h-2 w-full grow rounded-full bg-secondary',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type SliderFillProps<T extends ValidComponent = 'div'> =
|
||||
SliderPrimitive.SliderFillProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const SliderFill = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, SliderFillProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SliderFillProps, ['class']);
|
||||
return (
|
||||
<SliderPrimitive.Fill
|
||||
class={cn('absolute h-full rounded-full bg-primary', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type SliderThumbProps<T extends ValidComponent = 'span'> =
|
||||
SliderPrimitive.SliderThumbProps<T> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const SliderThumb = <T extends ValidComponent = 'span'>(
|
||||
props: PolymorphicProps<T, SliderThumbProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SliderThumbProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
return (
|
||||
<SliderPrimitive.Thumb
|
||||
class={cn(
|
||||
'top-[-6px] block size-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Input />
|
||||
</SliderPrimitive.Thumb>
|
||||
);
|
||||
};
|
||||
<SliderPrimitive.Track
|
||||
data-slot="slider-track"
|
||||
className={cn(
|
||||
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
|
||||
)}
|
||||
>
|
||||
<SliderPrimitive.Range
|
||||
data-slot="slider-range"
|
||||
className={cn(
|
||||
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
|
||||
)}
|
||||
/>
|
||||
</SliderPrimitive.Track>
|
||||
{Array.from({ length: _values.length }, (_, index) => (
|
||||
<SliderPrimitive.Thumb
|
||||
data-slot="slider-thumb"
|
||||
key={index}
|
||||
className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
||||
/>
|
||||
))}
|
||||
</SliderPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const SliderLabel = <T extends ValidComponent = 'label'>(
|
||||
props: PolymorphicProps<T, SliderPrimitive.SliderLabelProps<T>>
|
||||
) => {
|
||||
return <SliderPrimitive.Label as={Label} {...props} />;
|
||||
};
|
||||
|
||||
const SliderValueLabel = <T extends ValidComponent = 'label'>(
|
||||
props: PolymorphicProps<T, SliderPrimitive.SliderValueLabelProps<T>>
|
||||
) => {
|
||||
return <SliderPrimitive.ValueLabel as={Label} {...props} />;
|
||||
};
|
||||
|
||||
export {
|
||||
Slider,
|
||||
SliderTrack,
|
||||
SliderFill,
|
||||
SliderThumb,
|
||||
SliderLabel,
|
||||
SliderValueLabel,
|
||||
};
|
||||
export { Slider }
|
||||
|
@ -1,22 +1,20 @@
|
||||
import type { Component, ComponentProps } from "solid-js"
|
||||
import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner, ToasterProps } from "sonner"
|
||||
|
||||
import { Toaster as Sonner } from "solid-sonner"
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
|
||||
type ToasterProps = ComponentProps<typeof Sonner>
|
||||
|
||||
const Toaster: Component<ToasterProps> = (props) => {
|
||||
return (
|
||||
<Sonner
|
||||
class="toaster group"
|
||||
toastOptions={{
|
||||
classes: {
|
||||
toast:
|
||||
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||
description: "group-[.toast]:text-muted-foreground",
|
||||
actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||
cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground"
|
||||
}
|
||||
}}
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
style={
|
||||
{
|
||||
"--normal-bg": "var(--popover)",
|
||||
"--normal-text": "var(--popover-foreground)",
|
||||
"--normal-border": "var(--border)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
@ -1,50 +1,47 @@
|
||||
import { cn } from '@/styles/utils';
|
||||
import {
|
||||
LoaderCircleIcon,
|
||||
LoaderIcon,
|
||||
LoaderPinwheelIcon,
|
||||
Loader,
|
||||
LoaderCircle,
|
||||
LoaderPinwheel,
|
||||
type LucideProps,
|
||||
} from 'lucide-solid';
|
||||
import { mergeProps, splitProps } from 'solid-js';
|
||||
import { cn } from '~/utils/styles';
|
||||
} from 'lucide-react';
|
||||
|
||||
type SpinnerVariantProps = Omit<SpinnerProps, 'variant'>;
|
||||
export type SpinnerVariantProps = Omit<SpinnerProps, 'variant'>;
|
||||
|
||||
const Default = (props: SpinnerVariantProps) => {
|
||||
const [local, other] = splitProps(props, ['class']);
|
||||
return <LoaderIcon class={cn('animate-spin', local.class)} {...other} />;
|
||||
const Default = ({ className, ...props }: SpinnerVariantProps) => {
|
||||
return <Loader className={cn('animate-spin', className)} {...props} />;
|
||||
};
|
||||
|
||||
const Circle = (props: SpinnerVariantProps) => {
|
||||
const [local, other] = splitProps(props, ['class']);
|
||||
const Circle = ({ className, ...props }: SpinnerVariantProps) => {
|
||||
return <LoaderCircle className={cn('animate-spin', className)} {...props} />;
|
||||
};
|
||||
|
||||
const Pinwheel = ({ className, ...props }: SpinnerVariantProps) => {
|
||||
return (
|
||||
<LoaderCircleIcon class={cn('animate-spin', local.class)} {...other} />
|
||||
<LoaderPinwheel className={cn('animate-spin', className)} {...props} />
|
||||
);
|
||||
};
|
||||
|
||||
const Pinwheel = (props: SpinnerVariantProps) => {
|
||||
const [local, other] = splitProps(props, ['class']);
|
||||
const CircleFilled = ({
|
||||
className,
|
||||
size = 24,
|
||||
...props
|
||||
}: SpinnerVariantProps) => {
|
||||
return (
|
||||
<LoaderPinwheelIcon class={cn('animate-spin', local.class)} {...other} />
|
||||
);
|
||||
};
|
||||
|
||||
const CircleFilled = (props: SpinnerVariantProps) => {
|
||||
const [local, _] = splitProps(mergeProps({ size: 24 }, props), [
|
||||
'class',
|
||||
'size',
|
||||
]);
|
||||
const size = `${local.size}`;
|
||||
return (
|
||||
<div class="relative" style={{ width: size, height: size }}>
|
||||
<div class="absolute inset-0 rotate-180">
|
||||
<LoaderCircleIcon
|
||||
class={cn('animate-spin', local.class, 'text-foreground opacity-20')}
|
||||
<div className="relative" style={{ width: size, height: size }}>
|
||||
<div className="absolute inset-0 rotate-180">
|
||||
<LoaderCircle
|
||||
className={cn(
|
||||
'animate-spin',
|
||||
className,
|
||||
'text-foreground opacity-20'
|
||||
)}
|
||||
size={size}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
<LoaderCircleIcon
|
||||
class={cn('relative animate-spin', local.class)}
|
||||
<LoaderCircle
|
||||
className={cn('relative animate-spin', className)}
|
||||
size={size}
|
||||
{...props}
|
||||
/>
|
||||
@ -52,15 +49,14 @@ const CircleFilled = (props: SpinnerVariantProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Ellipsis = (props: SpinnerVariantProps) => {
|
||||
const [{ size = 24 }, other] = splitProps(props, ['size']);
|
||||
const Ellipsis = ({ size = 24, ...props }: SpinnerVariantProps) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
{...other}
|
||||
{...props}
|
||||
>
|
||||
<title>Loading...</title>
|
||||
<circle cx="4" cy="12" r="2" fill="currentColor">
|
||||
@ -99,8 +95,7 @@ const Ellipsis = (props: SpinnerVariantProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Ring = (props: SpinnerVariantProps) => {
|
||||
const [{ size = 24 }, other] = splitProps(props, ['size']);
|
||||
const Ring = ({ size = 24, ...props }: SpinnerVariantProps) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -108,10 +103,10 @@ const Ring = (props: SpinnerVariantProps) => {
|
||||
height={size}
|
||||
viewBox="0 0 44 44"
|
||||
stroke="currentColor"
|
||||
{...other}
|
||||
{...props}
|
||||
>
|
||||
<title>Loading...</title>
|
||||
<g fill="none" fill-rule="evenodd" stroke-width="2">
|
||||
<g fill="none" fillRule="evenodd" strokeWidth="2">
|
||||
<circle cx="22" cy="22" r="1">
|
||||
<animate
|
||||
attributeName="r"
|
||||
@ -171,30 +166,30 @@ const Bars = ({ size = 24, ...props }: SpinnerVariantProps) => (
|
||||
>
|
||||
<title>Loading...</title>
|
||||
<style>{`
|
||||
.spinner-bar {
|
||||
animation: spinner-bars-animation .8s linear infinite;
|
||||
animation-delay: -.8s;
|
||||
}
|
||||
.spinner-bars-2 {
|
||||
animation-delay: -.65s;
|
||||
}
|
||||
.spinner-bars-3 {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
@keyframes spinner-bars-animation {
|
||||
0% {
|
||||
y: 1px;
|
||||
height: 22px;
|
||||
.spinner-bar {
|
||||
animation: spinner-bars-animation .8s linear infinite;
|
||||
animation-delay: -.8s;
|
||||
}
|
||||
93.75% {
|
||||
y: 5px;
|
||||
height: 14px;
|
||||
opacity: 0.2;
|
||||
.spinner-bars-2 {
|
||||
animation-delay: -.65s;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
.spinner-bars-3 {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
@keyframes spinner-bars-animation {
|
||||
0% {
|
||||
y: 1px;
|
||||
height: 22px;
|
||||
}
|
||||
93.75% {
|
||||
y: 5px;
|
||||
height: 14px;
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
<rect
|
||||
class="spinner-bar"
|
||||
className="spinner-bar"
|
||||
x="1"
|
||||
y="1"
|
||||
width="6"
|
||||
@ -202,7 +197,7 @@ const Bars = ({ size = 24, ...props }: SpinnerVariantProps) => (
|
||||
fill="currentColor"
|
||||
/>
|
||||
<rect
|
||||
class="spinner-bar spinner-bars-2"
|
||||
className="spinner-bar spinner-bars-2"
|
||||
x="9"
|
||||
y="1"
|
||||
width="6"
|
||||
@ -210,7 +205,7 @@ const Bars = ({ size = 24, ...props }: SpinnerVariantProps) => (
|
||||
fill="currentColor"
|
||||
/>
|
||||
<rect
|
||||
class="spinner-bar spinner-bars-3"
|
||||
className="spinner-bar spinner-bars-3"
|
||||
x="17"
|
||||
y="1"
|
||||
width="6"
|
||||
@ -233,13 +228,13 @@ const Infinite = ({ size = 24, ...props }: SpinnerVariantProps) => (
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="10"
|
||||
stroke-dasharray="205.271142578125 51.317785644531256"
|
||||
strokeWidth="10"
|
||||
strokeDasharray="205.271142578125 51.317785644531256"
|
||||
d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40 C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z"
|
||||
stroke-linecap="round"
|
||||
strokeLinecap="round"
|
||||
style={{
|
||||
transform: 'scale(0.8)',
|
||||
'transform-origin': '50px 50px',
|
||||
transformOrigin: '50px 50px',
|
||||
}}
|
||||
>
|
||||
<animate
|
||||
|
@ -1,91 +1,31 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core';
|
||||
import * as SwitchPrimitive from '@kobalte/core/switch';
|
||||
import * as React from "react"
|
||||
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
const Switch = SwitchPrimitive.Root;
|
||||
const SwitchDescription = SwitchPrimitive.Description;
|
||||
const SwitchErrorMessage = SwitchPrimitive.ErrorMessage;
|
||||
|
||||
type SwitchControlProps = SwitchPrimitive.SwitchControlProps & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const SwitchControl = <T extends ValidComponent = 'input'>(
|
||||
props: PolymorphicProps<T, SwitchControlProps>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SwitchControlProps, [
|
||||
'class',
|
||||
'children',
|
||||
]);
|
||||
function Switch({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
||||
return (
|
||||
<>
|
||||
<SwitchPrimitive.Input
|
||||
class={cn(
|
||||
'[&:focus-visible+div]:outline-none [&:focus-visible+div]:ring-2 [&:focus-visible+div]:ring-ring [&:focus-visible+div]:ring-offset-2 [&:focus-visible+div]:ring-offset-background',
|
||||
local.class
|
||||
<SwitchPrimitive.Root
|
||||
data-slot="switch"
|
||||
className={cn(
|
||||
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
data-slot="switch-thumb"
|
||||
className={cn(
|
||||
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
<SwitchPrimitive.Control
|
||||
class={cn(
|
||||
'inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent bg-input transition-[color,background-color,box-shadow] data-[disabled]:cursor-not-allowed data-[checked]:bg-primary data-[disabled]:opacity-50',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
{local.children}
|
||||
</SwitchPrimitive.Control>
|
||||
</>
|
||||
);
|
||||
};
|
||||
</SwitchPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
type SwitchThumbProps = SwitchPrimitive.SwitchThumbProps & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const SwitchThumb = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, SwitchThumbProps>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SwitchThumbProps, ['class']);
|
||||
return (
|
||||
<SwitchPrimitive.Thumb
|
||||
class={cn(
|
||||
'pointer-events-none block size-5 translate-x-0 rounded-full bg-background shadow-lg ring-0 transition-transform data-[checked]:translate-x-5',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type SwitchLabelProps = SwitchPrimitive.SwitchLabelProps & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const SwitchLabel = <T extends ValidComponent = 'label'>(
|
||||
props: PolymorphicProps<T, SwitchLabelProps>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as SwitchLabelProps, ['class']);
|
||||
return (
|
||||
<SwitchPrimitive.Label
|
||||
class={cn(
|
||||
'font-medium text-sm leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
Switch,
|
||||
SwitchControl,
|
||||
SwitchThumb,
|
||||
SwitchLabel,
|
||||
SwitchDescription,
|
||||
SwitchErrorMessage,
|
||||
};
|
||||
export { Switch }
|
||||
|
@ -1,87 +1,106 @@
|
||||
import type { Component, ComponentProps } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
const Table: Component<ComponentProps<'table'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
||||
return (
|
||||
<div class="relative w-full overflow-auto">
|
||||
<div
|
||||
data-slot="table-container"
|
||||
className="relative w-full overflow-x-auto"
|
||||
>
|
||||
<table
|
||||
class={cn('w-full caption-bottom text-sm', local.class)}
|
||||
{...others}
|
||||
data-slot="table"
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const TableHeader: Component<ComponentProps<'thead'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
return <thead class={cn('[&_tr]:border-b', local.class)} {...others} />;
|
||||
};
|
||||
|
||||
const TableBody: Component<ComponentProps<'tbody'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
||||
return (
|
||||
<tbody class={cn('[&_tr:last-child]:border-0', local.class)} {...others} />
|
||||
);
|
||||
};
|
||||
<thead
|
||||
data-slot="table-header"
|
||||
className={cn("[&_tr]:border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const TableFooter: Component<ComponentProps<'tfoot'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
||||
return (
|
||||
<tbody
|
||||
data-slot="table-body"
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
||||
return (
|
||||
<tfoot
|
||||
class={cn('bg-primary font-medium text-primary-foreground', local.class)}
|
||||
{...others}
|
||||
data-slot="table-footer"
|
||||
className={cn(
|
||||
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const TableRow: Component<ComponentProps<'tr'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
||||
return (
|
||||
<tr
|
||||
class={cn(
|
||||
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
|
||||
local.class
|
||||
data-slot="table-row"
|
||||
className={cn(
|
||||
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const TableHead: Component<ComponentProps<'th'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
||||
return (
|
||||
<th
|
||||
class={cn(
|
||||
'h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
|
||||
local.class
|
||||
data-slot="table-head"
|
||||
className={cn(
|
||||
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const TableCell: Component<ComponentProps<'td'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
||||
return (
|
||||
<td
|
||||
class={cn('p-2 align-middle [&:has([role=checkbox])]:pr-0', local.class)}
|
||||
{...others}
|
||||
data-slot="table-cell"
|
||||
className={cn(
|
||||
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const TableCaption: Component<ComponentProps<'caption'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class']);
|
||||
function TableCaption({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"caption">) {
|
||||
return (
|
||||
<caption
|
||||
class={cn('mt-4 text-muted-foreground text-sm', local.class)}
|
||||
{...others}
|
||||
data-slot="table-caption"
|
||||
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Table,
|
||||
@ -92,4 +111,4 @@ export {
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
};
|
||||
}
|
||||
|
@ -1,91 +1,66 @@
|
||||
import type { ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as TabsPrimitive from '@kobalte/core/tabs';
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
const Tabs = TabsPrimitive.Root;
|
||||
function Tabs({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||
return (
|
||||
<TabsPrimitive.Root
|
||||
data-slot="tabs"
|
||||
className={cn("flex flex-col gap-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type TabsListProps<T extends ValidComponent = 'div'> =
|
||||
TabsPrimitive.TabsListProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TabsList = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, TabsListProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TabsListProps, ['class']);
|
||||
function TabsList({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
||||
return (
|
||||
<TabsPrimitive.List
|
||||
class={cn(
|
||||
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
|
||||
local.class
|
||||
data-slot="tabs-list"
|
||||
className={cn(
|
||||
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type TabsTriggerProps<T extends ValidComponent = 'button'> =
|
||||
TabsPrimitive.TabsTriggerProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TabsTrigger = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, TabsTriggerProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TabsTriggerProps, ['class']);
|
||||
function TabsTrigger({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
||||
return (
|
||||
<TabsPrimitive.Trigger
|
||||
class={cn(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 font-medium text-sm ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[selected]:bg-background data-[selected]:text-foreground data-[selected]:shadow-sm',
|
||||
local.class
|
||||
data-slot="tabs-trigger"
|
||||
className={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
|
||||
)}
|
||||
{...others}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type TabsContentProps<T extends ValidComponent = 'div'> =
|
||||
TabsPrimitive.TabsContentProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TabsContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, TabsContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TabsContentProps, ['class']);
|
||||
function TabsContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
||||
return (
|
||||
<TabsPrimitive.Content
|
||||
class={cn(
|
||||
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
data-slot="tabs-content"
|
||||
className={cn("flex-1 outline-none", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type TabsIndicatorProps<T extends ValidComponent = 'div'> =
|
||||
TabsPrimitive.TabsIndicatorProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TabsIndicator = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, TabsIndicatorProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TabsIndicatorProps, ['class']);
|
||||
return (
|
||||
<TabsPrimitive.Indicator
|
||||
class={cn(
|
||||
'data-[orientation=horizontal]:-bottom-px data-[orientation=vertical]:-right-px absolute transition-all duration-250ms data-[orientation=horizontal]:h-[2px] data-[orientation=vertical]:w-[2px]',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent, TabsIndicator };
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
|
@ -1,174 +0,0 @@
|
||||
import type { ValidComponent } from 'solid-js';
|
||||
import { mergeProps, splitProps } from 'solid-js';
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core';
|
||||
import * as TextFieldPrimitive from '@kobalte/core/text-field';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
type TextFieldRootProps<T extends ValidComponent = 'div'> =
|
||||
TextFieldPrimitive.TextFieldRootProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TextField = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, TextFieldRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TextFieldRootProps, ['class']);
|
||||
return (
|
||||
<TextFieldPrimitive.Root
|
||||
class={cn('flex flex-col gap-1', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type TextFieldInputProps<T extends ValidComponent = 'input'> =
|
||||
TextFieldPrimitive.TextFieldInputProps<T> & {
|
||||
class?: string | undefined;
|
||||
type?:
|
||||
| 'button'
|
||||
| 'checkbox'
|
||||
| 'color'
|
||||
| 'date'
|
||||
| 'datetime-local'
|
||||
| 'email'
|
||||
| 'file'
|
||||
| 'hidden'
|
||||
| 'image'
|
||||
| 'month'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'radio'
|
||||
| 'range'
|
||||
| 'reset'
|
||||
| 'search'
|
||||
| 'submit'
|
||||
| 'tel'
|
||||
| 'text'
|
||||
| 'time'
|
||||
| 'url'
|
||||
| 'week';
|
||||
};
|
||||
|
||||
const TextFieldInput = <T extends ValidComponent = 'input'>(
|
||||
rawProps: PolymorphicProps<T, TextFieldInputProps<T>>
|
||||
) => {
|
||||
const props = mergeProps<TextFieldInputProps<T>[]>(
|
||||
{ type: 'text' },
|
||||
rawProps
|
||||
);
|
||||
const [local, others] = splitProps(props as TextFieldInputProps, [
|
||||
'type',
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<TextFieldPrimitive.Input
|
||||
type={local.type}
|
||||
class={cn(
|
||||
'flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[invalid]:border-error-foreground data-[invalid]:text-error-foreground',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type TextFieldTextAreaProps<T extends ValidComponent = 'textarea'> =
|
||||
TextFieldPrimitive.TextFieldTextAreaProps<T> & { class?: string | undefined };
|
||||
|
||||
const TextFieldTextArea = <T extends ValidComponent = 'textarea'>(
|
||||
props: PolymorphicProps<T, TextFieldTextAreaProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TextFieldTextAreaProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<TextFieldPrimitive.TextArea
|
||||
class={cn(
|
||||
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const labelVariants = cva(
|
||||
'font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
label: 'data-[invalid]:text-destructive',
|
||||
description: 'font-normal text-muted-foreground',
|
||||
error: 'text-destructive text-xs',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'label',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type TextFieldLabelProps<T extends ValidComponent = 'label'> =
|
||||
TextFieldPrimitive.TextFieldLabelProps<T> & { class?: string | undefined };
|
||||
|
||||
const TextFieldLabel = <T extends ValidComponent = 'label'>(
|
||||
props: PolymorphicProps<T, TextFieldLabelProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TextFieldLabelProps, ['class']);
|
||||
return (
|
||||
<TextFieldPrimitive.Label
|
||||
class={cn(labelVariants(), local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type TextFieldDescriptionProps<T extends ValidComponent = 'div'> =
|
||||
TextFieldPrimitive.TextFieldDescriptionProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TextFieldDescription = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, TextFieldDescriptionProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TextFieldDescriptionProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<TextFieldPrimitive.Description
|
||||
class={cn(labelVariants({ variant: 'description' }), local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type TextFieldErrorMessageProps<T extends ValidComponent = 'div'> =
|
||||
TextFieldPrimitive.TextFieldErrorMessageProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const TextFieldErrorMessage = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, TextFieldErrorMessageProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TextFieldErrorMessageProps, [
|
||||
'class',
|
||||
]);
|
||||
return (
|
||||
<TextFieldPrimitive.ErrorMessage
|
||||
class={cn(labelVariants({ variant: 'error' }), local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
TextField,
|
||||
TextFieldInput,
|
||||
TextFieldTextArea,
|
||||
TextFieldLabel,
|
||||
TextFieldDescription,
|
||||
TextFieldErrorMessage,
|
||||
};
|
18
apps/webui/src/components/ui/textarea.tsx
Normal file
18
apps/webui/src/components/ui/textarea.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||
return (
|
||||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
"border-input placeholder: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 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Textarea }
|
@ -1,160 +0,0 @@
|
||||
import type { ComponentProps, ParentComponent } from 'solid-js';
|
||||
import {
|
||||
type Component,
|
||||
For,
|
||||
type JSXElement,
|
||||
Show,
|
||||
mergeProps,
|
||||
splitProps,
|
||||
} from 'solid-js';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
export type TimelinePropsItem = Omit<
|
||||
TimelineItemProps,
|
||||
'isActive' | 'isActiveBullet' | 'bulletSize' | 'lineSize'
|
||||
> & {
|
||||
bulletSize?: number;
|
||||
};
|
||||
|
||||
export type TimelineProps = {
|
||||
items: TimelinePropsItem[];
|
||||
activeItem: number;
|
||||
bulletSize?: number;
|
||||
lineSize?: number;
|
||||
};
|
||||
|
||||
/*
|
||||
No bullet or line is active when activeItem is -1
|
||||
First bullet is active only if activeItem is 0 or more
|
||||
First line is active only if activeItem is 1 or more
|
||||
*/
|
||||
|
||||
const Timeline: Component<TimelineProps> = (rawProps) => {
|
||||
const props = mergeProps({ bulletSize: 16, lineSize: 2 }, rawProps);
|
||||
|
||||
return (
|
||||
<ul
|
||||
style={{
|
||||
'padding-left': `${props.bulletSize / 2}px`,
|
||||
}}
|
||||
>
|
||||
<For each={props.items}>
|
||||
{(item, index) => (
|
||||
<TimelineItem
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
bullet={item.bullet}
|
||||
isLast={index() === props.items.length - 1}
|
||||
isActive={
|
||||
props.activeItem === -1 ? false : props.activeItem >= index() + 1
|
||||
}
|
||||
isActiveBullet={
|
||||
props.activeItem === -1 ? false : props.activeItem >= index()
|
||||
}
|
||||
bulletSize={props.bulletSize}
|
||||
lineSize={props.lineSize}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export type TimelineItemProps = {
|
||||
title: JSXElement;
|
||||
description?: JSXElement;
|
||||
bullet?: JSXElement;
|
||||
isLast?: boolean;
|
||||
isActive: boolean;
|
||||
isActiveBullet: boolean;
|
||||
class?: string;
|
||||
bulletSize: number;
|
||||
lineSize: number;
|
||||
};
|
||||
|
||||
const TimelineItem: Component<TimelineItemProps> = (props) => {
|
||||
const [local, others] = splitProps(props, [
|
||||
'class',
|
||||
'bullet',
|
||||
'description',
|
||||
'title',
|
||||
'isLast',
|
||||
'isActive',
|
||||
'isActiveBullet',
|
||||
'bulletSize',
|
||||
'lineSize',
|
||||
]);
|
||||
return (
|
||||
<li
|
||||
class={cn(
|
||||
'relative border-l pb-8 pl-8',
|
||||
local.isLast && 'border-l-transparent pb-0',
|
||||
local.isActive && !local.isLast && 'border-l-primary',
|
||||
local.class
|
||||
)}
|
||||
style={{
|
||||
'border-left-width': `${local.lineSize}px`,
|
||||
}}
|
||||
{...others}
|
||||
>
|
||||
<TimelineItemBullet
|
||||
lineSize={local.lineSize}
|
||||
bulletSize={local.bulletSize}
|
||||
isActive={local.isActiveBullet}
|
||||
>
|
||||
{local.bullet}
|
||||
</TimelineItemBullet>
|
||||
<TimelineItemTitle>{local.title}</TimelineItemTitle>
|
||||
<Show when={local.description}>
|
||||
<TimelineItemDescription>{local.description}</TimelineItemDescription>
|
||||
</Show>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export type TimelineItemBulletProps = {
|
||||
children?: JSXElement;
|
||||
isActive?: boolean;
|
||||
bulletSize: number;
|
||||
lineSize: number;
|
||||
};
|
||||
|
||||
const TimelineItemBullet: Component<TimelineItemBulletProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
`absolute top-0 flex items-center justify-center rounded-full border bg-background`,
|
||||
props.isActive && 'border-primary'
|
||||
)}
|
||||
style={{
|
||||
width: `${props.bulletSize}px`,
|
||||
height: `${props.bulletSize}px`,
|
||||
left: `${-props.bulletSize / 2 - props.lineSize / 2}px`,
|
||||
'border-width': `${props.lineSize}px`,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TimelineItemTitle: ParentComponent = (props) => {
|
||||
return (
|
||||
<div class="mb-1 font-semibold text-base leading-none">
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TimelineItemDescription: Component<ComponentProps<'p'>> = (props) => {
|
||||
const [local, others] = splitProps(props, ['class', 'children']);
|
||||
return (
|
||||
<p class={cn('text-muted-foreground text-sm', local.class)} {...others}>
|
||||
{local.children}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export { Timeline };
|
@ -1,205 +0,0 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { Match, Switch, splitProps } from 'solid-js';
|
||||
import { Portal } from 'solid-js/web';
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as ToastPrimitive from '@kobalte/core/toast';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
|
||||
const toastVariants = cva(
|
||||
'group data-[closed]:fade-out-80 data-[closed]:slide-out-to-right-full data-[opened]:slide-in-from-top-full data-[opened]:sm:slide-in-from-bottom-full pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--kb-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--kb-toast-swipe-move-x)] data-[closed]:animate-out data-[opened]:animate-in data-[swipe=end]:animate-out data-[swipe=move]:transition-none',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'border bg-background text-foreground',
|
||||
destructive:
|
||||
'destructive group border-destructive bg-destructive text-destructive-foreground',
|
||||
success:
|
||||
'success border-success-foreground bg-success text-success-foreground',
|
||||
warning:
|
||||
'warning border-warning-foreground bg-warning text-warning-foreground',
|
||||
error: 'error border-error-foreground bg-error text-error-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
}
|
||||
);
|
||||
type ToastVariant = NonNullable<VariantProps<typeof toastVariants>['variant']>;
|
||||
|
||||
type ToastListProps<T extends ValidComponent = 'ol'> =
|
||||
ToastPrimitive.ToastListProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const Toaster = <T extends ValidComponent = 'ol'>(
|
||||
props: PolymorphicProps<T, ToastListProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ToastListProps, ['class']);
|
||||
return (
|
||||
<Portal>
|
||||
<ToastPrimitive.Region>
|
||||
<ToastPrimitive.List
|
||||
class={cn(
|
||||
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse gap-2 p-4 sm:top-auto sm:right-0 sm:bottom-0 sm:flex-col md:max-w-[420px]',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
</ToastPrimitive.Region>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
type ToastRootProps<T extends ValidComponent = 'li'> =
|
||||
ToastPrimitive.ToastRootProps<T> &
|
||||
VariantProps<typeof toastVariants> & { class?: string | undefined };
|
||||
|
||||
const Toast = <T extends ValidComponent = 'li'>(
|
||||
props: PolymorphicProps<T, ToastRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ToastRootProps, [
|
||||
'class',
|
||||
'variant',
|
||||
]);
|
||||
return (
|
||||
<ToastPrimitive.Root
|
||||
class={cn(toastVariants({ variant: local.variant }), local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ToastCloseButtonProps<T extends ValidComponent = 'button'> =
|
||||
ToastPrimitive.ToastCloseButtonProps<T> & { class?: string | undefined };
|
||||
|
||||
const ToastClose = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, ToastCloseButtonProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ToastCloseButtonProps, ['class']);
|
||||
return (
|
||||
<ToastPrimitive.CloseButton
|
||||
class={cn(
|
||||
'absolute top-2 right-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-destructive-foreground group-[.error]:text-error-foreground group-[.success]:text-success-foreground group-[.warning]:text-warning-foreground',
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="size-4"
|
||||
>
|
||||
<path d="M18 6l-12 12" />
|
||||
<path d="M6 6l12 12" />
|
||||
</svg>
|
||||
</ToastPrimitive.CloseButton>
|
||||
);
|
||||
};
|
||||
|
||||
type ToastTitleProps<T extends ValidComponent = 'div'> =
|
||||
ToastPrimitive.ToastTitleProps<T> & {
|
||||
class?: string | undefined;
|
||||
};
|
||||
|
||||
const ToastTitle = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ToastTitleProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ToastTitleProps, ['class']);
|
||||
return (
|
||||
<ToastPrimitive.Title
|
||||
class={cn('font-semibold text-sm', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ToastDescriptionProps<T extends ValidComponent = 'div'> =
|
||||
ToastPrimitive.ToastDescriptionProps<T> & { class?: string | undefined };
|
||||
|
||||
const ToastDescription = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ToastDescriptionProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ToastDescriptionProps, ['class']);
|
||||
return (
|
||||
<ToastPrimitive.Description
|
||||
class={cn('text-sm opacity-90', local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function showToast(props: {
|
||||
title?: JSX.Element;
|
||||
description?: JSX.Element;
|
||||
variant?: ToastVariant;
|
||||
duration?: number;
|
||||
}) {
|
||||
ToastPrimitive.toaster.show((data) => (
|
||||
<Toast
|
||||
toastId={data.toastId}
|
||||
variant={props.variant}
|
||||
duration={props.duration}
|
||||
>
|
||||
<div class="grid gap-1">
|
||||
{props.title && <ToastTitle>{props.title}</ToastTitle>}
|
||||
{props.description && (
|
||||
<ToastDescription>{props.description}</ToastDescription>
|
||||
)}
|
||||
</div>
|
||||
<ToastClose />
|
||||
</Toast>
|
||||
));
|
||||
}
|
||||
|
||||
function showToastPromise<T, U>(
|
||||
promise: Promise<T> | (() => Promise<T>),
|
||||
options: {
|
||||
loading?: JSX.Element;
|
||||
success?: (data: T) => JSX.Element;
|
||||
error?: (error: U) => JSX.Element;
|
||||
duration?: number;
|
||||
}
|
||||
) {
|
||||
const variant: { [key in ToastPrimitive.ToastPromiseState]: ToastVariant } = {
|
||||
pending: 'default',
|
||||
fulfilled: 'success',
|
||||
rejected: 'error',
|
||||
};
|
||||
return ToastPrimitive.toaster.promise<T, U>(promise, (props) => (
|
||||
<Toast
|
||||
toastId={props.toastId}
|
||||
variant={variant[props.state]}
|
||||
duration={options.duration}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={props.state === 'pending'}>{options.loading}</Match>
|
||||
<Match when={props.state === 'fulfilled'}>
|
||||
{options.success?.(props.data!)}
|
||||
</Match>
|
||||
<Match when={props.state === 'rejected'}>
|
||||
{options.error?.(props.error!)}
|
||||
</Match>
|
||||
</Switch>
|
||||
</Toast>
|
||||
));
|
||||
}
|
||||
|
||||
export {
|
||||
Toaster,
|
||||
Toast,
|
||||
ToastClose,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
showToast,
|
||||
showToastPromise,
|
||||
};
|
@ -1,82 +1,73 @@
|
||||
import type { JSX, ValidComponent } from 'solid-js';
|
||||
import { createContext, splitProps, useContext } from 'solid-js';
|
||||
"use client"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as ToggleGroupPrimitive from '@kobalte/core/toggle-group';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import * as React from "react"
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
||||
import { type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { toggleVariants } from '~/components/ui/toggle';
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
import { toggleVariants } from "@/components/ui/toggle"
|
||||
|
||||
const ToggleGroupContext = createContext<VariantProps<typeof toggleVariants>>({
|
||||
size: 'default',
|
||||
variant: 'default',
|
||||
});
|
||||
|
||||
type ToggleGroupRootProps<T extends ValidComponent = 'div'> =
|
||||
ToggleGroupPrimitive.ToggleGroupRootProps<T> &
|
||||
VariantProps<typeof toggleVariants> & {
|
||||
class?: string | undefined;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const ToggleGroup = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, ToggleGroupRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ToggleGroupRootProps, [
|
||||
'class',
|
||||
'children',
|
||||
'size',
|
||||
'variant',
|
||||
]);
|
||||
const ToggleGroupContext = React.createContext<
|
||||
VariantProps<typeof toggleVariants>
|
||||
>({
|
||||
size: "default",
|
||||
variant: "default",
|
||||
})
|
||||
|
||||
function ToggleGroup({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>) {
|
||||
return (
|
||||
<ToggleGroupPrimitive.Root
|
||||
class={cn('flex items-center justify-center gap-1', local.class)}
|
||||
{...others}
|
||||
data-slot="toggle-group"
|
||||
data-variant={variant}
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ToggleGroupContext.Provider
|
||||
value={{
|
||||
get size() {
|
||||
return local.size;
|
||||
},
|
||||
get variant() {
|
||||
return local.variant;
|
||||
},
|
||||
}}
|
||||
>
|
||||
{local.children}
|
||||
<ToggleGroupContext.Provider value={{ variant, size }}>
|
||||
{children}
|
||||
</ToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
type ToggleGroupItemProps<T extends ValidComponent = 'button'> =
|
||||
ToggleGroupPrimitive.ToggleGroupItemProps<T> &
|
||||
VariantProps<typeof toggleVariants> & { class?: string | undefined };
|
||||
function ToggleGroupItem({
|
||||
className,
|
||||
children,
|
||||
variant,
|
||||
size,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
||||
VariantProps<typeof toggleVariants>) {
|
||||
const context = React.useContext(ToggleGroupContext)
|
||||
|
||||
const ToggleGroupItem = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, ToggleGroupItemProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ToggleGroupItemProps, [
|
||||
'class',
|
||||
'size',
|
||||
'variant',
|
||||
]);
|
||||
const context = useContext(ToggleGroupContext);
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
class={cn(
|
||||
data-slot="toggle-group-item"
|
||||
data-variant={context.variant || variant}
|
||||
data-size={context.size || size}
|
||||
className={cn(
|
||||
toggleVariants({
|
||||
size: context.size || local.size,
|
||||
variant: context.variant || local.variant,
|
||||
variant: context.variant || variant,
|
||||
size: context.size || size,
|
||||
}),
|
||||
'hover:bg-muted hover:text-muted-foreground data-[pressed]:bg-accent data-[pressed]:text-accent-foreground',
|
||||
local.class
|
||||
"min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem };
|
||||
export { ToggleGroup, ToggleGroupItem }
|
||||
|
@ -1,56 +1,45 @@
|
||||
import type { ValidComponent } from 'solid-js';
|
||||
import { splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as ToggleButtonPrimitive from '@kobalte/core/toggle-button';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
const toggleVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md font-medium text-sm ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-transparent',
|
||||
outline: 'border border-input bg-transparent shadow-sm',
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-3',
|
||||
sm: 'h-8 px-2',
|
||||
lg: 'h-10 px-3',
|
||||
default: "h-9 px-2 min-w-9",
|
||||
sm: "h-8 px-1.5 min-w-8",
|
||||
lg: "h-10 px-2.5 min-w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
type ToggleButtonRootProps<T extends ValidComponent = 'button'> =
|
||||
ToggleButtonPrimitive.ToggleButtonRootProps<T> &
|
||||
VariantProps<typeof toggleVariants> & { class?: string | undefined };
|
||||
|
||||
const Toggle = <T extends ValidComponent = 'button'>(
|
||||
props: PolymorphicProps<T, ToggleButtonRootProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as ToggleButtonRootProps, [
|
||||
'class',
|
||||
'variant',
|
||||
'size',
|
||||
]);
|
||||
function Toggle({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TogglePrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>) {
|
||||
return (
|
||||
<ToggleButtonPrimitive.Root
|
||||
class={cn(
|
||||
toggleVariants({ variant: local.variant, size: local.size }),
|
||||
local.class
|
||||
)}
|
||||
{...others}
|
||||
<TogglePrimitive.Root
|
||||
data-slot="toggle"
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export type { ToggleButtonRootProps as ToggleProps };
|
||||
export { toggleVariants, Toggle };
|
||||
export { Toggle, toggleVariants }
|
||||
|
@ -1,35 +1,59 @@
|
||||
import type { ValidComponent } from 'solid-js';
|
||||
import { type Component, splitProps } from 'solid-js';
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic';
|
||||
import * as TooltipPrimitive from '@kobalte/core/tooltip';
|
||||
import { cn } from "@/styles/utils"
|
||||
|
||||
import { cn } from '~/utils/styles';
|
||||
function TooltipProvider({
|
||||
delayDuration = 0,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||
return (
|
||||
<TooltipPrimitive.Provider
|
||||
data-slot="tooltip-provider"
|
||||
delayDuration={delayDuration}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
function Tooltip({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const Tooltip: Component<TooltipPrimitive.TooltipRootProps> = (props) => {
|
||||
return <TooltipPrimitive.Root gutter={4} {...props} />;
|
||||
};
|
||||
function TooltipTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
||||
}
|
||||
|
||||
type TooltipContentProps<T extends ValidComponent = 'div'> =
|
||||
TooltipPrimitive.TooltipContentProps<T> & { class?: string | undefined };
|
||||
|
||||
const TooltipContent = <T extends ValidComponent = 'div'>(
|
||||
props: PolymorphicProps<T, TooltipContentProps<T>>
|
||||
) => {
|
||||
const [local, others] = splitProps(props as TooltipContentProps, ['class']);
|
||||
function TooltipContent({
|
||||
className,
|
||||
sideOffset = 0,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||
return (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
class={cn(
|
||||
'fade-in-0 zoom-in-95 z-50 origin-[var(--kb-popover-content-transform-origin)] animate-in overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-popover-foreground text-sm shadow-md',
|
||||
local.class
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||
className
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent };
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
Settings2,
|
||||
SquareTerminal,
|
||||
Telescope,
|
||||
} from 'lucide-solid';
|
||||
} from 'lucide-react';
|
||||
|
||||
export const AppNavMainData = [
|
||||
{
|
||||
|
5
apps/webui/src/errors/common.ts
Normal file
5
apps/webui/src/errors/common.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class UnreachableError extends Error {
|
||||
constructor(detail: string) {
|
||||
super(`UnreachableError: ${detail}`);
|
||||
}
|
||||
}
|
19
apps/webui/src/hooks/use-mobile.ts
Normal file
19
apps/webui/src/hooks/use-mobile.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
}
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
|
||||
return !!isMobile
|
||||
}
|
@ -1,29 +1,19 @@
|
||||
import '@abraham/reflection';
|
||||
import {
|
||||
ColorModeProvider,
|
||||
ColorModeScript,
|
||||
createLocalStorageManager,
|
||||
} from '@kobalte/core';
|
||||
import { type Injector, ReflectiveInjector } from '@outposts/injection-js';
|
||||
import { RouterProvider, createRouter } from '@tanstack/solid-router';
|
||||
import {
|
||||
OidcSecurityService,
|
||||
provideAuth,
|
||||
withCheckAuthResultEvent,
|
||||
withDefaultFeatures,
|
||||
} from 'oidc-client-rx';
|
||||
import { withTanstackRouter } from 'oidc-client-rx/adapters/@tanstack/solid-router';
|
||||
import { RouterProvider, createRouter } from '@tanstack/react-router';
|
||||
import {
|
||||
InjectorContextVoidInjector,
|
||||
InjectorProvider,
|
||||
} from 'oidc-client-rx/adapters/solid-js';
|
||||
import { Dynamic, render } from 'solid-js/web';
|
||||
import { buildOidcConfig, isBasicAuth, isOidcAuth } from './auth/config';
|
||||
import { isAuthenticated } from './auth/context';
|
||||
import { useAuth } from './auth/hooks';
|
||||
import { routeTree } from './routeTree.gen';
|
||||
import './app.css';
|
||||
} from 'oidc-client-rx/adapters/react';
|
||||
import { Suspense } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { provideAuth, setupAuthContext } from './auth/context';
|
||||
import { AppNotFoundComponent } from './components/layout/app-not-found';
|
||||
import { providePlatform } from './platform/context';
|
||||
import { routeTree } from './routeTree.gen';
|
||||
import { provideStorages } from './storage/context';
|
||||
import { provideStyles } from './styles/context';
|
||||
import './app.css';
|
||||
|
||||
// Create a new router instance
|
||||
const router = createRouter({
|
||||
@ -34,79 +24,44 @@ const router = createRouter({
|
||||
defaultNotFoundComponent: AppNotFoundComponent,
|
||||
notFoundMode: 'root',
|
||||
context: {
|
||||
isAuthenticated: isAuthenticated,
|
||||
injector: InjectorContextVoidInjector,
|
||||
oidcSecurityService: {} as OidcSecurityService,
|
||||
},
|
||||
});
|
||||
|
||||
// Register the router instance for type safety
|
||||
declare module '@tanstack/solid-router' {
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: typeof router;
|
||||
}
|
||||
}
|
||||
|
||||
const injector: Injector = isBasicAuth
|
||||
? ReflectiveInjector.resolveAndCreate([])
|
||||
: ReflectiveInjector.resolveAndCreate(
|
||||
provideAuth(
|
||||
{
|
||||
config: buildOidcConfig(),
|
||||
},
|
||||
withDefaultFeatures({
|
||||
router: { enabled: false },
|
||||
securityStorage: { type: 'local-storage' },
|
||||
}),
|
||||
withTanstackRouter(router),
|
||||
withCheckAuthResultEvent()
|
||||
)
|
||||
);
|
||||
const injector: Injector = ReflectiveInjector.resolveAndCreate([
|
||||
...providePlatform(),
|
||||
...provideStorages(),
|
||||
...provideAuth(router),
|
||||
...provideStyles(),
|
||||
]);
|
||||
|
||||
// if needed, check when init
|
||||
let oidcSecurityService: OidcSecurityService | undefined;
|
||||
if (isOidcAuth) {
|
||||
oidcSecurityService = injector.get(OidcSecurityService);
|
||||
oidcSecurityService.checkAuth().subscribe();
|
||||
}
|
||||
setupAuthContext(injector);
|
||||
|
||||
// Render the app
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
const AppWithBasicAuth = () => {
|
||||
return <RouterProvider router={router} />;
|
||||
};
|
||||
|
||||
const AppWithOidcAuth = () => {
|
||||
const { isAuthenticated, oidcSecurityService, injector } = useAuth();
|
||||
return (
|
||||
<RouterProvider
|
||||
router={router}
|
||||
context={{
|
||||
isAuthenticated,
|
||||
oidcSecurityService,
|
||||
injector,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const storageManager = createLocalStorageManager('color-theme');
|
||||
return (
|
||||
<>
|
||||
<ColorModeScript storageType={storageManager.type} />
|
||||
<ColorModeProvider storageManager={storageManager}>
|
||||
<InjectorProvider injector={injector}>
|
||||
<Dynamic
|
||||
component={isBasicAuth ? AppWithBasicAuth : AppWithOidcAuth}
|
||||
/>
|
||||
</InjectorProvider>
|
||||
</ColorModeProvider>
|
||||
</>
|
||||
<Suspense>
|
||||
<InjectorProvider injector={injector}>
|
||||
<RouterProvider
|
||||
router={router}
|
||||
context={{
|
||||
injector,
|
||||
}}
|
||||
/>
|
||||
</InjectorProvider>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
if (rootElement && !rootElement.innerHTML) {
|
||||
render(App, rootElement);
|
||||
if (rootElement) {
|
||||
const root = createRoot(rootElement);
|
||||
root.render(<App />);
|
||||
}
|
||||
|
5
apps/webui/src/platform/context.ts
Normal file
5
apps/webui/src/platform/context.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { DOCUMENT } from './injection';
|
||||
|
||||
export const providePlatform = () => {
|
||||
return [{ provide: DOCUMENT, useValue: document }];
|
||||
};
|
7
apps/webui/src/platform/errors.ts
Normal file
7
apps/webui/src/platform/errors.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export class FeatureNotAvailablePlatformError extends Error {
|
||||
constructor(feature: string, platform?: string) {
|
||||
super(
|
||||
`Platform error: ${feature} is not available on ${platform ?? 'current platform'}`
|
||||
);
|
||||
}
|
||||
}
|
3
apps/webui/src/platform/injection.ts
Normal file
3
apps/webui/src/platform/injection.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { InjectionToken } from '@outposts/injection-js';
|
||||
|
||||
export const DOCUMENT = new InjectionToken<Document>('DOCUMENT');
|
@ -137,7 +137,7 @@ const AppSubscriptionsEditSubscriptionIdRoute =
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module '@tanstack/solid-router' {
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createFileRoute } from '@tanstack/solid-router';
|
||||
import { AppNotFoundComponent } from '~/components/layout/app-not-found';
|
||||
import { AppNotFoundComponent } from '@/components/layout/app-not-found';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/404')({
|
||||
component: AppNotFoundComponent,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Outlet, createRootRouteWithContext } from '@tanstack/solid-router';
|
||||
import { Home } from 'lucide-solid';
|
||||
import type { RouteStateDataOption, RouterContext } from '~/traits/router';
|
||||
import type { RouteStateDataOption, RouterContext } from '@/traits/router';
|
||||
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router';
|
||||
import { Home } from 'lucide-react';
|
||||
|
||||
export const Route = createRootRouteWithContext<RouterContext>()({
|
||||
component: Outlet,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createFileRoute } from '@tanstack/solid-router';
|
||||
import type { RouteStateDataOption } from '~/traits/router';
|
||||
import type { RouteStateDataOption } from '@/traits/router';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/_app/_explore/explore')({
|
||||
component: ExploreRouteComponent,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createFileRoute } from '@tanstack/solid-router';
|
||||
import type { RouteStateDataOption } from '~/traits/router';
|
||||
import type { RouteStateDataOption } from '@/traits/router';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/_app/_explore/feed')({
|
||||
component: FeedRouteComponent,
|
||||
|
@ -1,12 +1,10 @@
|
||||
import { useAuth } from '@/auth/hooks';
|
||||
import { type Fetcher, createGraphiQLFetcher } from '@graphiql/toolkit';
|
||||
import { createLazyFileRoute } from '@tanstack/solid-router';
|
||||
import { createLazyFileRoute } from '@tanstack/react-router';
|
||||
import GraphiQL from 'graphiql';
|
||||
import { createElement } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { onCleanup, onMount } from 'solid-js';
|
||||
import { isOidcAuth } from '~/auth/config';
|
||||
import { useAuth } from '~/auth/hooks';
|
||||
import { useCallback } from 'react';
|
||||
import 'graphiql/graphiql.css';
|
||||
import { AuthMethodEnum } from '@/auth/config';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
export const Route = createLazyFileRoute('/_app/playground/graphql-api')({
|
||||
@ -14,41 +12,38 @@ export const Route = createLazyFileRoute('/_app/playground/graphql-api')({
|
||||
});
|
||||
|
||||
function PlaygroundGraphQLApiRouteComponent() {
|
||||
const auth = useAuth();
|
||||
let containerRef: HTMLDivElement | undefined;
|
||||
const { authContext } = useAuth();
|
||||
|
||||
onMount(() => {
|
||||
if (containerRef) {
|
||||
const reactRoot = createRoot(containerRef);
|
||||
if (reactRoot) {
|
||||
const fetcher: Fetcher = async (props) => {
|
||||
const accessToken = isOidcAuth
|
||||
? await firstValueFrom(auth.oidcSecurityService!.getAccessToken())
|
||||
: undefined;
|
||||
return createGraphiQLFetcher({
|
||||
url: '/api/graphql',
|
||||
headers: accessToken
|
||||
? {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
}
|
||||
: undefined,
|
||||
})(props);
|
||||
};
|
||||
const graphiql = createElement(GraphiQL, {
|
||||
fetcher,
|
||||
});
|
||||
reactRoot.render(graphiql);
|
||||
|
||||
onCleanup(() => reactRoot.unmount());
|
||||
}
|
||||
}
|
||||
});
|
||||
const fetcher: Fetcher = useCallback(
|
||||
async (props) => {
|
||||
const accessToken =
|
||||
authContext.type === AuthMethodEnum.OIDC
|
||||
? await firstValueFrom(
|
||||
authContext.oidcSecurityService.getAccessToken()
|
||||
)
|
||||
: undefined;
|
||||
return createGraphiQLFetcher({
|
||||
url: '/api/graphql',
|
||||
headers: accessToken
|
||||
? {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
}
|
||||
: undefined,
|
||||
})(props);
|
||||
},
|
||||
[
|
||||
authContext.type,
|
||||
// @ts-ignore
|
||||
authContext?.oidcSecurityService?.getAccessToken,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-id="graphiql-playground-container"
|
||||
class="h-full overflow-hidden rounded-lg"
|
||||
ref={containerRef}
|
||||
/>
|
||||
className="h-full overflow-hidden rounded-lg"
|
||||
>
|
||||
<GraphiQL fetcher={fetcher} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/solid-router';
|
||||
import { AppSkeleton } from '~/components/layout/app-skeleton';
|
||||
import { buildLeafRouteStaticData } from '~/utils/route';
|
||||
import { AppSkeleton } from '@/components/layout/app-skeleton';
|
||||
import { buildLeafRouteStaticData } from '@/utils/route';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/_app/playground/graphql-api')({
|
||||
staticData: buildLeafRouteStaticData({ title: 'GraphQL Api' }),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createFileRoute } from '@tanstack/solid-router';
|
||||
import { buildVirtualBranchRouteOptions } from '~/utils/route';
|
||||
import { buildVirtualBranchRouteOptions } from '@/utils/route';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/_app/playground')(
|
||||
buildVirtualBranchRouteOptions({
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Outlet, createFileRoute } from '@tanstack/solid-router';
|
||||
import { beforeLoadGuard } from '~/auth/guard';
|
||||
import { AppAside } from '~/components/layout/app-layout';
|
||||
import { beforeLoadGuard } from '@/auth/guard';
|
||||
import { AppAside } from '@/components/layout/app-layout';
|
||||
import { Outlet, createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/_app')({
|
||||
component: AppLayoutRoute,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createFileRoute } from '@tanstack/solid-router';
|
||||
import { buildLeafRouteStaticData } from '~/utils/route';
|
||||
import { buildLeafRouteStaticData } from '@/utils/route';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/_app/settings/downloader')({
|
||||
component: SettingsDownloaderRouteComponent,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user