Daisy UI #14
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;
|
||||||
}
|
}
|
||||||
|
Will need to remove this when we switch to Clerk, maybe add a Todo to not forget it ? Will need to remove this when we switch to Clerk, maybe add a Todo to not forget it ?
|
|||||||
// 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>
|
||||||
|
Make me think that we should probably start using the library i18n, it will help with translation and not hard coding text in the code Make me think that we should probably start using the library i18n, it will help with translation and not hard coding text in the code
Yes, it was part of the sveltekit start project but I went with a simpler setup since it was a lot at once. #19 Yes, it was part of the sveltekit start project but I went with a simpler setup since it was a lot at once. #19
|
|||||||
|
</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
🙈