@falcondev-oss/nuxt-layers-base 0.33.2 → 0.35.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.
@@ -0,0 +1,89 @@
1
+ // import type { VNode } from 'vue'
2
+ // import { UCard, UDropdownMenu, UField, UForm, UInput } from '#components'
3
+ // import { z } from 'zod'
4
+
5
+ /* eslint-disable ts/no-empty-object-type */
6
+ // @ts-expect-error FIXME: empty emits causes type error
7
+ export default defineSetupComponent((_: { props: { name: string }; emits: {}; slots: {} }) => ({
8
+ props: props(_, ['name']),
9
+ emits: emits(_, []),
10
+ setup: (props) => {
11
+ console.log(props)
12
+ // const form = useForm({
13
+ // schema: z.object({
14
+ // duration: z.number().meta({ title: 'Duration' }),
15
+ // dateIso: z.string().meta({ title: 'Datum' }),
16
+ // text: z
17
+ // .string()
18
+ // .length(8)
19
+ // // .max(8)
20
+ // .meta({
21
+ // title: 'Text',
22
+ // description: 'Beschreibung',
23
+ // default: 'Default Wert',
24
+ // examples: ['Hier könnte ein Beispieltext stehen', '123'],
25
+ // }),
26
+ // }),
27
+ // sourceValues: () => ({
28
+ // dateIso: null,
29
+ // duration: null,
30
+ // text: '',
31
+ // }),
32
+ // async submit({ values }) {
33
+ // await new Promise((resolve) => setTimeout(resolve, 2000))
34
+ // console.log(values)
35
+ // },
36
+ // })
37
+
38
+ return () => (
39
+ // <UCard
40
+ // class="max-w-sm"
41
+ // ui={{
42
+ // body: 'flex flex-col gap-4 items-start ',
43
+ // }}
44
+ // >
45
+ // <UForm form={form} successToast={{ title: 'Success' }} class="flex flex-col gap-4">
46
+ // {form.data}
47
+
48
+ // {/* <UField
49
+ // field={form.fields.text.$use()}
50
+ // error-inline
51
+ // vSlots={vSlots(UField, {
52
+ // default({ bind }) {
53
+ // return <UInput class="w-full" {...bind} />
54
+ // },
55
+ // })}
56
+ // ></UField> */}
57
+ // </UForm>
58
+ // {/* <UForm
59
+ // :form
60
+ // :success-toast="{
61
+ // title: 'test',
62
+ // description: 'wow',
63
+ // }"
64
+ // class="flex flex-col gap-4"
65
+ // >
66
+ // {{ form.data }}
67
+ // <UField v-slot="{ bind, field }" :field="form.fields.text.$use()" error-inline>
68
+ // {{ field.schema }}
69
+ // <UInput class="w-full" v-bind="bind" />
70
+ // </UField>
71
+ // <UField
72
+ // v-slot="{ bind }"
73
+ // :field="
74
+ // form.fields.dateIso.$use({
75
+ // translate: dateValueIsoTranslator(),
76
+ // })
77
+ // "
78
+ // >
79
+ // <UInputDatePicker class="w-full" v-bind="bind" />
80
+ // </UField>
81
+ // <UField v-slot="{ bind }" :field="form.fields.duration.$use()">
82
+ // <UInputDurationMinutes class="w-full" v-bind="bind" />
83
+ // </UField>
84
+ // </UForm> */}
85
+ // </UCard>
86
+ <></>
87
+ )
88
+ },
89
+ }))
@@ -0,0 +1,58 @@
1
+ import { UButton, UCard } from '#components'
2
+ import { setup } from '../../../app/utils/define-setup-component'
3
+
4
+ export type ListItems = { label: string; value: string }[]
5
+
6
+ export default defineSetupComponent(
7
+ <T extends ListItems>(_: {
8
+ props: {
9
+ items: T
10
+ }
11
+ emits: {
12
+ choose: (value: T[number]) => void
13
+ }
14
+ slots: {
15
+ selected: (props: { item: T[number] }) => any
16
+ }
17
+ }) => ({
18
+ props: props(_, ['items']),
19
+ // props: ['items'],
20
+ emits: emits(_, ['choose']),
21
+ // emits: ['choose'],
22
+ setup: setup(_, (props, { emit, slots }) => {
23
+ const selected = ref<T[number]>()
24
+
25
+ return () => (
26
+ <UCard
27
+ class="w-fit"
28
+ vSlots={vSlots(UCard, {
29
+ header: () => [<h1>Select an item</h1>],
30
+ })}
31
+ >
32
+ <div class="flex flex-col gap-2">
33
+ {props.items.map((item) => (
34
+ <UButton
35
+ variant="subtle"
36
+ key={item.value}
37
+ class="rounded bg-gray-200 px-4 py-2 hover:bg-gray-300"
38
+ onClick={() => {
39
+ selected.value = item
40
+ emit('choose', item)
41
+ }}
42
+ vSlots={vSlots(UButton, {
43
+ leading: () => [<>{`[${selected.value?.value === item.value ? 'x' : ' '}] `}</>],
44
+ })}
45
+ >
46
+ {item.label}
47
+ </UButton>
48
+ ))}
49
+
50
+ {slots.selected && selected.value ? (
51
+ <div class="mt-4">{slots.selected({ item: selected.value })}</div>
52
+ ) : null}
53
+ </div>
54
+ </UCard>
55
+ )
56
+ }),
57
+ }),
58
+ )
@@ -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"
@@ -7,6 +7,7 @@ import type {
7
7
  NavigationMenuItem,
8
8
  NavigationMenuProps,
9
9
  } from '@nuxt/ui'
10
+ import { mergeSlotClass } from '~/utils/ui'
10
11
 
11
12
  defineProps<{
12
13
  sidebar?: DashboardSidebarProps
@@ -43,8 +44,8 @@ const config = useRuntimeConfig()
43
44
  collapsible
44
45
  :ui="{
45
46
  ...sidebar?.ui,
46
- header: [sidebar?.ui?.header, 'border-b border-default'],
47
- footer: [sidebar?.ui?.footer, 'border-t border-default'],
47
+ header: mergeSlotClass(sidebar?.ui?.header, 'border-b border-default'),
48
+ footer: mergeSlotClass(sidebar?.ui?.footer, 'border-t border-default'),
48
49
  }"
49
50
  >
50
51
  <template
@@ -4,6 +4,7 @@ import type { FormFieldProps, FormFieldSlots } from '@nuxt/ui'
4
4
  import { createReusableTemplate } from '@vueuse/core'
5
5
  import { useForwardProps } from 'reka-ui'
6
6
  import * as R from 'remeda'
7
+ import { mergeSlotClass } from '~/utils/ui'
7
8
 
8
9
  type InputProps<T> = {
9
10
  'modelValue': T
@@ -99,8 +100,8 @@ const [DefineErrorTemplate, ErrorTemplate] = createReusableTemplate({
99
100
  v-bind="formFieldProps"
100
101
  :ui="{
101
102
  ...formFieldProps.ui,
102
- hint: [formFieldProps.ui?.hint, isOverMaxLength ? 'text-error' : ''],
103
- error: [formFieldProps.ui?.error, 'mt-1!'],
103
+ hint: mergeSlotClass(formFieldProps.ui?.hint, isOverMaxLength ? 'text-error' : ''),
104
+ error: mergeSlotClass(formFieldProps.ui?.error, 'mt-1!'),
104
105
  }"
105
106
  :error="!!field.errors"
106
107
  :class="props.class"
@@ -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,113 @@
1
+ /* eslint-disable ts/no-empty-object-type */
2
+ import type { AllUnionFields, 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 setup<
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
+ // eslint-disable-next-line no-shadow
100
+ >(_opts: Opts, setup: Setup): NoInfer<Setup> {
101
+ return setup
102
+ }
103
+
104
+ // https://github.com/vuejs/language-tools/blob/master/packages/component-type-helpers/index.ts
105
+ type ComponentSlots<T> = T extends new (...args: any) => { $slots: infer S }
106
+ ? NonNullable<S>
107
+ : T extends (props: any, ctx: { slots: infer S; attrs: any; emit: any }, ...args: any) => any
108
+ ? NonNullable<S>
109
+ : {}
110
+
111
+ export function vSlots<C>(component: C, slots: ComponentSlots<C>) {
112
+ return slots
113
+ }
@@ -0,0 +1,13 @@
1
+ import type { SlotClass, SlotClassReplacer } from '@nuxt/ui'
2
+ import type { ClassValue } from 'tailwind-variants'
3
+ import { cnMerge } from 'tailwind-variants'
4
+
5
+ export function mergeSlotClass(ui: SlotClass, extend: ClassValue): SlotClassReplacer {
6
+ return (defaults) => {
7
+ const withBase = cnMerge(defaults, extend)() ?? ''
8
+ if (typeof ui === 'function') {
9
+ return ui(withBase)
10
+ }
11
+ return cnMerge(withBase, ui)() ?? ''
12
+ }
13
+ }
package/eslint.config.js CHANGED
@@ -13,10 +13,6 @@ export default eslintConfig({
13
13
  '.output/',
14
14
  '.temp/',
15
15
  '.data/',
16
- 'drizzle/',
17
- 'prisma/generated/',
18
- 'convex/_generated/',
19
16
  'pnpm-lock.yaml',
20
- '.playground/',
21
17
  ],
22
18
  })
package/nuxt.config.ts CHANGED
@@ -38,6 +38,7 @@ export default defineNuxtConfig({
38
38
  noImplicitOverride: true,
39
39
  noUncheckedIndexedAccess: true,
40
40
  },
41
+ exclude: ['.playground/'],
41
42
  },
42
43
  strict: true,
43
44
  },
package/package.json CHANGED
@@ -1,59 +1,62 @@
1
1
  {
2
2
  "name": "@falcondev-oss/nuxt-layers-base",
3
3
  "type": "module",
4
- "version": "0.33.2",
4
+ "version": "0.35.0",
5
5
  "description": "Nuxt layer with lots of useful helpers and @nuxt/ui components",
6
6
  "license": "MIT",
7
- "repository": "github:falcondev-oss/nuxt-layers",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "github:falcondev-oss/nuxt-layers"
10
+ },
8
11
  "bugs": {
9
12
  "url": "https://github.com/falcondev-oss/nuxt-layers/issues"
10
13
  },
11
14
  "main": "./nuxt.config.ts",
12
15
  "engines": {
13
16
  "node": "24",
14
- "pnpm": "10"
17
+ "pnpm": "11"
15
18
  },
16
19
  "peerDependencies": {
17
- "@falcondev-oss/form-core": ">=0.22.4",
18
- "@falcondev-oss/form-vue": ">=0.22.4",
19
- "@internationalized/date": ">=3.12.0",
20
- "@nuxt/ui": ">=4.6.1"
20
+ "@falcondev-oss/form-core": ">=0.23.5",
21
+ "@falcondev-oss/form-vue": ">=0.23.5",
22
+ "@internationalized/date": ">=3.12.1",
23
+ "@nuxt/ui": ">=4.9.0",
24
+ "reka-ui": ">=2.9.10",
25
+ "tailwind-variants": ">=3.2.2"
21
26
  },
22
27
  "dependencies": {
23
28
  "@falcondev-oss/trpc-typed-form-data": "^0.4.3",
24
- "@falcondev-oss/trpc-vue-query": "^0.5.3",
25
- "@iconify-json/lucide": "^1.2.101",
26
- "@nuxt/icon": "^2.2.1",
27
- "@nuxtjs/color-mode": "^4.0.0",
28
- "@tanstack/vue-query": "^5.97.0",
29
- "@trpc/client": "^11.16.0",
30
- "@trpc/server": "^11.16.0",
31
- "@vue/devtools-api": "^8.1.1",
32
- "@vueuse/core": "^14.2.1",
33
- "@vueuse/nuxt": "^14.2.1",
34
- "@vueuse/router": "^14.2.1",
29
+ "@falcondev-oss/trpc-vue-query": "^0.5.4",
30
+ "@iconify-json/lucide": "^1.2.114",
31
+ "@nuxt/icon": "^2.2.3",
32
+ "@nuxtjs/color-mode": "^4.0.1",
33
+ "@tanstack/vue-query": "^5.101.0",
34
+ "@trpc/client": "^11.18.0",
35
+ "@trpc/server": "^11.18.0",
36
+ "@vue/devtools-api": "^8.1.3",
37
+ "@vueuse/core": "^14.3.0",
38
+ "@vueuse/nuxt": "^14.3.0",
39
+ "@vueuse/router": "^14.3.0",
35
40
  "arkregex": "^0.0.5",
36
41
  "consola": "^3.4.2",
37
42
  "defu": "^6.1.7",
38
43
  "maska": "^3.2.0",
39
- "reka-ui": "^2.9.5",
40
- "remeda": "^2.33.7",
44
+ "remeda": "^2.39.0",
41
45
  "superjson": "^2.2.6",
42
- "tailwindcss": "^4.2.2",
43
- "trpc-nuxt": "^2.0.2",
44
- "type-fest": "^5.5.0",
45
- "vue": "^3.5.32",
46
- "vue-router": "^5.0.4"
46
+ "tailwindcss": "^4.3.1",
47
+ "trpc-nuxt": "^2.1.1",
48
+ "type-fest": "^5.7.0",
49
+ "vue": "^3.5.38",
50
+ "vue-router": "^5.1.0"
47
51
  },
48
52
  "devDependencies": {
49
- "nuxt": "^4.4.2",
53
+ "nuxt": "^4.4.8",
50
54
  "typescript": "^5.9.3",
51
- "vue-tsc": "^3.2.6",
52
- "zod": "^4.3.6"
55
+ "vue-tsc": "^3.3.5",
56
+ "zod": "^4.4.3"
53
57
  },
54
58
  "scripts": {
55
59
  "dev": "nuxi dev .playground",
56
- "dev:prepare": "nuxt prepare .playground",
57
60
  "type-check": "vue-tsc --build",
58
61
  "lint": "pnpm -w eslint:cmd \"$PWD\" && pnpm -w prettier:cmd \"$PWD\" --check",
59
62
  "lint:fix": "pnpm -w eslint:cmd \"$PWD\" --fix && pnpm -w prettier:cmd \"$PWD\" --write"