shadcn.io is not affiliated with official shadcn/ui
Calculator Command Menu Block for Radix UI
A finance scratchpad's command menu shown open over a drawn spreadsheet: type a sum and the first row is its result, Enter copies it and files it in History, and four quick operations act on the result.
Installation
Overview
Work out a sum without leaving the keyboard with this calculator command menu for React with Radix UI. It opens from a budget sheet in Sumline, a finance scratchpad, whose header holds an outline field with its ⌘ K cap, and the sheet is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The menu is the catalog Command, narrow: the field is the calculator, and a sum such as 1500 * 1.08 + 10% is read by a small tokenizer and a shunting-yard evaluator, with no eval and no new Function, so the first row is the result, "= 1,782.00", with the sum under it. A sum that cannot be read says why, for instance "Unfinished: ends with +". Above the field a row of quick operations (Add 10%, Half, Double, Square) acts on the result and scrolls sideways on a phone. Enter copies the result and files the sum in History, a History row puts its sum back in the field, and the Scratch cell of the drawn sheet shows what was filed last. For preview the menu is drawn open in place when the page loads; the field, or Ctrl or ⌘ K while focus is in the block, opens the real command dialog with the same sum and History. For spreadsheets, invoicing tools and any app where a quick sum should not need another window.
FAQ
Why is the menu already open, and what do I keep?
So you can see it without a click. On load the menu is the Command drawn inline over a picture of a budget sheet, in a plain shell that is not modal, so nothing is trapped, hidden or focused and the page around the preview keeps working. The header's field opens the real command dialog, modal, with its focus trap, Escape and focus return, and the inline copy stays closed until a reload. Both show the same Palette and share the block's state, so a sum or a History row in the copy is in the dialog. In your app keep the dialog and drop the copy: the "in-place" state, the scrim with its shell and the Escape and blur effects.
How is the sum evaluated, and what can it read?
tokenize cuts the text into numbers, operators, brackets and percent signs, feed runs them through a shunting-yard (a stack of waiting operators and an output queue) and runPostfix evaluates the result. It reads + - * / ^ (also x, × and ÷), brackets, a leading minus, thousands commas and decimals. A percent after a plus or a minus is a share of the number before it, so 200 + 10% is 220, and on its own 10% is 0.1. Nothing is passed to eval or new Function, so a pasted string can only ever be arithmetic.
What does it say when a sum is wrong?
It names the fault in the first row and on the status line: "Unfinished: ends with +", "Unfinished: a ( is not closed", "Extra ) with no ( before it", "Missing an operator before 4", "Unexpected “c”" or "Can’t divide by zero". Enter on that row repeats the message rather than filing anything, and the quick operations tell you there is no result yet. Typing words instead of a sum searches History.
What do Enter and History do?
Enter on the result writes it to the clipboard with navigator.clipboard.writeText (as a plain number without commas, so it pastes anywhere), files the sum at the top of History and shows it in the sheet's Scratch cell; the same sum filed twice moves up instead of repeating. History shows its four newest sums, each with its result, and Enter on one puts its sum back in the field so you can change it. If the browser refuses the clipboard, the status line says so and the sum is still filed.
What stays local, and how do I wire it?
The sums, History and the sheet are local: History starts with three typed sums and lives in state, and the evaluator needs no server. The clipboard write is real. In your app keep evalOf as it is, load START from your own saved history, and write each filed sum to your store in pick; if the result goes into a cell, put it there in the same handler.
Is it accessible from the keyboard?
Yes. Type a sum, the arrow keys move through the result and History, Enter copies or loads the row, and Tab reaches the quick operations above the field. Escape closes the menu with focus on the field, and Ctrl or ⌘ K opens the dialog while focus is inside the block, and only then. The rows are options of a cmdk listbox; the block turns off its own fuzzy filter and lists the rows itself.