* Add test infrastructure to @backstage/ui
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Add resolveResponsiveValue tests
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Add useDefinition prop resolution and classes tests
Fixed a bug in useDefinition where passing null for classNameTarget or
utilityTarget was incorrectly defaulted to 'root' due to the nullish
coalescing operator treating null as falsy.
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Add useDefinition data attributes tests
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Add resolveDefinitionProps tests
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Add processUtilityProps tests
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Add useDefinition bg system tests
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Add useDefinition utility style and analytics tests
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Add useDefinition href resolution tests
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Add useDefinition options tests
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Wrap all useDefinition tests with BUIProvider
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Fix type errors in useDefinition tests
Use `as const satisfies ComponentConfig<any, any>` instead of
`as ComponentConfig<any, any>` to preserve literal types needed
by the conditional type machinery (DataAttributes, ResolveBgProps,
analytics intersection).
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* Address review feedback in useDefinition tests
- Rename shadowed Wrapper to RouterWrapper in createRouterWrapper
- Fix inaccurate comment about splat vs non-splat routes
- Clarify misleading test name for provider data-bg behavior
Signed-off-by: Johan Persson <johanopersson@gmail.com>
---------
Signed-off-by: Johan Persson <johanopersson@gmail.com>
Deduplicate the plugin/module feature flag registration loops and
distinguish the error source (Plugin vs Module). Treat
FEATURE_FLAG_INVALID as a warning in frontend-defaults.
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
- Fixes the `Cannot access '_AppRootElementBlueprintesm' before
initialization` crash in `@backstage/frontend-plugin-api` caused by a
self-referential import in the packaged ESM.
- Cleans up similar self-imports in `@backstage/catalog-model`,
`@backstage/core-plugin-api`, `@backstage/plugin-catalog-node`,
`@backstage/plugin-kubernetes-common`, and
`@backstage/plugin-kubernetes-node`. Value imports switch to relative
paths; type-only imports use `import type` so they're erased at
runtime.
- Adds a new `@backstage/no-self-package-imports` ESLint rule. It reads
each package's `exports` map, traverses the relative import graph from
every entry's source file, and only reports imports where the current
file is in the same bundle as the target entry (same-entry). Files
that aren't reachable from any entry (tests, scripts, orphans) are
skipped. `import type`, `package.json` imports, and cross-entry
self-imports are allowed by default; cross-entry can be opted into
with `allowCrossEntry: false`.
Signed-off-by: Marat Dyatko <maratd@spotify.com>
Made-with: Cursor
The uuid package dropped its CommonJS entry point in v14, making it
incompatible with Backstage's CJS build output and Jest test runner.
Rather than working around the ESM-only issue, replace all usage with
the built-in crypto.randomUUID() which has been available in Node.js
since v16.7 and in all major browsers since March 2022.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Both @braintree/sanitize-url (this branch) and @internationalized/date
(master) were added to packages/ui dependencies; keeping both.
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
- Replace custom UNSAFE_HREF_RE with @braintree/sanitize-url for robust XSS prevention
- Shorten renderInlineMarkdown JSDoc
- Single-user with href: collapse two adjacent links into one wrapping avatar + name
- Multi-user list: use href ?? index:name as key to avoid collisions on duplicate names
- Status dot: replace role="img"/aria-label with aria-hidden (text label is sufficient)
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Wrap each feature flag registration in a try/catch so that a single
invalid flag name (e.g. containing a slash) is reported through the
error collector instead of crashing the entire app at bootstrap.
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
- Tags: replace div+Fragment with ul/li; move circle divider to CSS ::before pseudo-element, removing it from the DOM
- Metadata: replace div wrapper with dl/dt/dd for proper key-value semantics; reset dl and dd browser margins
- HeaderMetadataUsers: render multi-user stack as ul/li; simplify single-user branch into one ternary with a fragment
- HeaderMetadataStatus: add role="img" and aria-label to the status dot so screen readers announce its meaning
- Restore Fragment import from react
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Uses marked's Lexer.lexInline() instead of a hand-rolled regex to parse
inline links in the Header description. marked ships CommonJS, has zero
dependencies, and is already used in the monorepo. This gives us a proper
token model that handles edge cases the regex could not.
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
- Replace hardcoded gap: 20px with var(--bui-space-5) in metadata row
- Trim leading whitespace from href before unsafe-scheme check to prevent bypass
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Reverting the full-width change as it is too disruptive at this time
and will be handled in a separate PR.
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
- Guard against unsafe URL schemes (javascript:/vbscript:/data:) in description links
- Use index-based keys for tags and metadata to avoid duplicate-key warnings
- Render ReactNode metadata values directly instead of wrapping in Text to avoid invalid span>div nesting; only wrap plain strings in Text
- Replace empty-string Avatar src fallback with data:, to prevent spurious page requests
- Fix JSDoc in HeaderMetadataUsers to accurately describe the row layout
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
react-markdown v8+ is ESM-only and breaks Jest in Node-role packages that
transitively import @backstage/ui via core-app-api. Since the Header
description only needs inline link support, a small regex-based parser
is sufficient and avoids the ESM dependency entirely.
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Add `isPending` prop to Alert, Button, ButtonIcon, Table, and
TableRoot, aligning with React Aria naming conventions. The
`loading` prop is deprecated but remains functional as an alias.
CSS selectors now target `data-ispending` instead of `data-loading`
for pending state styling. The `data-loading` attribute is still
emitted for backward compatibility.
Internal Table hooks (`PaginationResult`, `UsePageCacheResult`)
renamed `loading` to `isPending`. The `useTable` hook returns both
`isPending` and `loading` on `tableProps` to preserve backward
compatibility.
Updated docs-ui documentation and stories accordingly.
Signed-off-by: Johan Persson <johanopersson@gmail.com>
When the app was served under a non-root base path, BUI link
components rewrote absolute `href` values as in-app paths — e.g.
`https://example.com` became `/basename/https:/example.com` —
because every href was passed through react-router's `useHref`,
which treats all strings as relative paths.
External URLs (`http://`, `https://`, `//`, `mailto:`, `tel:`)
now bypass href resolution. Internal hrefs are normalized to
their canonical pre-basename form in `useDefinition`, so
downstream resolution by react-router's `useHref` (for
rendering) and `navigate` (for click-navigation) adds the
basename exactly once.
Signed-off-by: Johan Persson <johanopersson@gmail.com>
Remove `export` from five internal helper functions that are not part of
the published API and are only used within their own files. The `sleep`
function in GitLabIntegration is kept exported for test access but marked
as `@internal`.
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
The `otherGraph` variable in `listChangedPackages` was incorrectly
created from `thisLockfile` instead of `otherLockfile`, making the
merged dependency graph a duplicate of the current one. This meant
that dependencies only present in the old lockfile were never added
to the graph, so transitive removals could not be detected.
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
- Add `href` to `HeaderMetadataUser`: avatar becomes a link and name renders as a primary `Link` with `standalone` (no underline at rest)
- Add `WithMetadataUsersNoLinks` story alongside `WithMetadataUsers` to cover both link and non-link variants
- Remove `strong`/`em` from ReactMarkdown `allowedElements` in Header description — only plain text and inline links are now supported
- Replace `<Container>` root with a plain `<div>` so the Header spans its full parent width
- Add `HeaderMetadataStatus` component and CSS (new files)
- Update docs: fixtures, snippets, props-definition, and page.mdx to reflect all changes
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Made-with: Cursor
- Add HeaderMetadataUsers component: single user shows avatar + name,
multiple users show avatar stack with tooltip on hover
- Use Pressable from react-aria for tooltip trigger compatibility
- Switch tags and metadata text to body-medium variant
- Fix metadata item styling: secondary color label, no bold, no colon,
flex row with gap-2 between label and value
- Update Header gap to space-3
- Update docs with HeaderMetadataUsers example, correct prop types,
and synced default snippet
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Made-with: Cursor
Add three new props to the Header component:
- `tags`: renders a row of text/link items above the title, separated by
3×3px circle dividers
- `description`: renders a markdown string below the title with inline
link support via react-markdown
- `metadata`: renders key-value pairs below the description with 20px gaps
Also deprecates the `breadcrumbs` prop for future removal.
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Made-with: Cursor
- Add aria-label="Open calendar" and aria-hidden on icon for the calendar trigger button
- Add aria-hidden on nav button icons (React Aria provides built-in labels for prev/next slots)
- Update React Aria docs link to react-aria.adobe.com format
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Made-with: Cursor
- Fixed size: 1.5rem (small), 2rem (medium) in rem units
- No padding, flex-centered icon
- Inset focus ring using 1px box-shadow with --bui-ring token
- Border radius bumped to radius-2
- Medium size padding-inline-end reduced to space-1
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Made-with: Cursor
- Switch DateRangePicker to use the BUI Popover component (with hideArrow)
instead of the raw react-aria-components Popover
- Add WithUnavailableDates story showing isDateUnavailable with weekends
blocked and non-contiguous ranges disallowed (default behaviour)
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Made-with: Cursor
Adds a new DateRangePicker component to @backstage/ui built on React Aria,
featuring a custom field group with two DateInput fields and a calendar
trigger, a RangeCalendar popover with proper range selection visuals
(solid circles for start/end, transparent solid fill for in-range cells
with row-boundary rounding), and full BUI token usage including bg
consumer auto-increment. Includes Storybook stories and a docs-ui page.
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Made-with: Cursor
Node.js setTimeout uses a 32-bit signed integer for the delay, so
values larger than 2^31-1 ms (~24.8 days) cause the callback to fire
immediately. Fix by chunking the wait into segments of at most 2^30 ms.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
React Aria's `CollectionBuilder` renders `TabList`'s children into both a
hidden collection-building tree and the real DOM. The hidden instance of
`TabsIndicators` sits outside the `TabListStateContext` provider, so its
`state` is `null` — causing its `updateCSSVariables` effect to hit the
`else` branch and write `--active-tab-opacity: 0` to the `tabsRef` DOM
element that the real instance also writes to. Under the right render
ordering, this hidden write lands after the real instance's `opacity: 1`
and makes the active indicator disappear on uncontrolled Tabs.
Guard `updateCSSVariables` with an early return when `state == null` so
the hidden instance never writes to the shared DOM element.
Signed-off-by: Johan Persson <johanopersson@gmail.com>