@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.
- package/.editorconfig +12 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/publish.yml +32 -0
- package/.prettierrc +10 -0
- package/.vscode/extensions.json +14 -0
- package/.vscode/settings.json +72 -0
- package/.vscode/vue.code-snippets +16 -0
- package/README.md +114 -0
- package/app/app.config.ts +41 -0
- package/app/app.vue +13 -0
- package/app/assets/css/tailwind.css +12 -0
- package/app/components/AdminView.vue +7 -0
- package/app/components/ColorModeButton.vue +28 -0
- package/app/components/Container.vue +20 -0
- package/app/components/Pluto/Navbar/PlutoNavbarAdmin.vue +9 -0
- package/app/components/Pluto/Navbar/PlutoNavbarAdminActions.vue +7 -0
- package/app/components/Pluto/PlutoRoot.vue +27 -0
- package/app/components/ScrollArea.vue +86 -0
- package/app/components/modal/Modal.vue +161 -0
- package/app/components/modal/ModalContent.vue +27 -0
- package/app/components/modal/ModalFooter.vue +59 -0
- package/app/components/modal/ModalHeader.vue +64 -0
- package/app/composables/object-url.ts +9 -0
- package/app/layouts/admin.vue +45 -0
- package/app/layouts/auth.vue +7 -0
- package/app/pages/admin/home.vue +21 -0
- package/app/pages/index.vue +9 -0
- package/app/plugins/autofocus.ts +5 -0
- package/bun.lock +2656 -0
- package/eslint.config.mjs +84 -0
- package/layers/sql/.env.example +5 -0
- package/layers/sql/nuxt.config.ts +26 -0
- package/nuxt.config.ts +91 -0
- package/package.json +65 -0
- package/public/img/pluto.png +0 -0
- package/tsconfig.json +9 -0
|
@@ -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,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,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>
|