1 Commits

Author SHA1 Message Date
Alexis
4ee412ee0c Added supabase and basic role management 2024-05-06 18:28:13 +02:00
193 changed files with 8173 additions and 11684 deletions

35
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,35 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
overrides: [
{
files: [
'**/__tests__/*.{cy,spec}.{js,ts,jsx,tsx}',
'cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}',
'cypress/support/**/*.{js,ts,jsx,tsx}'
],
extends: ['plugin:cypress/recommended']
}
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
indent: ['error', 2, { SwitchCase: 1 }],
'prettier/prettier': [
'error',
{
endOfLine: 'auto'
}
],
'vue/multi-word-component-names': 'off'
}
}

44
.gitignore vendored
View File

@@ -1,28 +1,32 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs # Logs
logs logs
*.log *.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Misc node_modules
.DS_Store .DS_Store
.fleet dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea .idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
# Local env files
.env .env
.env.*
!.env.example
# Supabase
.branches
.temp

3
.npmrc
View File

@@ -1,3 +0,0 @@
# https://pnpm.io/npmrc#shamefully-hoist
# Fixes: WARN Failed to resolve dependency: @supabase/gotrue-js, present in 'optimizeDeps.include'
shamefully-hoist=true

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

View File

@@ -1,5 +0,0 @@
{
"eslint.experimental.useFlatConfig": true,
"editor.indentSize": "tabSize",
"editor.tabSize": 2,
}

View File

@@ -1,75 +1,5 @@
# Nuxt 3 Minimal Starter # Leim Tools
A congregate of tools used to visualize TTRPG elements and help content creators (mostly me for now tbh) build their worlds.
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. ## Timeline
Visual helper for time-related data, such as events, eras and characters. Helpful to know how old a character is while something is happening in the world, or how far two events are separated in the timeline.
## Setup
Make sure to install the dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm run dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm run build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm run preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

42
app.vue
View File

@@ -1,42 +0,0 @@
<script setup lang="ts">
import { ConfigProvider } from 'radix-vue'
useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} — TTTools` : 'TTTools';
},
meta: [
{ name: "charset", content: "UTF-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1.0" },
{ name: "author", content: "Alexis Pelé" },
{ name: "description", content: "Tools destined to players and game master, helping them visualize their worlds better." },
{ name: "generator", content: "Nuxt" },
{ name: "msapplication-TileColor", content: "00aba9" },
{ name: "theme-color", content: "00aba9" },
{ name: "og:type", content: "tabletop-tools" },
{ name: "og:url", content: "ttt.alexcreates.fr" },
{ name: 'robots', content: 'noindex, nofollow'}
],
link: [
{ rel: 'apple-touch-icon', sizes: "76x76", href: '/apple-touch-icon.png' },
{ rel: 'icon', sizes: "32x32", type: 'image/png', href: '/favicon-32x32.png' },
{ rel: 'icon', sizes: "16x16", type: 'image/png', href: '/favicon-16x16.png' },
{ rel: 'manifest', href: '/site.webmanifest' },
{ rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#6595b4' },
]
})
const useIdFunction = () => useId()
</script>
<template>
<div class="h-screen">
<NuxtLoadingIndicator />
<NuxtLayout>
<ConfigProvider :use-id="useIdFunction">
<NuxtPage/>
</ConfigProvider>
</NuxtLayout>
</div>
</template>

View File

@@ -4,11 +4,11 @@
"typescript": true, "typescript": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.js", "config": "tailwind.config.js",
"css": "assets/main.css", "css": "src/assets/main.css",
"baseColor": "slate", "baseColor": "slate",
"cssVariables": true "cssVariables": true
}, },
"framework": "nuxt", "framework": "vite",
"aliases": { "aliases": {
"components": "@/components", "components": "@/components",
"utils": "@/lib/utils" "utils": "@/lib/utils"

View File

@@ -1,38 +0,0 @@
<script lang="ts" setup>
import { useCalendar } from '@/stores/CalendarStore'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { areDatesIdentical } from '@/models/Date'
const { defaultDate } = useCalendar()
const { selectedDate } = storeToRefs(useCalendar())
const { jumpToDefaultDate, getFormattedDateTitle } = useCalendar()
const defaultDateFormatted: string = getFormattedDateTitle(defaultDate, true)
const isDefaultDate: ComputedRef<boolean> = computed<boolean>(() => areDatesIdentical(selectedDate.value, defaultDate))
</script>
<template>
<UiTooltipProvider :delay-duration="250">
<UiTooltip>
<UiTooltipTrigger as-child>
<ClientOnly>
<UiButton size="sm" :disabled="isDefaultDate" @click="jumpToDefaultDate">
Aujourd'hui
</UiButton>
<template #fallback>
<UiButton size="sm">
Aujourd'hui
</UiButton>
</template>
</ClientOnly>
</UiTooltipTrigger>
<UiTooltipContent>
<p>{{ defaultDateFormatted }}</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
</template>

View File

@@ -1,32 +0,0 @@
<script setup lang="ts">
import { useCalendar } from '@/stores/CalendarStore'
import { PhCalendarBlank } from '@phosphor-icons/vue'
import { computed } from 'vue'
const { currentConfig, viewTypeOptions, getViewTypeTitle, setViewType } = useCalendar()
const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType))
</script>
<template>
<UiDropdownMenu>
<UiDropdownMenuTrigger as-child>
<UiButton size="sm" variant="secondary">
<PhCalendarBlank size="18" weight="fill" />
{{ viewTypeTitle }}
</UiButton>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent :side="'bottom'" :collision-padding="30">
<UiDropdownMenuLabel>Mode d'affichage</UiDropdownMenuLabel>
<UiDropdownMenuSeparator />
<UiDropdownMenuItem
v-for="option in viewTypeOptions"
:key="option"
@click="setViewType(option)"
>
{{ getViewTypeTitle(option) }}
</UiDropdownMenuItem>
</UiDropdownMenuContent>
</UiDropdownMenu>
</template>

View File

@@ -1,5 +0,0 @@
<template>
<h1 class="text-2xl font-bold flex">
<slot />
</h1>
</template>

View File

@@ -1,77 +0,0 @@
<script lang="ts" setup>
import { PhHouse, PhList } from '@phosphor-icons/vue'
import type { SidebarProps } from './SidebarProps';
defineProps<SidebarProps>()
</script>
<template>
<nav class="w-16 py-6 border-r-[1px] border-l-slate-500 grid grid-rows-[1fr_auto] justify-center">
<menu class="flex flex-col gap-4">
<li class="mb-12">
<UiButton variant="ghost" size="icon" class="rounded-full" @click="console.log">
<PhList size="27" />
</UiButton>
</li>
<li v-if="!isHome">
<UiTooltipProvider :delay-duration="100">
<UiTooltip>
<UiTooltipTrigger as-child>
<UiButton variant="ghost" size="icon" class="rounded-full" as-child>
<RouterLink to="/">
<PhHouse size="24" weight="fill" />
</RouterLink>
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent :side="'right'">
<p>Retourner aux outils</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
</li>
<li v-for="(item, i) in menuItems" :key="i">
<UiTooltipProvider :delay-duration="100">
<UiTooltip>
<UiTooltipTrigger as-child>
<UiButton v-if="item.to" variant="ghost" size="icon" class="rounded-full" as-child>
<RouterLink :to="item.to">
<component :is="item.phIcon" size="24" weight="fill" />
</RouterLink>
</UiButton>
<UiButton v-if="item.clickHandler" variant="ghost" size="icon" class="rounded-full" @click="item.clickHandler">
<component :is="item.phIcon" size="24" weight="fill" />
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent :side="'right'">
<p>{{ item.tooltip }}</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
</li>
<!-- <li>
<UiTooltipProvider :delay-duration="100">
<UiTooltip>
<UiTooltipTrigger as-child>
<UiButton
variant="ghost"
size="icon"
class="rounded-full"
@click="revealAdvancedSearch()"
>
<PhMagnifyingGlass size="24" weight="fill" />
</UiButton>
</UiTooltipTrigger>
<UiTooltipContent :side="'right'">
<p>Recherche avancée</p>
</UiTooltipContent>
</UiTooltip>
</UiTooltipProvider>
</li> -->
</menu>
<UserCTA />
</nav>
</template>

View File

@@ -1,11 +0,0 @@
export interface MenuItem {
phIcon: Component
tooltip: string
clickHandler?: () => void
to?: string
}
export interface SidebarProps {
menuItems: MenuItem[],
isHome?: boolean
}

View File

@@ -1,81 +0,0 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { PhUserCircle } from '@phosphor-icons/vue'
const router = useRouter()
const { auth } = useSupabaseClient()
const user = useSupabaseUser()
const userMeta = computed(() => user.value?.user_metadata)
const menuOpened = ref<boolean>(false)
function closeMenu() {
menuOpened.value = false
}
watch(user, closeMenu)
async function handleGoogleLogin() {
try {
auth.signInWithOAuth({
provider: 'google',
options: {
queryParams: {
access_type: 'offline',
prompt: 'consent'
}
}
})
} catch (err) {
console.log(err)
}
}
async function handleLogout() {
try {
const { error } = await auth.signOut()
if (error) throw error
} catch (err) {
console.log(err)
}
}
function gotoProfilePage() {
router.push({ path: '/profile' })
}
</script>
<template>
<ClientOnly>
<UiPopover v-model:open="menuOpened">
<UiPopoverTrigger>
<UiAvatar v-if="user" class="cursor-pointer">
<UiAvatarImage
:src="userMeta?.avatar_url"
:alt="userMeta?.full_name"
referrerpolicy="no-referrer"
/>
<UiAvatarFallback>Me</UiAvatarFallback>
</UiAvatar>
<UiButton v-else variant="outline" size="icon">
<PhUserCircle size="18" />
</UiButton>
</UiPopoverTrigger>
<UiPopoverContent class="w-fit p-0" :align="'start'" :side="'top'" :collision-padding="20">
<UiCommand>
<UiCommandList v-if="user">
<UiCommandGroup :heading="`Connecté en tant que ${user?.email}`">
<UiCommandItem value="profile" @select="gotoProfilePage"> Profil </UiCommandItem>
<UiCommandItem value="logout" @select="handleLogout"> Déconnexion </UiCommandItem>
</UiCommandGroup>
</UiCommandList>
<UiCommandList v-else>
<UiCommandItem value="logout" @select="handleGoogleLogin"> Connexion </UiCommandItem>
</UiCommandList>
</UiCommand>
</UiPopoverContent>
</UiPopover>
</ClientOnly>
</template>

View File

@@ -1,9 +0,0 @@
<script lang="ts" setup>
const user = useSupabaseUser()
</script>
<template>
<div class="p-8">
<Heading>{{ user?.user_metadata.full_name }}</Heading>
</div>
</template>

View File

@@ -1,14 +0,0 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
interface SkeletonProps {
class?: HTMLAttributes['class']
}
const props = defineProps<SkeletonProps>()
</script>
<template>
<div :class="cn('animate-pulse rounded-md bg-muted', props.class)" />
</template>

View File

@@ -1 +0,0 @@
export { default as Skeleton } from './Skeleton.vue'

15
cypress.config.ts Normal file
View File

@@ -0,0 +1,15 @@
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
baseUrl: 'http://localhost:4173'
},
component: {
specPattern: 'src/**/__tests__/*.{cy,spec}.{js,ts,jsx,tsx}',
devServer: {
framework: 'vue',
bundler: 'vite'
}
}
})

View File

@@ -0,0 +1,8 @@
// https://on.cypress.io/api
describe('My First Test', () => {
it('visits the app root url', () => {
cy.visit('/')
cy.contains('h1', 'You did it!')
})
})

10
cypress/e2e/tsconfig.json Normal file
View File

@@ -0,0 +1,10 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["./**/*", "../support/**/*"],
"compilerOptions": {
"isolatedModules": false,
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]
}
}

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,39 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
export {}

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@@ -0,0 +1,43 @@
// ***********************************************************
// This example support/component.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
// Import global styles
import '@/assets/main.css'
import { mount } from 'cypress/vue'
// Augment the Cypress namespace to include type definitions for
// your custom command.
// Alternatively, can be defined in cypress/support/component.d.ts
// with a <reference path="./component" /> at the top of your spec.
/* eslint-disable @typescript-eslint/no-namespace */
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount
}
}
}
Cypress.Commands.add('mount', mount)
// Example use:
// cy.mount(MyComponent)

20
cypress/support/e2e.ts Normal file
View File

@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

1
env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -1,15 +0,0 @@
import withNuxt from './.nuxt/eslint.config.mjs'
import stylisticJs from '@stylistic/eslint-plugin-js'
export default withNuxt({
plugins: {
'@stylistic/js': stylisticJs
},
rules: {
'vue/multi-word-component-names': 'off',
'no-unused-vars': 'warn',
'space-in-parens': 'off',
'computed-property-spacing': 'off',
'@stylistic/js/indent': [ 'warn', 2, { SwitchCase: 1 } ],
}
})

32
index.html Normal file
View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="Alexis Pelé" />
<meta name="description" content="Outil de visualisation chronologique des évènements de Léim" />
<meta name="generator" content="VueJS" />
<meta name="robots" content="noindex, nofollow">
<title>Outils — TTTools</title>
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#6595b4">
<meta name="msapplication-TileColor" content="#00aba9">
<meta name="theme-color" content="#a9d1eb">
<meta property="og:title" content="Outils — Leim Wiki"/>
<meta property="og:type" content="tabletop-tools" />
<meta property="og:url" content="ttt.alexcreates.fr" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -1,7 +0,0 @@
export default defineNuxtRouteMiddleware(() => {
const user = useSupabaseUser()
if (!user.value) {
return abortNavigation()
}
})

View File

@@ -1,35 +0,0 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
'@nuxtjs/supabase',
'@pinia/nuxt',
'@vueuse/nuxt',
'@nuxtjs/tailwindcss',
'@nuxtjs/color-mode',
'@nuxt/eslint',
'shadcn-nuxt'
],
css: ['~/assets/main.css'],
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
shadcn: {
prefix: 'Ui',
componentDir: './components/ui'
},
colorMode: {
classSuffix: '',
preference: 'dark',
fallback: 'dark',
},
supabase: {
redirect: false
},
eslint: {}
})

6432
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,50 +1,57 @@
{ {
"name": "nuxt-app", "name": "leim-tool",
"version": "0.1.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "nuxt build", "dev": "vite",
"dev": "nuxt dev", "build": "run-p type-check \"build-only {@}\" --",
"generate": "nuxt generate", "preview": "vite preview",
"preview": "nuxt preview", "test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
"postinstall": "nuxt prepare", "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
"lint": "eslint .", "test:unit": "cypress run --component",
"lint:fix": "eslint . --fix", "test:unit:dev": "cypress open --component",
"lint:prettier": "prettier . --check" "build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"@nuxt/eslint": "^0.3.12",
"@phosphor-icons/vue": "^2.2.1", "@phosphor-icons/vue": "^2.2.1",
"@pinia/nuxt": "^0.5.1", "@supabase/supabase-js": "^2.43.1",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"@vueuse/nuxt": "^10.9.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.0",
"jwt-decode": "^4.0.0",
"lucide-vue-next": "^0.364.0", "lucide-vue-next": "^0.364.0",
"nuxt": "^3.7.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"radix-vue": "^1.7.4", "radix-vue": "^1.6.1",
"shadcn-nuxt": "^0.10.4", "tailwind-merge": "^2.2.2",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vue": "^3.4.27", "vue": "^3.4.21",
"vue-router": "^4.3.2" "vue-router": "^4.3.0"
}, },
"devDependencies": { "devDependencies": {
"@nuxtjs/color-mode": "^3.4.1", "@rushstack/eslint-patch": "^1.3.3",
"@nuxtjs/supabase": "^1.2.2", "@tsconfig/node20": "^20.1.2",
"@nuxtjs/tailwindcss": "^6.12.0", "@types/node": "^20.11.28",
"@stylistic/eslint-plugin-js": "^2.1.0", "@vitejs/plugin-vue": "^5.0.4",
"@typescript-eslint/eslint-plugin": "^7.8.0", "@vue/eslint-config-prettier": "^8.0.0",
"@typescript-eslint/parser": "^7.8.0", "@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.19",
"cypress": "^13.7.0",
"eslint": "^8.49.0", "eslint": "^8.49.0",
"eslint-config-prettier": "^9.1.0", "eslint-plugin-cypress": "^2.15.1",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-vue": "^9.17.0",
"eslint-plugin-vue": "^9.26.0", "npm-run-all2": "^6.1.2",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.2.5", "prettier": "^3.0.3",
"sass": "^1.72.0", "sass": "^1.72.0",
"start-server-and-test": "^2.0.3",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "~5.4.0" "typescript": "~5.4.0",
"vite": "^5.1.6",
"vue-tsc": "^2.0.6"
} }
} }

View File

@@ -1,30 +0,0 @@
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { useCalendar } from '@/stores/CalendarStore'
import type { MenuItem } from '~/components/global/SidebarProps';
import { PhMagnifyingGlass } from '@phosphor-icons/vue'
const { revealAdvancedSearch } = useCalendar()
const { isAdvancedSearchOpen } = storeToRefs(useCalendar())
useHead({
title: 'Calendar'
})
const sidebarMenu: MenuItem[] = [
{
phIcon: PhMagnifyingGlass,
tooltip: 'Recherche avancée',
clickHandler: revealAdvancedSearch
}
]
</script>
<template>
<main class="h-full grid grid-cols-[auto_1fr]">
<Sidebar :menu-items="sidebarMenu" />
<Calendar />
<CalendarSearch v-model:model-value="isAdvancedSearchOpen" />
</main>
</template>

View File

@@ -1,31 +0,0 @@
<script lang="ts" setup>
import type { MenuItem } from '~/components/global/SidebarProps';
useHead({
title: 'Dashboard'
})
const sidebarMenu: MenuItem[] = []
</script>
<template>
<main class="h-full grid grid-cols-[auto_1fr]">
<Sidebar :menu-items="sidebarMenu" is-home />
<div class="h-full grid place-items-center">
<UiCard class="w-1/3 min-w-72" link="/calendar">
<UiCardHeader>
<UiCardTitle>Calendrier</UiCardTitle>
<UiCardDescription>
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
chronologie
</UiBadge>
</UiCardDescription>
</UiCardHeader>
<UiCardContent>
Chronologie complète de Léïm, rassemblant les évènements et personnages clés du monde
</UiCardContent>
</UiCard>
</div>
</main>
</template>

View File

@@ -1,29 +0,0 @@
<script lang="ts" setup>
import type { MenuItem } from '~/components/global/SidebarProps';
const user = useSupabaseUser()
useHead({
title: 'Profil'
})
definePageMeta({
middleware: ['auth-guard']
})
// Redirect user back home when they log out on the page
watch(user, (n, _o) => {
if (!n) {
navigateTo('/')
}
})
const sidebarMenu: MenuItem[] = []
</script>
<template>
<main class="h-full grid grid-cols-[auto_1fr]">
<Sidebar :menu-items="sidebarMenu" />
<ProfileLayout />
</main>
</template>

10204
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,252 +0,0 @@
export default defineEventHandler(() => {
return [
/**
* Player characters
*/
// "Les Milles Cages"
{
name: 'Sulvan Trois-Barbes',
birth: { day: 20, month: 3, year: 3169 },
wiki: 'https://alexcreates.fr/leim/index.php/Sulvan_Trois-Barbes',
category: 'joueur',
secondaryCategories: ['criminel', 'étincelle']
},
{
name: 'Vascylly',
birth: { day: 3, month: 5, year: 3181 },
wiki: 'https://alexcreates.fr/leim/index.php/Vascylly',
category: 'joueur',
secondaryCategories: ['mage']
},
{
name: 'Tara Belyus',
birth: { day: 14, month: 9, year: 3186 },
category: 'joueur',
secondaryCategories: ['mage']
},
// "Les Cloches de Cantane"
{
name: 'Malik',
birth: { day: 22, month: 8, year: 3181 },
category: 'joueur',
secondaryCategories: ['scientifique']
},
{
name: 'Elie',
birth: { day: 5, month: 6, year: 3192 },
category: 'joueur',
secondaryCategories: ['ecclésiastique']
},
{
name: 'Clayron',
birth: { day: 3, month: 5, year: 3178 },
category: 'joueur',
secondaryCategories: ['sentinelle']
},
/**
* NPC
*/
// Counts of the Alliance
{
name: 'Alfrid de Lagarde',
birth: { day: 29, month: 7, year: 3193 },
wiki: 'https://alexcreates.fr/leim/index.php/Alfrid_de_Lagarde',
category: 'comte',
secondaryCategories: ['sentinelle']
},
{
name: 'Alaric de Lagarde',
birth: { day: 23, month: 8, year: 3192 },
wiki: 'https://alexcreates.fr/leim/index.php/Alaric_de_Lagarde',
category: 'comte',
secondaryCategories: ['sentinelle']
},
{
name: 'Anastael III de Quillon',
birth: { day: 1, month: 5, year: 3184 },
wiki: 'https://alexcreates.fr/leim/index.php/Anastael_III_de_Quillon',
category: 'comte',
secondaryCategories: ['sentinelle', 'buse blanche']
},
{
name: 'Antoine de Mireloin',
birth: { day: 31, month: 5, year: 3190 },
wiki: 'https://alexcreates.fr/leim/index.php/Antoine_de_Mireloin',
category: 'comte'
},
{
name: 'Armance Ronchère',
birth: { day: 1, month: 1, year: 3132 },
wiki: 'https://alexcreates.fr/leim/index.php/Armance_Ronchère',
category: 'comte'
},
{
name: 'Baranne Rougefer',
birth: { day: 1, month: 0, year: 3175 },
wiki: 'https://alexcreates.fr/leim/index.php/Baranne_Rougefer',
category: 'comte',
secondaryCategories: ['sentinelle']
},
{
name: 'Béatrice II de Grandlac',
birth: { day: 21, month: 4, year: 3158 },
wiki: 'https://alexcreates.fr/leim/index.php/Béatrice_II_de_Grandlac',
category: 'comte',
secondaryCategories: ['mage']
},
{
name: 'Favio Asharos-Losantelle',
birth: { day: 17, month: 3, year: 3091 },
wiki: 'https://alexcreates.fr/leim/index.php/Favio_Asharos-Losantelle',
category: 'comte',
secondaryCategories: ['mage']
},
{
name: 'Firmin de Montardieu',
birth: { day: 9, month: 2, year: 3203 },
wiki: 'https://alexcreates.fr/leim/index.php/Firmin_de_Montardieu',
category: 'comte'
},
{
name: 'Laura de Montardieu',
birth: { day: 32, month: 3, year: 3167 },
death: { day: 1, month: 4, year: 3217 },
hiddenDeath: true,
wiki: 'https://alexcreates.fr/leim/index.php/Laura_de_Montardieu',
category: 'comte'
},
{
name: 'Lazarus Tymos',
birth: { day: 29, month: 9, year: 3145 },
wiki: 'https://alexcreates.fr/leim/index.php/Lazarus_Tymos',
category: 'comte',
secondaryCategories: ['mage', 'buse blanche']
},
{
name: 'Marion de Corambre',
birth: { day: 14, month: 7, year: 3190 },
wiki: 'https://alexcreates.fr/leim/index.php/Marion_de_Corambre',
category: 'comte',
secondaryCategories: ['scientifique', 'ecclésiastique']
},
{
name: 'Relforg Pergaré',
birth: { day: 18, month: 9, year: 3182 },
wiki: 'https://alexcreates.fr/leim/index.php/Relforg_Pergaré',
category: 'comte'
},
{
name: 'Vilgarde de Ternâcre',
birth: { day: 3, month: 3, year: 2998 },
wiki: 'https://alexcreates.fr/leim/index.php/Vilgarde_de_Ternâcre',
category: 'comte'
},
{
name: 'Ysildy Milopée',
birth: { day: 3, month: 1, year: 3187 },
wiki: 'https://alexcreates.fr/leim/index.php/Ysildy_Milopée',
category: 'comte',
secondaryCategories: ['mage', 'scientifique', 'professeur']
},
// Sparks
{
name: 'Izàc Tymos',
birth: { day: 13, month: 6, year: 3192 },
wiki: 'https://alexcreates.fr/leim/index.php/Izàc_Tymos',
category: 'mage',
secondaryCategories: ['étincelle', 'criminel', 'professeur', 'scientifique']
},
{
name: 'Tvernée',
birth: { day: 19, month: 2, year: 3205 },
wiki: 'https://alexcreates.fr/leim/index.php/Tvernée',
category: 'étincelle',
secondaryCategories: ['criminel']
},
// Pirates
{
name: 'Räzal',
birth: { day: 13, month: 8, year: 3178 },
wiki: 'https://alexcreates.fr/leim/index.php/Räzal'
},
// Legends
{
name: 'Jorhas Kirendre',
birth: { day: 2, month: 9, year: -452 },
death: { day: 2, month: 2, year: -419 },
category: 'sentinelle',
wiki: 'https://alexcreates.fr/leim/index.php/Jorhas_Kirendre'
},
// "Les Milles Cages"
{
name: 'Ernestin Pomel',
birth: { day: 11, month: 2, year: 3179 },
wiki: 'https://alexcreates.fr/leim/index.php/Ernestin_Pomel',
category: 'mage'
},
{
name: 'Quacille Lévios',
birth: { day: 3, month: 6, year: 3162 },
wiki: 'https://alexcreates.fr/leim/index.php/Quacille_Lévios',
category: 'professeur',
secondaryCategories: ['mage']
},
{
name: 'Morel Lévios',
birth: { day: 26, month: 3, year: 3157 },
wiki: 'https://alexcreates.fr/leim/index.php/Morel_Lévios',
category: 'sentinelle',
secondaryCategories: ['mage', 'buse blanche']
},
{
name: 'Anastael Simon',
birth: { day: 32, month: 2, year: 3166 },
wiki: 'https://alexcreates.fr/leim/index.php/Anastael_Simon',
category: 'étincelle',
secondaryCategories: ['mage']
},
{
name: 'Grestain',
birth: { day: 9, month: 2, year: 3162 },
wiki: 'https://alexcreates.fr/leim/index.php/Grestain',
category: 'criminel',
secondaryCategories: ['mage']
},
{
name: 'Lucien Malanth',
birth: { day: 31, month: 4, year: 3167 },
wiki: 'https://alexcreates.fr/leim/index.php/Lucien_Malanth',
category: 'criminel',
secondaryCategories: ['mage']
},
{
name: 'Tivian Rodhus',
birth: { day: 13, month: 3, year: 3157 },
wiki: 'https://alexcreates.fr/leim/index.php/Tivian_Rodhus',
category: 'professeur',
secondaryCategories: ['mage', 'criminel']
},
// "Les Cloches de Cantane"
{
name: 'Bénédicte Vaht',
description: "Moine d'Ikami ayant entrepris la construction du Pilier Blanc de Cantane",
birth: { day: 28, month: 6, year: 3139 },
category: 'ecclésiastique'
},
{
name: 'Taleb Vaht',
description:
"Fils de Bénédicte, il s'est engagé auprès des Étincelles comme alchimiste artificier.",
birth: { day: 9, month: 1, year: 3180 },
death: { day: 28, month: 7, year: 3209 },
category: 'étincelle',
secondaryCategories: ['scientifique']
}
]
})

View File

@@ -1,217 +0,0 @@
export default defineEventHandler(() => {
return [
// Histoire Antique
{
title: "Laurdieu devient la première cité de l'empire de Kaliatos",
startDate: { day: 3, month: 4, year: -1932 },
description:
"L'empire de Kaliatos établi sa capitale dans la cité de Laurdieu, qui connaitra une prospérité nouvelle au sein d'Aldys.",
category: 'législation',
secondaryCategories: ['inauguration'],
wiki: 'https://alexcreates.fr/leim/index.php/Laurdieu',
hidden: true
},
{
title: "Apparition d'Asménys",
startDate: { day: 19, month: 7, year: -1358 },
description:
"La défunte chanteuse Asménys apparaît à un barde pendant son jeune public, démarrant la religion des Prêtresses d'Asménys.",
category: 'religion',
secondaryCategories: ['bénédiction'],
wiki: 'https://alexcreates.fr/leim/index.php/Pr%C3%AAtresses_d%27Asm%C3%A9nys',
hidden: true
},
{
title: 'La Rupture',
startDate: { day: 26, month: 5, year: -756 },
endDate: { day: 4, month: 9, year: 29 },
description:
"Les Abysses se déversent à la surface de Léim, à travers plusieurs brèches. Plusieurs hordes de démons se rapprochent des villes, et ce qu'on appellera l'Âge des Abysses commencent alors sur la planète entière.",
category: 'catastrophe',
secondaryCategories: ['catastrophe naturelle'],
wiki: 'https://alexcreates.fr/leim/index.php/Seconde_Rupture',
hidden: true
},
{
title: 'Marche du sang',
startDate: { day: 18, month: 9, year: -420 },
endDate: { day: 27, month: 1, year: -419 },
description:
"L'empereur de Kaliatos ordonne personnellement la traque de Jorhas Kirendre pour connivence avec les démons. Plusieurs bataillons sont affectés à la chasse de Jorhas, qui se terminera par son incarcération ainsi que la mort de plusieurs centaines de soldats.",
category: 'criminalité',
wiki: 'https://alexcreates.fr/leim/index.php/Jorhas_Kirendre',
hidden: true
},
{
title: 'Exécution de Tyhos',
startDate: { day: 1, month: 0, year: 0 },
description:
"Le léviathan Tyhos rend l'âme après un combat de plusieurs années contre Lystos, le dieu du Soleil.",
category: 'bénédiction',
secondaryCategories: ['mort'],
wiki: 'https://alexcreates.fr/leim/index.php/Tyhos',
hidden: true
},
{
title: 'Traité de Kadel',
startDate: { day: 29, month: 4, year: 100 },
description: '',
category: 'inauguration',
secondaryCategories: ['législation'],
wiki: 'https://alexcreates.fr/leim/index.php/Alliance_Kald%C3%A9lienne#Trait%C3%A9_de_Kadel'
},
{
title: 'Découverte des Plaines de Poussières',
startDate: { day: 17, month: 7, year: 305 },
description:
"Les troupes de la reconquête aldienne explorent le littoral d'une immense étendue grise et inerte.",
category: 'découverte',
secondaryCategories: ['exploration'],
wiki: 'https://alexcreates.fr/leim/index.php/Plaines_de_poussi%C3%A8re',
hidden: true
},
{
title: 'Construction du Rempart de Laurdieu',
startDate: { day: 30, month: 2, year: 340 },
endDate: { day: 27, month: 9, year: 355 },
description:
"Le Grand Conseil Kaldélien ordonne la construction d'une muraille autour des Plaines de Poussières, afin de contenir les démons y sortant.",
category: 'construction',
wiki: 'https://alexcreates.fr/leim/index.php/Plaines_de_poussi%C3%A8re',
hidden: true
},
// Histoire Récente
{
title: "Inauguration de l'Académie Artistique Arcanique",
startDate: { day: 11, month: 6, year: 2545 },
description:
"Scäd Sceni ouvre son institut artistique dédié à l'apprentissage des arts arcaniques",
category: 'inauguration',
secondaryCategories: ['arcanologie'],
wiki: 'https://alexcreates.fr/leim/index.php/Buse_(arme)'
},
{
title: 'Création de la buse kaldélienne',
startDate: { day: 4, month: 2, year: 3113 },
description:
'Sophia de Rougefer invente la buse kaldélienne, une arme à feu souple à deux canons.',
category: 'invention',
secondaryCategories: ['science'],
wiki: 'https://alexcreates.fr/leim/index.php/Buse_(arme)'
},
{
title: 'Feux dans les champs de Bamast',
startDate: { day: 9, month: 5, year: 3200 },
description:
"Plusieurs incendies criminels se propagent à travers les champs de sérille des fermiers des littoraux de Bamast. Aucun suspect ni coupable ne sera trouvé et l'enquête sera baclée.",
category: 'catastrophe',
secondaryCategories: ['criminalité'],
wiki: 'https://alexcreates.fr/leim/index.php/Vascylly'
},
{
title: 'Meurtre de Darléon Typhos',
startDate: { day: 21, month: 6, year: 3200 },
description:
'Darléon Typhos surprend Vascylly fouillant sa demeure peu après la tombée de la nuit. Le majordome de la famille Typhos découvre le corps sans vie de son maître le lendemain.',
category: 'criminalité',
secondaryCategories: ['joueurs'],
wiki: 'https://alexcreates.fr/leim/index.php/Vascylly'
},
{
title: 'Scandale Rodhus',
startDate: { day: 25, month: 9, year: 3208 },
description:
"Tivian Rodhus, un professeur estimé de l'Académie Centrale Kaldélienne, est emprisonné pour corruption et aggression sexuelle. Le corps enseignant y est remanié sur ordre de Lazarus Tymos, comte de Nacride.",
category: 'criminalité',
secondaryCategories: ['scandale'],
wiki: 'https://alexcreates.fr/leim/index.php/Tivian_Rodhus'
},
// "Les Milles Cages"
{
title: "Arrivée d'aventuriers à Borélis",
startDate: { day: 12, month: 7, year: 3209 },
description:
'Tara Belyus, Vascylly et Adol Sulvan livrent 3 condamnés à Handany. Ils partent pour la mer durant la journée.',
category: 'joueurs'
},
{
title: "Naufrage de l'Éclipse",
description:
"L'Éclipse, le navire de la garde contenant des condamnés à destination des Cages Handaniennes, s'échoue au large des côtes montagneuses de la Lance d'Aldys.",
startDate: { day: 14, month: 7, year: 3209 },
category: 'catastrophe'
},
{
title: 'Emprisonnement de Tivian Rodhus',
description: "Celui qu'on surnomme la Bête d'Ambrose arrive à Handany, où il purgera sa peine.",
startDate: { day: 14, month: 7, year: 3209 },
category: 'législation'
},
{
title: 'Sulvan et Anastael atteignent Bamast',
startDate: { day: 19, month: 2, year: 3210 },
category: 'joueurs'
},
{
title: 'Jugement de Bormis Griloup',
description:
"Bromis Griloup est jugé coupable d'escroquerie et sabotage aux Cours d'Acier de Tourgrise. Il purgera une peine de 10 ans au sein des prisons royales.",
startDate: { day: 4, month: 8, year: 3209 },
category: 'législation'
},
// "Les Cloches de Cantane"
{
title: 'Inauguration de la Cloche du Pilier',
description: "Le Moine Premier inaugure la grande cloche d'argent au sommet du Pilier d'Ikami.",
startDate: { day: 29, month: 5, year: 3209 },
category: 'religion',
secondaryCategories: ['inauguration']
},
{
title: '1ère disparation à Cantane',
description: "Taleb Vaht décède dans une grotte à la suite d'une attaque d'ischiels enragées.",
startDate: { day: 28, month: 7, year: 3209 },
category: 'mort',
hidden: true
},
{
title: '2ème disparation à Cantane',
description: 'Donovane le mineur kéturien disparait sans laisser de traces.',
startDate: { day: 32, month: 7, year: 3209 },
category: 'mort',
hidden: true
},
{
title: '3ème disparation à Cantane',
description: 'Disparition de Sébastien, gredin sauride.',
startDate: { day: 10, month: 8, year: 3209 },
category: 'mort',
hidden: true
},
{
title: '4ème disparation à Cantane',
description: 'Disparition de Thérence, patrouilleur sauride de la Vieille Garde.',
startDate: { day: 19, month: 8, year: 3209 },
category: 'mort',
hidden: true
},
{
title: '5ème disparation à Cantane',
description: 'Disparition de Mathilda Boulais, vendeuse de pierres.',
startDate: { day: 22, month: 8, year: 3209 },
category: 'mort',
hidden: true
},
{
title: 'Grande Banque Minérale de Cantane',
description:
'Les artisans et mineurs de Rougefer se réunissent à Cantane pour vendre le fruit de leur dur labeur.',
startDate: { day: 23, month: 8, year: 3209 },
endDate: { day: 26, month: 8, year: 3209 },
category: 'commerce'
}
]
})

View File

@@ -1,3 +0,0 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

14
src/App.vue Normal file
View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import { useDark } from '@vueuse/core'
import { RouterView } from 'vue-router'
import { useAuth } from './stores/AuthStore'
useDark()
useAuth()
</script>
<template>
<div class="h-screen">
<RouterView />
</div>
</template>

View File

@@ -2,6 +2,7 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 84% 4.9%; --foreground: 222.2 84% 4.9%;
@@ -35,7 +36,7 @@
--radius: 0.5rem; --radius: 0.5rem;
} }
:root.dark { .dark {
--background: 222.2 84% 4.9%; --background: 222.2 84% 4.9%;
--foreground: 210 40% 98%; --foreground: 210 40% 98%;
@@ -65,6 +66,7 @@
--ring: 212.7 26.8% 83.9%; --ring: 212.7 26.8% 83.9%;
} }
}
@layer base { @layer base {
* { * {

View File

@@ -0,0 +1,20 @@
<script lang="ts" setup>
import { supabase } from '@/supabase/client'
import Button from '../ui/button/Button.vue'
async function handleGoogleLogin() {
supabase.auth.signInWithOAuth({
provider: 'google',
options: {
queryParams: {
access_type: 'offline',
prompt: 'consent'
}
}
})
}
</script>
<template>
<Button @click="handleGoogleLogin"> Google </Button>
</template>

View File

@@ -0,0 +1,11 @@
<script lang="ts" setup>
import { useAuth } from '@/stores/AuthStore'
const { sessionToken } = useAuth()
</script>
<template>
<pre class="text-[.6em]">
{{ sessionToken }}
</pre>
</template>

View File

@@ -10,10 +10,6 @@ import DecadeLayout from './state/decennially/Layout.vue'
import YearLayout from './state/yearly/Layout.vue' import YearLayout from './state/yearly/Layout.vue'
const { currentConfig } = useCalendar() const { currentConfig } = useCalendar()
const { selectedDate, jumpToDate } = useCalendar()
const { fetchEvents } = useCalendarEvents()
fetchEvents()
const currentViewComponent: ComputedRef<Component> = computed<Component>(() => { const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
switch (currentConfig.viewType) { switch (currentConfig.viewType) {
@@ -31,16 +27,11 @@ const currentViewComponent: ComputedRef<Component> = computed<Component>(() => {
return CenturyLayout return CenturyLayout
} }
}) })
onMounted(() => {
jumpToDate(selectedDate)
})
</script> </script>
<template> <template>
<div class="h-full grid grid-rows-[auto,1fr]"> <div class="h-full grid grid-rows-[auto,1fr]">
<CalendarMenu /> <CalendarMenu />
<KeepAlive> <KeepAlive>
<component :is="currentViewComponent" /> <component :is="currentViewComponent" />
</KeepAlive> </KeepAlive>

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { getRelativeString } from '@/models/Date' import { getRelativeString } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { computed } from 'vue' import { computed } from 'vue'
@@ -14,19 +15,10 @@ const dateDifference = computed(() => getRelativeString(defaultDate, selectedDat
</script> </script>
<template> <template>
<ClientOnly>
<h1 class="text-2xl font-bold flex items-center gap-1"> <h1 class="text-2xl font-bold flex items-center gap-1">
<PhMapPin size="26" weight="bold" /> {{ mainDateTitle }} <PhMapPin size="26" weight="bold" /> {{ mainDateTitle }}
</h1> </h1>
<h2 class="text-lg italic opacity-75"> <h2 class="text-lg italic opacity-75">
{{ dateDifference }} {{ dateDifference }}
</h2> </h2>
<template #fallback>
<div class="grid gap-1">
<UiSkeleton class="h-8 w-64" />
<UiSkeleton class="h-6 w-28" />
</div>
</template>
</ClientOnly>
</template> </template>

View File

@@ -1,10 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { areDatesIdentical, type LeimDate } from '@/models/Date' import { areDatesIdentical, type LeimDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/Events' import type { CalendarEvent } from '@/models/Events'
import { ref } from 'vue'
import CalendarEventDetails from './CalendarEventDetails.vue' import CalendarEventDetails from './CalendarEventDetails.vue'
import { Popover, PopoverTrigger } from '@/components/ui/popover'
const props = defineProps<{ const props = defineProps<{
event: CalendarEvent event: CalendarEvent
@@ -25,8 +26,8 @@ function handleClosePopover() {
</script> </script>
<template> <template>
<UiPopover v-model:open="isPopoverOpen"> <Popover v-model:open="isPopoverOpen">
<UiPopoverTrigger as-child> <PopoverTrigger as-child>
<button <button
class="text-xs px-2 py-1 block w-full text-left rounded-sm whitespace-nowrap overflow-hidden text-ellipsis" class="text-xs px-2 py-1 block w-full text-left rounded-sm whitespace-nowrap overflow-hidden text-ellipsis"
:class=" :class="
@@ -57,15 +58,15 @@ function handleClosePopover() {
> >
{{ event.title }} {{ event.title }}
</button> </button>
</UiPopoverTrigger> </PopoverTrigger>
<CalendarEventDetails <CalendarEventDetails
v-once
:event :event
:spans-multiple-days :spans-multiple-days
:is-start-event :is-start-event
:is-end-event :is-end-event
@query:close-popover="handleClosePopover" @query:close-popover="handleClosePopover"
v-once
/> />
</UiPopover> </Popover>
</template> </template>

View File

@@ -12,6 +12,8 @@ import {
PhArrowBendDoubleUpLeft, PhArrowBendDoubleUpLeft,
PhArrowBendDoubleUpRight PhArrowBendDoubleUpRight
} from '@phosphor-icons/vue' } from '@phosphor-icons/vue'
import { Badge } from '@/components/ui/badge'
import { PopoverContent } from '@/components/ui/popover'
const { defaultDate, getFormattedDateTitle, jumpToDate } = useCalendar() const { defaultDate, getFormattedDateTitle, jumpToDate } = useCalendar()
const { getRelativeEventFromEvent } = useCalendarEvents() const { getRelativeEventFromEvent } = useCalendarEvents()
@@ -49,8 +51,8 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
</script> </script>
<template> <template>
<UiPopoverContent <PopoverContent
class="event-details w-96 bg-slate-900" class="event-details w-96"
:align="'center'" :align="'center'"
:align-offset="50" :align-offset="50"
:side="'left'" :side="'left'"
@@ -109,21 +111,21 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
<template v-if="event.category || event.secondaryCategories"> <template v-if="event.category || event.secondaryCategories">
<ul class="flex gap-1"> <ul class="flex gap-1">
<li v-if="event.category"> <li v-if="event.category">
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary"> <Badge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
{{ event.category }} {{ event.category }}
</UiBadge> </Badge>
</li> </li>
<li v-for="cat in event.secondaryCategories" :key="cat"> <li v-for="cat in event.secondaryCategories" :key="cat">
<UiBadge class="mix-blend-luminosity bg-gray-600" variant="secondary"> <Badge class="mix-blend-luminosity bg-gray-600" variant="secondary">
{{ cat }} {{ cat }}
</UiBadge> </Badge>
</li> </li>
</ul> </ul>
</template> </template>
<template v-if="event.description"> <template v-if="event.description">
<hr class="border-slate-500 mt-2" > <hr class="border-slate-500 mt-2" />
<div class="mt-2 text-sm text-slate-300"> <div class="mt-2 text-sm text-slate-300">
{{ event.description }} {{ event.description }}
@@ -132,47 +134,47 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
</div> </div>
<nav v-if="event.startDate && event.endDate" class="absolute -top-2 right-2 flex gap-2"> <nav v-if="event.startDate && event.endDate" class="absolute -top-2 right-2 flex gap-2">
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child> <Badge class="hover:opacity-100 hover:bg-slate-300" as-child>
<button <button
class="flex gap-1" class="flex gap-1"
title="Naviguer au début"
@click="handleJumpToDate(event.startDate!)" @click="handleJumpToDate(event.startDate!)"
title="Naviguer au début"
> >
<PhHourglassHigh size="16" weight="fill" /> Début <PhHourglassHigh size="16" weight="fill" /> Début
</button> </button>
</UiBadge> </Badge>
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin"> <Badge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
<button <button
class="flex gap-1" class="flex gap-1"
title="Naviguer à la fin"
@click="handleJumpToDate(event.endDate!)" @click="handleJumpToDate(event.endDate!)"
title="Naviguer à la fin"
> >
<PhHourglassLow size="16" weight="fill" /> Fin <PhHourglassLow size="16" weight="fill" /> Fin
</button> </button>
</UiBadge> </Badge>
</nav> </nav>
<nav class="mt-2 flex gap-2"> <nav class="mt-2 flex gap-2">
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child> <Badge class="hover:opacity-100 hover:bg-slate-300" as-child>
<button <button
class="flex gap-1" class="flex gap-1"
title="Évènement précédent"
@click="handleGotoRelativeEvent('prev')" @click="handleGotoRelativeEvent('prev')"
title="Évènement précédent"
> >
<PhArrowBendDoubleUpLeft size="16" weight="fill" /> Précédent <PhArrowBendDoubleUpLeft size="16" weight="fill" /> Précédent
</button> </button>
</UiBadge> </Badge>
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin"> <Badge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
<button <button
class="flex gap-1" class="flex gap-1"
title="Évènement suivant"
@click="handleGotoRelativeEvent('next')" @click="handleGotoRelativeEvent('next')"
title="Évènement suivant"
> >
<PhArrowBendDoubleUpRight size="16" weight="fill" /> Suivant <PhArrowBendDoubleUpRight size="16" weight="fill" /> Suivant
</button> </button>
</UiBadge> </Badge>
</nav> </nav>
</UiPopoverContent> </PopoverContent>
</template> </template>
<style lang="scss"> <style lang="scss">

View File

@@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useCalendar } from '@/stores/CalendarStore' import { useCalendar } from '@/stores/CalendarStore'
import { Button } from '@/components/ui/button'
import { PhMagnifyingGlass } from '@phosphor-icons/vue' import { PhMagnifyingGlass } from '@phosphor-icons/vue'
import CalendarMenuNav from './CalendarMenuNav.vue' import CalendarMenuNav from './CalendarMenuNav.vue'
import CalendarMenuSubnav from './CalendarMenuSubnav.vue' import CalendarMenuSubnav from './CalendarMenuSubnav.vue'
@@ -28,10 +29,10 @@ const { revealAdvancedSearch } = useCalendar()
<menu class="flex items-center gap-2"> <menu class="flex items-center gap-2">
<li> <li>
<UiButton search-slash @click="revealAdvancedSearch()"> <Button search-slash @click="revealAdvancedSearch()">
<PhMagnifyingGlass size="20" weight="light" /> <PhMagnifyingGlass size="20" weight="light" />
Recherche avancée Recherche avancée
</UiButton> </Button>
</li> </li>
<li> <li>
<CalendarSwitch /> <CalendarSwitch />

View File

@@ -2,12 +2,14 @@
import { computed, type ComputedRef } from 'vue' import { computed, type ComputedRef } from 'vue'
import { useCalendar } from '@/stores/CalendarStore' import { useCalendar } from '@/stores/CalendarStore'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { import {
PhCaretDoubleLeft, PhCaretDoubleLeft,
PhCaretDoubleRight, PhCaretDoubleRight,
PhCaretLeft, PhCaretLeft,
PhCaretRight PhCaretRight
} from '@phosphor-icons/vue' } from '@phosphor-icons/vue'
import Button from '@/components/ui/button/Button.vue'
interface DirectionLabels { interface DirectionLabels {
pastFar: string pastFar: string
@@ -143,56 +145,56 @@ function toFutureFar(): void {
<template> <template>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<UiTooltipProvider :delay-duration="250"> <TooltipProvider :delayDuration="250">
<UiTooltip> <Tooltip>
<UiTooltipTrigger as-child> <TooltipTrigger as-child>
<UiButton variant="outline" size="icon" @click="toPastFar()"> <Button variant="outline" size="icon" @click="toPastFar()">
<PhCaretDoubleLeft size="18" /> <PhCaretDoubleLeft size="18" />
</UiButton> </Button>
</UiTooltipTrigger> </TooltipTrigger>
<UiTooltipContent> <TooltipContent>
<p>{{ activeDirectionLabels.pastFar }}</p> <p>{{ activeDirectionLabels.pastFar }}</p>
</UiTooltipContent> </TooltipContent>
</UiTooltip> </Tooltip>
</UiTooltipProvider> </TooltipProvider>
<UiTooltipProvider :delay-duration="250"> <TooltipProvider :delayDuration="250">
<UiTooltip> <Tooltip>
<UiTooltipTrigger as-child> <TooltipTrigger as-child>
<UiButton variant="outline" size="icon" @click="toPastNear()"> <Button variant="outline" size="icon" @click="toPastNear()">
<PhCaretLeft size="18" /> <PhCaretLeft size="18" />
</UiButton> </Button>
</UiTooltipTrigger> </TooltipTrigger>
<UiTooltipContent> <TooltipContent>
<p>{{ activeDirectionLabels.pastNear }}</p> <p>{{ activeDirectionLabels.pastNear }}</p>
</UiTooltipContent> </TooltipContent>
</UiTooltip> </Tooltip>
</UiTooltipProvider> </TooltipProvider>
<UiTooltipProvider :delay-duration="250"> <TooltipProvider :delayDuration="250">
<UiTooltip> <Tooltip>
<UiTooltipTrigger as-child> <TooltipTrigger as-child>
<UiButton variant="outline" size="icon" @click="toFutureNear()"> <Button variant="outline" size="icon" @click="toFutureNear()">
<PhCaretRight size="18" /> <PhCaretRight size="18" />
</UiButton> </Button>
</UiTooltipTrigger> </TooltipTrigger>
<UiTooltipContent> <TooltipContent>
<p>{{ activeDirectionLabels.futureNear }}</p> <p>{{ activeDirectionLabels.futureNear }}</p>
</UiTooltipContent> </TooltipContent>
</UiTooltip> </Tooltip>
</UiTooltipProvider> </TooltipProvider>
<UiTooltipProvider :delay-duration="250"> <TooltipProvider :delayDuration="250">
<UiTooltip> <Tooltip>
<UiTooltipTrigger as-child> <TooltipTrigger as-child>
<UiButton variant="outline" size="icon" @click="toFutureFar()"> <Button variant="outline" size="icon" @click="toFutureFar()">
<PhCaretDoubleRight size="18" /> <PhCaretDoubleRight size="18" />
</UiButton> </Button>
</UiTooltipTrigger> </TooltipTrigger>
<UiTooltipContent> <TooltipContent>
<p>{{ activeDirectionLabels.futureFar }}</p> <p>{{ activeDirectionLabels.futureFar }}</p>
</UiTooltipContent> </TooltipContent>
</UiTooltip> </Tooltip>
</UiTooltipProvider> </TooltipProvider>
</div> </div>
</template> </template>

View File

@@ -3,7 +3,9 @@ import { daysPerMonth, monthsPerYear, type LeimDate } from '@/models/Date'
import { useCalendar } from '@/stores/CalendarStore' import { useCalendar } from '@/stores/CalendarStore'
import { useCalendarEvents } from '@/stores/EventStore' import { useCalendarEvents } from '@/stores/EventStore'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { PhArrowLineLeft, PhArrowLineRight } from '@phosphor-icons/vue' import { PhArrowLineLeft, PhArrowLineRight } from '@phosphor-icons/vue'
import Button from '@/components/ui/button/Button.vue'
const { currentDate, currentConfig, jumpToDate } = useCalendar() const { currentDate, currentConfig, jumpToDate } = useCalendar()
const { getRelativeEventFromDate } = useCalendarEvents() const { getRelativeEventFromDate } = useCalendarEvents()
@@ -60,54 +62,46 @@ function handleGotoPreviousEventPage(position: 'next' | 'prev' = 'next') {
<template> <template>
<div class="flex gap-2"> <div class="flex gap-2">
<div class="grid items-end w-40 px-4 py-2 border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm text-sm"> <div class="w-40 px-4 py-2 border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
<ClientOnly> <span class="text-sm">{{ currentDate.currentDateTitle }}</span>
<span>{{ currentDate.currentDateTitle }}</span>
<template #fallback>
<span class="inline-block">
<UiSkeleton class="h-[19px] w-full rounded-sm" />
</span>
</template>
</ClientOnly>
</div> </div>
<div class="border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm"> <div class="border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
<UiTooltipProvider :delay-duration="250"> <TooltipProvider :delayDuration="250">
<UiTooltip> <Tooltip>
<UiTooltipTrigger as-child> <TooltipTrigger as-child>
<UiButton <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
class="rounded-t-sm rounded-b-none" class="rounded-t-sm rounded-b-none"
@click="handleGotoPreviousEventPage('prev')" @click="handleGotoPreviousEventPage('prev')"
> >
<PhArrowLineLeft size="22" /> <PhArrowLineLeft size="22" />
</UiButton> </Button>
</UiTooltipTrigger> </TooltipTrigger>
<UiTooltipContent> <TooltipContent>
<p>Précédente page à évènements</p> <p>Précédente page à évènements</p>
</UiTooltipContent> </TooltipContent>
</UiTooltip> </Tooltip>
</UiTooltipProvider> </TooltipProvider>
</div> </div>
<div class="border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm"> <div class="border-slate-700 border-x-[1px] border-t-[1px] rounded-t-sm">
<UiTooltipProvider :delay-duration="250"> <TooltipProvider :delayDuration="250">
<UiTooltip> <Tooltip>
<UiTooltipTrigger as-child> <TooltipTrigger as-child>
<UiButton <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
class="rounded-t-sm rounded-b-none" class="rounded-t-sm rounded-b-none"
@click="handleGotoPreviousEventPage('next')" @click="handleGotoPreviousEventPage('next')"
> >
<PhArrowLineRight size="22" /> <PhArrowLineRight size="22" />
</UiButton> </Button>
</UiTooltipTrigger> </TooltipTrigger>
<UiTooltipContent> <TooltipContent>
<p>Prochaine page à évènements</p> <p>Prochaine page à évènements</p>
</UiTooltipContent> </TooltipContent>
</UiTooltip> </Tooltip>
</UiTooltipProvider> </TooltipProvider>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -0,0 +1,33 @@
<script lang="ts" setup>
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { useCalendar } from '@/stores/CalendarStore'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import Button from '../ui/button/Button.vue'
import { areDatesIdentical } from '@/models/Date'
const { defaultDate, selectedDate } = storeToRefs(useCalendar())
const { jumpToDefaultDate, getFormattedDateTitle } = useCalendar()
const defaultDateFormatted = getFormattedDateTitle(defaultDate.value, true)
const isDefaultDate = computed(() => {
return areDatesIdentical(selectedDate.value, defaultDate.value)
})
</script>
<template>
<TooltipProvider :delayDuration="250">
<Tooltip>
<TooltipTrigger as-child>
<Button @click="jumpToDefaultDate" size="sm" :disabled="isDefaultDate">
Aujourd'hui
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{{ defaultDateFormatted }}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</template>

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
import { useCalendar } from '@/stores/CalendarStore'
import { PhCalendarBlank } from '@phosphor-icons/vue'
import { computed } from 'vue'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import Button from '../ui/button/Button.vue'
const { currentConfig, viewTypeOptions, getViewTypeTitle, setViewType } = useCalendar()
const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType))
</script>
<template>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button size="sm" variant="secondary">
<PhCalendarBlank size="18" weight="fill" />
{{ viewTypeTitle }}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent :side="'bottom'" :collision-padding="30">
<DropdownMenuLabel>Mode d'affichage</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem
v-for="option in viewTypeOptions"
:key="option"
@click="setViewType(option)"
>
{{ getViewTypeTitle(option) }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>

View File

@@ -0,0 +1,61 @@
<script lang="ts" setup>
import { useCalendar } from '@/stores/CalendarStore'
import { PhList, PhHouse, PhMagnifyingGlass } from '@phosphor-icons/vue'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import Button from '../ui/button/Button.vue'
import UserCTA from '../global/user/CTA.vue'
const { revealAdvancedSearch } = useCalendar()
</script>
<template>
<nav class="w-16 py-6 border-r-[1px] border-l-slate-500 grid grid-rows-[1fr_auto] justify-center">
<menu class="flex flex-col gap-4">
<li class="mb-12">
<Button variant="ghost" size="icon" @click="console.log" class="rounded-full">
<PhList size="27" />
</Button>
</li>
<li>
<TooltipProvider :delayDuration="100">
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="rounded-full" as-child>
<RouterLink to="/">
<PhHouse size="24" weight="fill" />
</RouterLink>
</Button>
</TooltipTrigger>
<TooltipContent :side="'right'">
<p>Retourner aux outils</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</li>
<li>
<TooltipProvider :delayDuration="100">
<Tooltip>
<TooltipTrigger as-child>
<Button
variant="ghost"
size="icon"
class="rounded-full"
@click="revealAdvancedSearch()"
>
<PhMagnifyingGlass size="24" weight="fill" />
</Button>
</TooltipTrigger>
<TooltipContent :side="'right'">
<p>Recherche avancée</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</li>
</menu>
<div>
<UserCTA />
</div>
</nav>
</template>

View File

@@ -17,8 +17,31 @@ import { useCalendarEvents } from '@/stores/EventStore'
import { capitalize } from '@/utils/Strings' import { capitalize } from '@/utils/Strings'
import { useMagicKeys, useScroll, useStorage, whenever } from '@vueuse/core' import { useMagicKeys, useScroll, useStorage, whenever } from '@vueuse/core'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { searchUnifier, type SearchMode } from '../SearchMode' import { searchUnifier, type SearchMode } from '../Search'
import { Button } from '@/components/ui/button'
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
import { Dialog, DialogContent, DialogDescription, DialogTitle } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import {
Pagination,
PaginationEllipsis,
PaginationFirst,
PaginationLast,
PaginationList,
PaginationListItem,
PaginationNext,
PaginationPrev
} from '@/components/ui/pagination'
import {
TagsInput,
TagsInputInput,
TagsInputItem,
TagsInputItemDelete,
TagsInputItemText
} from '@/components/ui/tags-input'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { PhClockClockwise, PhClockCounterClockwise, PhMagnifyingGlass } from '@phosphor-icons/vue' import { PhClockClockwise, PhClockCounterClockwise, PhMagnifyingGlass } from '@phosphor-icons/vue'
import { import {
ComboboxAnchor, ComboboxAnchor,
@@ -30,10 +53,10 @@ import {
import SearchList from './lists/SearchList.vue' import SearchList from './lists/SearchList.vue'
const { characters } = storeToRefs(useCharacters()) const { characters } = useCharacters()
const { allEvents } = storeToRefs(useCalendarEvents()) const { allEvents } = useCalendarEvents()
const modalOpen = defineModel<boolean>({ default: false }) const modalOpen = defineModel({ default: false })
const searchQuery = ref<string>('') const searchQuery = ref<string>('')
// const searchEnough = computed<boolean>(() => searchQuery.value.length >= 2) // const searchEnough = computed<boolean>(() => searchQuery.value.length >= 2)
@@ -73,11 +96,11 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
// Assign data to loop over and filter // Assign data to loop over and filter
// They are assigned this way for readability // They are assigned this way for readability
if (selectedEntity.value === 'events') { if (selectedEntity.value === 'events') {
dataToFilter = allEvents.value dataToFilter = allEvents
} else if (selectedEntity.value === 'characters') { } else if (selectedEntity.value === 'characters') {
dataToFilter = characters.value dataToFilter = characters
} else { } else {
dataToFilter = [...allEvents.value, ...characters.value] dataToFilter = [...allEvents, ...characters]
} }
/** /**
@@ -105,7 +128,7 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
// Handle categories logic // Handle categories logic
let hitCategories: boolean = false let hitCategories: boolean = false
const allCategories: CalendarEventCategory[] = [] let allCategories: CalendarEventCategory[] = []
if (item.category) { if (item.category) {
allCategories.push(item.category) allCategories.push(item.category)
@@ -139,7 +162,7 @@ const searchResults = computed<(Character | CalendarEvent)[]>(() => {
// Handle categories logic // Handle categories logic
let hitCategories: boolean = false let hitCategories: boolean = false
const allCategories: CharacterCategory[] = [] let allCategories: CharacterCategory[] = []
if (item.category) { if (item.category) {
allCategories.push(item.category) allCategories.push(item.category)
@@ -170,16 +193,16 @@ function resetSearch() {
} }
/** /**
* Opens the search Uidialog * Opens the search dialog
*/ */
function openUiDialog() { function openDialog() {
modalOpen.value = true modalOpen.value = true
} }
/** /**
* Closes the search Uidialog * Closes the search dialog
*/ */
function closeUiDialog() { function closeDialog() {
modalOpen.value = false modalOpen.value = false
} }
@@ -195,13 +218,13 @@ function handleEntitySwitch() {
const keys = useMagicKeys() const keys = useMagicKeys()
whenever(keys.control_period, () => { whenever(keys.control_period, () => {
openUiDialog() openDialog()
}) })
const searchResultsRef = ref<HTMLElement | null>(null) const searchResultsRef = ref<HTMLElement | null>(null)
const { y: searchResultsY } = useScroll(searchResultsRef) const { y: searchResultsY } = useScroll(searchResultsRef)
watch([currentPage, selectedEntity], () => { watch(currentPage, () => {
searchResultsY.value = 0 searchResultsY.value = 0
}) })
@@ -227,10 +250,10 @@ const filteredCategories = computed(() =>
* *
* @param e Radix Change Event * @param e Radix Change Event
*/ */
function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) { function handleCategorySelect(e: any) {
if (typeof e === 'string') { if (typeof e.detail.value === 'string') {
searchCategory.value = '' searchCategory.value = ''
selectedCategories.value.push(e) selectedCategories.value.push(e.detail.value)
} }
if (filteredCategories.value.length === 0) { if (filteredCategories.value.length === 0) {
@@ -240,31 +263,31 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
</script> </script>
<template> <template>
<UiDialog v-model:open="modalOpen" @update:open="resetSearch()"> <Dialog v-model:open="modalOpen" @update:open="resetSearch()">
<UiDialogContent <DialogContent
class="flex flex-col flex-nowrap top-16 -translate-y-0 data-[state=closed]:slide-out-to-top-[5%] data-[state=open]:slide-in-from-top-[5%]" class="flex flex-col flex-nowrap top-16 -translate-y-0 data-[state=closed]:slide-out-to-top-[5%] data-[state=open]:slide-in-from-top-[5%]"
:class="{ :class="{
'bottom-16': searchResults.length > 0 'bottom-16': searchResults.length > 0
}" }"
> >
<VisuallyHidden> <VisuallyHidden>
<UiDialogTitle> Recherche avancée </UiDialogTitle> <DialogTitle> Recherche avancée </DialogTitle>
</VisuallyHidden> </VisuallyHidden>
<VisuallyHidden> <VisuallyHidden>
<UiDialogDescription> <DialogDescription>
Rechercher les données disponibles sur le calendrier Rechercher les données disponibles sur le calendrier
</UiDialogDescription> </DialogDescription>
</VisuallyHidden> </VisuallyHidden>
<!-- UiDialog header --> <!-- Dialog header -->
<div id="searchForm" class="grid gap-3"> <div class="grid gap-3" id="searchForm">
<div class="relative w-full h-fit"> <div class="relative w-full h-fit">
<UiInput <Input
id="search" id="search"
v-model:model-value="searchQuery"
type="text" type="text"
placeholder="Rechercher le calendrier" placeholder="Rechercher le calendrier"
class="pl-10 py-6 text-lg" class="pl-10 py-6 text-lg"
v-model:model-value="searchQuery"
autocomplete="off" autocomplete="off"
/> />
<span class="absolute start-1 inset-y-0 flex items-center justify-center px-2 opacity-50"> <span class="absolute start-1 inset-y-0 flex items-center justify-center px-2 opacity-50">
@@ -274,28 +297,28 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<UiToggleGroup <ToggleGroup
v-model="selectedEntity"
type="single" type="single"
class="justify-start" class="justify-start"
v-model="selectedEntity"
@update:model-value="handleEntitySwitch()" @update:model-value="handleEntitySwitch()"
> >
<UiToggleGroupItem value="events" aria-label="Uniquement les évènements"> <ToggleGroupItem value="events" aria-label="Uniquement les évènements" v-once>
Évènements Évènements
</UiToggleGroupItem> </ToggleGroupItem>
<UiToggleGroupItem value="characters" aria-label="Uniquement les personnages"> <ToggleGroupItem value="characters" aria-label="Uniquement les personnages" v-once>
Personnages Personnages
</UiToggleGroupItem> </ToggleGroupItem>
</UiToggleGroup> </ToggleGroup>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<UiTagsInput class="px-0 gap-0 w-72" :model-value="selectedCategories"> <TagsInput class="px-0 gap-0 w-72" :model-value="selectedCategories">
<div class="flex gap-2 flex-wrap items-center px-3"> <div class="flex gap-2 flex-wrap items-center px-3">
<UiTagsInputItem v-for="item in selectedCategories" :key="item" :value="item"> <TagsInputItem v-for="item in selectedCategories" :key="item" :value="item">
<UiTagsInputItemText class="capitalize" /> <TagsInputItemText class="capitalize" />
<UiTagsInputItemDelete /> <TagsInputItemDelete />
</UiTagsInputItem> </TagsInputItem>
</div> </div>
<ComboboxRoot <ComboboxRoot
@@ -306,7 +329,7 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
> >
<ComboboxAnchor as-child> <ComboboxAnchor as-child>
<ComboboxInput placeholder="Catégories" as-child> <ComboboxInput placeholder="Catégories" as-child>
<UiTagsInputInput <TagsInputInput
class="w-full px-3" class="w-full px-3"
:class="selectedCategories.length > 0 ? 'mt-2' : ''" :class="selectedCategories.length > 0 ? 'mt-2' : ''"
@keydown.enter.prevent @keydown.enter.prevent
@@ -315,79 +338,79 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
</ComboboxAnchor> </ComboboxAnchor>
<ComboboxPortal :to="'#searchForm'"> <ComboboxPortal :to="'#searchForm'">
<UiCommandList <CommandList
position="popper" position="popper"
class="w-[--radix-popper-anchor-width] rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50" class="w-[--radix-popper-anchor-width] rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50"
:dismissable="true" :dismissable="true"
> >
<UiCommandEmpty /> <CommandEmpty />
<UiCommandGroup> <CommandGroup>
<UiCommandItem <CommandItem
v-for="cat in filteredCategories" v-for="framework in filteredCategories"
:key="cat" :key="framework"
:value="cat" :value="framework"
@select.prevent="handleCategorySelect(cat)" @select.prevent="handleCategorySelect"
> >
{{ capitalize(cat) }} {{ capitalize(framework) }}
</UiCommandItem> </CommandItem>
</UiCommandGroup> </CommandGroup>
</UiCommandList> </CommandList>
</ComboboxPortal> </ComboboxPortal>
</ComboboxRoot> </ComboboxRoot>
</UiTagsInput> </TagsInput>
<UiTooltipProvider :delay-duration="250"> <TooltipProvider :delayDuration="250">
<UiTooltip> <Tooltip>
<UiTooltipTrigger as-child> <TooltipTrigger as-child>
<UiButton <Button
:variant="selectedOrder === 'desc' ? 'secondary' : 'outline'" :variant="selectedOrder === 'desc' ? 'secondary' : 'outline'"
size="icon" size="icon"
@click="setOrderDesc()" @click="setOrderDesc()"
> >
<PhClockCounterClockwise size="18" /> <PhClockCounterClockwise size="18" />
</UiButton> </Button>
</UiTooltipTrigger> </TooltipTrigger>
<UiTooltipContent> <TooltipContent>
<p>Plus ancien</p> <p>Plus ancien</p>
</UiTooltipContent> </TooltipContent>
</UiTooltip> </Tooltip>
</UiTooltipProvider> </TooltipProvider>
<UiTooltipProvider :delay-duration="250"> <TooltipProvider :delayDuration="250">
<UiTooltip> <Tooltip>
<UiTooltipTrigger as-child> <TooltipTrigger as-child>
<UiButton <Button
:variant="selectedOrder === 'asc' ? 'secondary' : 'outline'" :variant="selectedOrder === 'asc' ? 'secondary' : 'outline'"
size="icon" size="icon"
@click="setOrderAsc()" @click="setOrderAsc()"
> >
<PhClockClockwise size="18" /> <PhClockClockwise size="18" />
</UiButton> </Button>
</UiTooltipTrigger> </TooltipTrigger>
<UiTooltipContent> <TooltipContent>
<p>Plus récent</p> <p>Plus récent</p>
</UiTooltipContent> </TooltipContent>
</UiTooltip> </Tooltip>
</UiTooltipProvider> </TooltipProvider>
</div> </div>
</div> </div>
</div> </div>
<hr > <hr />
<div v-if="searchResults.length > 0" ref="searchResultsRef" class="grow overflow-y-auto"> <div v-if="searchResults.length > 0" class="grow overflow-y-auto" ref="searchResultsRef">
<SearchList <SearchList
:results="searchResults" :results="searchResults"
:current-entity="selectedEntity" :current-entity="selectedEntity"
:order="selectedOrder" :order="selectedOrder"
:start-at="startOfList" :start-at="startOfList"
:end-at="endOfList" :end-at="endOfList"
@jumped-to-date="closeUiDialog()" @jumped-to-date="closeDialog()"
/> />
</div> </div>
<div class="flex justify-end"> <div class="flex justify-end">
<UiPagination <Pagination
v-model:page="currentPage" v-model:page="currentPage"
:total="searchResults.length" :total="searchResults.length"
:items-per-page="itemsPerPage" :items-per-page="itemsPerPage"
@@ -395,32 +418,32 @@ function handleCategorySelect(e: (CharacterCategory | CalendarEventCategory)) {
show-edges show-edges
:default-page="1" :default-page="1"
> >
<UiPaginationList v-slot="{ items }" class="flex items-center gap-1"> <PaginationList v-slot="{ items }" class="flex items-center gap-1">
<UiPaginationFirst /> <PaginationFirst />
<UiPaginationPrev /> <PaginationPrev />
<template v-for="(item, index) in items"> <template v-for="(item, index) in items">
<UiPaginationListItem <PaginationListItem
v-if="item.type === 'page'" v-if="item.type === 'page'"
:key="index" :key="index"
:value="item.value" :value="item.value"
as-child as-child
> >
<UiButton <Button
class="w-10 h-10 p-0" class="w-10 h-10 p-0"
:variant="item.value === currentPage ? 'default' : 'outline'" :variant="item.value === currentPage ? 'default' : 'outline'"
> >
{{ item.value }} {{ item.value }}
</UiButton> </Button>
</UiPaginationListItem> </PaginationListItem>
<UiPaginationEllipsis v-else :key="item.type" :index="index" /> <PaginationEllipsis v-else :key="item.type" :index="index" />
</template> </template>
<UiPaginationNext /> <PaginationNext />
<UiPaginationLast /> <PaginationLast />
</UiPaginationList> </PaginationList>
</UiPagination> </Pagination>
</div> </div>
</UiDialogContent> </DialogContent>
</UiDialog> </Dialog>
</template> </template>

View File

@@ -37,13 +37,13 @@ const { getFormattedDateTitle } = useCalendar()
<menu class="flex gap-2 border-[1px] border-slate-700 rounded-sm w-fit"> <menu class="flex gap-2 border-[1px] border-slate-700 rounded-sm w-fit">
<li v-if="character.birth"> <li v-if="character.birth">
<TooltipProvider :delay-duration="100"> <TooltipProvider :delayDuration="100">
<Tooltip> <Tooltip>
<TooltipTrigger as-child> <TooltipTrigger as-child>
<Button <Button
@click="$emit('query:date-jump', character.birth!)"
variant="ghost" variant="ghost"
size="xs" size="xs"
@click="$emit('query:date-jump', character.birth!)"
> >
<PhPlant size="16" weight="fill" /> <PhPlant size="16" weight="fill" />
{{ getFormattedDateTitle(character.birth, true) }} {{ getFormattedDateTitle(character.birth, true) }}
@@ -57,13 +57,13 @@ const { getFormattedDateTitle } = useCalendar()
</li> </li>
<span v-if="character.birth && character.death">-</span> <span v-if="character.birth && character.death">-</span>
<li v-if="character.death"> <li v-if="character.death">
<TooltipProvider :delay-duration="100"> <TooltipProvider :delayDuration="100">
<Tooltip> <Tooltip>
<TooltipTrigger as-child> <TooltipTrigger as-child>
<Button <Button
@click="$emit('query:date-jump', character.death!)"
variant="ghost" variant="ghost"
size="xs" size="xs"
@click="$emit('query:date-jump', character.death!)"
> >
<PhSkull size="16" weight="fill" /> <PhSkull size="16" weight="fill" />
{{ getFormattedDateTitle(character.death, true) }} {{ getFormattedDateTitle(character.death, true) }}
@@ -77,7 +77,7 @@ const { getFormattedDateTitle } = useCalendar()
</li> </li>
</menu> </menu>
<hr v-if="character.description" class="border-white opacity-25" > <hr v-if="character.description" class="border-white opacity-25" />
<div v-if="character.description" class="text-sm"> <div v-if="character.description" class="text-sm">
<span class="opacity-75"> <span class="opacity-75">

View File

@@ -105,7 +105,7 @@ const dateDuration: string | null = props.event.endDate
</div> </div>
<div v-if="event.description" class="text-sm"> <div v-if="event.description" class="text-sm">
<hr class="my-2 border-white opacity-50" > <hr class="my-2 border-white opacity-50" />
<span class="opacity-75"> <span class="opacity-75">
{{ event.description }} {{ event.description }}
</span> </span>

View File

@@ -3,11 +3,12 @@ import { isCharacter, type Character } from '@/models/Characters'
import { compareDates, type LeimDate, type LeimDateOrder } from '@/models/Date' import { compareDates, type LeimDate, type LeimDateOrder } from '@/models/Date'
import { isCalendarEvent, type CalendarEvent } from '@/models/Events' import { isCalendarEvent, type CalendarEvent } from '@/models/Events'
import { useCalendar } from '@/stores/CalendarStore' import { useCalendar } from '@/stores/CalendarStore'
import { computed } from 'vue' import { computed, ref } from 'vue'
import type { SearchMode } from '../../SearchMode' import type { SearchMode } from '../../Search'
import CharacterCallout from './CharacterCallout.vue' import CharacterCallout from './CharacterCallout.vue'
import EventCallout from './EventCallout.vue' import EventCallout from './EventCallout.vue'
import { useScroll } from '@vueuse/core'
const props = defineProps<{ const props = defineProps<{
results: (Character | CalendarEvent)[] results: (Character | CalendarEvent)[]

View File

@@ -6,6 +6,7 @@ import { useElementBounding } from '@vueuse/core'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { computed, ref, type ComputedRef } from 'vue' import { computed, ref, type ComputedRef } from 'vue'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import CalendarEventButton from '../../CalendarEvent.vue' import CalendarEventButton from '../../CalendarEvent.vue'
import type { CalendarEvent } from '@/models/Events' import type { CalendarEvent } from '@/models/Events'
@@ -73,7 +74,6 @@ const eventsNotDisplayed = computed(
class="relative z-10 group block w-full text-center cursor-pointer" class="relative z-10 group block w-full text-center cursor-pointer"
@click="selectDate(date)" @click="selectDate(date)"
> >
<ClientOnly>
<span <span
class="inline-flex w-8 h-8 aspect-square items-center justify-center rounded-full border-2 border-transparent font-bold transition-colors group-hover:border-slate-800" class="inline-flex w-8 h-8 aspect-square items-center justify-center rounded-full border-2 border-transparent font-bold transition-colors group-hover:border-slate-800"
:class="{ :class="{
@@ -83,7 +83,6 @@ const eventsNotDisplayed = computed(
> >
{{ date.day }} {{ date.day }}
</span> </span>
</ClientOnly>
</button> </button>
<ul <ul
@@ -98,15 +97,15 @@ const eventsNotDisplayed = computed(
</li> </li>
<li v-if="eventsNotDisplayed > 0" class="pointer-events-auto"> <li v-if="eventsNotDisplayed > 0" class="pointer-events-auto">
<UiPopover> <Popover>
<UiPopoverTrigger as-child> <PopoverTrigger as-child>
<button <button
class="text-xs px-2 py-1 block w-full text-left font-bold rounded-sm whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer transition-colors hover:bg-slate-800" class="text-xs px-2 py-1 block w-full text-left font-bold rounded-sm whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer transition-colors hover:bg-slate-800"
> >
{{ eventsNotDisplayed }} autre{{ eventsNotDisplayed > 1 ? 's' : '' }} {{ eventsNotDisplayed }} autre{{ eventsNotDisplayed > 1 ? 's' : '' }}
</button> </button>
</UiPopoverTrigger> </PopoverTrigger>
<UiPopoverContent class="w-80" :align="'center'" :side="'right'"> <PopoverContent class="w-80" :align="'center'" :side="'right'">
<div class="text-center mb-4"> <div class="text-center mb-4">
<span <span
class="inline-flex w-12 h-12 aspect-square items-center justify-center text-lg font-semibold text-slate-300 bg-slate-800 rounded-full" class="inline-flex w-12 h-12 aspect-square items-center justify-center text-lg font-semibold text-slate-300 bg-slate-800 rounded-full"
@@ -123,8 +122,8 @@ const eventsNotDisplayed = computed(
<CalendarEventButton :event :tile-date="date" /> <CalendarEventButton :event :tile-date="date" />
</li> </li>
</ul> </ul>
</UiPopoverContent> </PopoverContent>
</UiPopover> </Popover>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -26,7 +26,7 @@ const moveCalendarRight = useThrottleFn(() => {
<template> <template>
<div class="container mt-[10vh] mb-auto" @wheel="handleWheel"> <div class="container mt-[10vh] mb-auto" @wheel="handleWheel">
<div class="grid grid-cols-5 gap-x-8 gap-y-16"> <div ref="test" class="grid grid-cols-5 gap-x-8 gap-y-16">
<MonthTile <MonthTile
v-for="month in staticConfig.monthsPerYear" v-for="month in staticConfig.monthsPerYear"
:key="month" :key="month"

View File

@@ -0,0 +1,54 @@
<script lang="ts" setup>
import { supabase } from '@/supabase/client'
import { type Session } from '@supabase/supabase-js'
import { computed, onMounted, ref, watch } from 'vue'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { PhUserCircle } from '@phosphor-icons/vue'
import Button from '@/components/ui/button/Button.vue'
import MenuLoggedIn from './MenuLoggedIn.vue'
import MenuLoggedOut from './MenuLoggedOut.vue'
const session = ref<Session | null>()
const hasSession = computed(() => Boolean(session.value))
const menuOpened = ref<boolean>(false)
onMounted(() => {
supabase.auth.getSession().then(({ data }) => {
session.value = data.session
})
supabase.auth.onAuthStateChange((_, _session) => {
session.value = _session
})
})
watch(session, closeMenu)
function closeMenu() {
menuOpened.value = false
}
</script>
<template>
<Popover v-model:open="menuOpened">
<PopoverTrigger :as-child="hasSession">
<Avatar v-if="session" class="cursor-pointer">
<AvatarImage
:src="session?.user.user_metadata.avatar_url"
:alt="session?.user.user_metadata.full_name"
referrerpolicy="no-referrer"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Button v-else variant="outline" size="icon">
<PhUserCircle size="18" />
</Button>
</PopoverTrigger>
<PopoverContent class="w-fit p-0" :align="'start'" :side="'top'" :collision-padding="20">
<MenuLoggedIn v-if="session" :session />
<MenuLoggedOut v-else />
</PopoverContent>
</Popover>
</template>

View File

@@ -0,0 +1,37 @@
<script lang="ts" setup>
import { supabase } from '@/supabase/client'
import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
import type { Session } from '@supabase/supabase-js'
import { useRouter } from 'vue-router'
defineProps<{
session: Session
}>()
const router = useRouter()
async function handleLogout() {
try {
const { error } = await supabase.auth.signOut()
if (error) throw error
} catch (err) {
console.log(err)
}
}
function gotoProfilePage() {
router.push({ path: '/profil' })
}
</script>
<template>
<Command>
<CommandList>
<CommandGroup :heading="`Connecté en tant que ${session.user.user_metadata.email}`">
<CommandItem value="profile" @select="gotoProfilePage"> Profil </CommandItem>
<CommandItem value="logout" @select="handleLogout"> Déconnexion </CommandItem>
</CommandGroup>
</CommandList>
</Command>
</template>

View File

@@ -0,0 +1,33 @@
<script lang="ts" setup>
import { supabase } from '@/supabase/client'
import { Command, CommandItem, CommandList } from '@/components/ui/command'
import type { Session } from '@supabase/supabase-js'
defineProps<{
session?: Session | null
}>()
async function handleGoogleLogin() {
try {
supabase.auth.signInWithOAuth({
provider: 'google',
options: {
queryParams: {
access_type: 'offline',
prompt: 'consent'
}
}
})
} catch (err) {
console.log(err)
}
}
</script>
<template>
<Command>
<CommandList>
<CommandItem value="logout" @select="handleGoogleLogin"> Connexion </CommandItem>
</CommandList>
</Command>
</template>

View File

@@ -7,19 +7,18 @@ export const badgeVariants = cva(
{ {
variants: { variants: {
variant: { variant: {
default: default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
secondary: secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive: destructive:
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground', outline: 'text-foreground'
}, }
}, },
defaultVariants: { defaultVariants: {
variant: 'default', variant: 'default'
}, }
}, }
) )
export type BadgeVariants = VariantProps<typeof badgeVariants> export type BadgeVariants = VariantProps<typeof badgeVariants>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue' import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { RouterLink } from 'vue-router'
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes['class'] class?: HTMLAttributes['class']
@@ -19,7 +20,7 @@ const props = defineProps<{
> >
<slot /> <slot />
<NuxtLink <RouterLink
v-if="props.link" v-if="props.link"
:to="props.link" :to="props.link"
class="absolute inset-0 focus-visible:outline-none" class="absolute inset-0 focus-visible:outline-none"

View File

@@ -8,7 +8,7 @@ const props = defineProps<{
</script> </script>
<template> <template>
<div :class="cn('p-6 pt-0', props.class)"> <div :class="cn('p-6 pt-0 text-muted-foreground', props.class)">
<slot /> <slot />
</div> </div>
</template> </template>

View File

@@ -8,7 +8,7 @@ const props = defineProps<{
</script> </script>
<template> <template>
<div :class="cn('text-sm text-muted-foreground', props.class)"> <p :class="cn('text-sm text-muted-foreground mt-1', props.class)">
<slot /> <slot />
</div> </p>
</template> </template>

View File

@@ -8,7 +8,7 @@ const props = defineProps<{
</script> </script>
<template> <template>
<div :class="cn('flex flex-col gap-y-1.5 p-6', props.class)"> <div :class="cn('flex flex-col gap-y-1.5 px-6 pt-6 pb-3', props.class)">
<slot /> <slot />
</div> </div>
</template> </template>

View File

@@ -8,11 +8,7 @@ const props = defineProps<{
</script> </script>
<template> <template>
<h3 <h3 :class="cn('text-2xl font-semibold leading-none tracking-tight', props.class)">
:class="
cn('text-2xl font-semibold leading-none tracking-tight', props.class)
"
>
<slot /> <slot />
</h3> </h3>
</template> </template>

View File

@@ -1,5 +1,3 @@
export { ComboboxPortal } from 'radix-vue'
export { default as Command } from './Command.vue' export { default as Command } from './Command.vue'
export { default as CommandDialog } from './CommandDialog.vue' export { default as CommandDialog } from './CommandDialog.vue'
export { default as CommandEmpty } from './CommandEmpty.vue' export { default as CommandEmpty } from './CommandEmpty.vue'

Some files were not shown because too many files have changed in this diff Show More