EventBuzz_Console/src/app/components/panels/eventFormFields.tsx

304 lines
9.3 KiB
TypeScript

import React from "react";
import { ChevronDown, ChevronUp, Check, Upload } from "lucide-react";
export const IS: React.CSSProperties = {
width: "100%",
padding: "8px 12px",
border: "1px solid var(--border)",
borderRadius: "var(--radius-button)",
background: "var(--input-background)",
fontSize: "var(--text-sm)",
fontFamily: "var(--font-noto-sans)",
color: "var(--card-foreground)",
outline: "none",
boxSizing: "border-box",
};
export const SS: React.CSSProperties = { ...IS, cursor: "pointer", appearance: "none", paddingRight: "32px" };
export function Label({ text, required }: { text: string; required?: boolean }) {
return (
<label
style={{
display: "block",
fontSize: "var(--text-sm)",
fontFamily: "var(--font-noto-sans)",
fontWeight: "var(--font-weight-medium)",
color: "var(--card-foreground)",
marginBottom: "5px",
}}
>
{required && <span style={{ color: "var(--destructive)", marginRight: "2px" }}>*</span>}
{text}
</label>
);
}
export function Field({ children, error }: { children: React.ReactNode; error?: string }) {
return (
<div style={{ display: "flex", flexDirection: "column" }}>
{children}
{error ? (
<p style={{ fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", color: "var(--destructive)", margin: "5px 0 0", lineHeight: 1.35 }}>{error}</p>
) : null}
</div>
);
}
export function Row({ children }: { children: React.ReactNode }) {
return <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px" }}>{children}</div>;
}
export function SelectWrap({ children }: { children: React.ReactNode }) {
return (
<div style={{ position: "relative" }}>
{children}
<ChevronDown size={14} style={{ position: "absolute", right: 10, top: "50%", transform: "translateY(-50%)", color: "var(--muted)", pointerEvents: "none" }} />
</div>
);
}
export function RadioGroup({
name,
value,
onChange,
options,
disabled,
}: {
name: string;
value: string;
onChange: (v: string) => void;
options: { label: string; value: string }[];
disabled?: boolean;
}) {
return (
<div style={{ display: "flex", gap: "20px", alignItems: "center", flexWrap: "wrap", opacity: disabled ? 0.55 : 1 }}>
{options.map((o) => (
<label
key={o.value}
style={{
display: "flex",
alignItems: "center",
gap: "6px",
cursor: disabled ? "not-allowed" : "pointer",
fontSize: "var(--text-sm)",
fontFamily: "var(--font-noto-sans)",
color: "var(--card-foreground)",
}}
>
<input
type="radio"
name={name}
value={o.value}
checked={value === o.value}
disabled={disabled}
onChange={() => onChange(o.value)}
style={{ accentColor: "var(--primary)", width: "16px", height: "16px", cursor: disabled ? "not-allowed" : "pointer" }}
/>
{o.label}
</label>
))}
</div>
);
}
export function UploadZone({
label,
preview,
error,
inputRef,
onFile,
}: {
label: string;
preview: string | null;
error?: string;
inputRef: React.RefObject<HTMLInputElement | null>;
onFile: (dataUrl: string) => void;
}) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<div
onClick={() => inputRef.current?.click()}
onDragOver={(e) => e.preventDefault()}
onDrop={(e) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
if (file?.type.startsWith("image/")) {
const r = new FileReader();
r.onload = () => {
if (typeof r.result === "string") onFile(r.result);
};
r.readAsDataURL(file);
}
}}
style={{
border: `1.5px dashed ${error ? "var(--destructive)" : "var(--primary)"}`,
borderRadius: "var(--radius-button)",
background: "var(--secondary)",
padding: "16px 12px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "6px",
cursor: "pointer",
}}
>
<input
ref={inputRef}
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
const r = new FileReader();
r.onload = () => {
if (typeof r.result === "string") onFile(r.result);
};
r.readAsDataURL(file);
}
}}
/>
{preview ? (
<img src={preview} alt="" style={{ maxHeight: 72, maxWidth: "100%", objectFit: "contain", borderRadius: 4 }} />
) : (
<Upload size={22} style={{ color: "var(--primary)" }} />
)}
<p style={{ fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", color: "var(--muted)", textAlign: "center", margin: 0 }}>
Drag and drop or <span style={{ color: "var(--primary)", textDecoration: "underline" }}>select {label}</span>
</p>
</div>
{error ? <p style={{ fontSize: "var(--text-xs)", color: "var(--destructive)", margin: 0 }}>{error}</p> : null}
</div>
);
}
export function StepCircle({ n, state }: { n: number; state: "todo" | "active" | "done" }) {
const bg = state === "todo" ? "var(--muted)" : "var(--primary)";
return (
<div
style={{
width: 28,
height: 28,
borderRadius: "50%",
background: bg,
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
}}
>
{state === "done" ? (
<Check size={13} style={{ color: "#fff" }} />
) : (
<span style={{ fontSize: "var(--text-xs)", fontFamily: "var(--font-inter)", color: "#fff", fontWeight: "var(--font-weight-medium)" }}>{n}</span>
)}
</div>
);
}
export function AccordionSection({
n,
title,
subtitle,
open,
completed,
onToggle,
children,
onNext,
nextLabel,
nextDisabled,
}: {
n: number;
title: string;
subtitle: string;
open: boolean;
completed: boolean;
onToggle: () => void;
children: React.ReactNode;
onNext: () => void;
nextLabel: string;
nextDisabled?: boolean;
}) {
const state: "todo" | "active" | "done" = open ? "active" : completed ? "done" : "todo";
return (
<div style={{ borderBottom: "1px solid var(--border)" }}>
<button
type="button"
onClick={onToggle}
style={{
width: "100%",
display: "flex",
alignItems: "center",
gap: "12px",
padding: "14px 20px",
background: "none",
border: "none",
cursor: "pointer",
textAlign: "left",
}}
>
<StepCircle n={n} state={state} />
<div style={{ flex: 1, minWidth: 0 }}>
<p
style={{
fontSize: "var(--text-sm)",
fontFamily: "var(--font-inter)",
fontWeight: "var(--font-weight-medium)",
color: open ? "var(--primary)" : "var(--card-foreground)",
margin: 0,
}}
>
{title}
</p>
{!open && <p style={{ fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", color: "var(--muted)", margin: 0 }}>{subtitle}</p>}
</div>
{open ? <ChevronUp size={18} style={{ color: "var(--muted)" }} /> : <ChevronDown size={18} style={{ color: "var(--muted)" }} />}
</button>
{open && (
<div style={{ padding: "0 20px 16px" }}>
<p style={{ fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", color: "var(--muted)", margin: "0 0 14px 40px" }}>{subtitle}</p>
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>{children}</div>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
gap: "12px",
marginTop: "18px",
paddingTop: "12px",
borderTop: "1px solid var(--border)",
}}
>
{completed && (
<div style={{ display: "flex", alignItems: "center", gap: "5px", fontSize: "var(--text-xs)", color: "var(--primary)" }}>
<Check size={12} /> Complete
</div>
)}
<button
type="button"
disabled={nextDisabled}
onClick={onNext}
style={{
display: "flex",
alignItems: "center",
gap: "6px",
padding: "6px 16px",
borderRadius: "var(--radius-button)",
background: nextDisabled ? "var(--secondary)" : "var(--primary)",
color: nextDisabled ? "var(--muted)" : "var(--primary-foreground)",
border: "none",
cursor: nextDisabled ? "not-allowed" : "pointer",
fontSize: "var(--text-sm)",
fontFamily: "var(--font-noto-sans)",
fontWeight: "var(--font-weight-medium)",
}}
>
{nextLabel}
</button>
</div>
</div>
)}
</div>
);
}