import { MinusIcon, PlusIcon } from "@phosphor-icons/react";
import * as React from "react";
import {
Button,
Field,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "../../primitives";
import { selectedItemSurfaceClassName } from "../../primitives/selection-state";
import { cn } from "../../../lib/utils";
import {
formatStopPosition,
maxGradientStops,
minGradientStops,
normalizeStopOpacity,
parseStopOpacity,
parseStopPosition,
type IndexedGradientStop,
} from "./gradient-control-utils";
import { ColorValueControl } from "../color";
import { ControlFieldLabel } from "../../control-layout";
function GradientStopsHeader({
canAdd,
onAdd,
}: {
canAdd: boolean;
onAdd: () => void;
}): React.JSX.Element {
return (
);
}
function GradientStopColorControls({
onColorChange,
onOpacityChange,
stop,
stopLabel,
stopName,
}: {
onColorChange: (nextColor: string) => void;
onOpacityChange: (nextOpacity: number) => void;
stop: IndexedGradientStop;
stopLabel: string;
stopName: string;
}): React.JSX.Element {
return (
);
}
function GradientStopOpacityInput({
onOpacityChange,
stop,
stopLabel,
stopName,
}: {
onOpacityChange: (nextOpacity: number) => void;
stop: IndexedGradientStop;
stopLabel: string;
stopName: string;
}): React.JSX.Element {
const committedOpacity = String(parseStopOpacity(stop.opacity));
const [draftOpacity, setDraftOpacity] = React.useState(committedOpacity);
React.useEffect(() => {
setDraftOpacity(committedOpacity);
}, [committedOpacity]);
function commitOpacity(nextDraft = draftOpacity): void {
const trimmedDraft = nextDraft.trim();
if (trimmedDraft === "" || !Number.isFinite(Number.parseFloat(trimmedDraft))) {
setDraftOpacity(committedOpacity);
return;
}
const nextOpacity = normalizeStopOpacity(trimmedDraft);
setDraftOpacity(String(nextOpacity));
if (nextOpacity !== parseStopOpacity(stop.opacity)) {
onOpacityChange(nextOpacity);
}
}
return (
commitOpacity()}
onChange={(event) => setDraftOpacity(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
commitOpacity(event.currentTarget.value);
event.currentTarget.blur();
return;
}
if (event.key === "Escape") {
event.preventDefault();
setDraftOpacity(committedOpacity);
event.currentTarget.blur();
}
}}
type="text"
value={draftOpacity}
/>
%
);
}
function getStopPositionInputValue(position: string): string {
return String(Math.round(parseStopPosition(position) * 100));
}
function getNormalizedStopPositionFromInputValue(value: string): string | null {
const parsedValue = Number.parseFloat(value);
if (!Number.isFinite(parsedValue)) {
return null;
}
return formatStopPosition(parsedValue / 100);
}
function GradientStopPositionInput({
onPositionChange,
stop,
stopLabel,
stopName,
}: {
onPositionChange: (nextPosition: string) => void;
stop: IndexedGradientStop;
stopLabel: string;
stopName: string;
}): React.JSX.Element {
const committedPosition = formatStopPosition(parseStopPosition(stop.position));
const committedInputValue = getStopPositionInputValue(stop.position);
const [draftValue, setDraftValue] = React.useState(committedInputValue);
const skipNextBlurCommitRef = React.useRef(false);
React.useEffect(() => {
setDraftValue(committedInputValue);
}, [committedInputValue]);
function commitPosition(nextValue = draftValue): void {
const nextPosition = getNormalizedStopPositionFromInputValue(nextValue);
if (nextPosition === null) {
setDraftValue(committedInputValue);
return;
}
setDraftValue(getStopPositionInputValue(nextPosition));
if (nextPosition !== committedPosition) {
onPositionChange(nextPosition);
}
}
return (
{
if (skipNextBlurCommitRef.current) {
skipNextBlurCommitRef.current = false;
return;
}
commitPosition();
}}
onChange={(event) => setDraftValue(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
commitPosition(event.currentTarget.value);
skipNextBlurCommitRef.current = true;
event.currentTarget.blur();
}
if (event.key === "Escape") {
event.preventDefault();
setDraftValue(committedInputValue);
skipNextBlurCommitRef.current = true;
event.currentTarget.blur();
}
}}
type="text"
value={draftValue}
/>
%
);
}
function GradientStopRowSelectionSurface({
isSelected,
}: {
isSelected: boolean;
}): React.JSX.Element {
return (
);
}
function GradientStopRow({
canRemove,
isSelected,
onColorChange,
onOpacityChange,
onPositionChange,
onRemove,
onSelect,
stop,
}: {
canRemove: boolean;
isSelected: boolean;
onColorChange: (nextColor: string) => void;
onOpacityChange: (nextOpacity: number) => void;
onPositionChange: (nextPosition: string) => void;
onRemove: () => void;
onSelect: () => void;
stop: IndexedGradientStop;
}): React.JSX.Element {
const stopLabel = `Stop ${stop.originalIndex + 1}`;
const stopName = `gradient-stop-${stop.originalIndex + 1}`;
function selectAndChangeColor(nextColor: string): void {
onSelect();
onColorChange(nextColor);
}
function selectAndChangeOpacity(nextOpacity: number): void {
onSelect();
onOpacityChange(nextOpacity);
}
function selectAndChangePosition(nextPosition: string): void {
onSelect();
onPositionChange(nextPosition);
}
return (
);
}
export function GradientStopsList({
onAdd,
onColorChange,
onOpacityChange,
onPositionChange,
onRemove,
onSelect,
selectedIndex,
stops,
}: {
onAdd: () => void;
onColorChange: (index: number, nextColor: string) => void;
onOpacityChange: (index: number, nextOpacity: number) => void;
onPositionChange: (index: number, nextPosition: string) => void;
onRemove: (index: number) => void;
onSelect: (index: number) => void;
selectedIndex: number | null;
stops: readonly IndexedGradientStop[];
}): React.JSX.Element {
const canAdd = stops.length < maxGradientStops;
const canRemove = stops.length > minGradientStops;
return (
{stops.map((stop) => (
onColorChange(stop.originalIndex, nextColor)
}
onOpacityChange={(nextOpacity) =>
onOpacityChange(stop.originalIndex, nextOpacity)
}
onPositionChange={(nextPosition) =>
onPositionChange(stop.originalIndex, nextPosition)
}
onRemove={() => onRemove(stop.originalIndex)}
onSelect={() => onSelect(stop.originalIndex)}
stop={stop}
/>
))}
);
}