UNPKG

@kopexa/sight

Version:

Kopexa Sight Design System — React component library for GRC applications

243 lines (240 loc) 8.26 kB
"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 };