Skip to content

v0.1.9

Latest

Choose a tag to compare

@cixzhang cixzhang released this 27 Jul 19:22
c9fe437

v0.1.9 is an all-patch release — no breaking changes, so no codemods required.

New Features

  • Avatar: add a tooltip?: string | boolean prop for a name-on-hover tooltip. Omitting it (or true) shows the avatar's name on hover and keyboard focus; a string shows that text instead (no need to wrap in Tooltip); false disables it. Avatar owns the tooltip via the existing Tooltip hook, so there's no extra wrapper DOM. Because this adds a default tooltip to every existing named Avatar, set tooltip={false} when you supply your own Tooltip/HoverCard overlay. The root aria-label (alt || name) is unchanged; the default name tooltip is visual-only (no aria-describedby double-announce), while a custom string tooltip is exposed as a description. Decorative avatars (no name/alt) get no tooltip. (#4164)
  • BreadcrumbItem gains a menu prop that turns a crumb into a menu trigger for switching between sibling destinations. It accepts the same item API as DropdownMenu/MoreMenu/ContextMenu (a DropdownMenuOption[] array or composed item children), so existing menu-item definitions drop into a breadcrumb with no rewrite. The item components are also re-exported under Breadcrumb* aliases.
  • Calendar: make the today/selected day-cell ring precisely themeable. The day cell now reflects a compound marker state (today-only / today-in-range) that maps 1:1 to the treatment actually drawn, so defineTheme({components: {'calendar-day': {'marker:today-only': {...}}}}) targets exactly those states without over-matching or needing a :not() exclusion. Default rendering is unchanged.
  • Calendar: add a dedicated astryx-calendar-nav theme target for the prev/next month-nav buttons, so consumers can theme the nav controls (color, radius, per-direction, disabled edge) without reaching every Button via the global astryx-button handle. Reflects nav (prev/next) and the disabled state as data attributes.
  • ChatComposer: make custom inputs first-class. useChatComposerContext() and its types are now public, so any input in the input slot can read value/onChange/onSubmit/canSend/placeholder/isDisabled and drive the shell's send button. Inputs can register a focus control on inputControlRef so click-to-focus works for any input shape (not just contenteditable/textarea); the shell keeps a DOM-query fallback for uninstrumented inputs.
  • ChatComposerInput: add an onKeyDown seam so consumers can host platform- or app-specific key handling — e.g. preventDefault() Enter to insert a newline on a touch keyboard, or submit on Cmd/Ctrl+Enter. Enter also no longer submits mid-IME-composition.
  • CommandPalette: add a dedicated astryx-command-palette-group-heading theme target on the group heading, so consumers can theme just the heading (e.g. its padding or typography) via defineTheme instead of a fragile structural selector. The group root keeps its own astryx-command-palette-group target.
  • DateInput gains a format prop for the committed date value, reusing Timestamp's format vocabulary so the same literal renders the same date shape in both components. Named values are date_long (the default, "March 21, 2026"), date ("Mar 21, 2026"), date_weekday ("Wed, Mar 21, 2026"), and system_date ("2026-03-21"); a (value) => string function is also accepted for custom output. The date_long default is byte-identical to DateInput's previous long-month rendering, so existing usage is unchanged. This also extends Timestamp with two new shared members, date_long and date_weekday, giving the two components full value parity on the date-only formats. Formatting applies only to the committed value, never to text being typed.
  • Add an elevation prop to configurable surfaces — Card, ClickableCard, SelectableCard, Button, IconButton, ButtonGroup, and Banner take the full 'none' | 'low' | 'med' | 'high' scale; ChatComposer takes 'none' | 'low'. Defaults preserve today's appearance (none everywhere except ChatComposer's low), so nothing changes unless you opt in. (#4146)
  • OverflowList: add maxVisibleItems to cap the number of visible items (the ceiling partner to minVisibleItems) and maxRows for bounded multi-row wrapping — items wrap onto up to N rows, then collapse into the overflow indicator. Both props are optional and default to off, so single-line behavior is unchanged. See #4176.
  • Add useTableRowStatus, a plugin that prepends a narrow column
    signaling per-row status.
  • Thumbnail: add showRemoveOn prop — 'hover' (default) reveals the remove button on hover or keyboard focus and keeps it visible on touch; 'always' shows it at rest.
  • Table tree: add an optional expand-all/collapse-all header control (#4142)
    useTableTreeState now returns an aggregate isAllExpanded state (true / false / 'indeterminate') and threads expandAll / collapseAll into treeConfig. useTableTreeData gains a hasExpandAllControl prop: when set, it renders an expand-all/collapse-all toggle in the tree column header, wired to that state, so consumers no longer need to hand-roll external buttons. Flat data stays a full no-op. This is the first affordance folded in from useTableRowExpansion as part of converging the two tree plugins.
  • TreeList: add a dedicated astryx-tree-list-chevron theme target for the expand/collapse toggle, so consumers can theme the chevron (color, per open/closed state) via defineTheme instead of reaching it through the functional [data-tree-toggle] attribute. Reflects the open/closed state as a data-state attribute (expanded/collapsed); the functional data-tree-toggle hook is unchanged.
  • TreeList: add a stable astryx-tree-list-guide theme target on the hierarchy guide (connector) line elements, so consumers can recolor or hide the guides through defineTheme (e.g. backgroundColor, or display: 'none' to hide them) instead of hiding the built-in connectors and reimplementing them with unlayered CSS.
  • TreeList: add a dedicated astryx-tree-list-item-label theme target on the item's label text, so consumers can theme just the label (e.g. bold the selected item's label) via defineTheme instead of a fragile button:not([data-tree-toggle]) > span structural selector. Reflects the row's selected state as a data-selected attribute on the label.
  • CLI: full API coverage for the build, swizzle, layout, and validate commands — each is now scriptable through the ./api barrel with the CLI as a thin parse → API call → render wrapper. build gains --json output. Behavior is unchanged for existing command usage. (#4302)

Fixes

  • AvatarStatusDot: pair each variant with a distinct built-in shape — success stays a filled dot, neutral renders as a ring, error gets a minus bar — so status no longer relies on colour alone (WCAG 2.1 SC 1.4.1, #4143). A rendered icon replaces the shape glyph at sizes where icons fit; themes can target the new stable astryx-avatar-status-dot-glyph class and its data-shape attribute — a stroked inline <svg> painted from the dot's currentColor.
  • Button: link-rendered buttons (href) now expose aria-busy while loading, matching the <button> branch. Previously an interruptible loading link showed the spinner and announced "Loading" but carried no machine-readable busy state.
  • Calendar only marks in-month date cells as today, preventing duplicate today indicators in multi-month views.
  • Carousel: slides now expose APG slide semantics (role=group, aria-roledescription="slide", "Slide N of M" labels) instead of anonymous divs.
  • Honor prefers-reduced-motion in ChatToolCalls (chevron rotation, expand/collapse), ChatLayoutScrollButton (pill show/hide), and ChatDictationButton (equalizer bars).
  • Chat/useChatStreamScroll: the scroll-follow spring now respects prefers-reduced-motion — locked following, scrollToBottom(), and lock() fall back to the existing instant jump, so the transcript still tracks the bottom without animated travel. Follow-up promised in #3800.
  • Chat: the composer drawer toggle now references its disclosed content via aria-controls, so assistive tech can navigate from the toggle to the drawer.
  • ChatSendButton now forwards className, style, and pass-through attributes (data-*, aria-*, and other rest props) to the rendered button. Previously these were silently dropped. (#4190)
  • Chat: tool-call error details are now exposed to screen readers and keyboard users instead of living only in a hover-only title attribute.
  • CheckboxInput: the indeterminate mark now uses the --radius-full token instead of a hardcoded radius, for token consistency. No visual change.
  • CheckboxList: items with rich (non-string) labels can now provide an accessibleLabel so their checkbox no longer announces as the literal "Checkbox".
  • CodeBlock: collapsed code regions are now inert, so keyboard focus can no longer land on the invisible scroll container while collapsed.
  • CommandPalette: forward BaseProps pass-through attributes (className, style, xstyle, data-, aria-) to the underlying Dialog. Previously these were silently dropped.
  • CommandPalette: the search input (role=combobox) now has an accessible name by default, from the new label prop or the visible placeholder. Previously screen readers announced a nameless combobox.
  • DateTimeInput: ArrowDown (and Alt+ArrowDown) in the date field now opens the calendar popover from the keyboard, matching DateInput and the advertised combobox pattern.
  • Dialog: modals are now automatically labelled by their DialogHeader title via aria-labelledby, matching AlertDialog. Unnamed open dialogs warn in development.
  • Dialog: the entry animation is disabled under prefers-reduced-motion, matching the Layer animation guards.
  • Fix Divider rest-prop spread order so consumer-passed HTML attributes cannot overwrite the component's role="separator" or aria-orientation.
  • FieldLabel now forwards className, style, xstyle, and pass-through attributes (data-, aria-, event handlers) to the rendered element. Previously these were accepted by the type but silently dropped.
  • Field/FieldStatus: status and error messages are now announced through persistent live regions, so they are reliably read by screen readers regardless of when they appear.
  • FileInput: the trigger's accessible name now includes the selected filenames, so screen-reader users can review what is attached when refocusing the control.
  • Prevent PowerSearch edit popover from closing when selecting multi-select options via Enter (#4245)
  • Selector/MultiSelector: PageUp/PageDown now jump the active option to the first/last match while searching, complementing Home/End which stay on text-caret movement.
  • HoverCard: popups now expose a named dialog when the new label prop is set, and honest group semantics otherwise. Previously every hover card announced as an unnamed dialog.
  • Item: aria-selected is now emitted only when the item's role permits it (option, tab, treeitem, grid cells), removing invalid ARIA from plain list items.
  • MobileNav: the toggle button now exposes aria-expanded and references the nav drawer via aria-controls, so screen-reader users can tell whether the drawer is open and what the button controls. (#3721)
  • MultiSelector: searching within the options popover now announces the number of matching results ("3 results" / "No results found") to screen readers, mirroring Selector and Typeahead. Previously filtering happened silently.
  • SideNav/TopNav: collapsed heading popovers no longer wrap their menus in a modal dialog, and the heading button is no longer an invalid child of the menu.
  • Slider now clamps controlled values to its minimum and maximum before positioning the thumb and exposing ARIA values.
  • Slider: required sliders now convey the required state to screen readers through the thumb's accessible description (aria-required is invalid on the slider role).
  • Make the CLI's .mjs sources fully strict-typecheckable (checkJs + JSDoc)
    Annotated the entire CLI package so tsconfig.strict.json (full strict checkJs over src, bin, scripts, docs, and the emitted templates) reports zero errors — down from 1717. Fixes are JSDoc-only: no runtime logic changed, .mjs stays .mjs. Strict checking also surfaced and corrected several type-contract drifts: the upgrade.run response type (declared a depsUpdated field the command never emits, and omitted the real integrations/filesChanged/transformsApplied/errors), registered the emitted theme.list/theme.add/layout.* response types in the --json envelope union, and added category? to ReferenceSection in core's docs types (reference docs already emit it).
  • Table: the selection plugin accepts getRowLabel so row checkboxes announce which row they select, instead of an undifferentiated "Select row".
  • Text: an explicit size now overrides the font-size of a themed type. The size class lived in a lower cascade layer (astryx-base) than a theme's per-type font-size rule (astryx-theme), so <Text type="supporting" size="xsm"> silently kept the type's size. Themes now re-emit the size classes in the theme layer so size wins as documented.
  • Thumbnail: replace the hover box-shadow on interactive tiles with the same ::after overlay treatment ClickableCard and SelectableCard use. All three now tint on hover with --color-overlay-hover (and --color-overlay-pressed on press), so interactive feedback is consistent across the card family.
  • Thumbnail/TopNavMegaMenuFeaturedCard: images without alt text are now explicitly decorative instead of silently empty-alt, matching Avatar's handling.
  • Thumbnail: the overlaid remove button now uses a fixed --color-overlay scrim with an --color-on-dark icon instead of adapting to the image's luminance, so it has reliable contrast on any image.
  • Tokenizer: adding and removing tokens (including Backspace on an empty input) is now announced to screen readers. Previously tokens appeared and disappeared silently.
  • TopNav: TopNavMegaMenu triggers now expose aria-controls, and the panel is a labeled group instead of an invalid modal-dialog-wrapped menu.
  • TopNav: TopNavMenu popups now expose proper menu semantics (no modal dialog wrapper) with the full APG keyboard pattern (roving tabindex, arrow keys, typeahead).
  • Align two --json contract shapes with what the CLI actually emits
  • Register all emitted response types in the --json envelope union
    Three response types were defined, exported, and emitted by commands but never added to CLIAnyResponse — the union that jsonOut() type-checks payloads against: component.full, component.detail.blocks, and upgrade.status. Because their discriminators were missing from the map, jsonOut('upgrade.status', …) (and the two component variants) were rejected by the type-checker, and their payload shapes weren't actually being validated. build.help had no response type at all. Added a BuildHelpResponse type and wired all four into the union so every --json envelope the CLI can emit is now type-checked against a declared shape.
  • Type detectPackageManager honestly so astryx doctor's "no lockfile" branch is reachable
    detectPackageManager returns 'npx' as the sentinel for "nothing detected", but its return type only listed 'yarn' | 'pnpm' | 'bun' | 'npm'. Type-checkers therefore treated doctor's pm !== 'npx' guard as a dead comparison — the "No lockfile detected — defaulting to npm/npx" message looked unreachable and was at risk of being "cleaned up". The return type is now PackageManager | 'npx' and detection narrows via a shared type predicate, so the guard is honest and the branch is preserved.
  • Drop the dead cwd parameter from getLatestVersion
    checkForUpdate called getLatestVersion(cwd) and the JSDoc advertised a cwd parameter, but the function takes no arguments — it only reads the $ASTRYX_LATEST_VERSION env var, so the passed cwd was silently ignored. Removed the phantom parameter and its doc so the signature matches the behavior. No functional change to the update-nudge output.
  • Scope the source resolve condition to @astryxdesign packages in withAstryx
    withAstryx set webpack's conditionNames to ['source', …] globally, which resolved any dependency shipping a source export to its raw TypeScript — not just Astryx packages. Third-party deps that ship a source export (e.g. lexical, pulled in by the new RichTextEditor lab component) were then fed untranspiled .ts through Next's babel and failed on syntax like declare class fields.

Documentation

  • docs(Tokenizer, PowerSearch): document and test the existing startIcon prop
    Both components have shipped a working startIcon?: ReactNode | IconType prop for a while (PowerSearch forwards it verbatim to the internal Tokenizer, and it's already exercised in Storybook), but neither's .doc.mjs documented it and neither had test coverage — so it was invisible to astryx component <Name> --dense, the docsite props table, and anyone (human or AI) relying on those as the source of truth. No behavior change; adds the missing props-table entries (en/zh/dense) and colocated tests confirming the icon renders and forwards correctly.

Other Changes

  • Compact per-row status signal (error, warning, unread, etc.) without a
    dedicated status column: a colored status dot by default, or an icon when provided.
  • getStatus(item) maps a row to {color, icon?, label?}, or null for no
    indicator. color accepts a semantic status name (success/error/warning/accent/red/green/etc.) mapped to a theme token, or a raw CSS color as an escape hatch.
  • icon renders the status as a shape signifier instead of the dot, which is
    more accessible than color alone when multiple statuses coexist. label supplies the accessible name.
  • Memoize getStatus with useCallback for a stable plugin identity.
  • swizzle.copy payloads always include package and usesStyleX (both covered by tests), but SwizzleCopyResponse.data didn't declare them — the call site cast the payload to Record<string, unknown> to sidestep the mismatch. Added both fields to the type and dropped the loose cast so the payload is type-checked.
  • The error suggestions shape was declared as {name, reason} (reason required) in the JSON envelope / API error contract, but some call sites emit bare {name} (e.g. candidate component names on swizzle). Introduced a single canonical Suggestion type (reason? optional) and referenced it everywhere so the contract matches the emitted data.

Contributors

@AKnassa, @bhamodi, @cixzhang, @ejc3, @freddymeta, @HelloOjasMutreja, @humbertovirtudes, @josephfarina, @kentonquatman, @Kevinjohn, @potatowagon, @saadpocalypse, @yyq1025

Full Changelog: v0.1.8...v0.1.9