@givanov95/vue-forms
v0.1.2
Published
Form component library for Laravel + Inertia + Vue 3: Input, Textarea, NumberInput, Select (single/multi), DatePicker, RadioGroup, Checkbox, Editor (TinyMCE), ImageInput, FileInput, ResetSaveButtons + TranslationPlugin and shared types.
Readme
@givanov95/vue-forms
Form component library for Laravel + Inertia + Vue 3 projects.
Ships as a source-style npm package — your project's Vite/Tailwind setup processes the .vue files directly. No build step in the package itself, so iteration is fast.
What's included
| Component | Notes |
|---|---|
| Input | text / email / password / url / tel / search |
| Textarea | with rows prop |
| NumberInput | min/max/step, integer mode |
| Select | single AND multi via :multiple (vue-multiselect) |
| DatePicker | date / date+time (@vuepic/vue-datepicker) |
| RadioGroup | enum/options API like Select |
| CheckboxField | with label/error |
| Editor | TinyMCE self-hosted (no API key) |
| ImageInput | drag-drop, preview, reorder, emit error events |
| FileInput | document upload + download + reorder, emit error events |
| ResetSaveButtons | Cancel/Save pair |
| InputError, InputLabel | building blocks (used internally) |
Plus: TranslationPlugin (Vue plugin exposing __('key', { name })), utils (enumToOptions, multiselectToOptions, capitalizeFirstLetter, formatFileSize), and TypeScript types (Option, Enum<T>, Multiselect, DatabaseImage, DatabaseFile, GlobalInputErrors, …).
Install
npm install @givanov95/vue-formsPeers: vue ^3.3.0, @inertiajs/vue3 ^2.0.
Project setup
1. Vite — process .vue files inside node_modules
In vite.config.js:
export default defineConfig({
// ...
ssr: {
noExternal: ['@givanov95/vue-forms'],
},
});2. Tailwind — scan the package's templates
In tailwind.config.js:
content: [
'./resources/**/*.{vue,blade.php,ts,js}',
'./node_modules/@givanov95/vue-forms/src/**/*.vue',
],3. Provide globalInputErrors + register TranslationPlugin
In resources/js/app.ts:
import { router, usePage } from '@inertiajs/vue3';
import { ref } from 'vue';
import { TranslationPlugin, type GlobalInputErrors } from '@givanov95/vue-forms';
const globalInputErrors = ref<GlobalInputErrors>({
errorMessages: (usePage().props.errors ?? {}) as Record<string, string>,
failedKeys: [],
});
// keep it in sync with Inertia validation errors:
router.on('finish', () => {
globalInputErrors.value = {
errorMessages: (usePage().props.errors ?? {}) as Record<string, string>,
failedKeys: [],
};
});
createInertiaApp({
setup({ el, App, props, plugin }) {
const translations = props.initialPage.props.translations as Record<string, string>;
createApp({ render: () => h(App, props) })
.use(plugin)
.use(TranslationPlugin, translations)
.provide('globalInputErrors', globalInputErrors)
.mount(el);
},
});globalInputErrors is the contract: every form component reads inject('globalInputErrors') and surfaces the error matching its name prop.
Usage
<script setup lang="ts">
import {
Input, Textarea, NumberInput, Select, DatePicker,
RadioGroup, CheckboxField, Editor, ImageInput, FileInput,
ResetSaveButtons,
} from '@givanov95/vue-forms';
import { useForm } from '@inertiajs/vue3';
const form = useForm({
title: '', body: '', price: null,
category_id: null, tag_ids: [], published_at: '',
is_active: true, images: [], spec_sheets: [],
});
const flash = (msg: string) => {
// your flash mechanism
console.warn(msg);
};
</script>
<template>
<Input v-model="form.title" name="title" :label="__('Title')" required />
<Textarea v-model="form.body" name="body" :label="__('Body')" :rows="5" />
<NumberInput v-model="form.price" name="price" :label="__('Price')" :min="0" :step="0.01" />
<Select v-model="form.category_id" name="category_id" :options="categories" />
<Select v-model="form.tag_ids" name="tag_ids" :options="tags" multiple />
<DatePicker v-model="form.published_at" name="published_at" enable-time-picker />
<CheckboxField v-model="form.is_active" name="is_active" :label="__('Active')" />
<ImageInput v-model="form.images" name="images" :images="product?.images" @error="flash(__($event))" />
<FileInput v-model="form.spec_sheets" name="spec_sheets" :files="product?.files" @error="flash(__($event))" />
<ResetSaveButtons :disabled="form.processing" @reset="form.reset()" @save="submit" />
</template>Error handling contract
- Server-side validation errors flow through
globalInputErrors. Set uprouter.on('finish')inapp.ts(see above) and every component picks up its error bynameautomatically. - Client-side errors from
ImageInput/FileInput(file too big, wrong type, delete failed) come via the@errorevent — consumer wires it to its preferred flash/alert system.
Translation
The package's templates call __('key'). Install TranslationPlugin with a Record<string, string> and __ becomes available everywhere.
Development
npm install
npm test # Vitest (26 tests)
npm run test:watch # Vitest in watch modePre-commit hook
npm install runs the prepare script which symlinks the repo's pre-commit into .git/hooks/pre-commit. The hook runs npm test before any commit that touches .ts, .vue, or .js files — replacement for CI since the repo is private.
Bypass with git commit --no-verify when you genuinely need to (WIP commit, doc-only change you've already validated).
License
MIT
