@k3-universe/react-kit 0.0.12 → 0.0.13

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 (41) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +5393 -1357
  4. package/dist/kit/builder/form/components/FormBuilder.d.ts +21 -0
  5. package/dist/kit/builder/form/components/FormBuilder.d.ts.map +1 -1
  6. package/dist/kit/builder/form/components/fields/FileField.d.ts +1 -1
  7. package/dist/kit/builder/form/components/fields/FileField.d.ts.map +1 -1
  8. package/dist/kit/builder/stack-dialog/hooks.d.ts +2 -5
  9. package/dist/kit/builder/stack-dialog/hooks.d.ts.map +1 -1
  10. package/dist/kit/builder/stack-dialog/index.d.ts +3 -3
  11. package/dist/kit/builder/stack-dialog/index.d.ts.map +1 -1
  12. package/dist/kit/builder/stack-dialog/renderer.d.ts.map +1 -1
  13. package/dist/kit/builder/stack-dialog/types.d.ts +1 -0
  14. package/dist/kit/builder/stack-dialog/types.d.ts.map +1 -1
  15. package/dist/kit/components/fileuploader/FileUploader.d.ts +4 -0
  16. package/dist/kit/components/fileuploader/FileUploader.d.ts.map +1 -0
  17. package/dist/kit/components/fileuploader/index.d.ts +4 -0
  18. package/dist/kit/components/fileuploader/index.d.ts.map +1 -0
  19. package/dist/kit/components/fileuploader/types.d.ts +63 -0
  20. package/dist/kit/components/fileuploader/types.d.ts.map +1 -0
  21. package/dist/kit/themes/clean-slate.css +60 -0
  22. package/dist/kit/themes/default.css +60 -0
  23. package/dist/kit/themes/minimal-modern.css +60 -0
  24. package/dist/kit/themes/spotify.css +60 -0
  25. package/package.json +2 -1
  26. package/src/index.ts +2 -0
  27. package/src/kit/builder/form/components/FormBuilder.tsx +56 -0
  28. package/src/kit/builder/form/components/fields/FileField.tsx +17 -5
  29. package/src/kit/builder/stack-dialog/hooks.ts +2 -1
  30. package/src/kit/builder/stack-dialog/index.ts +8 -3
  31. package/src/kit/builder/stack-dialog/renderer.tsx +2 -2
  32. package/src/kit/builder/stack-dialog/types.ts +2 -0
  33. package/src/kit/components/fileuploader/FileUploader.tsx +488 -0
  34. package/src/kit/components/fileuploader/index.ts +3 -0
  35. package/src/kit/components/fileuploader/types.ts +73 -0
  36. package/src/stories/FileUploader.stories.tsx +166 -0
  37. package/src/stories/kit/builder/Form.ArrayLayouts.stories.tsx +1 -1
  38. package/src/stories/kit/builder/Form.Autocomplete.stories.tsx +5 -5
  39. package/src/stories/kit/builder/Form.DateTime.stories.tsx +1 -1
  40. package/src/stories/kit/builder/Form.Files.stories.tsx +125 -0
  41. package/src/stories/kit/builder/Form.Time.stories.tsx +1 -1
@@ -0,0 +1,166 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { FileUploader } from '../index';
4
+ import type { FileRecord } from '../kit/components/fileuploader/types';
5
+
6
+ // ---- helpers
7
+ function isImage(name?: string, type?: string) {
8
+ const t = (type || '').toLowerCase();
9
+ const ext = (name?.split('.')?.pop() || '').toLowerCase();
10
+ return t.startsWith('image/') || ['png','jpg','jpeg','webp','gif','bmp','svg','heic','heif'].includes(ext);
11
+ }
12
+
13
+ function mockUploaderFactory(options?: { minMs?: number; maxMs?: number; failRate?: number }) {
14
+ const minMs = options?.minMs ?? 800;
15
+ const maxMs = options?.maxMs ?? 1800;
16
+ const failRate = options?.failRate ?? 0;
17
+ return async (file: File, onProgress: (p: number) => void): Promise<Partial<FileRecord>> => {
18
+ const willFail = Math.random() < failRate;
19
+ const total = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
20
+ const start = Date.now();
21
+ return new Promise((resolve, reject) => {
22
+ const iv = window.setInterval(() => {
23
+ const elapsed = Date.now() - start;
24
+ const pct = Math.min(100, Math.round((elapsed / total) * 100));
25
+ onProgress(pct);
26
+ if (pct >= 100) {
27
+ window.clearInterval(iv);
28
+ if (willFail) {
29
+ reject(new Error('Simulated upload error'));
30
+ } else {
31
+ // Simulate server response: echo a URL
32
+ const url = isImage(file.name, file.type)
33
+ ? URL.createObjectURL(file)
34
+ : undefined;
35
+ resolve({ url, id: `${file.name}-${Date.now()}` });
36
+ }
37
+ }
38
+ }, 150);
39
+ });
40
+ };
41
+ }
42
+
43
+ const meta: Meta<typeof FileUploader> = {
44
+ title: 'Kit/Components/FileUploader',
45
+ component: FileUploader,
46
+ parameters: {
47
+ centered: false,
48
+ docs: {
49
+ description: {
50
+ component:
51
+ 'Upload files with drag-and-drop, thumbnails, progress, success/error indicators, download and remove actions. Uses react-dropzone and shadcn/ui.',
52
+ },
53
+ },
54
+ },
55
+ argTypes: {
56
+ onRetry: { action: 'onRetry' },
57
+ onRetryAll: { action: 'onRetryAll' },
58
+ },
59
+ args: {
60
+ multiple: true,
61
+ withDownload: true,
62
+ layout: 'grid',
63
+ placeholder: 'Drag and drop files here, or click Select files',
64
+ },
65
+ };
66
+ export default meta;
67
+
68
+ type Story = StoryObj<typeof FileUploader>;
69
+
70
+ export const ImagesGrid: Story = {
71
+ name: 'Images • Grid layout',
72
+ args: {
73
+ accept: { 'image/*': [] },
74
+ maxFiles: 8,
75
+ uploader: mockUploaderFactory({ minMs: 700, maxMs: 1600, failRate: 0 }),
76
+ defaultValue: [
77
+ {
78
+ id: 'p1',
79
+ name: 'mountain.jpg',
80
+ url: 'https://picsum.photos/seed/mountain/600/400',
81
+ size: 123456,
82
+ type: 'image/jpeg',
83
+ status: 'success',
84
+ progress: 100,
85
+ },
86
+ {
87
+ id: 'p2',
88
+ name: 'forest.jpg',
89
+ url: 'https://picsum.photos/seed/forest/600/400',
90
+ size: 234567,
91
+ type: 'image/jpeg',
92
+ status: 'success',
93
+ progress: 100,
94
+ },
95
+ ] satisfies FileRecord[],
96
+ },
97
+ };
98
+
99
+ export const MixedList: Story = {
100
+ name: 'Mixed files • List layout',
101
+ args: {
102
+ layout: 'list',
103
+ multiple: true,
104
+ maxFiles: 5,
105
+ uploader: mockUploaderFactory({ minMs: 900, maxMs: 2000, failRate: 0.1 }),
106
+ },
107
+ };
108
+
109
+ export const SingleFile: Story = {
110
+ name: 'Single file',
111
+ args: {
112
+ multiple: false,
113
+ maxFiles: 1,
114
+ accept: undefined,
115
+ uploader: mockUploaderFactory({ failRate: 0 }),
116
+ layout: 'grid',
117
+ },
118
+ };
119
+
120
+ export const ErrorSimulation: Story = {
121
+ name: 'Error simulation (50% fail)',
122
+ args: {
123
+ multiple: true,
124
+ uploader: mockUploaderFactory({ minMs: 600, maxMs: 1200, failRate: 0.5 }),
125
+ maxFiles: 6,
126
+ },
127
+ };
128
+
129
+ export const RetryAllDemo: Story = {
130
+ name: 'Retry all failed (demo)',
131
+ args: {
132
+ multiple: true,
133
+ uploader: mockUploaderFactory({ minMs: 600, maxMs: 1200, failRate: 0.5 }),
134
+ maxFiles: 6,
135
+ layout: 'list',
136
+ },
137
+ };
138
+
139
+ export const WithDefaultServerFiles: Story = {
140
+ name: 'Edit mode with default server files',
141
+ args: {
142
+ multiple: true,
143
+ layout: 'list',
144
+ uploader: mockUploaderFactory({ failRate: 0 }),
145
+ defaultValue: [
146
+ {
147
+ id: 'doc-1',
148
+ name: 'contract.pdf',
149
+ size: 987654,
150
+ type: 'application/pdf',
151
+ // no url -> will render as icon
152
+ status: 'success',
153
+ progress: 100,
154
+ },
155
+ {
156
+ id: 'img-1',
157
+ name: 'avatar.png',
158
+ url: 'https://picsum.photos/seed/avatar/300/300',
159
+ size: 54321,
160
+ type: 'image/png',
161
+ status: 'success',
162
+ progress: 100,
163
+ },
164
+ ] satisfies FileRecord[],
165
+ },
166
+ };
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
2
2
  import { FormBuilder, type FormBuilderProps } from '../../../kit/builder/form/components/FormBuilder'
3
3
 
4
4
  const meta: Meta<typeof FormBuilder> = {
5
- title: 'Kit/Builder/Form (Array Layouts)',
5
+ title: 'Kit/Builder/Form',
6
6
  component: FormBuilder,
7
7
  parameters: {
8
8
  controls: { expanded: true },
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
2
2
  import { FormBuilder, type FormBuilderProps } from '../../../kit/builder/form/components/FormBuilder'
3
3
 
4
4
  const meta: Meta<typeof FormBuilder> = {
5
- title: 'Kit/Builder/Form Autocomplete',
5
+ title: 'Kit/Builder/Form',
6
6
  component: FormBuilder,
7
7
  parameters: {
8
8
  controls: { expanded: true },
@@ -28,7 +28,7 @@ const CITY_OPTIONS = [
28
28
  ]
29
29
 
30
30
  export const SingleAutocomplete: Story = {
31
- name: 'Single select (clearable + custom allowed)',
31
+ name: 'Autocomplete - Single select (clearable + custom allowed)',
32
32
  args: {
33
33
  sections: [
34
34
  {
@@ -89,7 +89,7 @@ async function loadSelectedByIds(values: Array<string | number>) {
89
89
  }
90
90
 
91
91
  export const ServerEditPrefilledByIds: Story = {
92
- name: 'Server edit page (prefilled IDs)',
92
+ name: 'Autocomplete - Server edit page (prefilled IDs)',
93
93
  args: {
94
94
  sections: [
95
95
  {
@@ -135,7 +135,7 @@ export const ServerEditPrefilledByIds: Story = {
135
135
  }
136
136
 
137
137
  export const MultiAutocompleteChips: Story = {
138
- name: 'Multi select with chips',
138
+ name: 'Autocomplete - Multi select with chips',
139
139
  args: {
140
140
  sections: [
141
141
  {
@@ -172,7 +172,7 @@ export const MultiAutocompleteChips: Story = {
172
172
  }
173
173
 
174
174
  export const TaggingAutocomplete: Story = {
175
- name: 'Tagging (no options, custom values)',
175
+ name: 'Autocomplete - Tagging (no options, custom values)',
176
176
  args: {
177
177
  sections: [
178
178
  {
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
2
2
  import { FormBuilder, type FormBuilderProps } from '../../../kit/builder/form/components/FormBuilder'
3
3
 
4
4
  const meta: Meta<typeof FormBuilder> = {
5
- title: 'Kit/Builder/Form (DateTime)',
5
+ title: 'Kit/Builder/Form',
6
6
  component: FormBuilder,
7
7
  parameters: {
8
8
  controls: { expanded: true },
@@ -0,0 +1,125 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { FormBuilder, type FormBuilderProps } from '../../../kit/builder/form/components/FormBuilder'
3
+ import type { FileRecord } from '../../../kit/components/fileuploader/types'
4
+
5
+ // Simple mocked uploader with progress + optional failure
6
+ function mockUploader(options?: { minMs?: number; maxMs?: number; failRate?: number }) {
7
+ const minMs = options?.minMs ?? 700
8
+ const maxMs = options?.maxMs ?? 1600
9
+ const failRate = options?.failRate ?? 0.15
10
+ return async (file: File, onProgress: (pct: number) => void): Promise<Partial<FileRecord>> => {
11
+ const willFail = Math.random() < failRate
12
+ const total = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs
13
+ const start = Date.now()
14
+ return new Promise((resolve, reject) => {
15
+ const iv = setInterval(() => {
16
+ const elapsed = Date.now() - start
17
+ const pct = Math.min(100, Math.round((elapsed / total) * 100))
18
+ onProgress(pct)
19
+ if (pct >= 100) {
20
+ clearInterval(iv)
21
+ if (willFail) {
22
+ reject(new Error('Simulated upload error'))
23
+ } else {
24
+ resolve({ id: `${file.name}-${Date.now()}` })
25
+ }
26
+ }
27
+ }, 150)
28
+ })
29
+ }
30
+ }
31
+
32
+ const meta: Meta<typeof FormBuilder> = {
33
+ title: 'Kit/Builder/Form',
34
+ component: FormBuilder,
35
+ parameters: {
36
+ controls: { expanded: true },
37
+ backgrounds: { disable: true },
38
+ },
39
+ }
40
+
41
+ export default meta
42
+
43
+ type Story = StoryObj<typeof FormBuilder>
44
+
45
+ export const FileUploaderForm: Story = {
46
+ name: 'File Uploader',
47
+ args: {
48
+ sections: [
49
+ {
50
+ title: 'Attachments',
51
+ description: 'Demonstrates the FileUploader field integrated with FormBuilder',
52
+ variant: 'card',
53
+ layout: 'grid',
54
+ grid: { cols: 1, mdCols: 2, gap: 'gap-4' },
55
+ fields: [
56
+ {
57
+ name: 'images',
58
+ label: 'Product Images',
59
+ type: 'file',
60
+ gridCols: 2,
61
+ required: true,
62
+ // FileUploader pass-through
63
+ fileMultiple: true,
64
+ fileMaxFiles: 6,
65
+ fileAccept: { 'image/*': [] },
66
+ fileLayout: 'grid',
67
+ fileWithDownload: true,
68
+ fileUploader: mockUploader({ failRate: 0.1 }),
69
+ validation: {
70
+ minItems: { value: 1, message: 'Please upload at least 1 image' },
71
+ maxItems: { value: 6, message: 'Maximum 6 images allowed' },
72
+ },
73
+ },
74
+ {
75
+ name: 'docs',
76
+ label: 'Documents',
77
+ type: 'file',
78
+ gridCols: 2,
79
+ fileMultiple: true,
80
+ fileMaxFiles: 4,
81
+ fileAccept: { 'application/pdf': ['.pdf'], 'text/plain': ['.txt'] },
82
+ fileLayout: 'list',
83
+ fileWithDownload: false,
84
+ fileUploader: mockUploader({ failRate: 0.25 }),
85
+ validation: {
86
+ maxItems: { value: 4, message: 'Up to 4 documents' },
87
+ },
88
+ },
89
+ ],
90
+ },
91
+ ],
92
+ defaultValues: {
93
+ images: [
94
+ {
95
+ id: 'img-1',
96
+ name: 'existing-1.jpg',
97
+ url: 'https://picsum.photos/seed/fb-uploader-1/600/400',
98
+ type: 'image/jpeg',
99
+ size: 200000,
100
+ status: 'success',
101
+ progress: 100,
102
+ },
103
+ {
104
+ id: 'img-2',
105
+ name: 'existing-2.jpg',
106
+ url: 'https://picsum.photos/seed/fb-uploader-2/600/400',
107
+ type: 'image/jpeg',
108
+ size: 180000,
109
+ status: 'success',
110
+ progress: 100,
111
+ },
112
+ ] satisfies FileRecord[],
113
+ docs: [] as FileRecord[],
114
+ },
115
+ onSubmit: (data: unknown) => {
116
+ console.log('Submit (FileUploader form):', data)
117
+ },
118
+ showActions: true,
119
+ } satisfies Partial<FormBuilderProps>,
120
+ render: (args) => (
121
+ <div className="max-w-5xl mx-auto p-6">
122
+ <FormBuilder {...(args as FormBuilderProps)} />
123
+ </div>
124
+ ),
125
+ }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
2
2
  import { FormBuilder, type FormBuilderProps } from '../../../kit/builder/form/components/FormBuilder'
3
3
 
4
4
  const meta: Meta<typeof FormBuilder> = {
5
- title: 'Kit/Builder/Form (Time)',
5
+ title: 'Kit/Builder/Form',
6
6
  component: FormBuilder,
7
7
  parameters: {
8
8
  controls: { expanded: true },