SolanaUISolanaUI

Order Form

A take profit and stop loss order form for open trading positions

Edit TP/SL
Adjust the parameters on your order
Size150.0
Entry Price$148.32
Mark Price$162.56
Liquidation Price$118.66
Take Profit
Stop Loss
import { OrderForm } from "@/components/sol/order-form";

export function OrderFormDemo() {
  return (
    <OrderForm
      title="Edit TP/SL"
      description="Adjust the parameters on your order"
      entryPrice={148.32}
      details={[
        { label: "Size", value: "150.0" },
        { label: "Entry Price", value: "$148.32" },
        { label: "Mark Price", value: "$162.56" },
        { label: "Liquidation Price", value: "$118.66" },
      ]}
      onSubmit={(values) => console.log(values)}
    />
  );
}

Installation

pnpm dlx shadcn@latest add @solanaui/order-form
npx shadcn@latest add @solanaui/order-form
yarn dlx shadcn@latest add @solanaui/order-form

Usage

<OrderForm
  title="Edit TP/SL"
  description="Adjust the parameters on your order"
  entryPrice={148.32}
  details={[
    { label: "Size", value: "150.0" },
    { label: "Entry Price", value: "$148.32" },
    { label: "Mark Price", value: "$162.56" },
    { label: "Liquidation Price", value: "$118.66" },
  ]}
  onSubmit={(values) => {
    console.log("TP:", values.tpPrice, "TP%:", values.tpPercent);
    console.log("SL:", values.slPrice, "SL%:", values.slPercent);
  }}
/>

Source Code

"use client";import React from "react";import { NumericFormat } from "react-number-format";import { Button } from "@/components/ui/button";import {  Card,  CardContent,  CardDescription,  CardHeader,  CardTitle,} from "@/components/ui/card";import { Input } from "@/components/ui/input";import { Separator } from "@/components/ui/separator";import type { DetailRow } from "@/registry/lib/types";import { cn } from "@/lib/utils";type OrderFormDetail = DetailRow;interface OrderFormProps {  title?: string;  description?: string;  entryPrice?: number;  details?: OrderFormDetail[];  onSubmit?: (values: {    tpPrice: string;    tpPercent: string;    slPrice: string;    slPercent: string;  }) => void;  className?: string;}const OrderForm = ({  title = "Edit TP/SL",  description = "Adjust the parameters on your order",  entryPrice,  details,  onSubmit,  className,}: OrderFormProps) => {  const [tpPrice, setTpPrice] = React.useState("");  const [tpPercent, setTpPercent] = React.useState("");  const [slPrice, setSlPrice] = React.useState("");  const [slPercent, setSlPercent] = React.useState("");  const entryNum = entryPrice ?? 0;  const handleTpPriceChange = (value: string) => {    setTpPrice(value);    if (entryNum > 0) {      const priceNum = Number.parseFloat(value);      if (!Number.isNaN(priceNum)) {        setTpPercent((((priceNum - entryNum) / entryNum) * 100).toFixed(2));      } else {        setTpPercent("");      }    }  };  const handleTpPercentChange = (value: string) => {    setTpPercent(value);    if (entryNum > 0) {      const pctNum = Number.parseFloat(value);      if (!Number.isNaN(pctNum)) {        setTpPrice((entryNum * (1 + pctNum / 100)).toFixed(2));      } else {        setTpPrice("");      }    }  };  const handleSlPriceChange = (value: string) => {    setSlPrice(value);    if (entryNum > 0) {      const priceNum = Number.parseFloat(value);      if (!Number.isNaN(priceNum)) {        setSlPercent((((entryNum - priceNum) / entryNum) * 100).toFixed(2));      } else {        setSlPercent("");      }    }  };  const handleSlPercentChange = (value: string) => {    setSlPercent(value);    if (entryNum > 0) {      const pctNum = Number.parseFloat(value);      if (!Number.isNaN(pctNum)) {        setSlPrice((entryNum * (1 - pctNum / 100)).toFixed(2));      } else {        setSlPrice("");      }    }  };  const handleClearTp = () => {    setTpPrice("");    setTpPercent("");  };  const handleClearSl = () => {    setSlPrice("");    setSlPercent("");  };  const handleSubmit = () => {    onSubmit?.({ tpPrice, tpPercent, slPrice, slPercent });  };  return (    <Card className={cn("w-full max-w-sm", className)}>      <CardHeader>        <CardTitle>{title}</CardTitle>        <CardDescription>{description}</CardDescription>      </CardHeader>      <CardContent className="flex flex-col gap-4">        {/* Details summary */}        {details && details.length > 0 && (          <>            <div className="flex flex-col gap-2">              {details.map((detail) => (                <div                  key={detail.label}                  className="flex items-center justify-between"                >                  <span className="text-sm text-muted-foreground">                    {detail.label}                  </span>                  <span className={cn("text-sm font-medium", detail.className)}>                    {detail.value}                  </span>                </div>              ))}            </div>            <Separator />          </>        )}        {/* Take Profit */}        <div className="flex flex-col gap-2">          <div className="flex items-center justify-between">            <span className="text-sm font-medium text-emerald-500">              Take Profit            </span>            {(tpPrice || tpPercent) && (              <button                type="button"                onClick={handleClearTp}                className="text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer"              >                Clear              </button>            )}          </div>          <div className="flex items-center gap-2">            <NumericFormat              value={tpPrice}              onValueChange={(values) => handleTpPriceChange(values.value)}              thousandSeparator=","              decimalSeparator="."              allowNegative={false}              placeholder="TP Price"              inputMode="decimal"              customInput={Input}              className="text-sm"            />            <NumericFormat              value={tpPercent}              onValueChange={(values) => handleTpPercentChange(values.value)}              decimalSeparator="."              allowNegative={false}              placeholder="Gain"              suffix=" %"              inputMode="decimal"              customInput={Input}              className="text-sm"            />          </div>        </div>        <Separator />        {/* Stop Loss */}        <div className="flex flex-col gap-2">          <div className="flex items-center justify-between">            <span className="text-sm font-medium text-red-400">Stop Loss</span>            {(slPrice || slPercent) && (              <button                type="button"                onClick={handleClearSl}                className="text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer"              >                Clear              </button>            )}          </div>          <div className="flex items-center gap-2">            <NumericFormat              value={slPrice}              onValueChange={(values) => handleSlPriceChange(values.value)}              thousandSeparator=","              decimalSeparator="."              allowNegative={false}              placeholder="SL Price"              inputMode="decimal"              customInput={Input}              className="text-sm"            />            <NumericFormat              value={slPercent}              onValueChange={(values) => handleSlPercentChange(values.value)}              decimalSeparator="."              allowNegative={false}              placeholder="Loss"              suffix=" %"              inputMode="decimal"              customInput={Input}              className="text-sm"            />          </div>        </div>        <Button onClick={handleSubmit} className="w-full" size="lg">          Confirm TP / SL        </Button>      </CardContent>    </Card>  );};export type { OrderFormProps, OrderFormDetail };export { OrderForm };