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
resolveExportSrcto resolve the download address before the default export (Details) - Export previously had only two granularities — the built-in download, or a full
onExporttakeover; 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 toimage.src, matching the existing blob-fetch failure fallback, and the download itself stays inside the component resolveExportSrcis inert whenonExportis 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'sfetch- Purely additive: omitting the prop leaves both the default download path and the
onExportpath unchanged;downloadImageBySrcstays 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
onExportbypassing the hook
- (WEB-1541) feat(ImageViewer): add
-
- (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"), bypassingui/toggle-group— its defaulttoggleVariantsvisual conflicts with Segmented, and reusing it would first require addingunstyledVisualsupport to the toggle family, out of scope for this iteration - Declarative
optionsAPI 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 requireiconandaria-labelat the type level - Supports both controlled (
value) and uncontrolled (defaultValue) usage; in uncontrolled mode, whenever the current value isn't amongoptions(including on first mount and whenoptionsarrive asynchronously or change), it falls back to the first option — plusblock(stretch to fill, options split evenly) and whole-control / per-itemdisabled— when the whole control is disabled, a selected item keeps its white pill background and shadow but its text downgrades toLabels-Disabledgray, 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 fireonValueChangewith an empty string, which Segmented never allows - Selected state uses
Foregrounds-White/Foregrounds-Blackrather than semantic tokens likeBackgrounds-Primary/Labels-Primary, so the selected pill stays pure white/black and never flips with the theme - Root explicitly declares
role="radiogroup"— Radix hardcodesrole="group"on the root while renderingrole="radio"on items intype="single"mode, which is an incomplete ARIA pairing;radiogroup+radiomatches 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
onPointerDownOutsideonly 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'sContactSupportWidget, downstream USE-88) was misjudged as "outside" and dismissed the dialog/sheet when clicked - Add
shouldDismissOnOutsidePointerDownin a newoverlay-dismiss.tsutil: 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'scloseOnOverlayClickkeeps itsfalsedefault (unchanged behavior);Sheetgains the samecloseOnOverlayClicktoggle but defaults totrueto 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
mouseleavethat 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.tooltipfor per-node Tooltip behavior and presentation while keeping content, trigger structure, and controlled open state internal - Add 12
placementpresets for four sides with center, start, and end alignment, resolved to Tooltipside/alignwith a fixedsideOffsetof8 - 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
sideOffsetdefault to0
v0.9.1
🐛 Bug Fix
-
- (WEB-1496) fix(Breadcrumb): ellipsis dropdown no longer locks page scroll (Details)
BreadcrumbEllipsisMenurenderedDropdownMenuwithout amodalprop, falling through to Radix's defaultmodal={true}, which mountsreact-remove-scrolland blocks page-levelwheel/touchmovewhile 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-hiddenafter opening the dropdown, since Radix's modal path callshideOtherson 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.destructivedeclaredtext-(--Labels-Reverse), which resolves to#FFFFFFin light mode but#000000in dark mode, while the--Labels-Errorfill underneath is mode-invariant- Change the destructive label to
text-(--Foregrounds-White)(#FFFFFFin both modes). Verified against the Figma design system file (5ssRkvUdqpsRwwW59ooQCp): the Destructive label fill bindsForegrounds/White(VariableID:17:7) on Default (147:15406/147:15409/147:15412/20935:1635) and on Hover / Clicked (147:15424/147:15433) primarykeepstext-(--Labels-Reverse): Figma binds the Primary label toLabels/Reverse(VariableID:14:5, node147:15379), and its fill inverts with the mode, so the label has to invert with it- Record
Foregrounds/Whiteand a per-variant text color token table inButtonDesignSpec.md; the spec previously listed onlyLabels/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 longertext-(--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, ignoringfromDate/toDateentirely — passingfromDate={today}still listed decades of past years that looked selectable, even though every day cell in those months was disabled once selected CalendarMonthCaptionnow derives the year list floor fromfromDate(falls back to1970when absent) and the ceiling fromtoDate(falls back tocurrentYear + 50when 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 aftertoDate'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 Calendarnow forwardsfromDate/toDatetoCalendarMonthCaption(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
disabledmatcher entry (whatDatePicker'sdisabledDateprop maps to):disabled's{ before }/{ after }forms now converge the year/month dropdown bounds the same wayfromDate/toDatedo — sinceisDateDisabledtreats multiple matchers as a union, the selectable range is their intersection, so the floor takes the max offromDateand every{ before }value, and the ceiling takes the min oftoDateand every{ after }value; non-monotonic matchers (bareDate,{ 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 beforeminDate's month, the next arrow disables when the last displayed month (accounting fornumberOfMonthsinRangePicker's dual-calendar mode) is at or aftermaxDate's month; bothgoToPrevMonth/goToNextMonthearly-return when disabled to guard against keyboard/programmatic bypass of thedisabledattribute
-
- (WEB-1381) fix(Progress): forward
valueto the Radix Root so the bar reportsaria-valuenowinstead of staying indeterminate (Details) valuewas destructured out of props and fed only to the Indicator'stranslateXtransform, never toProgressPrimitive.Root— Radix therefore treated every bar as valueless, emittingdata-state="indeterminate"with noaria-valuenow/aria-valuetext, so screen readers announced a busy progressbar with no percentage while sighted users saw a correctly filled bar- Pass
valuetoProgressPrimitive.Rootas well; the Indicator transform is untouched, so rendering is unchanged - Behavioural delta for consumers:
aria-valuenow/aria-valuetext/data-valuego from absent to present, anddata-stateflips from a constantindeterminatetoloading/complete. Radix also validatesvaluenow and logs aconsole.errorfor a non-number / NaN /< 0/> max; rendering in that case is unaffected because the visual still comes from our own transform
- (WEB-1381) fix(Progress): forward
-
- (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_CLASSdeclareddata-[state=active]:font-medium(500) while the Figma Tabs node (522:53227) specifiesInter-SemiBold600 / 14 / 20, i.e. theBody/Emphasizedtext token (--Font-Weight-Emphasized: "Semibold") - Change the active weight to
font-semibold; font size and line height already read--Font-Size-Body/--Line-Height-Bodyand are unchanged - Correct the stale line height (22px → 20px) and active weight in
TabsDesignSpec.mdand 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-semiboldand no longerdata-[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 defaultoverflow-wrap: normalonly breaks at whitespace / CJK boundaries, and an underscore is not a break opportunity - Add
wrap-anywheretoTOAST_TITLE_CLASSandTOAST_DESCRIPTION_CLASS;break-wordsis insufficient becauseoverflow-wrap: break-worddoes not contribute break opportunities to min-content sizing, so under the text containers'min-w-0the word still overflows, andbreak-allwould 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 isabsolute 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 emitdata-draggingon 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-draggingand the actions wrapper gets the inset variant
v0.9.0
✨ Feature
-
- (WEB-1277) feat(Select): add a ghost trigger variant (Details)
Selectgains avariantprop ('outline' | 'ghost'), defaulting to'outline'— existing usage is unchanged. Consumers opt into ghost via<Select variant="ghost" />; the internalSelectTriggersubcomponent consumes it but stays internal and is not exported from the package barrelghostis a borderless, content-width, right-aligned trigger with a trailing sort (up/down) arrow and a gray hover/open background, for inline / toolbar select scenariosui/select.tsxSelectTriggergains an optionaliconprop to override the default chevron (internal capability passthrough)SortIconneutral state (sortDirection === null) now renders withcurrentColorso 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/TertiarytoLabels/Secondaryon theGrays/Gray-1background, 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/Primaryfocus 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 notdisplay; the underlyingui/select.tsxprimitive is a block-levelflexcontainer, sowidth: autoalone 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-flexto the ghost base class alongsidew-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.menuItemVarianttooptionItemVariant(Details) - Breaking:
MultiSelect.menuItemVariantis renamed tooptionItemVariant(same meaning, same values, same default'checkmark') — a dropdown row is aSelectOptionItem, not a "menu item", so the prop is renamed to match the domain vocabulary and to share one name acrossSelectandMultiSelect SelectContent(the combinatorial subcomponent) gains anoptionItemVariant?: 'checkmark' | 'checkmark-right'prop, defaulting to'checkmark-right'(zero regression), propagated via context to everySelectItemrendered inside itSelectgains a matchingoptionItemVariantprop, threaded through the declarative non-searchableoptionsbranch to theSelectContentit renders- Deliberately not placed on
SelectItemitself: 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 checkmarkmoves 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/SelectItemand the declarative non-searchable branch only —searchablesingle Select already covers left/right checkmark viaSearchableOptionItem
- (WEB-1310) feat(Select)!: support a left-checkmark option item variant, and rename
🐛 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 RadixDropdownMenu, 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 apd-calendar__scroll-menumarker class to both dropdown content containers - Add a
useEffectthat, on open (after onerequestAnimationFrame), focuses the[data-current]item withpreventScrolland centers it within its scroll container by adjustingscrollToponly (no page scroll) - Add 2 unit tests locking the
data-currentmarker contract for the current year and month
-
- (WEB-1286) fix(DropdownMenu): make portaled overlay lists scrollable inside a modal Dialog/Popover (Details)
useStopWheelPropagationuseduseEffectto readref.currentwith a stable dependency, so it ran once at mount and returned early because Radix'sContentmounts 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 todocument.bodyinside a modal Dialog could not be scrolled: the Dialog'sreact-remove-scrollisolationpreventDefaults any wheel whose target is outside its shard allowlist, and the (never-attached)stopPropagationguard could not stop the event from reaching that document-level listener - Rewrite
useStopWheelPropagationto return a stable ref callback that binds/unbinds thewheelstopPropagationlistener 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 runningapply, 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 DesignProvidernow wraps itsdata-theme/.darkflip withsuppressThemeTransition; consumers that toggle theme outsideDesignProvider(e.g. design-site's Docusaurus bridge, web4'sdarkMode.ts) can reuse the same util- Add unit tests for the util
- Tree item's
🐛 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.Viewportcarries an inlineoverflow: hidden autoandflex: 1, so it is the real scroll container (growing to the content'smax-h-[268px]). The dropdown's verticalpy-(--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_CLASSdown to just the semantic markerpd-select__viewport(drop the in-scrollpy-(--Spacing_8)and the redundantoverflow-y-autothat Radix's inline style already provides) - Move
py-(--Spacing_8)onto theSelectContentframe instead; Radix forcesdisplay:flex; flex-direction:columnon the popper content, so the padding lands on the non-scrolling flex box and stays fixed. Left the sharedSELECT_CONTENT_TOKEN_CLASSuntouched to avoid affecting the searchable Popover / MultiSelect that reuse it - Bump
SELECT_DROPDOWN_MAX_HEIGHTfrommax-h-[268px]tomax-h-[270px]: the content has a 1px border and Tailwind's preflight makesbox-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]
- Radix
- 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.menuItemVariantfrom'checkmark-right' | 'checkbox'to'checkmark' | 'checkmark-right' | 'checkbox', unlocking the left-aligned checkmark thatSearchableOptionItemalready supports - Change the default
menuItemVariantfrom'checkmark-right'to'checkmark'(left-aligned check), matching the Figma default; callers that relied on the implicit right-aligned checkmark must now passmenuItemVariant="checkmark-right"explicitly - No rendering change:
'checkmark'renders the check on the left, and automatically moves to the right when the item has a leadingicon(existing Figma indicator-right rule) - Add unit tests and design-site doc cases (en + zh) for the left checkmark variant
- Widen
🐛 Bug Fix
- Button — prevent hover/active visual effects on disabled buttons (WEB-1203) (Details)
- Add
disabled:hover:anddisabled: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-autois enabled on disabled state
- Add
- Dialog — align DialogTitle font-weight to Figma Modal (font-normal → font-semibold) (WEB-1193) (Details)
DialogTitleinui/dialogusedfont-normal(400); the Figma DS Modal title (node21231:6298, Size=Default) is H3/Emphasized: 18px / 24px / weight 600 (token--Font-Weight-Emphasized= Semibold)- Changed
font-normaltofont-semibold(600); font-size (18px) and line-height (--Line-Height-H3, 24px) unchanged - All dialogs using the structured
titleprop 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_CLASSusing an ancestor[data-disabled]selector so the icon container followsLabels/Disabledwhen 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 (viagetDropdownMenuItemIconColorClass),DropdownMenuSubTrigger's leading icon, and the trailing cascade arrow (DROPDOWN_SUB_ARROW_COLOR_CLASS) - Add
data-disabled:pointer-events-auto+data-disabled:cursor-not-allowedto the shared item base, overriding the ui layer'spointer-events-noneso disabled items show the not-allowed cursor (mirrors Button)
- Add shared
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
checkboxvariant to also cover thecheckmarkvariant inSearchableOptionItem, matching the Figma spec (Show Icon=True→[icon][text][indicator])
- Generalized the "icon → indicator on the right" layout rule from the
v0.6.4
🐛 Bug Fix
v0.6.3
🐛 Bug Fix
- DropdownMenu — polish cascading submenu styling (WEB-1129) (Details)
- Add required
listChevronRightkey toIconRegistry(BREAKING: icon registries must now provideicon_list_chevron_right) - Cascading arrow uses
ListChevronRightIcon, 20px container, Labels/Primary SubContentdefaults tosideOffset={-8}/alignOffset={-8}(overlaps parent menu + aligns first item to parent option)SubTriggeropen-state background overridden to Grays/Gray-1 (fixes dark background on open)- Leading icon container defaults to Labels/Primary (business-injected node can override)
- Add required
v0.6.2
🐛 Bug Fix
- Dialog — overlay click no longer closes the dialog by default; added
closeOnOverlayClick?: booleantoDialogProps/DialogOpenOptions(defaultfalse); Escape key and close button behavior unchanged (WEB-1125) - Tooltip — hover delay defaults to 500ms;
delayDurationdefault changed from0to500; hide-on-leave behavior unchanged; removedTree's local 300ms workaround (WEB-1124)
v0.6.1
🐛 Bug Fix
- Button — update font-weight to Medium per Figma spec;
Primary/Secondary/Tertiary/Quaternary/Destructive/Destructive Outlinetext weight changed from Regular(400) to Emphasized/Medium(500);Link Color/Link Grayremain Regular(400) (WEB-1119)
v0.6.0
✨ Feature
- Select / MultiSelect — add icon support, menuItemVariant and showSearchIcon toggle;
SearchableOptionItemgains avariantprop ('checkmark-right' | 'checkmark' | 'checkbox') and an optional leadingicon;MultiSelectgainsmenuItemVariantto switch dropdown item visual style, with new chip hover/active/focus-visible states;SelectgainsshowSearchIcon(defaulttrue) and itscheckboxvariant now renders via the sharedCheckboxcomponent;Inputborder gains a hover state; Select item default text color changed fromLabels-SecondarytoLabels-Primary(WEB-1075, WEB-1076, WEB-1118)
🐛 Bug Fix
- DropdownMenu — align group label style with Select;
font-semibold→font-normal,text-(--Labels-Secondary)→text-(--Labels-Tertiary),py-(--Spacing_8)→pt-(--Spacing_8)(removes 6px bottom padding leak from shadcn defaultpy-1.5) (WEB-1116) - Select — fix conditional rendering and add aria-hidden to decorative icons; replace
&&JSX conditional rendering with explicit ternaries inMultiSelect,SearchableOptionItemandSelect; addaria-hidden="true"to decorative icon spans (WEB-1118)
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
- Toast — correct Message form alignment for top-center position; fix left-offset of
w-fitMessage toasts by usingtranslateX(calc(210px - 50%))instead ofleft: 50%which shrank available width (WEB-1105)
v0.4.6
🐛 Bug Fix
- ui — migrate CSS variable class syntax from bracket to parenthesis for Tailwind v4 compatibility; all
xxx-[--radix-yyy]patterns replaced withxxx-(--radix-yyy)acrosspopover,hover-card,tooltip,navigation-menu,dropdown-menu,context-menu,menubar,select; fixes silent failures in v4 consumers (WEB-1108) - ui / Popover / DropdownMenu / ContextMenu / Select — fix wheel scroll blocked inside Dialog by react-remove-scroll;
useStopWheelPropagationhook attaches a nativewheellistener withstopPropagationto portaled overlay content nodes (WEB-1109) - Dialog — content area uses
flex-1to fill space between header and footer; footer stays pinned at bottom when content overflows
v0.4.5
🐛 Bug Fix
v0.4.4
🐛 Bug Fix
- Breadcrumb — Ellipsis menu now passes through
itemRender; newplacementfield added to distinguish dropdown rendering from inline (WEB-1044). Collapsed items previously bypasseditemRenderand fell back to a hardcoded<a href>, causing full-page navigation. They now correctly callitemRenderwithplacement: 'dropdown'. - Calendar — Selected date text invisible in dark mode fixed (WEB-1041). Text color changed from
--Foregrounds-White(always white) to--Labels-Reverse(white in light, black in dark), which always contrasts against the--Labels-Primaryselected background.
v0.4.3
🐛 Bug Fix
- Dialog — DialogTitle now reserves right-side spacing (
--Spacing_40) when close button is visible. WhenshowCloseistrue(the default), long titles are no longer obscured by the absolutely-positioned close button.
v0.4.2
🐛 Bug Fix
- Tooltip / HoverCard — Focus-open tightened to
:focus-visibleonly (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).
useTitleOverflowmeasures 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 theResizeObserveris removed. ... (rest of the file)
v0.3.1
🐛 Bug Fix
- ImageViewer — Toolbar optimizations for single image + accessibility fixes (DES-130). Hide prev/next navigation for a single image, restore
not-allowedcursor on disabled icons, addrole="status"/aria-live="polite"zoom announcements, and support keyboard zoom (+/=/-).
v0.3.0
✨ Feature
- Overlay — Composable triggers for Tooltip / DropdownMenu / Popover / HoverCard (DES-128). The four children-as-trigger overlays become
forwardRefand forward events / aria relationships / conditional id to the inner Trigger viaextractForwardableTriggerProps, enabling nesting like<Tooltip><DropdownMenu>…</DropdownMenu></Tooltip>.
v0.2.2
🐛 Bug Fix
- Input — Input family PR-review findings, documented in Input allowClear + visual cases.
useClearableInputnow clears via the native value setter + a realinputevent (completeSyntheticEvent);InputSearchclear button gains thefocus-visiblering; focus state is only managed whenallowClearis set; adds a localizableclearButtonAriaLabelprop.
v0.2.1
🐛 Bug Fix
- Input — Clear button (
allowClear) gains afocus-visiblekeyboard focus ring (ring-1+Labels/Primary, WCAG 2.4.7). See Input allowClear + visual cases.
v0.2.0
✨ Feature
- Input — new
allowClearone-click clear capability. - Skeleton — refactored to support multi-block rendering via
items, plus radius/color tokens and card-levelSkeletonGroupshimmer. - useFilePicker — new primitive, wired into ChatInput. (No design-site detail page.)
- Accessibility — a11y automation: vitest-axe unit layer + Playwright integration layer + 49-component audit.
- Button — icon-only interaction states aligned.
- Dialog — scrollable content with flex layout.
- Toast — per-item
closeButtonprop.
🐛 Bug Fix
- Button / Checkbox — disabled label color fixed via
:has(). - Typography — tokens aligned to the Figma design system.
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, addedSkeletonGroupcard-level shimmer. - Button — aligned icon-only states.
- Dialog — support scrollable content.
- Checkbox / RadioGroup / Menu / Slider — style and behavior refinements.
- utils — added the
merge-refshelper, 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
- 2026-06-12 — Radio & Checkbox hit area expanded to 20×20 + visual cases (DES-96 / DES-97)
- 2026-06-12 — Breadcrumb title truncation for long text (DES-93)
- 2026-06-12 — Tree drag-and-drop visuals & row layout aligned to new Figma (DES-89)
- 2026-06-08 — Tree item spacing 4px + title/action layout refactor + gradient mask
- 2026-06-03 — RadioGroup & Checkbox add
labelprop to expand click area (DES-68) - 2026-06-03 — Dialog
okButtonProps/cancelButtonProps+ content-only mode + AlertDialog removed - 2026-06-02 — Sync 2026/06/01 component updates (DES-40)
- 2026-06-02 — Dialog adds Size=Emphasized variant (DES-37)
🐛 Bug Fix
- 2026-06-10 — Toast title font weight changed to Emphasized (Semibold)
- 2026-06-03 — Switch Toggle-On color updated to blue #1573D1 (light mode) (DES-69)
- 2026-06-03 — Typography font-size & line-height tokens updated (DES-67)
- 2026-06-03 — Typography Font-Weight-Emphasized token updated (DES-66)
- 2026-06-03 — PageControls Dot color tokens updated (DES-65)
- 2026-05-06 — Second round of fixes (designer feedback) (WEB-377)
- 2026-04-28 — First round of batch style fixes (WEB-377)
Related
- Linear project: Plaud Design