DesignX
Trading@paxeer/trading/order-ticketimplemented

Order ticket

Limit ticket. buy-emphasis maps to primary; sell-emphasis maps to error.

Install

$pnpm exec designx add @paxeer/trading/order-ticket

Preview

Loading preview...

Source code

components/designx/trading/order-ticket.tsxtsx
"use client";

import { useState } from "react";
import {
  Button,
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
  NumericStepper,
  StatusChip,
  ToggleGroup,
  ToggleGroupItem,
} from "@paxeer/core";

export type OrderSide = "buy" | "sell";
export type OrderEmphasis = "buy-emphasis" | "sell-emphasis";

export interface OrderTicketProps {
  symbol?: string;
  defaultSide?: OrderSide;
  defaultQuantity?: number;
  defaultPrice?: number;
  onSubmit?: (order: {
    side: OrderSide;
    quantity: number;
    price: number;
    timeInForce: string;
  }) => void;
}

const tifOptions = ["GTC", "IOC", "FOK"] as const;

export function OrderTicket({
  symbol = "ETH-USD",
  defaultSide = "buy",
  defaultQuantity = 1,
  defaultPrice = 2480.4,
  onSubmit,
}: OrderTicketProps) {
  const [side, setSide] = useState<OrderSide>(defaultSide);
  const [quantity, setQuantity] = useState(defaultQuantity);
  const [price, setPrice] = useState(defaultPrice);
  const [tif, setTif] = useState<(typeof tifOptions)[number]>("GTC");
  const [submitted, setSubmitted] = useState<string | null>(null);

  const emphasis: OrderEmphasis = side === "buy" ? "buy-emphasis" : "sell-emphasis";

  return (
    <Card className="gap-4 py-4" data-slot="order-ticket">
      <CardHeader className="px-4">
        <CardTitle className="font-[family-name:var(--md-ref-typeface-mono)]">
          {symbol}
        </CardTitle>
        <CardDescription>Limit ticket</CardDescription>
        <StatusChip tone={side === "buy" ? "primary" : "error"} dot>
          {emphasis}
        </StatusChip>
      </CardHeader>
      <CardContent className="flex flex-col gap-4 px-4">
        <ToggleGroup
          type="single"
          value={side}
          onValueChange={(value) => {
            if (value === "buy" || value === "sell") setSide(value);
          }}
          className="grid w-full grid-cols-2 bg-surface-container-lowest p-1"
          aria-label="Order side"
        >
          <ToggleGroupItem
            value="buy"
            className="data-[state=on]:bg-primary data-[state=on]:text-on-primary"
          >
            Buy
          </ToggleGroupItem>
          <ToggleGroupItem
            value="sell"
            className="data-[state=on]:bg-error data-[state=on]:text-on-error"
          >
            Sell
          </ToggleGroupItem>
        </ToggleGroup>

        <div className="flex flex-wrap gap-3">
          <NumericStepper
            label="Quantity"
            value={quantity}
            min={0.01}
            step={0.01}
            onValueChange={setQuantity}
          />
          <NumericStepper
            label="Limit price"
            value={price}
            min={0}
            step={0.1}
            onValueChange={setPrice}
          />
        </div>

        <fieldset className="flex flex-col gap-2">
          <legend className="text-[length:var(--md-sys-typescale-label-medium-size)] text-on-surface-variant">
            Time in force
          </legend>
          <ToggleGroup
            type="single"
            value={tif}
            onValueChange={(value) => {
              if (value === "GTC" || value === "IOC" || value === "FOK") setTif(value);
            }}
            variant="outline"
          >
            {tifOptions.map((option) => (
              <ToggleGroupItem key={option} value={option}>
                {option}
              </ToggleGroupItem>
            ))}
          </ToggleGroup>
        </fieldset>
      </CardContent>
      <CardFooter className="flex-col items-stretch gap-2 px-4">
        <Button
          variant={side === "buy" ? "filled" : "error"}
          onClick={() => {
            onSubmit?.({ side, quantity, price, timeInForce: tif });
            setSubmitted(
              `${side === "buy" ? "Buy" : "Sell"} ${quantity} ${symbol} @ ${price} ${tif}`,
            );
          }}
        >
          {side === "buy" ? "Buy" : "Sell"} {symbol}
        </Button>
        {submitted ? (
          <p className="text-[length:var(--md-sys-typescale-body-small-size)] text-on-surface-variant">
            Drafted {submitted}
          </p>
        ) : null}
      </CardFooter>
    </Card>
  );
}