@falcondev-oss/nuxt-layers-base 0.33.2 → 0.34.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/.playground/app/components/FormCard.tsx +100 -0
- package/.playground/app/components/Select.tsx +57 -0
- package/.playground/app/pages/index.vue +14 -0
- package/app/components/layout/LayoutPage.vue +9 -0
- package/app/utils/define-setup-component-old.ts +55 -0
- package/app/utils/define-setup-component.ts +112 -0
- package/eslint.config.js +0 -4
- package/nuxt.config.ts +1 -0
- package/package.json +11 -11
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { VNode } from 'vue'
|
|
2
|
+
import { UCard, UDropdownMenu, UField, UForm, UInput } from '#components'
|
|
3
|
+
import { Component } from 'vue'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
// https://github.com/vuejs/language-tools/blob/master/packages/component-type-helpers/index.ts
|
|
7
|
+
type ComponentSlots<T> = T extends new (...args: any) => { $slots: infer S }
|
|
8
|
+
? NonNullable<S>
|
|
9
|
+
: T extends (props: any, ctx: { slots: infer S; attrs: any; emit: any }, ...args: any) => any
|
|
10
|
+
? NonNullable<S>
|
|
11
|
+
: {}
|
|
12
|
+
|
|
13
|
+
type ComponentProps<T> = T extends new (...args: any) => { $props: infer P }
|
|
14
|
+
? NonNullable<P>
|
|
15
|
+
: T extends (props: infer P, ...args: any) => any
|
|
16
|
+
? P
|
|
17
|
+
: {}
|
|
18
|
+
|
|
19
|
+
/* eslint-disable ts/no-empty-object-type */
|
|
20
|
+
export default defineSetupComponent((_: { props: {}; emits: {}; slots: {} }) => ({
|
|
21
|
+
props: [],
|
|
22
|
+
emits: [],
|
|
23
|
+
setup: () => {
|
|
24
|
+
const form = useForm({
|
|
25
|
+
schema: z.object({
|
|
26
|
+
duration: z.number().meta({ title: 'Duration' }),
|
|
27
|
+
dateIso: z.string().meta({ title: 'Datum' }),
|
|
28
|
+
text: z
|
|
29
|
+
.string()
|
|
30
|
+
.length(8)
|
|
31
|
+
// .max(8)
|
|
32
|
+
.meta({
|
|
33
|
+
title: 'Text',
|
|
34
|
+
description: 'Beschreibung',
|
|
35
|
+
default: 'Default Wert',
|
|
36
|
+
examples: ['Hier könnte ein Beispieltext stehen', '123'],
|
|
37
|
+
}),
|
|
38
|
+
}),
|
|
39
|
+
sourceValues: () => ({
|
|
40
|
+
dateIso: null,
|
|
41
|
+
duration: null,
|
|
42
|
+
text: '',
|
|
43
|
+
}),
|
|
44
|
+
async submit({ values }) {
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
46
|
+
console.log(values)
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return () => (
|
|
51
|
+
<UCard
|
|
52
|
+
class="max-w-sm"
|
|
53
|
+
ui={{
|
|
54
|
+
body: 'flex flex-col gap-4 items-start ',
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<UForm form={form} successToast={{ title: 'Success' }} class="flex flex-col gap-4">
|
|
58
|
+
{form.data}
|
|
59
|
+
|
|
60
|
+
{/* <UField
|
|
61
|
+
field={form.fields.text.$use()}
|
|
62
|
+
error-inline
|
|
63
|
+
vSlots={vSlots(UField, {
|
|
64
|
+
default({ bind }) {
|
|
65
|
+
return <UInput class="w-full" {...bind} />
|
|
66
|
+
},
|
|
67
|
+
})}
|
|
68
|
+
></UField> */}
|
|
69
|
+
</UForm>
|
|
70
|
+
{/* <UForm
|
|
71
|
+
:form
|
|
72
|
+
:success-toast="{
|
|
73
|
+
title: 'test',
|
|
74
|
+
description: 'wow',
|
|
75
|
+
}"
|
|
76
|
+
class="flex flex-col gap-4"
|
|
77
|
+
>
|
|
78
|
+
{{ form.data }}
|
|
79
|
+
<UField v-slot="{ bind, field }" :field="form.fields.text.$use()" error-inline>
|
|
80
|
+
{{ field.schema }}
|
|
81
|
+
<UInput class="w-full" v-bind="bind" />
|
|
82
|
+
</UField>
|
|
83
|
+
<UField
|
|
84
|
+
v-slot="{ bind }"
|
|
85
|
+
:field="
|
|
86
|
+
form.fields.dateIso.$use({
|
|
87
|
+
translate: dateValueIsoTranslator(),
|
|
88
|
+
})
|
|
89
|
+
"
|
|
90
|
+
>
|
|
91
|
+
<UInputDatePicker class="w-full" v-bind="bind" />
|
|
92
|
+
</UField>
|
|
93
|
+
<UField v-slot="{ bind }" :field="form.fields.duration.$use()">
|
|
94
|
+
<UInputDurationMinutes class="w-full" v-bind="bind" />
|
|
95
|
+
</UField>
|
|
96
|
+
</UForm> */}
|
|
97
|
+
</UCard>
|
|
98
|
+
)
|
|
99
|
+
},
|
|
100
|
+
}))
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { UButton, UCard } from '#components'
|
|
2
|
+
|
|
3
|
+
export type ListItems = { label: string; value: string }[]
|
|
4
|
+
|
|
5
|
+
export default defineSetupComponent(
|
|
6
|
+
<T extends ListItems>(_: {
|
|
7
|
+
props: {
|
|
8
|
+
items: T
|
|
9
|
+
}
|
|
10
|
+
emits: {
|
|
11
|
+
choose: (value: T[number]) => void
|
|
12
|
+
}
|
|
13
|
+
slots: {
|
|
14
|
+
selected: (props: { item: T[number] }) => any
|
|
15
|
+
}
|
|
16
|
+
}) => ({
|
|
17
|
+
props: props(_, ['items']),
|
|
18
|
+
// props: ['items'],
|
|
19
|
+
emits: emits(_, ['choose']),
|
|
20
|
+
// emits: ['choose'],
|
|
21
|
+
setup: generic(_, (props, { emit, slots }) => {
|
|
22
|
+
const selected = ref<T[number]>()
|
|
23
|
+
|
|
24
|
+
return () => (
|
|
25
|
+
<UCard
|
|
26
|
+
class="w-fit"
|
|
27
|
+
vSlots={vSlots(UCard, {
|
|
28
|
+
header: () => <h1>Select an item</h1>,
|
|
29
|
+
})}
|
|
30
|
+
>
|
|
31
|
+
<div class="flex flex-col gap-2">
|
|
32
|
+
{props.items.map((item) => (
|
|
33
|
+
<UButton
|
|
34
|
+
variant="subtle"
|
|
35
|
+
key={item.value}
|
|
36
|
+
class="rounded bg-gray-200 px-4 py-2 hover:bg-gray-300"
|
|
37
|
+
onClick={() => {
|
|
38
|
+
selected.value = item
|
|
39
|
+
emit('choose', item)
|
|
40
|
+
}}
|
|
41
|
+
vSlots={vSlots(UButton, {
|
|
42
|
+
leading: () => `[${selected.value?.value === item.value ? 'x' : ' '}] `,
|
|
43
|
+
})}
|
|
44
|
+
>
|
|
45
|
+
{item.label}
|
|
46
|
+
</UButton>
|
|
47
|
+
))}
|
|
48
|
+
|
|
49
|
+
{slots.selected && selected.value ? (
|
|
50
|
+
<div class="mt-4">{slots.selected({ item: selected.value })}</div>
|
|
51
|
+
) : null}
|
|
52
|
+
</div>
|
|
53
|
+
</UCard>
|
|
54
|
+
)
|
|
55
|
+
}),
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
@@ -278,6 +278,20 @@ const columns = useTableColumns<typeof data>(
|
|
|
278
278
|
</UField>
|
|
279
279
|
</UForm>
|
|
280
280
|
</UCard>
|
|
281
|
+
<Select
|
|
282
|
+
:items="[
|
|
283
|
+
{ label: 'One', value: '1' },
|
|
284
|
+
{ label: 'Two', value: '2' },
|
|
285
|
+
]"
|
|
286
|
+
@choose="console.warn"
|
|
287
|
+
>
|
|
288
|
+
<template #selected="{ item }">
|
|
289
|
+
<div class="flex items-center gap-2">
|
|
290
|
+
<span>Selected:</span>
|
|
291
|
+
<span>{{ item.label }}</span>
|
|
292
|
+
</div>
|
|
293
|
+
</template>
|
|
294
|
+
</Select>
|
|
281
295
|
</LayoutNavbar>
|
|
282
296
|
</LayoutSidebar>
|
|
283
297
|
</template>
|
|
@@ -20,6 +20,7 @@ defineProps<{
|
|
|
20
20
|
navigation?: NavigationMenuProps
|
|
21
21
|
actions?: ButtonProps[]
|
|
22
22
|
ui?: HeaderProps['ui']
|
|
23
|
+
mobileActions?: ButtonProps[]
|
|
23
24
|
}
|
|
24
25
|
footer?: {
|
|
25
26
|
items?: NavigationMenuItem[]
|
|
@@ -96,6 +97,14 @@ const footerSlots = computed(() =>
|
|
|
96
97
|
|
|
97
98
|
<template v-if="header.actions || slots['header-right']" #right>
|
|
98
99
|
<slot name="header-right">
|
|
100
|
+
<UActions
|
|
101
|
+
v-if="header.mobileActions"
|
|
102
|
+
class="md:hidden"
|
|
103
|
+
:actions="header.mobileActions"
|
|
104
|
+
:defaults="{
|
|
105
|
+
variant: 'subtle',
|
|
106
|
+
}"
|
|
107
|
+
/>
|
|
99
108
|
<UActions
|
|
100
109
|
v-if="header.actions"
|
|
101
110
|
class="max-sm:hidden"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* eslint-disable ts/no-empty-object-type */
|
|
2
|
+
import type { AllUnionFields, UnionToTuple } from 'type-fest'
|
|
3
|
+
import type {
|
|
4
|
+
ComponentOptionsMixin,
|
|
5
|
+
CreateComponentPublicInstanceWithMixins,
|
|
6
|
+
EmitsOptions,
|
|
7
|
+
EmitsToProps,
|
|
8
|
+
PublicProps,
|
|
9
|
+
RenderFunction,
|
|
10
|
+
SetupContext,
|
|
11
|
+
SlotsType,
|
|
12
|
+
} from 'vue'
|
|
13
|
+
|
|
14
|
+
export function defineSetupComponentOld<
|
|
15
|
+
const Opts extends {
|
|
16
|
+
props: Record<string, any>
|
|
17
|
+
emits: Record<string, any>
|
|
18
|
+
slots: Record<string, any>
|
|
19
|
+
},
|
|
20
|
+
Setup extends (
|
|
21
|
+
props: Props,
|
|
22
|
+
ctx: SetupContext<Opts['emits'], SlotsType<Partial<Opts['slots']>>>,
|
|
23
|
+
) => RenderFunction | Promise<RenderFunction>,
|
|
24
|
+
const PropsRuntime extends Readonly<UnionToTuple<keyof AllUnionFields<Opts['props']>>>,
|
|
25
|
+
E extends EmitsOptions = Opts['emits'],
|
|
26
|
+
Props extends Record<string, any> = Opts['props'] & EmitsToProps<E>,
|
|
27
|
+
PP = PublicProps & { vSlots?: Opts['slots'] },
|
|
28
|
+
S extends SlotsType<Partial<Opts['slots']>> = SlotsType<Partial<Opts['slots']>>,
|
|
29
|
+
>(
|
|
30
|
+
define: (opts: Opts) => { props: PropsRuntime },
|
|
31
|
+
setup: Setup,
|
|
32
|
+
): new (
|
|
33
|
+
props: Opts['props'],
|
|
34
|
+
) => CreateComponentPublicInstanceWithMixins<
|
|
35
|
+
Props,
|
|
36
|
+
{},
|
|
37
|
+
{},
|
|
38
|
+
{},
|
|
39
|
+
{},
|
|
40
|
+
ComponentOptionsMixin,
|
|
41
|
+
ComponentOptionsMixin,
|
|
42
|
+
E,
|
|
43
|
+
PP,
|
|
44
|
+
{},
|
|
45
|
+
false,
|
|
46
|
+
{},
|
|
47
|
+
S
|
|
48
|
+
> {
|
|
49
|
+
// eslint-disable-next-line ts/no-unsafe-argument
|
|
50
|
+
const opts = define({} as any)
|
|
51
|
+
// eslint-disable-next-line ts/no-unsafe-return
|
|
52
|
+
return defineComponent(setup, {
|
|
53
|
+
props: opts.props as unknown as string[],
|
|
54
|
+
}) as any
|
|
55
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* eslint-disable ts/no-empty-object-type */
|
|
2
|
+
import type { AllUnionFields, Schema, UnionToTuple } from 'type-fest'
|
|
3
|
+
import type {
|
|
4
|
+
ComponentOptionsMixin,
|
|
5
|
+
CreateComponentPublicInstanceWithMixins,
|
|
6
|
+
EmitsToProps,
|
|
7
|
+
ObjectEmitsOptions,
|
|
8
|
+
PublicProps,
|
|
9
|
+
RenderFunction,
|
|
10
|
+
SetupContext,
|
|
11
|
+
Slots as SlotOptions,
|
|
12
|
+
SlotsType,
|
|
13
|
+
} from 'vue'
|
|
14
|
+
|
|
15
|
+
declare module 'vue' {
|
|
16
|
+
interface ComponentCustomProps {
|
|
17
|
+
vSlots?: SlotOptions
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function defineSetupComponent<
|
|
22
|
+
const Opts extends {
|
|
23
|
+
props: Record<string, any>
|
|
24
|
+
emits: ObjectEmitsOptions
|
|
25
|
+
slots: Record<string, any>
|
|
26
|
+
},
|
|
27
|
+
Slots extends SlotsType<Partial<Opts['slots']>>,
|
|
28
|
+
Props extends Opts['props'] & EmitsToProps<Opts['emits']>,
|
|
29
|
+
Setup extends (
|
|
30
|
+
props: Props,
|
|
31
|
+
ctx: SetupContext<Opts['emits'], Slots>,
|
|
32
|
+
) => RenderFunction | Promise<RenderFunction>,
|
|
33
|
+
const RuntimeProps extends Readonly<UnionToTuple<keyof AllUnionFields<Opts['props']>>>,
|
|
34
|
+
const RuntimeEmits extends Readonly<UnionToTuple<keyof AllUnionFields<Opts['emits']>>>,
|
|
35
|
+
>(
|
|
36
|
+
define: (opts: Opts) => {
|
|
37
|
+
props: NoInfer<RuntimeProps>
|
|
38
|
+
emits: NoInfer<RuntimeEmits>
|
|
39
|
+
setup: NoInfer<Setup>
|
|
40
|
+
},
|
|
41
|
+
): new (
|
|
42
|
+
props: Opts['props'],
|
|
43
|
+
) => CreateComponentPublicInstanceWithMixins<
|
|
44
|
+
Props,
|
|
45
|
+
{},
|
|
46
|
+
{},
|
|
47
|
+
{},
|
|
48
|
+
{},
|
|
49
|
+
ComponentOptionsMixin,
|
|
50
|
+
ComponentOptionsMixin,
|
|
51
|
+
Opts['emits'],
|
|
52
|
+
PublicProps & { vSlots?: Opts['slots'] },
|
|
53
|
+
{},
|
|
54
|
+
false,
|
|
55
|
+
{},
|
|
56
|
+
Slots
|
|
57
|
+
> {
|
|
58
|
+
// eslint-disable-next-line ts/no-unsafe-argument
|
|
59
|
+
const opts = define({} as any)
|
|
60
|
+
// eslint-disable-next-line ts/no-unsafe-return, ts/no-unsafe-argument
|
|
61
|
+
return defineComponent(opts.setup as any, {
|
|
62
|
+
props: opts.props as unknown as string[],
|
|
63
|
+
emits: opts.emits as unknown as string[],
|
|
64
|
+
}) as any
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function props<
|
|
68
|
+
const Opts extends {
|
|
69
|
+
props: Record<string, any>
|
|
70
|
+
},
|
|
71
|
+
const RuntimeProps extends UnionToTuple<keyof AllUnionFields<Opts['props']>>,
|
|
72
|
+
// eslint-disable-next-line no-shadow
|
|
73
|
+
>(_opts: Opts, props: NoInfer<RuntimeProps>): NoInfer<RuntimeProps> {
|
|
74
|
+
return props
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function emits<
|
|
78
|
+
const Opts extends {
|
|
79
|
+
emits: ObjectEmitsOptions
|
|
80
|
+
},
|
|
81
|
+
const RuntimeEmits extends UnionToTuple<keyof AllUnionFields<Opts['emits']>>,
|
|
82
|
+
// eslint-disable-next-line no-shadow
|
|
83
|
+
>(_opts: Opts, emits: NoInfer<RuntimeEmits>): NoInfer<RuntimeEmits> {
|
|
84
|
+
return emits
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function generic<
|
|
88
|
+
const Opts extends {
|
|
89
|
+
props: Record<string, any>
|
|
90
|
+
emits: ObjectEmitsOptions
|
|
91
|
+
slots: Record<string, any>
|
|
92
|
+
},
|
|
93
|
+
Slots extends SlotsType<Partial<Opts['slots']>>,
|
|
94
|
+
Props extends Opts['props'] & EmitsToProps<Opts['emits']>,
|
|
95
|
+
Setup extends (
|
|
96
|
+
props: Props,
|
|
97
|
+
ctx: SetupContext<Opts['emits'], Slots>,
|
|
98
|
+
) => RenderFunction | Promise<RenderFunction>,
|
|
99
|
+
>(_opts: Opts, setup: Setup): NoInfer<Setup> {
|
|
100
|
+
return setup
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// https://github.com/vuejs/language-tools/blob/master/packages/component-type-helpers/index.ts
|
|
104
|
+
type ComponentSlots<T> = T extends new (...args: any) => { $slots: infer S }
|
|
105
|
+
? NonNullable<S>
|
|
106
|
+
: T extends (props: any, ctx: { slots: infer S; attrs: any; emit: any }, ...args: any) => any
|
|
107
|
+
? NonNullable<S>
|
|
108
|
+
: {}
|
|
109
|
+
|
|
110
|
+
export function vSlots<C>(component: C, slots: ComponentSlots<C>) {
|
|
111
|
+
return slots
|
|
112
|
+
}
|
package/eslint.config.js
CHANGED
package/nuxt.config.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@falcondev-oss/nuxt-layers-base",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.34.0",
|
|
5
5
|
"description": "Nuxt layer with lots of useful helpers and @nuxt/ui components",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:falcondev-oss/nuxt-layers",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"main": "./nuxt.config.ts",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": "24",
|
|
14
|
-
"pnpm": "
|
|
14
|
+
"pnpm": "11"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"@falcondev-oss/form-core": ">=0.22.4",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@falcondev-oss/trpc-typed-form-data": "^0.4.3",
|
|
24
|
-
"@falcondev-oss/trpc-vue-query": "^0.5.
|
|
25
|
-
"@iconify-json/lucide": "^1.2.
|
|
24
|
+
"@falcondev-oss/trpc-vue-query": "^0.5.4",
|
|
25
|
+
"@iconify-json/lucide": "^1.2.103",
|
|
26
26
|
"@nuxt/icon": "^2.2.1",
|
|
27
27
|
"@nuxtjs/color-mode": "^4.0.0",
|
|
28
|
-
"@tanstack/vue-query": "^5.
|
|
28
|
+
"@tanstack/vue-query": "^5.100.1",
|
|
29
29
|
"@trpc/client": "^11.16.0",
|
|
30
30
|
"@trpc/server": "^11.16.0",
|
|
31
31
|
"@vue/devtools-api": "^8.1.1",
|
|
@@ -36,19 +36,19 @@
|
|
|
36
36
|
"consola": "^3.4.2",
|
|
37
37
|
"defu": "^6.1.7",
|
|
38
38
|
"maska": "^3.2.0",
|
|
39
|
-
"reka-ui": "^2.9.
|
|
39
|
+
"reka-ui": "^2.9.6",
|
|
40
40
|
"remeda": "^2.33.7",
|
|
41
41
|
"superjson": "^2.2.6",
|
|
42
|
-
"tailwindcss": "^4.2.
|
|
42
|
+
"tailwindcss": "^4.2.4",
|
|
43
43
|
"trpc-nuxt": "^2.0.2",
|
|
44
|
-
"type-fest": "^5.
|
|
45
|
-
"vue": "^3.5.
|
|
46
|
-
"vue-router": "^5.0.
|
|
44
|
+
"type-fest": "^5.6.0",
|
|
45
|
+
"vue": "^3.5.33",
|
|
46
|
+
"vue-router": "^5.0.6"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"nuxt": "^4.4.2",
|
|
50
50
|
"typescript": "^5.9.3",
|
|
51
|
-
"vue-tsc": "^3.2.
|
|
51
|
+
"vue-tsc": "^3.2.7",
|
|
52
52
|
"zod": "^4.3.6"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|