KQ-1340 · GIFT/BUILDER
Gift Builder
A gift card you build by looking at it: the amount rolls onto the card's face digit by digit on snap, a design wipes across it as a circle growing on glide from the side its swatch sits on (or slides in 1:1 under your thumb when you swipe the card itself, committing by projection), and writing the message turns the card over on glide — side to side, or top over bottom — while each new run of characters inks onto the back and the type shrinks on glide to fit. Add to bag runs your promise and, when it lands, a paper band wraps across the card on glide and the seal lands on recoil. Amount, design, send day and the card's face are radiogroups, a slider and a toggle with full keyboard paths, and the card turns to follow the fields focus is in.
$50 · meadow · sends thu oct 8
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/gift-builder
Props
- flip
- "turn" | "tumble" | "none"
- default "turn"
- How the card shows its back: turned side to side about the vertical axis, tumbled top over bottom, or laid beside the front with no turning, both faces updating live.
- designs
- "swatches" | "tiles" | "swipe"
- default "swatches"
- How a design is picked: round swatches or miniature cards (the new design wipes across from the picker's side), or by swiping the card itself with dots as the radiogroup.
- amount
- "chips" | "slider"
- default "chips"
- How the amount is set: preset chips with a sliding pill and a Custom field, or a logarithmic detent slider that is drawn to the presets and settles on its step with the release velocity.
- designOptions
- GiftDesign[]
- default defaultGiftDesigns
- The designs on offer: id, name, art (meadow, confetti, linen, night, bloom, tide) and a tint drawn at fixed lightness.
- amounts
- number[]
- default [25, 50, 100, 150]
- The preset amounts: chips, or the slider's labelled detents.
- min
- number
- default 10
- The smallest amount, for the slider and the custom field.
- max
- number
- default 500
- The largest amount.
- step
- number
- default 5
- The slider's step; arrows move one, Page keys five.
- value / defaultValue
- GiftValue
- default defaultGiftValue
- The gift: amount, design id, to, from, message, recipient email and the send day (YYYY-MM-DD, or null to send now). Controlled or uncontrolled.
- onValueChange
- (value: GiftValue) => void
- default —
- Fires from the chip, key, drag, swipe or field that changed the gift.
- side / defaultSide
- "front" | "back"
- default "front"
- The face the card shows. Controlled or uncontrolled; ignored when flip is none.
- onSideChange
- (side: GiftSide) => void
- default —
- Fires when the visitor moves to the message (back), the amount or design (front), or turns the card with Show back.
- onSubmit
- (value: GiftValue) => void | Promise<void>
- default —
- Buy it. A promise holds Add to bag pending with its width kept; reject with an Error to show its message in an alert.
- state
- "idle" | "pending" | "done" | "error"
- default —
- Controlled purchase state; done wraps the card and shows the confirmation.
- onStateChange
- (state: GiftState) => void
- default —
- Fires as the purchase is asked for, done or refused.
- onReset
- () => void
- default —
- Make another was pressed after a purchase.
- brand
- string
- default "Fernworks"
- The shop on the card's face, back and seal.
- maxMessage
- number
- default 180
- The message's limit; the counter turns warn in the last twenty characters.
- scheduleDays
- number
- default 14
- Days ahead a send can be scheduled.
- now
- number | Date
- default defaultGiftNow
- The builder's moment: send days count from it, never the wall clock.
- formatPrice
- (amount: number) => string
- default US dollars, whole when whole
- Amounts to text, on the card, the chips, the slider and the button.
- title
- string
- default "Send a gift card"
- The heading, and the region's name unless label is given.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the designs have arrived: loading shows skeletons, error a message with Try again.
- onRetry
- () => void
- default —
- Try again was pressed after loading failed.
- label
- string
- default —
- The region's accessible name, when the visible title is not enough.
- sound
- boolean
- default false
- Paper when the visitor turns the card and as the band wraps; a swish when a design arrives or the amount moves.
- disabled
- boolean
- default false
- Read only: the card and the answers show, nothing changes.
- className
- string
- default —
- Classes for the root. It is at most 560px tall and scrolls inside itself.
| Prop | Type | Default | Description |
|---|---|---|---|
| flip | "turn" | "tumble" | "none" | "turn" | How the card shows its back: turned side to side about the vertical axis, tumbled top over bottom, or laid beside the front with no turning, both faces updating live. |
| designs | "swatches" | "tiles" | "swipe" | "swatches" | How a design is picked: round swatches or miniature cards (the new design wipes across from the picker's side), or by swiping the card itself with dots as the radiogroup. |
| amount | "chips" | "slider" | "chips" | How the amount is set: preset chips with a sliding pill and a Custom field, or a logarithmic detent slider that is drawn to the presets and settles on its step with the release velocity. |
| designOptions | GiftDesign[] | defaultGiftDesigns | The designs on offer: id, name, art (meadow, confetti, linen, night, bloom, tide) and a tint drawn at fixed lightness. |
| amounts | number[] | [25, 50, 100, 150] | The preset amounts: chips, or the slider's labelled detents. |
| min | number | 10 | The smallest amount, for the slider and the custom field. |
| max | number | 500 | The largest amount. |
| step | number | 5 | The slider's step; arrows move one, Page keys five. |
| value / defaultValue | GiftValue | defaultGiftValue | The gift: amount, design id, to, from, message, recipient email and the send day (YYYY-MM-DD, or null to send now). Controlled or uncontrolled. |
| onValueChange | (value: GiftValue) => void | — | Fires from the chip, key, drag, swipe or field that changed the gift. |
| side / defaultSide | "front" | "back" | "front" | The face the card shows. Controlled or uncontrolled; ignored when flip is none. |
| onSideChange | (side: GiftSide) => void | — | Fires when the visitor moves to the message (back), the amount or design (front), or turns the card with Show back. |
| onSubmit | (value: GiftValue) => void | Promise<void> | — | Buy it. A promise holds Add to bag pending with its width kept; reject with an Error to show its message in an alert. |
| state | "idle" | "pending" | "done" | "error" | — | Controlled purchase state; done wraps the card and shows the confirmation. |
| onStateChange | (state: GiftState) => void | — | Fires as the purchase is asked for, done or refused. |
| onReset | () => void | — | Make another was pressed after a purchase. |
| brand | string | "Fernworks" | The shop on the card's face, back and seal. |
| maxMessage | number | 180 | The message's limit; the counter turns warn in the last twenty characters. |
| scheduleDays | number | 14 | Days ahead a send can be scheduled. |
| now | number | Date | defaultGiftNow | The builder's moment: send days count from it, never the wall clock. |
| formatPrice | (amount: number) => string | US dollars, whole when whole | Amounts to text, on the card, the chips, the slider and the button. |
| title | string | "Send a gift card" | The heading, and the region's name unless label is given. |
| status | "ready" | "loading" | "error" | "ready" | Whether the designs have arrived: loading shows skeletons, error a message with Try again. |
| onRetry | () => void | — | Try again was pressed after loading failed. |
| label | string | — | The region's accessible name, when the visible title is not enough. |
| sound | boolean | false | Paper when the visitor turns the card and as the band wraps; a swish when a design arrives or the amount moves. |
| disabled | boolean | false | Read only: the card and the answers show, nothing changes. |
| className | string | — | Classes for the root. It is at most 560px tall and scrolls inside itself. |