@carlonicora/nextjs-jsonapi 1.116.0 → 1.117.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/{BlockNoteEditor-R6U4PCQ7.js → BlockNoteEditor-6UREFUSJ.js} +9 -9
- package/dist/{BlockNoteEditor-R6U4PCQ7.js.map → BlockNoteEditor-6UREFUSJ.js.map} +1 -1
- package/dist/{BlockNoteEditor-RFRK3STN.mjs → BlockNoteEditor-UQOOX3UT.mjs} +2 -2
- package/dist/billing/index.js +299 -299
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-SPRFCAQI.js → chunk-HXU2HDRN.js} +406 -272
- package/dist/chunk-HXU2HDRN.js.map +1 -0
- package/dist/{chunk-RKUUGCYB.mjs → chunk-SVEPL5J2.mjs} +512 -378
- package/dist/chunk-SVEPL5J2.mjs.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.d.mts +68 -21
- package/dist/components/index.d.ts +68 -21
- package/dist/components/index.js +4 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +3 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/features/help/index.js +30 -30
- package/dist/features/help/index.mjs +1 -1
- package/package.json +1 -1
- package/src/components/forms/CommonDeleter.tsx +49 -18
- package/src/components/forms/CommonEditorButtons.tsx +4 -2
- package/src/components/forms/CommonEditorHeader.tsx +20 -14
- package/src/components/forms/CommonEditorTrigger.tsx +18 -4
- package/src/components/forms/DateRangeSelector.tsx +55 -19
- package/src/components/forms/EditorSheet.tsx +36 -9
- package/src/components/forms/FileUploader.tsx +51 -21
- package/src/components/forms/FormDate.tsx +6 -2
- package/src/components/forms/FormDateTime.tsx +11 -2
- package/src/components/forms/FormFieldWrapper.tsx +3 -1
- package/src/components/forms/FormInput.tsx +3 -1
- package/src/components/forms/FormPassword.tsx +10 -1
- package/src/components/forms/FormPlaceAutocomplete.tsx +8 -3
- package/src/components/forms/FormSelect.tsx +3 -0
- package/src/components/forms/FormSlider.tsx +3 -1
- package/src/components/forms/FormTextarea.tsx +15 -1
- package/src/shadcnui/ui/badge.tsx +15 -0
- package/dist/chunk-RKUUGCYB.mjs.map +0 -1
- package/dist/chunk-SPRFCAQI.js.map +0 -1
- /package/dist/{BlockNoteEditor-RFRK3STN.mjs.map → BlockNoteEditor-UQOOX3UT.mjs.map} +0 -0
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { PencilIcon } from "lucide-react";
|
|
3
|
+
import { PencilIcon, PlusCircleIcon } from "lucide-react";
|
|
4
4
|
|
|
5
5
|
import { useTranslations } from "next-intl";
|
|
6
|
+
import React from "react";
|
|
6
7
|
import { Button, DialogTrigger } from "../../shadcnui";
|
|
7
8
|
|
|
8
|
-
type CommonEditorTriggerProps = { isEdit: boolean; edit?: string; create?: string; testId?: string };
|
|
9
|
+
type CommonEditorTriggerProps = { isEdit: boolean; edit?: string; create?: string; testId?: string; title?: string };
|
|
9
10
|
|
|
10
|
-
export function CommonEditorTrigger({ isEdit, edit: _edit, create, testId }: CommonEditorTriggerProps) {
|
|
11
|
+
export function CommonEditorTrigger({ isEdit, edit: _edit, create, testId, title }: CommonEditorTriggerProps) {
|
|
11
12
|
const t = useTranslations();
|
|
12
13
|
|
|
13
14
|
return (
|
|
14
15
|
<DialogTrigger>
|
|
15
|
-
{
|
|
16
|
+
{title ? (
|
|
17
|
+
<Button render={<div />} nativeButton={false} size="sm" variant={`outline`} data-testid={testId}>
|
|
18
|
+
{title}
|
|
19
|
+
</Button>
|
|
20
|
+
) : isEdit ? (
|
|
16
21
|
<Button
|
|
17
22
|
render={<div />}
|
|
18
23
|
nativeButton={false}
|
|
@@ -31,3 +36,12 @@ export function CommonEditorTrigger({ isEdit, edit: _edit, create, testId }: Com
|
|
|
31
36
|
</DialogTrigger>
|
|
32
37
|
);
|
|
33
38
|
}
|
|
39
|
+
|
|
40
|
+
export const CommonAddTrigger = React.forwardRef<HTMLButtonElement, React.ComponentPropsWithoutRef<typeof Button>>(
|
|
41
|
+
(props, ref) => (
|
|
42
|
+
<Button ref={ref} variant="ghost" size="sm" className="text-muted-foreground" {...props}>
|
|
43
|
+
<PlusCircleIcon />
|
|
44
|
+
</Button>
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
CommonAddTrigger.displayName = "CommonAddTrigger";
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { format } from "date-fns";
|
|
4
4
|
import { CalendarIcon } from "lucide-react";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
5
6
|
import { useEffect, useState } from "react";
|
|
6
7
|
import { DateRange } from "react-day-picker";
|
|
7
8
|
import { Button, Calendar, Popover, PopoverContent, PopoverTrigger } from "../../shadcnui";
|
|
@@ -14,6 +15,7 @@ type DateRangeSelectorProps = {
|
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviousMonth }: DateRangeSelectorProps) {
|
|
18
|
+
const t = useTranslations();
|
|
17
19
|
const [date, setDate] = useState<DateRange | undefined>(
|
|
18
20
|
avoidSettingDates
|
|
19
21
|
? undefined
|
|
@@ -24,6 +26,12 @@ export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviou
|
|
|
24
26
|
);
|
|
25
27
|
|
|
26
28
|
const [open, setOpen] = useState(false);
|
|
29
|
+
const [mounted, setMounted] = useState(false);
|
|
30
|
+
|
|
31
|
+
// Prevent hydration mismatch by only rendering Popover after mount
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setMounted(true);
|
|
34
|
+
}, []);
|
|
27
35
|
|
|
28
36
|
const [prevRange, setPrevRange] = useState<DateRange | undefined>(date);
|
|
29
37
|
useEffect(() => {
|
|
@@ -53,28 +61,56 @@ export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviou
|
|
|
53
61
|
}
|
|
54
62
|
};
|
|
55
63
|
|
|
64
|
+
// Show placeholder button during SSR to prevent hydration mismatch
|
|
65
|
+
if (!mounted) {
|
|
66
|
+
return (
|
|
67
|
+
<div className={cn("grid gap-2")}>
|
|
68
|
+
<Button
|
|
69
|
+
id="date"
|
|
70
|
+
variant={"outline"}
|
|
71
|
+
className={cn("w-[300px] justify-start text-left font-normal", !date && "text-muted-foreground")}
|
|
72
|
+
>
|
|
73
|
+
<CalendarIcon />
|
|
74
|
+
{date?.from ? (
|
|
75
|
+
date.to ? (
|
|
76
|
+
<>
|
|
77
|
+
{format(date.from, "LLL dd, y")} - {format(date.to, "LLL dd, y")}
|
|
78
|
+
</>
|
|
79
|
+
) : (
|
|
80
|
+
format(date.from, "LLL dd, y")
|
|
81
|
+
)
|
|
82
|
+
) : (
|
|
83
|
+
<span>{t("ui.labels.pick_a_date")}</span>
|
|
84
|
+
)}
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
56
90
|
return (
|
|
57
91
|
<div className={cn("grid gap-2")}>
|
|
58
92
|
<Popover open={open} onOpenChange={setOpen}>
|
|
59
|
-
<PopoverTrigger
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
format(date.from, "LLL dd, y")
|
|
73
|
-
|
|
93
|
+
<PopoverTrigger
|
|
94
|
+
render={
|
|
95
|
+
<Button
|
|
96
|
+
id="date"
|
|
97
|
+
variant={"outline"}
|
|
98
|
+
className={cn("w-[300px] justify-start text-left font-normal", !date && "text-muted-foreground")}
|
|
99
|
+
/>
|
|
100
|
+
}
|
|
101
|
+
>
|
|
102
|
+
<CalendarIcon />
|
|
103
|
+
{date?.from ? (
|
|
104
|
+
date.to ? (
|
|
105
|
+
<>
|
|
106
|
+
{format(date.from, "LLL dd, y")} - {format(date.to, "LLL dd, y")}
|
|
107
|
+
</>
|
|
74
108
|
) : (
|
|
75
|
-
|
|
76
|
-
)
|
|
77
|
-
|
|
109
|
+
format(date.from, "LLL dd, y")
|
|
110
|
+
)
|
|
111
|
+
) : (
|
|
112
|
+
<span>{t("ui.labels.pick_a_date")}</span>
|
|
113
|
+
)}
|
|
78
114
|
</PopoverTrigger>
|
|
79
115
|
<PopoverContent className="w-auto p-0" align="start">
|
|
80
116
|
<div className="flex flex-col gap-2 p-2">
|
|
@@ -100,7 +136,7 @@ export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviou
|
|
|
100
136
|
className="cursor-pointer"
|
|
101
137
|
disabled={!date}
|
|
102
138
|
>
|
|
103
|
-
|
|
139
|
+
{t("ui.buttons.clear")}
|
|
104
140
|
</Button>
|
|
105
141
|
</div>
|
|
106
142
|
</PopoverContent>
|
|
@@ -62,6 +62,17 @@ export type EditorSheetProps<T extends FieldValues> = {
|
|
|
62
62
|
dialogOpen?: boolean;
|
|
63
63
|
onDialogOpenChange?: (open: boolean) => void;
|
|
64
64
|
|
|
65
|
+
/** Render a fully custom footer instead of the default CommonEditorButtons.
|
|
66
|
+
* `setOpen` is the dirty-checked open handler (shows the discard dialog);
|
|
67
|
+
* `closeWithoutConfirm` is the raw setter that bypasses the discard dialog
|
|
68
|
+
* (use after a successful submit when no confirmation is needed). */
|
|
69
|
+
renderFooter?: (props: {
|
|
70
|
+
form: UseFormReturn<T>;
|
|
71
|
+
isEdit: boolean;
|
|
72
|
+
setOpen: (open: boolean) => void;
|
|
73
|
+
closeWithoutConfirm: (open: boolean) => void;
|
|
74
|
+
}) => ReactNode;
|
|
75
|
+
|
|
65
76
|
children: ReactNode;
|
|
66
77
|
};
|
|
67
78
|
|
|
@@ -90,6 +101,7 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
90
101
|
onClose,
|
|
91
102
|
dialogOpen,
|
|
92
103
|
onDialogOpenChange,
|
|
104
|
+
renderFooter,
|
|
93
105
|
children,
|
|
94
106
|
}: EditorSheetProps<T>) {
|
|
95
107
|
const t = useTranslations();
|
|
@@ -147,6 +159,7 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
147
159
|
<>
|
|
148
160
|
<Sheet open={open} onOpenChange={handleOpenChange}>
|
|
149
161
|
{dialogOpen === undefined &&
|
|
162
|
+
forceShow === undefined &&
|
|
150
163
|
(trigger ? (
|
|
151
164
|
<SheetTrigger>{trigger}</SheetTrigger>
|
|
152
165
|
) : (
|
|
@@ -184,17 +197,31 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
184
197
|
</SheetDescription>
|
|
185
198
|
</SheetHeader>
|
|
186
199
|
<Form {...form}>
|
|
187
|
-
<form
|
|
200
|
+
<form
|
|
201
|
+
onSubmit={(e) => {
|
|
202
|
+
// The Sheet content is portaled, but React synthetic events still
|
|
203
|
+
// bubble up the React tree — so without stopPropagation an inner
|
|
204
|
+
// EditorSheet's submit also triggers the outer form's submit
|
|
205
|
+
// (e.g. a create dialog opened from within another editor).
|
|
206
|
+
e.stopPropagation();
|
|
207
|
+
return form.handleSubmit(wrappedOnSubmit)(e);
|
|
208
|
+
}}
|
|
209
|
+
className="flex min-h-0 flex-1 flex-col"
|
|
210
|
+
>
|
|
188
211
|
<div className="flex-1 overflow-y-auto px-6 py-4">{children}</div>
|
|
189
212
|
<SheetFooter className="shrink-0 border-t px-6 py-4">
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
213
|
+
{renderFooter ? (
|
|
214
|
+
renderFooter({ form, isEdit, setOpen: handleOpenChange, closeWithoutConfirm: setOpen })
|
|
215
|
+
) : (
|
|
216
|
+
<CommonEditorButtons
|
|
217
|
+
form={form}
|
|
218
|
+
setOpen={handleOpenChange}
|
|
219
|
+
isEdit={isEdit}
|
|
220
|
+
disabled={disabled}
|
|
221
|
+
hideSubmit={hideSubmit}
|
|
222
|
+
centerButtons={centerButtons}
|
|
223
|
+
/>
|
|
224
|
+
)}
|
|
198
225
|
</SheetFooter>
|
|
199
226
|
</form>
|
|
200
227
|
</Form>
|
|
@@ -10,12 +10,13 @@ import {
|
|
|
10
10
|
useCallback,
|
|
11
11
|
useContext,
|
|
12
12
|
useEffect,
|
|
13
|
+
useMemo,
|
|
13
14
|
useRef,
|
|
14
15
|
useState,
|
|
15
16
|
} from "react";
|
|
16
|
-
import { DropzoneOptions, DropzoneState, FileRejection, useDropzone } from "react-dropzone";
|
|
17
|
+
import { Accept, DropzoneOptions, DropzoneState, FileRejection, useDropzone } from "react-dropzone";
|
|
17
18
|
import { showError } from "../../utils/toast";
|
|
18
|
-
import { buttonVariants, Input } from "../../shadcnui";
|
|
19
|
+
import { buttonVariants, Input, Tooltip, TooltipContent, TooltipTrigger } from "../../shadcnui";
|
|
19
20
|
import { cn } from "../../utils";
|
|
20
21
|
|
|
21
22
|
export type { DropzoneOptions } from "react-dropzone";
|
|
@@ -31,6 +32,7 @@ type FileUploaderContextType = {
|
|
|
31
32
|
setActiveIndex: Dispatch<SetStateAction<number>>;
|
|
32
33
|
orientation: "horizontal" | "vertical";
|
|
33
34
|
direction: DirectionOptions;
|
|
35
|
+
accept?: Accept;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
const FileUploaderContext = createContext<FileUploaderContextType | null>(null);
|
|
@@ -195,6 +197,7 @@ export const FileUploader = forwardRef<HTMLDivElement, FileUploaderProps & React
|
|
|
195
197
|
setActiveIndex,
|
|
196
198
|
orientation,
|
|
197
199
|
direction,
|
|
200
|
+
accept: dropzoneOptions.accept,
|
|
198
201
|
}}
|
|
199
202
|
>
|
|
200
203
|
<div
|
|
@@ -283,35 +286,62 @@ FileUploaderItem.displayName = "FileUploaderItem";
|
|
|
283
286
|
|
|
284
287
|
export const FileInput = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
285
288
|
({ className, children, ...props }, ref) => {
|
|
286
|
-
const { dropzoneState, isFileTooBig, isLOF } = useFileUpload();
|
|
289
|
+
const { dropzoneState, isFileTooBig, isLOF, accept } = useFileUpload();
|
|
290
|
+
const t = useTranslations();
|
|
287
291
|
const rootProps = isLOF ? {} : dropzoneState.getRootProps();
|
|
288
292
|
// Get isDragActive from the context for FileInput as well, to ensure it can react if needed, or to simplify its own styling.
|
|
289
293
|
const { isDragActive: parentIsDragActive } = dropzoneState;
|
|
290
294
|
|
|
295
|
+
const acceptedLabels = useMemo(() => {
|
|
296
|
+
if (!accept) return null;
|
|
297
|
+
const extensions = new Set<string>();
|
|
298
|
+
let hasWildcardImages = false;
|
|
299
|
+
for (const [mime, exts] of Object.entries(accept)) {
|
|
300
|
+
if (mime === "image/*") hasWildcardImages = true;
|
|
301
|
+
for (const ext of exts) extensions.add(ext);
|
|
302
|
+
}
|
|
303
|
+
const labels = Array.from(extensions).sort();
|
|
304
|
+
if (hasWildcardImages) labels.push(t("ui.labels.images"));
|
|
305
|
+
return labels.length > 0 ? labels : null;
|
|
306
|
+
}, [accept, t]);
|
|
307
|
+
|
|
308
|
+
const dropArea = (
|
|
309
|
+
<div
|
|
310
|
+
className={cn(
|
|
311
|
+
"w-full rounded-lg duration-300 ease-in-out",
|
|
312
|
+
// Simpler border logic: if parent is drag-active, it controls the border.
|
|
313
|
+
// Otherwise, FileInput can show its own accept/reject/default border.
|
|
314
|
+
{
|
|
315
|
+
"border-green-500": dropzoneState.isDragAccept && !parentIsDragActive,
|
|
316
|
+
"border-red-500": (dropzoneState.isDragReject || isFileTooBig) && !parentIsDragActive,
|
|
317
|
+
"border-gray-300":
|
|
318
|
+
!dropzoneState.isDragAccept && !dropzoneState.isDragReject && !isFileTooBig && !parentIsDragActive,
|
|
319
|
+
},
|
|
320
|
+
// className from props should be last to allow overrides if necessary
|
|
321
|
+
className,
|
|
322
|
+
)}
|
|
323
|
+
{...rootProps}
|
|
324
|
+
>
|
|
325
|
+
{children}
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
|
|
291
329
|
return (
|
|
292
330
|
<div
|
|
293
331
|
ref={ref}
|
|
294
332
|
{...props}
|
|
295
333
|
className={cn(`relative w-full ${isLOF ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`, className)}
|
|
296
334
|
>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
},
|
|
308
|
-
// className from props should be last to allow overrides if necessary
|
|
309
|
-
className,
|
|
310
|
-
)}
|
|
311
|
-
{...rootProps}
|
|
312
|
-
>
|
|
313
|
-
{children}
|
|
314
|
-
</div>
|
|
335
|
+
{acceptedLabels ? (
|
|
336
|
+
<Tooltip>
|
|
337
|
+
<TooltipTrigger render={dropArea} />
|
|
338
|
+
<TooltipContent>
|
|
339
|
+
{t("ui.labels.accepted_file_types")}: {acceptedLabels.join(", ")}
|
|
340
|
+
</TooltipContent>
|
|
341
|
+
</Tooltip>
|
|
342
|
+
) : (
|
|
343
|
+
dropArea
|
|
344
|
+
)}
|
|
315
345
|
<Input
|
|
316
346
|
ref={dropzoneState.inputRef}
|
|
317
347
|
disabled={isLOF}
|
|
@@ -26,6 +26,8 @@ export function FormDate({
|
|
|
26
26
|
isRequired = false,
|
|
27
27
|
defaultMonth,
|
|
28
28
|
allowEmpty,
|
|
29
|
+
description,
|
|
30
|
+
futureYears,
|
|
29
31
|
}: {
|
|
30
32
|
form: any;
|
|
31
33
|
id: string;
|
|
@@ -36,6 +38,8 @@ export function FormDate({
|
|
|
36
38
|
isRequired?: boolean;
|
|
37
39
|
defaultMonth?: Date;
|
|
38
40
|
allowEmpty?: boolean;
|
|
41
|
+
description?: string;
|
|
42
|
+
futureYears?: number;
|
|
39
43
|
}) {
|
|
40
44
|
const t = useI18nTranslations();
|
|
41
45
|
const locale = useI18nLocale();
|
|
@@ -117,7 +121,7 @@ export function FormDate({
|
|
|
117
121
|
|
|
118
122
|
return (
|
|
119
123
|
<div className="flex w-full flex-col">
|
|
120
|
-
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired}>
|
|
124
|
+
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired} description={description}>
|
|
121
125
|
{(field) => (
|
|
122
126
|
<Popover open={open} onOpenChange={setOpen} modal={true}>
|
|
123
127
|
<InputGroup>
|
|
@@ -162,7 +166,7 @@ export function FormDate({
|
|
|
162
166
|
month={displayMonth}
|
|
163
167
|
onMonthChange={setDisplayMonth}
|
|
164
168
|
startMonth={new Date(1900, 0)}
|
|
165
|
-
endMonth={new Date(new Date().getFullYear() + 10, 11)}
|
|
169
|
+
endMonth={new Date(new Date().getFullYear() + (futureYears ?? 10), 11)}
|
|
166
170
|
/>
|
|
167
171
|
{allowEmpty !== false && (
|
|
168
172
|
<div className="border-t p-2">
|
|
@@ -27,6 +27,9 @@ export function FormDateTime({
|
|
|
27
27
|
onChange,
|
|
28
28
|
allowEmpty,
|
|
29
29
|
defaultMonth,
|
|
30
|
+
placeholder,
|
|
31
|
+
isRequired,
|
|
32
|
+
description,
|
|
30
33
|
}: {
|
|
31
34
|
form: any;
|
|
32
35
|
id: string;
|
|
@@ -36,6 +39,8 @@ export function FormDateTime({
|
|
|
36
39
|
onChange?: (date?: Date) => Promise<void>;
|
|
37
40
|
allowEmpty?: boolean;
|
|
38
41
|
defaultMonth?: Date;
|
|
42
|
+
isRequired?: boolean;
|
|
43
|
+
description?: string;
|
|
39
44
|
}) {
|
|
40
45
|
const [open, setOpen] = useState<boolean>(false);
|
|
41
46
|
const t = useI18nTranslations();
|
|
@@ -100,7 +105,7 @@ export function FormDateTime({
|
|
|
100
105
|
|
|
101
106
|
return (
|
|
102
107
|
<div className="flex w-full flex-col">
|
|
103
|
-
<FormFieldWrapper form={form} name={id} label={name}>
|
|
108
|
+
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired} description={description}>
|
|
104
109
|
{(field) => (
|
|
105
110
|
<div className="relative flex flex-row">
|
|
106
111
|
<Popover open={open} onOpenChange={setOpen} modal={true}>
|
|
@@ -113,7 +118,11 @@ export function FormDateTime({
|
|
|
113
118
|
/>
|
|
114
119
|
}
|
|
115
120
|
>
|
|
116
|
-
{field.value ?
|
|
121
|
+
{field.value ? (
|
|
122
|
+
formatDateTime(field.value)
|
|
123
|
+
) : (
|
|
124
|
+
<span>{placeholder ? placeholder : t(`common.pick_date_time`)}</span>
|
|
125
|
+
)}
|
|
117
126
|
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
|
118
127
|
</PopoverTrigger>
|
|
119
128
|
{field.value && allowEmpty !== false && (
|
|
@@ -18,6 +18,7 @@ type FormFieldWrapperProps<T extends FieldValues> = {
|
|
|
18
18
|
description?: string;
|
|
19
19
|
isRequired?: boolean;
|
|
20
20
|
orientation?: "vertical" | "horizontal" | "responsive";
|
|
21
|
+
className?: string;
|
|
21
22
|
children: (field: ControllerRenderProps<T, Path<T>>, fieldState: ControllerFieldState) => ReactNode;
|
|
22
23
|
testId?: string;
|
|
23
24
|
};
|
|
@@ -29,6 +30,7 @@ export function FormFieldWrapper<T extends FieldValues>({
|
|
|
29
30
|
description,
|
|
30
31
|
isRequired,
|
|
31
32
|
orientation = "vertical",
|
|
33
|
+
className,
|
|
32
34
|
children,
|
|
33
35
|
testId,
|
|
34
36
|
}: FormFieldWrapperProps<T>) {
|
|
@@ -37,7 +39,7 @@ export function FormFieldWrapper<T extends FieldValues>({
|
|
|
37
39
|
control={form.control}
|
|
38
40
|
name={name}
|
|
39
41
|
render={({ field, fieldState }) => (
|
|
40
|
-
<Field orientation={orientation} data-invalid={!!fieldState.error} data-testid={testId}>
|
|
42
|
+
<Field orientation={orientation} className={className} data-invalid={!!fieldState.error} data-testid={testId}>
|
|
41
43
|
{label && (
|
|
42
44
|
<FieldLabel>
|
|
43
45
|
{label}
|
|
@@ -9,6 +9,7 @@ export function FormInput({
|
|
|
9
9
|
form,
|
|
10
10
|
id,
|
|
11
11
|
name,
|
|
12
|
+
description,
|
|
12
13
|
placeholder,
|
|
13
14
|
type,
|
|
14
15
|
onBlur,
|
|
@@ -22,6 +23,7 @@ export function FormInput({
|
|
|
22
23
|
form: any;
|
|
23
24
|
id: string;
|
|
24
25
|
name?: string;
|
|
26
|
+
description?: string;
|
|
25
27
|
placeholder?: string;
|
|
26
28
|
type?: "text" | "number" | "currency" | "decimal" | "password" | "link";
|
|
27
29
|
onBlur?: () => Promise<void>;
|
|
@@ -36,7 +38,7 @@ export function FormInput({
|
|
|
36
38
|
|
|
37
39
|
return (
|
|
38
40
|
<div className="flex w-full flex-col">
|
|
39
|
-
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired}>
|
|
41
|
+
<FormFieldWrapper form={form} name={id} label={name} description={description} isRequired={isRequired}>
|
|
40
42
|
{(field) => {
|
|
41
43
|
const handleBlur = async (e: React.FocusEvent<HTMLInputElement>) => {
|
|
42
44
|
let value = e.target.value;
|
|
@@ -12,6 +12,7 @@ export function FormPassword({
|
|
|
12
12
|
disabled,
|
|
13
13
|
testId,
|
|
14
14
|
isRequired,
|
|
15
|
+
description,
|
|
15
16
|
}: {
|
|
16
17
|
form: any;
|
|
17
18
|
id: string;
|
|
@@ -21,10 +22,18 @@ export function FormPassword({
|
|
|
21
22
|
disabled?: boolean;
|
|
22
23
|
testId?: string;
|
|
23
24
|
isRequired?: boolean;
|
|
25
|
+
description?: string;
|
|
24
26
|
}) {
|
|
25
27
|
return (
|
|
26
28
|
<div className="flex w-full flex-col">
|
|
27
|
-
<FormFieldWrapper
|
|
29
|
+
<FormFieldWrapper
|
|
30
|
+
form={form}
|
|
31
|
+
name={id}
|
|
32
|
+
label={name}
|
|
33
|
+
isRequired={isRequired}
|
|
34
|
+
description={description}
|
|
35
|
+
testId={testId}
|
|
36
|
+
>
|
|
28
37
|
{(field) => (
|
|
29
38
|
<PasswordInput
|
|
30
39
|
{...field}
|
|
@@ -42,6 +42,7 @@ interface PlaceAutocompleteProps {
|
|
|
42
42
|
form: any;
|
|
43
43
|
id: string;
|
|
44
44
|
name?: string;
|
|
45
|
+
description?: string;
|
|
45
46
|
placeholder?: string;
|
|
46
47
|
disabled?: boolean;
|
|
47
48
|
testId?: string;
|
|
@@ -56,12 +57,15 @@ interface PlaceAutocompleteProps {
|
|
|
56
57
|
* For regions, use: ["administrative_area_level_1", "administrative_area_level_2"]
|
|
57
58
|
*/
|
|
58
59
|
includeTypes?: string[];
|
|
60
|
+
/** Google Places language code for results. Defaults to "en". */
|
|
61
|
+
languageCode?: string;
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
export function FormPlaceAutocomplete({
|
|
62
65
|
form,
|
|
63
66
|
id,
|
|
64
67
|
name,
|
|
68
|
+
description,
|
|
65
69
|
placeholder,
|
|
66
70
|
disabled,
|
|
67
71
|
testId,
|
|
@@ -69,6 +73,7 @@ export function FormPlaceAutocomplete({
|
|
|
69
73
|
onPlaceSelect,
|
|
70
74
|
className,
|
|
71
75
|
includeTypes,
|
|
76
|
+
languageCode = "en",
|
|
72
77
|
}: PlaceAutocompleteProps) {
|
|
73
78
|
const [inputValue, setInputValue] = useState("");
|
|
74
79
|
const [suggestions, setSuggestions] = useState<PlaceSuggestion[]>([]);
|
|
@@ -117,7 +122,7 @@ export function FormPlaceAutocomplete({
|
|
|
117
122
|
body: JSON.stringify({
|
|
118
123
|
input: input,
|
|
119
124
|
...(includeTypes ? { includedPrimaryTypes: includeTypes } : {}),
|
|
120
|
-
languageCode:
|
|
125
|
+
languageCode: languageCode,
|
|
121
126
|
}),
|
|
122
127
|
});
|
|
123
128
|
|
|
@@ -270,7 +275,7 @@ export function FormPlaceAutocomplete({
|
|
|
270
275
|
if (loadError) {
|
|
271
276
|
return (
|
|
272
277
|
<div className="flex w-full flex-col">
|
|
273
|
-
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired}>
|
|
278
|
+
<FormFieldWrapper form={form} name={id} label={name} description={description} isRequired={isRequired}>
|
|
274
279
|
{(field) => (
|
|
275
280
|
<Input
|
|
276
281
|
{...field}
|
|
@@ -287,7 +292,7 @@ export function FormPlaceAutocomplete({
|
|
|
287
292
|
|
|
288
293
|
return (
|
|
289
294
|
<div className="flex w-full flex-col" ref={containerRef}>
|
|
290
|
-
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired}>
|
|
295
|
+
<FormFieldWrapper form={form} name={id} label={name} description={description} isRequired={isRequired}>
|
|
291
296
|
{(field) => (
|
|
292
297
|
<div className="relative">
|
|
293
298
|
<Input
|
|
@@ -9,6 +9,7 @@ export function FormSelect({
|
|
|
9
9
|
form,
|
|
10
10
|
id,
|
|
11
11
|
name,
|
|
12
|
+
description,
|
|
12
13
|
placeholder,
|
|
13
14
|
disabled,
|
|
14
15
|
values,
|
|
@@ -21,6 +22,7 @@ export function FormSelect({
|
|
|
21
22
|
form: any;
|
|
22
23
|
id: string;
|
|
23
24
|
name?: string;
|
|
25
|
+
description?: string;
|
|
24
26
|
placeholder?: string;
|
|
25
27
|
disabled?: boolean;
|
|
26
28
|
values: { id: string; text: string }[];
|
|
@@ -36,6 +38,7 @@ export function FormSelect({
|
|
|
36
38
|
form={form}
|
|
37
39
|
name={id}
|
|
38
40
|
label={name}
|
|
41
|
+
description={description}
|
|
39
42
|
isRequired={isRequired}
|
|
40
43
|
orientation={useRows ? "horizontal" : "vertical"}
|
|
41
44
|
testId={testId}
|
|
@@ -10,6 +10,7 @@ export function FormSlider({
|
|
|
10
10
|
name,
|
|
11
11
|
disabled,
|
|
12
12
|
showPercentage,
|
|
13
|
+
description,
|
|
13
14
|
}: {
|
|
14
15
|
form: any;
|
|
15
16
|
id: string;
|
|
@@ -17,12 +18,13 @@ export function FormSlider({
|
|
|
17
18
|
placeholder?: string;
|
|
18
19
|
disabled?: boolean;
|
|
19
20
|
showPercentage?: boolean;
|
|
21
|
+
description?: string;
|
|
20
22
|
}) {
|
|
21
23
|
const value = useWatch({ control: form.control, name: id });
|
|
22
24
|
|
|
23
25
|
return (
|
|
24
26
|
<div className="flex w-full flex-col">
|
|
25
|
-
<FormFieldWrapper form={form} name={id} label={name}>
|
|
27
|
+
<FormFieldWrapper form={form} name={id} label={name} description={description}>
|
|
26
28
|
{() => (
|
|
27
29
|
<div>
|
|
28
30
|
{showPercentage && (
|
|
@@ -11,6 +11,9 @@ export function FormTextarea({
|
|
|
11
11
|
className,
|
|
12
12
|
placeholder,
|
|
13
13
|
testId,
|
|
14
|
+
description,
|
|
15
|
+
isRequired,
|
|
16
|
+
textareaRef,
|
|
14
17
|
}: {
|
|
15
18
|
form: any;
|
|
16
19
|
id: string;
|
|
@@ -18,13 +21,24 @@ export function FormTextarea({
|
|
|
18
21
|
placeholder?: string;
|
|
19
22
|
className?: string;
|
|
20
23
|
testId?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
isRequired?: boolean;
|
|
26
|
+
textareaRef?: React.RefObject<HTMLTextAreaElement>;
|
|
21
27
|
}) {
|
|
22
28
|
return (
|
|
23
29
|
<div className="flex w-full flex-col">
|
|
24
|
-
<FormFieldWrapper
|
|
30
|
+
<FormFieldWrapper
|
|
31
|
+
form={form}
|
|
32
|
+
name={id}
|
|
33
|
+
label={name}
|
|
34
|
+
description={description}
|
|
35
|
+
isRequired={isRequired}
|
|
36
|
+
testId={testId}
|
|
37
|
+
>
|
|
25
38
|
{(field) => (
|
|
26
39
|
<Textarea
|
|
27
40
|
{...field}
|
|
41
|
+
ref={textareaRef}
|
|
28
42
|
className={cn("min-h-96 w-full", className)}
|
|
29
43
|
disabled={form.formState.isSubmitting}
|
|
30
44
|
placeholder={placeholder}
|
|
@@ -17,6 +17,18 @@ const badgeVariants = cva(
|
|
|
17
17
|
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/20 dark:bg-input/30",
|
|
18
18
|
ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
19
19
|
link: "text-primary underline-offset-4 hover:underline",
|
|
20
|
+
warning: "bg-warning text-warning-foreground [a]:hover:bg-warning/80",
|
|
21
|
+
blue: "bg-sky-500 text-primary-foreground [a]:hover:bg-sky-500/80",
|
|
22
|
+
green: "bg-emerald-500 text-primary-foreground [a]:hover:bg-emerald-500/80",
|
|
23
|
+
red: "bg-red-500 text-primary-foreground [a]:hover:bg-red-500/80",
|
|
24
|
+
yellow: "bg-yellow-500 text-primary-foreground [a]:hover:bg-yellow-500/80",
|
|
25
|
+
purple: "bg-purple-500 text-primary-foreground [a]:hover:bg-purple-500/80",
|
|
26
|
+
pink: "bg-pink-500 text-primary-foreground [a]:hover:bg-pink-500/80",
|
|
27
|
+
gray: "bg-gray-500 text-primary-foreground [a]:hover:bg-gray-500/80",
|
|
28
|
+
orange: "bg-orange-500 text-primary-foreground [a]:hover:bg-orange-500/80",
|
|
29
|
+
teal: "bg-teal-500 text-primary-foreground [a]:hover:bg-teal-500/80",
|
|
30
|
+
lime: "bg-lime-500 text-primary-foreground [a]:hover:bg-lime-500/80",
|
|
31
|
+
none: "border-input bg-transparent text-muted-foreground min-w-20 min-h-5",
|
|
20
32
|
},
|
|
21
33
|
},
|
|
22
34
|
defaultVariants: {
|
|
@@ -47,4 +59,7 @@ function Badge({
|
|
|
47
59
|
});
|
|
48
60
|
}
|
|
49
61
|
|
|
62
|
+
type BadgeProps = useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>;
|
|
63
|
+
|
|
50
64
|
export { Badge, badgeVariants };
|
|
65
|
+
export type { BadgeProps };
|