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-ticketPreview
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>
);
}