npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cujuju/solidjs-pill-date-picker

v0.2.0

Published

Compact expiration / DTE picker for SolidJS — collapsed pill with a hover DTE tooltip, portalled pop-out ladder, caller-supplied dates, per-row state (available / adjusted / disabled), a full row escape hatch, and deterministic DTE math.

Readme

@cujuju/solidjs-pill-date-picker

Compact expiration / DTE picker for SolidJS. Collapsed, it is the date alone (Jul 17) with the DTE in a hover tooltip; expanded, it pops out a portalled ladder of every expiration with both its DTE and its date.

The caller supplies the dates. The control does not fetch, validate, sort, or filter them — it renders what it is given and computes the DTE itself.

Install

pnpm add @cujuju/solidjs-pill-date-picker @cujuju/solidjs-kv-tooltip

Peer dependencies: solid-js >= 1.7.0, @cujuju/solidjs-kv-tooltip.

Usage

import { PillDatePicker } from '@cujuju/solidjs-pill-date-picker';
import '@cujuju/solidjs-pill-date-picker/styles.css';

const [expiry, setExpiry] = createSignal<string | null>('2026-07-17');

<PillDatePicker
  items={['2026-06-19', '2026-06-26', '2026-07-17', '2026-09-18']}
  value={expiry()}
  onChange={(item) => setExpiry(item)}
  ariaLabel="Expiration"
/>

Carrying your own payload

items also accepts objects. Whatever else you hang off each entry comes back through onChange by reference — the ORIGINAL item, not a copy — with its types intact.

const chain = [
  { date: '2026-06-19', oi: 12_400, monthly: false },
  { date: '2026-07-17', oi: 98_100, monthly: true },
];

<PillDatePicker
  items={chain}
  value={expiry()}
  onChange={(item) => {
    setExpiry(item.date);
    console.log(item.oi, item.monthly); // still there, still typed
  }}
  tooltipEntries={(item, dte) => ({
    Expires: item.date,
    DTE: `${dte}d`,
    OI: item.oi.toLocaleString(),
  })}
/>

Props

| Prop | Default | Description | |---|---|---| | items | (required) | Ordered list of expirations. string[] of ISO dates, or objects with a date key. Rendered verbatim; assumed legitimate. | | value | — | The selected expiration, as its key (see keyOf — by default, its ISO date). Keyed by value, not by object identity, so a refetched chain does not silently deselect. | | keyOf | (the item's date) | The item's stable key. Required when a date is not unique in your ladder — see below. | | onChange | (required) | Fires with the ORIGINAL item. | | now | new Date() | The clock DTE is measured from. Inject it to make DTE deterministic (tests, replay, backtests). | | size | 'md' | 'xs' \| 'sm' \| 'md' — matches the sibling pill-number-picker's rhythm. | | disabled | false | | | placeholder | 'Select' | Collapsed label when nothing is selected. | | emptyMessage | 'No expirations' | Pop-out body when items is empty. | | noneSelectableMessage | 'Nothing selectable' | Line above the rows when every row is 'disabled'. | | open / onOpenChange | — | Controlled open state. Omit open for uncontrolled; onOpenChange fires either way. | | popoutGap | 4 | Gap in px between the pill and the panel. | | preferPlacement | 'bottom' | Side to open toward when both fit. | | dteRamp | see below | Ordered urgency bands, first match wins. | | formatDate | Jul 17 | Override the collapsed/row label — the escape hatch for locales. | | tooltipEntries | long date + DTE | Rows for the hover tooltip. Takes the whole item. | | disableTooltip | false | | | ariaLabel, class | — | Passthrough. |

keyOf — when one date is two contracts

By default the selection is keyed by the item's ISO date, which is correct for an ordinary ladder. It is wrong for index options: SPX (AM-settled) and SPXW (PM-settled) both expire on the third Friday, so a date-keyed ladder cannot tell them apart — and lights up both rows as selected.

Supply the key and the ambiguity is gone:

const ladder = [
  { id: 'SPX-2026-06-19',  date: '2026-06-19', settle: 'AM' },
  { id: 'SPXW-2026-06-19', date: '2026-06-19', settle: 'PM' },
];

<PillDatePicker
  items={ladder}
  keyOf={(i) => i.id}
  value={selectedId()}
  onChange={(item) => setSelectedId(item.id)}
/>

The key must be stable across refetches, which is why you supply it rather than the control deriving it. A tempting-but-broken choice is the item's position in the array: rebuild the ladder after a weekly expires and position 3 names a different expiration, so the selection silently moves to the wrong contract. That is worse than deselecting — nothing about it looks wrong. Use an id, an OCC root, ${date}:${settlement} — anything that names the contract, not its slot.

Row state — itemState + annotation

"Can I pick this?" is not a boolean in a real ladder, so a row is one of three things:

| State | Pickable | Looks like | |---|---|---| | available | yes | an ordinary row | | adjusted | yes | tinted, with your annotation — a row with a caveat, not a lesser row | | disabled | no | dimmed, aria-disabled, still visible |

<PillDatePicker
  items={dates}
  itemState={(d) => listsMyStrike(d) ? 'available'
                  : listsAnything(d) ? 'adjusted' : 'disabled'}
  annotation={(d) => nearestRung(d)}   // '≈ 150' · 'no puts listed'
  …
/>

When EVERY row is disabled the rows still render, with noneSelectableMessage stated once above them — a user should be told that nothing is takeable, not discover it by clicking five rows. That is a different fact from an empty ladder, which keeps its own emptyMessage.

A disabled row stays in the list on purpose. Filtering it out of items is the obvious alternative and it lies: the user cannot tell "not available to you" from "does not exist", and a ladder missing dates misrepresents the calendar it is supposed to be showing.

Disabled is enforced, not merely styled. The row cannot be committed by clicking it, by clicking anything a custom row nested inside it, or by pressing Enter on it; the arrow keys step over it, and the pointer will not move the cursor onto it — the highlight must never sit somewhere Enter refuses to act. The one deliberate exception: opening with a selection that has since become disabled leaves the cursor on that row, because moving it would make Enter pick a value the user never chose. The commit guard refuses it and the first arrow key leaves.

The annotation's wording is yours. This package has no vocabulary for why a date is what it is — that is domain knowledge, and a date picker inventing it would be guessing. Keep it to a few characters; the long form belongs in tooltipEntries.

renderRow — when three columns are not enough

Replaces what is inside a row, never the row element itself. The package keeps role="option", the state attributes, the selection rail, the cursor, click-to-commit and the disabled guarantee — the parts that are easy to get wrong and invisible when they are.

renderRow={(ctx) => (
  <>
    <span>{ctx.label}</span>                       {/* honours formatDate */}
    <span>{ctx.item.kind} · {ctx.item.oi}</span>   {/* your payload */}
    <span style={{ color: ctx.dteColor }}>{ctx.dteLabel}</span>
  </>
)}

ctx carries item, date, label, dte, dteLabel, dteColor, state, annotation, selected, active, index. These are the values the BUILT-IN row renders, not raw lookups — notably dteColor is undefined on a 'disabled' row, because the default row drops the urgency ramp there and one state should not look like two different things. Call the exported resolveDteColor yourself if you want the raw ramp. Everything that can change is a getter, so reading it inside your JSX stays live — a custom row sees the cursor move and the selection change without doing anything special. (Handing over a plain snapshot is the trap this avoids: ctx.active would be frozen at first paint and no consumer would have done anything wrong.)

Reach for itemState + annotation first. They keep every consumer's ladder looking like the same control; renderRow is for the row shape they cannot express.

DTE

DTE is a calendar-day difference, not an elapsed-time division. (expiry - now) / 86400000 is the obvious implementation and it is wrong twice: it drifts with the time of day (34 days at 09:00, 33 at 23:00 — same date), and doing the arithmetic on local dates makes a DST boundary a 23- or 25-hour day, so the floor lands one day off. Both ends are collapsed to UTC midnight first, now via its local calendar fields (a trader at 20:00 ET on the 16th is on the 16th).

Negative DTEs are returned as-is. You own which dates are legitimate; clamping an expired entry to 0d would hide your bug behind our formatting.

DTE colour

The urgency ramp is a prop, because "urgent" is a house opinion:

<PillDatePicker
  dteRamp={[
    { maxDte: 0,   color: 'var(--danger)' },
    { maxDte: 7,   color: 'var(--warn)' },
    { maxDte: 30,  color: 'var(--caution)' },
    { maxDte: Infinity, color: 'var(--muted)' },
  ]}
  ...
/>

Bounds are inclusive upper edges, consulted in order, first match wins. The default ramp resolves to --pdp-dte-expiring / --pdp-dte-urgent / --pdp-dte-near / --pdp-dte-far, so the common case is re-themed from your stylesheet without touching the prop at all.

The pop-out

The panel is rendered through a <Portal> and positioned in viewport coordinates. That is not a stylistic choice: the pill lives in a dense row, dense rows live inside overflow: hidden and overflow-y: auto boxes, and an in-flow expansion is clipped dead by such an ancestor. It repositions on resize and on a capturing scroll listener — the container that scrolls the anchor out from under the panel is almost never window.

Prefers to open downward (a list reads top-down from its trigger), flipping up when there is no room below, and clamping — top edge first — when there is room on neither side.

Theming

:root {
  --pdp-bg: #1e293b;
  --pdp-border: #334155;
  --pdp-text: #e2e8f0;
  --pdp-text-muted: #94a3b8;
  --pdp-radius: 6px;
  --pdp-hover-bg: rgba(255, 255, 255, 0.06);

  --pdp-popout-z: 1000;
  --pdp-popout-bg: #1e293b;
  --pdp-popout-border: #334155;
  --pdp-popout-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
  --pdp-popout-min-width: 132px;
  --pdp-popout-max-height: 260px;

  --pdp-row-hover-bg: rgba(255, 255, 255, 0.07);
  --pdp-row-selected-bg: rgba(99, 102, 241, 0.18);

  --pdp-dte-expiring: #f87171;
  --pdp-dte-urgent: #fb923c;
  --pdp-dte-near: #fbbf24;
  --pdp-dte-far: #94a3b8;
}

License

MIT