@kopexa/sight
Version:
Kopexa Sight Design System — React component library for GRC applications
243 lines (240 loc) • 8.26 kB
JavaScript
"use client";
import {
dataUrlToFile
} from "./chunk-HVOS7SXF.mjs";
import {
formatAcceptedTypes
} from "./chunk-EAJ3DTJ3.mjs";
import {
isImageLike
} from "./chunk-UWQG4HGJ.mjs";
import {
Dialog
} from "./chunk-CZNF6WPR.mjs";
import {
messages
} from "./chunk-RK7G2GYB.mjs";
import {
useFileUpload
} from "./chunk-DWZQIPCX.mjs";
// src/components/file-upload/avatar-upload.tsx
import { Button, IconButton } from "@kopexa/button";
import { Callout } from "@kopexa/callout";
import { useSafeIntl } from "@kopexa/i18n";
import { CloseIcon, CropIcon, UserCircleIcon } from "@kopexa/icons";
import {
ImageCrop,
ImageCropApply,
ImageCropContent
} from "@kopexa/image-crop";
import { dataAttr, formatBytes } from "@kopexa/shared-utils";
import { avatarUpload } from "@kopexa/theme";
import { useCallback, useRef, useState } from "react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var defaultMaxSize = 2 * 1024 * 1024;
var AvatarUpload = (props) => {
const {
maxSize = defaultMaxSize,
className,
onFileChange,
defaultAvatar,
orientation,
size,
shape,
accept = ".png,.jpg",
cropperEnabled = true,
cropAspect = 1,
cropMaxImageSize = defaultMaxSize,
onCroppedFile,
...rest
} = props;
const t = useSafeIntl();
const [cropOpen, setCropOpen] = useState(false);
const [cropSourceFile, setCropSourceFile] = useState(null);
const pendingOriginalId = useRef(null);
const [
{ files, isDragging, errors },
{
removeFile,
handleDragEnter,
handleDragLeave,
handleDragOver,
handleDrop,
openFileDialog,
getInputProps,
clearErrors,
addFiles
}
] = useFileUpload({
maxFiles: 1,
maxSize,
accept,
multiple: false,
onFilesChange: (files2) => {
onFileChange == null ? void 0 : onFileChange(files2[0] || null);
},
onFilesAdded: (added) => {
var _a;
if (!cropperEnabled) return;
const raw = (_a = added[0]) == null ? void 0 : _a.file;
if (raw instanceof File && isImageLike(raw)) {
pendingOriginalId.current = added[0].id;
setCropSourceFile(raw);
setCropOpen(true);
}
}
});
const currentFile = files[0];
const previewUrl = (currentFile == null ? void 0 : currentFile.preview) || defaultAvatar;
const handleRemove = () => {
if (currentFile) {
removeFile(currentFile.id);
clearErrors();
}
};
const styles = avatarUpload({
size,
shape,
orientation
});
const typesLabel = formatAcceptedTypes(accept);
const handleCropCancel = useCallback(() => {
setCropOpen(false);
setCropSourceFile(null);
pendingOriginalId.current = null;
}, []);
const handleCropApply = useCallback(
async (croppedDataUrl) => {
if (!croppedDataUrl || !cropSourceFile) {
handleCropCancel();
return;
}
const newFile = await dataUrlToFile(
croppedDataUrl,
cropSourceFile.name,
cropSourceFile.type
);
if (pendingOriginalId.current) {
removeFile(pendingOriginalId.current);
pendingOriginalId.current = null;
}
addFiles([newFile]);
onCroppedFile == null ? void 0 : onCroppedFile(newFile);
setCropOpen(false);
setCropSourceFile(null);
},
[cropSourceFile, addFiles, removeFile, onCroppedFile, handleCropCancel]
);
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs(
"div",
{
"data-slot": "avatar-upload",
className: styles.root({ className }),
...rest,
children: [
/* @__PURE__ */ jsxs("div", { className: styles.previewAndText(), children: [
/* @__PURE__ */ jsxs("div", { className: styles.previewContainer(), children: [
/* @__PURE__ */ jsxs(
"button",
{
type: "button",
"aria-label": previewUrl ? t.formatMessage(messages.change_avatar) : t.formatMessage(messages.upload_avatar),
"data-dragging": dataAttr(isDragging),
"data-hasimage": dataAttr(!!previewUrl),
className: styles.preview(),
onDragEnter: handleDragEnter,
onDragLeave: handleDragLeave,
onDragOver: handleDragOver,
onDrop: handleDrop,
onClick: openFileDialog,
children: [
/* @__PURE__ */ jsx("input", { ...getInputProps(), className: "sr-only" }),
previewUrl ? /* @__PURE__ */ jsx(
"img",
{
src: previewUrl,
alt: "Avatar",
className: styles.previewImg()
}
) : /* @__PURE__ */ jsx("div", { className: styles.placeholderWrapper(), children: /* @__PURE__ */ jsx(UserCircleIcon, { className: styles.placeholder() }) })
]
}
),
currentFile && /* @__PURE__ */ jsx(
IconButton,
{
variant: "outline",
color: "default",
onClick: handleRemove,
size: "sm",
className: styles.removeButton(),
"aria-label": t.formatMessage(messages.remove_avatar),
children: /* @__PURE__ */ jsx(CloseIcon, {})
}
)
] }),
/* @__PURE__ */ jsxs("div", { className: styles.instructionsContainer(), children: [
/* @__PURE__ */ jsx("p", { className: styles.instructions(), children: currentFile ? t.formatMessage(messages.avatar_uploaded) : t.formatMessage(messages.upload_avatar) }),
/* @__PURE__ */ jsx("p", { className: styles.acceptedFiles(), children: t.formatMessage(messages.accepted_file_types, {
types: typesLabel || "-",
size: maxSize ? formatBytes(maxSize) : "none"
}) })
] })
] }),
errors.length > 0 && /* @__PURE__ */ jsx(Callout, { variant: "destructive", children: errors.map((error, index) => /* @__PURE__ */ jsx("p", { className: styles.errorItem(), children: error }, index.toString())) })
]
}
),
cropperEnabled && /* @__PURE__ */ jsx(
Dialog.Root,
{
open: cropOpen,
onOpenChange: (v) => !v ? handleCropCancel() : null,
size: "2xl",
children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
/* @__PURE__ */ jsx(Dialog.Header, { children: /* @__PURE__ */ jsx(Dialog.Title, { children: t.formatMessage(messages.change_avatar) }) }),
cropSourceFile && /* @__PURE__ */ jsxs(
ImageCrop,
{
aspect: cropAspect,
file: cropSourceFile,
maxImageSize: cropMaxImageSize,
onCrop: handleCropApply,
children: [
/* @__PURE__ */ jsx(Dialog.Body, { children: /* @__PURE__ */ jsx(ImageCropContent, { className: "max-w-md" }) }),
/* @__PURE__ */ jsxs(Dialog.Footer, { children: [
/* @__PURE__ */ jsx(
Dialog.CloseTrigger,
{
render: /* @__PURE__ */ jsx(
Button,
{
variant: "ghost",
color: "default",
startContent: /* @__PURE__ */ jsx(CloseIcon, {}),
children: t.formatMessage(messages.cancel)
}
)
}
),
/* @__PURE__ */ jsx(
ImageCropApply,
{
color: "primary",
variant: "solid",
render: /* @__PURE__ */ jsx(Button, { startContent: /* @__PURE__ */ jsx(CropIcon, {}), children: t.formatMessage(messages.apply_crop) })
}
)
] })
]
}
)
] })
}
)
] });
};
export {
AvatarUpload
};