Skip to content

Add Dropdown (menu) component#16

Merged
lifeiscontent merged 12 commits into
mainfrom
feat/dropdown
Jun 9, 2026
Merged

Add Dropdown (menu) component#16
lifeiscontent merged 12 commits into
mainfrom
feat/dropdown

Conversation

@lifeiscontent

@lifeiscontent lifeiscontent commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Component

Dropdown, a compound menu built on @base-ui/react/menu, styled with @plane/propel semantic tokens (Tailwind v4 utilities, cva+cx, no className/style props, no arbitrary hex).

Parts / subcomponents

  • Dropdown (= Menu.Root), open state + trigger/content wiring
  • DropdownTrigger, opens the menu; renders a <button>, projectable via render
  • DropdownContent, bundles Portal + Positioner + Popup (surface-1 bg, overlay shadow, subtle border, scale/fade transition); exposes side/sideOffset/align
  • DropdownItem, selectable row
  • DropdownCheckboxItem, toggleable row with a leading check indicator (multi-select; stays open)
  • DropdownGroup (= Menu.Group) + DropdownLabel (= Menu.GroupLabel), titled groups
  • DropdownSeparator, divider

The stories declare these via subcomponents, matching the AvatarGroup pattern.

Item variants

variant: default | with-description | with-value (from Figma node 27-1057). An item is optional leading icon/checkbox + label + optional description + optional trailing value/end icon, all content. selected/disabled are primitive state, not variants.

Stories

  • Default, trigger + menu with icons, a trailing value, a with-description item, a separator, and a disabled item; play test opens the menu (role="menu"), asserts items, and confirms selecting closes it.
  • WithCheckboxes, multi-select via DropdownCheckboxItem; play test toggles a checkbox and confirms the menu stays open.

expect from storybook/test, { canvas, userEvent, waitFor }, queried by role; portal awaited with waitFor.

Verification

  • vp install, ok
  • vp check (format + lint + types), pass
  • vp run -r test (Storybook play tests, chromium), 14 passed
  • vp run -r build, pass, attw No problems found (pre-existing publint ./hooks/* warnings are unrelated)

Follow-up

The Figma file also contains a Search-Dropdown / combobox surface. Select is intended as a separate follow-up component.

Dependencies

Blocks: #15 (Table, editable cells are built on the Dropdown).

Compound menu built on @base-ui/react/menu: Dropdown, DropdownTrigger,
DropdownContent (Portal+Positioner+Popup), DropdownItem, DropdownCheckboxItem,
DropdownGroup, DropdownLabel, DropdownSeparator. Item variant axis
default | with-description | with-value (Figma 27-1057); selected/disabled
are primitive state. propel tokens via cva+cx, no className/style props.
Copilot AI review requested due to automatic review settings June 8, 2026 14:02
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

📚 Storybook preview: https://pr-16-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

Adds a new Dropdown (menu) compound component to @plane/propel, built on @base-ui/react/menu and styled with Propel semantic tokens for consistent menu behavior + visuals across the design system.

Changes:

  • Introduces Dropdown compound primitives (DropdownTrigger, DropdownContent, DropdownItem, DropdownCheckboxItem, DropdownGroup, DropdownLabel, DropdownSeparator).
  • Implements item layout variants (default, with-description, with-value) and shared menu surface styling/positioning.
  • Adds Storybook stories with play tests for default selection (closes) and checkbox multi-select (stays open).

Reviewed changes

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

File Description
packages/propel/src/components/dropdown/index.tsx Implements the new Dropdown compound component API and styling.
packages/propel/src/components/dropdown/dropdown.stories.tsx Adds Storybook documentation + play tests for the Dropdown component.

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

Comment thread packages/propel/src/components/dropdown/dropdown.stories.tsx Outdated
Comment thread packages/propel/src/components/dropdown/dropdown.stories.tsx
Comment thread packages/propel/src/components/dropdown/index.tsx Outdated
lifeiscontent and others added 5 commits June 9, 2026 00:49
- stories: assert inside waitFor callback (querySelector null resolves immediately)
- stories: add DropdownGroup to subcomponents (all exported parts listed)
- DropdownCheckboxItem: disabled styling on check indicator + aria-hidden on icon
Multi-select rows now render the propel Checkbox as the leading control
(via a new presentational CheckboxVisual, so the menuitemcheckbox row stays
the single interactive control and ARIA-valid). Single-select rows show a
leading checkmark on the selected row only.

New compound parts: DropdownSearch (sticky search header, outside role=menu),
DropdownFooter, DropdownSub/DropdownSubTrigger/DropdownSubContent (submenu),
plus DropdownItem slots (leading control, selected checkmark, secondaryText,
trailing badge/shortcut) and a DropdownContent width axis. All documented in
the stories' subcomponents.

Adds one real, interactive story per Figma demo (12): Status, Labels,
ActionMenu, Description, Assignees, LanguagePicker, Priority, Filters,
DisplayProperties, DisplayAccordion, EmptyState, Submenu — each with an
open/select play test. Composes Checkbox/Radio/Avatar/Badge.

vp check + run -r test (59 passed) + run -r build (attw clean) all pass.
@lifeiscontent

lifeiscontent commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

Reworked: Checkbox integration + one interactive story per Figma demo

Checkbox integration (multi-select)

Multi-select rows (DropdownCheckboxItem) now render the propel Checkbox as their leading control. To stay ARIA-valid (a real <Checkbox> inside a role="menuitemcheckbox" row is a nested-interactive violation), I added a presentational sibling CheckboxVisual to the checkbox component: same tokens/states, non-interactive <span>, no role/focus. The row remains the single interactive control and owns the toggle + a11y semantics; the box mirrors checked/disabled. Single-select rows use a leading checkmark on the selected row only (DropdownItem selected), no per-row control on the others.

The 12 demos to stories (each interactive, with an open/select play test)

  1. Status 60-465: single-select, status icons, sticky search, selected checkmark
  2. Labels 64-1827: multi-select (Checkbox + color swatch), search, "Type to add a new label" footer
  3. ActionMenu 61-558: icons, Cmd+L shortcut, disabled item w/ description, destructive Delete, separators
  4. Description 71-1455: single-select, two-line label+description, wider menu
  5. Assignees 889-2525: multi-select (Checkbox + Avatar), search, disabled row
  6. LanguagePicker 4462-1565: single-select, label + inline muted secondary text, search, checkmark
  7. Priority 2296-11107: multi-select (Checkbox + priority glyph), search
  8. Filters 43-229: multi-select, multiple section headers, "View all", search, long scroll
  9. DisplayProperties 56-187: selectable pill group + single-select Radio section + checkbox toggles
  10. DisplayAccordion 4485-1694: collapsible sections, Radio sort list + sort-direction toggle, checkbox footer
  11. EmptyState 64-758: "No matching results" on search + filtered list restore
  12. Submenu 1763-1939: rows with trailing count Badge + chevron opening a nested submenu

New compound parts (all in subcomponents)

  • DropdownSearch: sticky search header, rendered outside the role="menu" element (via DropdownContent's search prop) so the menu only contains menu items
  • DropdownFooter: sticky footer note (via footer prop)
  • DropdownSub / DropdownSubTrigger / DropdownSubContent: submenu (Base UI Menu.SubmenuRoot); DropdownSubTrigger takes a trailing slot for the count Badge
  • DropdownItem gained: leading (compose a Radio/Avatar/swatch), selected (single-select checkmark), secondaryText (inline muted text), trailing (Badge/shortcut)
  • DropdownContent gained a width axis (anchor/sm/md/lg, matching Figma menu widths) and search/footer slots
  • DropdownLabel retuned to the Figma "Dropdown header" (title-case text/12 text/tertiary) + optional action slot

Missing-primitive flags (built minimally inside the relevant stories, NOT in the Dropdown API)

  • Selectable pill/chip (DisplayProperties): propel has no chip/toggle-pill component yet
  • ButtonGroup / sort-direction toggle (DisplayAccordion): propel has no ButtonGroup yet
  • Non-menu Popover/settings-panel surface: DisplayProperties & DisplayAccordion are settings panels (mixing radios/pills/toggles), not ARIA menus. They render DropdownContent role="group"; Base UI's menu popup still injects an aria-orientation that axe disallows on role="group", so those two stories suppress just aria-allowed-attr (documented). The Submenu story suppresses just aria-required-children because Base UI emits its own <span aria-owns> submenu-association markup inside the parent menu. A dedicated Popover primitive would remove both exceptions.

Verification

  • vp check (format + lint + types), pass
  • vp run -r test, 59 passed (had one port flake on first run; clean on re-run, as expected)
  • vp run -r build, pass, attw No problems found (pre-existing publint ./hooks/* warnings unrelated)

Drop defaultVariants from both cva blocks in dropdown/index.tsx. The
essential item layout axis (variant: default/with-description/with-value)
is now a required prop; the additive surface width axis stays optional
with no default. Update all 12 demo stories to pass variant explicitly.
@lifeiscontent

Copy link
Copy Markdown
Collaborator Author

Removed defaultVariants; item variant now required.

In RTL submenus open to the inline-start (left) side; flip the
DropdownSubTrigger ChevronRight so it points toward the submenu.
Rest of the component already uses logical utilities (gap, px, -mx),
so no other physical-direction conversions were needed.
@lifeiscontent

lifeiscontent commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

RTL fix

The submenu trigger ChevronRight now carries rtl:-scale-x-100 so it points toward the submenu in both directions:

  • LTR: submenu opens right, chevron points > (unchanged).
  • RTL: submenu opens to the inline-start (left), chevron points <.

Directional-utility scan

Scanned the whole component (items, search, footer, sub-parts, checkmark/value/trailing slots). It was already RTL-clean: everything uses logical/symmetric utilities (gap, px-*, -mx-*, min-w-*, flex-1), no ml/mr/pl/pr, left/right, text-left, border-l/r, or space-x. The chevron was the only physical-directional element. Base UI's Menu.Positioner side="right" resolves the side logically under DirectionProvider, so positioning flips automatically.

Verification

Rendered in Storybook wrapped in Base UI DirectionProvider direction="rtl" with dir="rtl" on <html> (so the portaled popup inherits it):

  • RTL: menu right-aligned; leading checkmark/checkbox on the inline-start (right) side; value/count on the inline-end (left) side; submenu opens to the LEFT; submenu chevron points left.
  • LTR (existing Submenu story): unchanged, icons left, count/chevron right, submenu opens right.

Gates: vp check, vp run -r test (59 passed), vp run -r build all pass.

@bhaveshraja

Copy link
Copy Markdown
Collaborator
  1. Description dropdown - Icon to be top alligned
  2. When selected - icon not to be replaced by 'tick'.
  3. Labels - Add new label option to be like this. Also, 2 horizontal lines between the search and new label item.
  4. Action Menu - Archive icon to be top aligned
  5. Filters - divider between categories missing. Icons for items missing. Category chevron (to close and open category) missing.
  6. View all option behavior - on hover no background change and cursor to change to hand pointer
  7. Display accordion - items in a category to have 0 spacing
  8. Text color of Sub-text in dropdown item should be text-tertiary
  9. Avatars used in dropdown items are very big. We need to use 2xs avatars.
  10. border radius of menu items should be --radius-md
  11. border radius of overall dropdown should be --radius-lg
  12. Search should no have any border radius.
  13. Display pills UI has to be updated.
  14. Top padding of each menu item should be 6px. With overall height to be 34px

@lifeiscontent

lifeiscontent commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

Fixed CI: rebasing onto main picked up Badge's now-required tone (from #24), and the submenu demo's count badges were missing it, added tone="neutral". (The intermittent ~5-test tooltip failure is the cold-start flake that #28 fixes.)

@lifeiscontent lifeiscontent mentioned this pull request Jun 9, 2026
…i, new-label, filters, pills)

- Item icon top-aligned (align-start) so it sits with the first line on
  multi-line (with-description) rows; Archive in ActionMenu now top-aligns.
- Selected single-select rows keep their own icon and mark selection with a
  trailing check (no longer replace the leading icon with a tick).
- Surface (content/panel) radius rounded-lg; menu item radius rounded-md;
  items 34px tall. Search keeps no radius.
- Sub-text/description stays text-tertiary.
- Labels demo: 'Add label "…"' option (Figma 64-626) with two divider lines
  between the search and the new-label item.
- Filters demo: divider between categories, leading icons per item, a
  collapse/expand chevron on each category heading (heading is a menuitem so
  it stays ARIA-valid); 'View all' uses new emphasis="link" (no hover bg,
  cursor-pointer).
- DisplayAccordion: items within a category sit flush (0 spacing).
- Avatars in dropdown items use 2xs magnitude.
- DisplayProperties pills restyled to Figma 56-366 (real Pills component pending).
@lifeiscontent

lifeiscontent commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

@bhaveshraja, all the feedback is addressed. Everything below is live in Storybook, best way to check is to open each story and compare against Figma.

  1. Description menu: leading icon now sits at the top of multi-line items (aligned with the first line, not floating in the middle). Description story
  2. Selected item keeps its own icon: selection now shows as a check on the right side instead of replacing the icon with a tick. Description / Status stories
  3. Labels "Add label" option added (matches the new-label design). When you type a name that doesn't exist yet, an "+ Add label …" row appears, with two divider lines separating it from the search. Labels story
  4. Action menu Archive icon top-aligned (same fix as Avatar redesign, type-only convention, and design guidelines #1). ActionMenu story
  5. Filters: added the divider between categories, an icon on every item, and a chevron to collapse/expand each category. Filters story
  6. "View all": no longer highlights on hover, and shows the pointer cursor. Filters story
  7. Display options (accordion): items inside a category now sit flush together (no gaps). DisplayAccordion story
  8. Item description text is the lighter tertiary grey. Description story
  9. Avatars in items are smaller now (the smallest size). Assignees / Filters stories
  10. Menu item corners: medium radius. (everywhere)
  11. Whole dropdown panel corners: large radius. (everywhere)
  12. Search field has no rounded corners. Any story with search
  13. Display pills restyled to match the pills design. See note in the questions below. DisplayProperties story
  14. Item height 34px with 6px top padding. (everywhere)

Visually checked Description, ActionMenu, Labels, Filters, DisplayProperties, and DisplayAccordion against Figma. A short list of design questions is in the next comment.

@lifeiscontent

lifeiscontent commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

@bhaveshraja, a few quick design questions so I can finish these off:

  1. Display pills (Add Tooltip component #14): these are a temporary copy I styled inside the Dropdown demo, there's no reusable Pills/Chip component yet. Do you want me to design+build a proper Pills component as its own piece of work (with its hover / selected / disabled states defined), or are the demo pills fine for now?

  2. "Add label" dividers (Add Checkbox component #3): the Figma frame shows one line between the search and the "Add label" row, but the note asked for two. I went with two, can you confirm that's what you want, or should it be one?

  3. "View all" (Add Breadcrumb component #6): I've styled it, but what should it do when clicked? Expand the rest of the list inline, open a separate panel, or jump to a full screen? Right now it doesn't do anything.

  4. Filters category headers: to make the collapse/expand work cleanly, the whole category title row is clickable (you click anywhere on "Priority" to fold it, and the chevron shows the state). Is that the behavior you want, or should only the little chevron be clickable? Check the Filters story.

  5. Display Properties / Display Accordion are settings panels (mixing toggles, radios, and pills) rather than a simple menu. They work, but a few rough edges would go away if we treat them as their own "panel/popover" pattern instead of a dropdown menu. Do you want me to spec that as a separate thing, or leave them as-is here?

@bhaveshraja

Copy link
Copy Markdown
Collaborator
  1. Yes we need to make pill as a component, will share it.
  2. There were 2 lines, we need only 1. My bad I didn't write it properly.
  3. View all - Expand the rest of the list inline
  4. Full header should be clickable. (I checked its working properly)
  5. Which where is feasible in technical point of you. We can treat them as their own panel.

@lifeiscontent lifeiscontent merged commit 9859e84 into main Jun 9, 2026
2 checks passed
lifeiscontent added a commit that referenced this pull request Jun 10, 2026
main typecheck broke once #16 (Dropdown, uses Checkbox) and #22 (Checkbox
tone required) both merged. The dropdown demo PanelCheckboxRow Checkbox had
no tone. Add tone="neutral".
@lifeiscontent lifeiscontent deleted the feat/dropdown branch June 10, 2026 15:10
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.

3 participants