@pol-studios/ui 1.0.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.
@@ -0,0 +1,504 @@
1
+ "use client";
2
+
3
+ // src/forms/MultiForm.tsx
4
+ import { isNullOrWhitespace } from "@pol-studios/utils";
5
+ import { Box, Step, StepLabel, Stepper } from "@mui/material";
6
+ import { useEffect, useMemo, useState } from "react";
7
+
8
+ // src/primitives/button.tsx
9
+ import * as React from "react";
10
+ import { Slot } from "@radix-ui/react-slot";
11
+ import { cva } from "class-variance-authority";
12
+ import { cn } from "@pol-studios/utils";
13
+ import { jsx } from "react/jsx-runtime";
14
+ var buttonVariants = cva(
15
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
16
+ {
17
+ variants: {
18
+ variant: {
19
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
20
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
21
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
22
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
23
+ ghost: "hover:bg-accent hover:text-accent-foreground",
24
+ link: "text-primary underline-offset-4 hover:underline"
25
+ },
26
+ size: {
27
+ default: "h-10 px-4 py-2",
28
+ sm: "h-9 rounded-md px-3",
29
+ lg: "h-11 rounded-md px-8",
30
+ icon: "h-10 w-10"
31
+ }
32
+ },
33
+ defaultVariants: {
34
+ variant: "default",
35
+ size: "default"
36
+ }
37
+ }
38
+ );
39
+ var Button = React.forwardRef(
40
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
41
+ const Comp = asChild ? Slot : "button";
42
+ return /* @__PURE__ */ jsx(
43
+ Comp,
44
+ {
45
+ className: cn(buttonVariants({ variant, size, className })),
46
+ ref,
47
+ ...props
48
+ }
49
+ );
50
+ }
51
+ );
52
+ Button.displayName = "Button";
53
+
54
+ // src/primitives/skeleton.tsx
55
+ import * as React2 from "react";
56
+ import { cn as cn2 } from "@pol-studios/utils";
57
+ import { jsx as jsx2 } from "react/jsx-runtime";
58
+ var Skeleton = React2.forwardRef(({ className, ...props }, ref) => {
59
+ return /* @__PURE__ */ jsx2(
60
+ "div",
61
+ {
62
+ ref,
63
+ className: cn2("animate-pulse rounded-md bg-muted", className),
64
+ ...props
65
+ }
66
+ );
67
+ });
68
+ Skeleton.displayName = "Skeleton";
69
+
70
+ // src/forms/MultiForm.tsx
71
+ import { cn as cn3 } from "@pol-studios/utils";
72
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
73
+ function useMultistepForm(steps) {
74
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
75
+ function goNext() {
76
+ setCurrentStepIndex((i) => i >= steps.length - 1 ? i : i + 1);
77
+ }
78
+ function goBack() {
79
+ setCurrentStepIndex((i) => i <= 0 ? i : i - 1);
80
+ }
81
+ function goTo(index) {
82
+ setCurrentStepIndex(index);
83
+ }
84
+ return {
85
+ currentStepIndex,
86
+ currentStep: steps[currentStepIndex],
87
+ steps,
88
+ isFirstStep: currentStepIndex === 0,
89
+ isLastStep: currentStepIndex === steps.length - 1,
90
+ goBack,
91
+ goNext,
92
+ goTo
93
+ };
94
+ }
95
+ function MultiForm({
96
+ views,
97
+ className,
98
+ validateStep,
99
+ onSuccess,
100
+ canGoBack = true,
101
+ canGoForward = true,
102
+ startingStepIndex,
103
+ onNavigateStep
104
+ }) {
105
+ const viewComponents = useMemo(() => Array.from(views.values()), [views]);
106
+ const [isLoading, setIsLoading] = useState(false);
107
+ const [errorMessage, setErrorMessage] = useState("");
108
+ const { currentStepIndex, currentStep, steps, goBack, goNext, goTo } = useMultistepForm(viewComponents.map((x) => x[1]));
109
+ useEffect(() => {
110
+ if (startingStepIndex) {
111
+ goTo(startingStepIndex);
112
+ onNavigateStep && onNavigateStep(startingStepIndex);
113
+ }
114
+ }, [startingStepIndex]);
115
+ async function onSubmit(e) {
116
+ e.preventDefault();
117
+ setIsLoading(true);
118
+ const nextStep = currentStepIndex + 1;
119
+ const response = validateStep && validateStep(currentStepIndex);
120
+ let errorMsg = null;
121
+ if (response instanceof Promise) {
122
+ errorMsg = await response;
123
+ } else {
124
+ errorMsg = response ?? null;
125
+ }
126
+ if (isNullOrWhitespace(errorMsg) && nextStep !== steps.length) {
127
+ goTo(nextStep);
128
+ await (onNavigateStep && onNavigateStep(nextStep));
129
+ setErrorMessage("");
130
+ } else {
131
+ setErrorMessage(errorMsg || "");
132
+ }
133
+ setIsLoading(false);
134
+ }
135
+ async function tryOnSuccess() {
136
+ let errorMsg = null;
137
+ if (currentStepIndex + 1 === steps.length) {
138
+ errorMsg = await (onSuccess ? onSuccess() : void 0);
139
+ }
140
+ setErrorMessage(errorMsg ?? "");
141
+ }
142
+ const onBackClicked = () => {
143
+ setErrorMessage("");
144
+ goBack();
145
+ };
146
+ return /* @__PURE__ */ jsxs("div", { className, children: [
147
+ /* @__PURE__ */ jsx3(
148
+ Box,
149
+ {
150
+ sx: {
151
+ width: "70%",
152
+ minWidth: "min(450px, 100dvw)",
153
+ textAlign: "center",
154
+ margin: "auto",
155
+ padding: "25px",
156
+ height: "10%"
157
+ },
158
+ children: /* @__PURE__ */ jsx3(Stepper, { activeStep: currentStepIndex, children: views.map((x) => x[0]).map((label) => /* @__PURE__ */ jsx3(Step, { children: /* @__PURE__ */ jsx3(StepLabel, { children: label }) }, label)) })
159
+ }
160
+ ),
161
+ /* @__PURE__ */ jsxs(
162
+ "form",
163
+ {
164
+ onSubmit,
165
+ className: "grid h-[90%] grid-rows-[auto_1fr_auto] p-5",
166
+ children: [
167
+ /* @__PURE__ */ jsxs("div", { className: cn3("relative", isLoading && "opacity-50 pointer-events-none"), children: [
168
+ isLoading && /* @__PURE__ */ jsx3("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx3(Skeleton, { className: "h-8 w-8 rounded-full" }) }),
169
+ /* @__PURE__ */ jsx3("div", { children: currentStep })
170
+ ] }),
171
+ /* @__PURE__ */ jsx3("p", { className: "text-center text-xs text-wrap text-red-500 font-bold", children: errorMessage }),
172
+ /* @__PURE__ */ jsxs("div", { className: "mt-auto grid grid-flow-col grid-cols-[auto_1fr_auto] pt-5", children: [
173
+ canGoBack && currentStepIndex !== 0 ? /* @__PURE__ */ jsx3(
174
+ Button,
175
+ {
176
+ type: "button",
177
+ "data-testid": "back-button",
178
+ onClick: onBackClicked,
179
+ children: "Back"
180
+ }
181
+ ) : /* @__PURE__ */ jsx3("div", {}),
182
+ /* @__PURE__ */ jsx3("div", {}),
183
+ canGoForward && /* @__PURE__ */ jsx3(
184
+ Button,
185
+ {
186
+ type: "submit",
187
+ "data-testid": "next-button",
188
+ onClick: tryOnSuccess,
189
+ children: currentStepIndex !== steps.length - 1 ? "Next" : "Finish"
190
+ }
191
+ )
192
+ ] })
193
+ ]
194
+ }
195
+ )
196
+ ] });
197
+ }
198
+
199
+ // src/forms/MultiStepForm.tsx
200
+ import React4 from "react";
201
+ import { useControlledState } from "@react-stately/utils";
202
+ import { m, LazyMotion, domAnimation } from "framer-motion";
203
+ import { cn as cn5 } from "@pol-studios/utils";
204
+
205
+ // src/primitives/scroll-area.tsx
206
+ import * as React3 from "react";
207
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
208
+ import { cn as cn4 } from "@pol-studios/utils";
209
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
210
+ var ScrollArea = React3.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs2(
211
+ ScrollAreaPrimitive.Root,
212
+ {
213
+ ref,
214
+ className: cn4("relative overflow-hidden", className),
215
+ ...props,
216
+ children: [
217
+ /* @__PURE__ */ jsx4(ScrollAreaPrimitive.Viewport, { className: "h-full w-full rounded-[inherit]", children }),
218
+ /* @__PURE__ */ jsx4(ScrollBar, {}),
219
+ /* @__PURE__ */ jsx4(ScrollAreaPrimitive.Corner, {})
220
+ ]
221
+ }
222
+ ));
223
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
224
+ var ScrollBar = React3.forwardRef(({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsx4(
225
+ ScrollAreaPrimitive.ScrollAreaScrollbar,
226
+ {
227
+ ref,
228
+ orientation,
229
+ className: cn4(
230
+ "flex touch-none select-none transition-colors",
231
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
232
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
233
+ className
234
+ ),
235
+ ...props,
236
+ children: /* @__PURE__ */ jsx4(ScrollAreaPrimitive.ScrollAreaThumb, { className: "relative flex-1 rounded-full bg-border" })
237
+ }
238
+ ));
239
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
240
+
241
+ // src/forms/MultiStepForm.tsx
242
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
243
+ function CheckIcon(props) {
244
+ return /* @__PURE__ */ jsx5(
245
+ "svg",
246
+ {
247
+ ...props,
248
+ fill: "none",
249
+ stroke: "currentColor",
250
+ strokeWidth: 2,
251
+ viewBox: "0 0 24 24",
252
+ children: /* @__PURE__ */ jsx5(
253
+ m.path,
254
+ {
255
+ animate: { pathLength: 1 },
256
+ d: "M5 13l4 4L19 7",
257
+ initial: { pathLength: 0 },
258
+ strokeLinecap: "round",
259
+ strokeLinejoin: "round",
260
+ transition: {
261
+ delay: 0.2,
262
+ type: "tween",
263
+ ease: "easeOut",
264
+ duration: 0.3
265
+ }
266
+ }
267
+ )
268
+ }
269
+ );
270
+ }
271
+ var MultiStepForm = React4.forwardRef(
272
+ ({
273
+ color = "primary",
274
+ steps = [],
275
+ defaultStep = 0,
276
+ onStepChange,
277
+ currentStep: currentStepProp,
278
+ hideProgressBars = false,
279
+ stepClassName,
280
+ className,
281
+ ...props
282
+ }, ref) => {
283
+ const [currentStep, setCurrentStep] = useControlledState(
284
+ currentStepProp,
285
+ defaultStep,
286
+ onStepChange
287
+ );
288
+ const colors = React4.useMemo(() => {
289
+ let userColor;
290
+ let fgColor;
291
+ const colorsVars = [
292
+ "[--active-fg-color:var(--step-fg-color)]",
293
+ "[--active-border-color:var(--step-color)]",
294
+ "[--active-color:var(--step-color)]",
295
+ "[--complete-background-color:var(--step-color)]",
296
+ "[--complete-border-color:var(--step-color)]",
297
+ "[--inactive-border-color:hsl(var(--muted))]",
298
+ "[--inactive-color:hsl(var(--muted))]"
299
+ ];
300
+ switch (color) {
301
+ case "primary":
302
+ userColor = "[--step-color:hsl(var(--primary))]";
303
+ fgColor = "[--step-fg-color:hsl(var(--primary-foreground))]";
304
+ break;
305
+ case "secondary":
306
+ userColor = "[--step-color:hsl(var(--secondary))]";
307
+ fgColor = "[--step-fg-color:hsl(var(--secondary-foreground))]";
308
+ break;
309
+ case "success":
310
+ userColor = "[--step-color:hsl(var(--success))]";
311
+ fgColor = "[--step-fg-color:hsl(var(--success-foreground))]";
312
+ break;
313
+ case "warning":
314
+ userColor = "[--step-color:hsl(var(--warning))]";
315
+ fgColor = "[--step-fg-color:hsl(var(--warning-foreground))]";
316
+ break;
317
+ case "danger":
318
+ userColor = "[--step-color:hsl(var(--destructive))]";
319
+ fgColor = "[--step-fg-color:hsl(var(--destructive-foreground))]";
320
+ break;
321
+ case "default":
322
+ userColor = "[--step-color:hsl(var(--muted-foreground))]";
323
+ fgColor = "[--step-fg-color:hsl(var(--muted))]";
324
+ break;
325
+ default:
326
+ userColor = "[--step-color:hsl(var(--primary))]";
327
+ fgColor = "[--step-fg-color:hsl(var(--primary-foreground))]";
328
+ break;
329
+ }
330
+ if (!className?.includes("--step-fg-color")) colorsVars.unshift(fgColor);
331
+ if (!className?.includes("--step-color")) colorsVars.unshift(userColor);
332
+ if (!className?.includes("--inactive-bar-color"))
333
+ colorsVars.push(
334
+ "[--inactive-bar-color:hsl(var(--muted))]"
335
+ );
336
+ return colorsVars;
337
+ }, [color, className]);
338
+ return /* @__PURE__ */ jsxs3("div", { className: cn5("flex flex-col overflow-auto", className), children: [
339
+ /* @__PURE__ */ jsx5(
340
+ "div",
341
+ {
342
+ "aria-label": "Progress",
343
+ className: "max-w-fit overflow-x-auto mx-auto pb-4 table",
344
+ children: /* @__PURE__ */ jsx5("ol", { className: cn5("flex flex-row flex-nowrap gap-x-3", colors), children: steps?.map((step, stepIdx) => {
345
+ let status = currentStep === stepIdx ? "active" : currentStep < stepIdx ? "inactive" : "complete";
346
+ return /* @__PURE__ */ jsx5(
347
+ "li",
348
+ {
349
+ className: "relative flex w-full items-center pr-12",
350
+ children: /* @__PURE__ */ jsxs3(
351
+ "button",
352
+ {
353
+ ref,
354
+ type: "button",
355
+ "aria-current": status === "active" ? "step" : void 0,
356
+ className: cn5(
357
+ "group flex w-full cursor-pointer flex-row items-center justify-center gap-x-3 rounded-lg py-2.5",
358
+ stepClassName
359
+ ),
360
+ disabled: status === "inactive",
361
+ onClick: () => setCurrentStep(stepIdx),
362
+ ...props,
363
+ children: [
364
+ /* @__PURE__ */ jsx5("div", { className: "h-ful relative flex items-center", children: /* @__PURE__ */ jsx5(LazyMotion, { features: domAnimation, children: /* @__PURE__ */ jsx5(m.div, { animate: status, className: "relative", children: /* @__PURE__ */ jsx5(
365
+ m.div,
366
+ {
367
+ className: cn5(
368
+ "relative flex h-[34px] w-[34px] items-center justify-center rounded-full border-2 text-large font-semibold",
369
+ {
370
+ "shadow-lg": status === "complete"
371
+ }
372
+ ),
373
+ initial: false,
374
+ transition: { duration: 0.25 },
375
+ variants: {
376
+ inactive: {
377
+ backgroundColor: "transparent",
378
+ borderColor: "var(--inactive-border-color)",
379
+ color: "var(--inactive-color)"
380
+ },
381
+ active: {
382
+ backgroundColor: "transparent",
383
+ borderColor: "var(--active-border-color)",
384
+ color: "var(--active-color)"
385
+ },
386
+ complete: {
387
+ backgroundColor: "var(--complete-background-color)",
388
+ borderColor: "var(--complete-border-color)"
389
+ }
390
+ },
391
+ children: /* @__PURE__ */ jsx5("div", { className: "flex items-center justify-center", children: status === "complete" ? /* @__PURE__ */ jsx5(CheckIcon, { className: "h-6 w-6 text-[var(--active-fg-color)]" }) : /* @__PURE__ */ jsx5("span", { children: stepIdx + 1 }) })
392
+ }
393
+ ) }) }) }),
394
+ /* @__PURE__ */ jsx5("div", { className: "max-w-full flex-1 text-start", children: /* @__PURE__ */ jsx5(
395
+ "div",
396
+ {
397
+ className: cn5(
398
+ "text-sm font-medium transition-[color,opacity] duration-300 group-active:opacity-80 lg:text-base",
399
+ {
400
+ "text-muted-foreground": status === "inactive"
401
+ }
402
+ ),
403
+ children: step.title
404
+ }
405
+ ) }),
406
+ stepIdx < steps.length - 1 && !hideProgressBars && /* @__PURE__ */ jsx5(
407
+ "div",
408
+ {
409
+ "aria-hidden": "true",
410
+ className: "pointer-events-none absolute right-0 w-10 flex-none items-center",
411
+ style: {
412
+ // @ts-ignore
413
+ "--idx": stepIdx
414
+ },
415
+ children: /* @__PURE__ */ jsx5(
416
+ "div",
417
+ {
418
+ className: cn5(
419
+ "relative h-0.5 w-full bg-[var(--inactive-bar-color)] transition-colors duration-300",
420
+ "after:absolute after:block after:h-full after:w-0 after:bg-[var(--active-border-color)] after:transition-[width] after:duration-300 after:content-['']",
421
+ {
422
+ "after:w-full": stepIdx < currentStep
423
+ }
424
+ )
425
+ }
426
+ )
427
+ }
428
+ )
429
+ ]
430
+ },
431
+ stepIdx
432
+ )
433
+ },
434
+ stepIdx
435
+ );
436
+ }) })
437
+ }
438
+ ),
439
+ /* @__PURE__ */ jsx5(ScrollArea, { className: "py-5 px-2 flex-1", children: steps[currentStep]?.content })
440
+ ] });
441
+ }
442
+ );
443
+ MultiStepForm.displayName = "MultiStepForm";
444
+ var MultiStepForm_default = MultiStepForm;
445
+
446
+ // src/forms/FileDropzone.tsx
447
+ import { FileInput, Label } from "flowbite-react";
448
+ import { cn as cn6 } from "@pol-studios/utils";
449
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
450
+ function FileDropzone({
451
+ children,
452
+ onFileUpload,
453
+ accept,
454
+ className
455
+ }) {
456
+ return /* @__PURE__ */ jsx6("div", { className: cn6("flex w-full items-center justify-center", className), children: /* @__PURE__ */ jsxs4(
457
+ Label,
458
+ {
459
+ htmlFor: "dropzone-file",
460
+ className: "flex h-64 w-full cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:hover:bg-gray-600",
461
+ children: [
462
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-center justify-center pb-6 pt-5", children: [
463
+ /* @__PURE__ */ jsx6(
464
+ "svg",
465
+ {
466
+ className: "mb-4 h-8 w-8 text-gray-500 dark:text-gray-400",
467
+ "aria-hidden": "true",
468
+ xmlns: "http://www.w3.org/2000/svg",
469
+ fill: "none",
470
+ viewBox: "0 0 20 16",
471
+ children: /* @__PURE__ */ jsx6(
472
+ "path",
473
+ {
474
+ stroke: "currentColor",
475
+ strokeLinecap: "round",
476
+ strokeLinejoin: "round",
477
+ strokeWidth: "2",
478
+ d: "M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
479
+ }
480
+ )
481
+ }
482
+ ),
483
+ children
484
+ ] }),
485
+ /* @__PURE__ */ jsx6(
486
+ FileInput,
487
+ {
488
+ id: "dropzone-file",
489
+ onChange: (e) => onFileUpload(e.target.files),
490
+ className: "hidden",
491
+ accept
492
+ }
493
+ )
494
+ ]
495
+ }
496
+ ) });
497
+ }
498
+ var FileDropzone_default = FileDropzone;
499
+ export {
500
+ FileDropzone,
501
+ FileDropzone_default as FileDropzoneDefault,
502
+ MultiForm,
503
+ MultiStepForm_default as MultiStepForm
504
+ };