Skip to content

Dropdown: standardize node slots on inlineStartNode/inlineEndNode#98

Merged
lifeiscontent merged 2 commits into
mainfrom
refactor/dropdown-slots
Jun 16, 2026
Merged

Dropdown: standardize node slots on inlineStartNode/inlineEndNode#98
lifeiscontent merged 2 commits into
mainfrom
refactor/dropdown-slots

Conversation

@lifeiscontent

@lifeiscontent lifeiscontent commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Closes #61.

DropdownItem was the worst offender for slot-naming soup. This aligns every Dropdown menu part with the library's node-slot convention (Button/IconButton use inlineStartNode/inlineEndNode + nodeSlotClass + --node-size), not leadingIcon/leading/trailing/trailingIcon/icon/value/action. Each part now exposes a single logical start + end node.

API

  • DropdownItem: icon + leading become inlineStartNode; value + trailing + endIcon become inlineEndNode. Keeps label / description / secondaryText / selected / variant / emphasis.
  • DropdownCheckboxItem: icon becomes inlineStartNode; value becomes inlineEndNode (the Checkbox stays the fixed leading control).
  • DropdownSubTrigger: icon becomes inlineStartNode; trailing becomes inlineEndNode.
  • DropdownLabel: action becomes inlineEndNode.

Icons dropped into a slot size to --node-size (16px) and inherit the row color; full-size controls (Checkbox/Avatar/swatch) size themselves. All consumers on main (dropdown / toolbar / breadcrumb stories) updated.

Notes

  • Breaking change; proposal for review (not self-merged).
  • The Table PR (Add Table component #15) composes DropdownItem with icon= in its RichRows story; it needs a one-line follow-up to inlineStartNode= when it merges.

Verification

vp check + vp run test green (167 tests incl. dropdown/toolbar/breadcrumb + axe gate).

Closes #61

Rename DropdownItem icon -> leadingIcon and endIcon -> trailingIcon, and
drop the value text slot (callers compose value text into trailing) plus
its with-value variant. Rename DropdownSubTrigger icon -> leadingIcon and
DropdownLabel action -> trailing so every slot follows the same
leading*/trailing* vocabulary used by Button and Input.

Update the dropdown, toolbar, and breadcrumb consumers on main to match.
Copilot AI review requested due to automatic review settings June 15, 2026 15:40
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

📚 Storybook preview: https://pr-98-propel-storybook.vamsi-906.workers.dev

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Standardizes Dropdown slot prop names to match the library’s leading/trailing conventions (e.g. Button/Input), and simplifies DropdownItem’s trailing-side API by removing the dedicated value slot in favor of composing into trailing. This is a breaking public API change affecting component consumers and stories.

Changes:

  • Rename DropdownItem props: iconleadingIcon, endIcontrailingIcon, remove value, remove with-value layout variant.
  • Rename DropdownSubTrigger prop: iconleadingIcon; rename DropdownLabel prop: actiontrailing.
  • Update Storybook consumers (Dropdown + Toolbar stories) to use the new prop names and compose prior value content into trailing.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
packages/propel/src/components/toolbar/toolbar.stories.tsx Updates toolbar dropdown story usage from icon to leadingIcon.
packages/propel/src/components/dropdown/index.tsx Renames public slot props, removes with-value / value, and updates rendering to the new slot names.
packages/propel/src/components/dropdown/dropdown.stories.tsx Updates dropdown stories to the renamed props and recomposes old value content into trailing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/propel/src/components/dropdown/index.tsx Outdated
Comment thread packages/propel/src/components/dropdown/index.tsx Outdated
Comment thread packages/propel/src/components/dropdown/index.tsx Outdated
…on/trailing

Aligns the Dropdown slots with the library's node-slot convention (Button/IconButton
use inlineStartNode/inlineEndNode + nodeSlotClass + --node-size), instead of the
leadingIcon/leading/trailing/trailingIcon/icon/value/action names. Each menu part now
exposes a single logical inlineStartNode + inlineEndNode:

- DropdownItem: leadingIcon + leading -> inlineStartNode; trailing + trailingIcon ->
  inlineEndNode (value was already folded into trailing).
- DropdownCheckboxItem: icon -> inlineStartNode; value -> inlineEndNode.
- DropdownSubTrigger: leadingIcon -> inlineStartNode; trailing -> inlineEndNode.
- DropdownLabel: trailing -> inlineEndNode.

Icons in a slot size to --node-size (16px) and inherit the row color; full-size
controls (Checkbox/Avatar/swatch) size themselves. Stories updated to match.
@lifeiscontent lifeiscontent changed the title Dropdown: standardize leading/trailing slot names Dropdown: standardize node slots on inlineStartNode/inlineEndNode Jun 15, 2026
@lifeiscontent lifeiscontent merged commit d21fe33 into main Jun 16, 2026
2 checks passed
@lifeiscontent lifeiscontent deleted the refactor/dropdown-slots branch June 16, 2026 05:50
lifeiscontent added a commit that referenced this pull request Jun 16, 2026
* Add Table component

* Table: use div wrappers in th/td; gate sort button on onSort

TableHead/TableCell wrapped children in a <span>, which restricts them to
phrasing content and produces invalid HTML when block-level children are
passed into <th>/<td>. Switch to <div> wrappers.

Also, a sortable header without an onSort handler rendered a focusable
<button> that did nothing. Only render the sort control when onSort is
provided; otherwise fall back to a plain header label.

* Table: remove cva defaultVariants; make TableHead variant required

The TableHead variant axis (default/sortable) no longer defaults to
"default" via cva. variant is now a required prop, so every header cell
declares its visual treatment explicitly. sort/onSort remain optional
(additive, only meaningful for sortable headers).

* Table stories: add Figma design link to meta parameters

* Table: add Spreadsheet variant + editable cells via Dropdown

Address design review on PR #15 (Figma 4017-653):

- Two variants on the root `Table` via a required `variant` prop, read by the
  cells through context: `table` (row dividers only) and `spreadsheet` (full
  grid — every cell bordered). Both share the Figma cell metrics (38px, px-3
  py-2, header on layer-1, body on surface-1) and a rounded outer border.
- `TableEditableCell`: a click-to-edit cell whose value + trailing chevron is the
  trigger for a propel Dropdown, picking a new value that updates the row in
  place (Figma "Account type" editable cell). Owns the Dropdown root + trigger;
  the consumer passes the DropdownContent.
- Stories: Default (Table), Spreadsheet, Sortable, EditableCells (click-cell →
  dropdown → value updates), all with the 4017-653 design link.

Pagination is intentionally left as a separate component to compose, per review.

* Table: add keyboard play tests for sortable header and editable cell

SortableKeyboard: Tab to the sortable header button, then Enter/Space
cycle the sort (none -> ascending -> descending -> none) with aria-sort
and onSort following along; asserts the th scope=col semantics.

EditableCellKeyboard: Tab+Enter opens the portaled Dropdown, Arrow Down +
Enter selects a value and the cell updates in place, and Escape closes the
menu and returns focus to the cell trigger. The portaled menu is queried
from the document body by unique item text with waitFor for open/close.

Both tagged [!dev,!autodocs,!manifest] so they run under test only.

* Table: add a WithPagination story composing the Pagination component

Designer follow-up on #15: pagination can be used with the table. The Table and
Pagination stay separate components, so the story keeps the full dataset, renders
only the current page of rows, and lets the Pagination below drive page and page
size (changing the size resets to page 1 and updates the range label).

Verified in Storybook (table variants, editable cell, and pagination) plus a play
test that advances a page and asserts the rows swap.

* Storybook: drop the ai-generated tag from the table story

* Table: fence the TableEditableCell TSDoc usage example as a code block

Same Markdown-in-autodocs issue as Popover/Tabs/Dropdown: the indented JSX example
let the wrapper tag leak out of the code box. Wrap it in a ```tsx fence.

* Table: 44px rows, fix editable-cell height, stable pagination width, cell states

Addresses design review on #15:
- Rows are 44px. The editable cell was forcing rows to ~55px because its p-0 override
  lost to the shared cell padding under cx; padding now lives on each cell type, so the
  editable cell is truly full-bleed and its 44px button sets the height.
- Editable cells get an explicit open/selected state (data-[popup-open]) next to the
  existing hover and focus states.
- The WithPagination demo gets a fixed width so the table no longer resizes between
  pages (it is w-full and was shrink-to-fitting each page's content under centered
  layout). The table already has the border-subtle outer border.

* Table: outer border, sticky header + pinned columns, cell slots, action cell (#15)

Addresses the design review (Figma 5196-4084):

1. The outer border-subtle now shows: the table is wrapped in a rounded, bordered,
   scrollable frame (a <table> can't clip its own rounded corners, so the border was
   invisible before).
2. Sticky header on vertical scroll, plus an opt-in pinned first/last column on
   horizontal scroll via each cell's pinned start/end. The frame is keyboard-scrollable
   (tabindex) only while it overflows, so it adds no stray tab stop and satisfies the
   axe scrollable-region rule.
3. TableCell gains inlineStartNode/inlineEndNode slots for a leading/trailing icon or
   Avatar (the Name column).
4. New TableActionCell: an icon-only (default ellipsis) row-options cell that opens a
   Dropdown, mirroring TableEditableCell.
5. Rows sit on layer-2 / layer-2-hover; actionable cells are layer-transparent /
   layer-transparent-hover so the cell hover reads distinctly over the row hover.

Closes #15

* Table: wait for the action menu to settle before asserting visibility

The RichRows action-menu test asserted toBeVisible right after findByRole, racing
the menu's open scale-fade in CI. Wrap it in waitFor like the other menu tests.

* Table: update RichRows DropdownItem to inlineStartNode (follows #98)

* Table: address design review (single grid border, tertiary header, overlay scrollbar, selected cell)

Round of fixes from the PR #15 design review:

- Spreadsheet variant no longer draws a double border. The scroll frame owns the
  single outer ring, so cells only draw interior dividers (inline-end + bottom, with
  the last column/row dropping theirs). This also clears the squared-off corners that
  poked past the rounded frame.
- Header text now uses text-tertiary to match the Figma "Table header" token (was
  text-secondary on plain headers).
- Scroll frame is now a Base UI ScrollArea, so the scrollbar overlays the content
  instead of reserving a 12px gutter. The header is no longer clipped and the table
  width stays constant whether or not the scrollbar is showing (also keeps width
  stable across pagination). Sticky header + pinned columns still work because the
  ScrollArea Viewport is the real scroll container; it also gets tabIndex=0 only while
  it overflows, which keeps the scrollable-region-focusable a11y rule satisfied
  without the manual ResizeObserver.
- Pinned columns draw a hairline on their inline edge so they read as separated from
  the content scrolling under them (logical border, RTL-safe).
- Editable cell gains a selected prop: a persistent layer-transparent-selected tint a
  step above hover, for marking the active cell. The story wires it to the last-opened
  cell.

Verified in the browser across light/dark and LTR/RTL: single clean grid, tertiary
header, centered avatars, 44px rows, overlay scrollbar (0 reserved space), constant
width across pages, sticky header + pinned columns. vp check and the full test suite
(736 tests, axe gate across 4 themes) pass.

* Table: dim editable/action cell icons when the trigger is disabled

* Table: use px-4 cell padding to match Figma spec
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dropdown: standardize leading/trailing slot names and collapse the trailing slot soup

3 participants