@nomos-ui/form 0.0.17 → 0.0.19
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/README.md +0 -274
- package/dist/components/file-input.d.ts +111 -0
- package/dist/components/file-input.d.ts.map +1 -0
- package/dist/components/file-input.js +124 -0
- package/dist/components/file-input.js.map +1 -0
- package/dist/components/form-file-input.d.ts +44 -0
- package/dist/components/form-file-input.d.ts.map +1 -0
- package/dist/components/form-file-input.js +49 -0
- package/dist/components/form-file-input.js.map +1 -0
- package/dist/exports/index.d.ts +6 -2
- package/dist/exports/index.d.ts.map +1 -1
- package/dist/exports/index.js +7 -1
- package/dist/exports/index.js.map +1 -1
- package/package.json +6 -24
package/README.md
CHANGED
|
@@ -1,277 +1,3 @@
|
|
|
1
1
|
# Nomos UI Forms
|
|
2
2
|
|
|
3
3
|
> **νόμος** (nomos) - Greek: law, order, principle
|
|
4
|
-
|
|
5
|
-
Beautiful, validated form components that integrate seamlessly with React Hook Form. Built on the principle that forms should enforce order and validation effortlessly.
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @nomos-ui/react react-hook-form
|
|
11
|
-
# or
|
|
12
|
-
yarn add @nomos-ui/react react-hook-form
|
|
13
|
-
# or
|
|
14
|
-
pnpm add @nomos-ui/react react-hook-form
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Quick Start
|
|
18
|
-
|
|
19
|
-
```jsx
|
|
20
|
-
import { useForm } from "react-hook-form";
|
|
21
|
-
import { Input, Button, Form } from "@nomos-ui/react";
|
|
22
|
-
|
|
23
|
-
function LoginForm() {
|
|
24
|
-
const form = useForm();
|
|
25
|
-
|
|
26
|
-
const onSubmit = (data) => {
|
|
27
|
-
console.log(data);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<Form form={form} onSubmit={onSubmit}>
|
|
32
|
-
<Input
|
|
33
|
-
name="email"
|
|
34
|
-
label="Email"
|
|
35
|
-
type="email"
|
|
36
|
-
rules={{ required: "Email is required" }}
|
|
37
|
-
/>
|
|
38
|
-
<Input
|
|
39
|
-
name="password"
|
|
40
|
-
label="Password"
|
|
41
|
-
type="password"
|
|
42
|
-
rules={{ required: "Password is required", minLength: 8 }}
|
|
43
|
-
/>
|
|
44
|
-
<Button type="submit">Sign In</Button>
|
|
45
|
-
</Form>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Why Nomos UI?
|
|
51
|
-
|
|
52
|
-
- **Zero Boilerplate** - Components auto-register with React Hook Form
|
|
53
|
-
- **Built-in Validation** - Error states handled automatically
|
|
54
|
-
- **Accessible** - WCAG 2.1 AA compliant
|
|
55
|
-
- **Customizable** - Style with Tailwind or your own CSS
|
|
56
|
-
- **Type Safe** - Full TypeScript support
|
|
57
|
-
- **Lightweight** - Tree-shakeable, minimal bundle size
|
|
58
|
-
|
|
59
|
-
## Components
|
|
60
|
-
|
|
61
|
-
### Form Components
|
|
62
|
-
|
|
63
|
-
- `<Input />` - Text, email, password, number inputs
|
|
64
|
-
- `<Textarea />` - Multi-line text input
|
|
65
|
-
- `<Select />` - Dropdown selection
|
|
66
|
-
- `<Checkbox />` - Single checkbox
|
|
67
|
-
- `<CheckboxGroup />` - Multiple checkboxes
|
|
68
|
-
- `<Radio />` - Radio button
|
|
69
|
-
- `<RadioGroup />` - Radio button group
|
|
70
|
-
- `<Switch />` - Toggle switch
|
|
71
|
-
- `<DatePicker />` - Date selection
|
|
72
|
-
- `<FileUpload />` - File input with drag & drop
|
|
73
|
-
|
|
74
|
-
### Layout & UI
|
|
75
|
-
|
|
76
|
-
- `<Form />` - Form wrapper with context
|
|
77
|
-
- `<Button />` - Submit and action buttons
|
|
78
|
-
- `<FieldGroup />` - Group related fields
|
|
79
|
-
- `<FormSection />` - Organize form into sections
|
|
80
|
-
|
|
81
|
-
## Examples
|
|
82
|
-
|
|
83
|
-
### Basic Input with Validation
|
|
84
|
-
|
|
85
|
-
```jsx
|
|
86
|
-
<Input
|
|
87
|
-
name="username"
|
|
88
|
-
label="Username"
|
|
89
|
-
placeholder="Enter username"
|
|
90
|
-
rules={{
|
|
91
|
-
required: "Username is required",
|
|
92
|
-
minLength: { value: 3, message: "Minimum 3 characters" },
|
|
93
|
-
pattern: { value: /^[a-zA-Z0-9_]+$/, message: "Alphanumeric only" },
|
|
94
|
-
}}
|
|
95
|
-
helperText="Choose a unique username"
|
|
96
|
-
/>
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Select with Options
|
|
100
|
-
|
|
101
|
-
```jsx
|
|
102
|
-
<Select
|
|
103
|
-
name="country"
|
|
104
|
-
label="Country"
|
|
105
|
-
rules={{ required: "Please select a country" }}
|
|
106
|
-
options={[
|
|
107
|
-
{ value: "us", label: "United States" },
|
|
108
|
-
{ value: "uk", label: "United Kingdom" },
|
|
109
|
-
{ value: "ca", label: "Canada" },
|
|
110
|
-
]}
|
|
111
|
-
/>
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Checkbox Group
|
|
115
|
-
|
|
116
|
-
```jsx
|
|
117
|
-
<CheckboxGroup
|
|
118
|
-
name="interests"
|
|
119
|
-
label="Interests"
|
|
120
|
-
options={[
|
|
121
|
-
{ value: "sports", label: "Sports" },
|
|
122
|
-
{ value: "music", label: "Music" },
|
|
123
|
-
{ value: "reading", label: "Reading" },
|
|
124
|
-
]}
|
|
125
|
-
rules={{ required: "Select at least one interest" }}
|
|
126
|
-
/>
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Custom Styling
|
|
130
|
-
|
|
131
|
-
```jsx
|
|
132
|
-
<Input
|
|
133
|
-
name="email"
|
|
134
|
-
label="Email"
|
|
135
|
-
className="custom-input"
|
|
136
|
-
labelClassName="custom-label"
|
|
137
|
-
errorClassName="custom-error"
|
|
138
|
-
/>
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Controlled Components
|
|
142
|
-
|
|
143
|
-
```jsx
|
|
144
|
-
const { watch, setValue } = useForm();
|
|
145
|
-
const emailValue = watch("email");
|
|
146
|
-
|
|
147
|
-
<Input
|
|
148
|
-
name="email"
|
|
149
|
-
label="Email"
|
|
150
|
-
onChange={(e) => {
|
|
151
|
-
// Custom logic
|
|
152
|
-
setValue("email", e.target.value.toLowerCase());
|
|
153
|
-
}}
|
|
154
|
-
/>;
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## API Reference
|
|
158
|
-
|
|
159
|
-
### Common Props
|
|
160
|
-
|
|
161
|
-
All form components share these props:
|
|
162
|
-
|
|
163
|
-
| Prop | Type | Description |
|
|
164
|
-
| ------------ | --------- | -------------------------------- |
|
|
165
|
-
| `name` | `string` | Field name (required) |
|
|
166
|
-
| `label` | `string` | Field label |
|
|
167
|
-
| `rules` | `object` | React Hook Form validation rules |
|
|
168
|
-
| `helperText` | `string` | Help text shown below field |
|
|
169
|
-
| `disabled` | `boolean` | Disable the field |
|
|
170
|
-
| `className` | `string` | Custom CSS class |
|
|
171
|
-
| `...rest` | `any` | Native HTML attributes |
|
|
172
|
-
|
|
173
|
-
### Input Props
|
|
174
|
-
|
|
175
|
-
| Prop | Type | Default | Description |
|
|
176
|
-
| -------------- | -------- | -------- | ---------------------------------------- |
|
|
177
|
-
| `type` | `string` | `'text'` | Input type (text, email, password, etc.) |
|
|
178
|
-
| `placeholder` | `string` | - | Placeholder text |
|
|
179
|
-
| `autoComplete` | `string` | - | Browser autocomplete hint |
|
|
180
|
-
|
|
181
|
-
### Button Props
|
|
182
|
-
|
|
183
|
-
| Prop | Type | Default | Description |
|
|
184
|
-
| ----------- | --------------------------------------------------- | ----------- | -------------------- |
|
|
185
|
-
| `variant` | `'primary' \| 'secondary' \| 'outline' \| 'danger'` | `'primary'` | Button style variant |
|
|
186
|
-
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
|
|
187
|
-
| `loading` | `boolean` | `false` | Show loading spinner |
|
|
188
|
-
| `fullWidth` | `boolean` | `false` | Take full width |
|
|
189
|
-
|
|
190
|
-
## Customization
|
|
191
|
-
|
|
192
|
-
### Theming
|
|
193
|
-
|
|
194
|
-
Override default styles with CSS variables:
|
|
195
|
-
|
|
196
|
-
```css
|
|
197
|
-
:root {
|
|
198
|
-
--nomos-primary: #3b82f6;
|
|
199
|
-
--nomos-error: #ef4444;
|
|
200
|
-
--nomos-border: #d1d5db;
|
|
201
|
-
--nomos-radius: 0.5rem;
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### Tailwind Integration
|
|
206
|
-
|
|
207
|
-
Components use Tailwind classes by default. Extend with your own:
|
|
208
|
-
|
|
209
|
-
```jsx
|
|
210
|
-
<Input
|
|
211
|
-
name="email"
|
|
212
|
-
className="shadow-xl border-purple-500 focus:ring-purple-500"
|
|
213
|
-
/>
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## TypeScript
|
|
217
|
-
|
|
218
|
-
Full type safety with TypeScript:
|
|
219
|
-
|
|
220
|
-
```tsx
|
|
221
|
-
import { useForm } from "react-hook-form";
|
|
222
|
-
import { Input, Button } from "@nomos-ui/react";
|
|
223
|
-
|
|
224
|
-
interface FormData {
|
|
225
|
-
email: string;
|
|
226
|
-
password: string;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function LoginForm() {
|
|
230
|
-
const form = useForm<FormData>();
|
|
231
|
-
|
|
232
|
-
const onSubmit = (data: FormData) => {
|
|
233
|
-
console.log(data);
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
return (
|
|
237
|
-
<Form form={form} onSubmit={onSubmit}>
|
|
238
|
-
<Input<FormData> name="email" label="Email" />
|
|
239
|
-
<Input<FormData> name="password" label="Password" type="password" />
|
|
240
|
-
<Button type="submit">Sign In</Button>
|
|
241
|
-
</Form>
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## Roadmap
|
|
247
|
-
|
|
248
|
-
- [ ] `@nomos-ui/vue` - Vue 3 components
|
|
249
|
-
- [ ] `@nomos-ui/svelte` - Svelte components
|
|
250
|
-
- [ ] Advanced components (Autocomplete, MultiSelect, RichText)
|
|
251
|
-
- [ ] Form builder/generator
|
|
252
|
-
- [ ] Headless components
|
|
253
|
-
- [ ] Theme presets
|
|
254
|
-
|
|
255
|
-
## Philosophy
|
|
256
|
-
|
|
257
|
-
**νόμος** (nomos) means "law" or "order" in Greek. In the New Testament, it often refers to divine law and principles. Just as law brings order to society, Nomos UI brings order to your forms - enforcing validation rules, maintaining structure, and ensuring data integrity.
|
|
258
|
-
|
|
259
|
-
> "For the law was given through Moses; grace and truth came through Jesus Christ." - John 1:17
|
|
260
|
-
|
|
261
|
-
We believe forms should be both functional and graceful, enforcing rules while remaining pleasant to use.
|
|
262
|
-
|
|
263
|
-
## Contributing
|
|
264
|
-
|
|
265
|
-
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) first.
|
|
266
|
-
|
|
267
|
-
## License
|
|
268
|
-
|
|
269
|
-
MIT ©
|
|
270
|
-
|
|
271
|
-
## Credits
|
|
272
|
-
|
|
273
|
-
Built on top of [React Hook Form](https://react-hook-form.com/) - the most performant, flexible form library for React.
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
**Made with ♥︎ for developers who value order in chaos**
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a single file entry managed by `FileInput`.
|
|
3
|
+
*
|
|
4
|
+
* The `id` is a stable random key generated on ingestion so React
|
|
5
|
+
* reconciliation stays correct even when the same filename is added twice.
|
|
6
|
+
*/
|
|
7
|
+
export type FileInputFile = {
|
|
8
|
+
/** The native browser `File` object. */
|
|
9
|
+
file: File;
|
|
10
|
+
/**
|
|
11
|
+
* Stable random identifier assigned when the file is added.
|
|
12
|
+
* Use this as the React `key` and to target a specific file for removal.
|
|
13
|
+
*/
|
|
14
|
+
id: string;
|
|
15
|
+
};
|
|
16
|
+
export type FileInputProps = {
|
|
17
|
+
/** Additional class name for the root container. */
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Additional class name applied to the dashed dropzone area. */
|
|
20
|
+
dropzoneClassName?: string;
|
|
21
|
+
/** Label text rendered above the dropzone. */
|
|
22
|
+
label?: string;
|
|
23
|
+
/** Additional class name for the label row wrapper. */
|
|
24
|
+
labelClassName?: string;
|
|
25
|
+
/** Marks the field as required for native form validation. */
|
|
26
|
+
required?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* When `true`, renders a red asterisk next to the label.
|
|
29
|
+
* Only has an effect when `required` is also `true`.
|
|
30
|
+
*/
|
|
31
|
+
showRequiredSign?: boolean;
|
|
32
|
+
/** Helper text rendered below the file list (hidden when `error` is set). */
|
|
33
|
+
tip?: string;
|
|
34
|
+
/** Validation error message rendered below the file list in destructive color. */
|
|
35
|
+
error?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Accepted file types forwarded directly to the native `<input accept>`.
|
|
38
|
+
* Supports MIME types (e.g. `"image/*"`) and extensions (e.g. `".pdf,.docx"`).
|
|
39
|
+
*/
|
|
40
|
+
accept?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Human-readable format list shown inside the dropzone subtitle.
|
|
43
|
+
* Keep in sync with `accept` but written for end-users (e.g. `"JPEG, PNG, PDF"`).
|
|
44
|
+
*/
|
|
45
|
+
acceptLabel?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Maximum allowed file size **in bytes**.
|
|
48
|
+
* Files that exceed this limit are rejected — `onError` is called and the
|
|
49
|
+
* file is NOT added to the list.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // 50 MB limit
|
|
53
|
+
* maxSize={50 * 1024 * 1024}
|
|
54
|
+
*/
|
|
55
|
+
maxSize?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Allow the user to pick or drop more than one file at a time.
|
|
58
|
+
* When `false` (default) a new selection replaces the existing file.
|
|
59
|
+
*/
|
|
60
|
+
multiple?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Controlled list of currently accepted files.
|
|
63
|
+
* Pass an empty array (`[]`) to represent an empty / reset state.
|
|
64
|
+
*/
|
|
65
|
+
value?: FileInputFile[];
|
|
66
|
+
/**
|
|
67
|
+
* Fired whenever the file list changes — either a file was added or removed.
|
|
68
|
+
*
|
|
69
|
+
* @param files - The complete updated file list.
|
|
70
|
+
*/
|
|
71
|
+
onChange?: (files: FileInputFile[]) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Fired when a file is rejected before being added to the list.
|
|
74
|
+
* Common reasons: file exceeds `maxSize`.
|
|
75
|
+
*
|
|
76
|
+
* @param message - A human-readable description of why the file was rejected.
|
|
77
|
+
*/
|
|
78
|
+
onError?: (message: string) => void;
|
|
79
|
+
/** Disables the dropzone click / drag and all per-file remove buttons. */
|
|
80
|
+
disabled?: boolean;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* A file-upload input that follows the shadcn component ideology.
|
|
84
|
+
*
|
|
85
|
+
* Features:
|
|
86
|
+
* - Semantic design tokens (`foreground`, `muted-foreground`, `destructive`, …)
|
|
87
|
+
* - Label + required sign + tip + error pattern identical to `Input` / `Textarea`
|
|
88
|
+
* - Drag-and-drop with a clear visual drag state
|
|
89
|
+
* - Per-file rows listing name, size, and a remove button
|
|
90
|
+
* - Single or multiple file support
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* const [files, setFiles] = React.useState<FileInputFile[]>([]);
|
|
95
|
+
*
|
|
96
|
+
* <FileInput
|
|
97
|
+
* label="Resume"
|
|
98
|
+
* required
|
|
99
|
+
* showRequiredSign
|
|
100
|
+
* accept=".pdf,.doc,.docx"
|
|
101
|
+
* acceptLabel="PDF, DOC, DOCX"
|
|
102
|
+
* maxSize={50 * 1024 * 1024}
|
|
103
|
+
* tip="Max 50 MB per file."
|
|
104
|
+
* value={files}
|
|
105
|
+
* onChange={setFiles}
|
|
106
|
+
* onError={(msg) => toast.error(msg)}
|
|
107
|
+
* />
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export default function FileInput({ className, dropzoneClassName, label, labelClassName, required, showRequiredSign, tip, error, accept, acceptLabel, maxSize, multiple, value, onChange, onError, disabled, }: FileInputProps): import("react/jsx-runtime").JSX.Element;
|
|
111
|
+
//# sourceMappingURL=file-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-input.d.ts","sourceRoot":"","sources":["../../src/components/file-input.tsx"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,wCAAwC;IACxC,IAAI,EAAE,IAAI,CAAC;IACX;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,aAAa,EAAE,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAcF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,SAAS,EACT,iBAAiB,EACjB,KAAK,EACL,cAAc,EACd,QAAgB,EAChB,gBAAwB,EACxB,GAAG,EACH,KAAK,EACL,MAAM,EACN,WAAW,EACX,OAAO,EACP,QAAgB,EAChB,KAAU,EACV,QAAQ,EACR,OAAO,EACP,QAAgB,GACjB,EAAE,cAAc,2CAyJhB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = FileInput;
|
|
37
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
const React = __importStar(require("react"));
|
|
39
|
+
const lucide_react_1 = require("lucide-react");
|
|
40
|
+
const utils_1 = require("../utils/shadcn-ui/utils");
|
|
41
|
+
/** Converts a raw byte count into a human-readable string (B / KB / MB). */
|
|
42
|
+
function formatBytes(bytes) {
|
|
43
|
+
if (bytes < 1024)
|
|
44
|
+
return `${bytes} B`;
|
|
45
|
+
if (bytes < 1024 * 1024)
|
|
46
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
47
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
48
|
+
}
|
|
49
|
+
/** Generates a short random alphanumeric ID used as a stable React key. */
|
|
50
|
+
function generateId() {
|
|
51
|
+
return Math.random().toString(36).slice(2, 9);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A file-upload input that follows the shadcn component ideology.
|
|
55
|
+
*
|
|
56
|
+
* Features:
|
|
57
|
+
* - Semantic design tokens (`foreground`, `muted-foreground`, `destructive`, …)
|
|
58
|
+
* - Label + required sign + tip + error pattern identical to `Input` / `Textarea`
|
|
59
|
+
* - Drag-and-drop with a clear visual drag state
|
|
60
|
+
* - Per-file rows listing name, size, and a remove button
|
|
61
|
+
* - Single or multiple file support
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* const [files, setFiles] = React.useState<FileInputFile[]>([]);
|
|
66
|
+
*
|
|
67
|
+
* <FileInput
|
|
68
|
+
* label="Resume"
|
|
69
|
+
* required
|
|
70
|
+
* showRequiredSign
|
|
71
|
+
* accept=".pdf,.doc,.docx"
|
|
72
|
+
* acceptLabel="PDF, DOC, DOCX"
|
|
73
|
+
* maxSize={50 * 1024 * 1024}
|
|
74
|
+
* tip="Max 50 MB per file."
|
|
75
|
+
* value={files}
|
|
76
|
+
* onChange={setFiles}
|
|
77
|
+
* onError={(msg) => toast.error(msg)}
|
|
78
|
+
* />
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
function FileInput({ className, dropzoneClassName, label, labelClassName, required = false, showRequiredSign = false, tip, error, accept, acceptLabel, maxSize, multiple = false, value = [], onChange, onError, disabled = false, }) {
|
|
82
|
+
const inputRef = React.useRef(null);
|
|
83
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
84
|
+
function processFiles(rawFiles) {
|
|
85
|
+
if (!rawFiles || disabled)
|
|
86
|
+
return;
|
|
87
|
+
const incoming = [];
|
|
88
|
+
Array.from(rawFiles).forEach((file) => {
|
|
89
|
+
if (maxSize && file.size > maxSize) {
|
|
90
|
+
onError?.(`"${file.name}" exceeds the maximum size of ${formatBytes(maxSize)}.`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
incoming.push({ file, id: generateId() });
|
|
94
|
+
});
|
|
95
|
+
if (!incoming.length)
|
|
96
|
+
return;
|
|
97
|
+
const next = multiple ? [...value, ...incoming] : [incoming[0]];
|
|
98
|
+
onChange?.(next);
|
|
99
|
+
}
|
|
100
|
+
function removeFile(id) {
|
|
101
|
+
onChange?.(value.filter((f) => f.id !== id));
|
|
102
|
+
if (inputRef.current)
|
|
103
|
+
inputRef.current.value = "";
|
|
104
|
+
}
|
|
105
|
+
function onDragOver(e) {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
if (!disabled)
|
|
108
|
+
setIsDragging(true);
|
|
109
|
+
}
|
|
110
|
+
function onDragLeave(e) {
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
setIsDragging(false);
|
|
113
|
+
}
|
|
114
|
+
function onDrop(e) {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
setIsDragging(false);
|
|
117
|
+
processFiles(e.dataTransfer.files);
|
|
118
|
+
}
|
|
119
|
+
const hasFiles = value.length > 0;
|
|
120
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)("w-full space-y-1.5", className), children: [label && ((0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)("flex items-center gap-1", labelClassName), children: [(0, jsx_runtime_1.jsx)("label", { className: "text-sm font-bold text-foreground", children: label }), required && showRequiredSign && ((0, jsx_runtime_1.jsx)(lucide_react_1.AsteriskIcon, { className: "h-3 w-3 text-destructive" }))] })), (0, jsx_runtime_1.jsxs)("div", { onDragOver: onDragOver, onDragLeave: onDragLeave, onDrop: onDrop, className: (0, utils_1.cn)("relative flex flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed px-6 py-8 text-center transition-colors", "border-input bg-transparent", !disabled && "hover:border-ring/50 hover:bg-accent/40", isDragging && "border-ring bg-accent/60", error && "border-destructive", disabled && "cursor-not-allowed opacity-50", dropzoneClassName), children: [(0, jsx_runtime_1.jsx)("input", { ref: inputRef, type: "file", accept: accept, multiple: multiple, disabled: disabled, className: "absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed", onChange: (e) => processFiles(e.target.files) }), (0, jsx_runtime_1.jsx)(lucide_react_1.CloudUploadIcon, { className: (0, utils_1.cn)("h-8 w-8 transition-colors", isDragging ? "text-ring" : "text-muted-foreground"), strokeWidth: 1.5 }), (0, jsx_runtime_1.jsxs)("div", { className: "pointer-events-none select-none space-y-1", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Choose a file or drag & drop it here" }), (acceptLabel || maxSize) && ((0, jsx_runtime_1.jsx)("p", { className: "text-xs text-muted-foreground", children: [acceptLabel, maxSize ? `up to ${formatBytes(maxSize)}` : null]
|
|
121
|
+
.filter(Boolean)
|
|
122
|
+
.join(", ") }))] }), (0, jsx_runtime_1.jsx)("span", { className: "pointer-events-none select-none inline-flex items-center rounded-md border border-input bg-background px-3 py-1.5 text-sm font-medium shadow-xs", children: "Browse File" })] }), hasFiles && ((0, jsx_runtime_1.jsx)("ul", { className: "space-y-2", children: value.map(({ file, id }) => ((0, jsx_runtime_1.jsxs)("li", { className: "flex items-center gap-3 rounded-lg border border-input bg-background px-3 py-2.5 shadow-xs", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-input bg-muted", children: (0, jsx_runtime_1.jsx)(lucide_react_1.FileIcon, { className: "h-4 w-4 text-muted-foreground" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-sm font-medium text-foreground", children: file.name }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-muted-foreground", children: formatBytes(file.size) })] }), !disabled && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => removeFile(id), className: "shrink-0 rounded-full p-1 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "aria-label": `Remove ${file.name}`, children: (0, jsx_runtime_1.jsx)(lucide_react_1.XIcon, { className: "h-4 w-4" }) }))] }, id))) })), tip && !error && ((0, jsx_runtime_1.jsx)("p", { className: "text-xs text-muted-foreground tip-message", children: tip })), error && ((0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-destructive error-message", children: ["*", error] }))] }));
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=file-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-input.js","sourceRoot":"","sources":["../../src/components/file-input.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Control, FieldValues, Path } from "react-hook-form";
|
|
2
|
+
import { FileInputProps } from "./file-input";
|
|
3
|
+
/**
|
|
4
|
+
* Props for the `FormFileInput` component with type-safe form integration.
|
|
5
|
+
*
|
|
6
|
+
* Omits the props that are managed internally by React Hook Form
|
|
7
|
+
* (`value`, `onChange`, `onError`, `error`) and adds the RHF-specific ones.
|
|
8
|
+
*/
|
|
9
|
+
export type FormFileInputProps<TFieldValues extends FieldValues> = Omit<FileInputProps, "value" | "onChange" | "onError" | "error"> & {
|
|
10
|
+
/** React Hook Form control instance. */
|
|
11
|
+
control: Control<TFieldValues>;
|
|
12
|
+
/** The name of the field (type-safe, must match the form schema). */
|
|
13
|
+
name: Path<TFieldValues>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A file-upload field that integrates `FileInput` with React Hook Form.
|
|
17
|
+
*
|
|
18
|
+
* - Wires `value`, `onChange`, and `error` automatically via `Controller`
|
|
19
|
+
* - File rejection errors (e.g. size exceeded) are forwarded to RHF via `setError`
|
|
20
|
+
* - Supports the `required` rule out of the box
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* type FormData = {
|
|
25
|
+
* resume: FileInputFile[];
|
|
26
|
+
* };
|
|
27
|
+
*
|
|
28
|
+
* const { control } = useForm<FormData>({ defaultValues: { resume: [] } });
|
|
29
|
+
*
|
|
30
|
+
* <FormFileInput
|
|
31
|
+
* control={control}
|
|
32
|
+
* name="resume"
|
|
33
|
+
* label="Resume"
|
|
34
|
+
* required
|
|
35
|
+
* showRequiredSign
|
|
36
|
+
* accept=".pdf,.doc,.docx"
|
|
37
|
+
* acceptLabel="PDF, DOC, DOCX"
|
|
38
|
+
* maxSize={50 * 1024 * 1024}
|
|
39
|
+
* tip="Max 50 MB per file."
|
|
40
|
+
* />
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export default function FormFileInput<TFieldValues extends FieldValues>({ control, name, ...props }: FormFileInputProps<TFieldValues>): import("react/jsx-runtime").JSX.Element;
|
|
44
|
+
//# sourceMappingURL=form-file-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-file-input.d.ts","sourceRoot":"","sources":["../../src/components/form-file-input.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAc,WAAW,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGzD;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,CAAC,YAAY,SAAS,WAAW,IAAI,IAAI,CACrE,cAAc,EACd,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAC3C,GAAG;IACF,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,qEAAqE;IACrE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;CAC1B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,YAAY,SAAS,WAAW,EAAE,EACtE,OAAO,EACP,IAAI,EACJ,GAAG,KAAK,EACT,EAAE,kBAAkB,CAAC,YAAY,CAAC,2CAwBlC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = FormFileInput;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const react_hook_form_1 = require("react-hook-form");
|
|
9
|
+
const file_input_1 = __importDefault(require("./file-input"));
|
|
10
|
+
const form_helpers_1 = require("../utils/helpers/form.helpers");
|
|
11
|
+
/**
|
|
12
|
+
* A file-upload field that integrates `FileInput` with React Hook Form.
|
|
13
|
+
*
|
|
14
|
+
* - Wires `value`, `onChange`, and `error` automatically via `Controller`
|
|
15
|
+
* - File rejection errors (e.g. size exceeded) are forwarded to RHF via `setError`
|
|
16
|
+
* - Supports the `required` rule out of the box
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* type FormData = {
|
|
21
|
+
* resume: FileInputFile[];
|
|
22
|
+
* };
|
|
23
|
+
*
|
|
24
|
+
* const { control } = useForm<FormData>({ defaultValues: { resume: [] } });
|
|
25
|
+
*
|
|
26
|
+
* <FormFileInput
|
|
27
|
+
* control={control}
|
|
28
|
+
* name="resume"
|
|
29
|
+
* label="Resume"
|
|
30
|
+
* required
|
|
31
|
+
* showRequiredSign
|
|
32
|
+
* accept=".pdf,.doc,.docx"
|
|
33
|
+
* acceptLabel="PDF, DOC, DOCX"
|
|
34
|
+
* maxSize={50 * 1024 * 1024}
|
|
35
|
+
* tip="Max 50 MB per file."
|
|
36
|
+
* />
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function FormFileInput({ control, name, ...props }) {
|
|
40
|
+
return ((0, jsx_runtime_1.jsx)(react_hook_form_1.Controller, { control: control, name: name, rules: {
|
|
41
|
+
required: props.required ? "Required" : false,
|
|
42
|
+
}, render: ({ field: { onChange, value }, formState: { errors } }) => ((0, jsx_runtime_1.jsx)(file_input_1.default, { ...props, value: value ?? [], onChange: onChange, onError: (errorMessage) => {
|
|
43
|
+
control.setError(name, {
|
|
44
|
+
type: "manual",
|
|
45
|
+
message: errorMessage,
|
|
46
|
+
});
|
|
47
|
+
}, error: (0, form_helpers_1.getNestedErrorMessage)(errors, name) })) }));
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=form-file-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-file-input.js","sourceRoot":"","sources":["../../src/components/form-file-input.tsx"],"names":[],"mappings":";;;;;AAgDA,gCA4BC;;AA5ED,qDAAyE;AACzE,8DAAyD;AACzD,gEAAsE;AAkBtE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,SAAwB,aAAa,CAAmC,EACtE,OAAO,EACP,IAAI,EACJ,GAAG,KAAK,EACyB;IACjC,OAAO,CACL,uBAAC,4BAAU,IACT,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,KAAK,EAAE;YACL,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK;SAC9C,EACD,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CACjE,uBAAC,oBAAS,OACJ,KAAK,EACT,KAAK,EAAE,KAAK,IAAI,EAAE,EAClB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,CAAC,YAAoB,EAAE,EAAE;gBAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE;oBACrB,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,YAAY;iBACtB,CAAC,CAAC;YACL,CAAC,EACD,KAAK,EAAE,IAAA,oCAAqB,EAAC,MAAM,EAAE,IAAI,CAAC,GAC1C,CACH,GACD,CACH,CAAC;AACJ,CAAC"}
|
package/dist/exports/index.d.ts
CHANGED
|
@@ -7,9 +7,13 @@ import Textarea from "../components/textarea";
|
|
|
7
7
|
import FormTextarea from "../components/form-textarea";
|
|
8
8
|
import FormImageInput from "../components/form-image-input";
|
|
9
9
|
import ImageInput from "../components/image-input";
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
import FileInput, { FileInputProps, FileInputFile } from "../components/file-input";
|
|
11
|
+
import FormFileInput, { FormFileInputProps } from "../components/form-file-input";
|
|
12
|
+
export { Button, FormInput, Input, Select, FormSelect, Textarea, FormTextarea, ImageInput, FormImageInput, FileInput, FormFileInput, };
|
|
13
|
+
export type { SelectOption, SelectProps, FormSelectProps, FormFileInputProps, FileInputProps, FileInputFile, };
|
|
12
14
|
export * from "../components/input";
|
|
13
15
|
export * from "../components/form-input";
|
|
14
16
|
export * from "../components/button";
|
|
17
|
+
export * from "../components/file-input";
|
|
18
|
+
export * from "../components/form-file-input";
|
|
15
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exports/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,SAAS,MAAM,0BAA0B,CAAC;AACjD,OAAO,KAAK,MAAM,qBAAqB,CAAC;AACxC,OAAO,MAAM,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,UAAU,EAAE,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,YAAY,MAAM,6BAA6B,CAAC;AACvD,OAAO,cAAc,MAAM,gCAAgC,CAAC;AAC5D,OAAO,UAAU,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exports/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,SAAS,MAAM,0BAA0B,CAAC;AACjD,OAAO,KAAK,MAAM,qBAAqB,CAAC;AACxC,OAAO,MAAM,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,UAAU,EAAE,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,YAAY,MAAM,6BAA6B,CAAC;AACvD,OAAO,cAAc,MAAM,gCAAgC,CAAC;AAC5D,OAAO,UAAU,MAAM,2BAA2B,CAAC;AACnD,OAAO,SAAS,EAAE,EAChB,cAAc,EACd,aAAa,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,aAAa,EAAE,EACpB,kBAAkB,EACnB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,MAAM,EACN,SAAS,EACT,KAAK,EACL,MAAM,EACN,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,cAAc,EACd,SAAS,EACT,aAAa,GACd,CAAC;AACF,YAAY,EACV,YAAY,EACZ,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,aAAa,GACd,CAAC;AACF,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC"}
|
package/dist/exports/index.js
CHANGED
|
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.FormImageInput = exports.ImageInput = exports.FormTextarea = exports.Textarea = exports.FormSelect = exports.Select = exports.Input = exports.FormInput = exports.Button = void 0;
|
|
20
|
+
exports.FormFileInput = exports.FileInput = exports.FormImageInput = exports.ImageInput = exports.FormTextarea = exports.Textarea = exports.FormSelect = exports.Select = exports.Input = exports.FormInput = exports.Button = void 0;
|
|
21
21
|
const button_1 = __importDefault(require("../components/button"));
|
|
22
22
|
exports.Button = button_1.default;
|
|
23
23
|
const form_input_1 = __importDefault(require("../components/form-input"));
|
|
@@ -36,7 +36,13 @@ const form_image_input_1 = __importDefault(require("../components/form-image-inp
|
|
|
36
36
|
exports.FormImageInput = form_image_input_1.default;
|
|
37
37
|
const image_input_1 = __importDefault(require("../components/image-input"));
|
|
38
38
|
exports.ImageInput = image_input_1.default;
|
|
39
|
+
const file_input_1 = __importDefault(require("../components/file-input"));
|
|
40
|
+
exports.FileInput = file_input_1.default;
|
|
41
|
+
const form_file_input_1 = __importDefault(require("../components/form-file-input"));
|
|
42
|
+
exports.FormFileInput = form_file_input_1.default;
|
|
39
43
|
__exportStar(require("../components/input"), exports);
|
|
40
44
|
__exportStar(require("../components/form-input"), exports);
|
|
41
45
|
__exportStar(require("../components/button"), exports);
|
|
46
|
+
__exportStar(require("../components/file-input"), exports);
|
|
47
|
+
__exportStar(require("../components/form-file-input"), exports);
|
|
42
48
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exports/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,kEAA0C;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/exports/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,kEAA0C;AAkBxC,iBAlBK,gBAAM,CAkBL;AAjBR,0EAAiD;AAkB/C,oBAlBK,oBAAS,CAkBL;AAjBX,gEAAwC;AAkBtC,gBAlBK,eAAK,CAkBL;AAjBP,kEAAyE;AAkBvE,iBAlBK,gBAAM,CAkBL;AAjBR,4EAAwE;AAkBtE,qBAlBK,qBAAU,CAkBL;AAjBZ,sEAA8C;AAkB5C,mBAlBK,kBAAQ,CAkBL;AAjBV,gFAAuD;AAkBrD,uBAlBK,uBAAY,CAkBL;AAjBd,sFAA4D;AAmB1D,yBAnBK,0BAAc,CAmBL;AAlBhB,4EAAmD;AAiBjD,qBAjBK,qBAAU,CAiBL;AAhBZ,0EAGkC;AAehC,oBAlBK,oBAAS,CAkBL;AAdX,oFAEuC;AAarC,wBAfK,yBAAa,CAeL;AAUf,sDAAoC;AACpC,2DAAyC;AACzC,uDAAqC;AACrC,2DAAyC;AACzC,gEAA8C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nomos-ui/form",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "The Shadcn library for building robust React forms",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/exports/index.js",
|
|
@@ -11,33 +11,15 @@
|
|
|
11
11
|
"import": "./dist/exports/index.js",
|
|
12
12
|
"require": "./dist/exports/index.js"
|
|
13
13
|
},
|
|
14
|
-
"./hooks": {
|
|
15
|
-
"types": "./dist/hooks/index.d.ts",
|
|
16
|
-
"import": "./dist/hooks/index.js",
|
|
17
|
-
"require": "./dist/hooks/index.js"
|
|
18
|
-
},
|
|
19
|
-
"./modals": {
|
|
20
|
-
"types": "./dist/exports/modals.d.ts",
|
|
21
|
-
"import": "./dist/exports/modals.js",
|
|
22
|
-
"require": "./dist/exports/modals.js"
|
|
23
|
-
},
|
|
24
14
|
"./utils": {
|
|
25
|
-
"types": "./dist/utils/index.d.ts",
|
|
26
|
-
"import": "./dist/utils/index.js",
|
|
27
|
-
"require": "./dist/utils/index.js"
|
|
28
|
-
},
|
|
29
|
-
"./types": {
|
|
30
|
-
"types": "./dist/types/index.d.ts",
|
|
31
|
-
"import": "./dist/types/index.js",
|
|
32
|
-
"require": "./dist/types/index.js"
|
|
15
|
+
"types": "./dist/exports/utils/index.d.ts",
|
|
16
|
+
"import": "./dist/exports/utils/index.js",
|
|
17
|
+
"require": "./dist/exports/utils/index.js"
|
|
33
18
|
}
|
|
34
19
|
},
|
|
35
20
|
"typesVersions": {
|
|
36
21
|
"*": {
|
|
37
|
-
"
|
|
38
|
-
"hooks": ["./dist/hooks/index.d.ts"],
|
|
39
|
-
"utils": ["./dist/exports/utils/index.d.ts"],
|
|
40
|
-
"types": ["./dist/types/index.d.ts"]
|
|
22
|
+
"utils": ["./dist/exports/utils/index.d.ts"]
|
|
41
23
|
}
|
|
42
24
|
},
|
|
43
25
|
"scripts": {
|
|
@@ -79,7 +61,7 @@
|
|
|
79
61
|
"bugs": {
|
|
80
62
|
"url": "https://github.com/uanela/nomos-ui/issues"
|
|
81
63
|
},
|
|
82
|
-
"homepage": "https://github.com/uanela/nomos-ui
|
|
64
|
+
"homepage": "https://github.com/uanela/nomos-ui",
|
|
83
65
|
"files": ["dist", "README.md", "LICENSE"],
|
|
84
66
|
"sideEffects": false,
|
|
85
67
|
"packageManager": "pnpm@10.13.1",
|