/
This commit is contained in:
@@ -1,31 +1,31 @@
|
||||
/** @type { import("eslint").Linter.Config } */
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
'prettier'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
parser: 'svelte-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
]
|
||||
root: true,
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
'prettier'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
parser: 'svelte-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": [
|
||||
"prettier-plugin-svelte",
|
||||
"prettier-plugin-tailwindcss"
|
||||
],
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
@@ -15,4 +12,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||
"style": "default",
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src\\app.css",
|
||||
"baseColor": "slate"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils"
|
||||
},
|
||||
"typescript": true
|
||||
}
|
||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||
"style": "default",
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src\\app.css",
|
||||
"baseColor": "slate"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils"
|
||||
},
|
||||
"typescript": true
|
||||
}
|
||||
|
||||
98
package.json
98
package.json
@@ -1,51 +1,51 @@
|
||||
{
|
||||
"name": "nir-messenger",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@types/eslint": "^8.56.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte-check": "^3.6.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"postcss": "^8.4.33",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"prettier-plugin-tailwindcss": "^0.5.1"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.8",
|
||||
"bits-ui": "^0.21.7",
|
||||
"clsx": "^2.1.1",
|
||||
"formsnap": "^1.0.0",
|
||||
"lucide-svelte": "^0.378.0",
|
||||
"mode-watcher": "^0.3.0",
|
||||
"svelte-persisted-store": "^0.9.2",
|
||||
"svelte-sonner": "^0.3.24",
|
||||
"sveltekit-superforms": "^2.13.1",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwind-variants": "^0.2.1",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
"name": "nir-messenger",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@types/eslint": "^8.56.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte-check": "^3.6.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"postcss": "^8.4.33",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"prettier-plugin-tailwindcss": "^0.5.1"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.8",
|
||||
"bits-ui": "^0.21.7",
|
||||
"clsx": "^2.1.1",
|
||||
"formsnap": "^1.0.0",
|
||||
"lucide-svelte": "^0.378.0",
|
||||
"mode-watcher": "^0.3.0",
|
||||
"svelte-persisted-store": "^0.9.2",
|
||||
"svelte-sonner": "^0.3.24",
|
||||
"sveltekit-superforms": "^2.13.1",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwind-variants": "^0.2.1",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
|
||||
125
src/app.css
125
src/app.css
@@ -1,78 +1,59 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 72.2% 50.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: hsl(212.7,26.8%,83.9);
|
||||
}
|
||||
:root {
|
||||
--background: 253 44% 98%;
|
||||
--foreground: 253 58% 0%;
|
||||
--muted: 253 7% 87%;
|
||||
--muted-foreground: 253 13% 37%;
|
||||
--popover: 253 44% 98%;
|
||||
--popover-foreground: 253 58% 0%;
|
||||
--card: 253 44% 97%;
|
||||
--card-foreground: 0 0% 0%;
|
||||
--border: 220 13% 91%;
|
||||
--input: 220 13% 91%;
|
||||
--primary: 253 91% 58%;
|
||||
--primary-foreground: 253 91% 98%;
|
||||
--secondary: 253 5% 89%;
|
||||
--secondary-foreground: 253 5% 29%;
|
||||
--accent: 253 12% 82%;
|
||||
--accent-foreground: 253 12% 22%;
|
||||
--destructive: 339.2 90.36% 51.18%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--ring: 253 91% 58%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 253 43% 3%;
|
||||
--foreground: 253 31% 98%;
|
||||
--muted: 253 7% 13%;
|
||||
--muted-foreground: 253 13% 63%;
|
||||
--popover: 253 43% 3%;
|
||||
--popover-foreground: 253 31% 98%;
|
||||
--card: 253 43% 4%;
|
||||
--card-foreground: 253 31% 99%;
|
||||
--border: 215 27.9% 16.9%;
|
||||
--input: 215 27.9% 16.9%;
|
||||
--primary: 253 91% 58%;
|
||||
--primary-foreground: 253 91% 98%;
|
||||
--secondary: 253 7% 9%;
|
||||
--secondary-foreground: 253 7% 69%;
|
||||
--accent: 253 13% 14%;
|
||||
--accent-foreground: 253 13% 74%;
|
||||
--destructive: 339.2 90.36% 51.18%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--ring: 253 91% 58%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
2
src/app.d.ts
vendored
2
src/app.d.ts
vendored
@@ -10,4 +10,4 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export { };
|
||||
export {};
|
||||
|
||||
42
src/app.html
42
src/app.html
@@ -1,28 +1,24 @@
|
||||
<!doctype html>
|
||||
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body data-sveltekit-preload-data="hover" class="dark">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Handle } from "@sveltejs/kit";
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
const response = await resolve(event);
|
||||
return response;
|
||||
};
|
||||
const response = await resolve(event);
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import { messagesCache, channelsCache } from '$lib/stores/cache';
|
||||
import type { Channel, Message } from '../types';
|
||||
import { apiRequest } from './utils';
|
||||
|
||||
export async function getAllChannels() {
|
||||
return await apiRequest<Channel[]>('/channel', 'get');
|
||||
return await apiRequest<Channel[]>('/channel', 'get', undefined, (data) => {
|
||||
data.forEach((channel) => {
|
||||
channelsCache.set(channel.id, channel);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function getChannelById(channelId: number) {
|
||||
return await apiRequest<Channel>(`/channel/${channelId}`, 'get');
|
||||
return await apiRequest<Channel>(`/channel/${channelId}`, 'get', undefined, (data) => {
|
||||
channelsCache.set(data.id, data);
|
||||
});
|
||||
}
|
||||
|
||||
export async function createChannel(name: string) {
|
||||
return await apiRequest<Channel>('/channel', 'post', { data: { name } });
|
||||
return await apiRequest<Channel>('/channel', 'post', { data: { name } }, (data) => {
|
||||
channelsCache.set(data.id, data);
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteChannel(channelId: number) {
|
||||
return await apiRequest<Channel>(`/channel/${channelId}`, 'delete');
|
||||
return await apiRequest<Channel>(`/channel/${channelId}`, 'delete', undefined, (data) => {
|
||||
channelsCache.remove(data.id);
|
||||
});
|
||||
}
|
||||
|
||||
export async function addUserToChannel(channelId: number, userId: number) {
|
||||
@@ -26,5 +37,14 @@ export async function removeUserFromChannel(channelId: number, userId: number) {
|
||||
}
|
||||
|
||||
export async function getMessagesByChannelId(channelId: number, beforeId?: number, limit?: number) {
|
||||
return await apiRequest<Message[]>(`/channel/${channelId}/message${beforeId || limit ? '?' : ''}${beforeId ? `before=${beforeId}` : ''}${limit ? `&limit=${limit}` : ''}`, 'get');
|
||||
}
|
||||
return await apiRequest<Message[]>(
|
||||
`/channel/${channelId}/message${beforeId || limit ? '?' : ''}${beforeId ? `before=${beforeId}` : ''}${limit ? `&limit=${limit}` : ''}`,
|
||||
'get',
|
||||
undefined,
|
||||
(data) => {
|
||||
data.forEach((message) => {
|
||||
messagesCache.set(message.id, message);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import { messagesCache } from '$lib/stores/cache';
|
||||
import type { Message } from '../types';
|
||||
import { apiRequest } from './utils';
|
||||
|
||||
export async function getMessageById(messageId: number) {
|
||||
return await apiRequest<Message>(`/message/${messageId}`, 'get');
|
||||
return await apiRequest<Message>(`/message/${messageId}`, 'get', undefined, (data) => {
|
||||
messagesCache.set(data.id, data);
|
||||
});
|
||||
}
|
||||
|
||||
export async function createMessage(channelId: number, content: string) {
|
||||
return await apiRequest<Message>('/message', 'post', { data: { channelId, content } });
|
||||
}
|
||||
return await apiRequest<Message>(
|
||||
'/message',
|
||||
'post',
|
||||
{ data: { channelId, content } },
|
||||
(data) => {
|
||||
messagesCache.set(data.id, data);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
import { usersCache } from '$lib/stores/cache';
|
||||
import type { Token, User } from '../types';
|
||||
import { apiRequest } from './utils';
|
||||
|
||||
export async function getByToken(token: string | undefined | null) {
|
||||
return await apiRequest<User>('/user/me', 'get', { token: token as string | undefined });
|
||||
return await apiRequest<User>(
|
||||
'/user/me',
|
||||
'get',
|
||||
{ token: token as string | undefined },
|
||||
(data) => {
|
||||
usersCache.set(data.id, data);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function getUserById(userId: number) {
|
||||
return await apiRequest<User>(`/user/${userId}`, 'get');
|
||||
return await apiRequest<User>(`/user/${userId}`, 'get', undefined, (data) => {
|
||||
usersCache.set(data.id, data);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUserByUsername(username: string) {
|
||||
return await apiRequest<User>(`/user/username/${username}`, 'get');
|
||||
return await apiRequest<User>(`/user/username/${username}`, 'get', undefined, (data) => {
|
||||
usersCache.set(data.id, data);
|
||||
});
|
||||
}
|
||||
|
||||
export async function loginUser(username: string, password: string) {
|
||||
@@ -19,4 +31,4 @@ export async function loginUser(username: string, password: string) {
|
||||
|
||||
export async function registerUser(username: string, password: string) {
|
||||
return await apiRequest<Token>('/user/register', 'post', { data: { username, password } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { API_URL } from "$lib/constants";
|
||||
import { getUserToken } from "$lib/stores/user";
|
||||
import type { ErrorResponse } from "$lib/types";
|
||||
import type { AxiosRequestConfig } from "axios";
|
||||
import axios from "axios";
|
||||
import { API_URL } from '$lib/constants';
|
||||
import { getUserToken } from '$lib/stores/user';
|
||||
import type { ErrorResponse } from '$lib/types';
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import axios from 'axios';
|
||||
|
||||
|
||||
|
||||
export async function apiRequest<T>(path: string, method: 'get' | 'post' | 'put' | 'delete', options?: AxiosRequestConfig & { token?: string }): Promise<T | ErrorResponse> {
|
||||
export async function apiRequest<T>(
|
||||
path: string,
|
||||
method: 'get' | 'post' | 'put' | 'delete',
|
||||
options?: AxiosRequestConfig & { token?: string },
|
||||
cacheCallback?: (data: T) => void
|
||||
): Promise<T | ErrorResponse> {
|
||||
const url = API_URL + path;
|
||||
|
||||
console.log(`[API] ${method.toUpperCase()} ${url}`);
|
||||
|
||||
const token = options?.token || getUserToken();
|
||||
|
||||
options = {
|
||||
@@ -16,16 +21,18 @@ export async function apiRequest<T>(path: string, method: 'get' | 'post' | 'put'
|
||||
method,
|
||||
headers: {
|
||||
...options?.headers,
|
||||
'Authorization': `Bearer ${token}`
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
validateStatus: () => true,
|
||||
...options,
|
||||
}
|
||||
...options
|
||||
};
|
||||
|
||||
const response = await axios.request(options);
|
||||
|
||||
if (response.status === 200)
|
||||
return response.data as T;
|
||||
else
|
||||
return response.data as ErrorResponse;
|
||||
}
|
||||
if (response.status === 200) {
|
||||
const data = response.data as T;
|
||||
if (cacheCallback) cacheCallback(data);
|
||||
|
||||
return data;
|
||||
} else return response.data as ErrorResponse;
|
||||
}
|
||||
|
||||
19
src/lib/components/theme-switch.svelte
Normal file
19
src/lib/components/theme-switch.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Moon, Sun } from 'lucide-svelte';
|
||||
import { Button } from './ui/button';
|
||||
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
let className = '';
|
||||
export { className as class };
|
||||
|
||||
function switchTheme() {
|
||||
$theme = $theme === 'light' ? 'dark' : 'light';
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button variant="ghost" class={cn('h-10 w-10 p-0', className)} on:click={() => switchTheme()}>
|
||||
<Sun class="hidden dark:block" />
|
||||
<Moon class="block dark:hidden" />
|
||||
</Button>
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Avatar as AvatarPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = AvatarPrimitive.FallbackProps;
|
||||
type $$Props = AvatarPrimitive.FallbackProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Fallback
|
||||
class={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
|
||||
{...$$restProps}
|
||||
class={cn('flex h-full w-full items-center justify-center rounded-full bg-muted', className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</AvatarPrimitive.Fallback>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Avatar as AvatarPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = AvatarPrimitive.ImageProps;
|
||||
type $$Props = AvatarPrimitive.ImageProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let src: $$Props["src"] = undefined;
|
||||
export let alt: $$Props["alt"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let src: $$Props['src'] = undefined;
|
||||
export let alt: $$Props['alt'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Image
|
||||
{src}
|
||||
{alt}
|
||||
class={cn("aspect-square h-full w-full", className)}
|
||||
{...$$restProps}
|
||||
{src}
|
||||
{alt}
|
||||
class={cn('aspect-square h-full w-full', className)}
|
||||
{...$$restProps}
|
||||
/>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Avatar as AvatarPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = AvatarPrimitive.Props;
|
||||
type $$Props = AvatarPrimitive.Props;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let delayMs: $$Props["delayMs"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let delayMs: $$Props['delayMs'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Root
|
||||
{delayMs}
|
||||
class={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
|
||||
{...$$restProps}
|
||||
{delayMs}
|
||||
class={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</AvatarPrimitive.Root>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import Root from "./avatar.svelte";
|
||||
import Image from "./avatar-image.svelte";
|
||||
import Fallback from "./avatar-fallback.svelte";
|
||||
import Root from './avatar.svelte';
|
||||
import Image from './avatar-image.svelte';
|
||||
import Fallback from './avatar-fallback.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Image,
|
||||
Fallback,
|
||||
//
|
||||
Root as Avatar,
|
||||
Image as AvatarImage,
|
||||
Fallback as AvatarFallback,
|
||||
Root,
|
||||
Image,
|
||||
Fallback,
|
||||
//
|
||||
Root as Avatar,
|
||||
Image as AvatarImage,
|
||||
Fallback as AvatarFallback
|
||||
};
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { Button as ButtonPrimitive } from "bits-ui";
|
||||
import { type Events, type Props, buttonVariants } from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Button as ButtonPrimitive } from 'bits-ui';
|
||||
import { type Events, type Props, buttonVariants } from './index.js';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = Props;
|
||||
type $$Events = Events;
|
||||
type $$Props = Props;
|
||||
type $$Events = Events;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let variant: $$Props["variant"] = "default";
|
||||
export let size: $$Props["size"] = "default";
|
||||
export let builders: $$Props["builders"] = [];
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let variant: $$Props['variant'] = 'default';
|
||||
export let size: $$Props['size'] = 'default';
|
||||
export let builders: $$Props['builders'] = [];
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<ButtonPrimitive.Root
|
||||
{builders}
|
||||
class={cn(buttonVariants({ variant, size, className }))}
|
||||
type="button"
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
{builders}
|
||||
class={cn(buttonVariants({ variant, size, className }))}
|
||||
type="button"
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</ButtonPrimitive.Root>
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
import type { Button as ButtonPrimitive } from "bits-ui";
|
||||
import Root from "./button.svelte";
|
||||
import { type VariantProps, tv } from 'tailwind-variants';
|
||||
import type { Button as ButtonPrimitive } from 'bits-ui';
|
||||
import Root from './button.svelte';
|
||||
|
||||
const buttonVariants = tv({
|
||||
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium 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",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
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",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
base: 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium 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',
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline:
|
||||
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
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'
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default'
|
||||
}
|
||||
});
|
||||
|
||||
type Variant = VariantProps<typeof buttonVariants>["variant"];
|
||||
type Size = VariantProps<typeof buttonVariants>["size"];
|
||||
type Variant = VariantProps<typeof buttonVariants>['variant'];
|
||||
type Size = VariantProps<typeof buttonVariants>['size'];
|
||||
|
||||
type Props = ButtonPrimitive.Props & {
|
||||
variant?: Variant;
|
||||
size?: Size;
|
||||
variant?: Variant;
|
||||
size?: Size;
|
||||
};
|
||||
|
||||
type Events = ButtonPrimitive.Events;
|
||||
|
||||
export {
|
||||
Root,
|
||||
type Props,
|
||||
type Events,
|
||||
//
|
||||
Root as Button,
|
||||
type Props as ButtonProps,
|
||||
type Events as ButtonEvents,
|
||||
buttonVariants,
|
||||
Root,
|
||||
type Props,
|
||||
type Events,
|
||||
//
|
||||
Root as Button,
|
||||
type Props as ButtonProps,
|
||||
type Events as ButtonEvents,
|
||||
buttonVariants
|
||||
};
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Check from "lucide-svelte/icons/check";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import Check from 'lucide-svelte/icons/check';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
|
||||
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
|
||||
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
|
||||
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let checked: $$Props["checked"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let checked: $$Props['checked'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
bind:checked
|
||||
class={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerdown
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
bind:checked
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerdown
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.CheckboxIndicator>
|
||||
<Check class="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.CheckboxIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.CheckboxIndicator>
|
||||
<Check class="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.CheckboxIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { cn, flyAndScale } from '$lib/utils.js';
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.ContentProps;
|
||||
type $$Events = DropdownMenuPrimitive.ContentEvents;
|
||||
type $$Props = DropdownMenuPrimitive.ContentProps;
|
||||
type $$Events = DropdownMenuPrimitive.ContentEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let sideOffset: $$Props["sideOffset"] = 4;
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let sideOffset: $$Props['sideOffset'] = 4;
|
||||
export let transition: $$Props['transition'] = flyAndScale;
|
||||
export let transitionConfig: $$Props['transitionConfig'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Content
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:keydown
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
'z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:keydown
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.Content>
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
};
|
||||
type $$Events = DropdownMenuPrimitive.ItemEvents;
|
||||
type $$Props = DropdownMenuPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
};
|
||||
type $$Events = DropdownMenuPrimitive.ItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let inset: $$Props["inset"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let inset: $$Props['inset'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Item
|
||||
class={cn(
|
||||
"relative flex cursor-default select-none items-center 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",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerdown
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center 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',
|
||||
inset && 'pl-8',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerdown
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.Item>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.LabelProps & {
|
||||
inset?: boolean;
|
||||
};
|
||||
type $$Props = DropdownMenuPrimitive.LabelProps & {
|
||||
inset?: boolean;
|
||||
};
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let inset: $$Props["inset"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let inset: $$Props['inset'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Label
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
||||
{...$$restProps}
|
||||
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.Label>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.RadioGroupProps;
|
||||
type $$Props = DropdownMenuPrimitive.RadioGroupProps;
|
||||
|
||||
export let value: $$Props["value"] = undefined;
|
||||
export let value: $$Props['value'] = undefined;
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioGroup {...$$restProps} bind:value>
|
||||
<slot />
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.RadioGroup>
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Circle from "lucide-svelte/icons/circle";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import Circle from 'lucide-svelte/icons/circle';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.RadioItemProps;
|
||||
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
|
||||
type $$Props = DropdownMenuPrimitive.RadioItemProps;
|
||||
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let value: $$Props["value"];
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let value: $$Props['value'];
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
class={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{value}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerdown
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
class={cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
|
||||
className
|
||||
)}
|
||||
{value}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerdown
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.RadioIndicator>
|
||||
<Circle class="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.RadioIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.RadioIndicator>
|
||||
<Circle class="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.RadioIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SeparatorProps;
|
||||
type $$Props = DropdownMenuPrimitive.SeparatorProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
class={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...$$restProps}
|
||||
class={cn('-mx-1 my-1 h-px bg-muted', className)}
|
||||
{...$$restProps}
|
||||
/>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<span class={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...$$restProps}>
|
||||
<slot />
|
||||
<span class={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...$$restProps}>
|
||||
<slot />
|
||||
</span>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import { cn, flyAndScale } from '$lib/utils.js';
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SubContentProps;
|
||||
type $$Events = DropdownMenuPrimitive.SubContentEvents;
|
||||
type $$Props = DropdownMenuPrimitive.SubContentProps;
|
||||
type $$Events = DropdownMenuPrimitive.SubContentEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||
x: -10,
|
||||
y: 0,
|
||||
};
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let transition: $$Props['transition'] = flyAndScale;
|
||||
export let transitionConfig: $$Props['transitionConfig'] = {
|
||||
x: -10,
|
||||
y: 0
|
||||
};
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:keydown
|
||||
on:focusout
|
||||
on:pointermove
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
'z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:keydown
|
||||
on:focusout
|
||||
on:pointermove
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</DropdownMenuPrimitive.SubContent>
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import ChevronRight from "lucide-svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import ChevronRight from 'lucide-svelte/icons/chevron-right';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
|
||||
inset?: boolean;
|
||||
};
|
||||
type $$Events = DropdownMenuPrimitive.SubTriggerEvents;
|
||||
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
|
||||
inset?: boolean;
|
||||
};
|
||||
type $$Events = DropdownMenuPrimitive.SubTriggerEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let inset: $$Props["inset"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let inset: $$Props['inset'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
class={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
class={cn(
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground',
|
||||
inset && 'pl-8',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:pointerleave
|
||||
on:pointermove
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto h-4 w-4" />
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto h-4 w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import Item from "./dropdown-menu-item.svelte";
|
||||
import Label from "./dropdown-menu-label.svelte";
|
||||
import Content from "./dropdown-menu-content.svelte";
|
||||
import Shortcut from "./dropdown-menu-shortcut.svelte";
|
||||
import RadioItem from "./dropdown-menu-radio-item.svelte";
|
||||
import Separator from "./dropdown-menu-separator.svelte";
|
||||
import RadioGroup from "./dropdown-menu-radio-group.svelte";
|
||||
import SubContent from "./dropdown-menu-sub-content.svelte";
|
||||
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
|
||||
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
||||
import Item from './dropdown-menu-item.svelte';
|
||||
import Label from './dropdown-menu-label.svelte';
|
||||
import Content from './dropdown-menu-content.svelte';
|
||||
import Shortcut from './dropdown-menu-shortcut.svelte';
|
||||
import RadioItem from './dropdown-menu-radio-item.svelte';
|
||||
import Separator from './dropdown-menu-separator.svelte';
|
||||
import RadioGroup from './dropdown-menu-radio-group.svelte';
|
||||
import SubContent from './dropdown-menu-sub-content.svelte';
|
||||
import SubTrigger from './dropdown-menu-sub-trigger.svelte';
|
||||
import CheckboxItem from './dropdown-menu-checkbox-item.svelte';
|
||||
|
||||
const Sub = DropdownMenuPrimitive.Sub;
|
||||
const Root = DropdownMenuPrimitive.Root;
|
||||
@@ -16,33 +16,33 @@ const Trigger = DropdownMenuPrimitive.Trigger;
|
||||
const Group = DropdownMenuPrimitive.Group;
|
||||
|
||||
export {
|
||||
Sub,
|
||||
Root,
|
||||
Item,
|
||||
Label,
|
||||
Group,
|
||||
Trigger,
|
||||
Content,
|
||||
Shortcut,
|
||||
Separator,
|
||||
RadioItem,
|
||||
SubContent,
|
||||
SubTrigger,
|
||||
RadioGroup,
|
||||
CheckboxItem,
|
||||
//
|
||||
Root as DropdownMenu,
|
||||
Sub as DropdownMenuSub,
|
||||
Item as DropdownMenuItem,
|
||||
Label as DropdownMenuLabel,
|
||||
Group as DropdownMenuGroup,
|
||||
Content as DropdownMenuContent,
|
||||
Trigger as DropdownMenuTrigger,
|
||||
Shortcut as DropdownMenuShortcut,
|
||||
RadioItem as DropdownMenuRadioItem,
|
||||
Separator as DropdownMenuSeparator,
|
||||
RadioGroup as DropdownMenuRadioGroup,
|
||||
SubContent as DropdownMenuSubContent,
|
||||
SubTrigger as DropdownMenuSubTrigger,
|
||||
CheckboxItem as DropdownMenuCheckboxItem,
|
||||
Sub,
|
||||
Root,
|
||||
Item,
|
||||
Label,
|
||||
Group,
|
||||
Trigger,
|
||||
Content,
|
||||
Shortcut,
|
||||
Separator,
|
||||
RadioItem,
|
||||
SubContent,
|
||||
SubTrigger,
|
||||
RadioGroup,
|
||||
CheckboxItem,
|
||||
//
|
||||
Root as DropdownMenu,
|
||||
Sub as DropdownMenuSub,
|
||||
Item as DropdownMenuItem,
|
||||
Label as DropdownMenuLabel,
|
||||
Group as DropdownMenuGroup,
|
||||
Content as DropdownMenuContent,
|
||||
Trigger as DropdownMenuTrigger,
|
||||
Shortcut as DropdownMenuShortcut,
|
||||
RadioItem as DropdownMenuRadioItem,
|
||||
Separator as DropdownMenuSeparator,
|
||||
RadioGroup as DropdownMenuRadioGroup,
|
||||
SubContent as DropdownMenuSubContent,
|
||||
SubTrigger as DropdownMenuSubTrigger,
|
||||
CheckboxItem as DropdownMenuCheckboxItem
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as Button from "$lib/components/ui/button/index.js";
|
||||
import * as Button from '$lib/components/ui/button/index.js';
|
||||
|
||||
type $$Props = Button.Props;
|
||||
type $$Events = Button.Events;
|
||||
type $$Props = Button.Props;
|
||||
type $$Events = Button.Events;
|
||||
</script>
|
||||
|
||||
<Button.Root type="submit" on:click on:keydown {...$$restProps}>
|
||||
<slot />
|
||||
<slot />
|
||||
</Button.Root>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Description
|
||||
class={cn("text-sm text-muted-foreground", className)}
|
||||
{...$$restProps}
|
||||
let:descriptionAttrs
|
||||
class={cn('text-sm text-muted-foreground', className)}
|
||||
{...$$restProps}
|
||||
let:descriptionAttrs
|
||||
>
|
||||
<slot {descriptionAttrs} />
|
||||
<slot {descriptionAttrs} />
|
||||
</FormPrimitive.Description>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<script lang="ts" context="module">
|
||||
import type { FormPathLeaves, SuperForm } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPathLeaves<T>;
|
||||
import type { FormPathLeaves, SuperForm } from 'sveltekit-superforms';
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPathLeaves<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
|
||||
export let form: SuperForm<T>;
|
||||
export let name: U;
|
||||
export let form: SuperForm<T>;
|
||||
export let name: U;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.ElementField {form} {name} let:constraints let:errors let:tainted let:value>
|
||||
<div class={cn("space-y-2", className)}>
|
||||
<slot {constraints} {errors} {tainted} {value} />
|
||||
</div>
|
||||
<div class={cn('space-y-2', className)}>
|
||||
<slot {constraints} {errors} {tainted} {value} />
|
||||
</div>
|
||||
</FormPrimitive.ElementField>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = FormPrimitive.FieldErrorsProps & {
|
||||
errorClasses?: string | undefined | null;
|
||||
};
|
||||
type $$Props = FormPrimitive.FieldErrorsProps & {
|
||||
errorClasses?: string | undefined | null;
|
||||
};
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
export let errorClasses: $$Props["class"] = undefined;
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
export let errorClasses: $$Props['class'] = undefined;
|
||||
</script>
|
||||
|
||||
<FormPrimitive.FieldErrors
|
||||
class={cn("text-sm font-medium text-destructive", className)}
|
||||
{...$$restProps}
|
||||
let:errors
|
||||
let:fieldErrorsAttrs
|
||||
let:errorAttrs
|
||||
class={cn('text-sm font-medium text-destructive', className)}
|
||||
{...$$restProps}
|
||||
let:errors
|
||||
let:fieldErrorsAttrs
|
||||
let:errorAttrs
|
||||
>
|
||||
<slot {errors} {fieldErrorsAttrs} {errorAttrs}>
|
||||
{#each errors as error}
|
||||
<div {...errorAttrs} class={cn(errorClasses)}>{error}</div>
|
||||
{/each}
|
||||
</slot>
|
||||
<slot {errors} {fieldErrorsAttrs} {errorAttrs}>
|
||||
{#each errors as error}
|
||||
<div {...errorAttrs} class={cn(errorClasses)}>{error}</div>
|
||||
{/each}
|
||||
</slot>
|
||||
</FormPrimitive.FieldErrors>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<script lang="ts" context="module">
|
||||
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPath<T>;
|
||||
import type { FormPath, SuperForm } from 'sveltekit-superforms';
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
|
||||
export let form: SuperForm<T>;
|
||||
export let name: U;
|
||||
export let form: SuperForm<T>;
|
||||
export let name: U;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Field {form} {name} let:constraints let:errors let:tainted let:value>
|
||||
<div class={cn("space-y-2", className)}>
|
||||
<slot {constraints} {errors} {tainted} {value} />
|
||||
</div>
|
||||
<div class={cn('space-y-2', className)}>
|
||||
<slot {constraints} {errors} {tainted} {value} />
|
||||
</div>
|
||||
</FormPrimitive.Field>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<script lang="ts" context="module">
|
||||
import type { FormPath, SuperForm } from "sveltekit-superforms";
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPath<T>;
|
||||
import type { FormPath, SuperForm } from 'sveltekit-superforms';
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = FormPrimitive.FieldsetProps<T, U>;
|
||||
type $$Props = FormPrimitive.FieldsetProps<T, U>;
|
||||
|
||||
export let form: SuperForm<T>;
|
||||
export let name: U;
|
||||
export let form: SuperForm<T>;
|
||||
export let name: U;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Fieldset
|
||||
{form}
|
||||
{name}
|
||||
let:constraints
|
||||
let:errors
|
||||
let:tainted
|
||||
let:value
|
||||
class={cn("space-y-2", className)}
|
||||
{form}
|
||||
{name}
|
||||
let:constraints
|
||||
let:errors
|
||||
let:tainted
|
||||
let:value
|
||||
class={cn('space-y-2', className)}
|
||||
>
|
||||
<slot {constraints} {errors} {tainted} {value} />
|
||||
<slot {constraints} {errors} {tainted} {value} />
|
||||
</FormPrimitive.Fieldset>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import type { Label as LabelPrimitive } from "bits-ui";
|
||||
import { getFormControl } from "formsnap";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Label } from "$lib/components/ui/label/index.js";
|
||||
import type { Label as LabelPrimitive } from 'bits-ui';
|
||||
import { getFormControl } from 'formsnap';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
|
||||
const { labelAttrs } = getFormControl();
|
||||
const { labelAttrs } = getFormControl();
|
||||
</script>
|
||||
|
||||
<Label {...$labelAttrs} class={cn("data-[fs-error]:text-destructive", className)} {...$$restProps}>
|
||||
<slot {labelAttrs} />
|
||||
<Label {...$labelAttrs} class={cn('data-[fs-error]:text-destructive', className)} {...$$restProps}>
|
||||
<slot {labelAttrs} />
|
||||
</Label>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = FormPrimitive.LegendProps;
|
||||
type $$Props = FormPrimitive.LegendProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Legend
|
||||
{...$$restProps}
|
||||
class={cn("text-sm font-medium leading-none data-[fs-error]:text-destructive", className)}
|
||||
let:legendAttrs
|
||||
{...$$restProps}
|
||||
class={cn('text-sm font-medium leading-none data-[fs-error]:text-destructive', className)}
|
||||
let:legendAttrs
|
||||
>
|
||||
<slot {legendAttrs} />
|
||||
<slot {legendAttrs} />
|
||||
</FormPrimitive.Legend>
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import Description from "./form-description.svelte";
|
||||
import Label from "./form-label.svelte";
|
||||
import FieldErrors from "./form-field-errors.svelte";
|
||||
import Field from "./form-field.svelte";
|
||||
import Fieldset from "./form-fieldset.svelte";
|
||||
import Legend from "./form-legend.svelte";
|
||||
import ElementField from "./form-element-field.svelte";
|
||||
import Button from "./form-button.svelte";
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import Description from './form-description.svelte';
|
||||
import Label from './form-label.svelte';
|
||||
import FieldErrors from './form-field-errors.svelte';
|
||||
import Field from './form-field.svelte';
|
||||
import Fieldset from './form-fieldset.svelte';
|
||||
import Legend from './form-legend.svelte';
|
||||
import ElementField from './form-element-field.svelte';
|
||||
import Button from './form-button.svelte';
|
||||
|
||||
const Control = FormPrimitive.Control;
|
||||
|
||||
export {
|
||||
Field,
|
||||
Control,
|
||||
Label,
|
||||
Button,
|
||||
FieldErrors,
|
||||
Description,
|
||||
Fieldset,
|
||||
Legend,
|
||||
ElementField,
|
||||
//
|
||||
Field as FormField,
|
||||
Control as FormControl,
|
||||
Description as FormDescription,
|
||||
Label as FormLabel,
|
||||
FieldErrors as FormFieldErrors,
|
||||
Fieldset as FormFieldset,
|
||||
Legend as FormLegend,
|
||||
ElementField as FormElementField,
|
||||
Button as FormButton,
|
||||
Field,
|
||||
Control,
|
||||
Label,
|
||||
Button,
|
||||
FieldErrors,
|
||||
Description,
|
||||
Fieldset,
|
||||
Legend,
|
||||
ElementField,
|
||||
//
|
||||
Field as FormField,
|
||||
Control as FormControl,
|
||||
Description as FormDescription,
|
||||
Label as FormLabel,
|
||||
FieldErrors as FormFieldErrors,
|
||||
Fieldset as FormFieldset,
|
||||
Legend as FormLegend,
|
||||
ElementField as FormElementField,
|
||||
Button as FormButton
|
||||
};
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import Root from "./input.svelte";
|
||||
import Root from './input.svelte';
|
||||
|
||||
export type FormInputEvent<T extends Event = Event> = T & {
|
||||
currentTarget: EventTarget & HTMLInputElement;
|
||||
currentTarget: EventTarget & HTMLInputElement;
|
||||
};
|
||||
export type InputEvents = {
|
||||
blur: FormInputEvent<FocusEvent>;
|
||||
change: FormInputEvent<Event>;
|
||||
click: FormInputEvent<MouseEvent>;
|
||||
focus: FormInputEvent<FocusEvent>;
|
||||
focusin: FormInputEvent<FocusEvent>;
|
||||
focusout: FormInputEvent<FocusEvent>;
|
||||
keydown: FormInputEvent<KeyboardEvent>;
|
||||
keypress: FormInputEvent<KeyboardEvent>;
|
||||
keyup: FormInputEvent<KeyboardEvent>;
|
||||
mouseover: FormInputEvent<MouseEvent>;
|
||||
mouseenter: FormInputEvent<MouseEvent>;
|
||||
mouseleave: FormInputEvent<MouseEvent>;
|
||||
mousemove: FormInputEvent<MouseEvent>;
|
||||
paste: FormInputEvent<ClipboardEvent>;
|
||||
input: FormInputEvent<InputEvent>;
|
||||
wheel: FormInputEvent<WheelEvent>;
|
||||
blur: FormInputEvent<FocusEvent>;
|
||||
change: FormInputEvent<Event>;
|
||||
click: FormInputEvent<MouseEvent>;
|
||||
focus: FormInputEvent<FocusEvent>;
|
||||
focusin: FormInputEvent<FocusEvent>;
|
||||
focusout: FormInputEvent<FocusEvent>;
|
||||
keydown: FormInputEvent<KeyboardEvent>;
|
||||
keypress: FormInputEvent<KeyboardEvent>;
|
||||
keyup: FormInputEvent<KeyboardEvent>;
|
||||
mouseover: FormInputEvent<MouseEvent>;
|
||||
mouseenter: FormInputEvent<MouseEvent>;
|
||||
mouseleave: FormInputEvent<MouseEvent>;
|
||||
mousemove: FormInputEvent<MouseEvent>;
|
||||
paste: FormInputEvent<ClipboardEvent>;
|
||||
input: FormInputEvent<InputEvent>;
|
||||
wheel: FormInputEvent<WheelEvent>;
|
||||
};
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
Root,
|
||||
//
|
||||
Root as Input
|
||||
};
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLInputAttributes } from "svelte/elements";
|
||||
import type { InputEvents } from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||
import type { InputEvents } from './index.js';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = HTMLInputAttributes;
|
||||
type $$Events = InputEvents;
|
||||
type $$Props = HTMLInputAttributes;
|
||||
type $$Events = InputEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let value: $$Props["value"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let value: $$Props['value'] = undefined;
|
||||
export { className as class };
|
||||
|
||||
// Workaround for https://github.com/sveltejs/svelte/issues/9305
|
||||
// Fixed in Svelte 5, but not backported to 4.x.
|
||||
export let readonly: $$Props["readonly"] = undefined;
|
||||
// Workaround for https://github.com/sveltejs/svelte/issues/9305
|
||||
// Fixed in Svelte 5, but not backported to 4.x.
|
||||
export let readonly: $$Props['readonly'] = undefined;
|
||||
</script>
|
||||
|
||||
<input
|
||||
class={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium 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",
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
{readonly}
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:mousemove
|
||||
on:paste
|
||||
on:input
|
||||
on:wheel|passive
|
||||
{...$$restProps}
|
||||
class={cn(
|
||||
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium 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',
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
{readonly}
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:mousemove
|
||||
on:paste
|
||||
on:input
|
||||
on:wheel|passive
|
||||
{...$$restProps}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from "./label.svelte";
|
||||
import Root from './label.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Label,
|
||||
Root,
|
||||
//
|
||||
Root as Label
|
||||
};
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Label as LabelPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Label as LabelPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
type $$Events = LabelPrimitive.Events;
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
type $$Events = LabelPrimitive.Events;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<LabelPrimitive.Root
|
||||
class={cn(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:mousedown
|
||||
class={cn(
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:mousedown
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</LabelPrimitive.Root>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||
import Content from "./popover-content.svelte";
|
||||
import { Popover as PopoverPrimitive } from 'bits-ui';
|
||||
import Content from './popover-content.svelte';
|
||||
const Root = PopoverPrimitive.Root;
|
||||
const Trigger = PopoverPrimitive.Trigger;
|
||||
const Close = PopoverPrimitive.Close;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Trigger,
|
||||
Close,
|
||||
//
|
||||
Root as Popover,
|
||||
Content as PopoverContent,
|
||||
Trigger as PopoverTrigger,
|
||||
Close as PopoverClose,
|
||||
Root,
|
||||
Content,
|
||||
Trigger,
|
||||
Close,
|
||||
//
|
||||
Root as Popover,
|
||||
Content as PopoverContent,
|
||||
Trigger as PopoverTrigger,
|
||||
Close as PopoverClose
|
||||
};
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils.js";
|
||||
import { Popover as PopoverPrimitive } from 'bits-ui';
|
||||
import { cn, flyAndScale } from '$lib/utils.js';
|
||||
|
||||
type $$Props = PopoverPrimitive.ContentProps;
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
||||
export { className as class };
|
||||
type $$Props = PopoverPrimitive.ContentProps;
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let transition: $$Props['transition'] = flyAndScale;
|
||||
export let transitionConfig: $$Props['transitionConfig'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<PopoverPrimitive.Content
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<slot />
|
||||
</PopoverPrimitive.Content>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Scrollbar from "./scroll-area-scrollbar.svelte";
|
||||
import Root from "./scroll-area.svelte";
|
||||
import Scrollbar from './scroll-area-scrollbar.svelte';
|
||||
import Root from './scroll-area.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Scrollbar,
|
||||
//,
|
||||
Root as ScrollArea,
|
||||
Scrollbar as ScrollAreaScrollbar,
|
||||
Root,
|
||||
Scrollbar,
|
||||
//,
|
||||
Root as ScrollArea,
|
||||
Scrollbar as ScrollAreaScrollbar
|
||||
};
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { ScrollArea as ScrollAreaPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { ScrollArea as ScrollAreaPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = ScrollAreaPrimitive.ScrollbarProps & {
|
||||
orientation?: "vertical" | "horizontal";
|
||||
};
|
||||
type $$Props = ScrollAreaPrimitive.ScrollbarProps & {
|
||||
orientation?: 'vertical' | 'horizontal';
|
||||
};
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let orientation: $$Props["orientation"] = "vertical";
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let orientation: $$Props['orientation'] = 'vertical';
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<ScrollAreaPrimitive.Scrollbar
|
||||
{orientation}
|
||||
class={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-px",
|
||||
orientation === "horizontal" && "h-2.5 w-full border-t border-t-transparent p-px",
|
||||
className
|
||||
)}
|
||||
{orientation}
|
||||
class={cn(
|
||||
'flex touch-none select-none transition-colors',
|
||||
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-px',
|
||||
orientation === 'horizontal' && 'h-2.5 w-full border-t border-t-transparent p-px',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
<ScrollAreaPrimitive.Thumb
|
||||
class={cn("relative rounded-full bg-border", orientation === "vertical" && "flex-1")}
|
||||
/>
|
||||
<slot />
|
||||
<ScrollAreaPrimitive.Thumb
|
||||
class={cn('relative rounded-full bg-border', orientation === 'vertical' && 'flex-1')}
|
||||
/>
|
||||
</ScrollAreaPrimitive.Scrollbar>
|
||||
|
||||
@@ -22,32 +22,34 @@
|
||||
|
||||
let viewport: HTMLDivElement;
|
||||
|
||||
const scrollBottom = (node: HTMLDivElement, top: number) => {
|
||||
const scroll = () =>
|
||||
node.scroll({
|
||||
top,
|
||||
behavior: 'instant'
|
||||
});
|
||||
scroll();
|
||||
|
||||
return { update: scroll };
|
||||
const scrollTo = (node: HTMLDivElement, top: number, behavior: ScrollBehavior) => {
|
||||
const scrollNode = () => {
|
||||
node.scrollTo({ top: top, behavior });
|
||||
node.scrollTop = top;
|
||||
};
|
||||
scrollNode();
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
if (scrollToBottom) {
|
||||
scrollBottom(viewport, viewport.scrollHeight);
|
||||
scrollTo(viewport, viewport.scrollHeight, 'instant');
|
||||
}
|
||||
});
|
||||
|
||||
export const scroll = (anchor: 'top' | 'bottom') => {
|
||||
export const scroll = (anchor: 'top' | 'bottom', behavior: ScrollBehavior = 'smooth') => {
|
||||
if (anchor === 'bottom') {
|
||||
scrollBottom(viewport, viewport.scrollHeight);
|
||||
scrollTo(viewport, viewport.scrollHeight, behavior);
|
||||
}
|
||||
|
||||
if (anchor === 'top') {
|
||||
scrollBottom(viewport, 0);
|
||||
scrollTo(viewport, 0, behavior);
|
||||
}
|
||||
};
|
||||
|
||||
export const getScrollPercent = () => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = viewport;
|
||||
return scrollTop / (scrollHeight - clientHeight);
|
||||
};
|
||||
</script>
|
||||
|
||||
<ScrollAreaPrimitive.Root {...$$restProps} class={cn('relative overflow-hidden', className)}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from "./separator.svelte";
|
||||
import Root from './separator.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
Root,
|
||||
//
|
||||
Root as Separator
|
||||
};
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { Separator as SeparatorPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Separator as SeparatorPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = SeparatorPrimitive.Props;
|
||||
type $$Props = SeparatorPrimitive.Props;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let orientation: $$Props["orientation"] = "horizontal";
|
||||
export let decorative: $$Props["decorative"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let orientation: $$Props['orientation'] = 'horizontal';
|
||||
export let decorative: $$Props['decorative'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SeparatorPrimitive.Root
|
||||
class={cn(
|
||||
"shrink-0 bg-border",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className
|
||||
)}
|
||||
{orientation}
|
||||
{decorative}
|
||||
{...$$restProps}
|
||||
class={cn(
|
||||
'shrink-0 bg-border',
|
||||
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
||||
className
|
||||
)}
|
||||
{orientation}
|
||||
{decorative}
|
||||
{...$$restProps}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from "./skeleton.svelte";
|
||||
import Root from './skeleton.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Skeleton,
|
||||
Root,
|
||||
//
|
||||
Root as Skeleton
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class={cn("animate-pulse rounded-md bg-muted", className)} {...$$restProps}></div>
|
||||
<div class={cn('animate-pulse rounded-md bg-muted', className)} {...$$restProps}></div>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as Toaster } from "./sonner.svelte";
|
||||
export { default as Toaster } from './sonner.svelte';
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
|
||||
import { mode } from "mode-watcher";
|
||||
import { Toaster as Sonner, type ToasterProps as SonnerProps } from 'svelte-sonner';
|
||||
import { mode } from 'mode-watcher';
|
||||
|
||||
type $$Props = SonnerProps;
|
||||
type $$Props = SonnerProps;
|
||||
</script>
|
||||
|
||||
<Sonner
|
||||
theme={$mode}
|
||||
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",
|
||||
},
|
||||
}}
|
||||
{...$$restProps}
|
||||
theme={$mode}
|
||||
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'
|
||||
}
|
||||
}}
|
||||
{...$$restProps}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Root from "./switch.svelte";
|
||||
import Root from './switch.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Switch,
|
||||
Root,
|
||||
//
|
||||
Root as Switch
|
||||
};
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { Switch as SwitchPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Switch as SwitchPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = SwitchPrimitive.Props;
|
||||
type $$Events = SwitchPrimitive.Events;
|
||||
type $$Props = SwitchPrimitive.Props;
|
||||
type $$Events = SwitchPrimitive.Events;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let checked: $$Props["checked"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let checked: $$Props['checked'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SwitchPrimitive.Root
|
||||
bind:checked
|
||||
class={cn(
|
||||
"peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
bind:checked
|
||||
class={cn(
|
||||
'peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
class={cn(
|
||||
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
<SwitchPrimitive.Thumb
|
||||
class={cn(
|
||||
'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0'
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import Root from "./textarea.svelte";
|
||||
import Root from './textarea.svelte';
|
||||
|
||||
type FormTextareaEvent<T extends Event = Event> = T & {
|
||||
currentTarget: EventTarget & HTMLTextAreaElement;
|
||||
currentTarget: EventTarget & HTMLTextAreaElement;
|
||||
};
|
||||
|
||||
type TextareaEvents = {
|
||||
blur: FormTextareaEvent<FocusEvent>;
|
||||
change: FormTextareaEvent<Event>;
|
||||
click: FormTextareaEvent<MouseEvent>;
|
||||
focus: FormTextareaEvent<FocusEvent>;
|
||||
keydown: FormTextareaEvent<KeyboardEvent>;
|
||||
keypress: FormTextareaEvent<KeyboardEvent>;
|
||||
keyup: FormTextareaEvent<KeyboardEvent>;
|
||||
mouseover: FormTextareaEvent<MouseEvent>;
|
||||
mouseenter: FormTextareaEvent<MouseEvent>;
|
||||
mouseleave: FormTextareaEvent<MouseEvent>;
|
||||
paste: FormTextareaEvent<ClipboardEvent>;
|
||||
input: FormTextareaEvent<InputEvent>;
|
||||
blur: FormTextareaEvent<FocusEvent>;
|
||||
change: FormTextareaEvent<Event>;
|
||||
click: FormTextareaEvent<MouseEvent>;
|
||||
focus: FormTextareaEvent<FocusEvent>;
|
||||
keydown: FormTextareaEvent<KeyboardEvent>;
|
||||
keypress: FormTextareaEvent<KeyboardEvent>;
|
||||
keyup: FormTextareaEvent<KeyboardEvent>;
|
||||
mouseover: FormTextareaEvent<MouseEvent>;
|
||||
mouseenter: FormTextareaEvent<MouseEvent>;
|
||||
mouseleave: FormTextareaEvent<MouseEvent>;
|
||||
paste: FormTextareaEvent<ClipboardEvent>;
|
||||
input: FormTextareaEvent<InputEvent>;
|
||||
};
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Textarea,
|
||||
type TextareaEvents,
|
||||
type FormTextareaEvent,
|
||||
Root,
|
||||
//
|
||||
Root as Textarea,
|
||||
type TextareaEvents,
|
||||
type FormTextareaEvent
|
||||
};
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLTextareaAttributes } from "svelte/elements";
|
||||
import type { TextareaEvents } from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
||||
import type { TextareaEvents } from './index.js';
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
type $$Props = HTMLTextareaAttributes;
|
||||
type $$Events = TextareaEvents;
|
||||
type $$Props = HTMLTextareaAttributes;
|
||||
type $$Events = TextareaEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let value: $$Props["value"] = undefined;
|
||||
export { className as class };
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let value: $$Props['value'] = undefined;
|
||||
export { className as class };
|
||||
|
||||
// Workaround for https://github.com/sveltejs/svelte/issues/9305
|
||||
// Fixed in Svelte 5, but not backported to 4.x.
|
||||
export let readonly: $$Props["readonly"] = undefined;
|
||||
// Workaround for https://github.com/sveltejs/svelte/issues/9305
|
||||
// Fixed in Svelte 5, but not backported to 4.x.
|
||||
export let readonly: $$Props['readonly'] = undefined;
|
||||
</script>
|
||||
|
||||
<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",
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
{readonly}
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:paste
|
||||
on:input
|
||||
{...$$restProps}
|
||||
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',
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
{readonly}
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:paste
|
||||
on:input
|
||||
{...$$restProps}
|
||||
></textarea>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export const API_URL = 'http://localhost:1234';
|
||||
export const BASE_API_URL = 'localhost:1234';
|
||||
export const API_URL = `http://${BASE_API_URL}`;
|
||||
|
||||
39
src/lib/event.ts
Normal file
39
src/lib/event.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export interface Listener<D> {
|
||||
(event: D): unknown;
|
||||
}
|
||||
|
||||
export interface Disposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class EventEmitter<E, D> {
|
||||
private listeners: Map<E, Listener<D>[]> = new Map();
|
||||
|
||||
on = (event: E, listener: Listener<D>): Disposable => {
|
||||
if (!this.listeners.has(event)) this.listeners.set(event, []);
|
||||
|
||||
this.listeners.get(event)?.push(listener);
|
||||
|
||||
return {
|
||||
dispose: () => this.off(event, listener)
|
||||
};
|
||||
};
|
||||
|
||||
off = (event: E, listener: Listener<D>) => {
|
||||
if (!this.listeners.has(event)) return;
|
||||
|
||||
const listeners = this.listeners.get(event)?.filter((l) => l !== listener);
|
||||
|
||||
this.listeners.set(event, listeners || []);
|
||||
};
|
||||
|
||||
emit = (event: E, data: D) => {
|
||||
if (!this.listeners.has(event)) return;
|
||||
|
||||
this.listeners.get(event)?.forEach((listener) => setTimeout(() => listener(data), 0));
|
||||
};
|
||||
|
||||
pipe = (event: E, te: EventEmitter<E, D>): Disposable => {
|
||||
return this.on(event, (e) => te.emit(event, e));
|
||||
};
|
||||
}
|
||||
41
src/lib/stores/cache/channels.ts
vendored
41
src/lib/stores/cache/channels.ts
vendored
@@ -1,41 +0,0 @@
|
||||
import { getChannelById } from "$lib/api/channel";
|
||||
import { isErrorResponse, type Channel } from "$lib/types";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
|
||||
const channelsCache: Writable<Map<number, Channel>> = writable(new Map<number, Channel>());
|
||||
|
||||
const runningCaches = new Set();
|
||||
|
||||
export function addChannelToCache(channel: Channel) {
|
||||
channelsCache.update((channels) => channels.set(channel.id, channel));
|
||||
}
|
||||
|
||||
export async function getCachedChannel(channelId: number): Promise<Channel | null> {
|
||||
const cached = get(channelsCache).get(channelId);
|
||||
|
||||
if (cached)
|
||||
return cached;
|
||||
|
||||
if (runningCaches.has(channelId))
|
||||
return await new Promise((resolve) => {
|
||||
channelsCache.subscribe((channels) => {
|
||||
const channel = channels.get(channelId);
|
||||
if (channel) {
|
||||
runningCaches.delete(channelId);
|
||||
resolve(channel);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
runningCaches.add(channelId);
|
||||
|
||||
const response = await getChannelById(channelId);
|
||||
if (isErrorResponse(response))
|
||||
return null;
|
||||
|
||||
const channel = response as Channel;
|
||||
|
||||
addChannelToCache(channel);
|
||||
|
||||
return channel;
|
||||
}
|
||||
23
src/lib/stores/cache/index.ts
vendored
Normal file
23
src/lib/stores/cache/index.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import { getChannelById } from '$lib/api/channel';
|
||||
import { getMessageById } from '$lib/api/message';
|
||||
import { getUserById } from '$lib/api/user';
|
||||
import { isErrorResponse, type Channel, type Message, type User } from '$lib/types';
|
||||
import { Cache } from './utils';
|
||||
|
||||
export const usersCache: Cache<number, User> = new Cache(async (id) => {
|
||||
const response = await getUserById(id);
|
||||
if (isErrorResponse(response)) return null;
|
||||
return response;
|
||||
});
|
||||
|
||||
export const messagesCache: Cache<number, Message> = new Cache(async (id) => {
|
||||
const response = await getMessageById(id);
|
||||
if (isErrorResponse(response)) return null;
|
||||
return response;
|
||||
});
|
||||
|
||||
export const channelsCache: Cache<number, Channel> = new Cache(async (id) => {
|
||||
const response = await getChannelById(id);
|
||||
if (isErrorResponse(response)) return null;
|
||||
return response;
|
||||
});
|
||||
42
src/lib/stores/cache/messages.ts
vendored
42
src/lib/stores/cache/messages.ts
vendored
@@ -1,42 +0,0 @@
|
||||
import { getMessageById } from "$lib/api/message";
|
||||
import { isErrorResponse, type Message } from "$lib/types";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
|
||||
const messagesCache: Writable<Map<number, Message>> = writable(new Map<number, Message>());
|
||||
|
||||
const runningCaches = new Set();
|
||||
|
||||
export function addMessageToCache(message: Message) {
|
||||
messagesCache.update((messages) => messages.set(message.id, message));
|
||||
}
|
||||
|
||||
export async function getCachedMessage(messageId: number): Promise<Message | null> {
|
||||
const cached = get(messagesCache).get(messageId);
|
||||
|
||||
if (cached)
|
||||
return cached;
|
||||
|
||||
if (runningCaches.has(messageId))
|
||||
return await new Promise((resolve) => {
|
||||
messagesCache.subscribe((users) => {
|
||||
|
||||
const user = users.get(messageId);
|
||||
if (user) {
|
||||
runningCaches.delete(messageId);
|
||||
resolve(user);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
runningCaches.add(messageId);
|
||||
|
||||
const response = await getMessageById(messageId);
|
||||
if (isErrorResponse(response))
|
||||
return null;
|
||||
|
||||
const message = response as Message;
|
||||
|
||||
addMessageToCache(message);
|
||||
|
||||
return message;
|
||||
}
|
||||
47
src/lib/stores/cache/users.ts
vendored
47
src/lib/stores/cache/users.ts
vendored
@@ -1,47 +0,0 @@
|
||||
import { getUserById } from "$lib/api/user";
|
||||
import { isErrorResponse, type User } from "$lib/types";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
|
||||
const usersCache: Writable<Map<number, User>> = writable(new Map<number, User>());
|
||||
|
||||
const runningCaches = new Set();
|
||||
|
||||
usersCache.subscribe((users) => {
|
||||
console.log(`Cached users: ${JSON.stringify([...users.values()])}`);
|
||||
})
|
||||
|
||||
export function addUserToCache(user: User) {
|
||||
usersCache.update((users) => users.set(user.id, user));
|
||||
}
|
||||
|
||||
export async function getCachedUser(userId: number): Promise<User | null> {
|
||||
const cachedUser = get(usersCache).get(userId);
|
||||
|
||||
if (cachedUser)
|
||||
return cachedUser;
|
||||
|
||||
if (runningCaches.has(userId)) {
|
||||
return await new Promise((resolve) => {
|
||||
usersCache.subscribe((users) => {
|
||||
console.log(`subsribed called`);
|
||||
const user = users.get(userId);
|
||||
if (user) {
|
||||
runningCaches.delete(userId);
|
||||
resolve(user);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
runningCaches.add(userId);
|
||||
|
||||
const response = await getUserById(userId);
|
||||
if (isErrorResponse(response))
|
||||
return null;
|
||||
|
||||
const user = response as User;
|
||||
|
||||
addUserToCache(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
57
src/lib/stores/cache/utils.ts
vendored
Normal file
57
src/lib/stores/cache/utils.ts
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
import { get, writable, type Writable } from 'svelte/store';
|
||||
|
||||
export class Cache<I, T> {
|
||||
private data: Writable<Map<I, T>> = writable(new Map<I, T>());
|
||||
private runningCaches: Set<I> = new Set();
|
||||
|
||||
private resolver: (data: I) => Promise<T | null>;
|
||||
|
||||
constructor(resolver: (data: I) => Promise<T | null>) {
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
async get(key: I): Promise<T | null> {
|
||||
const cached = get(this.data).get(key);
|
||||
if (cached) {
|
||||
console.log(`[Cache] Found in cache: `, cached);
|
||||
return cached;
|
||||
}
|
||||
|
||||
if (this.runningCaches.has(key)) {
|
||||
return new Promise((resolve) => {
|
||||
this.data.subscribe((data) => {
|
||||
const value = data.get(key);
|
||||
if (value) {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.runningCaches.add(key);
|
||||
|
||||
const data = await this.resolver(key);
|
||||
|
||||
this.runningCaches.delete(key);
|
||||
|
||||
if (data)
|
||||
console.log(`[Cache] Added to cache: `, data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
set(key: I, value: T) {
|
||||
console.log(`[Cache] Added to cache: `, value);
|
||||
|
||||
this.data.update((data) => data.set(key, value));
|
||||
}
|
||||
|
||||
remove(key: I) {
|
||||
console.log(`[Cache] Removed from cache: `, key);
|
||||
|
||||
this.data.update((data) => {
|
||||
data.delete(key);
|
||||
return data;
|
||||
});
|
||||
}
|
||||
}
|
||||
19
src/lib/stores/theme.ts
Normal file
19
src/lib/stores/theme.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
function initialTheme() {
|
||||
if (!browser) return 'light';
|
||||
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark';
|
||||
return 'light';
|
||||
}
|
||||
|
||||
export const theme: Writable<'light' | 'dark'> = persisted('theme', initialTheme());
|
||||
|
||||
theme.subscribe((theme) => {
|
||||
if (!browser) return;
|
||||
|
||||
if (theme === 'light') document.documentElement.classList.remove('dark');
|
||||
else document.documentElement.classList.add('dark');
|
||||
});
|
||||
@@ -1,23 +1,17 @@
|
||||
import { derived, get } from "svelte/store";
|
||||
import { isErrorResponse, type User } from "../types";
|
||||
import { getByToken } from "$lib/api/user";
|
||||
import { persisted } from "svelte-persisted-store";
|
||||
import { derived, get } from 'svelte/store';
|
||||
import { isErrorResponse, type User } from '../types';
|
||||
import { getByToken } from '$lib/api/user';
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
|
||||
export const token = persisted<string | null>('token', null);
|
||||
|
||||
token.subscribe((token) => {
|
||||
console.log(`updated token: ${JSON.stringify(token)}`);
|
||||
})
|
||||
|
||||
export const user = derived<typeof token, User | null>(token, ($token, set) => {
|
||||
getByToken($token).then((response) => {
|
||||
if (isErrorResponse(response))
|
||||
set(null);
|
||||
else
|
||||
set(response);
|
||||
})
|
||||
if (isErrorResponse(response)) set(null);
|
||||
else set(response);
|
||||
});
|
||||
});
|
||||
|
||||
export function getUserToken(): string | null {
|
||||
return get(token);
|
||||
}
|
||||
}
|
||||
|
||||
98
src/lib/stores/websocket.ts
Normal file
98
src/lib/stores/websocket.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { BASE_API_URL } from '$lib/constants';
|
||||
import { EventEmitter } from '$lib/event';
|
||||
import { derived, get } from 'svelte/store';
|
||||
import { token as tokenStore } from './user';
|
||||
import type { Channel, Message } from '$lib/types';
|
||||
import { messagesCache, channelsCache } from './cache';
|
||||
|
||||
export type WebSocketMessageType =
|
||||
| 'createMessage'
|
||||
| 'updateChannel'
|
||||
| 'createChannel'
|
||||
| 'deleteChannel'
|
||||
| 'connect'
|
||||
| 'disconnect'
|
||||
| 'any';
|
||||
|
||||
export type WebSocketMessageData =
|
||||
| Message
|
||||
| Channel
|
||||
| { id: number }
|
||||
| null
|
||||
| {
|
||||
type: WebSocketMessageType;
|
||||
};
|
||||
|
||||
export type WebsoketMessage = {
|
||||
type: WebSocketMessageType;
|
||||
data: WebSocketMessageData;
|
||||
};
|
||||
|
||||
export const appWebsocket = new EventEmitter<WebSocketMessageType, WebSocketMessageData>();
|
||||
|
||||
appWebsocket.on('any', (data) => {
|
||||
console.log(`[WS] Recieved message: `, data);
|
||||
});
|
||||
|
||||
function updateCache(type: WebSocketMessageType, data: WebSocketMessageData) {
|
||||
switch (type) {
|
||||
case 'createMessage':
|
||||
messagesCache.set((data as Message).id, data as Message);
|
||||
break;
|
||||
case 'updateChannel':
|
||||
channelsCache.set((data as Channel).id, data as Channel);
|
||||
break;
|
||||
case 'createChannel':
|
||||
channelsCache.set((data as Channel).id, data as Channel);
|
||||
break;
|
||||
case 'deleteChannel':
|
||||
channelsCache.remove((data as { id: number }).id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const connect = (token: string) => {
|
||||
const websocket = new WebSocket(`ws://${BASE_API_URL}/ws/${token}`);
|
||||
|
||||
websocket.onopen = () => {
|
||||
appWebsocket.emit('connect', null);
|
||||
appWebsocket.emit('any', { type: 'connect' });
|
||||
};
|
||||
websocket.onmessage = (event) => {
|
||||
const message: WebsoketMessage = JSON.parse(event.data);
|
||||
|
||||
updateCache(message.type, message.data);
|
||||
|
||||
appWebsocket.emit(message.type, message.data as WebSocketMessageData);
|
||||
appWebsocket.emit('any', message);
|
||||
};
|
||||
websocket.onclose = () => {
|
||||
appWebsocket.emit('disconnect', null);
|
||||
appWebsocket.emit('any', { type: 'disconnect' });
|
||||
|
||||
setTimeout(() => {
|
||||
const token = get(tokenStore);
|
||||
if (token) connect(token);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return websocket;
|
||||
};
|
||||
|
||||
const socket = derived<typeof tokenStore, WebSocket | null>(
|
||||
tokenStore,
|
||||
($token, set) => {
|
||||
if ($token) {
|
||||
set(connect($token));
|
||||
} else {
|
||||
set(null);
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
socket.subscribe((socket) => {
|
||||
console.log(`[WS] Connected: `, socket);
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
export type ErrorResponse = {
|
||||
error: string;
|
||||
}
|
||||
};
|
||||
|
||||
export function isErrorResponse(data: unknown): data is ErrorResponse {
|
||||
return ((data as ErrorResponse).error !== undefined);
|
||||
return (data as ErrorResponse).error !== undefined;
|
||||
}
|
||||
|
||||
export type Token = {
|
||||
@@ -11,7 +11,7 @@ export type Token = {
|
||||
userId: number;
|
||||
createdAt: string;
|
||||
expiresAt: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type User = {
|
||||
id: number;
|
||||
@@ -33,4 +33,4 @@ export type Channel = {
|
||||
name: string;
|
||||
lastMessageId?: number;
|
||||
createdAt: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,62 +1,60 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { cubicOut } from "svelte/easing";
|
||||
import type { TransitionConfig } from "svelte/transition";
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import type { TransitionConfig } from 'svelte/transition';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
type FlyAndScaleParams = {
|
||||
y?: number;
|
||||
x?: number;
|
||||
start?: number;
|
||||
duration?: number;
|
||||
y?: number;
|
||||
x?: number;
|
||||
start?: number;
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
export const flyAndScale = (
|
||||
node: Element,
|
||||
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
|
||||
node: Element,
|
||||
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
|
||||
): TransitionConfig => {
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === "none" ? "" : style.transform;
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === 'none' ? '' : style.transform;
|
||||
|
||||
const scaleConversion = (
|
||||
valueA: number,
|
||||
scaleA: [number, number],
|
||||
scaleB: [number, number]
|
||||
) => {
|
||||
const [minA, maxA] = scaleA;
|
||||
const [minB, maxB] = scaleB;
|
||||
const scaleConversion = (
|
||||
valueA: number,
|
||||
scaleA: [number, number],
|
||||
scaleB: [number, number]
|
||||
) => {
|
||||
const [minA, maxA] = scaleA;
|
||||
const [minB, maxB] = scaleB;
|
||||
|
||||
const percentage = (valueA - minA) / (maxA - minA);
|
||||
const valueB = percentage * (maxB - minB) + minB;
|
||||
const percentage = (valueA - minA) / (maxA - minA);
|
||||
const valueB = percentage * (maxB - minB) + minB;
|
||||
|
||||
return valueB;
|
||||
};
|
||||
return valueB;
|
||||
};
|
||||
|
||||
const styleToString = (
|
||||
style: Record<string, number | string | undefined>
|
||||
): string => {
|
||||
return Object.keys(style).reduce((str, key) => {
|
||||
if (style[key] === undefined) return str;
|
||||
return str + `${key}:${style[key]};`;
|
||||
}, "");
|
||||
};
|
||||
const styleToString = (style: Record<string, number | string | undefined>): string => {
|
||||
return Object.keys(style).reduce((str, key) => {
|
||||
if (style[key] === undefined) return str;
|
||||
return str + `${key}:${style[key]};`;
|
||||
}, '');
|
||||
};
|
||||
|
||||
return {
|
||||
duration: params.duration ?? 200,
|
||||
delay: 0,
|
||||
css: (t) => {
|
||||
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
|
||||
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
|
||||
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
|
||||
return {
|
||||
duration: params.duration ?? 200,
|
||||
delay: 0,
|
||||
css: (t) => {
|
||||
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
|
||||
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
|
||||
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
|
||||
|
||||
return styleToString({
|
||||
transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
|
||||
opacity: t
|
||||
});
|
||||
},
|
||||
easing: cubicOut
|
||||
};
|
||||
};
|
||||
return styleToString({
|
||||
transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
|
||||
opacity: t
|
||||
});
|
||||
},
|
||||
easing: cubicOut
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
import type { ParamMatcher } from '@sveltejs/kit';
|
||||
export const match: ParamMatcher = (param) => { return /^\d+$/.test(param); };
|
||||
export const match: ParamMatcher = (param) => {
|
||||
return /^\d+$/.test(param);
|
||||
};
|
||||
|
||||
@@ -3,20 +3,16 @@ import type { LayoutServerLoad } from './$types';
|
||||
import { getByToken } from '$lib/api/user';
|
||||
import { isErrorResponse } from '$lib/types';
|
||||
|
||||
export const prerender = false;
|
||||
export const ssr = false;
|
||||
// export const prerender = false;
|
||||
// export const ssr = false;
|
||||
|
||||
export const load = (async ({ cookies }) => {
|
||||
const token = cookies.get('token');
|
||||
|
||||
if (!token)
|
||||
return redirect(302, '/logout');
|
||||
if (!token) return redirect(302, '/logout');
|
||||
|
||||
const user = await getByToken(token)
|
||||
if (isErrorResponse(user))
|
||||
return redirect(302, '/logout');
|
||||
|
||||
console.log(`User: ${JSON.stringify(user)}`)
|
||||
const user = await getByToken(token);
|
||||
if (isErrorResponse(user)) return redirect(302, '/logout');
|
||||
|
||||
return { token, user };
|
||||
}) satisfies LayoutServerLoad;
|
||||
}) satisfies LayoutServerLoad;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { LayoutData } from './$types';
|
||||
import { token, user } from '$lib/stores/user';
|
||||
import { addUserToCache } from '$lib/stores/cache/users';
|
||||
import { token } from '$lib/stores/user';
|
||||
import { usersCache } from '$lib/stores/cache';
|
||||
import { appWebsocket } from '$lib/stores/websocket';
|
||||
|
||||
export let data: LayoutData;
|
||||
|
||||
console.log(`loading`);
|
||||
console.log(data);
|
||||
token.update(() => data.token);
|
||||
|
||||
$token = data.token;
|
||||
addUserToCache(data.user);
|
||||
const user = data.user;
|
||||
usersCache.set(user.id, user);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { createMessage } from '$lib/api/message';
|
||||
import * as Avatar from '$lib/components/ui/avatar';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area';
|
||||
import { isErrorResponse, type Channel, type Message as MessageType } from '$lib/types';
|
||||
import { type Channel, type Message, type Message as MessageType } from '$lib/types';
|
||||
import { afterUpdate } from 'svelte';
|
||||
import MessageArea from './message-area.svelte';
|
||||
import Message from './message.svelte';
|
||||
import TextField from './text-field.svelte';
|
||||
|
||||
export let channel: Channel | null = null;
|
||||
@@ -12,34 +11,40 @@
|
||||
|
||||
let messageArea: MessageArea;
|
||||
|
||||
const sendMessage = (message: string) => {
|
||||
const sendMessage = (content: string) => {
|
||||
if (!channel) return;
|
||||
|
||||
createMessage(channel.id, message).then((message) => {
|
||||
if (!isErrorResponse(message)) {
|
||||
messages = [...messages, message];
|
||||
messageArea.scroll('bottom');
|
||||
}
|
||||
});
|
||||
createMessage(channel.id, content);
|
||||
};
|
||||
|
||||
export function updateMessages(newMessages: Message[]) {
|
||||
messages = newMessages;
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
if (!messageArea) return;
|
||||
if (messageArea.getScrollPercent() > 0.95) messageArea.scroll('bottom', 'smooth');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-screen w-[95%] flex-col contain-strict">
|
||||
<div class="contents">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex h-screen w-full flex-col items-center contain-strict">
|
||||
<div class="w-full">
|
||||
<div class="flex items-center justify-center space-x-4 bg-secondary p-4">
|
||||
<Avatar.Root class="h-12 w-12">
|
||||
<Avatar.Image src="/default-avatar.png" />
|
||||
|
||||
<Avatar.Fallback>{channel?.name[0].toUpperCase() || ''}</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<span class="text-xl font-bold">{channel?.name || ''}</span>
|
||||
<span class="text-3xl font-bold">{channel?.name || ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="contents flex-grow">
|
||||
<MessageArea {messages} bind:this={messageArea} />
|
||||
</div>
|
||||
<div class="m-4 mt-1 max-h-[40%] flex-grow">
|
||||
<TextField onSend={sendMessage} />
|
||||
<div class="flex h-screen w-[95%] flex-col contain-strict">
|
||||
<div class="z-[10000] contents flex-grow">
|
||||
<MessageArea {messages} bind:this={messageArea} />
|
||||
</div>
|
||||
<div class="relative bottom-0 left-0 right-0 m-4 mt-1 max-h-[40%] flex-grow">
|
||||
<TextField onSend={sendMessage} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,21 +7,26 @@
|
||||
|
||||
let scrollArea: ScrollArea;
|
||||
|
||||
messages = [...messages].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
||||
$: messages = [...messages].sort(
|
||||
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
||||
);
|
||||
|
||||
export function scroll(anchor: 'top' | 'bottom') {
|
||||
if (scrollArea) {
|
||||
scrollArea.scroll(anchor);
|
||||
}
|
||||
export function scroll(anchor: 'top' | 'bottom', behavior: ScrollBehavior = 'smooth') {
|
||||
scrollArea.scroll(anchor, behavior);
|
||||
}
|
||||
|
||||
export function getScrollPercent() {
|
||||
return scrollArea.getScrollPercent();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="h-full w-full overflow-y-hidden">
|
||||
<ScrollArea class="h-full w-full" scrollToBottom={true} bind:this={scrollArea}>
|
||||
<div class="mx-4 flex flex-col gap-3">
|
||||
<div class="mx-4 flex flex-col gap-3 pt-4">
|
||||
{#each messages as message}
|
||||
<Message {message} />
|
||||
{/each}
|
||||
</div>
|
||||
<!-- <div class="h-[6rem] invisible" /> -->
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
@@ -3,15 +3,14 @@
|
||||
import { user } from '$lib/stores/user';
|
||||
import { cn } from '$lib/utils';
|
||||
import * as Avatar from '$lib/components/ui/avatar';
|
||||
import { getCachedUser } from '$lib/stores/cache/users';
|
||||
import { usersCache } from '$lib/stores/cache';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
|
||||
export let message: Message;
|
||||
|
||||
let sender: Writable<User | null> = writable(null);
|
||||
|
||||
getCachedUser(message.authorId).then((user) => ($sender = user));
|
||||
usersCache.get(message.authorId).then((user) => ($sender = user));
|
||||
|
||||
$: username = (isSelf ? $user?.username : $sender?.username) || 'N';
|
||||
$: isSelf = $user?.id === message.authorId;
|
||||
@@ -33,7 +32,7 @@
|
||||
<span class="whitespace-pre-line break-words break-all text-left text-xl font-bold">
|
||||
{message.content}
|
||||
</span>
|
||||
<span class={cn('text-md', timestampPosition)}
|
||||
<span class={cn('text-sm', timestampPosition)}
|
||||
>{new Date(message.createdAt).toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area';
|
||||
import { writable } from 'svelte/store';
|
||||
import { Send } from 'lucide-svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { browser } from '$app/environment';
|
||||
@@ -15,20 +14,11 @@
|
||||
const text = event.clipboardData?.getData('text/plain');
|
||||
|
||||
if (text && browser) {
|
||||
// if (window.getSelection) {
|
||||
// var selObj = window.getSelection();
|
||||
// var selRange = selObj?.getRangeAt(0);
|
||||
// selRange?.deleteContents();
|
||||
// selRange?.insertNode(document.createTextNode(text));
|
||||
|
||||
// selObj?.collapseToEnd();
|
||||
// }
|
||||
document.execCommand('insertText', false, text);
|
||||
}
|
||||
}
|
||||
|
||||
function onSendClick() {
|
||||
console.log(content);
|
||||
if (content) {
|
||||
onSend(content);
|
||||
content = '';
|
||||
@@ -36,7 +26,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full items-center gap-2 rounded-3xl bg-secondary p-2">
|
||||
<div
|
||||
class="flex h-full items-center gap-2 rounded-3xl bg-secondary p-2 shadow-md shadow-secondary-foreground dark:shadow-none"
|
||||
>
|
||||
<ScrollArea class="h-full w-full">
|
||||
<div
|
||||
contenteditable="true"
|
||||
|
||||
@@ -1,36 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { getMessageById } from '$lib/api/message';
|
||||
import * as Avatar from '$lib/components/ui/avatar';
|
||||
import { isErrorResponse, type Channel, type Message } from '$lib/types';
|
||||
import { usersCache, messagesCache } from '$lib/stores/cache';
|
||||
import { type Channel, type Message, type User } from '$lib/types';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
export let channel: Channel;
|
||||
|
||||
let lastMessage: Message | null = null;
|
||||
if (channel.lastMessageId) {
|
||||
getMessageById(channel.lastMessageId).then((message) => {
|
||||
if (!isErrorResponse(message)) lastMessage = message;
|
||||
});
|
||||
let lastMessageAuthor: User | null = null;
|
||||
|
||||
$: if (lastMessage) {
|
||||
usersCache.get(lastMessage.authorId).then((user) => (lastMessageAuthor = user));
|
||||
}
|
||||
$: if (channel.lastMessageId) {
|
||||
messagesCache.get(channel.lastMessageId).then((message) => (lastMessage = message));
|
||||
}
|
||||
|
||||
export let selected: boolean = false;
|
||||
export let onClick: () => void = () => {};
|
||||
|
||||
$: className = selected ? 'bg-accent' : 'hover:bg-secondary';
|
||||
$: buttonColor = selected ? 'bg-accent' : 'hover:bg-secondary-foreground';
|
||||
</script>
|
||||
|
||||
<button on:click={onClick} class={cn('flex w-full space-x-4 rounded-xl p-4', className)}>
|
||||
<button
|
||||
on:click={onClick}
|
||||
class={cn(
|
||||
'flex w-full space-x-4 rounded-xl p-4 transition-colors contain-inline-size',
|
||||
buttonColor
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<Avatar.Root class="h-12 w-12">
|
||||
<Avatar.Image src="/default-avatar.png" />
|
||||
<Avatar.Fallback>{channel.name[0].toUpperCase()}</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
</div>
|
||||
<div class="flex flex-col overflow-auto">
|
||||
<span class="overflow-hidden text-ellipsis text-left text-xl font-bold">{channel.name}</span
|
||||
>
|
||||
<span class="overflow-hidden text-ellipsis text-left text-sm"
|
||||
>{lastMessage?.content || ''}</span
|
||||
>
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
<span class="overflow-hidden text-ellipsis text-left text-xl font-bold">
|
||||
{channel.name}
|
||||
</span>
|
||||
<span class="overflow-hidden text-ellipsis whitespace-nowrap text-left text-sm">
|
||||
{#if lastMessage}
|
||||
{`${lastMessageAuthor?.username}: ${lastMessage.content}`}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { user } from '$lib/stores/user';
|
||||
import ThemeSwitch from '$lib/components/theme-switch.svelte';
|
||||
|
||||
export let menuItems: MenuItem[] = [];
|
||||
</script>
|
||||
|
||||
<div class="mx-4 my-3 flex w-full">
|
||||
<div class="mx-4 my-3 flex">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button builders={[builder]} variant="ghost" class="h-10 w-10 p-0">
|
||||
@@ -40,4 +42,12 @@
|
||||
{/each}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<div class="flex-grow pt-1 text-center text-2xl">
|
||||
{$user?.username}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ThemeSwitch />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { ScrollArea, ScrollAreaScrollbar } from '$lib/components/ui/scroll-area';
|
||||
|
||||
const tags = Array.from({ length: 50 }).map((_, i, a) => `v1.2.0-beta.${a.length - i}`);
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area';
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div class="light:shadow-secondary-foreground flex h-full w-full flex-col bg-secondary shadow-lg">
|
||||
<div class="p-0">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
import type { Icon } from 'lucide-svelte';
|
||||
import Settings from 'lucide-svelte/icons/settings';
|
||||
import LogOut from 'lucide-svelte/icons/log-out';
|
||||
import type { ComponentType } from 'svelte';
|
||||
import { onDestroy, onMount, type ComponentType } from 'svelte';
|
||||
import type { MenuItem } from './(components)/sidebar-header.svelte';
|
||||
import ChannelList from './(components)/channel-list.svelte';
|
||||
import { addChannelToCache } from '$lib/stores/cache/channels';
|
||||
import { appWebsocket, type WebSocketMessageType } from '$lib/stores/websocket';
|
||||
import type { Channel } from '$lib/types';
|
||||
|
||||
export let data: LayoutData;
|
||||
|
||||
for (const channel of data.channels) addChannelToCache(channel);
|
||||
let channels = data.channels;
|
||||
|
||||
let channelList: ChannelList | undefined;
|
||||
|
||||
@@ -44,6 +45,51 @@
|
||||
];
|
||||
|
||||
$: channelId = parseInt($page.params.channel_id);
|
||||
|
||||
function handleChannelCreated(channel: unknown) {
|
||||
const typedChannel = channel as Channel;
|
||||
|
||||
if (!channelList) return;
|
||||
|
||||
channels.push(typedChannel);
|
||||
}
|
||||
|
||||
function handleChannelUpdated(channel: unknown) {
|
||||
const typedChannel = channel as Channel;
|
||||
|
||||
if (!channelList) return;
|
||||
|
||||
for (let i = 0; i < channels.length; i++) {
|
||||
if (channels[i].id == typedChannel.id) {
|
||||
channels[i] = typedChannel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleChannelDeleted(channel_id: unknown) {
|
||||
const id = (channel_id as { id: number }).id;
|
||||
|
||||
if (!channelList) return;
|
||||
|
||||
channels = channels.filter((c) => c.id != id);
|
||||
}
|
||||
|
||||
const handlers = {
|
||||
createChannel: handleChannelCreated,
|
||||
updateChannel: handleChannelUpdated,
|
||||
deleteChannel: handleChannelDeleted
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
for (const [key, value] of Object.entries(handlers))
|
||||
appWebsocket.on(key as WebSocketMessageType, value);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
for (const [key, value] of Object.entries(handlers))
|
||||
appWebsocket.off(key as WebSocketMessageType, value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:keyup={onKeyUp} />
|
||||
@@ -56,11 +102,7 @@
|
||||
</div>
|
||||
|
||||
<div slot="channels">
|
||||
<ChannelList
|
||||
channels={data.channels}
|
||||
defaultSelected={channelId}
|
||||
bind:this={channelList}
|
||||
/>
|
||||
<ChannelList {channels} defaultSelected={channelId} bind:this={channelList} />
|
||||
</div>
|
||||
</Sidebar>
|
||||
</div>
|
||||
|
||||
@@ -2,13 +2,14 @@ import { getAllChannels } from '$lib/api/channel';
|
||||
import type { Channel } from '$lib/types';
|
||||
import type { LayoutLoad } from './$types';
|
||||
|
||||
export const ssr = false;
|
||||
|
||||
export const load = (async ({ parent }) => {
|
||||
await parent();
|
||||
|
||||
const response = await getAllChannels();
|
||||
|
||||
const channels = response as Channel[]
|
||||
const channels = response as Channel[];
|
||||
|
||||
return { channels };
|
||||
}) satisfies LayoutLoad;
|
||||
}) satisfies LayoutLoad;
|
||||
|
||||
@@ -2,21 +2,40 @@
|
||||
import type { PageData } from './$types';
|
||||
import { page } from '$app/stores';
|
||||
import ChannelArea from '../(components)/(channel)/channel-area.svelte';
|
||||
import { getCachedChannel } from '$lib/stores/cache/channels';
|
||||
import type { Channel } from '$lib/types';
|
||||
import { addMessageToCache } from '$lib/stores/cache/messages';
|
||||
import { channelsCache } from '$lib/stores/cache';
|
||||
import type { Channel, Message } from '$lib/types';
|
||||
import { appWebsocket } from '$lib/stores/websocket';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let channel: Channel | null = null;
|
||||
$: channelId = parseInt($page.params.channel_id);
|
||||
$: getCachedChannel(channelId).then((c) => (channel = c));
|
||||
$: channelsCache.get(channelId).then((c) => (channel = c));
|
||||
|
||||
for (const message of data.messages)
|
||||
addMessageToCache(message);
|
||||
|
||||
const messages = data.messages;
|
||||
|
||||
let channelArea: ChannelArea;
|
||||
|
||||
function handleCreateMessage(message: unknown) {
|
||||
const typedMessage = message as Message;
|
||||
|
||||
if (!channel) return;
|
||||
if (typedMessage.channelId != channel.id) return;
|
||||
|
||||
messages.push(typedMessage);
|
||||
channelArea?.updateMessages(messages);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
appWebsocket.on('createMessage', handleCreateMessage);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
appWebsocket.off('createMessage', handleCreateMessage);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-screen w-full items-center justify-center">
|
||||
<ChannelArea {channel} messages={data.messages} />
|
||||
<ChannelArea {channel} {messages} bind:this={channelArea} />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getMessagesByChannelId } from '$lib/api/channel';
|
||||
import { getCachedChannel } from '$lib/stores/cache/channels';
|
||||
import { channelsCache } from '$lib/stores/cache';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
import { isErrorResponse } from '$lib/types';
|
||||
@@ -9,15 +9,13 @@ export const ssr = false;
|
||||
export const load = (async ({ params, parent }) => {
|
||||
await parent();
|
||||
|
||||
const channelId = parseInt(params.channel_id)
|
||||
const channel = await getCachedChannel(channelId);
|
||||
if (!channel)
|
||||
return redirect(302, '/channels');
|
||||
const channelId = parseInt(params.channel_id);
|
||||
const channel = await channelsCache.get(channelId);
|
||||
if (!channel) return redirect(302, '/channels');
|
||||
|
||||
const messages = await getMessagesByChannelId(channel.id, channel.lastMessageId);
|
||||
|
||||
if (isErrorResponse(messages))
|
||||
return redirect(302, '/channels');
|
||||
if (isErrorResponse(messages)) return redirect(302, '/channels');
|
||||
|
||||
return { messages };
|
||||
}) satisfies PageLoad;
|
||||
}) satisfies PageLoad;
|
||||
|
||||
12
src/routes/(forms)/+layout.svelte
Normal file
12
src/routes/(forms)/+layout.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import ThemeSwitch from '$lib/components/theme-switch.svelte';
|
||||
import { Fan } from 'lucide-svelte';
|
||||
</script>
|
||||
|
||||
<div class="flex h-full w-full items-center justify-center">
|
||||
<ThemeSwitch class="absolute right-4 top-4" />
|
||||
<div class="flex h-full w-full flex-col items-center justify-center gap-8">
|
||||
<Fan class="h-32 w-32" />
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,15 +1,13 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { zod } from "sveltekit-superforms/adapters";
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { loginFormSchema } from './login-form.svelte';
|
||||
import { superValidate } from "sveltekit-superforms";
|
||||
import { type Actions, fail, redirect } from "@sveltejs/kit";
|
||||
import { superValidate } from 'sveltekit-superforms';
|
||||
import { type Actions, fail, redirect } from '@sveltejs/kit';
|
||||
import { loginUser } from '$lib/api/user';
|
||||
import { isErrorResponse } from '$lib/types';
|
||||
|
||||
|
||||
export const load = (async ({ cookies }) => {
|
||||
if (cookies.get('token'))
|
||||
throw redirect(302, '/channels');
|
||||
if (cookies.get('token')) throw redirect(302, '/channels');
|
||||
|
||||
return { form: await superValidate(zod(loginFormSchema)) };
|
||||
}) satisfies PageServerLoad;
|
||||
@@ -18,8 +16,7 @@ export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const form = await superValidate(event, zod(loginFormSchema));
|
||||
|
||||
if (!form.valid)
|
||||
return fail(400, { form });
|
||||
if (!form.valid) return fail(400, { form });
|
||||
|
||||
const response = await loginUser(form.data.username, form.data.password);
|
||||
|
||||
@@ -31,5 +28,5 @@ export const actions: Actions = {
|
||||
event.cookies.set('token', response.token, { path: '/' });
|
||||
|
||||
return { form, token: response };
|
||||
},
|
||||
}
|
||||
};
|
||||
11
src/routes/(forms)/login/+page.svelte
Normal file
11
src/routes/(forms)/login/+page.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import LoginForm from './login-form.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="w-[20%] min-w-[300px] space-y-4 rounded-lg bg-secondary p-4">
|
||||
<h1 class="text-center text-4xl font-bold">Login</h1>
|
||||
<LoginForm data={data.form}></LoginForm>
|
||||
</div>
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { superValidate } from "sveltekit-superforms";
|
||||
import { type Actions, fail } from "@sveltejs/kit";
|
||||
import { zod } from "sveltekit-superforms/adapters";
|
||||
import { superValidate } from 'sveltekit-superforms';
|
||||
import { type Actions, fail } from '@sveltejs/kit';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { registerFormSchema } from './register-form.svelte';
|
||||
import { registerUser } from '$lib/api/user';
|
||||
import { isErrorResponse } from '$lib/types';
|
||||
@@ -14,8 +14,7 @@ export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const form = await superValidate(event, zod(registerFormSchema));
|
||||
|
||||
if (!form.valid)
|
||||
return fail(400, { form });
|
||||
if (!form.valid) return fail(400, { form });
|
||||
|
||||
const response = await registerUser(form.data.username, form.data.password);
|
||||
|
||||
@@ -26,4 +25,4 @@ export const actions: Actions = {
|
||||
|
||||
return { form, success: true };
|
||||
}
|
||||
}
|
||||
};
|
||||
11
src/routes/(forms)/register/+page.svelte
Normal file
11
src/routes/(forms)/register/+page.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import RegisterForm from './register-form.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="w-[20%] min-w-[300px] space-y-4 rounded-lg bg-secondary p-4">
|
||||
<h1 class="text-center text-4xl font-bold">Register</h1>
|
||||
<RegisterForm data={data.form}></RegisterForm>
|
||||
</div>
|
||||
@@ -25,9 +25,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Form from '$lib/components/ui/form';
|
||||
import { superForm, type SuperValidated } from 'sveltekit-superforms';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export let data: SuperValidated<RegisterFormSchema>;
|
||||
export let loginUrl: string = '/login';
|
||||
@@ -1,5 +1,7 @@
|
||||
<script>
|
||||
import '../app.css';
|
||||
|
||||
import { theme } from '$lib/stores/theme';
|
||||
</script>
|
||||
|
||||
<div class="h-full w-full">
|
||||
|
||||
@@ -2,5 +2,5 @@ import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load = (async () => {
|
||||
redirect(302, '/login')
|
||||
}) satisfies PageServerLoad;
|
||||
redirect(302, '/login');
|
||||
}) satisfies PageServerLoad;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Fan } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import LoginForm from './login-form.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="flex h-full w-full flex-col items-center justify-center gap-8">
|
||||
<Fan class="h-32 w-32" />
|
||||
<div class="w-[20%] min-w-[300px] space-y-4 rounded-lg bg-secondary p-4">
|
||||
<h1 class="text-center text-4xl font-bold">Login</h1>
|
||||
<LoginForm data={data.form}></LoginForm>
|
||||
</div>
|
||||
</div>
|
||||
@@ -4,4 +4,4 @@ export const load = (async ({ cookies }) => {
|
||||
cookies.delete('token', { path: '/' });
|
||||
|
||||
return {};
|
||||
}) satisfies PageServerLoad;
|
||||
}) satisfies PageServerLoad;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
if (browser) {
|
||||
$token = null;
|
||||
|
||||
|
||||
goto('/login');
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import type { PageData } from './$types';
|
||||
import RegisterForm from './register-form.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="flex h-full w-full items-center justify-center">
|
||||
<div class="w-[20%] min-w-[300px] space-y-4 rounded-lg bg-secondary p-4">
|
||||
<h1 class="text-center text-4xl font-bold">Register</h1>
|
||||
<RegisterForm data={data.form}></RegisterForm>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3,16 +3,16 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
import { fontFamily } from "tailwindcss/defaultTheme";
|
||||
import type { Config } from "tailwindcss";
|
||||
import { fontFamily } from 'tailwindcss/defaultTheme';
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
const config: Config = {
|
||||
darkMode: ["class"],
|
||||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||
safelist: ["dark"],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px"
|
||||
}
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border) / <alpha-value>)",
|
||||
input: "hsl(var(--input) / <alpha-value>)",
|
||||
ring: "hsl(var(--ring) / <alpha-value>)",
|
||||
background: "hsl(var(--background) / <alpha-value>)",
|
||||
foreground: "hsl(var(--foreground) / <alpha-value>)",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
|
||||
foreground: "hsl(var(--primary-foreground) / <alpha-value>)"
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
|
||||
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)"
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
|
||||
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)"
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
|
||||
foreground: "hsl(var(--muted-foreground) / <alpha-value>)"
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "#AD5CD6",
|
||||
foreground: "hsl(var(--accent-foreground) / <alpha-value>)"
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
|
||||
foreground: "hsl(var(--popover-foreground) / <alpha-value>)"
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card) / <alpha-value>)",
|
||||
foreground: "hsl(var(--card-foreground) / <alpha-value>)"
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)"
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [...fontFamily.sans]
|
||||
}
|
||||
}
|
||||
},
|
||||
darkMode: ['class'],
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
safelist: ['dark'],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: '2rem',
|
||||
screens: {
|
||||
'2xl': '1400px'
|
||||
}
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: 'hsl(var(--border) / <alpha-value>)',
|
||||
input: 'hsl(var(--input) / <alpha-value>)',
|
||||
ring: 'hsl(var(--ring) / <alpha-value>)',
|
||||
background: 'hsl(var(--background) / <alpha-value>)',
|
||||
foreground: 'hsl(var(--foreground) / <alpha-value>)',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary) / <alpha-value>)',
|
||||
foreground: 'hsl(var(--primary-foreground) / <alpha-value>)'
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary) / <alpha-value>)',
|
||||
foreground: 'hsl(var(--secondary-foreground) / <alpha-value>)'
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive) / <alpha-value>)',
|
||||
foreground: 'hsl(var(--destructive-foreground) / <alpha-value>)'
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted) / <alpha-value>)',
|
||||
foreground: 'hsl(var(--muted-foreground) / <alpha-value>)'
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: '#AD5CD6',
|
||||
foreground: 'hsl(var(--accent-foreground) / <alpha-value>)'
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover) / <alpha-value>)',
|
||||
foreground: 'hsl(var(--popover-foreground) / <alpha-value>)'
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card) / <alpha-value>)',
|
||||
foreground: 'hsl(var(--card-foreground) / <alpha-value>)'
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)'
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [...fontFamily.sans]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user