mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-17 14:30:52 +01:00
switch to nuxt ui
This commit is contained in:
@ -1,48 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="accordion divide-neutral/20 divide-y accordion-shadow *:accordion-item-active:shadow-md"
|
||||
>
|
||||
<div
|
||||
:id="itemId"
|
||||
ref="accordionRef"
|
||||
class="accordion-item active"
|
||||
>
|
||||
<button
|
||||
class="accordion-toggle inline-flex items-center gap-x-4 text-start"
|
||||
:aria-controls="collapseId"
|
||||
aria-expanded="true"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="icon-[tabler--chevron-right] accordion-item-active:rotate-90 size-5 shrink-0 transition-transform duration-300 rtl:rotate-180"
|
||||
/>
|
||||
<slot name="title" />
|
||||
</button>
|
||||
<div
|
||||
:id="collapseId"
|
||||
class="accordion-content w-full overflow-hidden transition-[height] duration-300"
|
||||
:aria-labelledby="itemId"
|
||||
role="region"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HSAccordion } from 'flyonui/flyonui'
|
||||
|
||||
const itemId = useId()
|
||||
const collapseId = useId()
|
||||
|
||||
const accordionRef = useTemplateRef('accordionRef')
|
||||
const accordion = ref<HSAccordion>()
|
||||
|
||||
onMounted(() => {
|
||||
if (accordionRef.value) {
|
||||
accordion.value = new window.HSAccordion(accordionRef.value)
|
||||
accordion.value.hide()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -1,107 +0,0 @@
|
||||
<template>
|
||||
<div class="z-10 pointer-events-auto">
|
||||
<div
|
||||
class="dropdown relative inline-flex [--placement:top] [--strategy:absolute]"
|
||||
>
|
||||
<button
|
||||
:id
|
||||
class="dropdown-toggle btn btn-primary btn-xl btn-square dropdown-open:rotate-45 transition-transform"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
aria-label="Menu"
|
||||
>
|
||||
<Icon
|
||||
:name="icon"
|
||||
class="size-11 shrink-0"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-60 bg-transparent shadow-none"
|
||||
data-dropdown-transition
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
:aria-labelledby="id"
|
||||
>
|
||||
<li
|
||||
v-for="link in menu"
|
||||
class="dropdown-item hover:bg-transparent px-0 py-1"
|
||||
>
|
||||
<NuxtLinkLocale
|
||||
v-if="link.to"
|
||||
:to="link.to"
|
||||
class="btn btn-primary flex items-center no-underline rounded-lg flex-nowrap w-full"
|
||||
>
|
||||
<Icon
|
||||
v-if="link.icon"
|
||||
:name="link.icon"
|
||||
class="me-3"
|
||||
/>
|
||||
{{ te(link.label) ? t(link.label) : link.label }}
|
||||
</NuxtLinkLocale>
|
||||
|
||||
<button
|
||||
v-else
|
||||
@click="link.action"
|
||||
class="link hover:link-primary flex items-center no-underline w-full"
|
||||
>
|
||||
<Icon
|
||||
v-if="link.icon"
|
||||
:name="link.icon"
|
||||
class="me-3"
|
||||
/>
|
||||
{{ link.label }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IActionMenuItem } from './types'
|
||||
|
||||
defineProps({
|
||||
menu: {
|
||||
type: Array as PropType<IActionMenuItem[]>,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'mdi:plus',
|
||||
},
|
||||
})
|
||||
|
||||
const id = useId()
|
||||
|
||||
const { t, te } = useI18n()
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
@keyframes fadeInStagger {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(15px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 2. Die Listenelemente sind standardmäßig unsichtbar, damit sie nicht aufblitzen */
|
||||
.stagger-menu li {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 3. Wenn das Menü geöffnet wird, weise die Animation zu */
|
||||
:global(.dropdown-open) .stagger-menu li {
|
||||
animation-name: fadeInStagger;
|
||||
animation-duration: 0.4s;
|
||||
animation-timing-function: ease-out;
|
||||
|
||||
/* SEHR WICHTIG: Sorgt dafür, dass die Elemente nach der Animation sichtbar bleiben (den Zustand von 'to' beibehalten) */
|
||||
animation-fill-mode: forwards;
|
||||
|
||||
/* Die individuelle animation-delay wird per :style im Template gesetzt. */
|
||||
}
|
||||
</style>
|
||||
@ -1,22 +1,31 @@
|
||||
<template>
|
||||
<button
|
||||
class="btn join-item pointer-events-auto"
|
||||
:type
|
||||
>
|
||||
<UiTooltip
|
||||
:tooltip
|
||||
v-if="tooltip"
|
||||
>
|
||||
<slot />
|
||||
</UiTooltip>
|
||||
|
||||
<slot v-else />
|
||||
</button>
|
||||
<div>
|
||||
<UTooltip :text="buttonProps?.tooltip">
|
||||
<UButton
|
||||
class="pointer-events-auto"
|
||||
v-bind="{ ...buttonProps, ...$attrs }"
|
||||
@click="(e) => $emit('click', e)"
|
||||
>
|
||||
<template
|
||||
v-for="(_, slotName) in $slots"
|
||||
#[slotName]="slotProps"
|
||||
>
|
||||
<slot
|
||||
:name="slotName"
|
||||
v-bind="slotProps"
|
||||
/>
|
||||
</template>
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { type = 'button' } = defineProps<{
|
||||
type?: 'reset' | 'submit' | 'button'
|
||||
import type { ButtonProps } from '@nuxt/ui'
|
||||
|
||||
interface IButtonProps extends /* @vue-ignore */ ButtonProps {
|
||||
tooltip?: string
|
||||
}>()
|
||||
}
|
||||
const buttonProps = defineProps<IButtonProps>()
|
||||
defineEmits<{ click: [Event] }>()
|
||||
</script>
|
||||
|
||||
8
src/components/ui/button/types.d.ts
vendored
8
src/components/ui/button/types.d.ts
vendored
@ -1,8 +0,0 @@
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
|
||||
export interface IActionMenuItem {
|
||||
label: string
|
||||
icon?: string
|
||||
action?: () => Promise<unknown>
|
||||
to?: RouteLocationRaw
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
<template>
|
||||
<div class="card min-w-56">
|
||||
<slot name="image" />
|
||||
|
||||
<div
|
||||
class="card-header"
|
||||
v-if="$slots.title || title"
|
||||
>
|
||||
<slot name="header">
|
||||
<div
|
||||
v-if="$slots.title || title"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<Icon
|
||||
v-if="icon"
|
||||
:name="icon"
|
||||
size="28"
|
||||
/>
|
||||
<h5
|
||||
v-if="title"
|
||||
class="card-title mb-0"
|
||||
>
|
||||
{{ title }}
|
||||
</h5>
|
||||
<slot
|
||||
v-else
|
||||
name="title"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-base-content/45">{{ subtitle }}</div>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="card-body"
|
||||
:class="bodyClass"
|
||||
>
|
||||
<slot />
|
||||
<div
|
||||
v-if="$slots.action"
|
||||
class="card-actions"
|
||||
>
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="$slots.footer"
|
||||
class="card-footer"
|
||||
>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits(['close', 'submit'])
|
||||
|
||||
defineProps<{
|
||||
title?: string
|
||||
subtitle?: string
|
||||
icon?: string
|
||||
bodyClass?: string
|
||||
}>()
|
||||
|
||||
const { escape, enter } = useMagicKeys()
|
||||
|
||||
watchEffect(async () => {
|
||||
if (escape.value) {
|
||||
await nextTick()
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
watchEffect(async () => {
|
||||
if (enter.value) {
|
||||
await nextTick()
|
||||
emit('submit')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -1,61 +1,73 @@
|
||||
<template>
|
||||
<UiDialog
|
||||
:title
|
||||
@close="onAbort"
|
||||
<UModal
|
||||
v-model:open="open"
|
||||
:title
|
||||
:description
|
||||
:fullscreen="isSmallScreen"
|
||||
>
|
||||
<template #trigger>
|
||||
<slot name="trigger" />
|
||||
</template>
|
||||
<slot>
|
||||
<!-- <UiButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
icon="mdi:menu"
|
||||
:ui="{
|
||||
base: '',
|
||||
}"
|
||||
/> -->
|
||||
</slot>
|
||||
|
||||
<template #title>
|
||||
<slot name="title" />
|
||||
</template>
|
||||
|
||||
<slot />
|
||||
|
||||
<template #buttons>
|
||||
<slot name="buttons">
|
||||
<UiButton
|
||||
class="btn-error btn-outline w-full sm:w-auto"
|
||||
@click="onAbort"
|
||||
>
|
||||
<Icon :name="abortIcon || 'mdi:close'" />
|
||||
{{ abortLabel ?? t('abort') }}
|
||||
</UiButton>
|
||||
<UiButton
|
||||
class="btn-primary w-full sm:w-auto"
|
||||
@click="onConfirm"
|
||||
>
|
||||
<Icon :name="confirmIcon || 'mdi:check'" />
|
||||
{{ confirmLabel ?? t('confirm') }}
|
||||
</UiButton>
|
||||
</slot>
|
||||
<template #body>
|
||||
<slot name="body" />
|
||||
</template>
|
||||
</UiDialog>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-end w-full">
|
||||
<UiButton
|
||||
:icon="abortIcon || 'mdi:close'"
|
||||
:label="abortLabel || t('abort')"
|
||||
block
|
||||
color="error"
|
||||
variant="outline"
|
||||
@click="open = false"
|
||||
/>
|
||||
<UiButton
|
||||
:icon="confirmIcon || 'mdi:check'"
|
||||
:label="confirmLabel || t('confirm')"
|
||||
block
|
||||
color="primary"
|
||||
varaint="solid"
|
||||
@click="$emit('confirm')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
|
||||
|
||||
defineProps<{
|
||||
confirmLabel?: string
|
||||
abortLabel?: string
|
||||
title?: string
|
||||
abortIcon?: string
|
||||
abortLabel?: string
|
||||
confirmIcon?: string
|
||||
confirmLabel?: string
|
||||
description?: string
|
||||
title?: string
|
||||
}>()
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
|
||||
const { t } = useI18n()
|
||||
const emit = defineEmits(['confirm', 'abort'])
|
||||
defineEmits(['confirm'])
|
||||
|
||||
const onAbort = () => {
|
||||
emit('abort')
|
||||
}
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
|
||||
const onConfirm = () => {
|
||||
emit('confirm')
|
||||
}
|
||||
// "smAndDown" gilt für sm, xs usw.
|
||||
const isSmallScreen = breakpoints.smaller('sm')
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
||||
@ -1,116 +0,0 @@
|
||||
<template>
|
||||
<button
|
||||
v-if="$slots.trigger || label"
|
||||
v-bind="$attrs"
|
||||
type="button"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
:aria-label="label"
|
||||
@click="$emit('open')"
|
||||
>
|
||||
<slot name="trigger">
|
||||
{{ label }}
|
||||
</slot>
|
||||
</button>
|
||||
|
||||
<div class="hidden">
|
||||
<Teleport to="body">
|
||||
<div
|
||||
:id
|
||||
ref="modalRef"
|
||||
class="overlay modal overlay-open:opacity-100 overlay-open:duration-300 hidden modal-middle p-0 xs:p-2 --prevent-on-load-init pointer-events-auto max-w-none"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="overlay-animation-target overlay-open:duration-300 overlay-open:opacity-100 transition-all ease-out modal-dialog"
|
||||
>
|
||||
<div class="modal-content justify-between">
|
||||
<div class="modal-header py-0 sm:py-4">
|
||||
<div
|
||||
v-if="title || $slots.title"
|
||||
class="modal-title py-4 break-all"
|
||||
>
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
|
||||
:aria-label="t('close')"
|
||||
tabindex="1"
|
||||
@click="open = false"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:close"
|
||||
size="18"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body text-sm sm:text-base grow mt-0 pt-0">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="modal-footer flex-col sm:flex-row">
|
||||
<slot name="buttons" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HSOverlay } from 'flyonui/flyonui'
|
||||
const { currentTheme } = storeToRefs(useUiStore())
|
||||
|
||||
defineProps<{ title?: string; label?: string }>()
|
||||
|
||||
const emit = defineEmits(['open', 'close'])
|
||||
|
||||
const id = useId()
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const modalRef = useTemplateRef('modalRef')
|
||||
|
||||
defineExpose({ modalRef })
|
||||
|
||||
const modal = ref<HSOverlay>()
|
||||
|
||||
watch(open, async () => {
|
||||
if (!modal.value) return
|
||||
|
||||
if (open.value) {
|
||||
await modal.value.open()
|
||||
} else {
|
||||
await modal.value.close(true)
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
if (!modalRef.value) return
|
||||
|
||||
modal.value = new window.HSOverlay(modalRef.value)
|
||||
|
||||
modal.value.isLayoutAffect = true
|
||||
modal.value.on('close', () => {
|
||||
open.value = false
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
close: Schließen
|
||||
|
||||
en:
|
||||
close: Close
|
||||
</i18n>
|
||||
@ -1,55 +0,0 @@
|
||||
<template>
|
||||
<UiDialogConfirm
|
||||
:confirm-label="t('apply')"
|
||||
:title="t('title')"
|
||||
@abort="open = false"
|
||||
@click="open = true"
|
||||
class="btn btn-square btn-accent btn-outline"
|
||||
v-model:open="open"
|
||||
>
|
||||
<template #trigger>
|
||||
<Icon name="mdi:dice" />
|
||||
</template>
|
||||
|
||||
<form class="flex flex-col gap-4">
|
||||
<UiInputPassword
|
||||
v-model="newPassword"
|
||||
prepend-icon="mdi:key-outline"
|
||||
with-copy-button
|
||||
>
|
||||
<template #append>
|
||||
<UiButton class="btn-square btn-accent btn-outline">
|
||||
<Icon name="mdi:refresh" />
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiInputPassword>
|
||||
</form>
|
||||
</UiDialogConfirm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const open = defineModel<boolean>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { password } = defineProps<{
|
||||
autofocus?: boolean
|
||||
checkInput?: boolean
|
||||
label?: string
|
||||
placeholder?: string
|
||||
withCopyButton?: boolean
|
||||
password: string | null
|
||||
}>()
|
||||
|
||||
const newPassword = computed(() => password)
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
title: Passwortgenerator
|
||||
apply: Übernehmen
|
||||
|
||||
en:
|
||||
title: Passwordgenerator
|
||||
apply: Apply
|
||||
</i18n>
|
||||
@ -1,70 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
:class="offset"
|
||||
class="dropdown relative inline-flex"
|
||||
>
|
||||
<button
|
||||
:aria-label="label"
|
||||
:id
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
class="dropdown-toggle"
|
||||
type="button"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot name="activator">
|
||||
{{ label }}
|
||||
<span
|
||||
class="icon-[tabler--chevron-down] dropdown-open:rotate-180 size-4"
|
||||
/>
|
||||
</slot>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
:aria-labelledby="id"
|
||||
aria-orientation="vertical"
|
||||
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-28 z-20 shadow shadow-primary"
|
||||
role="menu"
|
||||
>
|
||||
<slot
|
||||
name="items"
|
||||
:items
|
||||
>
|
||||
<li
|
||||
:is="itemIs"
|
||||
@click="read_only ? '' : $emit('select', item)"
|
||||
class="dropdown-item"
|
||||
v-for="item in items"
|
||||
>
|
||||
<slot
|
||||
:item
|
||||
name="item"
|
||||
>
|
||||
{{ item }}
|
||||
</slot>
|
||||
</li>
|
||||
</slot>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T">
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const { itemIs = 'li', offset = '[--offset:0]' } = defineProps<{
|
||||
label?: string
|
||||
items?: T[]
|
||||
itemIs?: string
|
||||
activatorClass?: string
|
||||
offset?: string
|
||||
read_only?: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{ select: [T] }>()
|
||||
|
||||
const id = useId()
|
||||
|
||||
//const offset = '[--offset:30]'
|
||||
</script>
|
||||
@ -1,40 +1,38 @@
|
||||
<template>
|
||||
<UiDropdown
|
||||
:items="availableLocales"
|
||||
class="btn btn-primary btn-outline"
|
||||
@select="(locale) => $emit('select', locale)"
|
||||
<UDropdownMenu
|
||||
arrow
|
||||
:items
|
||||
:ui="{}"
|
||||
>
|
||||
<template #activator>
|
||||
<Icon :name="flags[locale]" />
|
||||
<Icon
|
||||
name="tabler:chevron-down"
|
||||
class="dropdown-open:rotate-180 size-4"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item="{ item }">
|
||||
<div class="flex gap-2 justify-center">
|
||||
<Icon
|
||||
:name="flags[item]"
|
||||
class="my-auto"
|
||||
/>
|
||||
<p>
|
||||
{{ item }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</UiDropdown>
|
||||
<UButton
|
||||
:icon="items.find((item) => item.label === locale)?.icon"
|
||||
:label="locale"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuItem } from '@nuxt/ui'
|
||||
import type { Locale } from 'vue-i18n'
|
||||
|
||||
const { locales, locale } = useI18n()
|
||||
|
||||
const flags = {
|
||||
de: 'emojione:flag-for-germany',
|
||||
en: 'emojione:flag-for-united-kingdom',
|
||||
de: 'circle-flags:de',
|
||||
en: 'circle-flags:uk',
|
||||
}
|
||||
|
||||
const { availableLocales, locale } = useI18n()
|
||||
const emit = defineEmits<{ select: [Locale] }>()
|
||||
|
||||
defineEmits<{ select: [Locale] }>()
|
||||
const items = computed<DropdownMenuItem[]>(() =>
|
||||
locales.value.map((locale) => ({
|
||||
label: locale.code,
|
||||
icon: flags[locale.code],
|
||||
onSelect() {
|
||||
emit('select', locale.code)
|
||||
},
|
||||
})),
|
||||
)
|
||||
</script>
|
||||
|
||||
@ -1,22 +1,24 @@
|
||||
<template>
|
||||
<UiDropdown :items="availableThemes" class="btn btn-primary btn-outline" @select="(theme) => $emit('select', theme)">
|
||||
<template #activator>
|
||||
<Icon :name="currentTheme.icon" />
|
||||
</template>
|
||||
|
||||
<template #item="{ item }">
|
||||
<div class="flex gap-2 justify-center">
|
||||
<Icon :name="item.icon" class="my-auto" />
|
||||
<p>
|
||||
{{ item.name }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</UiDropdown>
|
||||
<UDropdownMenu :items>
|
||||
<UButton :icon="currentTheme?.icon" />
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuItem } from '@nuxt/ui'
|
||||
|
||||
const { availableThemes, currentTheme } = storeToRefs(useUiStore())
|
||||
|
||||
defineEmits<{ select: [ITheme] }>()
|
||||
</script>
|
||||
const emit = defineEmits<{ select: [string] }>()
|
||||
|
||||
watchImmediate(availableThemes, () =>
|
||||
console.log('availableThemes', availableThemes),
|
||||
)
|
||||
|
||||
const items = computed<DropdownMenuItem[]>(() =>
|
||||
availableThemes?.value.map((theme) => ({
|
||||
...theme,
|
||||
onSelect: () => emit('select', theme.value),
|
||||
})),
|
||||
)
|
||||
</script>
|
||||
|
||||
45
src/components/ui/dropdown/vault.vue
Normal file
45
src/components/ui/dropdown/vault.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<UDropdownMenu :items>
|
||||
<UButton
|
||||
icon="mdi:menu"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuItem } from '@nuxt/ui'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { closeAsync } = useVaultStore()
|
||||
|
||||
const onVaultCloseAsync = async () => {
|
||||
await closeAsync()
|
||||
await navigateTo(useLocalePath()({ name: 'vaultOpen' }))
|
||||
}
|
||||
|
||||
const items: DropdownMenuItem[] = [
|
||||
{
|
||||
icon: 'tabler:settings',
|
||||
label: t('settings'),
|
||||
to: useLocalePath()({ name: 'settings' }),
|
||||
},
|
||||
{
|
||||
icon: 'tabler:logout',
|
||||
label: t('close'),
|
||||
onSelect: () => onVaultCloseAsync(),
|
||||
color: 'error',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
settings: 'Einstellungen'
|
||||
close: 'Vault schließen'
|
||||
|
||||
en:
|
||||
settings: 'Settings'
|
||||
close: 'Close Vault'
|
||||
</i18n>
|
||||
@ -1,181 +1,88 @@
|
||||
<template>
|
||||
<div>
|
||||
<fieldset
|
||||
class="join w-full"
|
||||
:class="{ 'pt-1.5': label }"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot name="prepend" />
|
||||
|
||||
<div class="input join-item">
|
||||
<Icon
|
||||
v-if="prependIcon"
|
||||
:name="prependIcon"
|
||||
class="my-auto shrink-0"
|
||||
/>
|
||||
|
||||
<div class="input-floating grow">
|
||||
<input
|
||||
:autofocus
|
||||
:id
|
||||
:name="name ?? id"
|
||||
:placeholder="placeholder || label"
|
||||
:readonly="read_only"
|
||||
:type
|
||||
class="ps-2"
|
||||
ref="inputRef"
|
||||
v-model="input"
|
||||
@keyup="(e:KeyboardEvent) => $emit('keyup', e)"
|
||||
/>
|
||||
<label
|
||||
:for="id"
|
||||
class="input-floating-label"
|
||||
>
|
||||
{{ label }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Icon
|
||||
v-if="appendIcon"
|
||||
:name="appendIcon"
|
||||
class="my-auto shrink-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<UiButton
|
||||
v-if="withClearButton"
|
||||
class="btn-outline btn-square"
|
||||
@click="input = ''"
|
||||
>
|
||||
<Icon name="mdi:close" />
|
||||
</UiButton>
|
||||
|
||||
<slot name="append" />
|
||||
|
||||
<UiButton
|
||||
v-if="withCopyButton"
|
||||
:tooltip="t('copy')"
|
||||
class="btn-outline btn-accent btn-square"
|
||||
@click="copy(`${input}`)"
|
||||
>
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
</UiButton>
|
||||
</fieldset>
|
||||
|
||||
<span
|
||||
v-show="errors"
|
||||
class="flex flex-col px-2 pt-0.5"
|
||||
<UInput
|
||||
v-model="value"
|
||||
:placeholder="props.placeholder || ' '"
|
||||
:readonly="props.readOnly"
|
||||
:leading-icon="props.leadingIcon"
|
||||
:ui="{ base: 'peer' }"
|
||||
@change="(e) => $emit('change', e)"
|
||||
@blur="(e) => $emit('blur', e)"
|
||||
@keyup="(e: KeyboardEvent) => $emit('keyup', e)"
|
||||
@keydown="(e: KeyboardEvent) => $emit('keydown', e)"
|
||||
>
|
||||
<label
|
||||
class="absolute pointer-events-none -top-2.5 left-0 text-highlighted text-xs font-medium px-1.5 transition-all peer-focus:-top-2.5 peer-focus:text-highlighted peer-focus:text-xs peer-focus:font-medium peer-placeholder-shown:text-sm peer-placeholder-shown:text-dimmed peer-placeholder-shown:top-1.5 peer-placeholder-shown:font-normal"
|
||||
>
|
||||
<span
|
||||
v-for="error in errors"
|
||||
class="label-text-alt text-error"
|
||||
class="inline-flex bg-default px-1"
|
||||
:class="props?.leadingIcon ? 'mx-6' : 'mx-0'"
|
||||
>
|
||||
{{ error }}
|
||||
{{ props?.label }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<template #trailing>
|
||||
<slot name="trailing" />
|
||||
|
||||
<UiButton
|
||||
v-show="props.withCopyButton"
|
||||
:color="copied ? 'success' : 'neutral'"
|
||||
:tooltip="t('copy')"
|
||||
:icon="copied ? 'mdi:check' : 'mdi:content-copy'"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click="copy(`${value}`)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-for="(_, slotName) in filteredSlots"
|
||||
#[slotName]="slotProps"
|
||||
>
|
||||
<slot
|
||||
:name="slotName"
|
||||
v-bind="slotProps"
|
||||
/>
|
||||
</template>
|
||||
</UInput>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ZodSchema } from 'zod'
|
||||
import type { AcceptableValue, InputProps } from '@nuxt/ui'
|
||||
|
||||
const input = defineModel<string | number | undefined | null>({
|
||||
required: true,
|
||||
})
|
||||
const value = defineModel<AcceptableValue | undefined>()
|
||||
|
||||
const inputRef = useTemplateRef('inputRef')
|
||||
defineExpose({ inputRef })
|
||||
|
||||
const emit = defineEmits<{
|
||||
error: [string[]]
|
||||
keyup: [KeyboardEvent]
|
||||
}>()
|
||||
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<
|
||||
| 'button'
|
||||
| 'checkbox'
|
||||
| 'color'
|
||||
| 'date'
|
||||
| 'datetime-local'
|
||||
| 'email'
|
||||
| 'file'
|
||||
| 'hidden'
|
||||
| 'image'
|
||||
| 'month'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'radio'
|
||||
| 'range'
|
||||
| 'reset'
|
||||
| 'search'
|
||||
| 'submit'
|
||||
| 'tel'
|
||||
| 'text'
|
||||
| 'time'
|
||||
| 'url'
|
||||
| 'week'
|
||||
>,
|
||||
default: 'text',
|
||||
},
|
||||
label: String,
|
||||
name: String,
|
||||
prependIcon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
prependLabel: String,
|
||||
appendIcon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
appendLabel: String,
|
||||
rules: Object as PropType<ZodSchema>,
|
||||
checkInput: Boolean,
|
||||
withCopyButton: Boolean,
|
||||
withClearButton: Boolean,
|
||||
autofocus: Boolean,
|
||||
read_only: Boolean,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autofocus && inputRef.value) inputRef.value.focus()
|
||||
})
|
||||
|
||||
const errors = defineModel<string[] | undefined>('errors')
|
||||
|
||||
const id = useId()
|
||||
|
||||
watch(input, () => checkInput())
|
||||
|
||||
watch(
|
||||
() => props.checkInput,
|
||||
() => {
|
||||
checkInput()
|
||||
},
|
||||
)
|
||||
|
||||
const checkInput = () => {
|
||||
if (props.rules) {
|
||||
const result = props.rules.safeParse(input.value)
|
||||
//console.log('check result', result.error, props.rules);
|
||||
if (!result.success) {
|
||||
errors.value = result.error.errors.map((error) => error.message)
|
||||
emit('error', errors.value)
|
||||
} else {
|
||||
errors.value = []
|
||||
}
|
||||
}
|
||||
interface IInputProps extends /* @vue-ignore */ InputProps {
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
const props = defineProps<
|
||||
IInputProps & {
|
||||
withCopyButton?: boolean
|
||||
readOnly?: boolean
|
||||
label?: string
|
||||
leadingIcon?: string
|
||||
}
|
||||
>()
|
||||
|
||||
defineEmits<{
|
||||
change: [Event]
|
||||
blur: [Event]
|
||||
keyup: [KeyboardEvent]
|
||||
keydown: [KeyboardEvent]
|
||||
}>()
|
||||
|
||||
const { copy, copied } = useClipboard()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const filteredSlots = computed(() => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(useSlots()).filter(([name]) => name !== 'trailing'),
|
||||
)
|
||||
})
|
||||
|
||||
watchImmediate(props, () => console.log('props', props))
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
||||
@ -1,62 +1,53 @@
|
||||
<template>
|
||||
<UiInput
|
||||
v-model="value"
|
||||
:autofocus
|
||||
:check-input
|
||||
:label="label || t('password')"
|
||||
:placeholder="placeholder || t('password')"
|
||||
:rules
|
||||
:type="type"
|
||||
:label="t('label')"
|
||||
:leading-icon
|
||||
:placeholder="placeholder || ' '"
|
||||
:read-only
|
||||
:type="show ? 'text' : 'password'"
|
||||
:with-copy-button
|
||||
@keyup="(e) => $emit('keyup', e)"
|
||||
>
|
||||
<template #append>
|
||||
<slot name="append" />
|
||||
|
||||
<template #trailing>
|
||||
<UiButton
|
||||
class="btn-outline btn-accent btn-square join-item"
|
||||
@click="tooglePasswordType"
|
||||
>
|
||||
<Icon :name="type === 'password' ? 'mdi:eye-off' : 'mdi:eye'" />
|
||||
</UiButton>
|
||||
aria-controls="password"
|
||||
color="neutral"
|
||||
variant="link"
|
||||
:aria-label="show ? t('hide') : t('show')"
|
||||
:aria-pressed="show"
|
||||
:icon="show ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
||||
:tooltip="show ? t('hide') : t('show')"
|
||||
size="sm"
|
||||
@click="show = !show"
|
||||
/>
|
||||
</template>
|
||||
</UiInput>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ZodSchema } from 'zod'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const value = defineModel<string | number | null | undefined>()
|
||||
import type { AcceptableValue } from '@nuxt/ui'
|
||||
|
||||
defineProps<{
|
||||
autofocus?: boolean
|
||||
checkInput?: boolean
|
||||
label?: string
|
||||
placeholder?: string
|
||||
rules?: ZodSchema
|
||||
leadingIcon?: string
|
||||
withCopyButton?: boolean
|
||||
readOnly?: boolean
|
||||
}>()
|
||||
const value = defineModel<AcceptableValue | undefined>()
|
||||
|
||||
defineEmits<{
|
||||
keyup: [KeyboardEvent]
|
||||
}>()
|
||||
|
||||
const type = ref<'password' | 'text'>('password')
|
||||
|
||||
const tooglePasswordType = () => {
|
||||
type.value = type.value === 'password' ? 'text' : 'password'
|
||||
}
|
||||
const show = ref(false)
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"password": "Passwort"
|
||||
},
|
||||
"en": {
|
||||
"password": "Password"
|
||||
}
|
||||
}
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
show: Passwort ansehen
|
||||
hide: Passwort verstecken
|
||||
label: Passwort
|
||||
|
||||
en:
|
||||
show: Show password
|
||||
hide: Hide password
|
||||
label: Password
|
||||
</i18n>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<UiInput
|
||||
v-model.trim="value"
|
||||
:autofocus
|
||||
:check-input="checkInput"
|
||||
:label="label || t('url')"
|
||||
@ -7,17 +8,18 @@
|
||||
:read_only
|
||||
:rules
|
||||
:with-copy-button
|
||||
v-model.trim="value"
|
||||
@keyup="(e) => $emit('keyup', e)"
|
||||
>
|
||||
<template #append>
|
||||
<template #trailing>
|
||||
<UiButton
|
||||
color="neutral"
|
||||
variant="link"
|
||||
size="sm"
|
||||
icon="streamline:web"
|
||||
:disabled="!value?.length"
|
||||
:tooltip="t('browse')"
|
||||
@click="openUrl(`${value}`)"
|
||||
class="btn-outline btn-accent btn-square"
|
||||
>
|
||||
<Icon name="streamline:web" />
|
||||
</UiButton>
|
||||
/>
|
||||
</template>
|
||||
</UiInput>
|
||||
</template>
|
||||
@ -45,13 +47,12 @@ defineEmits<{
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"url": "Url"
|
||||
},
|
||||
"en": {
|
||||
"url": "Url"
|
||||
}
|
||||
}
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
url: Url
|
||||
browse: Url öffnen
|
||||
|
||||
en:
|
||||
url: Url
|
||||
browse: Open url
|
||||
</i18n>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col h-fit border border-base-content/25 divide-base-content/25 divide-y rounded-md first:rounded-t-md last:rounded-b-md"
|
||||
class="flex flex-col h-fit border border-default/25 divide-default/25 divide-y rounded-md first:rounded-t-md last:rounded-b-md"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
<svg
|
||||
viewBox="122 107 263 292"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style="max-height: 500px"
|
||||
>
|
||||
<g
|
||||
stroke-width="0.3"
|
||||
|
||||
@ -10,24 +10,23 @@
|
||||
|
||||
<input
|
||||
:id
|
||||
:readonly="read_only"
|
||||
:disabled="read_only"
|
||||
ref="colorRef"
|
||||
v-model="model"
|
||||
:readonly="readOnly"
|
||||
:disabled="readOnly"
|
||||
:title="t('pick')"
|
||||
class="top-0 left-0 absolute size-0"
|
||||
type="color"
|
||||
v-model="model"
|
||||
ref="colorRef"
|
||||
/>
|
||||
|
||||
<UiTooltip :tooltip="t('reset')">
|
||||
<button
|
||||
<UiButton
|
||||
color="error"
|
||||
:class="{ 'btn-disabled': readOnly }"
|
||||
icon="mdi:refresh"
|
||||
:disabled="readOnly"
|
||||
@click="model = ''"
|
||||
class="btn btn-sm text-sm btn-outline btn-error"
|
||||
:class="{ 'btn-disabled': read_only }"
|
||||
type="button"
|
||||
>
|
||||
<Icon name="mdi:refresh" />
|
||||
</button>
|
||||
/>
|
||||
</UiTooltip>
|
||||
</div>
|
||||
</template>
|
||||
@ -39,12 +38,12 @@ const { t } = useI18n()
|
||||
const model = defineModel<string | null>()
|
||||
const colorRef = useTemplateRef('colorRef')
|
||||
defineProps({
|
||||
read_only: Boolean,
|
||||
readOnly: Boolean,
|
||||
})
|
||||
|
||||
const { currentTheme } = storeToRefs(useUiStore())
|
||||
const textColorClass = computed(() => {
|
||||
if (!model.value)
|
||||
if (!model.value && currentTheme.value)
|
||||
return currentTheme.value.value === 'dark' ? 'text-black' : 'text-white'
|
||||
|
||||
const color = getContrastingTextColor(model.value)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<UiDropdown
|
||||
<UDropdownMenu
|
||||
:items="icons"
|
||||
class="btn"
|
||||
@select="(newIcon) => (iconName = newIcon)"
|
||||
@ -23,7 +23,7 @@
|
||||
</li>
|
||||
</div>
|
||||
</template>
|
||||
</UiDropdown>
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
60
src/components/ui/sidebar/link.vue
Normal file
60
src/components/ui/sidebar/link.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<li
|
||||
class="rounded hover:bg-elevated py-2 cursor-pointer"
|
||||
:class="{
|
||||
['bg-base-content/20 ']: isActive,
|
||||
}"
|
||||
@click="triggerNavigate"
|
||||
>
|
||||
<UTooltip :tooltip="tooltip ?? name">
|
||||
<NuxtLinkLocale
|
||||
ref="linkRef"
|
||||
:to
|
||||
class="flex items-center justify-center cursor-pointer tooltip-toogle"
|
||||
>
|
||||
<div
|
||||
v-if="iconType === 'svg'"
|
||||
class="shrink-0 size-5"
|
||||
v-html="icon"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
:name="icon"
|
||||
size="1.5em"
|
||||
/>
|
||||
</NuxtLinkLocale>
|
||||
</UTooltip>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ISidebarItem } from '#imports'
|
||||
|
||||
const props = defineProps<ISidebarItem>()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
console.log('to', props.to)
|
||||
const isActive = computed(() => {
|
||||
if (props.to?.name === 'haexExtension') {
|
||||
return (
|
||||
getSingleRouteParam(router.currentRoute.value.params.extensionId) ===
|
||||
props.id
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
props.to?.name === router.currentRoute.value.meta.name ||
|
||||
router
|
||||
.getRoutes()
|
||||
.find((route) => route.meta.name === props.to?.name)
|
||||
?.children.some(
|
||||
(route) => route.meta?.name === router.currentRoute.value.meta.name,
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const linkRef = useTemplateRef('linkRef')
|
||||
|
||||
const triggerNavigate = () => linkRef.value?.$el.click()
|
||||
</script>
|
||||
@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<input v-model="value" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const value = defineModel()
|
||||
</script>
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<p
|
||||
class="bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent font-black"
|
||||
class="bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent font-black"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
|
||||
@ -1,41 +1,54 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<UiButton
|
||||
v-if="withCopyButton"
|
||||
:tooltip="t('copy')"
|
||||
class="btn-square btn-outline btn-accent absolute z-10 top-2 right-2"
|
||||
@click="copy(`${value}`)"
|
||||
<div>
|
||||
<UTextarea
|
||||
:id
|
||||
v-model="value"
|
||||
:ui="{ base: 'peer' }"
|
||||
:readonly="readOnly"
|
||||
class="w-full"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
</UiButton>
|
||||
|
||||
<div class="textarea-floating">
|
||||
<textarea
|
||||
:class="{ 'pr-10': withCopyButton }"
|
||||
:id
|
||||
:placeholder
|
||||
:readonly="read_only"
|
||||
class="textarea"
|
||||
v-bind="$attrs"
|
||||
v-model="value"
|
||||
></textarea>
|
||||
<label
|
||||
class="textarea-floating-label"
|
||||
:for="id"
|
||||
class="absolute pointer-events-none -top-2.5 left-0 text-highlighted text-xs font-medium px-1.5 transition-all peer-focus:-top-2.5 peer-focus:text-highlighted peer-focus:text-xs peer-focus:font-medium peer-placeholder-shown:text-sm peer-placeholder-shown:text-dimmed peer-placeholder-shown:top-1.5 peer-placeholder-shown:font-normal"
|
||||
>
|
||||
{{ label }}
|
||||
<span class="inline-flex bg-default px-1">
|
||||
{{ props.label }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<template #trailing>
|
||||
<UiButton
|
||||
v-show="withCopyButton"
|
||||
:color="copied ? 'success' : 'neutral'"
|
||||
:tooltip="t('copy')"
|
||||
:icon="copied ? 'mdi:check' : 'mdi:content-copy'"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click="copy(`${value}`)"
|
||||
/>
|
||||
</template>
|
||||
</UTextarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
import type { TextareaProps } from '@nuxt/ui'
|
||||
|
||||
interface ITextareaProps extends /* @vue-ignore */ TextareaProps {
|
||||
tooltip?: string
|
||||
withCopyButton?: boolean
|
||||
readOnly?: boolean
|
||||
label?: string
|
||||
}
|
||||
|
||||
const props = defineProps<ITextareaProps>()
|
||||
|
||||
/* defineProps<{
|
||||
placeholder?: string
|
||||
label?: string
|
||||
read_only?: boolean
|
||||
readOnly?: boolean
|
||||
withCopyButton?: boolean
|
||||
}>()
|
||||
}>() */
|
||||
|
||||
const id = useId()
|
||||
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<div class="tooltip [--prevent-popper:false]">
|
||||
<div
|
||||
class="tooltip-toggle"
|
||||
:aria-label="tooltip"
|
||||
>
|
||||
<slot>
|
||||
<button class="btn btn-square">
|
||||
<Icon name="mdi:chevron-up-box-outline" />
|
||||
</button>
|
||||
</slot>
|
||||
|
||||
<span
|
||||
class="tooltip-content tooltip-shown:opacity-100 tooltip-shown:visible pointer-events-none z-50"
|
||||
role="tooltip"
|
||||
>
|
||||
<span class="tooltip-body">
|
||||
{{ tooltip }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
direction: {
|
||||
type: String as PropType<
|
||||
| 'top'
|
||||
| 'top-start'
|
||||
| 'top-end'
|
||||
| 'bottom'
|
||||
| 'bottom-start'
|
||||
| 'bottom-end'
|
||||
| 'right'
|
||||
| 'right-start'
|
||||
| 'right-end'
|
||||
| 'left'
|
||||
| 'left-start'
|
||||
| 'left-end'
|
||||
>,
|
||||
default: 'top',
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
trigger: {
|
||||
type: String as PropType<'focus' | 'hover' | 'click'>,
|
||||
default: 'hover',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="tree-view-selected:bg-base-200/60 dragged:bg-primary/20 dragged:rounded nested-4 cursor-pointer rounded-md px-2"
|
||||
role="treeitem" :data-tree-view-item="JSON.stringify({
|
||||
value,
|
||||
isDir: false,
|
||||
})
|
||||
">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<span class="icon-[tabler--file] text-base-content size-4 flex-shrink-0"/>
|
||||
<div class="grow">
|
||||
<span class="text-base-content">{{ value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
value: String,
|
||||
});
|
||||
|
||||
const id = useId();
|
||||
const controlId = useId();
|
||||
const isActive = ref(false);
|
||||
</script>
|
||||
@ -1,204 +0,0 @@
|
||||
<template>
|
||||
<div :id ref="folderRef" data-nested-draggable="" :value class="">
|
||||
<div
|
||||
isDir :data-tree-view-item="JSON.stringify({ value })"
|
||||
class="accordion-item active motion-preset-slide-left motion-ease-spring-bouncier" :class="{
|
||||
'selected': isActive?.value,
|
||||
'text-base-content': !color,
|
||||
}" role="treeitem" :style="{ color: color || '' }">
|
||||
<div
|
||||
class="accordion-heading tree-view-selected:bg-primary/80 flex items-center gap-x-0.5 rounded-md hover:bg-primary/20 group">
|
||||
<button class="accordion-toggle btn btn-sm btn-circle btn-text shrink-0" :aria-controls="controlId">
|
||||
<Icon name="tabler:plus" class="accordion-item-active:rotate-45 size-4 transition-all duration-300" />
|
||||
</button>
|
||||
<button class="cursor-pointer rounded-md px-1.5 w-full" @click.stop="$emit('click', value)">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<Icon v-if="icon" :name="icon || 'mdi:folder-outline'" class="shrink-0" />
|
||||
|
||||
<div class="flex whitespace-nowrap">
|
||||
{{ value }}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
class="sticky right-2 btn btn-sm btn-circle btn-text shrink-0 group-hover:flex hidden ml-auto"
|
||||
@click.stop="$emit('edit', value)">
|
||||
<Icon name="mdi:pencil-outline" class="size-4 transition-all duration-300" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
:id="controlId" class="accordion-content w-full transition-[height] duration-300" role="group"
|
||||
:aria-labelledby="id">
|
||||
<div ref="childRef" class="tree-view-space min-h-1" data-nested-draggable="">
|
||||
<slot>
|
||||
<template
|
||||
v-for="(item, index) in children?.sort(
|
||||
(a, b) => a.order ?? 0 - (b.order ?? 0)
|
||||
)" :key="item.id!" :data-tree-view-item="JSON.stringify({ value: item.value })">
|
||||
<UiTreeFolder
|
||||
v-if="item.type === 'folder'" :icon="item.icon || 'tabler:folder'"
|
||||
v-bind="item" @click="(value) => $emit('click', value)" @edit="(value) => $emit('edit', value)" />
|
||||
|
||||
<UiTreeFile v-if="item.type === 'file'" v-bind="item" />
|
||||
</template>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HSAccordion } from 'flyonui/flyonui';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
const props = defineProps({
|
||||
value: String,
|
||||
icon: {
|
||||
type: [String, null],
|
||||
default: 'tabler:folder',
|
||||
},
|
||||
children: {
|
||||
type: Array as PropType<ITreeItem[] | null>,
|
||||
default: () => [],
|
||||
},
|
||||
name: String,
|
||||
color: [String, null],
|
||||
isActive: Object as PropType<ComputedRef<boolean>>,
|
||||
});
|
||||
|
||||
const id = useId();
|
||||
const controlId = useId();
|
||||
const folderRef = ref<HTMLElement>();
|
||||
const childRef = ref<HTMLElement>();
|
||||
|
||||
defineEmits<{
|
||||
click: [value: string | undefined];
|
||||
edit: [value: string | undefined];
|
||||
}>();
|
||||
|
||||
const { groups } = storeToRefs(useVaultGroupStore());
|
||||
const sorty = ref([]);
|
||||
onMounted(() => {
|
||||
if (folderRef.value && childRef.value)
|
||||
[folderRef.value, childRef.value].forEach((element) => {
|
||||
const create = Sortable.create(element, {
|
||||
animation: 150,
|
||||
ghostClass: 'bg-opacity-20',
|
||||
group: 'vault',
|
||||
swapThreshold: 0.65,
|
||||
fallbackOnBody: true,
|
||||
fallbackTolerance: 3,
|
||||
|
||||
onEnd: (evt) => {
|
||||
const { item } = evt;
|
||||
|
||||
/* if (item.classList.contains('accordion')) {
|
||||
let existingInstance = HSAccordion.getInstance(item, true);
|
||||
let updatedInstance;
|
||||
|
||||
existingInstance.element.update();
|
||||
updatedInstance = HSAccordion.getInstance(item, true);
|
||||
window.$hsAccordionCollection.map((el) => {
|
||||
if (
|
||||
el.element.el !== existingInstance.element.el &&
|
||||
el.element.group === existingInstance.element.group &&
|
||||
el.element.el.closest('.accordion') &&
|
||||
el.element.el.classList.contains('active') &&
|
||||
existingInstance.element.el.classList.contains('active')
|
||||
)
|
||||
el.element.hide();
|
||||
|
||||
return el;
|
||||
});
|
||||
}
|
||||
|
||||
if (!!item.hasAttribute('data-tree-view-item')) {
|
||||
const treeViewItem = HSTreeView.getInstance(
|
||||
item.closest('[data-tree-view]'),
|
||||
true
|
||||
);
|
||||
|
||||
treeViewItem.element.update();
|
||||
} */
|
||||
},
|
||||
onUpdate: (evt) => {
|
||||
console.log('update', evt.item, props.value, sorty.value);
|
||||
},
|
||||
});
|
||||
/* const sortable = new Sortable(element, {
|
||||
animation: 150,
|
||||
ghostClass: 'bg-opacity-20',
|
||||
group: 'vault',
|
||||
swapThreshold: 0.65,
|
||||
fallbackOnBody: true,
|
||||
fallbackTolerance: 3,
|
||||
|
||||
onEnd: (evt) => {
|
||||
console.log(
|
||||
'end',
|
||||
evt.item,
|
||||
props.value,
|
||||
sorty.value.at(0).toArray(),
|
||||
sorty.value.at(1).toArray()
|
||||
);
|
||||
},
|
||||
onUpdate: (evt) => {
|
||||
console.log('update', evt.item, props.value, sorty.value);
|
||||
},
|
||||
});
|
||||
sorty.value.push(sortable); */
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const draggable = document.querySelectorAll('[data-nested-draggable]');
|
||||
|
||||
draggable.forEach((el) => {
|
||||
const options = {
|
||||
group: 'nested',
|
||||
animation: 150,
|
||||
fallbackOnBody: true,
|
||||
swapThreshold: 0.65,
|
||||
ghostClass: 'dragged',
|
||||
onEnd: (evt) => {
|
||||
const { item, items } = evt;
|
||||
console.log('standard', item, evt);
|
||||
if (item.classList.contains('accordion')) {
|
||||
const existingInstance = HSAccordion.getInstance(item, true);
|
||||
let updatedInstance;
|
||||
|
||||
existingInstance.element.update();
|
||||
updatedInstance = HSAccordion.getInstance(item, true);
|
||||
window.$hsAccordionCollection.map((el) => {
|
||||
if (
|
||||
el.element.el !== existingInstance.element.el &&
|
||||
el.element.group === existingInstance.element.group &&
|
||||
el.element.el.closest('.accordion') &&
|
||||
el.element.el.classList.contains('active') &&
|
||||
existingInstance.element.el.classList.contains('active')
|
||||
)
|
||||
el.element.hide();
|
||||
|
||||
return el;
|
||||
});
|
||||
}
|
||||
|
||||
if (item.hasAttribute('data-tree-view-item')) {
|
||||
const treeViewItem = HSTreeView.getInstance(
|
||||
item.closest('[data-tree-view]'),
|
||||
true
|
||||
);
|
||||
|
||||
treeViewItem.element.update();
|
||||
}
|
||||
},
|
||||
};
|
||||
const data = el.getAttribute('data-nested-draggable');
|
||||
const dataOptions = data ? JSON.parse(data) : {};
|
||||
const sortable = new Sortable(el, options);
|
||||
console.log('stand', sortable.toArray());
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<div data-tree-view role="tree" aria-orientation="vertical" class="rounded min-w-fit w-full">
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
19
src/components/ui/tree/types.d.ts
vendored
19
src/components/ui/tree/types.d.ts
vendored
@ -1,19 +0,0 @@
|
||||
interface ITreeItem {
|
||||
id: string | null
|
||||
value: string
|
||||
name: string | null
|
||||
icon?: string | null
|
||||
children?: ITreeItem[] | null
|
||||
type: 'folder' | 'file'
|
||||
color?: string | null
|
||||
order?: number | null
|
||||
parentId?: string | null
|
||||
}
|
||||
|
||||
interface IVaultGroupTreeItem {
|
||||
id?: string | null
|
||||
name: string
|
||||
icon?: string | null
|
||||
children?: IVaultGroupTreeItem[] | null
|
||||
desciption?: string | null
|
||||
}
|
||||
Reference in New Issue
Block a user