A pill that opens into the list it was showing one line of.
The prompt21,077 characters
Add the "Select" component from notboring to my project.
It is licensed for use in my projects — notboring.dev/license. Please:
1. Create the component at a sensible path for this
project, named Select, from the source below.
2. Install what it needs: npm i framer-motion lucide-react
3. Add the CSS to the project's stylesheet.
4. THE PART THAT NEEDS YOUR JUDGMENT. The CSS reads
these custom properties and does not define them:
--card
--font-ui
--ink
--ink-rgb
--pane-edge
They are notboring's design tokens. Map each one to
whatever this project already uses for the same
job — its own ink, ground, surface and UI font —
rather than defining them as new globals. If the
project has no equivalent, define it locally on
the component's own root so nothing leaks out.
Anything ending -rgb wants three bare numbers,
because the CSS builds rgba() from it.
Keep the comments. They say why the numbers are what
they are, and they are most of what makes this worth
copying rather than rewriting.
--- Select.tsx ---
import { useEffect, useId, useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Check, ChevronDown } from "lucide-react";
/* ══ Select ═══════════════════════════════════════════════
A pill that opens into the list it was showing one line of.
THE PILL IS A ROW OF THE LIST. It has the list's type and
the list's inset, so when the card unfolds beneath it the
word in the pill reads as the one row you could already see
— not as a button that summoned a menu. The obvious build
gives the trigger a style of its own, and then the open
state is two things: a control, and a panel that belongs to
a different family.
THE WORD ROLLS, IT DOES NOT SWAP. A pick sends the old word
up and out of the pill and brings the new one in from
below, clipped by the pill's own box, so the change reads
as a counter turning rather than a label being replaced.
One direction, always: a roll that reversed to match where
the option sat in the list would be a second thing to
notice on a control that only wants one. */
const W = 240;
The rest — the source and its comments — is what you are buying.