Files
community-rule/app/components/controls/Upload/Upload.view.tsx
T

147 lines
4.9 KiB
TypeScript

"use client";
import { memo } from "react";
import InputLabel from "../../type/InputLabel";
import type { UploadViewProps } from "./Upload.types";
function UploadView({
state,
hasLabel,
label,
showHelpIcon = true,
hintText,
uploadButtonLabel,
uploadAriaLabel,
previewSrc,
previewAlt,
fileName,
onClick,
className = "",
}: UploadViewProps) {
const isActive = state !== "disabled";
const isDisabled = state === "disabled";
const hasPreview =
typeof previewSrc === "string" && previewSrc.length > 0;
const buttonBgClass = isActive
? "bg-[var(--color-surface-invert-primary)]"
: "bg-[var(--color-surface-default-secondary)]";
const buttonTextColor = isActive
? "text-[color:var(--color-content-invert-primary)]"
: "text-[color:var(--color-content-invert-tertiary)]";
const descriptionTextColor = isActive
? "text-[color:var(--color-content-default-primary)]"
: "text-[color:var(--color-content-default-tertiary)]";
const iconColor = isActive
? "text-[color:var(--color-content-invert-primary)]"
: "text-[color:var(--color-content-invert-tertiary)]";
return (
<div
className={`flex flex-col gap-[var(--space-300)] items-start relative w-full ${className}`}
>
{hasLabel && label ? (
<InputLabel
label={label}
helpIcon={showHelpIcon}
asterisk={false}
helperText={false}
size="s"
palette="default"
/>
) : null}
<div
className={`bg-[var(--color-surface-default-secondary)] mx-auto flex w-full max-w-[474px] shrink-0 items-center justify-center gap-[24px] rounded-[var(--radius-200)] px-[var(--space-600)] py-[var(--space-1200)] ${
state === "error"
? "border-2 border-[var(--color-border-default-negative-primary)]"
: ""
} ${state === "focus" ? "shadow-[var(--shadow-focus-default)]" : ""}`}
>
{hasPreview ? (
<div
className="relative size-[80px] shrink-0 overflow-hidden rounded-full bg-[var(--color-surface-invert-brand-teal)]"
aria-hidden={false}
>
{/* eslint-disable-next-line @next/next/no-img-element -- user/device file or same-origin upload URL */}
<img
src={previewSrc}
alt={previewAlt}
className="h-full w-full object-cover opacity-70 mix-blend-color-burn"
/>
</div>
) : null}
<button
type="button"
onClick={onClick}
disabled={isDisabled}
className={`${buttonBgClass} flex gap-[var(--space-150)] items-center justify-center overflow-clip px-[var(--space-400)] py-[var(--space-300)] rounded-[var(--radius-full)] shrink-0 hover:opacity-80 transition-opacity ${
state === "hover" ? "opacity-80" : ""
} ${state === "active" ? "scale-[0.98]" : ""}`}
aria-label={uploadAriaLabel}
>
{hasPreview ? null : (
<div className={`relative shrink-0 size-[20px] ${iconColor}`}>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="size-full"
>
<path
d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<polyline
points="17 8 12 3 7 8"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<line
x1="12"
y1="3"
x2="12"
y2="15"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
)}
<div
className={`flex flex-col font-medium justify-center leading-[0] relative shrink-0 text-[length:var(--sizing-400)] whitespace-nowrap ${buttonTextColor}`}
>
<p className="leading-[20px]">{uploadButtonLabel}</p>
</div>
</button>
<div
className={`flex flex-[1_0_0] flex-col font-normal min-h-[32px] justify-center leading-[0] min-w-px relative text-[length:var(--sizing-350)] ${descriptionTextColor}`}
role={state === "error" ? "alert" : undefined}
>
<p className="leading-[20px] whitespace-pre-wrap">
{hasPreview && fileName ? fileName : hintText}
</p>
</div>
</div>
</div>
);
}
UploadView.displayName = "UploadView";
export default memo(UploadView);