@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.
Files changed (176) hide show
  1. package/.nuxt/app.config.mjs +317 -0
  2. package/.nuxt/components.d.ts +332 -0
  3. package/.nuxt/imports.d.ts +61 -0
  4. package/.nuxt/manifest/meta/24fb5e89-93fb-44cb-bbff-2a13f165c1e8.json +1 -0
  5. package/.nuxt/nuxt-icon-client-bundle.mjs +1 -0
  6. package/.nuxt/nuxt-icon-server-bundle.mjs +14 -0
  7. package/.nuxt/nuxt.d.ts +24 -0
  8. package/.nuxt/nuxt.node.d.ts +14 -0
  9. package/.nuxt/nuxt.shared.d.ts +5 -0
  10. package/.nuxt/schema/nuxt.schema.d.ts +210 -0
  11. package/.nuxt/schema/nuxt.schema.json +263 -0
  12. package/.nuxt/tsconfig.app.json +217 -0
  13. package/.nuxt/tsconfig.json +219 -0
  14. package/.nuxt/tsconfig.node.json +114 -0
  15. package/.nuxt/tsconfig.server.json +140 -0
  16. package/.nuxt/tsconfig.shared.json +139 -0
  17. package/.nuxt/types/app.config.d.ts +331 -0
  18. package/.nuxt/types/build.d.ts +24 -0
  19. package/.nuxt/types/builder-env.d.ts +1 -0
  20. package/.nuxt/types/components.d.ts +337 -0
  21. package/.nuxt/types/imports.d.ts +906 -0
  22. package/.nuxt/types/layouts.d.ts +7 -0
  23. package/.nuxt/types/middleware.d.ts +7 -0
  24. package/.nuxt/types/modules.d.ts +159 -0
  25. package/.nuxt/types/nitro-config.d.ts +14 -0
  26. package/.nuxt/types/nitro-imports.d.ts +141 -0
  27. package/.nuxt/types/nitro-middleware.d.ts +11 -0
  28. package/.nuxt/types/nitro-nuxt.d.ts +61 -0
  29. package/.nuxt/types/nitro-routes.d.ts +17 -0
  30. package/.nuxt/types/nitro.d.ts +3 -0
  31. package/.nuxt/types/plugins.d.ts +35 -0
  32. package/.nuxt/types/runtime-config.d.ts +36 -0
  33. package/.nuxt/types/ui.d.ts +36 -0
  34. package/.nuxt/types/vue-shim.d.ts +0 -0
  35. package/.nuxt/ui/accordion.ts +20 -0
  36. package/.nuxt/ui/alert.ts +264 -0
  37. package/.nuxt/ui/auth-form.ts +20 -0
  38. package/.nuxt/ui/avatar-group.ts +52 -0
  39. package/.nuxt/ui/avatar.ts +54 -0
  40. package/.nuxt/ui/badge.ts +263 -0
  41. package/.nuxt/ui/banner.ts +108 -0
  42. package/.nuxt/ui/blog-post.ts +143 -0
  43. package/.nuxt/ui/blog-posts.ts +9 -0
  44. package/.nuxt/ui/breadcrumb.ts +45 -0
  45. package/.nuxt/ui/button.ts +378 -0
  46. package/.nuxt/ui/calendar.ts +315 -0
  47. package/.nuxt/ui/card.ts +34 -0
  48. package/.nuxt/ui/carousel.ts +38 -0
  49. package/.nuxt/ui/changelog-version.ts +45 -0
  50. package/.nuxt/ui/changelog-versions.ts +8 -0
  51. package/.nuxt/ui/chat-message.ts +136 -0
  52. package/.nuxt/ui/chat-messages.ts +14 -0
  53. package/.nuxt/ui/chat-palette.ts +8 -0
  54. package/.nuxt/ui/chat-prompt-submit.ts +5 -0
  55. package/.nuxt/ui/chat-prompt.ts +35 -0
  56. package/.nuxt/ui/checkbox-group.ts +207 -0
  57. package/.nuxt/ui/checkbox.ts +237 -0
  58. package/.nuxt/ui/chip.ts +96 -0
  59. package/.nuxt/ui/collapsible.ts +6 -0
  60. package/.nuxt/ui/color-picker.ts +47 -0
  61. package/.nuxt/ui/command-palette.ts +62 -0
  62. package/.nuxt/ui/container.ts +3 -0
  63. package/.nuxt/ui/context-menu.ts +219 -0
  64. package/.nuxt/ui/dashboard-group.ts +3 -0
  65. package/.nuxt/ui/dashboard-navbar.ts +21 -0
  66. package/.nuxt/ui/dashboard-panel.ts +17 -0
  67. package/.nuxt/ui/dashboard-resize-handle.ts +3 -0
  68. package/.nuxt/ui/dashboard-search-button.ts +15 -0
  69. package/.nuxt/ui/dashboard-search.ts +13 -0
  70. package/.nuxt/ui/dashboard-sidebar-collapse.ts +9 -0
  71. package/.nuxt/ui/dashboard-sidebar-toggle.ts +9 -0
  72. package/.nuxt/ui/dashboard-sidebar.ts +37 -0
  73. package/.nuxt/ui/dashboard-toolbar.ts +7 -0
  74. package/.nuxt/ui/drawer.ts +149 -0
  75. package/.nuxt/ui/dropdown-menu.ts +220 -0
  76. package/.nuxt/ui/editor-drag-handle.ts +6 -0
  77. package/.nuxt/ui/editor-emoji-menu.ts +35 -0
  78. package/.nuxt/ui/editor-mention-menu.ts +35 -0
  79. package/.nuxt/ui/editor-suggestion-menu.ts +35 -0
  80. package/.nuxt/ui/editor-toolbar.ts +21 -0
  81. package/.nuxt/ui/editor.ts +35 -0
  82. package/.nuxt/ui/empty.ts +83 -0
  83. package/.nuxt/ui/error.ts +9 -0
  84. package/.nuxt/ui/field-group.ts +16 -0
  85. package/.nuxt/ui/file-upload.ts +290 -0
  86. package/.nuxt/ui/footer-columns.ts +28 -0
  87. package/.nuxt/ui/footer.ts +11 -0
  88. package/.nuxt/ui/form-field.ts +62 -0
  89. package/.nuxt/ui/form.ts +3 -0
  90. package/.nuxt/ui/header.ts +25 -0
  91. package/.nuxt/ui/index.ts +109 -0
  92. package/.nuxt/ui/input-date.ts +337 -0
  93. package/.nuxt/ui/input-menu.ts +460 -0
  94. package/.nuxt/ui/input-number.ts +256 -0
  95. package/.nuxt/ui/input-tags.ts +310 -0
  96. package/.nuxt/ui/input-time.ts +336 -0
  97. package/.nuxt/ui/input.ts +289 -0
  98. package/.nuxt/ui/kbd.ts +195 -0
  99. package/.nuxt/ui/link.ts +22 -0
  100. package/.nuxt/ui/main.ts +3 -0
  101. package/.nuxt/ui/marquee.ts +66 -0
  102. package/.nuxt/ui/modal.ts +60 -0
  103. package/.nuxt/ui/navigation-menu.ts +512 -0
  104. package/.nuxt/ui/page-anchors.ts +30 -0
  105. package/.nuxt/ui/page-aside.ts +10 -0
  106. package/.nuxt/ui/page-body.ts +3 -0
  107. package/.nuxt/ui/page-card.ts +274 -0
  108. package/.nuxt/ui/page-columns.ts +3 -0
  109. package/.nuxt/ui/page-cta.ts +70 -0
  110. package/.nuxt/ui/page-feature.ts +34 -0
  111. package/.nuxt/ui/page-grid.ts +3 -0
  112. package/.nuxt/ui/page-header.ts +18 -0
  113. package/.nuxt/ui/page-hero.ts +44 -0
  114. package/.nuxt/ui/page-links.ts +25 -0
  115. package/.nuxt/ui/page-list.ts +8 -0
  116. package/.nuxt/ui/page-logos.ts +15 -0
  117. package/.nuxt/ui/page-section.ts +84 -0
  118. package/.nuxt/ui/page.ts +32 -0
  119. package/.nuxt/ui/pagination.ts +13 -0
  120. package/.nuxt/ui/pin-input.ts +171 -0
  121. package/.nuxt/ui/popover.ts +6 -0
  122. package/.nuxt/ui/pricing-plan.ts +101 -0
  123. package/.nuxt/ui/pricing-plans.ts +22 -0
  124. package/.nuxt/ui/pricing-table.ts +51 -0
  125. package/.nuxt/ui/progress.ts +297 -0
  126. package/.nuxt/ui/radio-group.ts +352 -0
  127. package/.nuxt/ui/scroll-area.ts +21 -0
  128. package/.nuxt/ui/select-menu.ts +361 -0
  129. package/.nuxt/ui/select.ts +348 -0
  130. package/.nuxt/ui/separator.ts +172 -0
  131. package/.nuxt/ui/skeleton.ts +3 -0
  132. package/.nuxt/ui/slideover.ts +132 -0
  133. package/.nuxt/ui/slider.ts +171 -0
  134. package/.nuxt/ui/stepper.ts +202 -0
  135. package/.nuxt/ui/switch.ts +132 -0
  136. package/.nuxt/ui/table.ts +162 -0
  137. package/.nuxt/ui/tabs.ts +258 -0
  138. package/.nuxt/ui/textarea.ts +294 -0
  139. package/.nuxt/ui/timeline.ts +321 -0
  140. package/.nuxt/ui/toast.ts +74 -0
  141. package/.nuxt/ui/toaster.ts +91 -0
  142. package/.nuxt/ui/tooltip.ts +9 -0
  143. package/.nuxt/ui/tree.ts +168 -0
  144. package/.nuxt/ui/user.ts +101 -0
  145. package/.nuxt/ui-image-component.ts +1 -0
  146. package/.nuxt/ui.css +154 -0
  147. package/app/app.config.ts +7 -0
  148. package/app/app.vue +3 -0
  149. package/app/assets/css/base.css +28 -0
  150. package/app/components/Var.vue +13 -0
  151. package/app/components/layout/LayoutDashboard.vue +102 -0
  152. package/app/components/overlay/modal/ModalActions.vue +25 -0
  153. package/app/components/overlay/modal/ModalConfirm.vue +25 -0
  154. package/app/components/overlay/modal/ModalRadioGroup.vue +37 -0
  155. package/app/components/u/UActions.vue +23 -0
  156. package/app/components/u/UCustomApp.vue +20 -0
  157. package/app/components/u/UField.vue +55 -0
  158. package/app/components/u/UImageWithFallback.vue +35 -0
  159. package/app/components/u/UInputDatePicker.vue +40 -0
  160. package/app/components/u/UInputDurationMinutes.vue +88 -0
  161. package/app/components/u/UTableCard.vue +16 -0
  162. package/app/composables/useConfirm.ts +16 -0
  163. package/app/composables/usePreventPageLeave.ts +31 -0
  164. package/app/composables/useRouteParamString.ts +22 -0
  165. package/app/composables/useTableColumns.tsx +68 -0
  166. package/app/composables/useToast.ts +40 -0
  167. package/app/composables/useTrpc.ts +3 -0
  168. package/app/error.vue +15 -0
  169. package/app/pages/index.vue +3 -0
  170. package/app/utils/form-field-translators.ts +41 -0
  171. package/app/utils/plugins.ts +95 -0
  172. package/app/utils/reactivity.ts +17 -0
  173. package/nuxt.config.ts +85 -0
  174. package/package.json +46 -0
  175. package/pnpm-workspace.yaml +5 -0
  176. 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
+ })
@@ -0,0 +1,3 @@
1
+ export function useTrpc() {
2
+ return useNuxtApp().$trpc
3
+ }
package/app/error.vue ADDED
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+ import type { NuxtError } from '#app'
3
+
4
+ const props = defineProps<{
5
+ error: NuxtError
6
+ }>()
7
+
8
+ console.error(props.error)
9
+ </script>
10
+
11
+ <template>
12
+ <UApp>
13
+ <UError :error />
14
+ </UApp>
15
+ </template>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <NuxtWelcome />
3
+ </template>
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ onlyBuiltDependencies:
2
+ - '@parcel/watcher'
3
+ - esbuild
4
+ - unrs-resolver
5
+ - vue-demi
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "references": [
3
+ {
4
+ "path": "./.nuxt/tsconfig.app.json"
5
+ },
6
+ {
7
+ "path": "./.nuxt/tsconfig.server.json"
8
+ },
9
+ {
10
+ "path": "./.nuxt/tsconfig.shared.json"
11
+ },
12
+ {
13
+ "path": "./.nuxt/tsconfig.node.json"
14
+ }
15
+ ],
16
+ "files": []
17
+ }