@streamoid/chat-components 0.1.0 → 0.2.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.
- package/dist/index.d.ts +40 -4
- package/dist/index.js +1065 -140
- package/package.json +14 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
|
|
3
|
+
interface FieldOption {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
}
|
|
8
|
+
interface TodoItem {
|
|
9
|
+
id: string;
|
|
10
|
+
content: string;
|
|
11
|
+
status: "pending" | "completed";
|
|
12
|
+
}
|
|
13
|
+
interface DynamicFormField {
|
|
14
|
+
id: string;
|
|
15
|
+
type: "text" | "textarea" | "number" | "select" | "multiselect" | "checkbox" | "radio" | "approval" | "todo_list" | "file";
|
|
16
|
+
label?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
required?: boolean;
|
|
20
|
+
default_value?: unknown;
|
|
21
|
+
options?: FieldOption[];
|
|
22
|
+
items?: TodoItem[];
|
|
23
|
+
accept?: string;
|
|
24
|
+
multiple?: boolean;
|
|
25
|
+
}
|
|
3
26
|
interface DynamicFormProps {
|
|
4
27
|
workspaceId: string;
|
|
5
28
|
userId: string;
|
|
@@ -8,10 +31,23 @@ interface DynamicFormProps {
|
|
|
8
31
|
isActive: boolean;
|
|
9
32
|
submitted?: boolean;
|
|
10
33
|
submittedValues?: Record<string, unknown>;
|
|
11
|
-
uiProps:
|
|
34
|
+
uiProps: {
|
|
35
|
+
title?: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
fields?: DynamicFormField[];
|
|
38
|
+
submit_button_text?: string;
|
|
39
|
+
cancel_button_text?: string;
|
|
40
|
+
_componentName?: string;
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
};
|
|
12
43
|
meta?: Record<string, unknown>;
|
|
13
|
-
onSubmit: (values: Record<string, unknown>, message?: string) => void;
|
|
14
|
-
|
|
44
|
+
onSubmit: (values: Record<string, unknown>, message?: string) => Promise<void> | void;
|
|
45
|
+
onFileUpload?: (formData: FormData) => Promise<{
|
|
46
|
+
status: string;
|
|
47
|
+
data?: {
|
|
48
|
+
download_url: string;
|
|
49
|
+
};
|
|
50
|
+
}>;
|
|
15
51
|
}
|
|
16
52
|
declare function DynamicForm(props: DynamicFormProps): react_jsx_runtime.JSX.Element;
|
|
17
53
|
|
|
@@ -30,4 +66,4 @@ declare const dynamicFormManifest: {
|
|
|
30
66
|
};
|
|
31
67
|
};
|
|
32
68
|
|
|
33
|
-
export { DynamicForm, dynamicFormManifest };
|
|
69
|
+
export { DynamicForm, type DynamicFormField, type DynamicFormProps, dynamicFormManifest };
|
package/dist/index.js
CHANGED
|
@@ -1,163 +1,1088 @@
|
|
|
1
1
|
// src/DynamicForm.tsx
|
|
2
|
-
import { useState, useCallback } from "react";
|
|
3
|
-
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from "react";
|
|
3
|
+
|
|
4
|
+
// src/ui/utils.ts
|
|
5
|
+
import { clsx } from "clsx";
|
|
6
|
+
import { twMerge } from "tailwind-merge";
|
|
7
|
+
function cn(...inputs) {
|
|
8
|
+
return twMerge(clsx(inputs));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/ui/card.tsx
|
|
12
|
+
import { jsx } from "react/jsx-runtime";
|
|
13
|
+
function Card({ className, ...props }) {
|
|
14
|
+
return /* @__PURE__ */ jsx(
|
|
15
|
+
"div",
|
|
16
|
+
{
|
|
17
|
+
"data-slot": "card",
|
|
18
|
+
className: cn(
|
|
19
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border",
|
|
20
|
+
className
|
|
21
|
+
),
|
|
22
|
+
...props
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
function CardHeader({ className, ...props }) {
|
|
27
|
+
return /* @__PURE__ */ jsx(
|
|
28
|
+
"div",
|
|
29
|
+
{
|
|
30
|
+
"data-slot": "card-header",
|
|
31
|
+
className: cn(
|
|
32
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 pt-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
33
|
+
className
|
|
34
|
+
),
|
|
35
|
+
...props
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
function CardTitle({ className, ...props }) {
|
|
40
|
+
return /* @__PURE__ */ jsx(
|
|
41
|
+
"h4",
|
|
42
|
+
{
|
|
43
|
+
"data-slot": "card-title",
|
|
44
|
+
className: cn("leading-none", className),
|
|
45
|
+
...props
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function CardDescription({ className, ...props }) {
|
|
50
|
+
return /* @__PURE__ */ jsx(
|
|
51
|
+
"p",
|
|
52
|
+
{
|
|
53
|
+
"data-slot": "card-description",
|
|
54
|
+
className: cn("text-muted-foreground", className),
|
|
55
|
+
...props
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/ui/button.tsx
|
|
61
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
62
|
+
import { cva } from "class-variance-authority";
|
|
63
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
64
|
+
var buttonVariants = cva(
|
|
65
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
66
|
+
{
|
|
67
|
+
variants: {
|
|
68
|
+
variant: {
|
|
69
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
70
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
71
|
+
outline: "border bg-background text-foreground hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
72
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
73
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
74
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
75
|
+
},
|
|
76
|
+
size: {
|
|
77
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
78
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
79
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
80
|
+
icon: "size-9 rounded-md"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
defaultVariants: {
|
|
84
|
+
variant: "default",
|
|
85
|
+
size: "default"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
function Button({
|
|
90
|
+
className,
|
|
91
|
+
variant,
|
|
92
|
+
size,
|
|
93
|
+
asChild = false,
|
|
94
|
+
...props
|
|
95
|
+
}) {
|
|
96
|
+
const Comp = asChild ? Slot : "button";
|
|
97
|
+
return /* @__PURE__ */ jsx2(
|
|
98
|
+
Comp,
|
|
99
|
+
{
|
|
100
|
+
"data-slot": "button",
|
|
101
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
102
|
+
...props
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/ui/input.tsx
|
|
108
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
109
|
+
function Input({ className, type, ...props }) {
|
|
110
|
+
return /* @__PURE__ */ jsx3(
|
|
111
|
+
"input",
|
|
112
|
+
{
|
|
113
|
+
type,
|
|
114
|
+
"data-slot": "input",
|
|
115
|
+
className: cn(
|
|
116
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base bg-input-background transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
117
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
118
|
+
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
119
|
+
className
|
|
120
|
+
),
|
|
121
|
+
...props
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/ui/textarea.tsx
|
|
127
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
128
|
+
function Textarea({ className, ...props }) {
|
|
129
|
+
return /* @__PURE__ */ jsx4(
|
|
130
|
+
"textarea",
|
|
131
|
+
{
|
|
132
|
+
"data-slot": "textarea",
|
|
133
|
+
className: cn(
|
|
134
|
+
"resize-none border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-input-background px-3 py-2 text-base transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
135
|
+
className
|
|
136
|
+
),
|
|
137
|
+
...props
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/ui/select.tsx
|
|
143
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
144
|
+
import {
|
|
145
|
+
CheckIcon,
|
|
146
|
+
ChevronDownIcon,
|
|
147
|
+
ChevronUpIcon
|
|
148
|
+
} from "lucide-react";
|
|
149
|
+
import { jsx as jsx5, jsxs } from "react/jsx-runtime";
|
|
150
|
+
function Select({
|
|
151
|
+
...props
|
|
152
|
+
}) {
|
|
153
|
+
return /* @__PURE__ */ jsx5(SelectPrimitive.Root, { "data-slot": "select", ...props });
|
|
154
|
+
}
|
|
155
|
+
function SelectValue({
|
|
156
|
+
...props
|
|
157
|
+
}) {
|
|
158
|
+
return /* @__PURE__ */ jsx5(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
159
|
+
}
|
|
160
|
+
function SelectTrigger({
|
|
161
|
+
className,
|
|
162
|
+
size = "default",
|
|
163
|
+
children,
|
|
164
|
+
...props
|
|
165
|
+
}) {
|
|
166
|
+
return /* @__PURE__ */ jsxs(
|
|
167
|
+
SelectPrimitive.Trigger,
|
|
168
|
+
{
|
|
169
|
+
"data-slot": "select-trigger",
|
|
170
|
+
"data-size": size,
|
|
171
|
+
className: cn(
|
|
172
|
+
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-full items-center justify-between gap-2 rounded-md border bg-input-background px-3 py-2 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
173
|
+
className
|
|
174
|
+
),
|
|
175
|
+
...props,
|
|
176
|
+
children: [
|
|
177
|
+
children,
|
|
178
|
+
/* @__PURE__ */ jsx5(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx5(ChevronDownIcon, { className: "size-4 opacity-50" }) })
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
function SelectContent({
|
|
184
|
+
className,
|
|
185
|
+
children,
|
|
186
|
+
position = "popper",
|
|
187
|
+
...props
|
|
188
|
+
}) {
|
|
189
|
+
return /* @__PURE__ */ jsx5(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
|
|
190
|
+
SelectPrimitive.Content,
|
|
191
|
+
{
|
|
192
|
+
"data-slot": "select-content",
|
|
193
|
+
className: cn(
|
|
194
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
|
195
|
+
position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
196
|
+
className
|
|
197
|
+
),
|
|
198
|
+
position,
|
|
199
|
+
...props,
|
|
200
|
+
children: [
|
|
201
|
+
/* @__PURE__ */ jsx5(SelectScrollUpButton, {}),
|
|
202
|
+
/* @__PURE__ */ jsx5(
|
|
203
|
+
SelectPrimitive.Viewport,
|
|
204
|
+
{
|
|
205
|
+
className: cn(
|
|
206
|
+
"p-1",
|
|
207
|
+
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
|
208
|
+
),
|
|
209
|
+
children
|
|
210
|
+
}
|
|
211
|
+
),
|
|
212
|
+
/* @__PURE__ */ jsx5(SelectScrollDownButton, {})
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
) });
|
|
216
|
+
}
|
|
217
|
+
function SelectItem({
|
|
218
|
+
className,
|
|
219
|
+
children,
|
|
220
|
+
...props
|
|
221
|
+
}) {
|
|
222
|
+
return /* @__PURE__ */ jsxs(
|
|
223
|
+
SelectPrimitive.Item,
|
|
224
|
+
{
|
|
225
|
+
"data-slot": "select-item",
|
|
226
|
+
className: cn(
|
|
227
|
+
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
228
|
+
className
|
|
229
|
+
),
|
|
230
|
+
...props,
|
|
231
|
+
children: [
|
|
232
|
+
/* @__PURE__ */ jsx5("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx5(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx5(CheckIcon, { className: "size-4" }) }) }),
|
|
233
|
+
/* @__PURE__ */ jsx5(SelectPrimitive.ItemText, { children })
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
function SelectScrollUpButton({
|
|
239
|
+
className,
|
|
240
|
+
...props
|
|
241
|
+
}) {
|
|
242
|
+
return /* @__PURE__ */ jsx5(
|
|
243
|
+
SelectPrimitive.ScrollUpButton,
|
|
244
|
+
{
|
|
245
|
+
"data-slot": "select-scroll-up-button",
|
|
246
|
+
className: cn(
|
|
247
|
+
"flex cursor-default items-center justify-center py-1",
|
|
248
|
+
className
|
|
249
|
+
),
|
|
250
|
+
...props,
|
|
251
|
+
children: /* @__PURE__ */ jsx5(ChevronUpIcon, { className: "size-4" })
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
function SelectScrollDownButton({
|
|
256
|
+
className,
|
|
257
|
+
...props
|
|
258
|
+
}) {
|
|
259
|
+
return /* @__PURE__ */ jsx5(
|
|
260
|
+
SelectPrimitive.ScrollDownButton,
|
|
261
|
+
{
|
|
262
|
+
"data-slot": "select-scroll-down-button",
|
|
263
|
+
className: cn(
|
|
264
|
+
"flex cursor-default items-center justify-center py-1",
|
|
265
|
+
className
|
|
266
|
+
),
|
|
267
|
+
...props,
|
|
268
|
+
children: /* @__PURE__ */ jsx5(ChevronDownIcon, { className: "size-4" })
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/ui/checkbox.tsx
|
|
274
|
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
275
|
+
import { CheckIcon as CheckIcon2 } from "lucide-react";
|
|
276
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
277
|
+
function Checkbox({
|
|
278
|
+
className,
|
|
279
|
+
...props
|
|
280
|
+
}) {
|
|
281
|
+
return /* @__PURE__ */ jsx6(
|
|
282
|
+
CheckboxPrimitive.Root,
|
|
283
|
+
{
|
|
284
|
+
"data-slot": "checkbox",
|
|
285
|
+
className: cn(
|
|
286
|
+
"peer border border-muted-foreground/40 bg-input-background dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
287
|
+
className
|
|
288
|
+
),
|
|
289
|
+
...props,
|
|
290
|
+
children: /* @__PURE__ */ jsx6(
|
|
291
|
+
CheckboxPrimitive.Indicator,
|
|
292
|
+
{
|
|
293
|
+
"data-slot": "checkbox-indicator",
|
|
294
|
+
className: "flex items-center justify-center text-current transition-none",
|
|
295
|
+
children: /* @__PURE__ */ jsx6(CheckIcon2, { className: "size-3.5" })
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/ui/radio-group.tsx
|
|
303
|
+
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
304
|
+
import { CircleIcon } from "lucide-react";
|
|
305
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
306
|
+
function RadioGroup({
|
|
307
|
+
className,
|
|
308
|
+
...props
|
|
309
|
+
}) {
|
|
310
|
+
return /* @__PURE__ */ jsx7(
|
|
311
|
+
RadioGroupPrimitive.Root,
|
|
312
|
+
{
|
|
313
|
+
"data-slot": "radio-group",
|
|
314
|
+
className: cn("grid gap-3", className),
|
|
315
|
+
...props
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
function RadioGroupItem({
|
|
320
|
+
className,
|
|
321
|
+
...props
|
|
322
|
+
}) {
|
|
323
|
+
return /* @__PURE__ */ jsx7(
|
|
324
|
+
RadioGroupPrimitive.Item,
|
|
325
|
+
{
|
|
326
|
+
"data-slot": "radio-group-item",
|
|
327
|
+
className: cn(
|
|
328
|
+
"border-[1.5px] border-muted-foreground/50 text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
329
|
+
className
|
|
330
|
+
),
|
|
331
|
+
...props,
|
|
332
|
+
children: /* @__PURE__ */ jsx7(
|
|
333
|
+
RadioGroupPrimitive.Indicator,
|
|
334
|
+
{
|
|
335
|
+
"data-slot": "radio-group-indicator",
|
|
336
|
+
className: "relative flex items-center justify-center",
|
|
337
|
+
children: /* @__PURE__ */ jsx7(CircleIcon, { className: "fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" })
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/ui/label.tsx
|
|
345
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
346
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
347
|
+
function Label2({
|
|
348
|
+
className,
|
|
349
|
+
...props
|
|
350
|
+
}) {
|
|
351
|
+
return /* @__PURE__ */ jsx8(
|
|
352
|
+
LabelPrimitive.Root,
|
|
353
|
+
{
|
|
354
|
+
"data-slot": "label",
|
|
355
|
+
className: cn(
|
|
356
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
357
|
+
className
|
|
358
|
+
),
|
|
359
|
+
...props
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/ui/progress.tsx
|
|
365
|
+
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
|
366
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
367
|
+
function Progress({
|
|
368
|
+
className,
|
|
369
|
+
value,
|
|
370
|
+
...props
|
|
371
|
+
}) {
|
|
372
|
+
return /* @__PURE__ */ jsx9(
|
|
373
|
+
ProgressPrimitive.Root,
|
|
374
|
+
{
|
|
375
|
+
"data-slot": "progress",
|
|
376
|
+
className: cn(
|
|
377
|
+
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
|
378
|
+
className
|
|
379
|
+
),
|
|
380
|
+
...props,
|
|
381
|
+
children: /* @__PURE__ */ jsx9(
|
|
382
|
+
ProgressPrimitive.Indicator,
|
|
383
|
+
{
|
|
384
|
+
"data-slot": "progress-indicator",
|
|
385
|
+
className: "bg-primary h-full w-full flex-1 transition-all",
|
|
386
|
+
style: { transform: `translateX(-${100 - (value || 0)}%)` }
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/DynamicForm.tsx
|
|
394
|
+
import {
|
|
395
|
+
CheckCircle,
|
|
396
|
+
Loader2,
|
|
397
|
+
Send,
|
|
398
|
+
AlertCircle,
|
|
399
|
+
Upload,
|
|
400
|
+
File,
|
|
401
|
+
X,
|
|
402
|
+
Plus,
|
|
403
|
+
Pencil,
|
|
404
|
+
Trash2
|
|
405
|
+
} from "lucide-react";
|
|
406
|
+
import { Fragment, jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
4
407
|
function DynamicForm(props) {
|
|
5
|
-
const {
|
|
408
|
+
const {
|
|
409
|
+
submitted,
|
|
410
|
+
submittedValues,
|
|
411
|
+
uiProps,
|
|
412
|
+
onSubmit,
|
|
413
|
+
onFileUpload
|
|
414
|
+
} = props;
|
|
6
415
|
const title = uiProps.title;
|
|
7
416
|
const description = uiProps.description;
|
|
8
|
-
const fields = uiProps.fields
|
|
9
|
-
const submitText = uiProps.submit_button_text
|
|
417
|
+
const fields = uiProps.fields ?? [];
|
|
418
|
+
const submitText = uiProps.submit_button_text ?? "Submit";
|
|
10
419
|
const cancelText = uiProps.cancel_button_text;
|
|
11
|
-
const
|
|
12
|
-
if (submittedValues) return submittedValues;
|
|
13
|
-
const
|
|
14
|
-
fields.forEach((
|
|
15
|
-
|
|
420
|
+
const getInitialValues = () => {
|
|
421
|
+
if (submittedValues) return { ...submittedValues };
|
|
422
|
+
const values = {};
|
|
423
|
+
fields.forEach((field) => {
|
|
424
|
+
if (field.default_value !== void 0) {
|
|
425
|
+
values[field.id] = field.default_value;
|
|
426
|
+
} else if (field.type === "checkbox" || field.type === "multiselect") {
|
|
427
|
+
values[field.id] = [];
|
|
428
|
+
} else if (field.type === "todo_list" && field.items) {
|
|
429
|
+
values[field.id] = field.items.map((item) => ({ ...item }));
|
|
430
|
+
} else if (field.type === "approval") {
|
|
431
|
+
values[field.id] = null;
|
|
432
|
+
} else {
|
|
433
|
+
values[field.id] = "";
|
|
434
|
+
}
|
|
16
435
|
});
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
const [
|
|
20
|
-
const
|
|
21
|
-
|
|
436
|
+
return values;
|
|
437
|
+
};
|
|
438
|
+
const [formValues, setFormValues] = useState(getInitialValues);
|
|
439
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
440
|
+
const [error, setError] = useState(null);
|
|
441
|
+
const [isSubmitted, setIsSubmitted] = useState(submitted ?? false);
|
|
442
|
+
const [submittedData, setSubmittedData] = useState(
|
|
443
|
+
submittedValues ?? null
|
|
444
|
+
);
|
|
445
|
+
const [editingTodoId, setEditingTodoId] = useState(null);
|
|
446
|
+
const [editingTodoContent, setEditingTodoContent] = useState("");
|
|
447
|
+
const [newTodoInputs, setNewTodoInputs] = useState({});
|
|
448
|
+
const [fileUploadState, setFileUploadState] = useState({});
|
|
449
|
+
const scrollRef = useRef(null);
|
|
450
|
+
const [canScrollUp, setCanScrollUp] = useState(false);
|
|
451
|
+
const [canScrollDown, setCanScrollDown] = useState(false);
|
|
452
|
+
const checkScroll = useCallback(() => {
|
|
453
|
+
const el = scrollRef.current;
|
|
454
|
+
if (!el) return;
|
|
455
|
+
const threshold = 4;
|
|
456
|
+
setCanScrollUp(el.scrollTop > threshold);
|
|
457
|
+
setCanScrollDown(el.scrollTop + el.clientHeight < el.scrollHeight - threshold);
|
|
22
458
|
}, []);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
const el = scrollRef.current;
|
|
461
|
+
if (!el) return;
|
|
462
|
+
const raf = requestAnimationFrame(checkScroll);
|
|
463
|
+
el.addEventListener("scroll", checkScroll, { passive: true });
|
|
464
|
+
const ro = new ResizeObserver(checkScroll);
|
|
465
|
+
ro.observe(el);
|
|
466
|
+
const mo = new MutationObserver(checkScroll);
|
|
467
|
+
mo.observe(el, { childList: true, subtree: true });
|
|
468
|
+
return () => {
|
|
469
|
+
cancelAnimationFrame(raf);
|
|
470
|
+
el.removeEventListener("scroll", checkScroll);
|
|
471
|
+
ro.disconnect();
|
|
472
|
+
mo.disconnect();
|
|
473
|
+
};
|
|
474
|
+
}, [checkScroll, isSubmitted, isLoading]);
|
|
475
|
+
const handleFieldChange = (fieldId, value) => {
|
|
476
|
+
setFormValues((prev) => ({ ...prev, [fieldId]: value }));
|
|
477
|
+
setError(null);
|
|
478
|
+
};
|
|
479
|
+
const handleCheckboxChange = (fieldId, optionId, checked) => {
|
|
480
|
+
setFormValues((prev) => {
|
|
481
|
+
const cur = prev[fieldId] || [];
|
|
482
|
+
return {
|
|
483
|
+
...prev,
|
|
484
|
+
[fieldId]: checked ? [...cur, optionId] : cur.filter((id) => id !== optionId)
|
|
485
|
+
};
|
|
486
|
+
});
|
|
487
|
+
};
|
|
488
|
+
const handleTodoToggle = (fieldId, itemId) => {
|
|
489
|
+
setFormValues((prev) => {
|
|
490
|
+
const items = prev[fieldId] || [];
|
|
491
|
+
return {
|
|
492
|
+
...prev,
|
|
493
|
+
[fieldId]: items.map(
|
|
494
|
+
(item) => item.id === itemId ? { ...item, status: item.status === "completed" ? "pending" : "completed" } : item
|
|
495
|
+
)
|
|
496
|
+
};
|
|
497
|
+
});
|
|
498
|
+
};
|
|
499
|
+
const handleTodoEdit = (fieldId, itemId, newContent) => {
|
|
500
|
+
setFormValues((prev) => {
|
|
501
|
+
const items = prev[fieldId] || [];
|
|
502
|
+
return {
|
|
503
|
+
...prev,
|
|
504
|
+
[fieldId]: items.map(
|
|
505
|
+
(item) => item.id === itemId ? { ...item, content: newContent } : item
|
|
506
|
+
)
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
const handleTodoAdd = (fieldId, content) => {
|
|
511
|
+
if (!content.trim()) return;
|
|
512
|
+
const newItem = {
|
|
513
|
+
id: `user_step_${Date.now()}`,
|
|
514
|
+
content: content.trim(),
|
|
515
|
+
status: "pending"
|
|
516
|
+
};
|
|
517
|
+
setFormValues((prev) => {
|
|
518
|
+
const items = prev[fieldId] || [];
|
|
519
|
+
return { ...prev, [fieldId]: [...items, newItem] };
|
|
520
|
+
});
|
|
521
|
+
};
|
|
522
|
+
const handleTodoDelete = (fieldId, itemId) => {
|
|
523
|
+
setFormValues((prev) => {
|
|
524
|
+
const items = prev[fieldId] || [];
|
|
525
|
+
return { ...prev, [fieldId]: items.filter((item) => item.id !== itemId) };
|
|
526
|
+
});
|
|
527
|
+
};
|
|
528
|
+
const handleFileUpload = async (fieldId, files, multiple = false) => {
|
|
529
|
+
if (!files || files.length === 0 || !onFileUpload) return;
|
|
530
|
+
setFileUploadState((prev) => ({
|
|
531
|
+
...prev,
|
|
532
|
+
[fieldId]: { isUploading: true, progress: 0, fileName: null, error: null }
|
|
533
|
+
}));
|
|
534
|
+
try {
|
|
535
|
+
const uploadedUrls = [];
|
|
536
|
+
const fileNames = [];
|
|
537
|
+
for (let i = 0; i < files.length; i++) {
|
|
538
|
+
const file = files[i];
|
|
539
|
+
const fd = new FormData();
|
|
540
|
+
fd.append("file", file);
|
|
541
|
+
setFileUploadState((prev) => ({
|
|
542
|
+
...prev,
|
|
543
|
+
[fieldId]: {
|
|
544
|
+
...prev[fieldId],
|
|
545
|
+
progress: Math.round(i / files.length * 50),
|
|
546
|
+
fileName: file.name
|
|
547
|
+
}
|
|
548
|
+
}));
|
|
549
|
+
const result = await onFileUpload(fd);
|
|
550
|
+
if (result.status === "SUCCESS" && result.data?.download_url) {
|
|
551
|
+
uploadedUrls.push(result.data.download_url);
|
|
552
|
+
fileNames.push(file.name);
|
|
553
|
+
} else {
|
|
554
|
+
throw new Error("Upload failed");
|
|
555
|
+
}
|
|
556
|
+
setFileUploadState((prev) => ({
|
|
557
|
+
...prev,
|
|
558
|
+
[fieldId]: { ...prev[fieldId], progress: Math.round((i + 1) / files.length * 100) }
|
|
559
|
+
}));
|
|
42
560
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
561
|
+
const value = multiple ? uploadedUrls : uploadedUrls[0];
|
|
562
|
+
handleFieldChange(fieldId, value);
|
|
563
|
+
setFileUploadState((prev) => ({
|
|
564
|
+
...prev,
|
|
565
|
+
[fieldId]: {
|
|
566
|
+
isUploading: false,
|
|
567
|
+
progress: 100,
|
|
568
|
+
fileName: multiple ? `${fileNames.length} file(s)` : fileNames[0],
|
|
569
|
+
error: null
|
|
570
|
+
}
|
|
571
|
+
}));
|
|
572
|
+
} catch (err) {
|
|
573
|
+
const msg = err instanceof Error ? err.message : "Upload failed";
|
|
574
|
+
console.error("File upload error:", err);
|
|
575
|
+
setFileUploadState((prev) => ({
|
|
576
|
+
...prev,
|
|
577
|
+
[fieldId]: { isUploading: false, progress: 0, fileName: null, error: msg }
|
|
578
|
+
}));
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
const handleFileClear = (fieldId) => {
|
|
582
|
+
handleFieldChange(fieldId, "");
|
|
583
|
+
setFileUploadState((prev) => ({
|
|
584
|
+
...prev,
|
|
585
|
+
[fieldId]: { isUploading: false, progress: 0, fileName: null, error: null }
|
|
586
|
+
}));
|
|
587
|
+
};
|
|
588
|
+
const getFieldStatusText = (field) => {
|
|
589
|
+
const value = formValues[field.id];
|
|
590
|
+
switch (field.type) {
|
|
591
|
+
case "text":
|
|
592
|
+
case "textarea": {
|
|
593
|
+
const s = value;
|
|
594
|
+
return s && s.trim().length > 0 ? `Text entered (${s.length} characters)` : null;
|
|
595
|
+
}
|
|
596
|
+
case "number":
|
|
597
|
+
return value !== "" && value !== null && value !== void 0 ? `Value set: ${value}` : null;
|
|
598
|
+
case "select": {
|
|
599
|
+
if (!value) return null;
|
|
600
|
+
const opt = field.options?.find((o) => o.id === value);
|
|
601
|
+
return `Selected: ${opt?.label ?? value}`;
|
|
602
|
+
}
|
|
603
|
+
case "multiselect":
|
|
604
|
+
case "checkbox": {
|
|
605
|
+
const arr = value;
|
|
606
|
+
if (!Array.isArray(arr) || arr.length === 0) return null;
|
|
607
|
+
const labels = arr.map((v) => {
|
|
608
|
+
const o = field.options?.find((opt) => opt.id === v);
|
|
609
|
+
return o?.label ?? v;
|
|
610
|
+
});
|
|
611
|
+
return `${arr.length} option(s) selected: ${labels.join(", ")}`;
|
|
612
|
+
}
|
|
613
|
+
case "radio": {
|
|
614
|
+
if (!value) return null;
|
|
615
|
+
const opt = field.options?.find((o) => o.id === value);
|
|
616
|
+
return `Selected: ${opt?.label ?? value}`;
|
|
617
|
+
}
|
|
618
|
+
default:
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
const handleApproval = async (fieldId, approved) => {
|
|
623
|
+
const action = approved ? "approve" : "reject";
|
|
624
|
+
handleFieldChange(fieldId, action);
|
|
625
|
+
await submitForm(approved ? "Approve" : "Reject", { ...formValues, [fieldId]: action });
|
|
626
|
+
};
|
|
627
|
+
const validateForm = () => {
|
|
628
|
+
for (const field of fields) {
|
|
629
|
+
if (field.required) {
|
|
630
|
+
const value = formValues[field.id];
|
|
631
|
+
if (value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0) {
|
|
632
|
+
setError(`${field.label} is required`);
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return true;
|
|
638
|
+
};
|
|
639
|
+
const submitForm = async (userAction, values) => {
|
|
640
|
+
setIsLoading(true);
|
|
641
|
+
const resumePayload = {
|
|
642
|
+
user_action: userAction,
|
|
643
|
+
form_title: title,
|
|
644
|
+
values
|
|
645
|
+
};
|
|
646
|
+
const messageText = `User clicked "${userAction}" on form "${title}"`;
|
|
647
|
+
try {
|
|
648
|
+
await onSubmit(resumePayload, messageText);
|
|
649
|
+
setSubmittedData({ user_action: userAction, values });
|
|
650
|
+
setIsSubmitted(true);
|
|
651
|
+
} catch (err) {
|
|
652
|
+
const msg = err instanceof Error ? err.message : "Failed to submit form";
|
|
653
|
+
console.error("DynamicForm submit error:", err);
|
|
654
|
+
setError(msg);
|
|
655
|
+
} finally {
|
|
656
|
+
setIsLoading(false);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
const handleSubmit = async (e) => {
|
|
660
|
+
e.preventDefault();
|
|
661
|
+
if (!validateForm()) return;
|
|
662
|
+
await submitForm(submitText, formValues);
|
|
663
|
+
};
|
|
664
|
+
const handleCancel = async () => {
|
|
665
|
+
await submitForm(cancelText ?? "Cancel", formValues);
|
|
666
|
+
};
|
|
667
|
+
const renderField = (field) => {
|
|
668
|
+
switch (field.type) {
|
|
669
|
+
case "text":
|
|
670
|
+
return /* @__PURE__ */ jsx10(
|
|
671
|
+
Input,
|
|
672
|
+
{
|
|
673
|
+
type: "text",
|
|
674
|
+
value: formValues[field.id] || "",
|
|
675
|
+
onChange: (e) => handleFieldChange(field.id, e.target.value),
|
|
676
|
+
placeholder: field.placeholder,
|
|
677
|
+
className: "bg-background w-full h-8 text-xs"
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
case "textarea":
|
|
681
|
+
return /* @__PURE__ */ jsx10(
|
|
682
|
+
Textarea,
|
|
683
|
+
{
|
|
684
|
+
value: formValues[field.id] || "",
|
|
685
|
+
onChange: (e) => handleFieldChange(field.id, e.target.value),
|
|
686
|
+
placeholder: field.placeholder,
|
|
687
|
+
className: "bg-background min-h-[72px] w-full text-xs"
|
|
688
|
+
}
|
|
689
|
+
);
|
|
690
|
+
case "number":
|
|
691
|
+
return /* @__PURE__ */ jsx10(
|
|
692
|
+
Input,
|
|
693
|
+
{
|
|
694
|
+
type: "number",
|
|
695
|
+
value: formValues[field.id] || "",
|
|
696
|
+
onChange: (e) => handleFieldChange(field.id, e.target.value ? Number(e.target.value) : ""),
|
|
697
|
+
placeholder: field.placeholder,
|
|
698
|
+
className: "bg-background w-full h-8 text-xs"
|
|
699
|
+
}
|
|
700
|
+
);
|
|
701
|
+
case "select":
|
|
702
|
+
return /* @__PURE__ */ jsxs2(
|
|
703
|
+
Select,
|
|
704
|
+
{
|
|
705
|
+
value: formValues[field.id] || "",
|
|
706
|
+
onValueChange: (value) => handleFieldChange(field.id, value),
|
|
707
|
+
children: [
|
|
708
|
+
/* @__PURE__ */ jsx10(SelectTrigger, { className: "bg-background w-full h-8 text-xs", children: /* @__PURE__ */ jsx10(SelectValue, { placeholder: field.placeholder || "Select an option..." }) }),
|
|
709
|
+
/* @__PURE__ */ jsx10(SelectContent, { children: field.options?.map((option) => /* @__PURE__ */ jsx10(SelectItem, { value: option.id, className: "text-xs", children: option.label }, option.id)) })
|
|
710
|
+
]
|
|
711
|
+
}
|
|
712
|
+
);
|
|
713
|
+
case "multiselect":
|
|
714
|
+
case "checkbox":
|
|
715
|
+
return /* @__PURE__ */ jsx10("div", { className: "space-y-0.5", children: field.options?.map((option) => /* @__PURE__ */ jsxs2(
|
|
716
|
+
"label",
|
|
717
|
+
{
|
|
718
|
+
className: "flex items-start gap-2 cursor-pointer py-1 px-1.5 rounded hover:bg-muted/50 transition-colors",
|
|
719
|
+
children: [
|
|
720
|
+
/* @__PURE__ */ jsx10(
|
|
721
|
+
Checkbox,
|
|
722
|
+
{
|
|
723
|
+
checked: (formValues[field.id] || []).includes(option.id),
|
|
724
|
+
onCheckedChange: (checked) => handleCheckboxChange(field.id, option.id, !!checked),
|
|
725
|
+
className: "mt-0.5 h-3.5 w-3.5 border-[1.5px] border-muted-foreground/50"
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex-1", children: [
|
|
729
|
+
/* @__PURE__ */ jsx10("span", { className: "text-xs font-medium", children: option.label }),
|
|
730
|
+
option.description && /* @__PURE__ */ jsx10("p", { className: "text-[11px] text-muted-foreground", children: option.description })
|
|
731
|
+
] })
|
|
732
|
+
]
|
|
733
|
+
},
|
|
734
|
+
option.id
|
|
735
|
+
)) });
|
|
736
|
+
case "radio":
|
|
737
|
+
return /* @__PURE__ */ jsx10(
|
|
738
|
+
RadioGroup,
|
|
739
|
+
{
|
|
740
|
+
value: formValues[field.id] || "",
|
|
741
|
+
onValueChange: (value) => handleFieldChange(field.id, value),
|
|
742
|
+
className: "space-y-0.5",
|
|
743
|
+
children: field.options?.map((option) => /* @__PURE__ */ jsxs2(
|
|
744
|
+
"label",
|
|
745
|
+
{
|
|
746
|
+
className: "flex items-start gap-2 cursor-pointer py-1 px-1.5 rounded hover:bg-muted/50 transition-colors",
|
|
747
|
+
children: [
|
|
748
|
+
/* @__PURE__ */ jsx10(RadioGroupItem, { value: option.id, className: "mt-0.5" }),
|
|
749
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex-1", children: [
|
|
750
|
+
/* @__PURE__ */ jsx10("span", { className: "text-xs font-medium", children: option.label }),
|
|
751
|
+
option.description && /* @__PURE__ */ jsx10("p", { className: "text-[11px] text-muted-foreground", children: option.description })
|
|
752
|
+
] })
|
|
753
|
+
]
|
|
67
754
|
},
|
|
755
|
+
option.id
|
|
756
|
+
))
|
|
757
|
+
}
|
|
758
|
+
);
|
|
759
|
+
case "approval":
|
|
760
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex gap-2 justify-end", children: [
|
|
761
|
+
/* @__PURE__ */ jsxs2(
|
|
762
|
+
Button,
|
|
763
|
+
{
|
|
764
|
+
type: "button",
|
|
765
|
+
variant: "destructive",
|
|
766
|
+
size: "sm",
|
|
767
|
+
onClick: () => handleApproval(field.id, false),
|
|
768
|
+
className: "bg-destructive/10 text-destructive hover:bg-destructive/20 border-transparent shadow-none h-8 text-xs",
|
|
769
|
+
disabled: isLoading,
|
|
68
770
|
children: [
|
|
69
|
-
|
|
70
|
-
|
|
771
|
+
/* @__PURE__ */ jsx10(AlertCircle, { className: "w-3 h-3 mr-1.5" }),
|
|
772
|
+
"Reject"
|
|
71
773
|
]
|
|
72
774
|
}
|
|
73
775
|
),
|
|
74
|
-
|
|
75
|
-
|
|
776
|
+
/* @__PURE__ */ jsxs2(
|
|
777
|
+
Button,
|
|
76
778
|
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
fontSize: "14px",
|
|
83
|
-
resize: "vertical"
|
|
84
|
-
},
|
|
85
|
-
placeholder: field.placeholder,
|
|
86
|
-
value: formValues[field.id] || "",
|
|
87
|
-
onChange: (e) => handleChange(field.id, e.target.value)
|
|
88
|
-
}
|
|
89
|
-
) : field.type === "select" ? /* @__PURE__ */ jsxs(
|
|
90
|
-
"select",
|
|
91
|
-
{
|
|
92
|
-
style: {
|
|
93
|
-
width: "100%",
|
|
94
|
-
padding: "8px",
|
|
95
|
-
border: "1px solid #d1d5db",
|
|
96
|
-
borderRadius: "6px",
|
|
97
|
-
fontSize: "14px"
|
|
98
|
-
},
|
|
99
|
-
value: formValues[field.id] || "",
|
|
100
|
-
onChange: (e) => handleChange(field.id, e.target.value),
|
|
779
|
+
type: "button",
|
|
780
|
+
size: "sm",
|
|
781
|
+
onClick: () => handleApproval(field.id, true),
|
|
782
|
+
className: "bg-emerald-600 hover:bg-emerald-700 text-white shadow-none border-transparent h-8 text-xs",
|
|
783
|
+
disabled: isLoading,
|
|
101
784
|
children: [
|
|
102
|
-
/* @__PURE__ */
|
|
103
|
-
|
|
785
|
+
/* @__PURE__ */ jsx10(CheckCircle, { className: "w-3 h-3 mr-1.5" }),
|
|
786
|
+
"Approve"
|
|
104
787
|
]
|
|
105
788
|
}
|
|
106
|
-
) : /* @__PURE__ */ jsx(
|
|
107
|
-
"input",
|
|
108
|
-
{
|
|
109
|
-
style: {
|
|
110
|
-
width: "100%",
|
|
111
|
-
padding: "8px",
|
|
112
|
-
border: "1px solid #d1d5db",
|
|
113
|
-
borderRadius: "6px",
|
|
114
|
-
fontSize: "14px"
|
|
115
|
-
},
|
|
116
|
-
type: field.type === "number" ? "number" : "text",
|
|
117
|
-
placeholder: field.placeholder,
|
|
118
|
-
value: formValues[field.id] || "",
|
|
119
|
-
onChange: (e) => handleChange(field.id, e.target.value)
|
|
120
|
-
}
|
|
121
789
|
)
|
|
122
|
-
] }
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
790
|
+
] });
|
|
791
|
+
case "todo_list": {
|
|
792
|
+
const todoItems = formValues[field.id] || [];
|
|
793
|
+
const completedCount = todoItems.filter((i) => i.status === "completed").length;
|
|
794
|
+
const totalCount = todoItems.length;
|
|
795
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
|
|
796
|
+
totalCount > 0 && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
|
|
797
|
+
/* @__PURE__ */ jsx10(CheckCircle, { className: "w-3 h-3" }),
|
|
798
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
799
|
+
completedCount,
|
|
800
|
+
" of ",
|
|
801
|
+
totalCount,
|
|
802
|
+
" completed"
|
|
803
|
+
] })
|
|
804
|
+
] }),
|
|
805
|
+
/* @__PURE__ */ jsx10("div", { className: "space-y-0", children: todoItems.map((item) => /* @__PURE__ */ jsxs2(
|
|
806
|
+
"div",
|
|
126
807
|
{
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
808
|
+
className: "flex items-start gap-2 py-1 px-1.5 rounded hover:bg-muted/30 transition-all group",
|
|
809
|
+
children: [
|
|
810
|
+
/* @__PURE__ */ jsx10(
|
|
811
|
+
Checkbox,
|
|
812
|
+
{
|
|
813
|
+
checked: item.status === "completed",
|
|
814
|
+
onCheckedChange: () => handleTodoToggle(field.id, item.id),
|
|
815
|
+
className: "mt-0.5 h-3.5 w-3.5 rounded-full border-[1.5px] border-muted-foreground/50"
|
|
816
|
+
}
|
|
817
|
+
),
|
|
818
|
+
editingTodoId === item.id ? /* @__PURE__ */ jsx10(
|
|
819
|
+
Input,
|
|
820
|
+
{
|
|
821
|
+
type: "text",
|
|
822
|
+
value: editingTodoContent,
|
|
823
|
+
onChange: (e) => setEditingTodoContent(e.target.value),
|
|
824
|
+
onKeyDown: (e) => {
|
|
825
|
+
if (e.key === "Enter") {
|
|
826
|
+
handleTodoEdit(field.id, item.id, editingTodoContent);
|
|
827
|
+
setEditingTodoId(null);
|
|
828
|
+
setEditingTodoContent("");
|
|
829
|
+
} else if (e.key === "Escape") {
|
|
830
|
+
setEditingTodoId(null);
|
|
831
|
+
setEditingTodoContent("");
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
onBlur: () => {
|
|
835
|
+
handleTodoEdit(field.id, item.id, editingTodoContent);
|
|
836
|
+
setEditingTodoId(null);
|
|
837
|
+
setEditingTodoContent("");
|
|
838
|
+
},
|
|
839
|
+
autoFocus: true,
|
|
840
|
+
className: "flex-1 h-6 text-xs bg-background"
|
|
841
|
+
}
|
|
842
|
+
) : /* @__PURE__ */ jsx10(
|
|
843
|
+
"span",
|
|
844
|
+
{
|
|
845
|
+
className: `text-xs flex-1 leading-snug ${item.status === "completed" ? "line-through text-muted-foreground" : ""}`,
|
|
846
|
+
children: item.content
|
|
847
|
+
}
|
|
848
|
+
),
|
|
849
|
+
/* @__PURE__ */ jsx10("div", { className: "flex items-center gap-0.5 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity", children: editingTodoId !== item.id && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
850
|
+
/* @__PURE__ */ jsx10(
|
|
851
|
+
"button",
|
|
852
|
+
{
|
|
853
|
+
type: "button",
|
|
854
|
+
onClick: () => {
|
|
855
|
+
setEditingTodoId(item.id);
|
|
856
|
+
setEditingTodoContent(item.content);
|
|
857
|
+
},
|
|
858
|
+
className: "p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground",
|
|
859
|
+
title: "Edit step",
|
|
860
|
+
children: /* @__PURE__ */ jsx10(Pencil, { className: "w-3 h-3" })
|
|
861
|
+
}
|
|
862
|
+
),
|
|
863
|
+
/* @__PURE__ */ jsx10(
|
|
864
|
+
"button",
|
|
865
|
+
{
|
|
866
|
+
type: "button",
|
|
867
|
+
onClick: () => handleTodoDelete(field.id, item.id),
|
|
868
|
+
className: "p-1 rounded hover:bg-destructive/10 text-muted-foreground hover:text-destructive",
|
|
869
|
+
title: "Remove step",
|
|
870
|
+
children: /* @__PURE__ */ jsx10(Trash2, { className: "w-3 h-3" })
|
|
871
|
+
}
|
|
872
|
+
)
|
|
873
|
+
] }) })
|
|
874
|
+
]
|
|
875
|
+
},
|
|
876
|
+
item.id
|
|
877
|
+
)) }),
|
|
878
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5 pt-1", children: [
|
|
879
|
+
/* @__PURE__ */ jsx10(
|
|
880
|
+
Input,
|
|
881
|
+
{
|
|
882
|
+
type: "text",
|
|
883
|
+
value: newTodoInputs[field.id] || "",
|
|
884
|
+
onChange: (e) => setNewTodoInputs((prev) => ({ ...prev, [field.id]: e.target.value })),
|
|
885
|
+
onKeyDown: (e) => {
|
|
886
|
+
if (e.key === "Enter") {
|
|
887
|
+
e.preventDefault();
|
|
888
|
+
handleTodoAdd(field.id, newTodoInputs[field.id] || "");
|
|
889
|
+
setNewTodoInputs((prev) => ({ ...prev, [field.id]: "" }));
|
|
890
|
+
}
|
|
891
|
+
},
|
|
892
|
+
placeholder: "Ask for follow-up changes",
|
|
893
|
+
className: "flex-1 h-7 text-xs bg-muted/30 border-transparent hover:bg-muted/50 focus:bg-background transition-colors"
|
|
894
|
+
}
|
|
895
|
+
),
|
|
896
|
+
/* @__PURE__ */ jsx10(
|
|
897
|
+
Button,
|
|
898
|
+
{
|
|
899
|
+
type: "button",
|
|
900
|
+
variant: "ghost",
|
|
901
|
+
size: "sm",
|
|
902
|
+
disabled: !(newTodoInputs[field.id] || "").trim(),
|
|
903
|
+
onClick: () => {
|
|
904
|
+
handleTodoAdd(field.id, newTodoInputs[field.id] || "");
|
|
905
|
+
setNewTodoInputs((prev) => ({ ...prev, [field.id]: "" }));
|
|
906
|
+
},
|
|
907
|
+
className: "h-7 px-2",
|
|
908
|
+
children: /* @__PURE__ */ jsx10(Plus, { className: "w-3.5 h-3.5" })
|
|
909
|
+
}
|
|
910
|
+
)
|
|
911
|
+
] })
|
|
912
|
+
] });
|
|
913
|
+
}
|
|
914
|
+
case "file": {
|
|
915
|
+
const uploadState = fileUploadState[field.id] || {
|
|
916
|
+
isUploading: false,
|
|
917
|
+
progress: 0,
|
|
918
|
+
fileName: null,
|
|
919
|
+
error: null
|
|
920
|
+
};
|
|
921
|
+
const val = formValues[field.id];
|
|
922
|
+
const hasFile = !!val && (typeof val === "string" ? val.length > 0 : Array.isArray(val) && val.length > 0);
|
|
923
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
924
|
+
!hasFile && !uploadState.isUploading && /* @__PURE__ */ jsxs2("label", { className: "flex flex-col items-center justify-center w-full h-32 border border-dashed rounded-lg cursor-pointer bg-muted/20 hover:bg-muted/40 transition-all duration-200 border-border group", children: [
|
|
925
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center pt-5 pb-6", children: [
|
|
926
|
+
/* @__PURE__ */ jsx10(Upload, { className: "w-8 h-8 mb-2 text-muted-foreground group-hover:text-primary transition-colors" }),
|
|
927
|
+
/* @__PURE__ */ jsxs2("p", { className: "mb-1 text-sm text-muted-foreground", children: [
|
|
928
|
+
/* @__PURE__ */ jsx10("span", { className: "font-semibold group-hover:text-foreground transition-colors", children: "Click to upload" }),
|
|
929
|
+
" ",
|
|
930
|
+
"or drag and drop"
|
|
931
|
+
] }),
|
|
932
|
+
field.accept && /* @__PURE__ */ jsxs2("p", { className: "text-xs text-muted-foreground/70", children: [
|
|
933
|
+
"Accepted: ",
|
|
934
|
+
field.accept
|
|
935
|
+
] })
|
|
936
|
+
] }),
|
|
937
|
+
/* @__PURE__ */ jsx10(
|
|
938
|
+
"input",
|
|
939
|
+
{
|
|
940
|
+
type: "file",
|
|
941
|
+
className: "hidden",
|
|
942
|
+
accept: field.accept,
|
|
943
|
+
multiple: field.multiple,
|
|
944
|
+
onChange: (e) => handleFileUpload(field.id, e.target.files, field.multiple)
|
|
945
|
+
}
|
|
946
|
+
)
|
|
947
|
+
] }),
|
|
948
|
+
uploadState.isUploading && /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center w-full h-32 border rounded-lg bg-muted/20 border-border", children: [
|
|
949
|
+
/* @__PURE__ */ jsx10(Loader2, { className: "w-7 h-7 mb-2 text-primary animate-spin" }),
|
|
950
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-sm text-muted-foreground", children: [
|
|
951
|
+
"Uploading ",
|
|
952
|
+
uploadState.fileName,
|
|
953
|
+
"..."
|
|
954
|
+
] }),
|
|
955
|
+
/* @__PURE__ */ jsx10(Progress, { value: uploadState.progress, className: "w-48 mt-2.5 h-1.5" }),
|
|
956
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-xs text-muted-foreground/70 mt-1.5", children: [
|
|
957
|
+
uploadState.progress,
|
|
958
|
+
"%"
|
|
959
|
+
] })
|
|
960
|
+
] }),
|
|
961
|
+
hasFile && !uploadState.isUploading && /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
962
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center space-x-2 text-emerald-600", children: [
|
|
963
|
+
/* @__PURE__ */ jsx10(CheckCircle, { className: "w-4 h-4" }),
|
|
964
|
+
/* @__PURE__ */ jsx10("span", { className: "text-sm font-medium", children: Array.isArray(val) ? `${val.length} file(s) uploaded successfully` : "File uploaded successfully" })
|
|
965
|
+
] }),
|
|
966
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2 p-3 border rounded-lg bg-muted/30 border-border overflow-hidden", children: [
|
|
967
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center space-x-3 min-w-0 flex-1", children: [
|
|
968
|
+
/* @__PURE__ */ jsx10("div", { className: "w-10 h-10 bg-muted rounded-md flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx10(File, { className: "w-5 h-5 text-muted-foreground" }) }),
|
|
969
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
|
|
970
|
+
/* @__PURE__ */ jsx10("p", { className: "text-sm font-medium truncate", children: uploadState.fileName || "File uploaded" }),
|
|
971
|
+
/* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground truncate", children: typeof val === "string" ? val : `${val.length} file(s)` })
|
|
972
|
+
] })
|
|
973
|
+
] }),
|
|
974
|
+
/* @__PURE__ */ jsx10(
|
|
975
|
+
Button,
|
|
976
|
+
{
|
|
977
|
+
type: "button",
|
|
978
|
+
variant: "ghost",
|
|
979
|
+
size: "sm",
|
|
980
|
+
onClick: () => handleFileClear(field.id),
|
|
981
|
+
className: "text-muted-foreground hover:text-destructive transition-colors",
|
|
982
|
+
children: /* @__PURE__ */ jsx10(X, { className: "w-4 h-4" })
|
|
983
|
+
}
|
|
984
|
+
)
|
|
985
|
+
] })
|
|
986
|
+
] }),
|
|
987
|
+
uploadState.error && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-destructive text-sm bg-destructive/5 border border-destructive/20 rounded-lg p-2.5", children: [
|
|
988
|
+
/* @__PURE__ */ jsx10(AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
|
|
989
|
+
/* @__PURE__ */ jsx10("span", { children: uploadState.error })
|
|
990
|
+
] })
|
|
991
|
+
] });
|
|
992
|
+
}
|
|
993
|
+
default:
|
|
994
|
+
return /* @__PURE__ */ jsxs2("div", { className: "text-sm text-muted-foreground", children: [
|
|
995
|
+
"Unsupported field type: ",
|
|
996
|
+
field.type
|
|
997
|
+
] });
|
|
159
998
|
}
|
|
160
|
-
|
|
999
|
+
};
|
|
1000
|
+
if (isLoading) {
|
|
1001
|
+
return /* @__PURE__ */ jsx10("div", { className: "w-full py-8 text-center animate-in fade-in duration-300", children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center gap-3", children: [
|
|
1002
|
+
/* @__PURE__ */ jsx10(Loader2, { className: "w-6 h-6 text-primary animate-spin" }),
|
|
1003
|
+
/* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground", children: "Submitting response..." })
|
|
1004
|
+
] }) });
|
|
1005
|
+
}
|
|
1006
|
+
if (isSubmitted) {
|
|
1007
|
+
const wasCancelled = submittedData?.cancelled;
|
|
1008
|
+
return /* @__PURE__ */ jsx10("div", { className: "w-full py-6 animate-in fade-in duration-300", children: /* @__PURE__ */ jsxs2(
|
|
1009
|
+
"div",
|
|
1010
|
+
{
|
|
1011
|
+
className: `rounded-lg border p-4 flex items-start gap-4 ${wasCancelled ? "bg-amber-50/50 border-amber-200" : "bg-emerald-50/50 border-emerald-200"} dark:bg-muted/20 dark:border-border`,
|
|
1012
|
+
children: [
|
|
1013
|
+
/* @__PURE__ */ jsx10("div", { className: `mt-0.5 ${wasCancelled ? "text-amber-500" : "text-emerald-500"}`, children: /* @__PURE__ */ jsx10(CheckCircle, { className: "w-5 h-5" }) }),
|
|
1014
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex-1", children: [
|
|
1015
|
+
/* @__PURE__ */ jsx10("h3", { className: "text-sm font-medium mb-1", children: wasCancelled ? "Cancelled" : "Response Submitted" }),
|
|
1016
|
+
/* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground mb-3", children: title }),
|
|
1017
|
+
submittedData && !wasCancelled && /* @__PURE__ */ jsx10("div", { className: "bg-background/50 rounded p-2 text-xs text-muted-foreground font-mono overflow-hidden", children: JSON.stringify(submittedData, null, 2) })
|
|
1018
|
+
] })
|
|
1019
|
+
]
|
|
1020
|
+
}
|
|
1021
|
+
) });
|
|
1022
|
+
}
|
|
1023
|
+
return /* @__PURE__ */ jsxs2(Card, { className: "w-[60%] max-w-2xl max-h-[65vh] flex flex-col border border-border bg-card shadow-sm animate-in fade-in duration-300", children: [
|
|
1024
|
+
/* @__PURE__ */ jsxs2(CardHeader, { className: "pb-2 pt-4 px-5 flex-shrink-0", children: [
|
|
1025
|
+
/* @__PURE__ */ jsx10(CardTitle, { className: "text-sm font-semibold tracking-tight", children: title }),
|
|
1026
|
+
description && /* @__PURE__ */ jsx10(CardDescription, { className: "text-xs text-muted-foreground leading-snug", children: description })
|
|
1027
|
+
] }),
|
|
1028
|
+
/* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, className: "relative flex-1 min-h-0 flex flex-col", children: [
|
|
1029
|
+
/* @__PURE__ */ jsx10("div", { className: `scroll-fade-top ${!canScrollUp ? "fade-hidden" : ""}` }),
|
|
1030
|
+
/* @__PURE__ */ jsx10(
|
|
1031
|
+
"div",
|
|
1032
|
+
{
|
|
1033
|
+
ref: scrollRef,
|
|
1034
|
+
className: "px-5 overflow-y-auto flex-1 min-h-0 dynamic-form-scroll",
|
|
1035
|
+
children: /* @__PURE__ */ jsxs2("div", { className: "space-y-3 pb-2", children: [
|
|
1036
|
+
fields.map((field) => {
|
|
1037
|
+
const statusText = getFieldStatusText(field);
|
|
1038
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
|
|
1039
|
+
/* @__PURE__ */ jsxs2(Label2, { className: "text-xs font-medium flex items-center gap-1.5", children: [
|
|
1040
|
+
/* @__PURE__ */ jsx10("span", { children: field.label }),
|
|
1041
|
+
field.required && /* @__PURE__ */ jsx10("span", { className: "text-destructive text-[10px]", children: "*" })
|
|
1042
|
+
] }),
|
|
1043
|
+
field.description && /* @__PURE__ */ jsx10("p", { className: "text-[11px] text-muted-foreground leading-snug", children: field.description }),
|
|
1044
|
+
renderField(field),
|
|
1045
|
+
statusText && field.type !== "file" && field.type !== "todo_list" && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 text-emerald-600 mt-0.5", children: [
|
|
1046
|
+
/* @__PURE__ */ jsx10(CheckCircle, { className: "w-2.5 h-2.5" }),
|
|
1047
|
+
/* @__PURE__ */ jsx10("span", { className: "text-[10px]", children: statusText })
|
|
1048
|
+
] })
|
|
1049
|
+
] }, field.id);
|
|
1050
|
+
}),
|
|
1051
|
+
error && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-destructive text-xs bg-destructive/5 border border-destructive/20 rounded-md p-2", children: [
|
|
1052
|
+
/* @__PURE__ */ jsx10(AlertCircle, { className: "w-3.5 h-3.5 flex-shrink-0" }),
|
|
1053
|
+
/* @__PURE__ */ jsx10("span", { children: error })
|
|
1054
|
+
] })
|
|
1055
|
+
] })
|
|
1056
|
+
}
|
|
1057
|
+
),
|
|
1058
|
+
/* @__PURE__ */ jsx10("div", { className: `scroll-fade-bottom ${!canScrollDown ? "fade-hidden" : ""}` }),
|
|
1059
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex justify-end gap-2 px-5 py-2 flex-shrink-0", children: [
|
|
1060
|
+
cancelText && /* @__PURE__ */ jsx10(
|
|
1061
|
+
Button,
|
|
1062
|
+
{
|
|
1063
|
+
type: "button",
|
|
1064
|
+
variant: "ghost",
|
|
1065
|
+
size: "sm",
|
|
1066
|
+
onClick: handleCancel,
|
|
1067
|
+
className: "text-muted-foreground hover:text-foreground h-8 text-xs",
|
|
1068
|
+
children: cancelText
|
|
1069
|
+
}
|
|
1070
|
+
),
|
|
1071
|
+
/* @__PURE__ */ jsxs2(
|
|
1072
|
+
Button,
|
|
1073
|
+
{
|
|
1074
|
+
type: "submit",
|
|
1075
|
+
size: "sm",
|
|
1076
|
+
className: "bg-primary text-primary-foreground shadow-none hover:bg-primary/90 h-8 text-xs",
|
|
1077
|
+
children: [
|
|
1078
|
+
/* @__PURE__ */ jsx10(Send, { className: "w-3 h-3 mr-1.5" }),
|
|
1079
|
+
submitText
|
|
1080
|
+
]
|
|
1081
|
+
}
|
|
1082
|
+
)
|
|
1083
|
+
] })
|
|
1084
|
+
] })
|
|
1085
|
+
] });
|
|
161
1086
|
}
|
|
162
1087
|
|
|
163
1088
|
// src/manifest.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamoid/chat-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Shared chat UI components for the Streamoid chat host — DynamicForm and other cross-service components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,7 +19,20 @@
|
|
|
19
19
|
"build": "tsup",
|
|
20
20
|
"prepublishOnly": "npm run build"
|
|
21
21
|
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@radix-ui/react-checkbox": "^1.1.0",
|
|
24
|
+
"@radix-ui/react-label": "^2.1.0",
|
|
25
|
+
"@radix-ui/react-progress": "^1.1.0",
|
|
26
|
+
"@radix-ui/react-radio-group": "^1.2.0",
|
|
27
|
+
"@radix-ui/react-select": "^2.1.0",
|
|
28
|
+
"@radix-ui/react-separator": "^1.1.0",
|
|
29
|
+
"@radix-ui/react-slot": "^1.1.0",
|
|
30
|
+
"class-variance-authority": "^0.7.0",
|
|
31
|
+
"clsx": "^2.1.0",
|
|
32
|
+
"tailwind-merge": "^2.3.0"
|
|
33
|
+
},
|
|
22
34
|
"peerDependencies": {
|
|
35
|
+
"lucide-react": ">=0.300.0",
|
|
23
36
|
"react": "^18.0.0",
|
|
24
37
|
"react-dom": "^18.0.0"
|
|
25
38
|
},
|