Skip to main content

Changelog

Release history of @plaud/design, organized by published version (newest first). Within each version, changes are grouped under ✨ Feature and 🐛 Bug Fix sub-sections, and listed per component. Version numbers come from the package's changeset changelog. Changes that predate the first published release are grouped under Pre-release.

v0.9.0

✨ Feature

    • (WEB-1277) feat(Select): add a ghost trigger variant (Details)
    • Select gains a variant prop ('outline' | 'ghost'), defaulting to 'outline' — existing usage is unchanged. Consumers opt into ghost via <Select variant="ghost" />; the internal SelectTrigger subcomponent consumes it but stays internal and is not exported from the package barrel
    • ghost is a borderless, content-width, right-aligned trigger with a trailing sort (up/down) arrow and a gray hover/open background, for inline / toolbar select scenarios
    • ui/select.tsx SelectTrigger gains an optional icon prop to override the default chevron (internal capability passthrough)
    • SortIcon neutral state (sortDirection === null) now renders with currentColor so it follows the parent text color (lets the ghost disabled state dim correctly); asc/desc active highlight is unchanged
    • (WEB-1277) fix(Select): fix WCAG AA contrast issues in the ghost trigger
    • Hover/open state text now deepens from Labels/Tertiary to Labels/Secondary on the Grays/Gray-1 background, raising contrast from 4.47:1 (fails 4.5:1) to 9.11:1
    • Keyboard focus indicator switches from the low-contrast gray background alone (1.19:1 against white, fails the 3:1 non-text contrast requirement) to a visible Labels/Primary focus ring (21:1)
    • (WEB-1277) fix(Select): fix ghost trigger width bug where it stretched to fill the full line
    • The ghost base class only overrode width (w-fullw-auto) but not display; the underlying ui/select.tsx primitive is a block-level flex container, so width: auto alone still filled the parent width in normal document flow (outside a flex/grid parent), causing the hover/focus gray background to span the entire line instead of hugging the content
    • Added inline-flex to the ghost base class alongside w-auto, so the trigger is now an inline-level flex box that truly shrink-to-fits its content in any layout context
    • (WEB-1310) feat(Select)!: support a left-checkmark option item variant, and rename MultiSelect.menuItemVariant to optionItemVariant (Details)
    • Breaking: MultiSelect.menuItemVariant is renamed to optionItemVariant (same meaning, same values, same default 'checkmark') — a dropdown row is a SelectOptionItem, not a "menu item", so the prop is renamed to match the domain vocabulary and to share one name across Select and MultiSelect
    • SelectContent (the combinatorial subcomponent) gains an optionItemVariant?: 'checkmark' | 'checkmark-right' prop, defaulting to 'checkmark-right' (zero regression), propagated via context to every SelectItem rendered inside it
    • Select gains a matching optionItemVariant prop, threaded through the declarative non-searchable options branch to the SelectContent it renders
    • Deliberately not placed on SelectItem itself: all rows in one dropdown must share the same checkmark placement (no Figma variant mixes left/right within a single menu), so a per-item prop would let a caller silently produce an inconsistent menu by forgetting it on one row
    • checkmark moves the selected-state indicator from the right to the left of the label, matching the Ghost trigger menu item Figma variant; item padding shifts from a right reservation to a left reservation accordingly
    • Scope: combinatorial SelectContent/SelectItem and the declarative non-searchable branch only — searchable single Select already covers left/right checkmark via SearchableOptionItem

🐛 Bug Fix

    • (WEB-1295) fix(Calendar): scroll & focus the current month/year when the month/year dropdown opens (Details)
    • The month/year dropdowns (CalendarMonthCaption) render with Radix DropdownMenu, which focuses the first list item on open, so the year dropdown (current year ±50 = 101 items) landed on the earliest year (1976) instead of the currently selected year (e.g. 2026); the month dropdown had the same latent issue
    • Tag the current month/year item with data-current="true" and add a pd-calendar__scroll-menu marker class to both dropdown content containers
    • Add a useEffect that, on open (after one requestAnimationFrame), focuses the [data-current] item with preventScroll and centers it within its scroll container by adjusting scrollTop only (no page scroll)
    • Add 2 unit tests locking the data-current marker contract for the current year and month
    • (WEB-1286) fix(DropdownMenu): make portaled overlay lists scrollable inside a modal Dialog/Popover (Details)
    • useStopWheelPropagation used useEffect to read ref.current with a stable dependency, so it ran once at mount and returned early because Radix's Content mounts its DOM lazily (Portal/Presence) — the wheel listener was never attached and never re-attached when the panel later opened
    • As a result, a DropdownMenu (e.g. Calendar's month/year selector) portaled to document.body inside a modal Dialog could not be scrolled: the Dialog's react-remove-scroll isolation preventDefaults any wheel whose target is outside its shard allowlist, and the (never-attached) stopPropagation guard could not stop the event from reaching that document-level listener
    • Rewrite useStopWheelPropagation to return a stable ref callback that binds/unbinds the wheel stopPropagation listener exactly when the node mounts/unmounts, so it works with lazily-mounted portaled content
    • Apply the ref-callback form at all consumers: DropdownMenu, ContextMenu, Menubar (Content + SubContent), Popover, Select
    • Add a unit test asserting the hook attaches on mount and detaches on null

v0.8.0

✨ Feature

  • Tree — suppress delayed color transition when switching light/dark theme (WEB-1207) (Details)
    • Tree item's transition-colors (for hover/selected background) also interpolated the whole CSS-variable flip on theme switch, so row background/border colors animated slowly during light↔dark toggles (the perceived "delayed color switch")
    • Add and export a shared suppressThemeTransition(apply) util: it self-injects a one-off <style> (.pd-theme-switching * { transition: none !important }, no globals.css import needed), adds the class before running apply, forces a synchronous style recalc so the variable flip commits with transitions disabled, then removes the class on the next animation frame — theme switching is instant while hover/selected transitions are unaffected
    • DesignProvider now wraps its data-theme / .dark flip with suppressThemeTransition; consumers that toggle theme outside DesignProvider (e.g. design-site's Docusaurus bridge, web4's darkMode.ts) can reuse the same util
    • Add unit tests for the util

🐛 Bug Fix

  • Dialog — use Grays/White background token so dark mode Modal is #292929 instead of near-black (WEB-1244) (Details)
  • Select — stop the dropdown's top item being clipped when it auto-scrolls to the selected option (WEB-1194) (Details)
    • Radix SelectPrimitive.Viewport carries an inline overflow: hidden auto and flex: 1, so it is the real scroll container (growing to the content's max-h-[268px]). The dropdown's vertical py-(--Spacing_8) used to sit on the viewport, i.e. inside the scroll region — when the list auto-scrolled to reveal a later selected option (e.g. display-language "中文(简体)", index 8) that top padding scrolled away and the first visible item ("Español") was clipped flush against the panel's top edge
    • Slim SELECT_VIEWPORT_TOKEN_CLASS down to just the semantic marker pd-select__viewport (drop the in-scroll py-(--Spacing_8) and the redundant overflow-y-auto that Radix's inline style already provides)
    • Move py-(--Spacing_8) onto the SelectContent frame instead; Radix forces display:flex; flex-direction:column on the popper content, so the padding lands on the non-scrolling flex box and stays fixed. Left the shared SELECT_CONTENT_TOKEN_CLASS untouched to avoid affecting the searchable Popover / MultiSelect that reuse it
    • Bump SELECT_DROPDOWN_MAX_HEIGHT from max-h-[268px] to max-h-[270px]: the content has a 1px border and Tailwind's preflight makes box-sizing: border-box, so max-height includes the border. At 268px the viewport was only 268 − 2(border) − 16(padding) = 250px (< 7 × 36 = 252px), leaving a ~2px clip on scroll; counting the 2px border gives a 252px viewport = exactly 7 × 36px so scrolling snaps to item boundaries
    • Add regression tests asserting the padding lives on the content frame (listbox) not the viewport, and that the listbox is max-h-[270px]
  • Toast — restore variant background by updating to renamed Accent tokens (WEB-1208) (Details)

v0.7.0

✨ Feature

  • MultiSelect — expose menuItemVariant='checkmark' and default to it (left-aligned checkmark) (WEB-1201) (Details)
    • Widen MultiSelect.menuItemVariant from 'checkmark-right' | 'checkbox' to 'checkmark' | 'checkmark-right' | 'checkbox', unlocking the left-aligned checkmark that SearchableOptionItem already supports
    • Change the default menuItemVariant from 'checkmark-right' to 'checkmark' (left-aligned check), matching the Figma default; callers that relied on the implicit right-aligned checkmark must now pass menuItemVariant="checkmark-right" explicitly
    • No rendering change: 'checkmark' renders the check on the left, and automatically moves to the right when the item has a leading icon (existing Figma indicator-right rule)
    • Add unit tests and design-site doc cases (en + zh) for the left checkmark variant

🐛 Bug Fix

  • Button — prevent hover/active visual effects on disabled buttons (WEB-1203) (Details)
    • Add disabled:hover: and disabled:active: class guards to all button variants (primary, secondary, tertiary, quaternary, destructive, destructive-outline, link-color, link-gray)
    • Ensures disabled buttons maintain consistent styling when clicked or hovered, preventing unintended border/background changes
    • Overrides any hover/active pseudo-class effects that could slip through when pointer-events-auto is enabled on disabled state
  • Dialog — align DialogTitle font-weight to Figma Modal (font-normal → font-semibold) (WEB-1193) (Details)
    • DialogTitle in ui/dialog used font-normal (400); the Figma DS Modal title (node 21231:6298, Size=Default) is H3/Emphasized: 18px / 24px / weight 600 (token --Font-Weight-Emphasized = Semibold)
    • Changed font-normal to font-semibold (600); font-size (18px) and line-height (--Line-Height-H3, 24px) unchanged
    • All dialogs using the structured title prop now render the title at weight 600, converging to the design
  • DropdownMenu — dim disabled item icon/arrow to Labels/Disabled and show not-allowed cursor (WEB-1190) (Details)
    • Add shared DROPDOWN_ICON_DISABLED_COLOR_CLASS using an ancestor [data-disabled] selector so the icon container follows Labels/Disabled when the item is disabled (the icon color was declared explicitly and did not inherit the item's disabled text color)
    • Apply it to DropdownMenuItem's leading icon (via getDropdownMenuItemIconColorClass), DropdownMenuSubTrigger's leading icon, and the trailing cascade arrow (DROPDOWN_SUB_ARROW_COLOR_CLASS)
    • Add data-disabled:pointer-events-auto + data-disabled:cursor-not-allowed to the shared item base, overriding the ui layer's pointer-events-none so disabled items show the not-allowed cursor (mirrors Button)

v0.6.8

🐛 Bug Fix

    • (WEB-1183) fix(DropdownMenu): cascading submenu indicator arrow color changed to Grays/Gray-4 (design spec labels-gray4) (Details)
    • the arrow icon (icon_list_chevron_right) was already switched in WEB-1129; this only changes its color, which was Labels/Primary before
    • the arrow color is decoupled from the leading-icon color via a new DROPDOWN_SUB_ARROW_COLOR_CLASS; leading icons stay Labels/Primary

v0.6.7

🐛 Bug Fix

    • (WEB-1181) feat(Toast): support action.actionProps to forward props to the underlying action button (Details)
    • actionProps is spread after the default variant/size (so callers can override them) but before the composed onClick, so the click + auto-close behavior can never be overridden
    • excludes children (carried by label) and onClick (composed by Toast); data-* index signature follows the Dialog okButtonProps convention

v0.6.6

🐛 Bug Fix

    • (WEB-1176) feat(AiButton): add Loading and Disabled (reserved) states (Details)
    • loading swaps the sparkle icon for a 16px Spinner, grays the label, keeps the white bg + gradient stroke, and blocks interaction via native disabled (aria-busy + not-allowed cursor)
    • disabled (reserved) fills the button gray (--Grays-Gray-2), drops the gradient stroke, and grays the sparkle icon

v0.6.5

🐛 Bug Fix

  • Select — move the menu item selection indicator to the right when the item has a leading icon (WEB-1174) (Details)
    • Generalized the "icon → indicator on the right" layout rule from the checkbox variant to also cover the checkmark variant in SearchableOptionItem, matching the Figma spec (Show Icon=True[icon][text][indicator])

v0.6.4

🐛 Bug Fix

    • (WEB-1128) fix(Toast): adjust Alert (with-title) variant top padding from 8px to 12px (Details)
    • only the top padding of the Alert form; Message form and left/right/bottom padding unchanged

v0.6.3

🐛 Bug Fix

  • DropdownMenu — polish cascading submenu styling (WEB-1129) (Details)
    • Add required listChevronRight key to IconRegistry (BREAKING: icon registries must now provide icon_list_chevron_right)
    • Cascading arrow uses ListChevronRightIcon, 20px container, Labels/Primary
    • SubContent defaults to sideOffset={-8} / alignOffset={-8} (overlaps parent menu + aligns first item to parent option)
    • SubTrigger open-state background overridden to Grays/Gray-1 (fixes dark background on open)
    • Leading icon container defaults to Labels/Primary (business-injected node can override)

v0.6.2

🐛 Bug Fix

v0.6.1

🐛 Bug Fix

v0.6.0

✨ Feature

  • Select / MultiSelectadd icon support, menuItemVariant and showSearchIcon toggle; SearchableOptionItem gains a variant prop ('checkmark-right' | 'checkmark' | 'checkbox') and an optional leading icon; MultiSelect gains menuItemVariant to switch dropdown item visual style, with new chip hover/active/focus-visible states; Select gains showSearchIcon (default true) and its checkbox variant now renders via the shared Checkbox component; Input border gains a hover state; Select item default text color changed from Labels-Secondary to Labels-Primary (WEB-1075, WEB-1076, WEB-1118)

🐛 Bug Fix

v0.5.0

✨ Feature

  • ImageVieweradd drag-to-pan when zoom > 1; mouse drag pans the image when zoomed in; cursor switches to grab / grabbing; boundary-clamped so image never fully leaves the viewport; auto-resets on image switch, zoom-out to default, or viewer close (WEB-1062)

🐛 Bug Fix

v0.4.6

🐛 Bug Fix

v0.4.5

🐛 Bug Fix

v0.4.4

🐛 Bug Fix

v0.4.3

🐛 Bug Fix

v0.4.2

🐛 Bug Fix

  • Tooltip / HoverCardFocus-open tightened to :focus-visible only (DES-134). Mouse focus and programmatic focus restoration (e.g. after a nested DropdownMenu closes) no longer reopen the overlay. Keyboard-visible focus continues to open as before.

v0.4.1

🐛 Bug Fix

  • ImageViewerDefault export downloads cross-origin images via blob (DES-133). The default export now fetches the image as a blob and uses a same-origin objectURL for downloads. This ensures cross-origin CDN images are properly downloaded instead of opening in a new tab. It falls back to the original URL if fetch fails or returns a non-2xx status, with delayed objectURL revocation.

v0.4.0

✨ Feature

  • TreeTree title overflow detection switched to on-demand measurement (DES-132). useTitleOverflow measures lazily on pointer-enter / focus instead of at mount, eliminating large-list layout thrashing. Contract change: long titles are wrapped as a Tooltip trigger only after first hover/focus, and the ResizeObserver is removed. ... (rest of the file)

v0.3.1

🐛 Bug Fix

v0.3.0

✨ Feature

v0.2.2

🐛 Bug Fix

  • Input — Input family PR-review findings, documented in Input allowClear + visual cases. useClearableInput now clears via the native value setter + a real input event (complete SyntheticEvent); InputSearch clear button gains the focus-visible ring; focus state is only managed when allowClear is set; adds a localizable clearButtonAriaLabel prop.

v0.2.1

🐛 Bug Fix

  • Input — Clear button (allowClear) gains a focus-visible keyboard focus ring (ring-1 + Labels/Primary, WCAG 2.4.7). See Input allowClear + visual cases.

v0.2.0

✨ Feature

🐛 Bug Fix

v0.1.0

Skeleton overhaul and assorted component refinements (largely superseded by the more detailed v0.2.0 entries above).

✨ Feature

  • Skeleton — refactored for multi-block rendering via items, added SkeletonGroup card-level shimmer.
  • Button — aligned icon-only states.
  • Dialog — support scrollable content.
  • Checkbox / RadioGroup / Menu / Slider — style and behavior refinements.
  • utils — added the merge-refs helper, exported from the package entry.

🐛 Bug Fix

  • Button — fixed disabled label styling.

v0.0.1

Initial release — publish design packages to the private registry. @plaud/design-tokens and @plaud/design shipped as built dist artifacts (0.0.1).

Pre-release

Changes made before the changeset-based release flow — not individually versioned.

✨ Feature

🐛 Bug Fix