@bitrix24/b24ui-nuxt 0.1.1

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 (159) hide show
  1. package/.nuxt/b24ui/advice.ts +52 -0
  2. package/.nuxt/b24ui/alert.ts +118 -0
  3. package/.nuxt/b24ui/avatar-group.ts +52 -0
  4. package/.nuxt/b24ui/avatar.ts +63 -0
  5. package/.nuxt/b24ui/badge.ts +256 -0
  6. package/.nuxt/b24ui/button-group.ts +27 -0
  7. package/.nuxt/b24ui/button.ts +342 -0
  8. package/.nuxt/b24ui/checkbox.ts +128 -0
  9. package/.nuxt/b24ui/chip.ts +205 -0
  10. package/.nuxt/b24ui/container.ts +3 -0
  11. package/.nuxt/b24ui/content/description-list.ts +62 -0
  12. package/.nuxt/b24ui/countdown.ts +94 -0
  13. package/.nuxt/b24ui/form-field.ts +57 -0
  14. package/.nuxt/b24ui/form.ts +3 -0
  15. package/.nuxt/b24ui/index.ts +28 -0
  16. package/.nuxt/b24ui/input.ts +472 -0
  17. package/.nuxt/b24ui/kbd.ts +31 -0
  18. package/.nuxt/b24ui/link.ts +20 -0
  19. package/.nuxt/b24ui/progress.ts +303 -0
  20. package/.nuxt/b24ui/radio-group.ts +135 -0
  21. package/.nuxt/b24ui/range.ts +172 -0
  22. package/.nuxt/b24ui/select.ts +550 -0
  23. package/.nuxt/b24ui/separator.ts +176 -0
  24. package/.nuxt/b24ui/skeleton.ts +3 -0
  25. package/.nuxt/b24ui/switch.ts +134 -0
  26. package/.nuxt/b24ui/tabs.ts +341 -0
  27. package/.nuxt/b24ui/textarea.ts +332 -0
  28. package/.nuxt/b24ui/toast.ts +89 -0
  29. package/.nuxt/b24ui/toaster.ts +91 -0
  30. package/.nuxt/b24ui/tooltip.ts +16 -0
  31. package/LICENSE +21 -0
  32. package/README.md +101 -0
  33. package/cli/commands/make/component.mjs +95 -0
  34. package/cli/commands/make/index.mjs +14 -0
  35. package/cli/commands/make/locale.mjs +64 -0
  36. package/cli/index.mjs +15 -0
  37. package/cli/package.json +13 -0
  38. package/cli/templates.mjs +240 -0
  39. package/cli/utils.mjs +31 -0
  40. package/dist/meta.cjs +23610 -0
  41. package/dist/meta.d.cts +23608 -0
  42. package/dist/meta.d.mts +23608 -0
  43. package/dist/meta.d.ts +23608 -0
  44. package/dist/meta.mjs +23608 -0
  45. package/dist/module.cjs +54 -0
  46. package/dist/module.d.cts +14 -0
  47. package/dist/module.d.mts +14 -0
  48. package/dist/module.d.ts +14 -0
  49. package/dist/module.json +13 -0
  50. package/dist/module.mjs +51 -0
  51. package/dist/runtime/components/Advice.vue +115 -0
  52. package/dist/runtime/components/Alert.vue +136 -0
  53. package/dist/runtime/components/App.vue +52 -0
  54. package/dist/runtime/components/Avatar.vue +118 -0
  55. package/dist/runtime/components/AvatarGroup.vue +99 -0
  56. package/dist/runtime/components/Badge.vue +114 -0
  57. package/dist/runtime/components/Button.vue +177 -0
  58. package/dist/runtime/components/ButtonGroup.vue +58 -0
  59. package/dist/runtime/components/Checkbox.vue +110 -0
  60. package/dist/runtime/components/Chip.vue +81 -0
  61. package/dist/runtime/components/Container.vue +36 -0
  62. package/dist/runtime/components/Countdown.vue +498 -0
  63. package/dist/runtime/components/Form.vue +271 -0
  64. package/dist/runtime/components/FormField.vue +128 -0
  65. package/dist/runtime/components/Input.vue +224 -0
  66. package/dist/runtime/components/Kbd.vue +50 -0
  67. package/dist/runtime/components/Link.vue +219 -0
  68. package/dist/runtime/components/LinkBase.vue +63 -0
  69. package/dist/runtime/components/Progress.vue +182 -0
  70. package/dist/runtime/components/RadioGroup.vue +178 -0
  71. package/dist/runtime/components/Range.vue +114 -0
  72. package/dist/runtime/components/Select.vue +328 -0
  73. package/dist/runtime/components/Separator.vue +82 -0
  74. package/dist/runtime/components/Skeleton.vue +31 -0
  75. package/dist/runtime/components/Switch.vue +133 -0
  76. package/dist/runtime/components/Tabs.vue +127 -0
  77. package/dist/runtime/components/Textarea.vue +216 -0
  78. package/dist/runtime/components/Toast.vue +168 -0
  79. package/dist/runtime/components/Toaster.vue +143 -0
  80. package/dist/runtime/components/Tooltip.vue +94 -0
  81. package/dist/runtime/components/content/DescriptionList.vue +220 -0
  82. package/dist/runtime/composables/defineLocale.d.ts +9 -0
  83. package/dist/runtime/composables/defineLocale.js +4 -0
  84. package/dist/runtime/composables/defineShortcuts.d.ts +15 -0
  85. package/dist/runtime/composables/defineShortcuts.js +135 -0
  86. package/dist/runtime/composables/useAvatarGroup.d.ts +10 -0
  87. package/dist/runtime/composables/useAvatarGroup.js +10 -0
  88. package/dist/runtime/composables/useButtonGroup.d.ts +17 -0
  89. package/dist/runtime/composables/useButtonGroup.js +10 -0
  90. package/dist/runtime/composables/useComponentIcons.d.ts +20 -0
  91. package/dist/runtime/composables/useComponentIcons.js +25 -0
  92. package/dist/runtime/composables/useFormField.d.ts +42 -0
  93. package/dist/runtime/composables/useFormField.js +65 -0
  94. package/dist/runtime/composables/useKbd.d.ts +35 -0
  95. package/dist/runtime/composables/useKbd.js +52 -0
  96. package/dist/runtime/composables/useLocale.d.ts +4 -0
  97. package/dist/runtime/composables/useLocale.js +10 -0
  98. package/dist/runtime/composables/useToast.d.ts +12 -0
  99. package/dist/runtime/composables/useToast.js +62 -0
  100. package/dist/runtime/dictionary/icons.d.ts +20 -0
  101. package/dist/runtime/dictionary/icons.js +35 -0
  102. package/dist/runtime/index.css +1 -0
  103. package/dist/runtime/keyframes.css +1 -0
  104. package/dist/runtime/locale/en.d.ts +2 -0
  105. package/dist/runtime/locale/en.js +48 -0
  106. package/dist/runtime/locale/es.d.ts +2 -0
  107. package/dist/runtime/locale/es.js +48 -0
  108. package/dist/runtime/locale/index.d.ts +3 -0
  109. package/dist/runtime/locale/index.js +3 -0
  110. package/dist/runtime/locale/ru.d.ts +2 -0
  111. package/dist/runtime/locale/ru.js +48 -0
  112. package/dist/runtime/plugins/colors.d.ts +2 -0
  113. package/dist/runtime/plugins/colors.js +40 -0
  114. package/dist/runtime/types/app.config.d.ts +6 -0
  115. package/dist/runtime/types/form.d.ts +84 -0
  116. package/dist/runtime/types/form.js +12 -0
  117. package/dist/runtime/types/icons.d.ts +3 -0
  118. package/dist/runtime/types/icons.js +0 -0
  119. package/dist/runtime/types/index.d.ts +33 -0
  120. package/dist/runtime/types/index.js +33 -0
  121. package/dist/runtime/types/locale.d.ts +50 -0
  122. package/dist/runtime/types/locale.js +0 -0
  123. package/dist/runtime/types/utils.d.ts +22 -0
  124. package/dist/runtime/types/utils.js +0 -0
  125. package/dist/runtime/utils/form.d.ts +17 -0
  126. package/dist/runtime/utils/form.js +153 -0
  127. package/dist/runtime/utils/fuse.d.ts +4 -0
  128. package/dist/runtime/utils/fuse.js +63 -0
  129. package/dist/runtime/utils/index.d.ts +6 -0
  130. package/dist/runtime/utils/index.js +63 -0
  131. package/dist/runtime/utils/link.d.ts +29 -0
  132. package/dist/runtime/utils/link.js +4 -0
  133. package/dist/runtime/utils/locale.d.ts +15 -0
  134. package/dist/runtime/utils/locale.js +25 -0
  135. package/dist/runtime/utils/tv.d.ts +1 -0
  136. package/dist/runtime/utils/tv.js +4 -0
  137. package/dist/runtime/vue/components/Link.vue +203 -0
  138. package/dist/runtime/vue/plugins/color-mode.d.ts +4 -0
  139. package/dist/runtime/vue/plugins/color-mode.js +6 -0
  140. package/dist/runtime/vue/plugins/head.d.ts +4 -0
  141. package/dist/runtime/vue/plugins/head.js +6 -0
  142. package/dist/runtime/vue/stubs.d.ts +15 -0
  143. package/dist/runtime/vue/stubs.js +27 -0
  144. package/dist/shared/b24ui-nuxt.CNGvMe2S.mjs +4074 -0
  145. package/dist/shared/b24ui-nuxt.D22QQtm8.cjs +4079 -0
  146. package/dist/types.d.mts +1 -0
  147. package/dist/types.d.ts +1 -0
  148. package/dist/unplugin.cjs +213 -0
  149. package/dist/unplugin.d.cts +22 -0
  150. package/dist/unplugin.d.mts +22 -0
  151. package/dist/unplugin.d.ts +22 -0
  152. package/dist/unplugin.mjs +202 -0
  153. package/dist/vite.cjs +21 -0
  154. package/dist/vite.d.cts +12 -0
  155. package/dist/vite.d.mts +12 -0
  156. package/dist/vite.d.ts +12 -0
  157. package/dist/vite.mjs +19 -0
  158. package/package.json +166 -0
  159. package/vue-plugin.d.ts +5 -0
@@ -0,0 +1,271 @@
1
+ <script lang="ts">
2
+ import type { AppConfig } from '@nuxt/schema'
3
+ import _appConfig from '#build/app.config'
4
+ import theme from '#build/b24ui/form'
5
+ import { tv } from '../utils/tv'
6
+ import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId } from '../types/form'
7
+ import type { DeepReadonly } from 'vue'
8
+
9
+ const appConfigForm = _appConfig as AppConfig & { b24ui: { form: Partial<typeof theme> } }
10
+
11
+ const form = tv({ extend: tv(theme), ...(appConfigForm.b24ui?.form || {}) })
12
+
13
+ export interface FormProps<T extends object> {
14
+ id?: string | number
15
+ schema?: FormSchema<T>
16
+ state: Partial<T>
17
+ validate?: (state: Partial<T>) => Promise<FormError[]> | FormError[]
18
+ validateOn?: FormInputEvents[]
19
+ disabled?: boolean
20
+ validateOnInputDelay?: number
21
+ class?: any
22
+ onSubmit?: ((event: FormSubmitEvent<T>) => void | Promise<void>) | (() => void | Promise<void>)
23
+ }
24
+
25
+ export interface FormEmits<T extends object> {
26
+ (e: 'submit', payload: FormSubmitEvent<T>): void
27
+ (e: 'error', payload: FormErrorEvent): void
28
+ }
29
+
30
+ export interface FormSlots {
31
+ default(props?: {}): any
32
+ }
33
+ </script>
34
+
35
+ <script lang="ts" setup generic="T extends object">
36
+ import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from 'vue'
37
+ import { useEventBus } from '@vueuse/core'
38
+ import { formOptionsInjectionKey, formInputsInjectionKey, formBusInjectionKey, formLoadingInjectionKey } from '../composables/useFormField'
39
+ import { validateSchema } from '../utils/form'
40
+ import { FormValidationException } from '../types/form'
41
+
42
+ const props = withDefaults(defineProps<FormProps<T>>(), {
43
+ validateOn() {
44
+ return ['input', 'blur', 'change'] as FormInputEvents[]
45
+ },
46
+ validateOnInputDelay: 300
47
+ })
48
+ const emits = defineEmits<FormEmits<T>>()
49
+ defineSlots<FormSlots>()
50
+
51
+ const formId = props.id ?? useId() as string
52
+
53
+ const bus = useEventBus<FormEvent<T>>(`form-${formId}`)
54
+ const parentBus = inject(
55
+ formBusInjectionKey,
56
+ undefined
57
+ )
58
+
59
+ provide(formBusInjectionKey, bus)
60
+
61
+ const nestedForms = ref<Map<string | number, { validate: typeof _validate }>>(new Map())
62
+
63
+ onMounted(async () => {
64
+ bus.on(async (event) => {
65
+ if (event.type === 'attach') {
66
+ nestedForms.value.set(event.formId, { validate: event.validate })
67
+ } else if (event.type === 'detach') {
68
+ nestedForms.value.delete(event.formId)
69
+ } else if (props.validateOn?.includes(event.type)) {
70
+ if (event.type !== 'input') {
71
+ await _validate({ name: event.name, silent: true, nested: false })
72
+ } else if (event.eager || blurredFields.has(event.name)) {
73
+ await _validate({ name: event.name, silent: true, nested: false })
74
+ }
75
+ }
76
+
77
+ if (event.type === 'blur') {
78
+ blurredFields.add(event.name)
79
+ }
80
+
81
+ if (event.type === 'change' || event.type === 'input' || event.type === 'blur' || event.type === 'focus') {
82
+ touchedFields.add(event.name)
83
+ }
84
+
85
+ if (event.type === 'change' || event.type === 'input') {
86
+ dirtyFields.add(event.name)
87
+ }
88
+ })
89
+ })
90
+
91
+ onUnmounted(() => {
92
+ bus.reset()
93
+ })
94
+
95
+ onMounted(async () => {
96
+ if (parentBus) {
97
+ await nextTick()
98
+ parentBus.emit({ type: 'attach', validate: _validate, formId })
99
+ }
100
+ })
101
+
102
+ onUnmounted(() => {
103
+ if (parentBus) {
104
+ parentBus.emit({ type: 'detach', formId })
105
+ }
106
+ })
107
+
108
+ const errors = ref<FormErrorWithId[]>([])
109
+ provide('form-errors', errors)
110
+
111
+ const inputs = ref<{ [P in keyof T]?: { id?: string, pattern?: RegExp } }>({})
112
+ provide(formInputsInjectionKey, inputs as any)
113
+
114
+ const dirtyFields = new Set<keyof T>()
115
+ const touchedFields = new Set<keyof T>()
116
+ const blurredFields = new Set<keyof T>()
117
+
118
+ function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
119
+ return errs.map(err => ({
120
+ ...err,
121
+ id: inputs.value[err.name]?.id
122
+ }))
123
+ }
124
+
125
+ const transformedState = ref<T | null>(null)
126
+
127
+ async function getErrors(): Promise<FormErrorWithId[]> {
128
+ let errs = props.validate ? (await props.validate(props.state)) ?? [] : []
129
+
130
+ if (props.schema) {
131
+ const { errors, result } = await validateSchema(props.state, props.schema as FormSchema<typeof props.state>)
132
+ if (errors) {
133
+ errs = errs.concat(errors)
134
+ } else {
135
+ transformedState.value = result
136
+ }
137
+ }
138
+
139
+ return resolveErrorIds(errs)
140
+ }
141
+
142
+ async function _validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<T | false> {
143
+ const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof T)[]
144
+
145
+ const nestedValidatePromises = !names && opts.nested
146
+ ? Array.from(nestedForms.value.values()).map(
147
+ ({ validate }) => validate(opts).then(() => undefined).catch((error: Error) => {
148
+ if (!(error instanceof FormValidationException)) {
149
+ throw error
150
+ }
151
+ return error
152
+ })
153
+ )
154
+ : []
155
+
156
+ if (names) {
157
+ const otherErrors = errors.value.filter(error => !names.some((name) => {
158
+ const pattern = inputs.value?.[name]?.pattern
159
+ return name === error.name || (pattern && error.name.match(pattern))
160
+ }))
161
+
162
+ const pathErrors = (await getErrors()).filter(error => names.some((name) => {
163
+ const pattern = inputs.value?.[name]?.pattern
164
+ return name === error.name || (pattern && error.name.match(pattern))
165
+ }))
166
+
167
+ errors.value = otherErrors.concat(pathErrors)
168
+ } else {
169
+ errors.value = await getErrors()
170
+ }
171
+
172
+ const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val !== undefined)
173
+
174
+ if (errors.value.length + childErrors.length > 0) {
175
+ if (opts.silent) return false
176
+ throw new FormValidationException(formId, errors.value, childErrors)
177
+ }
178
+
179
+ if (opts.transform) {
180
+ Object.assign(props.state, transformedState.value)
181
+ }
182
+
183
+ return props.state as T
184
+ }
185
+
186
+ const loading = ref(false)
187
+ provide(formLoadingInjectionKey, readonly(loading))
188
+
189
+ async function onSubmitWrapper(payload: Event) {
190
+ loading.value = true
191
+
192
+ const event = payload as FormSubmitEvent<any>
193
+
194
+ try {
195
+ event.data = await _validate({ nested: true, transform: true })
196
+ await props.onSubmit?.(event)
197
+ } catch (error) {
198
+ if (!(error instanceof FormValidationException)) {
199
+ throw error
200
+ }
201
+
202
+ const errorEvent: FormErrorEvent = {
203
+ ...event,
204
+ errors: error.errors,
205
+ children: error.children
206
+ }
207
+ emits('error', errorEvent)
208
+ }
209
+
210
+ loading.value = false
211
+ }
212
+
213
+ const disabled = computed(() => props.disabled || loading.value)
214
+
215
+ provide(formOptionsInjectionKey, computed(() => ({
216
+ disabled: disabled.value,
217
+ validateOnInputDelay: props.validateOnInputDelay
218
+ })))
219
+
220
+ defineExpose<Form<T>>({
221
+ validate: _validate,
222
+ errors,
223
+
224
+ setErrors(errs: FormError[], name?: keyof T) {
225
+ if (name) {
226
+ errors.value = errors.value
227
+ .filter(error => error.name !== name)
228
+ .concat(resolveErrorIds(errs))
229
+ } else {
230
+ errors.value = resolveErrorIds(errs)
231
+ }
232
+ },
233
+
234
+ async submit() {
235
+ await onSubmitWrapper(new Event('submit'))
236
+ },
237
+
238
+ getErrors(name?: keyof T) {
239
+ if (name) {
240
+ return errors.value.filter(err => err.name === name)
241
+ }
242
+ return errors.value
243
+ },
244
+
245
+ clear(name?: string) {
246
+ if (name) {
247
+ errors.value = errors.value.filter(err => err.name !== name)
248
+ } else {
249
+ errors.value = []
250
+ }
251
+ },
252
+
253
+ disabled,
254
+ dirty: computed(() => !!dirtyFields.size),
255
+
256
+ dirtyFields: readonly(dirtyFields) as DeepReadonly<Set<keyof T>>,
257
+ blurredFields: readonly(blurredFields) as DeepReadonly<Set<keyof T>>,
258
+ touchedFields: readonly(touchedFields) as DeepReadonly<Set<keyof T>>
259
+ })
260
+ </script>
261
+
262
+ <template>
263
+ <component
264
+ :is="parentBus ? 'div' : 'form'"
265
+ :id="formId"
266
+ :class="form({ class: props.class })"
267
+ @submit.prevent="onSubmitWrapper"
268
+ >
269
+ <slot />
270
+ </component>
271
+ </template>
@@ -0,0 +1,128 @@
1
+ <script lang="ts">
2
+ import type { VariantProps } from 'tailwind-variants'
3
+ import type { AppConfig } from '@nuxt/schema'
4
+ import _appConfig from '#build/app.config'
5
+ import theme from '#build/b24ui/form-field'
6
+ import { tv } from '../utils/tv'
7
+
8
+ const appConfigFormField = _appConfig as AppConfig & { b24ui: { formField: Partial<typeof theme> } }
9
+
10
+ const formField = tv({ extend: tv(theme), ...(appConfigFormField.b24ui?.formField || {}) })
11
+
12
+ type FormFieldVariants = VariantProps<typeof formField>
13
+
14
+ export interface FormFieldProps {
15
+ /**
16
+ * The element or component this component should render as.
17
+ * @defaultValue 'div'
18
+ */
19
+ as?: any
20
+ /** The name of the FormField. Also used to match form errors. */
21
+ name?: string
22
+ /** A regular expression to match form error names. */
23
+ errorPattern?: RegExp
24
+ label?: string
25
+ description?: string
26
+ help?: string
27
+ error?: string | boolean
28
+ hint?: string
29
+ size?: FormFieldVariants['size']
30
+ required?: boolean
31
+ eagerValidation?: boolean
32
+ validateOnInputDelay?: number
33
+ class?: any
34
+ b24ui?: Partial<typeof formField.slots>
35
+ }
36
+
37
+ export interface FormFieldSlots {
38
+ label(props: { label?: string }): any
39
+ hint(props: { hint?: string }): any
40
+ description(props: { description?: string }): any
41
+ help(props: { help?: string }): any
42
+ error(props: { error?: string | boolean }): any
43
+ default(props: { error?: string | boolean }): any
44
+ }
45
+ </script>
46
+
47
+ <script setup lang="ts">
48
+ import { computed, ref, inject, provide, type Ref, useId } from 'vue'
49
+ import { Primitive, Label } from 'reka-ui'
50
+ import { formFieldInjectionKey, inputIdInjectionKey } from '../composables/useFormField'
51
+ import type { FormError, FormFieldInjectedOptions } from '../types/form'
52
+ import WarningIcon from '@bitrix24/b24icons-vue/main/WarningIcon'
53
+
54
+ const props = defineProps<FormFieldProps>()
55
+ const slots = defineSlots<FormFieldSlots>()
56
+
57
+ const b24ui = computed(() => formField({
58
+ size: props.size,
59
+ required: props.required,
60
+ useDescription: Boolean(props.description) || !!slots.description
61
+ }))
62
+
63
+ const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
64
+
65
+ const error = computed(() => props.error || formErrors?.value?.find(error => error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern)))?.message)
66
+
67
+ const id = ref(useId())
68
+ // Copies id's initial value to bind aria-attributes such as aria-describedby.
69
+ // This is required for the RadioGroup component which unsets the id value.
70
+ const ariaId = id.value
71
+
72
+ provide(inputIdInjectionKey, id)
73
+
74
+ provide(formFieldInjectionKey, computed(() => ({
75
+ error: error.value,
76
+ name: props.name,
77
+ size: props.size,
78
+ eagerValidation: props.eagerValidation,
79
+ validateOnInputDelay: props.validateOnInputDelay,
80
+ errorPattern: props.errorPattern,
81
+ hint: props.hint,
82
+ description: props.description,
83
+ ariaId
84
+ }) as FormFieldInjectedOptions<FormFieldProps>))
85
+ </script>
86
+
87
+ <template>
88
+ <Primitive :as="as" :class="b24ui.root({ class: [props.class, props.b24ui?.root] })">
89
+ <div :class="b24ui.wrapper({ class: props.b24ui?.wrapper })">
90
+ <div v-if="label || !!slots.label" :class="b24ui.labelWrapper({ class: props.b24ui?.labelWrapper })">
91
+ <Label :for="id" :class="b24ui.label({ class: props.b24ui?.label })">
92
+ <slot name="label" :label="label">
93
+ {{ label }}
94
+ </slot>
95
+ </Label>
96
+ <span v-if="hint || !!slots.hint" :id="`${ariaId}-hint`" :class="b24ui.hint({ class: props.b24ui?.hint })">
97
+ <slot name="hint" :hint="hint">
98
+ {{ hint }}
99
+ </slot>
100
+ </span>
101
+ </div>
102
+
103
+ <p v-if="description || !!slots.description" :id="`${ariaId}-description`" :class="b24ui.description({ class: props.b24ui?.description })">
104
+ <slot name="description" :description="description">
105
+ {{ description }}
106
+ </slot>
107
+ </p>
108
+ </div>
109
+
110
+ <div :class="[(label || !!slots.label || description || !!slots.description) && b24ui.container({ class: props.b24ui?.container })]">
111
+ <slot :error="error" />
112
+
113
+ <div v-if="(typeof error === 'string' && error) || !!slots.error" :id="`${ariaId}-error`" :class="b24ui.error({ class: props.b24ui?.error })">
114
+ <slot name="error" :error="error">
115
+ <div class="flex flex-row flex-nowrap gap-0.5">
116
+ <WarningIcon :class="b24ui.errorIcon()" />
117
+ <div>{{ error }}</div>
118
+ </div>
119
+ </slot>
120
+ </div>
121
+ <p v-else-if="help || !!slots.help" :class="b24ui.help({ class: props.b24ui?.help })">
122
+ <slot name="help" :help="help">
123
+ {{ help }}
124
+ </slot>
125
+ </p>
126
+ </div>
127
+ </Primitive>
128
+ </template>
@@ -0,0 +1,224 @@
1
+ <script lang="ts">
2
+ import type { InputHTMLAttributes } from 'vue'
3
+ import type { VariantProps } from 'tailwind-variants'
4
+ import type { AppConfig } from '@nuxt/schema'
5
+ import _appConfig from '#build/app.config'
6
+ import theme from '#build/b24ui/input'
7
+ import type { UseComponentIconsProps } from '../composables/useComponentIcons'
8
+ import { tv } from '../utils/tv'
9
+ import type { AvatarProps } from '../types'
10
+ import type { PartialString } from '../types/utils'
11
+
12
+ const appConfigInput = _appConfig as AppConfig & { b24ui: { input: Partial<typeof theme> } }
13
+
14
+ const input = tv({ extend: tv(theme), ...(appConfigInput.b24ui?.input || {}) })
15
+
16
+ type InputVariants = VariantProps<typeof input>
17
+
18
+ export interface InputProps extends UseComponentIconsProps {
19
+ /**
20
+ * The element or component this component should render as.
21
+ * @defaultValue 'div'
22
+ */
23
+ as?: any
24
+ id?: string
25
+ name?: string
26
+ type?: InputHTMLAttributes['type']
27
+ /** The placeholder text when the input is empty. */
28
+ placeholder?: string
29
+ color?: InputVariants['color']
30
+ size?: InputVariants['size']
31
+ /** Removes padding from input. */
32
+ noPadding?: boolean
33
+ /** removes all borders (rings). */
34
+ noBorder?: boolean
35
+ /** removes all borders (rings) except the bottom one. */
36
+ underline?: boolean
37
+ /** Rounds the corners of the button. */
38
+ rounded?: boolean
39
+ required?: boolean
40
+ autocomplete?: InputHTMLAttributes['autocomplete']
41
+ autofocus?: boolean
42
+ autofocusDelay?: number
43
+ disabled?: boolean
44
+ tag?: string
45
+ tagColor?: InputVariants['tagColor']
46
+ /** Highlight the ring color like a focus state. */
47
+ highlight?: boolean
48
+ class?: any
49
+ b24ui?: PartialString<typeof input.slots>
50
+ }
51
+
52
+ export interface InputEmits {
53
+ (e: 'update:modelValue', payload: string | number): void
54
+ (e: 'blur', event: FocusEvent): void
55
+ (e: 'change', event: Event): void
56
+ }
57
+
58
+ export interface InputSlots {
59
+ leading(props?: {}): any
60
+ default(props?: {}): any
61
+ trailing(props?: {}): any
62
+ }
63
+ </script>
64
+
65
+ <script setup lang="ts">
66
+ import { ref, computed, onMounted } from 'vue'
67
+ import { Primitive } from 'reka-ui'
68
+ import { useButtonGroup } from '../composables/useButtonGroup'
69
+ import { useComponentIcons } from '../composables/useComponentIcons'
70
+ import { useFormField } from '../composables/useFormField'
71
+ import { looseToNumber } from '../utils'
72
+ import B24Avatar from './Avatar.vue'
73
+
74
+ defineOptions({ inheritAttrs: false })
75
+
76
+ const props = withDefaults(defineProps<InputProps>(), {
77
+ type: 'text',
78
+ autocomplete: 'off',
79
+ autofocusDelay: 0
80
+ })
81
+ const emits = defineEmits<InputEmits>()
82
+ const slots = defineSlots<InputSlots>()
83
+
84
+ const [modelValue, modelModifiers] = defineModel<string | number>()
85
+
86
+ const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps>(props, { deferInputValidation: true })
87
+ const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
88
+ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
89
+
90
+ const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
91
+
92
+ const isTag = computed(() => {
93
+ return props.tag
94
+ })
95
+
96
+ const b24ui = computed(() => input({
97
+ type: props.type as InputVariants['type'],
98
+ color: color.value,
99
+ size: inputSize?.value,
100
+ loading: props.loading,
101
+ tagColor: props.tagColor,
102
+ highlight: highlight.value,
103
+ rounded: Boolean(props.rounded),
104
+ noPadding: Boolean(props.noPadding),
105
+ noBorder: Boolean(props.noBorder),
106
+ underline: Boolean(props.underline),
107
+ leading: Boolean(isLeading.value || !!props.avatar || !!slots.leading),
108
+ trailing: Boolean(isTrailing.value || !!slots.trailing),
109
+ buttonGroup: orientation.value
110
+ }))
111
+
112
+ const inputRef = ref<HTMLInputElement | null>(null)
113
+
114
+ function autoFocus() {
115
+ if (props.autofocus) {
116
+ inputRef.value?.focus()
117
+ }
118
+ }
119
+
120
+ // Custom function to handle the v-model properties
121
+ function updateInput(value: string) {
122
+ if (modelModifiers.trim) {
123
+ value = value.trim()
124
+ }
125
+
126
+ if (modelModifiers.number || props.type === 'number') {
127
+ value = looseToNumber(value)
128
+ }
129
+
130
+ modelValue.value = value
131
+ emitFormInput()
132
+ }
133
+
134
+ function onInput(event: Event) {
135
+ if (!modelModifiers.lazy) {
136
+ updateInput((event.target as HTMLInputElement).value)
137
+ }
138
+ }
139
+
140
+ function onChange(event: Event) {
141
+ const value = (event.target as HTMLInputElement).value
142
+
143
+ if (modelModifiers.lazy) {
144
+ updateInput(value)
145
+ }
146
+
147
+ // Update trimmed input so that it has same behavior as native input https://github.com/vuejs/core/blob/5ea8a8a4fab4e19a71e123e4d27d051f5e927172/packages/runtime-dom/src/directives/vModel.ts#L63
148
+ if (modelModifiers.trim) {
149
+ (event.target as HTMLInputElement).value = value.trim()
150
+ }
151
+
152
+ emitFormChange()
153
+ emits('change', event)
154
+ }
155
+
156
+ function onBlur(event: FocusEvent) {
157
+ emitFormBlur()
158
+ emits('blur', event)
159
+ }
160
+
161
+ defineExpose({
162
+ inputRef
163
+ })
164
+
165
+ onMounted(() => {
166
+ setTimeout(() => {
167
+ autoFocus()
168
+ }, props.autofocusDelay)
169
+ })
170
+ </script>
171
+
172
+ <template>
173
+ <Primitive :as="as" :class="b24ui.root({ class: [props.class, props.b24ui?.root] })">
174
+ <div v-if="isTag" :class="b24ui.tag({ class: props.b24ui?.tag })">
175
+ {{ props.tag }}
176
+ </div>
177
+
178
+ <input
179
+ :id="id"
180
+ ref="inputRef"
181
+ :type="type"
182
+ :value="modelValue"
183
+ :name="name"
184
+ :placeholder="placeholder"
185
+ :class="b24ui.base({ class: props.b24ui?.base })"
186
+ :disabled="disabled"
187
+ :required="required"
188
+ :autocomplete="autocomplete"
189
+ v-bind="{ ...$attrs, ...ariaAttrs }"
190
+ @input="onInput"
191
+ @blur="onBlur"
192
+ @change="onChange"
193
+ @focus="emitFormFocus"
194
+ >
195
+
196
+ <slot />
197
+
198
+ <span v-if="isLeading || !!avatar || !!slots.leading" :class="b24ui.leading({ class: props.b24ui?.leading })">
199
+ <slot name="leading">
200
+ <Component
201
+ :is="leadingIconName"
202
+ v-if="isLeading && leadingIconName"
203
+ :class="b24ui.leadingIcon({ class: props.b24ui?.leadingIcon })"
204
+ />
205
+ <B24Avatar
206
+ v-else-if="!!avatar"
207
+ :size="((props.b24ui?.leadingAvatarSize || b24ui.leadingAvatarSize()) as AvatarProps['size'])"
208
+ v-bind="avatar"
209
+ :class="b24ui.leadingAvatar({ class: props.b24ui?.leadingAvatar })"
210
+ />
211
+ </slot>
212
+ </span>
213
+
214
+ <span v-if="isTrailing || !!slots.trailing" :class="b24ui.trailing({ class: props.b24ui?.trailing })">
215
+ <slot name="trailing">
216
+ <Component
217
+ :is="trailingIconName"
218
+ v-if="trailingIconName"
219
+ :class="b24ui.trailingIcon({ class: props.b24ui?.trailingIcon })"
220
+ />
221
+ </slot>
222
+ </span>
223
+ </Primitive>
224
+ </template>
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import type { VariantProps } from 'tailwind-variants'
3
+ import type { AppConfig } from '@nuxt/schema'
4
+ import _appConfig from '#build/app.config'
5
+ import theme from '#build/b24ui/kbd'
6
+ import type { KbdKey } from '../composables/useKbd'
7
+ import { tv } from '../utils/tv'
8
+
9
+ const appConfigKbd = _appConfig as AppConfig & { b24ui: { kbd: Partial<typeof theme> } }
10
+
11
+ const kbd = tv({ extend: tv(theme), ...(appConfigKbd.b24ui?.kbd || {}) })
12
+
13
+ type KbdVariants = VariantProps<typeof kbd>
14
+
15
+ export interface KbdProps {
16
+ /**
17
+ * The element or component this component should render as.
18
+ * @defaultValue 'kbd'
19
+ */
20
+ as?: any
21
+ value?: KbdKey | string
22
+ depth?: KbdVariants['depth']
23
+ size?: KbdVariants['size']
24
+ class?: any
25
+ }
26
+
27
+ export interface KbdSlots {
28
+ default(props?: {}): any
29
+ }
30
+ </script>
31
+
32
+ <script setup lang="ts">
33
+ import { Primitive } from 'reka-ui'
34
+ import { useKbd } from '../composables/useKbd'
35
+
36
+ const props = withDefaults(defineProps<KbdProps>(), {
37
+ as: 'kbd'
38
+ })
39
+ defineSlots<KbdSlots>()
40
+
41
+ const { getKbdKey } = useKbd()
42
+ </script>
43
+
44
+ <template>
45
+ <Primitive :as="as" :class="kbd({ depth, size, class: props.class })">
46
+ <slot>
47
+ {{ getKbdKey(value) }}
48
+ </slot>
49
+ </Primitive>
50
+ </template>