Files
ersatztv/design-system/components/forms/Button.jsx
T
2026-07-02 07:37:00 +02:00

108 lines
3.1 KiB
React

import React from "react";
const sizeMap = {
sm: { height: "var(--control-h-sm)", padding: "0 12px", font: "var(--text-xs)" },
md: { height: "var(--control-h)", padding: "0 14px", font: "var(--text-sm)" },
};
const variantStyle = {
primary: {
background: "var(--action-primary)",
color: "var(--text-on-accent)",
border: "1px solid transparent",
},
secondary: {
background: "var(--ctv-surface-2)",
color: "var(--text-primary)",
border: "1px solid var(--border-control)",
},
ghost: {
background: "transparent",
color: "var(--text-secondary)",
border: "1px solid transparent",
},
danger: {
background: "var(--ctv-error-soft)",
color: "var(--status-error)",
border: "1px solid rgba(229,72,77,0.35)",
},
};
const hoverBg = {
primary: "var(--action-primary-hover)",
secondary: "var(--ctv-surface-3)",
ghost: "var(--ctv-surface-2)",
danger: "rgba(229,72,77,0.22)",
};
/**
* ChicoryTV primary action button.
*/
export function Button({
children,
variant = "primary",
size = "md",
startIcon = null,
endIcon = null,
disabled = false,
loading = false,
fullWidth = false,
onClick,
type = "button",
style = {},
...rest
}) {
const [hover, setHover] = React.useState(false);
const [active, setActive] = React.useState(false);
const s = sizeMap[size] || sizeMap.md;
const v = variantStyle[variant] || variantStyle.primary;
const isDisabled = disabled || loading;
return (
<button
type={type}
disabled={isDisabled}
onClick={onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => { setHover(false); setActive(false); }}
onMouseDown={() => setActive(true)}
onMouseUp={() => setActive(false)}
style={{
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
gap: "7px",
height: s.height,
padding: s.padding,
width: fullWidth ? "100%" : "auto",
font: `var(--weight-medium) ${s.font}/1 var(--font-sans)`,
letterSpacing: "0.01em",
borderRadius: "var(--radius-sm)",
cursor: isDisabled ? "not-allowed" : "pointer",
opacity: isDisabled ? 0.45 : 1,
whiteSpace: "nowrap",
transition: "background var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard)",
transform: active && !isDisabled ? "translateY(0.5px)" : "none",
...v,
background: !isDisabled && hover ? hoverBg[variant] : v.background,
...style,
}}
{...rest}
>
{loading ? <Spinner16 /> : startIcon}
{children}
{!loading && endIcon}
</button>
);
}
function Spinner16() {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" style={{ animation: "ctv-spin 0.7s linear infinite" }}>
<style>{`@keyframes ctv-spin{to{transform:rotate(360deg)}}`}</style>
<circle cx="12" cy="12" r="9" stroke="currentColor" strokeOpacity="0.25" strokeWidth="3" />
<path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
</svg>
);
}