@falcondev-oss/nuxt-layers-base 0.1.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/.nuxt/app.config.mjs +317 -0
- package/.nuxt/components.d.ts +332 -0
- package/.nuxt/imports.d.ts +61 -0
- package/.nuxt/manifest/meta/24fb5e89-93fb-44cb-bbff-2a13f165c1e8.json +1 -0
- package/.nuxt/nuxt-icon-client-bundle.mjs +1 -0
- package/.nuxt/nuxt-icon-server-bundle.mjs +14 -0
- package/.nuxt/nuxt.d.ts +24 -0
- package/.nuxt/nuxt.node.d.ts +14 -0
- package/.nuxt/nuxt.shared.d.ts +5 -0
- package/.nuxt/schema/nuxt.schema.d.ts +210 -0
- package/.nuxt/schema/nuxt.schema.json +263 -0
- package/.nuxt/tsconfig.app.json +217 -0
- package/.nuxt/tsconfig.json +219 -0
- package/.nuxt/tsconfig.node.json +114 -0
- package/.nuxt/tsconfig.server.json +140 -0
- package/.nuxt/tsconfig.shared.json +139 -0
- package/.nuxt/types/app.config.d.ts +331 -0
- package/.nuxt/types/build.d.ts +24 -0
- package/.nuxt/types/builder-env.d.ts +1 -0
- package/.nuxt/types/components.d.ts +337 -0
- package/.nuxt/types/imports.d.ts +906 -0
- package/.nuxt/types/layouts.d.ts +7 -0
- package/.nuxt/types/middleware.d.ts +7 -0
- package/.nuxt/types/modules.d.ts +159 -0
- package/.nuxt/types/nitro-config.d.ts +14 -0
- package/.nuxt/types/nitro-imports.d.ts +141 -0
- package/.nuxt/types/nitro-middleware.d.ts +11 -0
- package/.nuxt/types/nitro-nuxt.d.ts +61 -0
- package/.nuxt/types/nitro-routes.d.ts +17 -0
- package/.nuxt/types/nitro.d.ts +3 -0
- package/.nuxt/types/plugins.d.ts +35 -0
- package/.nuxt/types/runtime-config.d.ts +36 -0
- package/.nuxt/types/ui.d.ts +36 -0
- package/.nuxt/types/vue-shim.d.ts +0 -0
- package/.nuxt/ui/accordion.ts +20 -0
- package/.nuxt/ui/alert.ts +264 -0
- package/.nuxt/ui/auth-form.ts +20 -0
- package/.nuxt/ui/avatar-group.ts +52 -0
- package/.nuxt/ui/avatar.ts +54 -0
- package/.nuxt/ui/badge.ts +263 -0
- package/.nuxt/ui/banner.ts +108 -0
- package/.nuxt/ui/blog-post.ts +143 -0
- package/.nuxt/ui/blog-posts.ts +9 -0
- package/.nuxt/ui/breadcrumb.ts +45 -0
- package/.nuxt/ui/button.ts +378 -0
- package/.nuxt/ui/calendar.ts +315 -0
- package/.nuxt/ui/card.ts +34 -0
- package/.nuxt/ui/carousel.ts +38 -0
- package/.nuxt/ui/changelog-version.ts +45 -0
- package/.nuxt/ui/changelog-versions.ts +8 -0
- package/.nuxt/ui/chat-message.ts +136 -0
- package/.nuxt/ui/chat-messages.ts +14 -0
- package/.nuxt/ui/chat-palette.ts +8 -0
- package/.nuxt/ui/chat-prompt-submit.ts +5 -0
- package/.nuxt/ui/chat-prompt.ts +35 -0
- package/.nuxt/ui/checkbox-group.ts +207 -0
- package/.nuxt/ui/checkbox.ts +237 -0
- package/.nuxt/ui/chip.ts +96 -0
- package/.nuxt/ui/collapsible.ts +6 -0
- package/.nuxt/ui/color-picker.ts +47 -0
- package/.nuxt/ui/command-palette.ts +62 -0
- package/.nuxt/ui/container.ts +3 -0
- package/.nuxt/ui/context-menu.ts +219 -0
- package/.nuxt/ui/dashboard-group.ts +3 -0
- package/.nuxt/ui/dashboard-navbar.ts +21 -0
- package/.nuxt/ui/dashboard-panel.ts +17 -0
- package/.nuxt/ui/dashboard-resize-handle.ts +3 -0
- package/.nuxt/ui/dashboard-search-button.ts +15 -0
- package/.nuxt/ui/dashboard-search.ts +13 -0
- package/.nuxt/ui/dashboard-sidebar-collapse.ts +9 -0
- package/.nuxt/ui/dashboard-sidebar-toggle.ts +9 -0
- package/.nuxt/ui/dashboard-sidebar.ts +37 -0
- package/.nuxt/ui/dashboard-toolbar.ts +7 -0
- package/.nuxt/ui/drawer.ts +149 -0
- package/.nuxt/ui/dropdown-menu.ts +220 -0
- package/.nuxt/ui/editor-drag-handle.ts +6 -0
- package/.nuxt/ui/editor-emoji-menu.ts +35 -0
- package/.nuxt/ui/editor-mention-menu.ts +35 -0
- package/.nuxt/ui/editor-suggestion-menu.ts +35 -0
- package/.nuxt/ui/editor-toolbar.ts +21 -0
- package/.nuxt/ui/editor.ts +35 -0
- package/.nuxt/ui/empty.ts +83 -0
- package/.nuxt/ui/error.ts +9 -0
- package/.nuxt/ui/field-group.ts +16 -0
- package/.nuxt/ui/file-upload.ts +290 -0
- package/.nuxt/ui/footer-columns.ts +28 -0
- package/.nuxt/ui/footer.ts +11 -0
- package/.nuxt/ui/form-field.ts +62 -0
- package/.nuxt/ui/form.ts +3 -0
- package/.nuxt/ui/header.ts +25 -0
- package/.nuxt/ui/index.ts +109 -0
- package/.nuxt/ui/input-date.ts +337 -0
- package/.nuxt/ui/input-menu.ts +460 -0
- package/.nuxt/ui/input-number.ts +256 -0
- package/.nuxt/ui/input-tags.ts +310 -0
- package/.nuxt/ui/input-time.ts +336 -0
- package/.nuxt/ui/input.ts +289 -0
- package/.nuxt/ui/kbd.ts +195 -0
- package/.nuxt/ui/link.ts +22 -0
- package/.nuxt/ui/main.ts +3 -0
- package/.nuxt/ui/marquee.ts +66 -0
- package/.nuxt/ui/modal.ts +60 -0
- package/.nuxt/ui/navigation-menu.ts +512 -0
- package/.nuxt/ui/page-anchors.ts +30 -0
- package/.nuxt/ui/page-aside.ts +10 -0
- package/.nuxt/ui/page-body.ts +3 -0
- package/.nuxt/ui/page-card.ts +274 -0
- package/.nuxt/ui/page-columns.ts +3 -0
- package/.nuxt/ui/page-cta.ts +70 -0
- package/.nuxt/ui/page-feature.ts +34 -0
- package/.nuxt/ui/page-grid.ts +3 -0
- package/.nuxt/ui/page-header.ts +18 -0
- package/.nuxt/ui/page-hero.ts +44 -0
- package/.nuxt/ui/page-links.ts +25 -0
- package/.nuxt/ui/page-list.ts +8 -0
- package/.nuxt/ui/page-logos.ts +15 -0
- package/.nuxt/ui/page-section.ts +84 -0
- package/.nuxt/ui/page.ts +32 -0
- package/.nuxt/ui/pagination.ts +13 -0
- package/.nuxt/ui/pin-input.ts +171 -0
- package/.nuxt/ui/popover.ts +6 -0
- package/.nuxt/ui/pricing-plan.ts +101 -0
- package/.nuxt/ui/pricing-plans.ts +22 -0
- package/.nuxt/ui/pricing-table.ts +51 -0
- package/.nuxt/ui/progress.ts +297 -0
- package/.nuxt/ui/radio-group.ts +352 -0
- package/.nuxt/ui/scroll-area.ts +21 -0
- package/.nuxt/ui/select-menu.ts +361 -0
- package/.nuxt/ui/select.ts +348 -0
- package/.nuxt/ui/separator.ts +172 -0
- package/.nuxt/ui/skeleton.ts +3 -0
- package/.nuxt/ui/slideover.ts +132 -0
- package/.nuxt/ui/slider.ts +171 -0
- package/.nuxt/ui/stepper.ts +202 -0
- package/.nuxt/ui/switch.ts +132 -0
- package/.nuxt/ui/table.ts +162 -0
- package/.nuxt/ui/tabs.ts +258 -0
- package/.nuxt/ui/textarea.ts +294 -0
- package/.nuxt/ui/timeline.ts +321 -0
- package/.nuxt/ui/toast.ts +74 -0
- package/.nuxt/ui/toaster.ts +91 -0
- package/.nuxt/ui/tooltip.ts +9 -0
- package/.nuxt/ui/tree.ts +168 -0
- package/.nuxt/ui/user.ts +101 -0
- package/.nuxt/ui-image-component.ts +1 -0
- package/.nuxt/ui.css +154 -0
- package/app/app.config.ts +7 -0
- package/app/app.vue +3 -0
- package/app/assets/css/base.css +28 -0
- package/app/components/Var.vue +13 -0
- package/app/components/layout/LayoutDashboard.vue +102 -0
- package/app/components/overlay/modal/ModalActions.vue +25 -0
- package/app/components/overlay/modal/ModalConfirm.vue +25 -0
- package/app/components/overlay/modal/ModalRadioGroup.vue +37 -0
- package/app/components/u/UActions.vue +23 -0
- package/app/components/u/UCustomApp.vue +20 -0
- package/app/components/u/UField.vue +55 -0
- package/app/components/u/UImageWithFallback.vue +35 -0
- package/app/components/u/UInputDatePicker.vue +40 -0
- package/app/components/u/UInputDurationMinutes.vue +88 -0
- package/app/components/u/UTableCard.vue +16 -0
- package/app/composables/useConfirm.ts +16 -0
- package/app/composables/usePreventPageLeave.ts +31 -0
- package/app/composables/useRouteParamString.ts +22 -0
- package/app/composables/useTableColumns.tsx +68 -0
- package/app/composables/useToast.ts +40 -0
- package/app/composables/useTrpc.ts +3 -0
- package/app/error.vue +15 -0
- package/app/pages/index.vue +3 -0
- package/app/utils/form-field-translators.ts +41 -0
- package/app/utils/plugins.ts +95 -0
- package/app/utils/reactivity.ts +17 -0
- package/nuxt.config.ts +85 -0
- package/package.json +46 -0
- package/pnpm-workspace.yaml +5 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { InputEmits, InputProps } from '@nuxt/ui'
|
|
3
|
+
import type { MaskOptions } from 'maska'
|
|
4
|
+
import { regex } from 'arkregex'
|
|
5
|
+
import { vMaska } from 'maska/vue'
|
|
6
|
+
import { useForwardPropsEmits } from 'reka-ui'
|
|
7
|
+
|
|
8
|
+
const props =
|
|
9
|
+
defineProps<Omit<InputProps<string>, 'modelValue' | 'defaultValue' | 'modelModifiers'>>()
|
|
10
|
+
const emit = defineEmits<Omit<InputEmits<string>, 'update:modelValue'>>()
|
|
11
|
+
const forwardedProps = useForwardPropsEmits(props, emit)
|
|
12
|
+
|
|
13
|
+
const model = defineModel<number | null>({
|
|
14
|
+
required: true,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line unicorn/prefer-string-raw
|
|
18
|
+
const parser = regex('^-?(?<hours>\\d{1,}):?(?<minutes>\\d{1,2})?$')
|
|
19
|
+
const duration = computed({
|
|
20
|
+
get: () => {
|
|
21
|
+
if (model.value === null) return null
|
|
22
|
+
|
|
23
|
+
const absoluteMinutes = Math.abs(model.value)
|
|
24
|
+
|
|
25
|
+
const hours = Math.floor(absoluteMinutes / 60)
|
|
26
|
+
const minutes = absoluteMinutes % 60
|
|
27
|
+
const sign = model.value < 0 ? '-' : ''
|
|
28
|
+
|
|
29
|
+
// don't always add :00 if minutes is 0
|
|
30
|
+
if (minutes > 0)
|
|
31
|
+
return `${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`
|
|
32
|
+
|
|
33
|
+
return `${sign}${hours.toString()}`
|
|
34
|
+
},
|
|
35
|
+
set: (val) => {
|
|
36
|
+
if (!val) {
|
|
37
|
+
model.value = null
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const parsed = parser.exec(val)
|
|
42
|
+
if (!parsed) {
|
|
43
|
+
model.value = null
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isNegative = val.startsWith('-')
|
|
48
|
+
|
|
49
|
+
const hours = Number.parseInt(parsed.groups.hours) * 60
|
|
50
|
+
const minutes = parsed.groups.minutes ? Number.parseInt(parsed.groups.minutes) : 0
|
|
51
|
+
|
|
52
|
+
model.value = isNegative ? (hours + minutes) * -1 : hours + minutes
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const maskOptions: MaskOptions = {
|
|
57
|
+
// S: sign, H: hours (unlimited), 5: tens digit of minutes, D: digit of minutes
|
|
58
|
+
mask: 'SH:5D',
|
|
59
|
+
tokens: {
|
|
60
|
+
'S': { pattern: /[-+]/, optional: true },
|
|
61
|
+
'D': { pattern: /\d/ },
|
|
62
|
+
'H': { pattern: /\d/, multiple: true },
|
|
63
|
+
'5': {
|
|
64
|
+
pattern: /[0-5]/,
|
|
65
|
+
transform(char) {
|
|
66
|
+
// clamp char to max 5, otherwise maska would just block input if user tries to input 6-9
|
|
67
|
+
const num = Number.parseInt(char)
|
|
68
|
+
if (Number.isNaN(num)) return char
|
|
69
|
+
return num > 5 ? '5' : char
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<template>
|
|
77
|
+
<UInput
|
|
78
|
+
v-bind="forwardedProps"
|
|
79
|
+
v-model="duration"
|
|
80
|
+
v-maska="maskOptions"
|
|
81
|
+
:model-modifiers="{
|
|
82
|
+
nullable: true,
|
|
83
|
+
lazy: true,
|
|
84
|
+
}"
|
|
85
|
+
placeholder="HH:mm"
|
|
86
|
+
trailing-icon="ph:timer"
|
|
87
|
+
/>
|
|
88
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { CardSlots } from '@nuxt/ui'
|
|
3
|
+
const slots = defineSlots<CardSlots>()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<UCard
|
|
8
|
+
:ui="{
|
|
9
|
+
body: 'p-0!',
|
|
10
|
+
}"
|
|
11
|
+
>
|
|
12
|
+
<template v-for="(_, name) of slots" #[name]>
|
|
13
|
+
<slot :name />
|
|
14
|
+
</template>
|
|
15
|
+
</UCard>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { OverlayModalConfirm } from '#components'
|
|
2
|
+
|
|
3
|
+
export const useConfirm = createGlobalState(() => {
|
|
4
|
+
const overlay = useOverlay()
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
confirmDestructive: async (props: {
|
|
8
|
+
description: string
|
|
9
|
+
submitLabel: string
|
|
10
|
+
title: string
|
|
11
|
+
}) => {
|
|
12
|
+
const modal = overlay.create(OverlayModalConfirm)
|
|
13
|
+
return modal.open(props).result as Promise<boolean>
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { onBeforeRouteLeave } from '#app'
|
|
2
|
+
import { useConfirm } from './useConfirm'
|
|
3
|
+
|
|
4
|
+
export function usePreventPageLeave(
|
|
5
|
+
preventPageLeave: MaybeRefOrGetter<boolean> = true,
|
|
6
|
+
opts?: {
|
|
7
|
+
leaveTitle?: string
|
|
8
|
+
leaveDescription?: string
|
|
9
|
+
},
|
|
10
|
+
) {
|
|
11
|
+
useEventListener('beforeunload', (event) => {
|
|
12
|
+
if (!toValue(preventPageLeave)) return
|
|
13
|
+
event.returnValue = opts?.leaveDescription
|
|
14
|
+
return opts?.leaveDescription
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const confirm = useConfirm()
|
|
18
|
+
|
|
19
|
+
onBeforeRouteLeave(async (_, __, next) => {
|
|
20
|
+
if (!toValue(preventPageLeave)) return next()
|
|
21
|
+
|
|
22
|
+
const allowLeave = await confirm.confirmDestructive({
|
|
23
|
+
title: opts?.leaveTitle || 'Unsaved Changes',
|
|
24
|
+
description:
|
|
25
|
+
opts?.leaveDescription ||
|
|
26
|
+
'You have unsaved changes. Are you sure you want to leave this page?',
|
|
27
|
+
submitLabel: 'Leave Page',
|
|
28
|
+
})
|
|
29
|
+
if (allowLeave) return next()
|
|
30
|
+
})
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useRouteParams } from '@vueuse/router'
|
|
2
|
+
|
|
3
|
+
export function useRouteParamString(paramName: string) {
|
|
4
|
+
const param = useRouteParams<string>(paramName)
|
|
5
|
+
|
|
6
|
+
const paramRef = ref(param.value)
|
|
7
|
+
const stop = watch(
|
|
8
|
+
param,
|
|
9
|
+
() => {
|
|
10
|
+
if (typeof param.value !== 'string') throw new Error(`${paramName} must be string`)
|
|
11
|
+
paramRef.value = param.value
|
|
12
|
+
},
|
|
13
|
+
{ immediate: true },
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
onBeforeRouteLeave((_, __, next) => {
|
|
17
|
+
stop()
|
|
18
|
+
next()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return paramRef
|
|
22
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ButtonProps, TableColumn, TableRow } from '@nuxt/ui'
|
|
2
|
+
import type { UnwrapRef } from 'vue'
|
|
3
|
+
import { UActions } from '#components'
|
|
4
|
+
|
|
5
|
+
interface ActionsOptions<T> {
|
|
6
|
+
onDelete?: (row: TableRow<T>) => any
|
|
7
|
+
headerActions?: ButtonProps[]
|
|
8
|
+
rowActions?: ButtonProps[] | ((row: TableRow<T>) => ButtonProps[])
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type GetRowType<T extends MaybeRef<Record<string, any>[] | undefined | null>> = NonNullable<
|
|
12
|
+
UnwrapRef<T>
|
|
13
|
+
>[number]
|
|
14
|
+
|
|
15
|
+
const rowActionDefaults = {
|
|
16
|
+
variant: 'ghost',
|
|
17
|
+
} satisfies ButtonProps
|
|
18
|
+
const headerActionDefaults = {
|
|
19
|
+
variant: 'subtle',
|
|
20
|
+
} satisfies ButtonProps
|
|
21
|
+
|
|
22
|
+
export function useTableColumns<T extends MaybeRef<Record<string, any>[] | undefined | null>>(
|
|
23
|
+
columns: MaybeRefOrGetter<TableColumn<GetRowType<T>>[]>,
|
|
24
|
+
actions?: ActionsOptions<GetRowType<T>>,
|
|
25
|
+
) {
|
|
26
|
+
return computed(() => {
|
|
27
|
+
const cols = [...toValue(columns)]
|
|
28
|
+
if (!actions) return cols
|
|
29
|
+
|
|
30
|
+
return [
|
|
31
|
+
...cols,
|
|
32
|
+
{
|
|
33
|
+
id: '$actions',
|
|
34
|
+
header: () => (
|
|
35
|
+
<UActions
|
|
36
|
+
defaults={headerActionDefaults}
|
|
37
|
+
class="justify-end"
|
|
38
|
+
actions={actions.headerActions}
|
|
39
|
+
/>
|
|
40
|
+
),
|
|
41
|
+
cell: ({ row }) => {
|
|
42
|
+
const rowActions = [
|
|
43
|
+
...((typeof actions.rowActions === 'function'
|
|
44
|
+
? actions.rowActions(row)
|
|
45
|
+
: actions.rowActions) ?? []),
|
|
46
|
+
]
|
|
47
|
+
if (actions.onDelete)
|
|
48
|
+
rowActions.push({
|
|
49
|
+
icon: 'ph:trash',
|
|
50
|
+
color: 'error',
|
|
51
|
+
loadingAuto: true,
|
|
52
|
+
onClick: async () => {
|
|
53
|
+
await actions.onDelete?.(row)
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<UActions
|
|
59
|
+
defaults={rowActionDefaults}
|
|
60
|
+
class="justify-end gap-2!"
|
|
61
|
+
actions={rowActions}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
] satisfies TableColumn<GetRowType<T>>[]
|
|
67
|
+
})
|
|
68
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Toast } from '#ui/composables'
|
|
2
|
+
import { useToast as useNuxtUiToast } from '#ui/composables'
|
|
3
|
+
|
|
4
|
+
const presets = {
|
|
5
|
+
success: {
|
|
6
|
+
color: 'success',
|
|
7
|
+
icon: 'ph:check-circle',
|
|
8
|
+
},
|
|
9
|
+
error: {
|
|
10
|
+
color: 'error',
|
|
11
|
+
icon: 'ph:x-circle',
|
|
12
|
+
},
|
|
13
|
+
warning: {
|
|
14
|
+
color: 'warning',
|
|
15
|
+
icon: 'ph:warning-circle',
|
|
16
|
+
},
|
|
17
|
+
} as const satisfies Record<string, Partial<Toast>>
|
|
18
|
+
|
|
19
|
+
interface ToastOptions extends Partial<Toast> {
|
|
20
|
+
preset?: keyof typeof presets
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const useToast = createGlobalState(() => {
|
|
24
|
+
const toast = useNuxtUiToast()
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...toast,
|
|
28
|
+
add: (toastOpts: ToastOptions) => {
|
|
29
|
+
const { preset, ...opts } = toastOpts
|
|
30
|
+
return toast.add(
|
|
31
|
+
preset
|
|
32
|
+
? {
|
|
33
|
+
...presets[preset],
|
|
34
|
+
...opts,
|
|
35
|
+
}
|
|
36
|
+
: opts,
|
|
37
|
+
)
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
})
|
package/app/error.vue
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { FormFieldTranslator } from '@falcondev-oss/form-core'
|
|
2
|
+
import type { DateValue } from 'reka-ui'
|
|
3
|
+
import { parseDate } from '@internationalized/date'
|
|
4
|
+
|
|
5
|
+
export function nullUndefinedTranslator<T>() {
|
|
6
|
+
return {
|
|
7
|
+
get(v: T | null) {
|
|
8
|
+
return v ?? undefined
|
|
9
|
+
},
|
|
10
|
+
set(v: T | undefined) {
|
|
11
|
+
return v ?? null
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function nullIndeterminateTranslator(): FormFieldTranslator<
|
|
16
|
+
boolean | null,
|
|
17
|
+
boolean | 'indeterminate'
|
|
18
|
+
> {
|
|
19
|
+
return {
|
|
20
|
+
get: (v) => v ?? ('indeterminate' as const),
|
|
21
|
+
set: (v) => (v === 'indeterminate' ? null : v),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function indeterminateFalseTranslator(): FormFieldTranslator<
|
|
25
|
+
boolean,
|
|
26
|
+
boolean | 'indeterminate'
|
|
27
|
+
> {
|
|
28
|
+
return {
|
|
29
|
+
get: (v) => (v === false ? 'indeterminate' : v),
|
|
30
|
+
set: (v) => (v === 'indeterminate' ? false : v),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function dateValueIsoTranslator(): FormFieldTranslator<string | null, DateValue | null> {
|
|
34
|
+
return {
|
|
35
|
+
get: (v) => (v ? parseDate(v) : null),
|
|
36
|
+
set: (v) => {
|
|
37
|
+
if (!v) return null
|
|
38
|
+
return v.toString()
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { DehydratedState, QueryClientConfig, VueQueryPluginOptions } from '@tanstack/vue-query'
|
|
2
|
+
import type { AnyTRPCRouter } from '@trpc/server'
|
|
3
|
+
import type { ObjectPlugin } from 'nuxt/app'
|
|
4
|
+
import { createTRPCVueQueryClient } from '@falcondev-oss/trpc-vue-query'
|
|
5
|
+
import {
|
|
6
|
+
dehydrate,
|
|
7
|
+
hydrate,
|
|
8
|
+
QueryClient,
|
|
9
|
+
useQueryClient,
|
|
10
|
+
VueQueryPlugin,
|
|
11
|
+
} from '@tanstack/vue-query'
|
|
12
|
+
import { httpSubscriptionLink, splitLink } from '@trpc/client'
|
|
13
|
+
import defu from 'defu'
|
|
14
|
+
import { useState } from 'nuxt/app'
|
|
15
|
+
import superjson from 'superjson'
|
|
16
|
+
import { httpBatchLink, httpLink } from 'trpc-nuxt/client'
|
|
17
|
+
|
|
18
|
+
interface VueQueryNuxtPluginOptions {
|
|
19
|
+
queryClientOptions: QueryClientConfig
|
|
20
|
+
vuePluginOptions?: VueQueryPluginOptions
|
|
21
|
+
}
|
|
22
|
+
export function vueQueryPlugin(opts?: VueQueryNuxtPluginOptions) {
|
|
23
|
+
return {
|
|
24
|
+
name: 'vue-query',
|
|
25
|
+
setup(nuxt) {
|
|
26
|
+
const vueQueryState = useState<DehydratedState | null>('vue-query')
|
|
27
|
+
|
|
28
|
+
const queryClient = new QueryClient(defu(opts?.queryClientOptions, {}))
|
|
29
|
+
const options: VueQueryPluginOptions = { queryClient, ...opts?.vuePluginOptions }
|
|
30
|
+
|
|
31
|
+
nuxt.vueApp.use(VueQueryPlugin, options)
|
|
32
|
+
|
|
33
|
+
if (import.meta.server) {
|
|
34
|
+
nuxt.hooks.hook('app:rendered', () => {
|
|
35
|
+
vueQueryState.value = dehydrate(queryClient)
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (import.meta.client) {
|
|
40
|
+
nuxt.hooks.hook('app:created', () => {
|
|
41
|
+
hydrate(queryClient, vueQueryState.value)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
} satisfies ObjectPlugin
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface TrpcNuxtPluginOptions {
|
|
49
|
+
url: string
|
|
50
|
+
}
|
|
51
|
+
export function trpcPlugin<Router extends AnyTRPCRouter>(opts: TrpcNuxtPluginOptions) {
|
|
52
|
+
return {
|
|
53
|
+
name: 'trpc',
|
|
54
|
+
// eslint-disable-next-line ts/no-unsafe-assignment
|
|
55
|
+
dependsOn: ['vue-query'] as any,
|
|
56
|
+
setup() {
|
|
57
|
+
const queryClient = useQueryClient()
|
|
58
|
+
const headers = useRequestHeaders()
|
|
59
|
+
|
|
60
|
+
const trpc = createTRPCVueQueryClient<Router>({
|
|
61
|
+
queryClient,
|
|
62
|
+
trpc: {
|
|
63
|
+
links: [
|
|
64
|
+
splitLink({
|
|
65
|
+
condition: (op) => op.type === 'subscription',
|
|
66
|
+
true: httpSubscriptionLink({
|
|
67
|
+
url: opts.url,
|
|
68
|
+
transformer: superjson,
|
|
69
|
+
}),
|
|
70
|
+
false: splitLink({
|
|
71
|
+
condition: (op) => op.type === 'mutation',
|
|
72
|
+
true: httpLink({
|
|
73
|
+
transformer: superjson,
|
|
74
|
+
url: opts.url,
|
|
75
|
+
headers,
|
|
76
|
+
}),
|
|
77
|
+
false: httpBatchLink({
|
|
78
|
+
transformer: superjson,
|
|
79
|
+
url: opts.url,
|
|
80
|
+
maxURLLength: 2000,
|
|
81
|
+
headers,
|
|
82
|
+
}),
|
|
83
|
+
}),
|
|
84
|
+
}),
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
return {
|
|
89
|
+
provide: {
|
|
90
|
+
trpc,
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
} satisfies ObjectPlugin
|
|
95
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function refEffect<T>(getter: MaybeRefOrGetter<T>) {
|
|
2
|
+
const getterRef = toRef(getter)
|
|
3
|
+
const __ref = ref(getterRef.value as T)
|
|
4
|
+
const _ref = __ref as typeof __ref & { reset: () => void }
|
|
5
|
+
|
|
6
|
+
watchEffect(() => {
|
|
7
|
+
// eslint-disable-next-line ts/no-unsafe-assignment
|
|
8
|
+
_ref.value = getterRef.value
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
_ref.reset = () => {
|
|
12
|
+
// eslint-disable-next-line ts/no-unsafe-assignment
|
|
13
|
+
_ref.value = getterRef.value
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return _ref
|
|
17
|
+
}
|
package/nuxt.config.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
|
|
4
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
|
5
|
+
|
|
6
|
+
export default defineNuxtConfig({
|
|
7
|
+
compatibilityDate: '2025-07-15',
|
|
8
|
+
devtools: { enabled: true },
|
|
9
|
+
|
|
10
|
+
// dev
|
|
11
|
+
typescript: {
|
|
12
|
+
tsConfig: {
|
|
13
|
+
vueCompilerOptions: {
|
|
14
|
+
strictTemplates: true,
|
|
15
|
+
htmlAttributes: ['aria-*'],
|
|
16
|
+
dataAttributes: ['data-*'],
|
|
17
|
+
strictVModel: true,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
strict: true,
|
|
21
|
+
},
|
|
22
|
+
nitro: {
|
|
23
|
+
typescript: {
|
|
24
|
+
strict: true,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
imports: {
|
|
28
|
+
imports: [
|
|
29
|
+
{
|
|
30
|
+
from: '@falcondev-oss/form-vue',
|
|
31
|
+
name: 'useForm',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'useToast',
|
|
35
|
+
from: path.join(currentDir, './app/composables/useToast'),
|
|
36
|
+
priority: 10,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// runtime
|
|
42
|
+
experimental: {
|
|
43
|
+
defaults: {
|
|
44
|
+
nuxtLink: {
|
|
45
|
+
prefetch: true,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
css: [path.join(currentDir, './app/assets/css/base.css')],
|
|
50
|
+
runtimeConfig: {
|
|
51
|
+
public: {
|
|
52
|
+
/**
|
|
53
|
+
* used as a unique identifier in all sorts of places
|
|
54
|
+
*/
|
|
55
|
+
projectId: 'base',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// modules
|
|
60
|
+
modules: ['@nuxt/ui', '@vueuse/nuxt'],
|
|
61
|
+
ui: {
|
|
62
|
+
theme: {
|
|
63
|
+
colors: ['primary', 'secondary', 'success', 'info', 'warning', 'error'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
colorMode: {
|
|
67
|
+
preference: 'light',
|
|
68
|
+
fallback: 'light',
|
|
69
|
+
},
|
|
70
|
+
icon: {
|
|
71
|
+
cssLayer: 'base',
|
|
72
|
+
provider: 'server',
|
|
73
|
+
localApiEndpoint: '/_icons',
|
|
74
|
+
},
|
|
75
|
+
app: {
|
|
76
|
+
head: {
|
|
77
|
+
meta: [
|
|
78
|
+
{
|
|
79
|
+
name: 'robots',
|
|
80
|
+
content: 'noindex',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@falcondev-oss/nuxt-layers-base",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"main": "./nuxt.config.ts",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@falcondev-oss/form-core": "^0.18.5",
|
|
8
|
+
"@falcondev-oss/form-vue": "^0.18.5",
|
|
9
|
+
"@falcondev-oss/trpc-vue-query": "^0.5.2",
|
|
10
|
+
"@iconify-json/ph": "^1.2.2",
|
|
11
|
+
"@internationalized/date": "^3.10.1",
|
|
12
|
+
"@nuxt/icon": "^2.2.0",
|
|
13
|
+
"@nuxt/ui": "4.3.0",
|
|
14
|
+
"@nuxtjs/color-mode": "^4.0.0",
|
|
15
|
+
"@tanstack/vue-query": "^5.92.5",
|
|
16
|
+
"@trpc/client": "^11.8.1",
|
|
17
|
+
"@trpc/server": "^11.8.1",
|
|
18
|
+
"@vue/devtools-api": "^8.0.5",
|
|
19
|
+
"@vueuse/core": "^14.1.0",
|
|
20
|
+
"@vueuse/nuxt": "^14.1.0",
|
|
21
|
+
"@vueuse/router": "^14.1.0",
|
|
22
|
+
"arkregex": "^0.0.5",
|
|
23
|
+
"defu": "^6.1.4",
|
|
24
|
+
"maska": "^3.2.0",
|
|
25
|
+
"reka-ui": "^2.7.0",
|
|
26
|
+
"remeda": "^2.33.1",
|
|
27
|
+
"superjson": "^2.2.6",
|
|
28
|
+
"tailwindcss": "^4.1.18",
|
|
29
|
+
"trpc-nuxt": "^2.0.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@falcondev-oss/configs": "^5.0.2",
|
|
33
|
+
"eslint": "^9.39.2",
|
|
34
|
+
"nuxt": "^4.2.2",
|
|
35
|
+
"prettier": "^3.7.4",
|
|
36
|
+
"type-fest": "^5.3.1",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"vue": "^3.5.26",
|
|
39
|
+
"vue-router": "^4.6.4",
|
|
40
|
+
"zod": "^4.3.5"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "nuxi dev .playground",
|
|
44
|
+
"dev:prepare": "nuxt prepare .playground"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/tsconfig.json
ADDED