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.10.0​

✨ Feature​

    • (WEB-1541) feat(ImageViewer): add resolveExportSrc to resolve the download address before the default export (Details)
    • Export previously had only two granularities — the built-in download, or a full onExport takeover; time-limited addresses (re-signing an expired presigned URL) had to take over and re-orchestrate the whole download, duplicating the blob / cache:'reload' / cross-origin fallback / filename inference nuance in the consumer
    • The new optional resolveExportSrc(image, index) answers only "which address"; returning an empty value or throwing falls back to image.src, matching the existing blob-fetch failure fallback, and the download itself stays inside the component
    • resolveExportSrc is inert when onExport is passed (a takeover skips it entirely), and there is no loading state on the export button while it resolves — the same wait window as today's fetch
    • Purely additive: omitting the prop leaves both the default download path and the onExport path unchanged; downloadImageBySrc stays module-private rather than becoming public API
    • Add four tests covering resolve-then-download, fallback on an empty resolve, fallback on a rejected resolve, and onExport bypassing the hook
    • (WEB-1501) feat(Segmented): add a new Segmented component (Details)
    • A mutually-exclusive value picker for switching between a small set of options (e.g. view modes, filter dimensions); unlike Tabs, it does not bind to content panels
    • Built directly on @radix-ui/react-toggle-group (type="single"), bypassing ui/toggle-group — its default toggleVariants visual conflicts with Segmented, and reusing it would first require adding unstyledVisual support to the toggle family, out of scope for this iteration
    • Declarative options API with bare-string shorthand ('a' ≡ { value: 'a', label: 'a' }), supporting text-only / icon+text / icon-only content shapes with icons vertically centered against the item and adjacent text; icon-only options require icon and aria-label at the type level
    • Supports both controlled (value) and uncontrolled (defaultValue) usage; in uncontrolled mode, whenever the current value isn't among options (including on first mount and when options arrive asynchronously or change), it falls back to the first option — plus block (stretch to fill, options split evenly) and whole-control / per-item disabled — when the whole control is disabled, a selected item keeps its white pill background and shadow but its text downgrades to Labels-Disabled gray, signaling "this is the current selection, but it can't be changed"
    • Clicking the already-selected option is intercepted as a no-op — Radix's type="single" would otherwise fire onValueChange with an empty string, which Segmented never allows
    • Selected state uses Foregrounds-White / Foregrounds-Black rather than semantic tokens like Backgrounds-Primary / Labels-Primary, so the selected pill stays pure white/black and never flips with the theme
    • Root explicitly declares role="radiogroup" — Radix hardcodes role="group" on the root while rendering role="radio" on items in type="single" mode, which is an incomplete ARIA pairing; radiogroup + radio matches the W3C-recommended pattern for a segmented control
    • Unselected state text uses Labels/Secondary, which meets WCAG 2.1 AA contrast in both light and dark themes

🐛 Bug Fix​

    • (WEB-1497) fix(Dialog,Sheet): scope outside-click dismissal to the overlay element, not the whole viewport (Details)
    • Radix's onPointerDownOutside only checks whether the pointerdown target is inside the Content subtree, ignoring z-index and visual stacking — any globally-mounted, interactive element floating above the modal (e.g. web4's ContactSupportWidget, downstream USE-88) was misjudged as "outside" and dismissed the dialog/sheet when clicked
    • Add shouldDismissOnOutsidePointerDown in a new overlay-dismiss.ts util: dismissal now only proceeds when the pointerdown target is contained by the overlay element itself; when non-modal (modal={false}, no overlay rendered), the legacy "close on any outside click" semantics are preserved
    • Dialog's closeOnOverlayClick keeps its false default (unchanged behavior); Sheet gains the same closeOnOverlayClick toggle but defaults to true to match its existing "close on outside click" behavior while narrowing the effective area to the overlay
    • Add unit tests covering: clicking a floating element above the dialog/sheet no longer dismisses it, and non-modal dialogs/sheets still dismiss on outside click
    • (WEB-1537) fix(Toast): resume auto-dismiss after the hovered toast stack becomes empty (Details)
    • When dismissal leaves no visible toast, clear the manager's paused state immediately instead of waiting for a mouseleave that cannot fire after the host unmounts
    • Use the manager's pause snapshot as the single source of truth for both timer suspension and stacked-toast expansion, preventing later toasts from inheriting a stale expanded state
    • Add regression tests for normal auto-dismiss, hover pause/resume, dismissing the last hovered toast, creating a toast during the exit-animation window, and restoring the collapsed stack layout
    • (WEB-1539) feat(Tree): allow automatic overflow-title tooltips to be configured safely (Details)
    • Add TreeDataNode.tooltip for per-node Tooltip behavior and presentation while keeping content, trigger structure, and controlled open state internal
    • Add 12 placement presets for four sides with center, start, and end alignment, resolved to Tooltip side / align with a fixed sideOffset of 8
    • Preserve the existing Tooltip defaults when the prop is omitted, while allowing left-sidebar trees to prefer right-side placement
    • Add unit and browser coverage, update the Tree DesignSpec and bilingual docs, and correct Tooltip's documented sideOffset default to 0

v0.9.1​

🐛 Bug Fix​

    • (WEB-1496) fix(Breadcrumb): ellipsis dropdown no longer locks page scroll (Details)
    • BreadcrumbEllipsisMenu rendered DropdownMenu without a modal prop, falling through to Radix's default modal={true}, which mounts react-remove-scroll and blocks page-level wheel / touchmove while the collapsed-items menu is open
    • Pass modal={false} explicitly — the ellipsis dropdown is a navigation aid, not a modal interaction, so it should never lock the rest of the page
    • Add a unit test asserting the render container carries no aria-hidden after opening the dropdown, since Radix's modal path calls hideOthers on sibling elements while the non-modal path does not
    • (WEB-1326) fix(Button): destructive label binds Foregrounds/White instead of Labels/Reverse (Details)
    • variant="destructive" rendered black label text on the red fill in dark mode: BUTTON_VARIANT_CLASS.destructive declared text-(--Labels-Reverse), which resolves to #FFFFFF in light mode but #000000 in dark mode, while the --Labels-Error fill underneath is mode-invariant
    • Change the destructive label to text-(--Foregrounds-White) (#FFFFFF in both modes). Verified against the Figma design system file (5ssRkvUdqpsRwwW59ooQCp): the Destructive label fill binds Foregrounds/White (VariableID:17:7) on Default (147:15406 / 147:15409 / 147:15412 / 20935:1635) and on Hover / Clicked (147:15424 / 147:15433)
    • primary keeps text-(--Labels-Reverse): Figma binds the Primary label to Labels/Reverse (VariableID:14:5, node 147:15379), and its fill inverts with the mode, so the label has to invert with it
    • Record Foregrounds/White and a per-variant text color token table in ButtonDesignSpec.md; the spec previously listed only Labels/Reverse, which is what let the two tokens get conflated during implementation
    • Add a unit test asserting the destructive button carries text-(--Foregrounds-White) and no longer text-(--Labels-Reverse)
    • (WEB-1312) fix(Calendar): derive year dropdown range from fromDate/toDate, default floor 1970 (Details)
    • The year dropdown range was hardcoded to currentYear ± 50, ignoring fromDate / toDate entirely — passing fromDate={today} still listed decades of past years that looked selectable, even though every day cell in those months was disabled once selected
    • CalendarMonthCaption now derives the year list floor from fromDate (falls back to 1970 when absent) and the ceiling from toDate (falls back to currentYear + 50 when absent), clipping the list rather than disabling entries; an inverted floor/ceiling degrades to a single-year list instead of throwing or rendering an empty menu
    • On the boundary year, the month dropdown disables months before fromDate's month or after toDate's month while keeping all 12 entries in place; selecting a year clamps the target month into range so it never lands on a fully-disabled month
    • Calendar now forwards fromDate / toDate to CalendarMonthCaption (previously only used to disable day cells)
    • Add unit tests covering the default 1970 floor, fromDate/toDate-derived list bounds, boundary-year month disabling, and year-selection month clamping
    • Extended the same derivation to the disabled matcher entry (what DatePicker's disabledDate prop maps to): disabled's { before } / { after } forms now converge the year/month dropdown bounds the same way fromDate / toDate do — since isDateDisabled treats multiple matchers as a union, the selectable range is their intersection, so the floor takes the max of fromDate and every { before } value, and the ceiling takes the min of toDate and every { after } value; non-monotonic matchers (bare Date, { from, to }, function predicates) don't yield a reliable bound and are intentionally ignored
    • The prev/next navigation arrows now disable themselves once the displayed month range reaches the resolved minDate/maxDate (compared at year-month granularity): the prev arrow disables when the first displayed month is at or before minDate's month, the next arrow disables when the last displayed month (accounting for numberOfMonths in RangePicker's dual-calendar mode) is at or after maxDate's month; both goToPrevMonth/goToNextMonth early-return when disabled to guard against keyboard/programmatic bypass of the disabled attribute
    • (WEB-1381) fix(Progress): forward value to the Radix Root so the bar reports aria-valuenow instead of staying indeterminate (Details)
    • value was destructured out of props and fed only to the Indicator's translateX transform, never to ProgressPrimitive.Root — Radix therefore treated every bar as valueless, emitting data-state="indeterminate" with no aria-valuenow / aria-valuetext, so screen readers announced a busy progressbar with no percentage while sighted users saw a correctly filled bar
    • Pass value to ProgressPrimitive.Root as well; the Indicator transform is untouched, so rendering is unchanged
    • Behavioural delta for consumers: aria-valuenow / aria-valuetext / data-value go from absent to present, and data-state flips from a constant indeterminate to loading / complete. Radix also validates value now and logs a console.error for a non-number / NaN / < 0 / > max; rendering in that case is unaffected because the visual still comes from our own transform
    • (WEB-1352) fix(Tabs): active trigger uses Body/Emphasized semibold (600) instead of medium (500) (Details)
    • The active tab label rendered one weight step too light: TABS_TRIGGER_BASE_CLASS declared data-[state=active]:font-medium (500) while the Figma Tabs node (522:53227) specifies Inter-SemiBold 600 / 14 / 20, i.e. the Body/Emphasized text token (--Font-Weight-Emphasized: "Semibold")
    • Change the active weight to font-semibold; font size and line height already read --Font-Size-Body / --Line-Height-Body and are unchanged
    • Correct the stale line height (22px → 20px) and active weight in TabsDesignSpec.md and the docs-site typography table (en + zh); the implementation always used the token, only the docs were wrong
    • Add a unit test asserting the trigger carries data-[state=active]:font-semibold and no longer data-[state=active]:font-medium
    • (WEB-1350) fix(Toast): wrap unbreakable long words instead of overflowing the line box (Details)
    • Toast text containing a token with no natural break opportunity (typically a file name such as 320X240(1)_rmv8_904kbps_15fps_rma_44100hz_96.5kbps) overflowed its line box and visually collided with the surrounding text — the default overflow-wrap: normal only breaks at whitespace / CJK boundaries, and an underscore is not a break opportunity
    • Add wrap-anywhere to TOAST_TITLE_CLASS and TOAST_DESCRIPTION_CLASS; break-words is insufficient because overflow-wrap: break-word does not contribute break opportunities to min-content sizing, so under the text containers' min-w-0 the word still overflows, and break-all would split ordinary English words mid-word
    • Add a unit test rendering a long file name as title / description and asserting both text nodes carry wrap-anywhere
    • (WEB-1323) fix(Tree): keep the drag source outline unbroken behind row actions (Details)
    • The dragged source row draws a 1px inset-ring (box-shadow on the row); the hover-only actions container is absolute inset-y-0 bg-inherit, an opaque child that paints above the row's inset shadow, so its full-height fill covered the ring's top / bottom edges in the button region — the outline looked clipped exactly under the + / ⋯ buttons while dragging
    • Inset the actions container by 1px top / bottom only while dragging via group-data-[dragging]/pd-tree-item:inset-y-px, and emit data-dragging on the source row as the group switch; normal (non-drag) state keeps the full-height mask unchanged
    • Add a unit test asserting the drag source row carries data-dragging and the actions wrapper gets the inset variant

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-full → w-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 / MultiSelect — add 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​

  • ImageViewer — add 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 / HoverCard — Focus-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​

  • ImageViewer — Default 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​

  • Tree — Tree 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​