@plutocms/pluto 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,161 @@
1
+ <script setup lang="ts">
2
+ import type { ClassNameValue } from 'tailwind-merge'
3
+ import { useBodyScrollLock } from 'reka-ui'
4
+ import { twMerge } from 'tailwind-merge'
5
+
6
+ interface Props {
7
+ size?: 'small' | 'medium' | 'large' | 'default'
8
+ customSize?: number
9
+ preventClickOutside?: boolean
10
+ class?: ClassNameValue
11
+ scrollAreaClass?: ClassNameValue
12
+ backgroundClass?: ClassNameValue
13
+ }
14
+
15
+ defineOptions({
16
+ inheritAttrs: false,
17
+ })
18
+
19
+ const props = withDefaults(defineProps<Props>(), {
20
+ isOpen: false,
21
+ preventClickOutside: false,
22
+ })
23
+
24
+ const emit = defineEmits(['update:isOpen', 'scroll'])
25
+
26
+ const scrollLock = useBodyScrollLock()
27
+
28
+ const scrollRef = useTemplateRef('scrollRef')
29
+
30
+ const { isScrolling, y } = useScroll(scrollRef)
31
+
32
+ const model = defineModel<boolean>()
33
+
34
+ const resolvedClasses = computed(() => {
35
+ const defaultClasses = `
36
+ group
37
+ relative
38
+ w-234 h-fit
39
+ rounded-lg
40
+ my-4 mx-4
41
+ shadow-2xl
42
+ bg-default
43
+ light:text-zinc-800 dark:text-white
44
+ `
45
+
46
+ return twMerge([
47
+ defaultClasses,
48
+
49
+ !props.customSize && props.size && props.size === 'small'
50
+ ? 'w-[320px]'
51
+ : null,
52
+
53
+ !props.customSize && props.size && props.size === 'medium' ? 'w-3xl' : null,
54
+
55
+ !props.customSize && props.size && props.size === 'large' ? 'w-7xl' : null,
56
+
57
+ !props.customSize && props.size && props.size === 'default'
58
+ ? 'w-234'
59
+ : null,
60
+
61
+ props.class,
62
+ ])
63
+ })
64
+
65
+ const resolvedOverlayClasses = computed(() => {
66
+ const defaultClasses = `
67
+ pointer-events-auto
68
+ fixed
69
+ left-0 top-0
70
+ z-51
71
+ h-screen w-screen
72
+ bg-black/90
73
+ `
74
+
75
+ return twMerge([defaultClasses])
76
+ })
77
+
78
+ watchEffect(() => {
79
+ if (model.value === true) {
80
+ scrollLock.value = true
81
+
82
+ nextTick(() => {
83
+ document.body.style.pointerEvents = ''
84
+ })
85
+
86
+ return
87
+ }
88
+
89
+ scrollLock.value = false
90
+ })
91
+
92
+ watch(y, (y) => {
93
+ if (isScrolling.value) {
94
+ emit('scroll', y)
95
+ }
96
+ })
97
+
98
+ function closeModal() {
99
+ model.value = false
100
+ }
101
+ </script>
102
+
103
+ <template>
104
+ <div>
105
+ <Teleport to="body">
106
+ <Transition name="fade">
107
+ <div
108
+ v-if="model === true"
109
+ :class="resolvedOverlayClasses"
110
+ @click="props.preventClickOutside === false ? closeModal() : null"
111
+ >
112
+ <div ref="scrollRef" class="h-full overflow-y-scroll py-4">
113
+ <div class="flex min-h-full items-center justify-center">
114
+ <div
115
+ :class="twMerge(['modal-content my-auto', resolvedClasses])"
116
+ :style="{
117
+ width:
118
+ !props.size && props.customSize
119
+ ? `${props.customSize}px`
120
+ : '',
121
+ }"
122
+ v-bind="$attrs"
123
+ @click.stop
124
+ >
125
+ <slot />
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </div>
130
+ </Transition>
131
+ </Teleport>
132
+ </div>
133
+ </template>
134
+
135
+ <style scoped>
136
+ .fade-enter-active {
137
+ transition:
138
+ transform 1000ms cubic-bezier(0.08, 0.915, 0.005, 0.985),
139
+ opacity 250ms cubic-bezier(0.295, 0.485, 0, 0.975);
140
+ }
141
+
142
+ .fade-leave-active {
143
+ transition: 300ms cubic-bezier(0.295, 0.485, 0, 0.975);
144
+ }
145
+
146
+ .fade-enter-from,
147
+ .fade-leave-to {
148
+ opacity: 0;
149
+ }
150
+
151
+ .fade-enter-active .modal-content,
152
+ .fade-leave-active .modal-content {
153
+ transition: all 700ms cubic-bezier(0.08, 0.915, 0.005, 0.985);
154
+ }
155
+
156
+ .fade-enter-from .modal-content,
157
+ .fade-leave-to .modal-content {
158
+ opacity: 0;
159
+ transform: translateY(40px);
160
+ }
161
+ </style>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ import { twMerge } from 'tailwind-merge'
3
+ </script>
4
+
5
+ <template>
6
+ <div class="flex flex-col gap-8 py-1">
7
+ <div
8
+ v-if="$slots.header"
9
+ :class="twMerge(['bg-opacity-90 sticky z-50 backdrop-blur-md'])"
10
+ style="top: var(--header-top-offset)"
11
+ >
12
+ <slot name="header" />
13
+ </div>
14
+
15
+ <div class="px-4 pt-6">
16
+ <slot />
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <style scoped>
22
+ * {
23
+ --header-padding: 1rem;
24
+ --header-height: 4.625rem;
25
+ --header-top-offset: calc(var(--header-height) - var(--header-padding));
26
+ }
27
+ </style>
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import type { ClassNameValue } from 'tailwind-merge'
3
+ import { twMerge } from 'tailwind-merge'
4
+
5
+ interface Props {
6
+ align?: 'left' | 'center' | 'right'
7
+ hideDivider?: boolean
8
+ class?: ClassNameValue
9
+ }
10
+
11
+ const props = withDefaults(defineProps<Props>(), {
12
+ align: 'center',
13
+ hideDivider: false,
14
+ class: '',
15
+ })
16
+
17
+ const resolvedAlign = computed(() => {
18
+ if (props.align === 'left') {
19
+ return 'justify-start'
20
+ }
21
+ if (props.align === 'center') {
22
+ return 'justify-center'
23
+ }
24
+ if (props.align === 'right') {
25
+ return 'justify-end'
26
+ }
27
+
28
+ return 'justify-center'
29
+ })
30
+
31
+ const resolvedClasses = computed(() => {
32
+ const defaultClasses = `
33
+ sticky -bottom-4 z-51
34
+ w-full
35
+ mt-8
36
+ `
37
+
38
+ return twMerge([defaultClasses, props.class])
39
+ })
40
+ </script>
41
+
42
+ <template>
43
+ <div :class="resolvedClasses">
44
+ <USeparator v-if="!props.hideDivider" />
45
+
46
+ <div
47
+ :class="
48
+ twMerge([
49
+ 'bg-opacity-90 absolute top-0 left-0 z-[-1] h-full w-full rounded-b-lg backdrop-blur-md',
50
+ 'bg-default',
51
+ ])
52
+ "
53
+ />
54
+
55
+ <div :class="resolvedAlign" class="flex py-4">
56
+ <slot />
57
+ </div>
58
+ </div>
59
+ </template>
@@ -0,0 +1,64 @@
1
+ <script setup lang="ts">
2
+ import type { ClassNameValue } from 'tailwind-merge'
3
+ import { twMerge } from 'tailwind-merge'
4
+
5
+ interface Props {
6
+ hideCloseButton?: boolean
7
+ hideDivider?: boolean | null
8
+ class?: ClassNameValue
9
+ }
10
+
11
+ const props = withDefaults(defineProps<Props>(), {
12
+ hideCloseButton: false,
13
+ hideDivider: false,
14
+ class: '',
15
+ })
16
+
17
+ const emit = defineEmits(['close'])
18
+
19
+ const closeModel = defineModel<() => void>('close')
20
+
21
+ const resolvedClasses = computed(() => {
22
+ const defaultClasses = `
23
+ sticky -top-4 left-0
24
+ z-51
25
+ -mt-4
26
+ flex flex-col gap-x-6
27
+ w-full
28
+ px-4
29
+ rounded-t-lg
30
+ `
31
+
32
+ return twMerge([defaultClasses, props.class])
33
+ })
34
+
35
+ function closeModal() {
36
+ emit('close')
37
+
38
+ closeModel.value?.()
39
+ }
40
+ </script>
41
+
42
+ <template>
43
+ <div :class="resolvedClasses">
44
+ <div
45
+ class="bg-default absolute top-0 left-0 z-[-1] h-full w-full rounded-t-lg backdrop-blur-md"
46
+ />
47
+
48
+ <div class="flex items-start justify-between py-4">
49
+ <h2 :class="twMerge(['text-3xl leading-10 font-medium'])">
50
+ <slot />
51
+ </h2>
52
+
53
+ <div v-if="props.hideCloseButton === false">
54
+ <UButton size="sm" variant="ghost" square @click="closeModal">
55
+ <span class="sr-only">Close</span>
56
+
57
+ <Icon name="lucide:x" class="text-xl" />
58
+ </UButton>
59
+ </div>
60
+ </div>
61
+
62
+ <USeparator v-if="props.hideDivider === false" color="neutral" />
63
+ </div>
64
+ </template>
@@ -0,0 +1,9 @@
1
+ export function useObjectURL(file: File) {
2
+ const blob = shallowRef(file ? URL.createObjectURL(file) : undefined)
3
+
4
+ watch(toRef(file), (value) => {
5
+ blob.value = value ? URL.createObjectURL(value) : undefined
6
+ })
7
+
8
+ return { blob }
9
+ }
@@ -0,0 +1,45 @@
1
+ <script setup lang="ts">
2
+ const { actions: sidebarActions } = useSidebarAdminActions()
3
+
4
+ const menu = ref([
5
+ {
6
+ label: 'Home',
7
+ href: '/admin/home',
8
+ icon: 'lucide:house',
9
+ },
10
+ ...sidebarActions.value,
11
+ {
12
+ label: 'Settings',
13
+ href: '/admin/settings',
14
+ icon: 'lucide:settings',
15
+ },
16
+ ])
17
+ </script>
18
+
19
+ <template>
20
+ <div
21
+ class="dark:bg-admin-content light:bg-white font-outfit flex h-full grow flex-col"
22
+ >
23
+ <NavbarAdmin />
24
+
25
+ <div class="flex h-full items-stretch">
26
+ <div
27
+ class="dark:bg-admin-sidebar light:bg-zinc-100 w-64 shrink-0 px-4 py-4"
28
+ >
29
+ <UNavigationMenu
30
+ :items="menu"
31
+ :ui="{
32
+ link: 'text-base gap-x-3 font-normal',
33
+ }"
34
+ orientation="vertical"
35
+ class="data-[orientation=vertical]:w-full"
36
+ highlight
37
+ />
38
+ </div>
39
+
40
+ <div class="grow overflow-hidden">
41
+ <slot />
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </template>
@@ -0,0 +1,7 @@
1
+ <script setup lang="ts"></script>
2
+
3
+ <template>
4
+ <div class="bg-admin-content font-outfit flex h-full grow flex-col">
5
+ <slot />
6
+ </div>
7
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ useHead({
3
+ title: `Admin`,
4
+ })
5
+ </script>
6
+
7
+ <template>
8
+ <main>
9
+ <AdminView>
10
+ <h1 class="text-4xl font-bold">Admin Dashboard</h1>
11
+
12
+ <div class="grid grid-cols-3 gap-x-4 gap-y-6">
13
+ <div
14
+ v-for="i in 6"
15
+ :key="i"
16
+ class="h-50 rounded-3xl border border-gray-500"
17
+ />
18
+ </div>
19
+ </AdminView>
20
+ </main>
21
+ </template>
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ definePageMeta({
3
+ layout: 'admin',
4
+ })
5
+ </script>
6
+
7
+ <template>
8
+ <div>Home</div>
9
+ </template>
@@ -0,0 +1,5 @@
1
+ export default defineNuxtPlugin((app) => {
2
+ app.vueApp.directive('autofocus', {
3
+ mounted: (el) => el.focus(),
4
+ })
5
+ })