Daisy UI (#14)
* add dependency * rename Input to TextInput and use daisy * base styling * storybook setup with tailwind and theme changer * daisy buttons * add flaticons * text input to daisy * Navbar to daisy * login using daisy * autodocs is... auto * refactor Tabs to separate components * move TextInput * move button * move navbar * remove index * move container * move loader * move tabs to navigation * organize storybook hierarchy * use card * remove storybook dark mode * README * ignore db file * ignore db * prisma scripts * format * blyat * fix redirect
This commit is contained in:
parent
992eb07f5c
commit
6ddaa69f69
69 changed files with 663 additions and 449 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -17,4 +17,5 @@ Thumbs.db
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
*storybook.log
|
*/dev.db
|
||||||
|
*/dev.db-journal
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
{
|
{
|
||||||
"useTabs": true,
|
"useTabs": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "none",
|
"trailingComma": "es5",
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"arrowParens": "always",
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"semi": true,
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"tabWidth": 4,
|
||||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,16 @@
|
||||||
const config = {
|
const config = {
|
||||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'],
|
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'],
|
||||||
addons: [
|
addons: [
|
||||||
'@storybook/addon-svelte-csf',
|
|
||||||
'@storybook/addon-essentials',
|
|
||||||
'@chromatic-com/storybook',
|
'@chromatic-com/storybook',
|
||||||
|
'@storybook/addon-essentials',
|
||||||
'@storybook/addon-interactions',
|
'@storybook/addon-interactions',
|
||||||
'storybook-dark-mode'
|
'@storybook/addon-styling-webpack',
|
||||||
|
'@storybook/addon-svelte-csf',
|
||||||
|
'@storybook/addon-themes',
|
||||||
],
|
],
|
||||||
framework: {
|
framework: {
|
||||||
name: '@storybook/sveltekit',
|
name: '@storybook/sveltekit',
|
||||||
options: {}
|
options: {},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
4
.storybook/preview.css
Normal file
4
.storybook/preview.css
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
@import 'tailwindcss/base';
|
||||||
|
@import 'tailwindcss/components';
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
|
@import '@flaticon/flaticon-uicons/css/all/all';
|
||||||
|
|
@ -1,13 +1,28 @@
|
||||||
|
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
||||||
|
import './preview.css';
|
||||||
|
|
||||||
/** @type { import('@storybook/svelte').Preview } */
|
/** @type { import('@storybook/svelte').Preview } */
|
||||||
const preview = {
|
const preview = {
|
||||||
|
tags: ['autodocs'],
|
||||||
parameters: {
|
parameters: {
|
||||||
controls: {
|
controls: {
|
||||||
matchers: {
|
matchers: {
|
||||||
color: /(background|color)$/i,
|
color: /(background|color)$/i,
|
||||||
date: /Date$/i
|
date: /Date$/i,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
decorators: [
|
||||||
|
withThemeByDataAttribute({
|
||||||
|
themes: {
|
||||||
|
light: 'light',
|
||||||
|
dark: 'dark',
|
||||||
|
night: 'night',
|
||||||
|
},
|
||||||
|
defaultTheme: 'dark',
|
||||||
|
attributeName: 'data-theme',
|
||||||
|
}),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default preview;
|
export default preview;
|
||||||
57
README.md
57
README.md
|
|
@ -1,38 +1,69 @@
|
||||||
# sv
|
# Hestia
|
||||||
|
|
||||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
Hestia is an early stage project
|
||||||
|
|
||||||
## Creating a project
|
## Setup
|
||||||
|
|
||||||
If you're seeing this, you've probably already done this step. Congrats!
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# create a new project in the current directory
|
# install dependencies
|
||||||
bunx sv create
|
bun install
|
||||||
|
|
||||||
# create a new project in my-app
|
# set up local database
|
||||||
bunx sv create my-app
|
bun prisma:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
Once you've created a project and installed dependencies with `bun install`, start a development server:
|
Once you've created a project and installed dependencies, start a development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run dev
|
bun dev
|
||||||
|
|
||||||
# or start the server and open the app in a new browser tab
|
# or start the server and open the app in a new browser tab
|
||||||
bun run dev -- --open
|
bun dev -- --open
|
||||||
|
|
||||||
|
# to use storybook for components development
|
||||||
|
bun storybook
|
||||||
|
|
||||||
|
# interact with local database
|
||||||
|
bun prisma:studio
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> You can access the Yoga web-app at `/api/graphql`
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
To create a production version of your app:
|
To create a production version of your app:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run build
|
bun build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- https://svelte.dev/docs/kit/introduction
|
||||||
|
- https://zod.dev/
|
||||||
|
- https://day.js.org/
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
- https://tailwindcss.com/
|
||||||
|
- https://www.flaticon.com/
|
||||||
|
- https://daisyui.com/
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
- https://www.prisma.io/
|
||||||
|
- https://pothos-graphql.dev/
|
||||||
|
- https://the-guild.dev/graphql/yoga-server
|
||||||
|
- https://github.com/pinojs/pino
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
- https://storybook.js.org/
|
||||||
|
- https://vite.dev/
|
||||||
|
- https://vitest.dev/
|
||||||
|
|
||||||
You can preview the production build with `bun run preview`.
|
You can preview the production build with `bun run preview`.
|
||||||
|
|
||||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||||
|
|
|
||||||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
3
docs/DAISY.md
Normal file
3
docs/DAISY.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Experienced Issues
|
||||||
|
|
||||||
|
- https://github.com/saadeghi/daisyui/issues/811
|
||||||
|
|
@ -18,17 +18,17 @@ export default ts.config(
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
...globals.browser,
|
...globals.browser,
|
||||||
...globals.node
|
...globals.node,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.svelte'],
|
files: ['**/*.svelte'],
|
||||||
|
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
parser: ts.parser
|
parser: ts.parser,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
12
package.json
12
package.json
|
|
@ -15,7 +15,12 @@
|
||||||
"storybook": "storybook dev -p 6006",
|
"storybook": "storybook dev -p 6006",
|
||||||
"build-storybook": "storybook build",
|
"build-storybook": "storybook build",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
"prisma:generate": "prisma generate"
|
"prisma:dev": "prisma migrate dev",
|
||||||
|
"prisma:format": "prisma format",
|
||||||
|
"prisma:generate": "prisma generate",
|
||||||
|
"prisma:push": "prisma db push",
|
||||||
|
"prisma:reset": "prisma migrate reset --force",
|
||||||
|
"prisma:studio": "prisma studio"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@chromatic-com/storybook": "^3.2.2",
|
"@chromatic-com/storybook": "^3.2.2",
|
||||||
|
|
@ -23,7 +28,9 @@
|
||||||
"@playwright/test": "^1.45.3",
|
"@playwright/test": "^1.45.3",
|
||||||
"@storybook/addon-essentials": "^8.4.7",
|
"@storybook/addon-essentials": "^8.4.7",
|
||||||
"@storybook/addon-interactions": "^8.4.7",
|
"@storybook/addon-interactions": "^8.4.7",
|
||||||
|
"@storybook/addon-styling-webpack": "^1.0.1",
|
||||||
"@storybook/addon-svelte-csf": "^5.0.0-next.13",
|
"@storybook/addon-svelte-csf": "^5.0.0-next.13",
|
||||||
|
"@storybook/addon-themes": "^8.4.7",
|
||||||
"@storybook/blocks": "^8.4.7",
|
"@storybook/blocks": "^8.4.7",
|
||||||
"@storybook/svelte": "^8.4.7",
|
"@storybook/svelte": "^8.4.7",
|
||||||
"@storybook/sveltekit": "^8.4.7",
|
"@storybook/sveltekit": "^8.4.7",
|
||||||
|
|
@ -33,6 +40,7 @@
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
"@types/bun": "^1.1.14",
|
"@types/bun": "^1.1.14",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
|
"daisyui": "^4.12.22",
|
||||||
"eslint": "^9.7.0",
|
"eslint": "^9.7.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.36.0",
|
"eslint-plugin-svelte": "^2.36.0",
|
||||||
|
|
@ -42,7 +50,6 @@
|
||||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||||
"prisma": "^6.0.1",
|
"prisma": "^6.0.1",
|
||||||
"storybook": "^8.4.7",
|
"storybook": "^8.4.7",
|
||||||
"storybook-dark-mode": "^4.0.2",
|
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.0.0",
|
||||||
"tailwindcss": "^3.4.9",
|
"tailwindcss": "^3.4.9",
|
||||||
|
|
@ -52,6 +59,7 @@
|
||||||
"vitest": "^2.0.4"
|
"vitest": "^2.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@flaticon/flaticon-uicons": "^3.3.1",
|
||||||
"@lucia-auth/adapter-prisma": "^4.0.1",
|
"@lucia-auth/adapter-prisma": "^4.0.1",
|
||||||
"@pothos/core": "^4.3.0",
|
"@pothos/core": "^4.3.0",
|
||||||
"@pothos/plugin-prisma": "^4.4.0",
|
"@pothos/plugin-prisma": "^4.4.0",
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { defineConfig } from '@playwright/test';
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run build && npm run preview',
|
command: 'npm run build && npm run preview',
|
||||||
port: 4173
|
port: 4173,
|
||||||
},
|
},
|
||||||
|
|
||||||
testDir: 'e2e'
|
testDir: 'e2e',
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {}
|
autoprefixer: {},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- The primary key for the `Post` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- RedefineTables
|
|
||||||
PRAGMA defer_foreign_keys=ON;
|
|
||||||
PRAGMA foreign_keys=OFF;
|
|
||||||
CREATE TABLE "new_Post" (
|
|
||||||
"id" TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"title" TEXT NOT NULL,
|
|
||||||
"content" TEXT NOT NULL,
|
|
||||||
"published" BOOLEAN DEFAULT false,
|
|
||||||
"authorId" TEXT NOT NULL,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
|
||||||
);
|
|
||||||
INSERT INTO "new_Post" ("authorId", "content", "createdAt", "id", "published", "title", "updatedAt") SELECT "authorId", "content", "createdAt", "id", "published", "title", "updatedAt" FROM "Post";
|
|
||||||
DROP TABLE "Post";
|
|
||||||
ALTER TABLE "new_Post" RENAME TO "Post";
|
|
||||||
PRAGMA foreign_keys=ON;
|
|
||||||
PRAGMA defer_foreign_keys=OFF;
|
|
||||||
34
prisma/migrations/20241220011159_init/migration.sql
Normal file
34
prisma/migrations/20241220011159_init/migration.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"email" TEXT,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Session" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"expiresAt" DATETIME NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Post" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"published" BOOLEAN DEFAULT false,
|
||||||
|
"authorId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||||
|
|
@ -15,34 +15,38 @@ datasource db {
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
email String? @unique
|
|
||||||
name String
|
email String? @unique
|
||||||
password String
|
name String
|
||||||
posts Post[]
|
password String
|
||||||
sessions Session[]
|
posts Post[]
|
||||||
|
sessions Session[]
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
model Session {
|
model Session {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
expiresAt DateTime
|
|
||||||
createdAt DateTime @default(now())
|
expiresAt DateTime
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
user User @relation(references: [id], fields: [userId])
|
||||||
userId String
|
userId String
|
||||||
user User @relation(references: [id], fields: [userId])
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
model Post {
|
model Post {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|
||||||
title String
|
title String
|
||||||
content String
|
content String
|
||||||
published Boolean? @default(false)
|
published Boolean? @default(false)
|
||||||
author User @relation(references: [id], fields: [authorId])
|
author User @relation(references: [id], fields: [authorId])
|
||||||
authorId String
|
authorId String
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
}
|
}
|
||||||
15
src/app.css
15
src/app.css
|
|
@ -1,19 +1,8 @@
|
||||||
@import 'tailwindcss/base';
|
@import 'tailwindcss/base';
|
||||||
@import 'tailwindcss/components';
|
@import 'tailwindcss/components';
|
||||||
@import 'tailwindcss/utilities';
|
@import 'tailwindcss/utilities';
|
||||||
|
@import '@flaticon/flaticon-uicons/css/all/all';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@apply text-slate-800;
|
@apply text-base-content;
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
@apply font-display text-4xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
@apply font-display text-3xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
@apply font-display text-2xl;
|
|
||||||
}
|
}
|
||||||
3
src/app.d.ts
vendored
3
src/app.d.ts
vendored
|
|
@ -2,10 +2,9 @@
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
|
|
||||||
// interface Error {}
|
// interface Error {}
|
||||||
interface Locals {
|
interface Locals {
|
||||||
user: import("lucia").User | null;
|
user: import('lucia').User | null;
|
||||||
session: import('lucia').Session | null;
|
session: import('lucia').Session | null;
|
||||||
}
|
}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
<script module lang="ts">
|
|
||||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
||||||
import Button from './Button.svelte';
|
|
||||||
import { fn } from '@storybook/test';
|
|
||||||
|
|
||||||
const { Story } = defineMeta({
|
|
||||||
title: 'Button',
|
|
||||||
component: Button,
|
|
||||||
tags: ['autodocs'],
|
|
||||||
args: {
|
|
||||||
onClick: fn()
|
|
||||||
},
|
|
||||||
argTypes: {
|
|
||||||
size: {
|
|
||||||
control: 'select',
|
|
||||||
options: ['small', 'normal', 'large'],
|
|
||||||
defaultValue: 'normal'
|
|
||||||
},
|
|
||||||
backgroundColor: {
|
|
||||||
control: 'color'
|
|
||||||
},
|
|
||||||
primary: {
|
|
||||||
control: 'boolean',
|
|
||||||
defaultValue: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Story name="Default" args={{ label: 'Button', size: 'normal', primary: true }} />
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
||||||
|
|
||||||
let {
|
|
||||||
type = 'button',
|
|
||||||
onClick,
|
|
||||||
label,
|
|
||||||
size = 'normal',
|
|
||||||
backgroundColor,
|
|
||||||
primary = false
|
|
||||||
}: {
|
|
||||||
type?: HTMLButtonAttributes['type'];
|
|
||||||
onClick?: () => void;
|
|
||||||
label: string;
|
|
||||||
size?: 'small' | 'normal' | 'large';
|
|
||||||
backgroundColor?: string;
|
|
||||||
primary?: boolean;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button
|
|
||||||
{type}
|
|
||||||
onclick={onClick}
|
|
||||||
class={`button button--${size}`}
|
|
||||||
style:background-color={backgroundColor}
|
|
||||||
class:button--primary={primary}
|
|
||||||
class:button--secondary={!primary}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.button {
|
|
||||||
@apply inline-block cursor-pointer rounded-lg border-0 font-semibold leading-none transition-colors hover:opacity-80 hover:shadow-lg;
|
|
||||||
}
|
|
||||||
.button--small {
|
|
||||||
@apply px-2 py-0.5 text-sm;
|
|
||||||
}
|
|
||||||
.button--normal {
|
|
||||||
@apply px-2 py-1 text-base;
|
|
||||||
}
|
|
||||||
.button--large {
|
|
||||||
@apply px-3 py-1.5 text-lg;
|
|
||||||
}
|
|
||||||
.button--primary {
|
|
||||||
@apply bg-red-600 text-white;
|
|
||||||
}
|
|
||||||
.button--secondary {
|
|
||||||
@apply bg-blue-600 text-white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
<script module lang="ts">
|
|
||||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
||||||
import Input from './Input.svelte';
|
|
||||||
|
|
||||||
const { Story } = defineMeta({
|
|
||||||
title: 'Input',
|
|
||||||
component: Input,
|
|
||||||
tags: ['autodocs'],
|
|
||||||
argTypes: {
|
|
||||||
type: {
|
|
||||||
control: 'select',
|
|
||||||
options: [
|
|
||||||
'number',
|
|
||||||
'button',
|
|
||||||
'checkbox',
|
|
||||||
'color',
|
|
||||||
'date',
|
|
||||||
'datetime-local',
|
|
||||||
'email',
|
|
||||||
'file',
|
|
||||||
'hidden',
|
|
||||||
'image',
|
|
||||||
'month',
|
|
||||||
'password',
|
|
||||||
'radio',
|
|
||||||
'range',
|
|
||||||
'reset',
|
|
||||||
'search',
|
|
||||||
'submit',
|
|
||||||
'tel',
|
|
||||||
'text',
|
|
||||||
'time',
|
|
||||||
'url',
|
|
||||||
'week'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Story name="Text" args={{ label: 'Text', name: 'text', type: 'text' }} />
|
|
||||||
<Story name="Password" args={{ label: 'Password', name: 'pass', type: 'password' }} />
|
|
||||||
<Story name="Email" args={{ label: 'Email', name: 'email', type: 'email' }} />
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
|
||||||
|
|
||||||
type InputProps = {
|
|
||||||
label?: string;
|
|
||||||
name: string;
|
|
||||||
type?: HTMLInputTypeAttribute;
|
|
||||||
} & HTMLAttributes<HTMLDivElement>;
|
|
||||||
|
|
||||||
let { label, name, type = 'text', ...props }: InputProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div {...props} class={twMerge('hestia-input', props.class)}>
|
|
||||||
{#if label}
|
|
||||||
<label for={name}>{label}</label>
|
|
||||||
<div class="line"></div>
|
|
||||||
{/if}
|
|
||||||
<input {name} id={name} {type} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.line {
|
|
||||||
@apply h-8 border border-l-slate-200;
|
|
||||||
}
|
|
||||||
.hestia-input {
|
|
||||||
@apply flex w-fit items-center gap-2 rounded-lg bg-slate-100 p-2 text-slate-700 outline-blue-400 focus-within:outline;
|
|
||||||
}
|
|
||||||
.hestia-input > label {
|
|
||||||
@apply font-display text-lg;
|
|
||||||
}
|
|
||||||
.hestia-input > input {
|
|
||||||
all: unset;
|
|
||||||
@apply text-lg;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
let { title }: { title: string } = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<header>
|
|
||||||
<div class="navbar">
|
|
||||||
<div>
|
|
||||||
<h2>Hestia</h2>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h1>{title}</h1>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p>Welcome!</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.navbar {
|
|
||||||
@apply flex items-center justify-between border-b border-slate-200 bg-slate-50 px-6 py-2 font-display drop-shadow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar h1 {
|
|
||||||
@apply text-2xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar h2 {
|
|
||||||
@apply text-xl;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -3,9 +3,8 @@
|
||||||
import Navbar from './Navbar.svelte';
|
import Navbar from './Navbar.svelte';
|
||||||
|
|
||||||
const { Story } = defineMeta({
|
const { Story } = defineMeta({
|
||||||
title: 'Navbar',
|
title: 'Navigation/Navbar',
|
||||||
component: Navbar,
|
component: Navbar,
|
||||||
tags: ['autodocs']
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
12
src/lib/components/Navigation/Navbar/Navbar.svelte
Normal file
12
src/lib/components/Navigation/Navbar/Navbar.svelte
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script lang="ts">
|
||||||
|
let { title }: { title: string } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header class="navbar justify-between bg-base-200 px-4">
|
||||||
|
<h2 class="prose prose-xl">Hestia</h2>
|
||||||
|
<h1 class="prose prose-2xl">{title}</h1>
|
||||||
|
<p class="prose prose-lg">Welcome!</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
3
src/lib/components/Navigation/Navbar/index.ts
Normal file
3
src/lib/components/Navigation/Navbar/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Navbar from './Navbar.svelte';
|
||||||
|
|
||||||
|
export default Navbar;
|
||||||
5
src/lib/components/Navigation/Tabs/Tab.svelte
Normal file
5
src/lib/components/Navigation/Tabs/Tab.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script lang="ts">
|
||||||
|
let { active, label, onclick } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input aria-label={label} type="radio" role="tab" class="tab" class:tab-active={active} {onclick} />
|
||||||
27
src/lib/components/Navigation/Tabs/Tabs.stories.svelte
Normal file
27
src/lib/components/Navigation/Tabs/Tabs.stories.svelte
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script module lang="ts">
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import type { ComponentProps } from 'svelte';
|
||||||
|
import Tabs from './Tabs.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Navigation/Tabs',
|
||||||
|
component: Tabs,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['xs', 'sm', 'rg', 'lg'],
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['none', 'bordered', 'lifted', 'boxed'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#snippet template(args: Partial<ComponentProps<typeof Tabs>>)}
|
||||||
|
<Tabs tabs={['Tab 1', 'Tab 2']} {...args} />
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<Story name="Default" args={{}} children={template} />
|
||||||
36
src/lib/components/Navigation/Tabs/Tabs.svelte
Normal file
36
src/lib/components/Navigation/Tabs/Tabs.svelte
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { DaisySize } from '$lib/types';
|
||||||
|
import Tab from './Tab.svelte';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
size?: DaisySize;
|
||||||
|
tabs: string[];
|
||||||
|
selected?: number;
|
||||||
|
variant?: 'none' | 'bordered' | 'lifted' | 'boxed';
|
||||||
|
};
|
||||||
|
|
||||||
|
let { size, tabs, selected: value = $bindable(0), variant = 'none' }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
role="tablist"
|
||||||
|
class="tabs w-full"
|
||||||
|
class:tabs-xs={size === 'xs'}
|
||||||
|
class:tabs-sm={size === 'sm'}
|
||||||
|
class:tabs-lg={size === 'lg'}
|
||||||
|
class:tabs-bordered={variant === 'bordered'}
|
||||||
|
class:tabs-lifted={variant === 'lifted'}
|
||||||
|
class:tabs-boxed={variant === 'boxed'}
|
||||||
|
>
|
||||||
|
{#each tabs as tab, index}
|
||||||
|
{#key [tab, value]}
|
||||||
|
<Tab
|
||||||
|
active={index === value}
|
||||||
|
label={tab}
|
||||||
|
onclick={() => {
|
||||||
|
value = index;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
4
src/lib/components/Navigation/Tabs/index.ts
Normal file
4
src/lib/components/Navigation/Tabs/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import Tabs from './Tabs.svelte';
|
||||||
|
|
||||||
|
export default Tabs;
|
||||||
|
export { default as Tabs } from './Tabs.svelte';
|
||||||
4
src/lib/components/Navigation/index.ts
Normal file
4
src/lib/components/Navigation/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import Navbar from './Navbar';
|
||||||
|
import Tabs from './Tabs';
|
||||||
|
|
||||||
|
export { Navbar, Tabs };
|
||||||
44
src/lib/components/common/Button/Button.stories.svelte
Normal file
44
src/lib/components/common/Button/Button.stories.svelte
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<script module lang="ts">
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import Button from './Button.svelte';
|
||||||
|
import { fn } from '@storybook/test';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Actions/Button',
|
||||||
|
component: Button,
|
||||||
|
args: {
|
||||||
|
onClick: fn(),
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
color: {
|
||||||
|
control: 'select',
|
||||||
|
options: [
|
||||||
|
'neutral',
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'accent',
|
||||||
|
'ghost',
|
||||||
|
'link',
|
||||||
|
'info',
|
||||||
|
'success',
|
||||||
|
'warning',
|
||||||
|
'error',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
control: 'boolean',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['Default', 'xs', 'sm', 'lg'],
|
||||||
|
defaultValue: 'Default',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['button', 'reset', 'submit'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Default" args={{ label: 'Button', color: 'primary' }} />
|
||||||
57
src/lib/components/common/Button/Button.svelte
Normal file
57
src/lib/components/common/Button/Button.svelte
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { DaisyColor, DaisySize } from '$lib/types';
|
||||||
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
block?: boolean;
|
||||||
|
color?: DaisyColor;
|
||||||
|
glass?: boolean;
|
||||||
|
label: string;
|
||||||
|
outline?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
|
responsive?: boolean;
|
||||||
|
size?: DaisySize;
|
||||||
|
type?: HTMLButtonAttributes['type'];
|
||||||
|
wide?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
block = false,
|
||||||
|
color,
|
||||||
|
glass = false,
|
||||||
|
label,
|
||||||
|
outline = false,
|
||||||
|
onClick,
|
||||||
|
responsive = false,
|
||||||
|
size,
|
||||||
|
type = 'button',
|
||||||
|
wide = false,
|
||||||
|
}: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
{type}
|
||||||
|
onclick={onClick}
|
||||||
|
class:btn-outline={outline}
|
||||||
|
class:btn-block={block}
|
||||||
|
class:btn-wide={wide}
|
||||||
|
class:glass
|
||||||
|
class:btn-xs={size === 'xs'}
|
||||||
|
class:btn-sm={size === 'sm'}
|
||||||
|
class:btn-lg={size === 'lg'}
|
||||||
|
class:btn-neutral={color === 'neutral'}
|
||||||
|
class:btn-primary={color === 'primary'}
|
||||||
|
class:btn-secondary={color === 'secondary'}
|
||||||
|
class:btn-accent={color === 'accent'}
|
||||||
|
class:btn-ghost={color === 'ghost'}
|
||||||
|
class:btn-link={color === 'link'}
|
||||||
|
class:btn-info={color === 'info'}
|
||||||
|
class:btn-success={color === 'success'}
|
||||||
|
class:btn-warning={color === 'warning'}
|
||||||
|
class:btn-error={color === 'error'}
|
||||||
|
class={`btn ${responsive && 'btn-xs sm:btn-sm md:btn-md lg:btn-lg'}`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
3
src/lib/components/common/Button/index.ts
Normal file
3
src/lib/components/common/Button/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Button from './Button.svelte';
|
||||||
|
|
||||||
|
export default Button;
|
||||||
|
|
@ -3,9 +3,8 @@
|
||||||
import Loader from './Loader.svelte';
|
import Loader from './Loader.svelte';
|
||||||
|
|
||||||
const { Story } = defineMeta({
|
const { Story } = defineMeta({
|
||||||
title: 'Loader',
|
title: 'Feedback/Loader',
|
||||||
component: Loader,
|
component: Loader,
|
||||||
tags: ['autodocs']
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
3
src/lib/components/common/Loader/index.ts
Normal file
3
src/lib/components/common/Loader/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Loader from './Loader.svelte';
|
||||||
|
|
||||||
|
export default Loader;
|
||||||
45
src/lib/components/common/TextInput/TextInput.stories.svelte
Normal file
45
src/lib/components/common/TextInput/TextInput.stories.svelte
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<script module lang="ts">
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import TextInput from './TextInput.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Data Input/Text Input',
|
||||||
|
component: TextInput,
|
||||||
|
argTypes: {
|
||||||
|
color: {
|
||||||
|
control: 'select',
|
||||||
|
options: [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'accent',
|
||||||
|
'ghost',
|
||||||
|
'link',
|
||||||
|
'info',
|
||||||
|
'success',
|
||||||
|
'warning',
|
||||||
|
'error',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
bordered: {
|
||||||
|
control: 'boolean',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['Default', 'xs', 'sm', 'lg'],
|
||||||
|
defaultValue: 'Default',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['email', 'password', 'search', 'tel', 'text', 'url'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#snippet icon()}
|
||||||
|
<i class="fi fi-rr-user"></i>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<Story name="Text Label" args={{ color: 'primary', name: 'text', start: 'Text' }} />
|
||||||
|
<Story name="Icon Start" args={{ color: 'secondary', name: 'text', start: icon }} />
|
||||||
|
<Story name="Icon End" args={{ color: 'secondary', name: 'text', end: icon }} />
|
||||||
68
src/lib/components/common/TextInput/TextInput.svelte
Normal file
68
src/lib/components/common/TextInput/TextInput.svelte
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { DaisyColor, DaisySize } from '$lib/types';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import type { HTMLInputTypeAttribute } from 'svelte/elements';
|
||||||
|
import { fade as fadeTransition } from 'svelte/transition';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
bordered?: boolean;
|
||||||
|
color?: Exclude<DaisyColor, 'neutral'>;
|
||||||
|
disabled?: boolean;
|
||||||
|
fade?: boolean;
|
||||||
|
start?: Snippet | string;
|
||||||
|
end?: Snippet | string;
|
||||||
|
name: string;
|
||||||
|
placeholder?: string;
|
||||||
|
size?: DaisySize;
|
||||||
|
type?: Extract<
|
||||||
|
HTMLInputTypeAttribute,
|
||||||
|
'email' | 'password' | 'search' | 'tel' | 'text' | 'url'
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
bordered = false,
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
fade,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
name,
|
||||||
|
placeholder,
|
||||||
|
size,
|
||||||
|
type = 'text',
|
||||||
|
}: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label
|
||||||
|
transition:fadeTransition={{ duration: fade ? 200 : 0 }}
|
||||||
|
class="input flex w-full items-center gap-2"
|
||||||
|
class:input-bordered={bordered}
|
||||||
|
class:input-xs={size === 'xs'}
|
||||||
|
class:input-sm={size === 'sm'}
|
||||||
|
class:input-lg={size === 'lg'}
|
||||||
|
class:input-primary={color === 'primary'}
|
||||||
|
class:input-secondary={color === 'secondary'}
|
||||||
|
class:input-accent={color === 'accent'}
|
||||||
|
class:input-ghost={color === 'ghost'}
|
||||||
|
class:input-link={color === 'link'}
|
||||||
|
class:input-info={color === 'info'}
|
||||||
|
class:input-success={color === 'success'}
|
||||||
|
class:input-warning={color === 'warning'}
|
||||||
|
class:input-error={color === 'error'}
|
||||||
|
>
|
||||||
|
{#if typeof start === 'string'}
|
||||||
|
{start}
|
||||||
|
{:else}
|
||||||
|
{@render start?.()}
|
||||||
|
{/if}
|
||||||
|
<input {disabled} {name} {placeholder} {type} class="grow" />
|
||||||
|
{#if typeof end === 'string'}
|
||||||
|
{end}
|
||||||
|
{:else}
|
||||||
|
{@render end?.()}
|
||||||
|
{/if}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
3
src/lib/components/common/TextInput/index.ts
Normal file
3
src/lib/components/common/TextInput/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import TextInput from './TextInput.svelte';
|
||||||
|
|
||||||
|
export default TextInput;
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './Navbar.svelte';
|
|
||||||
|
|
@ -8,7 +8,7 @@ export interface Configuration {
|
||||||
export const LoadConfig = (): Configuration => {
|
export const LoadConfig = (): Configuration => {
|
||||||
const { success, data, error } = z
|
const { success, data, error } = z
|
||||||
.object({
|
.object({
|
||||||
VITE_APP_VERSION: z.string().default('development')
|
VITE_APP_VERSION: z.string().default('development'),
|
||||||
})
|
})
|
||||||
.safeParse(import.meta.env);
|
.safeParse(import.meta.env);
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ export const LoadConfig = (): Configuration => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
app_version: data!.VITE_APP_VERSION
|
app_version: data!.VITE_APP_VERSION,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,5 @@ export const yogaLogger: YogaLogger = {
|
||||||
error(...args) {
|
error(...args) {
|
||||||
// @ts-expect-error types dont match
|
// @ts-expect-error types dont match
|
||||||
logger.error(...args);
|
logger.error(...args);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -7,14 +7,14 @@ const adapter = new PrismaAdapter(prisma.session, prisma.user);
|
||||||
export const auth = new Lucia(adapter, {
|
export const auth = new Lucia(adapter, {
|
||||||
sessionCookie: {
|
sessionCookie: {
|
||||||
attributes: {
|
attributes: {
|
||||||
secure: process.env.NODE_ENV === 'production'
|
secure: process.env.NODE_ENV === 'production',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
getUserAttributes: (attributes) => {
|
getUserAttributes: (attributes) => {
|
||||||
return {
|
return {
|
||||||
email: attributes.email
|
email: attributes.email,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
declare module 'lucia' {
|
declare module 'lucia' {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,6 @@ export const builder = new SchemaBuilder<PothosType>({
|
||||||
// use where clause from prismaRelatedConnection for totalCount (defaults to true)
|
// use where clause from prismaRelatedConnection for totalCount (defaults to true)
|
||||||
filterConnectionTotalCount: true,
|
filterConnectionTotalCount: true,
|
||||||
// warn when not using a query parameter correctly
|
// warn when not using a query parameter correctly
|
||||||
onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn'
|
onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -10,5 +10,5 @@ export const DateScalar = builder.scalarType('Date', {
|
||||||
throw new Error('Cyka blyat');
|
throw new Error('Cyka blyat');
|
||||||
}
|
}
|
||||||
return new Date(date);
|
return new Date(date);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -5,7 +5,7 @@ builder.queryType({});
|
||||||
builder.queryField('version', (t) =>
|
builder.queryField('version', (t) =>
|
||||||
t.string({
|
t.string({
|
||||||
description: 'Application version',
|
description: 'Application version',
|
||||||
resolve: (parent, args, context) => context.config.app_version
|
resolve: (parent, args, context) => context.config.app_version,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,39 +9,39 @@ export const Post = builder.prismaObject('Post', {
|
||||||
published: t.exposeBoolean('published'),
|
published: t.exposeBoolean('published'),
|
||||||
author: t.relation('author'),
|
author: t.relation('author'),
|
||||||
createdAt: t.expose('createdAt', {
|
createdAt: t.expose('createdAt', {
|
||||||
type: 'Date'
|
type: 'Date',
|
||||||
}),
|
}),
|
||||||
updatedAt: t.expose('updatedAt', {
|
updatedAt: t.expose('updatedAt', {
|
||||||
type: 'Date'
|
type: 'Date',
|
||||||
})
|
}),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreatePost = builder.inputType('CreatePost', {
|
const CreatePost = builder.inputType('CreatePost', {
|
||||||
fields: (t) => ({
|
fields: (t) => ({
|
||||||
title: t.string({
|
title: t.string({
|
||||||
required: true
|
required: true,
|
||||||
}),
|
}),
|
||||||
content: t.string({
|
content: t.string({
|
||||||
required: true
|
required: true,
|
||||||
}),
|
}),
|
||||||
published: t.boolean(),
|
published: t.boolean(),
|
||||||
authorId: t.id({
|
authorId: t.id({
|
||||||
required: true
|
required: true,
|
||||||
})
|
}),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const UpdatePost = builder.inputType('UpdatePost', {
|
const UpdatePost = builder.inputType('UpdatePost', {
|
||||||
fields: (t) => ({
|
fields: (t) => ({
|
||||||
id: t.id({
|
id: t.id({
|
||||||
required: true
|
required: true,
|
||||||
}),
|
}),
|
||||||
title: t.string(),
|
title: t.string(),
|
||||||
content: t.string(),
|
content: t.string(),
|
||||||
published: t.boolean(),
|
published: t.boolean(),
|
||||||
authorId: t.id()
|
authorId: t.id(),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.queryFields((t) => ({
|
builder.queryFields((t) => ({
|
||||||
|
|
@ -49,19 +49,19 @@ builder.queryFields((t) => ({
|
||||||
type: [Post],
|
type: [Post],
|
||||||
resolve: async () => {
|
resolve: async () => {
|
||||||
return await prisma.post.findMany();
|
return await prisma.post.findMany();
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
builder.mutationFields((t) => ({
|
builder.mutationFields((t) => ({
|
||||||
createPost: t.field({
|
createPost: t.field({
|
||||||
type: Post,
|
type: Post,
|
||||||
args: {
|
args: {
|
||||||
input: t.arg({ required: true, type: CreatePost })
|
input: t.arg({ required: true, type: CreatePost }),
|
||||||
},
|
},
|
||||||
resolve: async (parent, args) => {
|
resolve: async (parent, args) => {
|
||||||
const author = await prisma.user.findUnique({
|
const author = await prisma.user.findUnique({
|
||||||
where: { id: Number(args.input.authorId) }
|
where: { id: Number(args.input.authorId) },
|
||||||
});
|
});
|
||||||
if (!author) {
|
if (!author) {
|
||||||
throw new Error('Author does not exist!');
|
throw new Error('Author does not exist!');
|
||||||
|
|
@ -73,23 +73,23 @@ builder.mutationFields((t) => ({
|
||||||
published: args.input.published,
|
published: args.input.published,
|
||||||
author: {
|
author: {
|
||||||
connect: {
|
connect: {
|
||||||
id: author.id
|
id: author.id,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return post;
|
return post;
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
updatePost: t.field({
|
updatePost: t.field({
|
||||||
type: Post,
|
type: Post,
|
||||||
args: {
|
args: {
|
||||||
input: t.arg({ required: true, type: UpdatePost })
|
input: t.arg({ required: true, type: UpdatePost }),
|
||||||
},
|
},
|
||||||
resolve: async (parent, args) => {
|
resolve: async (parent, args) => {
|
||||||
const post = await prisma.post.update({
|
const post = await prisma.post.update({
|
||||||
where: {
|
where: {
|
||||||
id: Number(args.input.id)
|
id: Number(args.input.id),
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
title: args.input.title ?? undefined,
|
title: args.input.title ?? undefined,
|
||||||
|
|
@ -98,13 +98,13 @@ builder.mutationFields((t) => ({
|
||||||
...(args.input.authorId && {
|
...(args.input.authorId && {
|
||||||
author: {
|
author: {
|
||||||
connect: {
|
connect: {
|
||||||
id: Number(args.input.authorId)
|
id: Number(args.input.authorId),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return post;
|
return post;
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
@ -8,33 +8,33 @@ export const User = builder.prismaObject('User', {
|
||||||
name: t.exposeString('name'),
|
name: t.exposeString('name'),
|
||||||
posts: t.relation('posts'),
|
posts: t.relation('posts'),
|
||||||
createdAt: t.expose('createdAt', {
|
createdAt: t.expose('createdAt', {
|
||||||
type: 'Date'
|
type: 'Date',
|
||||||
}),
|
}),
|
||||||
updatedAt: t.expose('updatedAt', {
|
updatedAt: t.expose('updatedAt', {
|
||||||
type: 'Date'
|
type: 'Date',
|
||||||
})
|
}),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreateUser = builder.inputType('CreateUser', {
|
const CreateUser = builder.inputType('CreateUser', {
|
||||||
fields: (t) => ({
|
fields: (t) => ({
|
||||||
email: t.string({
|
email: t.string({
|
||||||
required: true
|
required: true,
|
||||||
}),
|
}),
|
||||||
name: t.string({
|
name: t.string({
|
||||||
required: true
|
required: true,
|
||||||
})
|
}),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const UpdateUser = builder.inputType('UpdateUser', {
|
const UpdateUser = builder.inputType('UpdateUser', {
|
||||||
fields: (t) => ({
|
fields: (t) => ({
|
||||||
id: t.id({
|
id: t.id({
|
||||||
required: true
|
required: true,
|
||||||
}),
|
}),
|
||||||
email: t.string(),
|
email: t.string(),
|
||||||
name: t.string()
|
name: t.string(),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.queryFields((t) => ({
|
builder.queryFields((t) => ({
|
||||||
|
|
@ -42,42 +42,42 @@ builder.queryFields((t) => ({
|
||||||
type: [User],
|
type: [User],
|
||||||
resolve: async () => {
|
resolve: async () => {
|
||||||
return await prisma.user.findMany();
|
return await prisma.user.findMany();
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
builder.mutationFields((t) => ({
|
builder.mutationFields((t) => ({
|
||||||
createUser: t.field({
|
createUser: t.field({
|
||||||
type: User,
|
type: User,
|
||||||
args: {
|
args: {
|
||||||
input: t.arg({ required: true, type: CreateUser })
|
input: t.arg({ required: true, type: CreateUser }),
|
||||||
},
|
},
|
||||||
resolve: async (parent, args) => {
|
resolve: async (parent, args) => {
|
||||||
const post = await prisma.user.create({
|
const post = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
email: args.input.email,
|
email: args.input.email,
|
||||||
name: args.input.name
|
name: args.input.name,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return post;
|
return post;
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
updateUser: t.field({
|
updateUser: t.field({
|
||||||
type: User,
|
type: User,
|
||||||
args: {
|
args: {
|
||||||
input: t.arg({ required: true, type: UpdateUser })
|
input: t.arg({ required: true, type: UpdateUser }),
|
||||||
},
|
},
|
||||||
resolve: async (parent, args) => {
|
resolve: async (parent, args) => {
|
||||||
const post = await prisma.user.update({
|
const post = await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: Number(args.input.id)
|
id: Number(args.input.id),
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
email: args.input.email,
|
email: args.input.email,
|
||||||
name: args.input.name ?? undefined
|
name: args.input.name ?? undefined,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return post;
|
return post;
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
@ -3,5 +3,5 @@ import type { YogaInitialContext } from 'graphql-yoga';
|
||||||
|
|
||||||
export const Context = (initialContext: YogaInitialContext) => ({
|
export const Context = (initialContext: YogaInitialContext) => ({
|
||||||
...initialContext,
|
...initialContext,
|
||||||
config: Config
|
config: Config,
|
||||||
});
|
});
|
||||||
|
|
@ -10,5 +10,5 @@ export const Yoga = createYoga<RequestEvent>({
|
||||||
graphqlEndpoint: '/api/graphql',
|
graphqlEndpoint: '/api/graphql',
|
||||||
// Let Yoga use sveltekit's Response object
|
// Let Yoga use sveltekit's Response object
|
||||||
fetchAPI: { Response },
|
fetchAPI: { Response },
|
||||||
logging: yogaLogger
|
logging: yogaLogger,
|
||||||
});
|
});
|
||||||
12
src/lib/types/daisy-colors.ts
Normal file
12
src/lib/types/daisy-colors.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
export type DaisyColor =
|
||||||
|
| 'default'
|
||||||
|
| 'neutral'
|
||||||
|
| 'primary'
|
||||||
|
| 'secondary'
|
||||||
|
| 'accent'
|
||||||
|
| 'ghost'
|
||||||
|
| 'link'
|
||||||
|
| 'info'
|
||||||
|
| 'success'
|
||||||
|
| 'warning'
|
||||||
|
| 'error';
|
||||||
1
src/lib/types/daisy-sizes.ts
Normal file
1
src/lib/types/daisy-sizes.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export type DaisySize = 'xs' | 'sm' | 'lg';
|
||||||
2
src/lib/types/index.ts
Normal file
2
src/lib/types/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './daisy-colors';
|
||||||
|
export * from './daisy-sizes';
|
||||||
|
|
@ -9,6 +9,6 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.layout {
|
.layout {
|
||||||
@apply h-screen w-screen animate-fade bg-slate-100;
|
@apply h-screen w-screen bg-base-100;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import { prisma } from '$lib/server/prisma';
|
import { prisma } from '$lib/server/prisma';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
export async function load(event) {
|
export async function load(event) {
|
||||||
const userId = event.cookies.get('user');
|
const sessionId = event.cookies.get('auth_session');
|
||||||
if (!userId) {
|
if (!sessionId) {
|
||||||
return {
|
redirect(303, '/login');
|
||||||
authenticated: false
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.session.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: userId
|
id: sessionId,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return {
|
if (!user) {
|
||||||
authenticated: !!user
|
redirect(401, '/login');
|
||||||
};
|
}
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import Loader from '$lib/components/Loader.svelte';
|
import Loader from '$lib/components/common/Loader';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
let { data } = $props();
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const id = setTimeout(() => (data.authenticated ? goto('/app') : goto('/login')), 1500);
|
const id = setTimeout(() => goto('/app'), 1500);
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(id);
|
clearTimeout(id);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="site-loader">
|
<div class="site-loader" transition:fade>
|
||||||
<h1>Hestia</h1>
|
|
||||||
<Loader />
|
<Loader />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Navbar from '$lib/components/Navbar.svelte';
|
import { Navbar } from '$lib/components/Navigation';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ export const actions = {
|
||||||
}
|
}
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
email: form.get('email') as string
|
email: form.get('email') as string,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
if (!user) {
|
if (!user) {
|
||||||
logger.error('User not found! ${user}');
|
logger.error('User not found! ${user}');
|
||||||
|
|
@ -31,7 +31,7 @@ export const actions = {
|
||||||
const sessionCookie = auth.createSessionCookie(session.id);
|
const sessionCookie = auth.createSessionCookie(session.id);
|
||||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||||
path: '/',
|
path: '/',
|
||||||
maxAge: 120
|
maxAge: 120,
|
||||||
});
|
});
|
||||||
redirect(302, '/');
|
redirect(302, '/');
|
||||||
},
|
},
|
||||||
|
|
@ -47,8 +47,8 @@ export const actions = {
|
||||||
data: {
|
data: {
|
||||||
email: form.get('email') as string,
|
email: form.get('email') as string,
|
||||||
name: form.get('name') as string,
|
name: form.get('name') as string,
|
||||||
password: hashedPassword
|
password: hashedPassword,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
const session = await auth.createSession(user.id.toString(), {});
|
const session = await auth.createSession(user.id.toString(), {});
|
||||||
const sessionCookie = auth.createSessionCookie(session.id);
|
const sessionCookie = auth.createSessionCookie(session.id);
|
||||||
|
|
@ -57,8 +57,8 @@ export const actions = {
|
||||||
}
|
}
|
||||||
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
event.cookies.set(sessionCookie.name, sessionCookie.value, {
|
||||||
path: '/',
|
path: '/',
|
||||||
maxAge: 120
|
maxAge: 120,
|
||||||
});
|
});
|
||||||
redirect(302, '/');
|
redirect(302, '/');
|
||||||
}
|
},
|
||||||
} satisfies Actions;
|
} satisfies Actions;
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,50 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Button from '$lib/components/Button.svelte';
|
import Button from '$lib/components/common/Button';
|
||||||
import Input from '$lib/components/Input.svelte';
|
import TextInput from '$lib/components/common/TextInput';
|
||||||
import { fade, scale } from 'svelte/transition';
|
import Tabs from '$lib/components/Navigation/Tabs';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
let mode: 'register' | 'login' = $state('login');
|
let tab: 0 | 1 = $state(0);
|
||||||
let action = $derived(mode === 'login' ? '?/login' : '?/register');
|
|
||||||
|
|
||||||
function onViewToggle() {
|
|
||||||
mode = mode === 'login' ? 'register' : 'login';
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="page">
|
{#snippet userIcon()}
|
||||||
<h1 class="underline">Hestia</h1>
|
<i class="fi fi-br-envelope"></i>
|
||||||
<div class="login">
|
{/snippet}
|
||||||
<form method="POST" {action} transition:scale>
|
|
||||||
<h2 transition:fade>{mode === 'login' ? 'Login' : 'Register'}</h2>
|
{#snippet passwordIcon()}
|
||||||
{#if mode === 'register'}
|
<i class="fi fi-br-key"></i>
|
||||||
<div transition:fade>
|
{/snippet}
|
||||||
<Input label="Name" name="name" />
|
|
||||||
</div>
|
{#snippet nameIcon()}
|
||||||
|
<i class="fi fi-rr-user"></i>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet form(variant: 'login' | 'register')}
|
||||||
|
<form method="POST" action={`?/${variant}`}>
|
||||||
|
<div class="card-body gap-4">
|
||||||
|
<TextInput start={userIcon} placeholder="Email" name="email" type="email" />
|
||||||
|
<TextInput
|
||||||
|
start={passwordIcon}
|
||||||
|
placeholder="Password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
{#if variant === 'register'}
|
||||||
|
<TextInput start={nameIcon} placeholder="Name" name="name" fade />
|
||||||
{/if}
|
{/if}
|
||||||
<Input label="Email" name="email" type="email" />
|
</div>
|
||||||
<Input label="Password" name="password" type="password" />
|
<div class="card-actions px-4">
|
||||||
<div class="flex gap-2">
|
<Button block type="submit" label="Submit" outline />
|
||||||
<Button
|
</div>
|
||||||
onClick={onViewToggle}
|
</form>
|
||||||
label={mode === 'login' ? 'Register' : 'Login'}
|
{/snippet}
|
||||||
size="large"
|
|
||||||
primary
|
<div class="page" transition:fade>
|
||||||
/>
|
<div class="card bg-base-200 py-4 shadow-xl">
|
||||||
<Button type="submit" label="Submit" size="large" />
|
<div class="card-title">
|
||||||
</div>
|
<Tabs variant="bordered" bind:selected={tab} tabs={['Login', 'Register']} />
|
||||||
</form>
|
</div>
|
||||||
|
{@render form(tab === 0 ? 'login' : 'register')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -40,10 +52,4 @@
|
||||||
.page {
|
.page {
|
||||||
@apply flex flex-col items-center justify-around gap-24 py-[10%];
|
@apply flex flex-col items-center justify-around gap-24 py-[10%];
|
||||||
}
|
}
|
||||||
.login {
|
|
||||||
@apply w-fit max-w-lg animate-fade rounded-lg bg-white p-8;
|
|
||||||
}
|
|
||||||
.login > form {
|
|
||||||
@apply flex w-full flex-col items-center gap-8 rounded-lg;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -11,8 +11,8 @@ const config = {
|
||||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
adapter: adapter()
|
adapter: adapter(),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import typography from '@tailwindcss/typography';
|
import typography from '@tailwindcss/typography';
|
||||||
|
import daisyui from 'daisyui';
|
||||||
import type { Config } from 'tailwindcss';
|
import type { Config } from 'tailwindcss';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -7,19 +8,22 @@ export default {
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
display: ['Baskervville SC']
|
display: ['Baskervville SC'],
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
fade: 'fadeIn .5s ease-in-out'
|
fade: 'fadeIn .5s ease-in-out',
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
fadeIn: {
|
fadeIn: {
|
||||||
from: { opacity: '0' },
|
from: { opacity: '0' },
|
||||||
to: { opacity: '1' }
|
to: { opacity: '1' },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [typography]
|
plugins: [typography, daisyui],
|
||||||
|
daisyui: {
|
||||||
|
logs: false,
|
||||||
|
},
|
||||||
} satisfies Config;
|
} satisfies Config;
|
||||||
|
|
@ -5,6 +5,6 @@ export default defineConfig({
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()],
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
include: ['src/**/*.{test,spec}.{js,ts}']
|
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue