@falcondev-oss/nuxt-layers-base 0.21.0 → 0.22.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.
@@ -7,12 +7,14 @@ const overlay = useOverlay()
7
7
 
8
8
  const form = useForm({
9
9
  schema: z.object({
10
- duration: z.number(),
11
- dateIso: z.string(),
10
+ duration: z.number().meta({ title: 'Duration' }),
11
+ dateIso: z.string().meta({ title: 'Datum' }),
12
+ text: z.string().max(10).meta({ title: 'Text' }).optional(),
12
13
  }),
13
14
  sourceValues: () => ({
14
15
  dateIso: null,
15
16
  duration: null,
17
+ text: '',
16
18
  }),
17
19
  async submit({ values }) {
18
20
  await new Promise((resolve) => setTimeout(resolve, 2000))
@@ -230,6 +232,7 @@ const columns = useTableColumns<typeof data>(
230
232
  />
231
233
  </UCard>
232
234
  <UCard
235
+ class="max-w-sm"
233
236
  :ui="{
234
237
  body: 'flex flex-col gap-4 items-start ',
235
238
  }"
@@ -240,20 +243,24 @@ const columns = useTableColumns<typeof data>(
240
243
  title: 'test',
241
244
  description: 'wow',
242
245
  }"
246
+ class="flex flex-col gap-4"
243
247
  >
244
248
  {{ form.data }}
249
+ <UField v-slot="{ props }" :field="form.fields.text.$use()">
250
+ <UInput class="w-full" v-bind="props" />
251
+ </UField>
245
252
  <UField
246
- v-slot="props"
253
+ v-slot="{ props }"
247
254
  :field="
248
255
  form.fields.dateIso.$use({
249
256
  translate: dateValueIsoTranslator(),
250
257
  })
251
258
  "
252
259
  >
253
- <UInputDatePicker v-bind="props" />
260
+ <UInputDatePicker class="w-full" v-bind="props" />
254
261
  </UField>
255
- <UField v-slot="props" :field="form.fields.duration.$use()">
256
- <UInputDurationMinutes v-bind="props" />
262
+ <UField v-slot="{ props }" :field="form.fields.duration.$use()">
263
+ <UInputDurationMinutes class="w-full" v-bind="props" />
257
264
  </UField>
258
265
  </UForm>
259
266
  </UCard>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { de } from '@nuxt/ui/locale'
3
+ import { Settings } from 'luxon'
4
+ import { authRedirect } from './middleware/auth.global'
5
+ import { channel } from './utils/channel'
6
+
7
+ Settings.throwOnInvalid = true
8
+ Settings.defaultLocale = 'de'
9
+
10
+ // redirect when auth status changes through broadcast channel
11
+ const route = useRoute()
12
+ useEventListener<MessageEvent>(channel, 'message', (event) => {
13
+ if (event.data.type === 'auth') {
14
+ authRedirect(route)
15
+ }
16
+ })
17
+ </script>
18
+
19
+ <template>
20
+ <UCustomApp
21
+ :app="{
22
+ locale: de,
23
+ }"
24
+ />
25
+ </template>
@@ -3,7 +3,7 @@ import type { FormField } from '@falcondev-oss/form-core'
3
3
  import type { FormFieldProps, FormFieldSlots } from '@nuxt/ui'
4
4
  import type { ModelModifiers } from '@nuxt/ui/runtime/types/input.js'
5
5
  import { useForwardProps } from 'reka-ui'
6
- import { omit } from 'remeda'
6
+ import * as R from 'remeda'
7
7
 
8
8
  type FieldSlotProps<T> = {
9
9
  'modelValue': T
@@ -12,6 +12,7 @@ type FieldSlotProps<T> = {
12
12
  'disabled': boolean
13
13
  'loading': boolean
14
14
  'modelModifiers'?: Pick<ModelModifiers, 'nullable'>
15
+ 'placeholder'?: string
15
16
  }
16
17
 
17
18
  const props = defineProps<
@@ -21,46 +22,106 @@ const props = defineProps<
21
22
  >()
22
23
  const slots = defineSlots<
23
24
  {
24
- default: (props: Omit<FieldSlotProps<T>, 'modelModifiers'>) => any
25
+ default: (slot: {
26
+ props: Omit<FieldSlotProps<T>, 'modelModifiers'>
27
+ field: FormField<T>
28
+ }) => any
25
29
  } & Omit<FormFieldSlots, 'default'>
26
30
  >()
27
31
 
28
32
  const forwardedProps = useForwardProps(props)
29
33
 
30
- const formFieldProps = computed(() => {
34
+ const isOverMaxLength = computed(() => {
35
+ const field = forwardedProps.value.field
36
+
37
+ return field.schema.maxLength === undefined || field.value == null
38
+ ? false
39
+ : (field.value as string | number)?.toString().length > field.schema.maxLength
40
+ })
41
+
42
+ const formFieldProps = computed<FormFieldProps>(() => {
31
43
  const { field, ...rest } = forwardedProps.value
32
- return rest
44
+
45
+ const hint =
46
+ field.schema.maxLength === undefined
47
+ ? undefined
48
+ : `${(field.value as string | number | null)?.toString().length ?? 0}/${field.schema.maxLength}`
49
+
50
+ return {
51
+ required: field.schema.required,
52
+ label: field.schema.title,
53
+ description: field.schema.description,
54
+ hint,
55
+ ...R.omitBy(rest, (v) => v === undefined),
56
+ }
33
57
  })
34
58
 
35
- const slotProps = computed(
36
- () =>
37
- ({
38
- 'modelValue': forwardedProps.value.field.value,
39
- 'onUpdate:modelValue': (value) => forwardedProps.value.field.handleChange(value),
40
- 'onBlur': () => forwardedProps.value.field.handleBlur(),
41
- 'disabled': forwardedProps.value.field.disabled,
42
- 'loading': forwardedProps.value.field.isPending,
43
- 'modelModifiers': {
44
- nullable: true,
45
- },
46
- }) satisfies FieldSlotProps<T>,
47
- )
59
+ const inputProps = computed(() => {
60
+ const field = forwardedProps.value.field
61
+
62
+ const placeholder = field.errors && field.errors.join('\n')
63
+
64
+ return {
65
+ 'modelValue': field.value,
66
+ 'onUpdate:modelValue': (value) => field.handleChange(value),
67
+ 'onBlur': () => field.handleBlur(),
68
+ 'disabled': field.disabled,
69
+ 'loading': field.isPending,
70
+ 'modelModifiers': {
71
+ nullable: true,
72
+ },
73
+ placeholder,
74
+ } satisfies FieldSlotProps<T>
75
+ })
48
76
  </script>
49
77
 
50
78
  <template>
51
79
  <UFormField
52
80
  v-bind="formFieldProps"
53
- :error="forwardedProps.field.errors && forwardedProps.field.errors.join('\n')"
81
+ :ui="{
82
+ hint: isOverMaxLength ? 'text-error' : '',
83
+ }"
84
+ :error="!!field.errors"
54
85
  >
55
- <slot v-bind="slotProps">
86
+ <template #hint="{ hint }">
87
+ <span class="flex items-center gap-1.5">
88
+ <UPopover
89
+ v-if="!!field.errors"
90
+ mode="hover"
91
+ :delay-duration="0"
92
+ :ui="{
93
+ content: 'bg-error-50 ring-error-200! rounded py-1 px-2',
94
+ }"
95
+ >
96
+ <UIcon name="lucide:circle-alert" class="text-error" />
97
+ <template #content>
98
+ <p class="text-(--ui-color-neutral-800) max-w-sm whitespace-normal text-xs">
99
+ {{ field.errors.join('\n') }}
100
+ </p>
101
+ </template>
102
+ </UPopover>
103
+
104
+ {{ hint }}
105
+ </span>
106
+ </template>
107
+
108
+ <slot v-bind="{ props: inputProps, field: forwardedProps.field }">
56
109
  <DevOnly>
57
110
  <p class="font-black text-red-500">UField missing slot</p>
58
111
  </DevOnly>
59
112
  </slot>
60
113
 
61
- <template v-for="(_, name) in omit(slots, ['default'])" #[name]="slotData">
114
+ <template v-for="(_, name) in R.omit(slots, ['default'])" #[name]="slotData">
62
115
  <!-- @vue-ignore -->
63
116
  <slot :name="name" v-bind="slotData || {}" />
64
117
  </template>
65
118
  </UFormField>
66
119
  </template>
120
+
121
+ <style scoped>
122
+ :deep([aria-invalid='true']) {
123
+ &::placeholder {
124
+ color: var(--ui-color-error-400);
125
+ }
126
+ }
127
+ </style>
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.21.0",
4
+ "version": "0.22.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",
@@ -14,8 +14,8 @@
14
14
  "pnpm": "10"
15
15
  },
16
16
  "dependencies": {
17
- "@falcondev-oss/form-core": "^0.19.3",
18
- "@falcondev-oss/form-vue": "^0.19.3",
17
+ "@falcondev-oss/form-core": "^0.21.0",
18
+ "@falcondev-oss/form-vue": "^0.21.0",
19
19
  "@falcondev-oss/trpc-typed-form-data": "^0.4.1",
20
20
  "@falcondev-oss/trpc-vue-query": "^0.5.2",
21
21
  "@iconify-json/lucide": "^1.2.90",