@falcondev-oss/nuxt-layers-base 0.40.3 → 0.41.0
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/app/app.tsx +14 -0
- package/app/assets/css/base.css +6 -0
- package/app/components/layout/LayoutAuth.tsx +22 -0
- package/app/components/layout/LayoutNavbar.tsx +193 -0
- package/app/components/layout/LayoutPage.tsx +183 -0
- package/app/components/layout/LayoutSidebar.tsx +167 -0
- package/app/components/modals/ConfirmModal.tsx +80 -0
- package/app/components/modals/EntityCrudModal.tsx +124 -0
- package/app/components/overlay/modal/ModalActions.tsx +36 -0
- package/app/components/overlay/modal/ModalConfirm.tsx +46 -0
- package/app/components/overlay/modal/ModalRadioGroup.tsx +62 -0
- package/app/components/u/UActions.tsx +30 -0
- package/app/components/u/UAuthForm.tsx +30 -0
- package/app/components/u/UCustomApp.tsx +33 -0
- package/app/components/u/UField.tsx +211 -0
- package/app/components/u/UForm.tsx +133 -0
- package/app/components/u/UImageWithFallback.tsx +47 -0
- package/app/components/u/UInputDatePicker.tsx +94 -0
- package/app/components/u/UInputDateRangePicker.tsx +143 -0
- package/app/components/u/UInputDurationMinutes.tsx +106 -0
- package/app/components/u/UInputFile.tsx +132 -0
- package/app/components/u/URibbonCard.tsx +54 -0
- package/app/components/u/URibbonSection.tsx +36 -0
- package/app/components/u/USwitchCard.tsx +64 -0
- package/app/components/util/Define.tsx +21 -0
- package/app/components/util/SlotRef.tsx +21 -0
- package/app/components/util/Sync.tsx +37 -0
- package/app/components/util/TemplateText.tsx +31 -0
- package/app/composables/confirm.ts +1 -1
- package/app/error.tsx +24 -0
- package/app/utils/define-setup-component.ts +39 -5
- package/nuxt.config.ts +8 -0
- package/package.json +4 -3
- package/app/app.vue +0 -3
- package/app/components/layout/LayoutAuth.vue +0 -13
- package/app/components/layout/LayoutNavbar.vue +0 -140
- package/app/components/layout/LayoutPage.vue +0 -174
- package/app/components/layout/LayoutSidebar.vue +0 -117
- package/app/components/modals/ConfirmModal.vue +0 -65
- package/app/components/modals/EntityCrudModal.vue +0 -109
- package/app/components/overlay/modal/ModalActions.vue +0 -25
- package/app/components/overlay/modal/ModalConfirm.vue +0 -25
- package/app/components/overlay/modal/ModalRadioGroup.vue +0 -37
- package/app/components/u/UActions.vue +0 -23
- package/app/components/u/UAuthForm.vue +0 -36
- package/app/components/u/UCustomApp.vue +0 -23
- package/app/components/u/UField.vue +0 -191
- package/app/components/u/UForm.vue +0 -96
- package/app/components/u/UImageWithFallback.vue +0 -35
- package/app/components/u/UInputDatePicker.vue +0 -79
- package/app/components/u/UInputDateRangePicker.vue +0 -100
- package/app/components/u/UInputDurationMinutes.vue +0 -90
- package/app/components/u/UInputFile.vue +0 -127
- package/app/components/u/URibbonCard.vue +0 -43
- package/app/components/u/URibbonSection.vue +0 -29
- package/app/components/u/USwitchCard.vue +0 -51
- package/app/components/util/Define.vue +0 -13
- package/app/components/util/SlotRef.vue +0 -17
- package/app/components/util/Sync.vue +0 -24
- package/app/components/util/TemplateText.vue +0 -20
- package/app/error.vue +0 -15
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts" generic="Multiple extends boolean = false">
|
|
2
|
-
import type { FileUploadEmits, FileUploadProps } from '@nuxt/ui'
|
|
3
|
-
import { useForwardPropsEmits } from 'reka-ui'
|
|
4
|
-
import { omit } from 'remeda'
|
|
5
|
-
|
|
6
|
-
const props = withDefaults(
|
|
7
|
-
defineProps<
|
|
8
|
-
FileUploadProps<Multiple> & {
|
|
9
|
-
/**
|
|
10
|
-
* Set to `false` to disable compression
|
|
11
|
-
*/
|
|
12
|
-
compression?:
|
|
13
|
-
| boolean
|
|
14
|
-
| {
|
|
15
|
-
/**
|
|
16
|
-
* @default 1920
|
|
17
|
-
*/
|
|
18
|
-
maxDimension?: number
|
|
19
|
-
/**
|
|
20
|
-
* @default 0.85
|
|
21
|
-
*/
|
|
22
|
-
quality?: number
|
|
23
|
-
/**
|
|
24
|
-
* @default 'image/webp'
|
|
25
|
-
*/
|
|
26
|
-
outputType?: string
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
>(),
|
|
30
|
-
{ compression: true },
|
|
31
|
-
)
|
|
32
|
-
const emit = defineEmits<
|
|
33
|
-
FileUploadEmits & {
|
|
34
|
-
compressed: [
|
|
35
|
-
event: {
|
|
36
|
-
original: File
|
|
37
|
-
compressed: File
|
|
38
|
-
savedBytes: number
|
|
39
|
-
savedPercentage: number
|
|
40
|
-
},
|
|
41
|
-
]
|
|
42
|
-
}
|
|
43
|
-
>()
|
|
44
|
-
const forwarded = useForwardPropsEmits(props, emit)
|
|
45
|
-
|
|
46
|
-
type Files = Multiple extends true ? File[] : File
|
|
47
|
-
const model = defineModel<Files | null>()
|
|
48
|
-
|
|
49
|
-
const compressedFiles = new WeakMap<File, File>()
|
|
50
|
-
async function forwardFiles(files: File | File[] | null | undefined) {
|
|
51
|
-
if (!files) {
|
|
52
|
-
model.value = files
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const filesArray = Array.isArray(files) ? files : [files]
|
|
57
|
-
const compressed = await Promise.all(
|
|
58
|
-
filesArray.map(async (file) => {
|
|
59
|
-
if (!file.type.startsWith('image/')) return file
|
|
60
|
-
return await compressImage(file)
|
|
61
|
-
}),
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
model.value = (props.multiple ? compressed : compressed[0]!) as Files
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function compressImage(file: File) {
|
|
68
|
-
if (props.compression === false) return file
|
|
69
|
-
if (compressedFiles.has(file)) return compressedFiles.get(file)!
|
|
70
|
-
|
|
71
|
-
const maxDimension =
|
|
72
|
-
typeof props.compression === 'object' ? (props.compression?.maxDimension ?? 1920) : 1920
|
|
73
|
-
const quality =
|
|
74
|
-
typeof props.compression === 'object' ? (props.compression?.quality ?? 0.85) : 0.85
|
|
75
|
-
const outputType =
|
|
76
|
-
typeof props.compression === 'object'
|
|
77
|
-
? (props.compression?.outputType ?? 'image/webp')
|
|
78
|
-
: 'image/webp'
|
|
79
|
-
|
|
80
|
-
const img = new Image()
|
|
81
|
-
await new Promise((resolve, reject) => {
|
|
82
|
-
img.addEventListener('load', resolve)
|
|
83
|
-
img.addEventListener('error', reject)
|
|
84
|
-
img.src = URL.createObjectURL(file)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const scale = Math.min(1, maxDimension / Math.max(img.width, img.height))
|
|
88
|
-
const canvas = document.createElement('canvas')
|
|
89
|
-
canvas.width = img.width * scale
|
|
90
|
-
canvas.height = img.height * scale
|
|
91
|
-
const ctx = canvas.getContext('2d')!
|
|
92
|
-
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
|
|
93
|
-
URL.revokeObjectURL(img.src)
|
|
94
|
-
|
|
95
|
-
return new Promise<File>((resolve, reject) => {
|
|
96
|
-
canvas.toBlob(
|
|
97
|
-
(blob) => {
|
|
98
|
-
if (!blob) return reject(new Error('Canvas is empty'))
|
|
99
|
-
|
|
100
|
-
const name = file.name.replace(/\.\w+$/, `.${outputType.split('/')[1]}`)
|
|
101
|
-
const compressedFile = new File([blob], name, { type: outputType })
|
|
102
|
-
|
|
103
|
-
compressedFiles.set(file, compressedFile)
|
|
104
|
-
compressedFiles.set(compressedFile, compressedFile) // required since we set model value to the compressed file
|
|
105
|
-
resolve(compressedFile)
|
|
106
|
-
|
|
107
|
-
emit('compressed', {
|
|
108
|
-
original: file,
|
|
109
|
-
compressed: compressedFile,
|
|
110
|
-
savedBytes: file.size - compressedFile.size,
|
|
111
|
-
savedPercentage: ((file.size - compressedFile.size) / file.size) * 100,
|
|
112
|
-
})
|
|
113
|
-
},
|
|
114
|
-
outputType,
|
|
115
|
-
quality,
|
|
116
|
-
)
|
|
117
|
-
canvas.remove()
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
</script>
|
|
121
|
-
|
|
122
|
-
<template>
|
|
123
|
-
<UFileUpload
|
|
124
|
-
v-bind="omit(forwarded, ['compression'])"
|
|
125
|
-
@update:model-value="(files) => forwardFiles(files)"
|
|
126
|
-
/>
|
|
127
|
-
</template>
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { CardProps, CardSlots } from '@nuxt/ui'
|
|
3
|
-
import { useForwardProps } from 'reka-ui'
|
|
4
|
-
|
|
5
|
-
const props = defineProps<CardProps>()
|
|
6
|
-
const slots = defineSlots<
|
|
7
|
-
CardSlots & {
|
|
8
|
-
/** Ribbon strip above the body — fill it with `URibbonSection`s. */
|
|
9
|
-
ribbon?: () => any
|
|
10
|
-
}
|
|
11
|
-
>()
|
|
12
|
-
|
|
13
|
-
const forwarded = useForwardProps(props)
|
|
14
|
-
|
|
15
|
-
const cardSlots = computed(
|
|
16
|
-
() => Object.keys(slots).filter((name) => name !== 'ribbon') as (keyof CardSlots)[],
|
|
17
|
-
)
|
|
18
|
-
</script>
|
|
19
|
-
|
|
20
|
-
<template>
|
|
21
|
-
<div class="flex flex-col">
|
|
22
|
-
<!-- the ribbon's lower edge runs behind the card, so the card keeps its own rounded top -->
|
|
23
|
-
<div
|
|
24
|
-
v-if="slots.ribbon"
|
|
25
|
-
class="divide-default ring-default bg-elevated -mb-2 flex items-stretch divide-x overflow-x-auto rounded-t-lg pb-2 shadow-[inset_0_2px_3px_-2px_rgb(0_0_0/0.06),inset_2px_0_3px_-2px_rgb(0_0_0/0.06),inset_-2px_0_3px_-2px_rgb(0_0_0/0.06)] ring"
|
|
26
|
-
>
|
|
27
|
-
<slot name="ribbon" />
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<UCard
|
|
31
|
-
v-bind="forwarded"
|
|
32
|
-
:ui="{
|
|
33
|
-
body: 'p-0!',
|
|
34
|
-
...forwarded.ui,
|
|
35
|
-
}"
|
|
36
|
-
class="ring-accented relative shadow-[0_-2px_3px_-1px_rgb(0_0_0/0.08)]"
|
|
37
|
-
>
|
|
38
|
-
<template v-for="name of cardSlots" #[name]>
|
|
39
|
-
<slot :name />
|
|
40
|
-
</template>
|
|
41
|
-
</UCard>
|
|
42
|
-
</div>
|
|
43
|
-
</template>
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineOptions({
|
|
3
|
-
inheritAttrs: false,
|
|
4
|
-
})
|
|
5
|
-
|
|
6
|
-
defineProps<{
|
|
7
|
-
title?: string
|
|
8
|
-
end?: boolean
|
|
9
|
-
}>()
|
|
10
|
-
|
|
11
|
-
defineSlots<{
|
|
12
|
-
default: () => any
|
|
13
|
-
}>()
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<template>
|
|
17
|
-
<div
|
|
18
|
-
:data-end="end || undefined"
|
|
19
|
-
class="flex shrink-0 flex-col gap-1.5 px-3 py-2"
|
|
20
|
-
:class="end && 'border-default [&:not([data-end]~*)]:ml-auto [&:not([data-end]~*)]:border-l'"
|
|
21
|
-
>
|
|
22
|
-
<p v-if="title" class="text-dimmed text-[10px] leading-none">
|
|
23
|
-
{{ title }}
|
|
24
|
-
</p>
|
|
25
|
-
<div class="flex flex-1 flex-wrap items-center gap-1" v-bind="$attrs">
|
|
26
|
-
<slot />
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
</template>
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
const props = defineProps<{
|
|
3
|
-
title: string
|
|
4
|
-
icon?: string
|
|
5
|
-
loading?: boolean
|
|
6
|
-
alwaysExpanded?: boolean
|
|
7
|
-
}>()
|
|
8
|
-
|
|
9
|
-
const emit = defineEmits<{
|
|
10
|
-
toggle: [value: boolean, controller: AbortController]
|
|
11
|
-
}>()
|
|
12
|
-
|
|
13
|
-
defineSlots<{
|
|
14
|
-
default: () => any
|
|
15
|
-
}>()
|
|
16
|
-
|
|
17
|
-
const model = defineModel<boolean>({ required: true })
|
|
18
|
-
|
|
19
|
-
const expanded = computed(() => props.alwaysExpanded || model.value)
|
|
20
|
-
|
|
21
|
-
function onToggle(value: boolean) {
|
|
22
|
-
const controller = new AbortController()
|
|
23
|
-
emit('toggle', value, controller)
|
|
24
|
-
if (controller.signal.aborted) return
|
|
25
|
-
|
|
26
|
-
model.value = value
|
|
27
|
-
}
|
|
28
|
-
</script>
|
|
29
|
-
|
|
30
|
-
<template>
|
|
31
|
-
<UCard
|
|
32
|
-
class="m-px"
|
|
33
|
-
variant="subtle"
|
|
34
|
-
:ui="{
|
|
35
|
-
header: `text-sm font-semibold py-2 px-3! ${expanded ? '' : 'border-b-0'}`,
|
|
36
|
-
body: `p-3! ${expanded ? '' : 'hidden'}`,
|
|
37
|
-
}"
|
|
38
|
-
>
|
|
39
|
-
<template #header>
|
|
40
|
-
<div class="flex items-center justify-between gap-8">
|
|
41
|
-
<span class="flex items-center gap-2">
|
|
42
|
-
<UIcon v-if="icon" :name="icon" class="text-muted size-5" />
|
|
43
|
-
{{ title }}
|
|
44
|
-
</span>
|
|
45
|
-
<USwitch :model-value="model" :loading @update:model-value="(value) => onToggle(value)" />
|
|
46
|
-
</div>
|
|
47
|
-
</template>
|
|
48
|
-
|
|
49
|
-
<slot />
|
|
50
|
-
</UCard>
|
|
51
|
-
</template>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { ComponentPublicInstance } from 'vue'
|
|
3
|
-
|
|
4
|
-
defineProps<{
|
|
5
|
-
slotRef?: ComponentPublicInstance
|
|
6
|
-
}>()
|
|
7
|
-
|
|
8
|
-
defineSlots<{
|
|
9
|
-
default: () => any
|
|
10
|
-
}>()
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<template>
|
|
14
|
-
<component :is="slotRef.$slots.default" v-if="slotRef" />
|
|
15
|
-
<!-- eslint-disable-next-line vue/no-lone-template -->
|
|
16
|
-
<template v-else />
|
|
17
|
-
</template>
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts" generic="T">
|
|
2
|
-
const props = defineProps<{
|
|
3
|
-
input: T
|
|
4
|
-
deep?: boolean
|
|
5
|
-
once?: boolean
|
|
6
|
-
}>()
|
|
7
|
-
|
|
8
|
-
const output = defineModel<T>('output', { required: true })
|
|
9
|
-
|
|
10
|
-
watch(
|
|
11
|
-
() => props.input,
|
|
12
|
-
() => {
|
|
13
|
-
output.value = props.input
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
immediate: true,
|
|
17
|
-
deep: props.deep,
|
|
18
|
-
once: props.once,
|
|
19
|
-
},
|
|
20
|
-
)
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<!-- eslint-disable-next-line vue/valid-template-root -->
|
|
24
|
-
<template></template>
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { templateParts } from '../../utils/display'
|
|
3
|
-
|
|
4
|
-
defineProps<{
|
|
5
|
-
text: ReturnType<typeof templateParts>
|
|
6
|
-
}>()
|
|
7
|
-
</script>
|
|
8
|
-
|
|
9
|
-
<template>
|
|
10
|
-
<p>
|
|
11
|
-
<span v-for="(string, index) in text.strings" :key="index">
|
|
12
|
-
<span>{{ string }}</span>
|
|
13
|
-
<strong v-if="text.values[index]" class="font-semibold">
|
|
14
|
-
<UBadge variant="soft" color="neutral" size="lg" class="h-6! p-1! font-semibold">
|
|
15
|
-
{{ text.values[index] }}
|
|
16
|
-
</UBadge>
|
|
17
|
-
</strong>
|
|
18
|
-
</span>
|
|
19
|
-
</p>
|
|
20
|
-
</template>
|
package/app/error.vue
DELETED