Merge branch 'master' into master
Signed-off-by: Justin Bryant <justintbry@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/eslint-plugin': minor
|
||||
---
|
||||
|
||||
Added a new `no-self-package-imports` lint rule, enabled as `error` in the recommended config, that reports when a package imports itself by its own name instead of using a relative path. This pattern causes circular initialization errors in bundled ESM and with `jest.requireActual`.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added `isPending` prop to Alert, Button, ButtonIcon, Table, and TableRoot as a replacement for the `loading` prop, aligning with React Aria naming conventions. The `loading` prop is now deprecated but still supported as an alias. CSS selectors now use `data-ispending` instead of `data-loading` for styling pending states; `data-loading` is still emitted for backward compatibility but will be removed alongside the `loading` prop.
|
||||
|
||||
**Affected components:** Alert, Button, ButtonIcon, Table, TableRoot
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added a public `--bui-bg-inherit` CSS variable that resolves to the background
|
||||
color of the nearest enclosing bg provider (`Box`, `Flex`, `Grid`, `Card`,
|
||||
`Accordion`, or any element with a `data-bg` attribute), falling back to
|
||||
`--bui-bg-app`. Use it from CSS for sticky or fixed elements that need to match
|
||||
their surrounding surface without hardcoding a specific level.
|
||||
|
||||
```css
|
||||
.searchBarContainer {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--bui-bg-inherit);
|
||||
}
|
||||
```
|
||||
|
||||
As part of this change, the `data-bg` painting rules previously duplicated in
|
||||
`Box`, `Flex`, `Grid`, `Accordion`, and `Card` have been centralized into a
|
||||
single source in `core.css`. Painting and component behavior are unchanged for
|
||||
all existing usages, with one minor expansion: any element with a `data-bg`
|
||||
attribute (including provider elements and any element that sets it directly)
|
||||
is now painted, not only `Box`/`Flex`/`Grid`/`Card`/`Accordion` elements.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Added stable DOM markers to the legacy Page and Header so adjacent layout components can coordinate spacing without relying on generated class names.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Bumped create-app version.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Bumped create-app version.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-devtools': patch
|
||||
---
|
||||
|
||||
Scheduled Tasks page now refreshes its table automatically after a task is triggered or cancelled, so the updated status is visible without reloading the browser.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-test-utils': patch
|
||||
---
|
||||
|
||||
Added support for `ExternalRouteRef` in the `mountedRoutes` option of `renderInTestApp` and `renderTestApp`.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added support for grouping options into sections in the Select component. You can now pass section objects with a `title` and a nested `options` array alongside (or instead of) regular options to render grouped dropdowns with section headers.
|
||||
|
||||
**Affected components:** Select
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-defaults': patch
|
||||
---
|
||||
|
||||
Invalid feature flag declarations are now treated as warnings rather than errors, letting the app load normally.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
Updated the `list-scaffolder-tasks` action to support the new "status" filter parameter, allowing the action to return tasks matching a specific status.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Disabled `Card` scroll shadow in browsers that don't support `animation-timeline: scroll()`. Prevents the shadow from being always visible over the `CardBody` when there's nothing to scroll or the body is not scrolled.
|
||||
|
||||
**Affected components:** Card
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Fixed `CardBody` showing an unwanted scrollbar when constrained below the scroll shadow height.
|
||||
|
||||
**Affected components:** Card
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
'@backstage/catalog-model': patch
|
||||
'@backstage/core-plugin-api': patch
|
||||
'@backstage/plugin-catalog-node': patch
|
||||
'@backstage/plugin-kubernetes-common': patch
|
||||
'@backstage/plugin-kubernetes-node': patch
|
||||
---
|
||||
|
||||
Removed a handful of internal imports that referenced the package by its own name. Value imports were switched to relative paths, and type-only imports to `import type`. These self-referential imports could trigger circular initialization errors in bundled ESM and when the package was loaded via `jest.requireActual` — most visibly `Cannot access '_AppRootElementBlueprintesm' before initialization` from `@backstage/frontend-plugin-api`. There are no user-facing API changes.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Fixed a performance regression in the `/entity-facets` endpoint when filters or permission conditions are applied, by routing the EXISTS-based filter through `final_entities` instead of correlating against the much larger `search` table.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli-node': patch
|
||||
---
|
||||
|
||||
Fixed a bug in `PackageGraph.listChangedPackages` where removed dependencies were not detected during lockfile analysis. The dependency graph from the previous lockfile was not being merged, causing transitive dependency removals to be missed.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Fixed scheduler `sleep` firing immediately for durations longer than ~24.8 days, caused by Node.js `setTimeout` overflowing its 32-bit millisecond limit.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend': patch
|
||||
---
|
||||
|
||||
Added action for search backend to query search engine using the actions registry
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@techdocs/cli': minor
|
||||
---
|
||||
|
||||
Add support for disabling external font downloads via techdocs-cli `techdocs-cli generate --disableExternalFonts`, useful for air-gapped Backstage instances.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': minor
|
||||
---
|
||||
|
||||
Add support for flex item props (`grow`, `shrink`, and `basis`) to `Box`, `Card`, `Grid`, and `Flex` itself.
|
||||
|
||||
**Affected components:** Box, Card, Grid, Flex
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Fixed two issues in the GitLab integration's fetch behavior:
|
||||
|
||||
- The internal fetch wrapper was passing `mode: 'same-origin'` on every request. This had no practical effect server-side, but would have caused cross-origin requests to be rejected when the integration is used from a browser. Requests now use the default fetch mode and work correctly in both browser and Node environments.
|
||||
- When retries are configured, transient network errors (such as dropped connections or DNS hiccups) are now retried using the same `maxRetries` and exponential delay as retryable HTTP status codes. Previously, a thrown fetch error would propagate immediately on the first failure regardless of the retry configuration. Caller-initiated aborts continue to surface immediately without being retried.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-node': patch
|
||||
---
|
||||
|
||||
Added optional `status` filter to `ScaffolderService.listTasks`, allowing callers to retrieve tasks matching a specific status.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added `description`, `tags`, and `metadata` props to the `Header` component. The `description` prop accepts a markdown string with support for inline links. The `tags` prop renders a row of text or link items above the title. The `metadata` prop renders key-value pairs below the description. The `breadcrumbs` prop has been deprecated and will be removed in a future release.
|
||||
|
||||
**Affected components:** Header
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/ui': minor
|
||||
---
|
||||
|
||||
Added a `sticky` prop to the `Header` component. When `true`, the title-and-actions bar stays fixed to the top of its scroll container while the rest of the header (tags, description, metadata) scrolls away. The sticky bar background color automatically matches the container surface using the bg-consumer system.
|
||||
|
||||
**BREAKING**: Removed the main header class from the `Header` component. Custom styles that target this class should be updated to target the component sections that remain.
|
||||
|
||||
**Affected components:** Header
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-app-api': patch
|
||||
---
|
||||
|
||||
Invalid feature flag declarations no longer crash the app during bootstrap. They are now reported through the error collector and skipped, letting the rest of the app load normally.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-backend': minor
|
||||
'@backstage/plugin-techdocs-node': minor
|
||||
---
|
||||
|
||||
Add support for disabling external font downloads via app-config option `techdocs.generator.mkdocs.disableExternalFonts`, useful for air-gapped Backstage instances.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
Added `always()` and `failure()` status check functions for scaffolder steps. These functions can be used in the if field of a step to control execution after failures. `always()` ensures a step runs regardless of previous step outcomes, while `failure()` runs a step only when a previous step has failed.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
'@backstage/plugin-api-docs': patch
|
||||
'@backstage/plugin-app': patch
|
||||
'@backstage/plugin-app-visualizer': patch
|
||||
'@backstage/plugin-auth': patch
|
||||
'@backstage/plugin-catalog-graph': patch
|
||||
'@backstage/plugin-catalog-import': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-notifications': patch
|
||||
'@backstage/plugin-org': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
'@backstage/plugin-signals': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Limited `@remixicon/react` dependency to versions below 4.9.0 due to a license change in that release.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Fix `Card href=...` not showing a focus indicator on keyboard navigation. `Link` now composes `useFocusRing`, emits `data-focus-visible`, and renders a `--bui-ring` outline when keyboard-focused. The Card's existing focus-ring CSS matches when the trigger is focused.
|
||||
|
||||
_Affected components_: Card, Link
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-react': patch
|
||||
---
|
||||
|
||||
Added optional clustersCacheTtlMs option to KubernetesBackendClient that caches getClusters() responses for the specified duration, avoiding repeated /clusters requests when multiple proxy calls resolve cluster auth in quick succession.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added invalid-state styling for Checkbox and corresponding Storybook variants for verification.
|
||||
|
||||
**Affected components:** Checkbox, CheckboxGroup
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-msgraph-incremental': minor
|
||||
---
|
||||
|
||||
Introduces a cursor-based incremental ingestion provider for Microsoft Graph that processes users and groups one page at a time. Unlike `MicrosoftGraphOrgEntityProvider`, this module never holds the full dataset in memory — each burst processes a single page (up to 999 users or 100 groups). The `@odata.nextLink` cursor is persisted so a pod restart resumes from the last completed page rather than starting over.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Simplified the `OwnerEntityColumn` in the task list to rely on `EntityRefLink` and the entity presentation API instead of manually fetching entities from the catalog.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Adjusted PluginHeader spacing and borders so headers with and without tabs align more consistently with surrounding page content, including when paired with page headers.
|
||||
|
||||
**Affected components:** PluginHeader, Header
|
||||
+74
-2
@@ -223,7 +223,79 @@
|
||||
"@backstage/plugin-techdocs-react": "1.3.10",
|
||||
"@backstage/plugin-user-settings": "0.9.2",
|
||||
"@backstage/plugin-user-settings-backend": "0.4.2",
|
||||
"@backstage/plugin-user-settings-common": "0.1.0"
|
||||
"@backstage/plugin-user-settings-common": "0.1.0",
|
||||
"@backstage/plugin-catalog-backend-module-msgraph-incremental": "0.0.0"
|
||||
},
|
||||
"changesets": []
|
||||
"changesets": [
|
||||
"add-missing-transitive-deps",
|
||||
"add-no-self-package-imports-lint-rule",
|
||||
"add-service-unavailable-error-name",
|
||||
"brave-groups-learn",
|
||||
"bui-bg-inherit-css-var",
|
||||
"chubby-candies-cry",
|
||||
"clamp-react-aria-deps",
|
||||
"core-header-marker",
|
||||
"create-app-1777391535",
|
||||
"create-app-1777992972",
|
||||
"deduplicate-joinpaths-routing",
|
||||
"delegate-attach-mock-api-factory",
|
||||
"devtools-refresh-after-scheduled-task-action",
|
||||
"eager-wolves-enjoy",
|
||||
"extension-point-middleware-backend-app-api",
|
||||
"extension-point-middleware-backend-defaults",
|
||||
"fancy-parents-sit",
|
||||
"feature-flag-invalid-warning",
|
||||
"fifty-clubs-play",
|
||||
"fix-alter-target-nullability",
|
||||
"fix-card-scroll-shadow",
|
||||
"fix-cardbody-min-height",
|
||||
"fix-circular-self-imports",
|
||||
"fix-dialog-dark-theme-selector",
|
||||
"fix-embedded-postgres-config-paths",
|
||||
"fix-facets-perf-regression",
|
||||
"fix-implicit-any-renderInTestApp",
|
||||
"fix-lockfile-removal-detection",
|
||||
"fix-scheduler-sleep-overflow",
|
||||
"fix-tabs-active-indicator-disappearing",
|
||||
"fix-widget-resize-after-edit",
|
||||
"fluffy-brooms-sniff",
|
||||
"free-ways-flow",
|
||||
"funny-animals-prove",
|
||||
"funny-areas-rescue",
|
||||
"gitlab-integration-fetch-fixes",
|
||||
"gold-drinks-poke",
|
||||
"gold-friends-end",
|
||||
"header-improvements",
|
||||
"header-sticky-prop",
|
||||
"isolate-invalid-feature-flags",
|
||||
"kind-files-press",
|
||||
"lazy-rings-end",
|
||||
"limit-remix-icon-version",
|
||||
"link-focus-visible",
|
||||
"mean-monkeys-create",
|
||||
"move-registermswtesthooks-to-test-utils",
|
||||
"msgraph-incremental-initial",
|
||||
"owner-column-cleanup",
|
||||
"plugin-header-spacing",
|
||||
"preserve-external-hrefs-useDefinition",
|
||||
"purple-insects-cross",
|
||||
"remove-duplicate-deps",
|
||||
"remove-portable-schema-deprecated-prop",
|
||||
"remove-unused-deps",
|
||||
"remove-unused-getgithubintegrationconfig",
|
||||
"remove-uuid-dependency",
|
||||
"replace-duplicate-error-utilities",
|
||||
"shy-ways-lay",
|
||||
"slack-scope-message-updates",
|
||||
"swappable-notification-description",
|
||||
"tough-pots-dream",
|
||||
"ui-date-range-picker",
|
||||
"unprocessed-entities-read-auth-check",
|
||||
"unprocessed-entities-read-permission",
|
||||
"upgrade-module-federation-v2",
|
||||
"usetable-complete-debounce",
|
||||
"whole-bees-wave",
|
||||
"zod-v3-config-schema-docs",
|
||||
"zod-v4-dep-bump"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Fixed external URLs in BUI link components being rewritten as in-app paths when the app is served under a non-root base path. Absolute URLs (`http://`, `https://`, `//`, `mailto:`, `tel:`) are now passed through unchanged. Internal `href` values are resolved against the current `basename` exactly once, which also fixes a latent issue where internal link clicks under a non-root base path could navigate to a URL with the `basename` prefix doubled.
|
||||
|
||||
**Affected components:** ButtonLink, Card, Link, Menu, Tab, Table, Tag
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-devtools': patch
|
||||
---
|
||||
|
||||
Migrated `ConfigContent` component from Material UI to Backstage UI (BUI).
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
'@backstage/backend-test-utils': patch
|
||||
'@backstage/cli-module-new': patch
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch
|
||||
'@backstage/plugin-auth-node': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/plugin-catalog-backend-module-aws': patch
|
||||
'@backstage/plugin-catalog-backend-module-azure': patch
|
||||
'@backstage/plugin-catalog-backend-module-backstage-openapi': patch
|
||||
'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch
|
||||
'@backstage/plugin-catalog-backend-module-bitbucket-server': patch
|
||||
'@backstage/plugin-catalog-backend-module-gerrit': patch
|
||||
'@backstage/plugin-catalog-backend-module-gitea': patch
|
||||
'@backstage/plugin-catalog-backend-module-github': patch
|
||||
'@backstage/plugin-catalog-backend-module-gitlab': patch
|
||||
'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
|
||||
'@backstage/plugin-catalog-backend-module-ldap': patch
|
||||
'@backstage/plugin-catalog-backend-module-msgraph': patch
|
||||
'@backstage/plugin-catalog-backend-module-puppetdb': patch
|
||||
'@backstage/plugin-notifications-backend': patch
|
||||
'@backstage/plugin-permission-common': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
'@backstage/plugin-search-backend-module-elasticsearch': patch
|
||||
'@backstage/plugin-search-backend-module-pg': patch
|
||||
'@backstage/plugin-search-backend-node': patch
|
||||
'@backstage/plugin-search-react': patch
|
||||
'@backstage/plugin-signals': patch
|
||||
'@backstage/plugin-signals-backend': patch
|
||||
---
|
||||
|
||||
Removed the `uuid` dependency and replaced usage with the built-in `crypto.randomUUID()`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-notifications': patch
|
||||
---
|
||||
|
||||
The notification description used in the notifications table is now a swappable component, so that apps can replace its rendering with a custom implementation.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-node': patch
|
||||
---
|
||||
|
||||
Fixed bug causing `--legacyCopyReadmeMdToIndexMd` option to fail if docs directory is not present
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added new `DateRangePicker` component — combines two date fields and a calendar popover for selecting a date range, built on React Aria with full keyboard and screen reader accessibility. Uses BUI design tokens throughout, including auto-incremented backgrounds via the bg consumer pattern.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-unprocessed': patch
|
||||
---
|
||||
|
||||
Added permission authorization checks to the unprocessed entities read endpoints for pending and failed entities.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-unprocessed-entities-common': patch
|
||||
---
|
||||
|
||||
Added `unprocessedEntitiesReadPermission` for authorizing read access to unprocessed entity endpoints.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added `searchDebounceMs` and `filterDebounceMs` options to `useTable` in `complete` mode. Both default to `0` (no debounce, no observable change for existing consumers); set them to defer the client-side filter/search/sort pipeline on large datasets without reimplementing input-layer debouncing. The controlled `search` / `onSearchChange` and `filter` / `onFilterChange` callbacks continue to fire on every change.
|
||||
|
||||
**Affected components:** Table
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
'@backstage/plugin-auth': patch
|
||||
---
|
||||
|
||||
Improved the OAuth consent dialog for MCP authorization by showing more client details, including the client metadata host for CIMD clients, the metadata URL, callback URL, and requested scopes.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added a new `Combobox` component. It pairs a text input with a filterable dropdown of options and supports single selection, sectioned options, icons, sizes, and custom typed values via `allowsCustomValue`.
|
||||
|
||||
**Affected components:** Combobox
|
||||
@@ -58,6 +58,7 @@ yarn.lock @backstage/maintainers @backst
|
||||
/plugins/catalog-backend-module-backstage-openapi @backstage/maintainers @backstage/openapi-tooling-maintainers
|
||||
/plugins/catalog-backend-module-gitea @backstage/maintainers @backstage/catalog-maintainers
|
||||
/plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/catalog-maintainers @pjungermann
|
||||
/plugins/catalog-backend-module-msgraph-incremental @backstage/maintainers @backstage/catalog-maintainers
|
||||
/plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/catalog-maintainers
|
||||
/plugins/catalog-graph @backstage/maintainers @backstage/catalog-maintainers @backstage/sda-se-reviewers
|
||||
/plugins/devtools @backstage/maintainers @awanlin
|
||||
|
||||
@@ -75,6 +75,7 @@ codemod
|
||||
codemods
|
||||
codeowners
|
||||
codescene
|
||||
Combobox
|
||||
composability
|
||||
composable
|
||||
config
|
||||
@@ -102,6 +103,7 @@ dayjs
|
||||
debounce
|
||||
debounced
|
||||
debounces
|
||||
debouncing
|
||||
debuggability
|
||||
declaratively
|
||||
deduplicate
|
||||
@@ -345,6 +347,7 @@ params
|
||||
parseable
|
||||
passthrough
|
||||
passwordless
|
||||
patcher
|
||||
Patrik
|
||||
pattison
|
||||
Peloton
|
||||
@@ -590,4 +593,4 @@ Zhou
|
||||
zod
|
||||
Zolotusky
|
||||
zoomable
|
||||
zsh
|
||||
zsh
|
||||
@@ -0,0 +1 @@
|
||||
Fix home page widgets not being draggable or resizable after the first save
|
||||
@@ -1 +0,0 @@
|
||||
Make TechDocs sidebar positioning configurable via CSS custom properties
|
||||
@@ -1 +0,0 @@
|
||||
Bump zod dependency to v4 for packages using configSchema and clarify that zod/v4 subpath from v3 is not supported
|
||||
@@ -1 +0,0 @@
|
||||
Clamp React Aria dependency ranges to patch-only updates to prevent unintended minor version upgrades
|
||||
@@ -1 +0,0 @@
|
||||
Fix active tab indicator disappearing on uncontrolled Tabs in @backstage/ui
|
||||
@@ -0,0 +1 @@
|
||||
Fix facets endpoint performance regression when filters or permissions are applied
|
||||
@@ -0,0 +1 @@
|
||||
Preserve external hrefs in BUI link components under non-root base path
|
||||
+18
-31
@@ -50,26 +50,11 @@ export default definePreview({
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
background: {
|
||||
name: 'Background',
|
||||
description: 'Global background for components',
|
||||
defaultValue: 'app',
|
||||
toolbar: {
|
||||
icon: 'contrast',
|
||||
items: [
|
||||
{ value: 'app', title: 'App Background' },
|
||||
{ value: 'neutral-1', title: 'Neutral 1 Background' },
|
||||
{ value: 'neutral-2', title: 'Neutral 2 Background' },
|
||||
{ value: 'neutral-3', title: 'Neutral 3 Background' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initialGlobals: {
|
||||
themeMode: 'light',
|
||||
themeName: 'backstage',
|
||||
background: 'app',
|
||||
},
|
||||
|
||||
parameters: {
|
||||
@@ -143,7 +128,6 @@ export default definePreview({
|
||||
globals.themeMode === 'light' ? themes.light : themes.dark;
|
||||
const selectedThemeMode = globals.themeMode || 'light';
|
||||
const selectedThemeName = globals.themeName || 'backstage';
|
||||
const selectedBackground = globals.background || 'app';
|
||||
const isFullscreen = context.parameters.layout === 'fullscreen';
|
||||
|
||||
useEffect(() => {
|
||||
@@ -155,15 +139,13 @@ export default definePreview({
|
||||
document.body.removeAttribute('data-theme-mode');
|
||||
document.body.removeAttribute('data-theme-name');
|
||||
};
|
||||
}, [selectedTheme, selectedThemeName]);
|
||||
}, [selectedThemeMode, selectedThemeName]);
|
||||
|
||||
useEffect(() => {
|
||||
appThemeApi.setActiveThemeId(selectedThemeMode);
|
||||
}, [selectedThemeMode]);
|
||||
|
||||
document.body.style.backgroundColor = 'var(--bui-bg-app)';
|
||||
document.body.style.padding =
|
||||
isFullscreen && selectedBackground !== 'app' ? '1rem' : '';
|
||||
const docsStoryElements = document.getElementsByClassName('docs-story');
|
||||
Array.from(docsStoryElements).forEach(element => {
|
||||
(element as HTMLElement).style.backgroundColor = 'var(--bui-bg-app)';
|
||||
@@ -174,18 +156,23 @@ export default definePreview({
|
||||
{/* @ts-ignore */}
|
||||
<TestApiProvider apis={apis}>
|
||||
<AlertDisplay />
|
||||
{Array.from({
|
||||
length:
|
||||
selectedBackground === 'app'
|
||||
? 0
|
||||
: parseInt(selectedBackground.split('-')[1], 10),
|
||||
}).reduce<React.ReactNode>(
|
||||
children => (
|
||||
<Box bg="neutral" p="4">
|
||||
{children}
|
||||
</Box>
|
||||
),
|
||||
<Story />,
|
||||
{selectedThemeName === 'spotify' ? (
|
||||
<Box
|
||||
bg="neutral"
|
||||
m={isFullscreen ? '4' : undefined}
|
||||
style={{
|
||||
borderRadius: 'var(--bui-radius-3)',
|
||||
height: isFullscreen
|
||||
? 'calc(100vh - (var(--bui-space-4) * 2))'
|
||||
: undefined,
|
||||
overflow: 'auto',
|
||||
overscrollBehavior: 'none',
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</Box>
|
||||
) : (
|
||||
<Story />
|
||||
)}
|
||||
</TestApiProvider>
|
||||
</UnifiedThemeProvider>
|
||||
|
||||
@@ -190,10 +190,6 @@
|
||||
.bui-Tag {
|
||||
border-radius: var(--bui-radius-full);
|
||||
}
|
||||
|
||||
.bui-Container {
|
||||
padding-inline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme-mode='light'][data-theme-name='spotify'] {
|
||||
@@ -243,24 +239,3 @@
|
||||
|
||||
--bui-ring: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Plugin header (@backstage/ui) and story shell header — kept at the bottom of
|
||||
* this file for easier scanning alongside other component overrides above.
|
||||
*/
|
||||
[data-theme-name='spotify'] {
|
||||
.bui-PluginHeaderToolbar {
|
||||
padding: 0;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
margin-bottom: var(--bui-space-2);
|
||||
}
|
||||
|
||||
.bui-PluginHeaderTabsWrapper {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
margin-left: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,7 @@ backend:
|
||||
pluginSources:
|
||||
- catalog
|
||||
- scaffolder
|
||||
- search
|
||||
# See README.md in the proxy-backend plugin for information on the configuration format
|
||||
proxy:
|
||||
endpoints:
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"sync:changelog:force": "node scripts/sync-changelog.mjs --force"
|
||||
},
|
||||
"resolutions": {
|
||||
"@remixicon/react": ">=4.6.0 <4.9.0",
|
||||
"@types/react": "19.2.10",
|
||||
"@types/react-dom": "19.2.3"
|
||||
},
|
||||
@@ -22,13 +23,13 @@
|
||||
"@mdx-js/loader": "^3.1.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@next/mdx": "16.2.1",
|
||||
"@remixicon/react": "^4.6.0",
|
||||
"@remixicon/react": ">=4.6.0 <4.9.0",
|
||||
"@uiw/codemirror-themes": "^4.23.7",
|
||||
"@uiw/react-codemirror": "^4.23.7",
|
||||
"clsx": "^2.1.1",
|
||||
"html-react-parser": "^5.2.5",
|
||||
"motion": "^12.4.1",
|
||||
"next": "16.2.1",
|
||||
"next": "16.2.3",
|
||||
"next-mdx-remote-client": "^2.1.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "19.2.4",
|
||||
@@ -44,7 +45,7 @@
|
||||
"@types/react-dom": "19.2.3",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.2.1",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss": "^8.5.10",
|
||||
"postcss-import": "^16.1.1",
|
||||
"typescript": "^5",
|
||||
"unified": "^11.0.4"
|
||||
|
||||
@@ -119,20 +119,20 @@ export const WithActionsAndDescriptions = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const LoadingStates = () => {
|
||||
export const PendingStates = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
loading
|
||||
isPending
|
||||
title="Processing your request..."
|
||||
/>
|
||||
<Alert status="success" icon={true} loading title="Saving changes..." />
|
||||
<Alert status="success" icon={true} isPending title="Saving changes..." />
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
loading
|
||||
isPending
|
||||
title="Processing your request"
|
||||
description="This may take a few moments. Please do not close this window."
|
||||
/>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
statusVariantsSnippet,
|
||||
withDescriptionSnippet,
|
||||
withActionsSnippet,
|
||||
loadingStatesSnippet,
|
||||
pendingStatesSnippet,
|
||||
withoutIconsSnippet,
|
||||
customIconSnippet,
|
||||
} from './snippets';
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
StatusVariants,
|
||||
WithDescription,
|
||||
WithActions,
|
||||
LoadingStates,
|
||||
PendingStates,
|
||||
WithoutIcons,
|
||||
CustomIcon,
|
||||
} from './components';
|
||||
@@ -79,16 +79,16 @@ Include custom actions like buttons for interactive alerts.
|
||||
code={withActionsSnippet}
|
||||
/>
|
||||
|
||||
### Loading States
|
||||
### Pending State
|
||||
|
||||
The loading spinner replaces the icon to indicate an ongoing process.
|
||||
The pending spinner replaces the icon to indicate an ongoing process.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<LoadingStates />}
|
||||
code={loadingStatesSnippet}
|
||||
preview={<PendingStates />}
|
||||
code={pendingStatesSnippet}
|
||||
/>
|
||||
|
||||
### Without Icons
|
||||
|
||||
@@ -10,31 +10,45 @@ export const alertPropDefs: Record<string, PropDef> = {
|
||||
values: ['info', 'success', 'warning', 'danger'],
|
||||
responsive: true,
|
||||
default: 'info',
|
||||
description:
|
||||
'Visual status of the alert, which determines color and default icon.',
|
||||
},
|
||||
icon: {
|
||||
type: 'enum',
|
||||
values: ['boolean', 'React.ReactElement'],
|
||||
responsive: false,
|
||||
description:
|
||||
'Set to true to show the default status icon, or pass a custom icon element. Set to false to hide the icon.',
|
||||
},
|
||||
isPending: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description:
|
||||
'Replaces the icon with a spinner to indicate a pending operation.',
|
||||
},
|
||||
loading: {
|
||||
type: 'enum',
|
||||
values: ['boolean'],
|
||||
responsive: false,
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description: 'Deprecated. Use `isPending` instead.',
|
||||
},
|
||||
title: {
|
||||
type: 'enum',
|
||||
values: ['React.ReactNode'],
|
||||
responsive: false,
|
||||
description: 'Primary message displayed in the alert.',
|
||||
},
|
||||
description: {
|
||||
type: 'enum',
|
||||
values: ['React.ReactNode'],
|
||||
responsive: false,
|
||||
description: 'Additional detail shown below the title.',
|
||||
},
|
||||
customActions: {
|
||||
type: 'enum',
|
||||
values: ['React.ReactNode'],
|
||||
responsive: false,
|
||||
description:
|
||||
'Custom action buttons displayed on the right side of the alert.',
|
||||
},
|
||||
m: {
|
||||
type: 'enum',
|
||||
|
||||
@@ -97,13 +97,13 @@ export const withActionsAndDescriptionsSnippet = `<Alert
|
||||
}
|
||||
/>`;
|
||||
|
||||
export const loadingStatesSnippet = `<Flex direction="column" gap="4">
|
||||
<Alert status="info" icon={true} loading title="Processing your request..." />
|
||||
<Alert status="success" icon={true} loading title="Saving changes..." />
|
||||
export const pendingStatesSnippet = `<Flex direction="column" gap="4">
|
||||
<Alert status="info" icon={true} isPending title="Processing your request..." />
|
||||
<Alert status="success" icon={true} isPending title="Saving changes..." />
|
||||
<Alert
|
||||
status="info"
|
||||
icon={true}
|
||||
loading
|
||||
isPending
|
||||
title="Processing your request"
|
||||
description="This may take a few moments. Please do not close this window."
|
||||
/>
|
||||
|
||||
@@ -56,12 +56,12 @@ export const Disabled = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Loading = () => {
|
||||
export const Pending = () => {
|
||||
return (
|
||||
<ButtonIcon
|
||||
icon={<RiCloudLine />}
|
||||
variant="primary"
|
||||
loading
|
||||
isPending
|
||||
aria-label="Cloud"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
variantsSnippet,
|
||||
sizesSnippet,
|
||||
disabledSnippet,
|
||||
loadingSnippet,
|
||||
isPendingSnippet,
|
||||
} from './snippets';
|
||||
import { Variants, Sizes, Disabled, Loading } from './components';
|
||||
import { Variants, Sizes, Disabled, Pending } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ButtonIconDefinition } from '../../../utils/definitions';
|
||||
@@ -63,7 +63,7 @@ export const reactAriaUrls = {
|
||||
code={disabledSnippet}
|
||||
/>
|
||||
|
||||
### Loading
|
||||
### Pending
|
||||
|
||||
Shows a spinner during async operations.
|
||||
|
||||
@@ -71,8 +71,8 @@ Shows a spinner during async operations.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Loading />}
|
||||
code={loadingSnippet}
|
||||
preview={<Pending />}
|
||||
code={isPendingSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={ButtonIconDefinition} />
|
||||
|
||||
@@ -42,11 +42,16 @@ export const buttonIconPropDefs: Record<string, PropDef> = {
|
||||
default: 'false',
|
||||
description: 'Prevents interaction and applies disabled styling.',
|
||||
},
|
||||
loading: {
|
||||
isPending: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description: 'Shows a spinner and disables the button.',
|
||||
},
|
||||
loading: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description: 'Deprecated. Use `isPending` instead.',
|
||||
},
|
||||
type: {
|
||||
type: 'enum',
|
||||
values: ['button', 'submit', 'reset'],
|
||||
|
||||
@@ -20,4 +20,4 @@ export const disabledSnippet = `<Flex direction="row" gap="2">
|
||||
<ButtonIcon isDisabled icon={<RiCloudLine />} variant="tertiary" aria-label="Cloud" />
|
||||
</Flex>`;
|
||||
|
||||
export const loadingSnippet = `<ButtonIcon icon={<RiCloudLine />} variant="primary" loading aria-label="Cloud" />`;
|
||||
export const isPendingSnippet = `<ButtonIcon icon={<RiCloudLine />} variant="primary" isPending aria-label="Cloud" />`;
|
||||
|
||||
@@ -75,9 +75,9 @@ export const Destructive = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Loading = () => {
|
||||
export const Pending = () => {
|
||||
return (
|
||||
<Button variant="primary" loading={true}>
|
||||
<Button variant="primary" isPending={true}>
|
||||
Load more items
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
withIconsSnippet,
|
||||
disabledSnippet,
|
||||
destructiveSnippet,
|
||||
loadingSnippet,
|
||||
isPendingSnippet,
|
||||
asLinkSnippet,
|
||||
buttonSnippetUsage,
|
||||
buttonResponsiveSnippet,
|
||||
@@ -24,17 +24,17 @@ import {
|
||||
WithIcons,
|
||||
Disabled,
|
||||
Destructive,
|
||||
Loading,
|
||||
Pending,
|
||||
AsLink,
|
||||
} from './components';
|
||||
|
||||
export const reactAriaUrls = {
|
||||
button: 'https://react-spectrum.adobe.com/react-aria/Button.html',
|
||||
button: 'https://react-aria.adobe.com/Button',
|
||||
};
|
||||
|
||||
<PageTitle
|
||||
title="Button"
|
||||
description="A button with primary, secondary, and tertiary variants, destructive styling, and loading state."
|
||||
description="A button with primary, secondary, and tertiary variants, destructive styling, and pending state."
|
||||
/>
|
||||
|
||||
<Snippet align="center" py={4} preview={<Variants />} code={variantsSnippet} />
|
||||
@@ -110,7 +110,7 @@ Use the `destructive` prop for dangerous actions like delete or remove.
|
||||
layout="side-by-side"
|
||||
/>
|
||||
|
||||
### Loading
|
||||
### Pending
|
||||
|
||||
Shows a spinner and disables interaction during async operations.
|
||||
|
||||
@@ -118,8 +118,8 @@ Shows a spinner and disables interaction during async operations.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Loading />}
|
||||
code={loadingSnippet}
|
||||
preview={<Pending />}
|
||||
code={isPendingSnippet}
|
||||
layout="side-by-side"
|
||||
/>
|
||||
|
||||
|
||||
@@ -48,11 +48,16 @@ export const buttonPropDefs: Record<string, PropDef> = {
|
||||
default: 'false',
|
||||
description: 'Prevents interaction and applies disabled styling.',
|
||||
},
|
||||
loading: {
|
||||
isPending: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description: 'Shows a spinner and disables the button.',
|
||||
},
|
||||
loading: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description: 'Deprecated. Use `isPending` instead.',
|
||||
},
|
||||
children: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
|
||||
@@ -55,7 +55,7 @@ export const destructiveSnippet = `<Flex gap="4">
|
||||
</Button>
|
||||
</Flex>`;
|
||||
|
||||
export const loadingSnippet = `<Button variant="primary" loading={true}>
|
||||
export const isPendingSnippet = `<Button variant="primary" isPending={true}>
|
||||
Load more items
|
||||
</Button>`;
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
'use client';
|
||||
|
||||
import { Combobox } from '../../../../../packages/ui/src/components/Combobox/Combobox';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { RiCloudLine } from '@remixicon/react';
|
||||
|
||||
const fontOptions = [
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
];
|
||||
|
||||
const countries = [
|
||||
{ value: 'us', label: 'United States' },
|
||||
{ value: 'ca', label: 'Canada' },
|
||||
{ value: 'mx', label: 'Mexico' },
|
||||
{ value: 'uk', label: 'United Kingdom' },
|
||||
{ value: 'fr', label: 'France' },
|
||||
{ value: 'de', label: 'Germany' },
|
||||
{ value: 'it', label: 'Italy' },
|
||||
{ value: 'es', label: 'Spain' },
|
||||
{ value: 'jp', label: 'Japan' },
|
||||
{ value: 'cn', label: 'China' },
|
||||
{ value: 'in', label: 'India' },
|
||||
{ value: 'br', label: 'Brazil' },
|
||||
{ value: 'au', label: 'Australia' },
|
||||
];
|
||||
|
||||
const sectionedFonts = [
|
||||
{
|
||||
title: 'Serif Fonts',
|
||||
options: [
|
||||
{ value: 'times', label: 'Times New Roman' },
|
||||
{ value: 'georgia', label: 'Georgia' },
|
||||
{ value: 'garamond', label: 'Garamond' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sans-Serif Fonts',
|
||||
options: [
|
||||
{ value: 'arial', label: 'Arial' },
|
||||
{ value: 'helvetica', label: 'Helvetica' },
|
||||
{ value: 'verdana', label: 'Verdana' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Monospace Fonts',
|
||||
options: [
|
||||
{ value: 'courier', label: 'Courier New' },
|
||||
{ value: 'consolas', label: 'Consolas' },
|
||||
{ value: 'fira', label: 'Fira Code' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const Preview = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
style={{ maxWidth: 260 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithLabelAndDescription = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
description="Choose a font family for your document"
|
||||
options={fontOptions}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Sizes = () => (
|
||||
<Flex direction="row" gap="2">
|
||||
<Combobox
|
||||
label="Small"
|
||||
size="small"
|
||||
options={fontOptions}
|
||||
name="font-small"
|
||||
placeholder="Pick a font"
|
||||
style={{ maxWidth: 260 }}
|
||||
/>
|
||||
<Combobox
|
||||
label="Medium"
|
||||
size="medium"
|
||||
options={fontOptions}
|
||||
name="font-medium"
|
||||
placeholder="Pick a font"
|
||||
style={{ maxWidth: 260 }}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export const WithIcon = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
icon={<RiCloudLine />}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Disabled = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
isDisabled
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const AllowsCustomValue = () => (
|
||||
<Combobox
|
||||
label="Country"
|
||||
options={countries}
|
||||
placeholder="Type any country"
|
||||
allowsCustomValue
|
||||
name="country"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const DisabledOption = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
disabledKeys={['serif']}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithSections = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
options={sectionedFonts}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,153 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ReactAriaLink } from '@/components/ReactAriaLink';
|
||||
import {
|
||||
Preview,
|
||||
WithLabelAndDescription,
|
||||
Sizes,
|
||||
WithIcon,
|
||||
Disabled,
|
||||
DisabledOption,
|
||||
AllowsCustomValue,
|
||||
WithSections,
|
||||
} from './components';
|
||||
import { comboboxPropDefs } from './props-definition';
|
||||
import {
|
||||
optionPropDefs,
|
||||
optionSectionPropDefs,
|
||||
} from '../select/props-definition';
|
||||
import {
|
||||
comboboxUsageSnippet,
|
||||
comboboxDefaultSnippet,
|
||||
comboboxDescriptionSnippet,
|
||||
comboboxSizesSnippet,
|
||||
comboboxDisabledSnippet,
|
||||
comboboxResponsiveSnippet,
|
||||
comboboxIconSnippet,
|
||||
comboboxDisabledOptionsSnippet,
|
||||
comboboxCustomValueSnippet,
|
||||
comboboxSectionsSnippet,
|
||||
} from './snippets';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { ComboboxDefinition } from '../../../utils/definitions';
|
||||
|
||||
export const reactAriaUrls = {
|
||||
combobox: 'https://react-aria.adobe.com/ComboBox',
|
||||
};
|
||||
|
||||
<PageTitle
|
||||
title="Combobox"
|
||||
description="A text input paired with a filterable dropdown for selecting or typing a value."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Preview />}
|
||||
code={comboboxDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={comboboxUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={comboboxPropDefs} />
|
||||
|
||||
<ReactAriaLink component="ComboBox" href={reactAriaUrls.combobox} />
|
||||
|
||||
### Option types
|
||||
|
||||
The `options` prop accepts an array containing either of the following shapes.
|
||||
|
||||
#### `Option`
|
||||
|
||||
<PropsTable data={optionPropDefs} />
|
||||
|
||||
#### `OptionSection`
|
||||
|
||||
<PropsTable data={optionSectionPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Label and description
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<WithLabelAndDescription />}
|
||||
code={comboboxDescriptionSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={comboboxSizesSnippet}
|
||||
/>
|
||||
|
||||
### With icon
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<WithIcon />}
|
||||
code={comboboxIconSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={comboboxDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Disabled options
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<DisabledOption />}
|
||||
code={comboboxDisabledOptionsSnippet}
|
||||
/>
|
||||
|
||||
### Custom values
|
||||
|
||||
Allow the user to type a value that is not in the option list by setting `allowsCustomValue`.
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<AllowsCustomValue />}
|
||||
code={comboboxCustomValueSnippet}
|
||||
/>
|
||||
|
||||
### With sections
|
||||
|
||||
Group options under section headings by passing objects with a `title` and a
|
||||
nested `options` array.
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<WithSections />}
|
||||
code={comboboxSectionsSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Size can change at different breakpoints.
|
||||
|
||||
<CodeBlock code={comboboxResponsiveSnippet} />
|
||||
|
||||
<Theming definition={ComboboxDefinition} />
|
||||
|
||||
<ChangelogComponent component="combobox" />
|
||||
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
import { Chip } from '@/components/Chip';
|
||||
|
||||
export const comboboxPropDefs: Record<string, PropDef> = {
|
||||
options: {
|
||||
type: 'enum',
|
||||
values: ['(Option | OptionSection)[]'],
|
||||
description: (
|
||||
<>
|
||||
Options to display in the dropdown. Pass <Chip>Option</Chip> objects
|
||||
directly, or <Chip>OptionSection</Chip> objects to render grouped
|
||||
options under section headings.
|
||||
</>
|
||||
),
|
||||
},
|
||||
allowsCustomValue: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description:
|
||||
'When true, the typed text is accepted as the value on blur or Enter even if it does not match any option.',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
description: 'Controlled selected value.',
|
||||
},
|
||||
defaultValue: {
|
||||
type: 'string',
|
||||
description: 'Initial value for uncontrolled usage.',
|
||||
},
|
||||
onChange: {
|
||||
type: 'enum',
|
||||
values: ['(value: Key | null) => void'],
|
||||
description: 'Called when the selected option changes.',
|
||||
},
|
||||
inputValue: {
|
||||
type: 'string',
|
||||
description: 'Controlled input text.',
|
||||
},
|
||||
defaultInputValue: {
|
||||
type: 'string',
|
||||
description: 'Initial input text for uncontrolled usage.',
|
||||
},
|
||||
onInputChange: {
|
||||
type: 'enum',
|
||||
values: ['(value: string) => void'],
|
||||
description: 'Called when the input text changes.',
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
description: 'Visible label above the combobox.',
|
||||
},
|
||||
secondaryLabel: {
|
||||
type: 'string',
|
||||
description: (
|
||||
<>
|
||||
Secondary text shown next to the label. If not provided and isRequired
|
||||
is true, displays <Chip>Required</Chip>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Helper text displayed below the label.',
|
||||
},
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
description: 'Text shown when the input is empty.',
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'small',
|
||||
responsive: true,
|
||||
description: 'Visual size of the combobox field.',
|
||||
},
|
||||
icon: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
description: 'Icon displayed before the input.',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
description: 'Called when the dropdown opens or closes.',
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
description: 'Prevents user interaction when true.',
|
||||
},
|
||||
disabledKeys: {
|
||||
type: 'enum',
|
||||
values: ['Iterable<Key>'],
|
||||
description: 'Keys of options that should be disabled.',
|
||||
},
|
||||
isRequired: {
|
||||
type: 'boolean',
|
||||
description: 'Marks the field as required for form validation.',
|
||||
},
|
||||
isInvalid: {
|
||||
type: 'boolean',
|
||||
description: 'Displays the combobox in an error state.',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Form field name for form submission.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
export const comboboxUsageSnippet = `import { Combobox } from '@backstage/ui';
|
||||
|
||||
<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const comboboxDefaultSnippet = `<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
placeholder="Pick a font"
|
||||
options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const comboboxDescriptionSnippet = `<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
description="Choose a font family for your document"
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const comboboxIconSnippet = `<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
icon={<RiCloudLine />}
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const comboboxSizesSnippet = `<Flex>
|
||||
<Combobox
|
||||
size="small"
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>
|
||||
<Combobox
|
||||
size="medium"
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>
|
||||
</Flex>`;
|
||||
|
||||
export const comboboxDisabledSnippet = `<Combobox
|
||||
isDisabled
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const comboboxResponsiveSnippet = `<Combobox
|
||||
size={{ initial: 'small', lg: 'medium' }}
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const comboboxDisabledOptionsSnippet = `<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
placeholder="Pick a font"
|
||||
disabledKeys={['cursive', 'serif']}
|
||||
options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const comboboxCustomValueSnippet = `<Combobox
|
||||
name="country"
|
||||
label="Country"
|
||||
allowsCustomValue
|
||||
placeholder="Type any country"
|
||||
options={[
|
||||
{ value: 'us', label: 'United States' },
|
||||
{ value: 'ca', label: 'Canada' },
|
||||
{ value: 'uk', label: 'United Kingdom' },
|
||||
{ value: 'fr', label: 'France' },
|
||||
{ value: 'de', label: 'Germany' },
|
||||
// ... more options
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const comboboxSectionsSnippet = `<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
options={[
|
||||
{
|
||||
title: 'Serif Fonts',
|
||||
options: [
|
||||
{ value: 'times', label: 'Times New Roman' },
|
||||
{ value: 'georgia', label: 'Georgia' },
|
||||
{ value: 'garamond', label: 'Garamond' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sans-Serif Fonts',
|
||||
options: [
|
||||
{ value: 'arial', label: 'Arial' },
|
||||
{ value: 'helvetica', label: 'Helvetica' },
|
||||
{ value: 'verdana', label: 'Verdana' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>`;
|
||||
@@ -0,0 +1,45 @@
|
||||
'use client';
|
||||
|
||||
import { DateRangePicker } from '../../../../../packages/ui/src/components/DateRangePicker/DateRangePicker';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { parseDate } from '@internationalized/date';
|
||||
|
||||
export const WithLabel = () => {
|
||||
return <DateRangePicker label="Date range" style={{ maxWidth: '280px' }} />;
|
||||
};
|
||||
|
||||
export const Sizes = () => {
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="4"
|
||||
style={{ width: '100%', maxWidth: '360px' }}
|
||||
>
|
||||
<DateRangePicker label="Small" size="small" />
|
||||
<DateRangePicker label="Medium" size="medium" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithDefaultValue = () => {
|
||||
return (
|
||||
<DateRangePicker
|
||||
label="Booking period"
|
||||
defaultValue={{
|
||||
start: parseDate('2025-02-03'),
|
||||
end: parseDate('2025-02-14'),
|
||||
}}
|
||||
style={{ maxWidth: '360px' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Disabled = () => {
|
||||
return (
|
||||
<DateRangePicker
|
||||
label="Date range"
|
||||
isDisabled
|
||||
style={{ maxWidth: '360px' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { dateRangePickerPropDefs } from './props-definition';
|
||||
import {
|
||||
dateRangePickerUsageSnippet,
|
||||
withLabelSnippet,
|
||||
sizesSnippet,
|
||||
withDefaultValueSnippet,
|
||||
disabledSnippet,
|
||||
} from './snippets';
|
||||
import { WithLabel, Sizes, WithDefaultValue, Disabled } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { DateRangePickerDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ReactAriaLink } from '@/components/ReactAriaLink';
|
||||
|
||||
export const reactAriaUrls = {
|
||||
dateRangePicker: 'https://react-aria.adobe.com/DateRangePicker',
|
||||
};
|
||||
|
||||
<PageTitle
|
||||
title="DateRangePicker"
|
||||
description="A date range picker that combines two date fields and a calendar popover for selecting a start and end date."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<WithLabel />}
|
||||
code={withLabelSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={dateRangePickerUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={dateRangePickerPropDefs} />
|
||||
|
||||
<ReactAriaLink
|
||||
component="DateRangePicker"
|
||||
href={reactAriaUrls.dateRangePicker}
|
||||
/>
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={sizesSnippet}
|
||||
layout="side-by-side"
|
||||
/>
|
||||
|
||||
### With default value
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithDefaultValue />}
|
||||
code={withDefaultValueSnippet}
|
||||
layout="side-by-side"
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={disabledSnippet}
|
||||
layout="side-by-side"
|
||||
/>
|
||||
|
||||
<Theming definition={DateRangePickerDefinition} />
|
||||
|
||||
<ChangelogComponent component="date-range-picker" />
|
||||
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
import { Chip } from '@/components/Chip';
|
||||
|
||||
export const dateRangePickerPropDefs: Record<string, PropDef> = {
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'small',
|
||||
responsive: true,
|
||||
description: (
|
||||
<>
|
||||
Visual size of the picker. Use <Chip>small</Chip> for dense layouts,{' '}
|
||||
<Chip>medium</Chip> for prominent fields.
|
||||
</>
|
||||
),
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
description: 'Visible label displayed above the picker.',
|
||||
},
|
||||
secondaryLabel: {
|
||||
type: 'string',
|
||||
description: (
|
||||
<>
|
||||
Secondary text shown next to the label. If not provided and isRequired
|
||||
is true, displays <Chip>Required</Chip>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Help text displayed below the label.',
|
||||
},
|
||||
value: {
|
||||
type: 'enum',
|
||||
values: ['RangeValue<DateValue>'],
|
||||
description: 'Controlled value of the date range.',
|
||||
},
|
||||
defaultValue: {
|
||||
type: 'enum',
|
||||
values: ['RangeValue<DateValue>'],
|
||||
description: 'Default value for uncontrolled usage.',
|
||||
},
|
||||
onChange: {
|
||||
type: 'enum',
|
||||
values: ['(value: RangeValue<DateValue> | null) => void'],
|
||||
description: 'Handler called when the selected range changes.',
|
||||
},
|
||||
granularity: {
|
||||
type: 'enum',
|
||||
values: ['day', 'hour', 'minute', 'second'],
|
||||
default: 'day',
|
||||
description:
|
||||
'Smallest unit displayed. Defaults to "day" for dates and "minute" for times.',
|
||||
},
|
||||
minValue: {
|
||||
type: 'enum',
|
||||
values: ['DateValue'],
|
||||
description: 'Minimum allowed date. Dates before this are disabled.',
|
||||
},
|
||||
maxValue: {
|
||||
type: 'enum',
|
||||
values: ['DateValue'],
|
||||
description: 'Maximum allowed date. Dates after this are disabled.',
|
||||
},
|
||||
isDateUnavailable: {
|
||||
type: 'enum',
|
||||
values: ['(date: DateValue) => boolean'],
|
||||
description:
|
||||
'Callback invoked for each calendar date. Return true to mark a date as unavailable.',
|
||||
},
|
||||
allowsNonContiguousRanges: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'When combined with isDateUnavailable, allows selecting ranges that contain unavailable dates.',
|
||||
},
|
||||
startName: {
|
||||
type: 'string',
|
||||
description: 'Form field name for the start date, submitted as ISO 8601.',
|
||||
},
|
||||
endName: {
|
||||
type: 'string',
|
||||
description: 'Form field name for the end date, submitted as ISO 8601.',
|
||||
},
|
||||
isRequired: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the field is required for form submission.',
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the picker is disabled.',
|
||||
},
|
||||
isReadOnly: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the picker is read-only.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
export const dateRangePickerUsageSnippet = `import { DateRangePicker } from '@backstage/ui';
|
||||
|
||||
<DateRangePicker label="Date range" />`;
|
||||
|
||||
export const withLabelSnippet = `<DateRangePicker
|
||||
label="Date range"
|
||||
description="Select a start and end date for your event."
|
||||
/>`;
|
||||
|
||||
export const sizesSnippet = `<Flex direction="column" gap="4">
|
||||
<DateRangePicker label="Small" size="small" />
|
||||
<DateRangePicker label="Medium" size="medium" />
|
||||
</Flex>`;
|
||||
|
||||
export const withDefaultValueSnippet = `import { parseDate } from '@internationalized/date';
|
||||
|
||||
<DateRangePicker
|
||||
label="Booking period"
|
||||
defaultValue={{
|
||||
start: parseDate('2025-02-03'),
|
||||
end: parseDate('2025-02-14'),
|
||||
}}
|
||||
/>`;
|
||||
|
||||
export const disabledSnippet = `<DateRangePicker
|
||||
label="Date range"
|
||||
isDisabled
|
||||
/>`;
|
||||
@@ -1,6 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { Header } from '../../../../../packages/ui/src/components/Header/Header';
|
||||
import { HeaderMetadataUsers } from '../../../../../packages/ui/src/components/Header/HeaderMetadataUsers';
|
||||
import { HeaderMetadataStatus } from '../../../../../packages/ui/src/components/Header/HeaderMetadataStatus';
|
||||
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
|
||||
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
|
||||
import {
|
||||
@@ -11,6 +13,29 @@ import {
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { RiMore2Line } from '@remixicon/react';
|
||||
|
||||
const users = {
|
||||
giles: {
|
||||
name: 'Giles Peyton-Nicoll',
|
||||
src: 'https://i.pravatar.cc/150?u=giles',
|
||||
href: '/users/giles',
|
||||
},
|
||||
alice: {
|
||||
name: 'Alice Johnson',
|
||||
src: 'https://i.pravatar.cc/150?u=alice42',
|
||||
href: '/users/alice',
|
||||
},
|
||||
bob: {
|
||||
name: 'Bob Smith',
|
||||
src: 'https://i.pravatar.cc/150?u=bob',
|
||||
href: '/users/bob',
|
||||
},
|
||||
carol: {
|
||||
name: 'Carol Williams',
|
||||
src: 'https://i.pravatar.cc/150?u=carol',
|
||||
href: '/users/carol',
|
||||
},
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{ id: 'overview', label: 'Overview', href: '/overview' },
|
||||
{ id: 'checks', label: 'Checks', href: '/checks' },
|
||||
@@ -29,12 +54,37 @@ const breadcrumbs = [
|
||||
},
|
||||
];
|
||||
|
||||
const tags = [
|
||||
{ label: 'TypeScript' },
|
||||
{ label: 'Platform', href: '/platform' },
|
||||
];
|
||||
|
||||
const metadataUsers = [
|
||||
{ label: 'Type', value: 'website' },
|
||||
{
|
||||
label: 'Status',
|
||||
value: <HeaderMetadataStatus label="Passing" color="success" />,
|
||||
},
|
||||
{
|
||||
label: 'Owner',
|
||||
value: <HeaderMetadataUsers users={[users.giles]} />,
|
||||
},
|
||||
{
|
||||
label: 'Contributors',
|
||||
value: (
|
||||
<HeaderMetadataUsers users={[users.alice, users.bob, users.carol]} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export const WithEverything = () => (
|
||||
<MemoryRouter initialEntries={['/overview']}>
|
||||
<Header
|
||||
title="Page Title"
|
||||
tags={tags}
|
||||
description="A short description of this page. Supports [inline links](https://backstage.io)."
|
||||
metadata={metadataUsers}
|
||||
tabs={tabs.slice(0, 2)}
|
||||
breadcrumbs={breadcrumbs.slice(0, 2)}
|
||||
customActions={
|
||||
<>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
@@ -45,6 +95,84 @@ export const WithEverything = () => (
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithMetadataUsers = () => (
|
||||
<MemoryRouter>
|
||||
<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{ label: 'Type', value: 'website' },
|
||||
{
|
||||
label: 'Owner',
|
||||
value: <HeaderMetadataUsers users={[users.giles]} />,
|
||||
},
|
||||
{
|
||||
label: 'Contributors',
|
||||
value: (
|
||||
<HeaderMetadataUsers
|
||||
users={[users.alice, users.bob, users.carol]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithTags = () => (
|
||||
<MemoryRouter>
|
||||
<Header title="Page Title" tags={tags} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithDescription = () => (
|
||||
<MemoryRouter>
|
||||
<Header
|
||||
title="Page Title"
|
||||
description="A short description of this page. Supports [inline links](https://backstage.io)."
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithMetadata = () => (
|
||||
<MemoryRouter>
|
||||
<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{ label: 'Owner', value: 'platform-team' },
|
||||
{ label: 'Type', value: 'website' },
|
||||
]}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithMetadataStatus = () => (
|
||||
<MemoryRouter>
|
||||
<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{
|
||||
label: 'Status',
|
||||
value: <HeaderMetadataStatus label="Passing" color="success" />,
|
||||
},
|
||||
{
|
||||
label: 'Build',
|
||||
value: (
|
||||
<HeaderMetadataStatus
|
||||
label="Failed"
|
||||
color="danger"
|
||||
href="/builds/123"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Coverage',
|
||||
value: <HeaderMetadataStatus label="Warning" color="warning" />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithLongBreadcrumbs = () => (
|
||||
<MemoryRouter>
|
||||
<Header title="Page Title" breadcrumbs={breadcrumbs.slice(0, 2)} />
|
||||
|
||||
@@ -3,17 +3,28 @@ import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import {
|
||||
WithEverything,
|
||||
WithLongBreadcrumbs,
|
||||
WithTabs,
|
||||
WithTags,
|
||||
WithDescription,
|
||||
WithMetadata,
|
||||
WithMetadataUsers,
|
||||
WithMetadataStatus,
|
||||
WithCustomActions,
|
||||
WithMenu,
|
||||
} from './components';
|
||||
import { headerPagePropDefs } from './props-definition';
|
||||
import {
|
||||
headerPagePropDefs,
|
||||
headerMetadataUsersPropDefs,
|
||||
} from './props-definition';
|
||||
import {
|
||||
usage,
|
||||
defaultSnippet,
|
||||
withTabs,
|
||||
withBreadcrumbs,
|
||||
withTags,
|
||||
withDescription,
|
||||
withMetadata,
|
||||
withMetadataUsers,
|
||||
withMetadataStatus,
|
||||
withCustomActions,
|
||||
withMenu,
|
||||
} from './snippets';
|
||||
@@ -24,7 +35,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Header"
|
||||
description="A secondary header with title, breadcrumbs, tabs, and actions."
|
||||
description="A secondary header with title, tags, description, metadata, tabs, and actions."
|
||||
/>
|
||||
|
||||
<Snippet py={4} preview={<WithEverything />} code={defaultSnippet} />
|
||||
@@ -39,11 +50,37 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
## Examples
|
||||
|
||||
### Breadcrumbs
|
||||
### Tags
|
||||
|
||||
Labels are truncated at 240px.
|
||||
Tags are rendered above the title. Each tag with an `href` renders as a link; tags without `href` render as plain text. Tags are separated by a small circle divider.
|
||||
|
||||
<Snippet open preview={<WithLongBreadcrumbs />} code={withBreadcrumbs} />
|
||||
<Snippet open preview={<WithTags />} code={withTags} />
|
||||
|
||||
### Description
|
||||
|
||||
The description accepts a markdown string with support for inline links. Bold, italic, and block-level markdown are not rendered.
|
||||
|
||||
<Snippet open preview={<WithDescription />} code={withDescription} />
|
||||
|
||||
### Metadata
|
||||
|
||||
Key-value pairs displayed below the description.
|
||||
|
||||
<Snippet open preview={<WithMetadata />} code={withMetadata} />
|
||||
|
||||
### Metadata with users
|
||||
|
||||
Use `HeaderMetadataUsers` as the metadata value to display users as avatars. A single user shows the avatar with their name beside it. Multiple users show a row of avatars — hover to reveal each name via tooltip. When a user has an `href`, the avatar and name become links.
|
||||
|
||||
<Snippet open preview={<WithMetadataUsers />} code={withMetadataUsers} />
|
||||
|
||||
<PropsTable data={headerMetadataUsersPropDefs} />
|
||||
|
||||
### Metadata with status
|
||||
|
||||
Use `HeaderMetadataStatus` as the metadata value to display a status indicator. The dot colour is driven by the `color` prop which maps to BUI status tokens. Pass an `href` to make the label a link.
|
||||
|
||||
<Snippet open preview={<WithMetadataStatus />} code={withMetadataStatus} />
|
||||
|
||||
### Tabs
|
||||
|
||||
|
||||
@@ -5,6 +5,51 @@ export const headerPagePropDefs: Record<string, PropDef> = {
|
||||
type: 'string',
|
||||
description: 'Page heading displayed in the header.',
|
||||
},
|
||||
tags: {
|
||||
type: 'complex',
|
||||
description:
|
||||
'Items displayed above the title. Each tag renders as a link when href is provided, or as plain text otherwise. Tags are separated by a small circle divider.',
|
||||
complexType: {
|
||||
name: 'HeaderTag[]',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the tag.',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'URL to navigate to when the tag is clicked.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Markdown string rendered below the title. Only inline links are supported. Bold, italic, and block-level markdown are not rendered.',
|
||||
},
|
||||
metadata: {
|
||||
type: 'complex',
|
||||
description: 'Key-value pairs displayed below the description.',
|
||||
complexType: {
|
||||
name: 'HeaderMetadataItem[]',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The key label, displayed in secondary color.',
|
||||
},
|
||||
value: {
|
||||
type: 'string | ReactNode',
|
||||
required: true,
|
||||
description:
|
||||
'The value to display alongside the label. Pass a string for plain text or a ReactNode for custom content such as HeaderMetadataUsers.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
customActions: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
@@ -49,6 +94,7 @@ export const headerPagePropDefs: Record<string, PropDef> = {
|
||||
},
|
||||
breadcrumbs: {
|
||||
type: 'complex',
|
||||
deprecated: true,
|
||||
description: 'Breadcrumb trail displayed above the title.',
|
||||
complexType: {
|
||||
name: 'HeaderBreadcrumb[]',
|
||||
@@ -68,3 +114,33 @@ export const headerPagePropDefs: Record<string, PropDef> = {
|
||||
},
|
||||
...classNamePropDefs,
|
||||
};
|
||||
|
||||
export const headerMetadataUsersPropDefs: Record<string, PropDef> = {
|
||||
users: {
|
||||
type: 'complex',
|
||||
description:
|
||||
'List of users to display. A single user shows the avatar with their name beside it. Multiple users show a row of avatars with names revealed on hover via tooltip.',
|
||||
complexType: {
|
||||
name: 'HeaderMetadataUser[]',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description:
|
||||
'Display name shown beside the avatar (single) or in the tooltip (multiple).',
|
||||
},
|
||||
src: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'URL for the avatar image.',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description:
|
||||
'When provided, the avatar becomes a link and the name is rendered as a Link component.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,15 +2,41 @@ export const usage = `import { Header } from '@backstage/ui';
|
||||
|
||||
<Header title="Page Title" />`;
|
||||
|
||||
export const defaultSnippet = `<Header
|
||||
export const defaultSnippet = `import { Header, HeaderMetadataUsers, HeaderMetadataStatus } from '@backstage/ui';
|
||||
|
||||
<Header
|
||||
title="Page Title"
|
||||
breadcrumbs={[
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Dashboard', href: '/dashboard' },
|
||||
tags={[
|
||||
{ label: 'TypeScript' },
|
||||
{ label: 'Platform', href: '/platform' },
|
||||
]}
|
||||
description="A short description. Supports [inline links](https://backstage.io)."
|
||||
metadata={[
|
||||
{ label: 'Type', value: 'website' },
|
||||
{
|
||||
label: 'Status',
|
||||
value: <HeaderMetadataStatus label="Passing" color="success" />,
|
||||
},
|
||||
{
|
||||
label: 'Owner',
|
||||
value: <HeaderMetadataUsers users={[{ name: 'Giles Peyton-Nicoll', src: '...', href: '/users/giles' }]} />,
|
||||
},
|
||||
{
|
||||
label: 'Contributors',
|
||||
value: (
|
||||
<HeaderMetadataUsers
|
||||
users={[
|
||||
{ name: 'Alice Johnson', src: '...', href: '/users/alice' },
|
||||
{ name: 'Bob Smith', src: '...', href: '/users/bob' },
|
||||
{ name: 'Carol Williams', src: '...', href: '/users/carol' },
|
||||
]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
tabs={[
|
||||
{ id: 'overview', label: 'Overview', href: '/overview' },
|
||||
{ id: 'settings', label: 'Settings', href: '/settings' },
|
||||
{ id: 'checks', label: 'Checks', href: '/checks' },
|
||||
]}
|
||||
customActions={
|
||||
<>
|
||||
@@ -54,3 +80,70 @@ export const withMenu = `<Header
|
||||
</MenuTrigger>
|
||||
}
|
||||
/>`;
|
||||
|
||||
export const withTags = `<Header
|
||||
title="Page Title"
|
||||
tags={[
|
||||
{ label: 'TypeScript' },
|
||||
{ label: 'Platform', href: '/platform' },
|
||||
{ label: 'Gold' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withDescription = `<Header
|
||||
title="Page Title"
|
||||
description="A short description. Supports [inline links](https://backstage.io)."
|
||||
/>`;
|
||||
|
||||
export const withMetadata = `<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{ label: 'Owner', value: 'platform-team' },
|
||||
{ label: 'Type', value: 'website' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withMetadataStatus = `import { Header, HeaderMetadataStatus } from '@backstage/ui';
|
||||
|
||||
<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{
|
||||
label: 'Status',
|
||||
value: <HeaderMetadataStatus label="Passing" color="success" />,
|
||||
},
|
||||
{
|
||||
label: 'Build',
|
||||
value: <HeaderMetadataStatus label="Failed" color="danger" href="/builds/123" />,
|
||||
},
|
||||
{
|
||||
label: 'Coverage',
|
||||
value: <HeaderMetadataStatus label="Warning" color="warning" />,
|
||||
},
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withMetadataUsers = `import { Header, HeaderMetadataUsers } from '@backstage/ui';
|
||||
|
||||
<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{ label: 'Type', value: 'website' },
|
||||
{
|
||||
label: 'Owner',
|
||||
value: <HeaderMetadataUsers users={[{ name: 'Giles Peyton-Nicoll', src: '...', href: '/users/giles' }]} />,
|
||||
},
|
||||
{
|
||||
label: 'Contributors',
|
||||
value: (
|
||||
<HeaderMetadataUsers
|
||||
users={[
|
||||
{ name: 'Alice Johnson', src: '...', href: '/users/alice' },
|
||||
{ name: 'Bob Smith', src: '...', href: '/users/bob' },
|
||||
{ name: 'Carol Williams', src: '...', href: '/users/carol' },
|
||||
]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>`;
|
||||
|
||||
@@ -40,6 +40,33 @@ const skills = [
|
||||
{ value: 'swift', label: 'Swift' },
|
||||
];
|
||||
|
||||
const sectionedFonts = [
|
||||
{
|
||||
title: 'Serif Fonts',
|
||||
options: [
|
||||
{ value: 'times', label: 'Times New Roman' },
|
||||
{ value: 'georgia', label: 'Georgia' },
|
||||
{ value: 'garamond', label: 'Garamond' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sans-Serif Fonts',
|
||||
options: [
|
||||
{ value: 'arial', label: 'Arial' },
|
||||
{ value: 'helvetica', label: 'Helvetica' },
|
||||
{ value: 'verdana', label: 'Verdana' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Monospace Fonts',
|
||||
options: [
|
||||
{ value: 'courier', label: 'Courier New' },
|
||||
{ value: 'consolas', label: 'Consolas' },
|
||||
{ value: 'fira', label: 'Fira Code' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const Preview = () => (
|
||||
<Select
|
||||
label="Font Family"
|
||||
@@ -148,3 +175,23 @@ export const SearchableMultiple = () => (
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithSections = () => (
|
||||
<Select
|
||||
label="Font Family"
|
||||
options={sectionedFonts}
|
||||
name="font"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const SearchableWithSections = () => (
|
||||
<Select
|
||||
label="Font Family"
|
||||
searchable
|
||||
searchPlaceholder="Search fonts..."
|
||||
options={sectionedFonts}
|
||||
name="font"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -12,8 +12,14 @@ import {
|
||||
Searchable,
|
||||
MultipleSelection,
|
||||
SearchableMultiple,
|
||||
WithSections,
|
||||
SearchableWithSections,
|
||||
} from './components';
|
||||
import { selectPropDefs } from './props-definition';
|
||||
import {
|
||||
selectPropDefs,
|
||||
optionPropDefs,
|
||||
optionSectionPropDefs,
|
||||
} from './props-definition';
|
||||
import {
|
||||
selectUsageSnippet,
|
||||
selectDefaultSnippet,
|
||||
@@ -26,6 +32,8 @@ import {
|
||||
selectMultipleSnippet,
|
||||
selectSearchableMultipleSnippet,
|
||||
selectDisabledOptionsSnippet,
|
||||
selectSectionsSnippet,
|
||||
selectSearchableSectionsSnippet,
|
||||
} from './snippets';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
@@ -58,6 +66,18 @@ export const reactAriaUrls = {
|
||||
|
||||
<ReactAriaLink component="Select" href={reactAriaUrls.select} />
|
||||
|
||||
### Option types
|
||||
|
||||
The `options` prop accepts an array containing either of the following shapes.
|
||||
|
||||
#### `Option`
|
||||
|
||||
<PropsTable data={optionPropDefs} />
|
||||
|
||||
#### `OptionSection`
|
||||
|
||||
<PropsTable data={optionSectionPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Label and description
|
||||
@@ -136,6 +156,29 @@ Combine search and multiple selection.
|
||||
code={selectSearchableMultipleSnippet}
|
||||
/>
|
||||
|
||||
### With sections
|
||||
|
||||
Group options under section headings by passing objects with a `title` and a
|
||||
nested `options` array.
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<WithSections />}
|
||||
code={selectSectionsSnippet}
|
||||
/>
|
||||
|
||||
### Searchable with sections
|
||||
|
||||
Sections are preserved when filtering with `searchable`.
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<SearchableWithSections />}
|
||||
code={selectSearchableSectionsSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Size can change at different breakpoints.
|
||||
|
||||
@@ -5,30 +5,48 @@ import {
|
||||
} from '@/utils/propDefs';
|
||||
import { Chip } from '@/components/Chip';
|
||||
|
||||
export const optionPropDefs: Record<string, PropDef> = {
|
||||
value: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Unique value for the option.',
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the option.',
|
||||
},
|
||||
disabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the option is disabled.',
|
||||
},
|
||||
};
|
||||
|
||||
export const optionSectionPropDefs: Record<string, PropDef> = {
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Heading displayed above the grouped options.',
|
||||
},
|
||||
options: {
|
||||
type: 'enum',
|
||||
values: ['Option[]'],
|
||||
required: true,
|
||||
description: 'Options nested inside the section.',
|
||||
},
|
||||
};
|
||||
|
||||
export const selectPropDefs: Record<string, PropDef> = {
|
||||
options: {
|
||||
type: 'complex',
|
||||
description: 'Array of options to display in the dropdown.',
|
||||
complexType: {
|
||||
name: 'SelectOption[]',
|
||||
properties: {
|
||||
value: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Unique value for the option.',
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the option.',
|
||||
},
|
||||
disabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
description: 'Whether the option is disabled.',
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'enum',
|
||||
values: ['(Option | OptionSection)[]'],
|
||||
description: (
|
||||
<>
|
||||
Options to display in the dropdown. Pass <Chip>Option</Chip> objects
|
||||
directly, or <Chip>OptionSection</Chip> objects to render grouped
|
||||
options under section headings.
|
||||
</>
|
||||
),
|
||||
},
|
||||
selectionMode: {
|
||||
type: 'enum',
|
||||
|
||||
@@ -110,3 +110,49 @@ export const selectDisabledOptionsSnippet = `<Select
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const selectSectionsSnippet = `<Select
|
||||
name="font"
|
||||
label="Font Family"
|
||||
options={[
|
||||
{
|
||||
title: 'Serif Fonts',
|
||||
options: [
|
||||
{ value: 'times', label: 'Times New Roman' },
|
||||
{ value: 'georgia', label: 'Georgia' },
|
||||
{ value: 'garamond', label: 'Garamond' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sans-Serif Fonts',
|
||||
options: [
|
||||
{ value: 'arial', label: 'Arial' },
|
||||
{ value: 'helvetica', label: 'Helvetica' },
|
||||
{ value: 'verdana', label: 'Verdana' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const selectSearchableSectionsSnippet = `<Select
|
||||
name="font"
|
||||
label="Font Family"
|
||||
searchable
|
||||
searchPlaceholder="Search fonts..."
|
||||
options={[
|
||||
{
|
||||
title: 'Serif Fonts',
|
||||
options: [
|
||||
{ value: 'times', label: 'Times New Roman' },
|
||||
{ value: 'georgia', label: 'Georgia' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sans-Serif Fonts',
|
||||
options: [
|
||||
{ value: 'arial', label: 'Arial' },
|
||||
{ value: 'helvetica', label: 'Helvetica' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>`;
|
||||
|
||||
@@ -28,7 +28,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { SliderDefinition } from '../../../utils/definitions';
|
||||
|
||||
export const reactAriaUrls = {
|
||||
slider: 'https://react-spectrum.adobe.com/react-aria/Slider.html',
|
||||
slider: 'https://react-aria.adobe.com/Slider',
|
||||
};
|
||||
|
||||
<PageTitle
|
||||
|
||||
@@ -126,9 +126,9 @@ Configure page size and available options through `paginationOptions`. The table
|
||||
|
||||
### Search
|
||||
|
||||
The `useTable` hook returns a `search` object with `value` and `onChange` properties, ready to connect to a search input. With `mode: 'complete'`, provide a `searchFn` that filters the dataset based on the search query.
|
||||
The `useTable` hook returns a `search` object with `value` and `onChange` properties, ready to connect to a search input. With `mode: 'complete'`, provide a `searchFn` that filters the dataset based on the search query. When the search query changes, pagination resets to the first page automatically.
|
||||
|
||||
The search state is debounced internally, so rapid typing doesn't trigger excessive re-filtering. When the search query changes, pagination resets to the first page automatically.
|
||||
In `complete` mode, set `searchDebounceMs` (and/or `filterDebounceMs`) to defer the filtering pipeline until typing settles — useful for large datasets. Both default to `0` (no debounce). The controlled `search` / `onSearchChange` (and `filter` / `onFilterChange`) surface continues to fire on every change. For `offset` and `cursor` modes, requests are already debounced internally, so these options don't apply.
|
||||
|
||||
For server-side search with `offset` or `cursor` modes, the search query is passed to your `getData` function. See [Server-Side Data](#server-side-data).
|
||||
|
||||
@@ -202,11 +202,11 @@ Use `mode: 'cursor'` when your API uses cursor-based pagination (common with Gra
|
||||
|
||||
<CodeBlock code={tableCursorPaginationSnippet} />
|
||||
|
||||
### Loading States
|
||||
### Pending States
|
||||
|
||||
When fetching data, the table shows a loading state. If the user triggers a new query (by paginating, sorting, or searching) while previous data is displayed, the table enters a "stale" state where it continues showing the previous data until new data arrives. This prevents jarring layout shifts.
|
||||
When fetching data, the table shows a pending state. If the user triggers a new query (by paginating, sorting, or searching) while previous data is displayed, the table enters a "stale" state where it continues showing the previous data until new data arrives. This prevents jarring layout shifts.
|
||||
|
||||
You can access these states via `tableProps.loading` and `tableProps.isStale` if you need to render additional loading indicators.
|
||||
You can access these states via `tableProps.isPending` and `tableProps.isStale` if you need to render additional pending indicators.
|
||||
|
||||
## Combining Features
|
||||
|
||||
|
||||
@@ -157,6 +157,28 @@ export const useTableOptionsPropDefs: Record<string, PropDef> = {
|
||||
</>
|
||||
),
|
||||
},
|
||||
searchDebounceMs: {
|
||||
type: 'number',
|
||||
description: (
|
||||
<>
|
||||
Trailing-edge debounce delay (ms) applied to the search value before it
|
||||
reaches <Chip>searchFn</Chip>. Defaults to <Chip>0</Chip> (no debounce).
|
||||
Does not affect the controlled <Chip>onSearchChange</Chip> callback.
|
||||
Only used with <Chip>complete</Chip> mode.
|
||||
</>
|
||||
),
|
||||
},
|
||||
filterDebounceMs: {
|
||||
type: 'number',
|
||||
description: (
|
||||
<>
|
||||
Trailing-edge debounce delay (ms) applied to the filter value before it
|
||||
reaches <Chip>filterFn</Chip>. Defaults to <Chip>0</Chip> (no debounce).
|
||||
Does not affect the controlled <Chip>onFilterChange</Chip> callback.
|
||||
Only used with <Chip>complete</Chip> mode.
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const useTableReturnPropDefs: Record<string, PropDef> = {
|
||||
@@ -166,7 +188,7 @@ export const useTableReturnPropDefs: Record<string, PropDef> = {
|
||||
description: (
|
||||
<>
|
||||
Props to spread onto the <Chip>Table</Chip> component. Includes data,
|
||||
loading, error, pagination, and sort state.
|
||||
isPending, error, pagination, and sort state.
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -207,10 +229,15 @@ export const tablePropDefs: Record<string, PropDef> = {
|
||||
values: ['T[]'],
|
||||
description: 'Array of data items to display in the table.',
|
||||
},
|
||||
isPending: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description: 'Whether the table is in a pending state.',
|
||||
},
|
||||
loading: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description: 'Whether the table is in a loading state.',
|
||||
description: 'Deprecated. Use `isPending` instead.',
|
||||
},
|
||||
isStale: {
|
||||
type: 'boolean',
|
||||
@@ -466,17 +493,22 @@ export const tableRootPropDefs: Record<string, PropDef> = {
|
||||
</>
|
||||
),
|
||||
},
|
||||
loading: {
|
||||
isPending: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description: (
|
||||
<>
|
||||
Whether the table is in a loading state (e.g., initial data fetch). Adds{' '}
|
||||
<Chip>aria-busy</Chip> attribute and <Chip>data-loading</Chip> data
|
||||
Whether the table is in a pending state (e.g., initial data fetch). Adds{' '}
|
||||
<Chip>aria-busy</Chip> attribute and <Chip>data-ispending</Chip> data
|
||||
attribute for styling.
|
||||
</>
|
||||
),
|
||||
},
|
||||
loading: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description: 'Deprecated. Use `isPending` instead.',
|
||||
},
|
||||
};
|
||||
|
||||
export const columnPropDefs: Record<string, PropDef> = {
|
||||
|
||||
@@ -264,6 +264,42 @@ pressed, and disabled variants for interactive states.
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
## Inherited background
|
||||
|
||||
`--bui-bg-inherit` resolves to the bg color of the nearest enclosing element with a
|
||||
`data-bg` attribute (set by `Box`, `Flex`, `Grid`, `Card`, `Accordion`, or any
|
||||
element that explicitly sets `data-bg`). When no such ancestor exists it falls
|
||||
back to `--bui-bg-app`. Use it from CSS when a sticky, fixed, or otherwise
|
||||
overlapping element needs to match its surrounding bg without hardcoding a level.
|
||||
|
||||
<CodeBlock
|
||||
code={`.searchBarContainer {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--bui-bg-inherit);
|
||||
}`}
|
||||
/>
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--bui-bg-inherit</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
Resolves to the bg color of the nearest enclosing `data-bg` ancestor,
|
||||
falling back to `--bui-bg-app`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
## Solid background colors
|
||||
|
||||
<Table.Root>
|
||||
|
||||
@@ -4,12 +4,18 @@ import styles from './styles.module.css';
|
||||
export const Chip = ({
|
||||
children,
|
||||
head = false,
|
||||
deprecated = false,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
head?: boolean;
|
||||
deprecated?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<span className={`${styles.chip} ${head ? styles.head : ''}`}>
|
||||
<span
|
||||
className={`${styles.chip} ${head ? styles.head : ''} ${
|
||||
deprecated ? styles.deprecated : ''
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.deprecated {
|
||||
background-color: #fff4e5;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
[data-theme-mode='dark'] .chip {
|
||||
background-color: #2c2c2c;
|
||||
color: #fff;
|
||||
@@ -22,3 +27,8 @@
|
||||
[data-theme-mode='dark'] .chip.head {
|
||||
background-color: #33405b;
|
||||
}
|
||||
|
||||
[data-theme-mode='dark'] .chip.deprecated {
|
||||
background-color: #3d2a10;
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,12 @@ export const PropsTable = <T extends Record<string, PropData>>({
|
||||
|
||||
switch (column) {
|
||||
case 'prop':
|
||||
return <Chip head>{propName}</Chip>;
|
||||
return (
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.375rem' }}>
|
||||
<Chip head>{propName}</Chip>
|
||||
{propData.deprecated && <Chip deprecated>deprecated</Chip>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'type':
|
||||
return (
|
||||
|
||||
@@ -49,10 +49,19 @@ export const components: Page[] = [
|
||||
title: 'CheckboxGroup',
|
||||
slug: 'checkbox-group',
|
||||
},
|
||||
{
|
||||
title: 'Combobox',
|
||||
slug: 'combobox',
|
||||
status: 'new',
|
||||
},
|
||||
{
|
||||
title: 'Container',
|
||||
slug: 'container',
|
||||
},
|
||||
{
|
||||
title: 'DateRangePicker',
|
||||
slug: 'date-range-picker',
|
||||
},
|
||||
{
|
||||
title: 'Dialog',
|
||||
slug: 'dialog',
|
||||
|
||||
@@ -44,6 +44,7 @@ export type PropDef = {
|
||||
required?: boolean;
|
||||
responsive?: boolean;
|
||||
description?: ReactNode;
|
||||
deprecated?: boolean;
|
||||
};
|
||||
|
||||
export { breakpoints };
|
||||
|
||||
+96
-96
@@ -314,12 +314,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.1, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.5.0":
|
||||
version: 6.5.2
|
||||
resolution: "@codemirror/state@npm:6.5.2"
|
||||
"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.1, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.6.0":
|
||||
version: 6.6.0
|
||||
resolution: "@codemirror/state@npm:6.6.0"
|
||||
dependencies:
|
||||
"@marijn/find-cluster-break": "npm:^1.0.0"
|
||||
checksum: 10/5ccd3acb0c0a5b88e83fb91be39099fceb9f44a5047cc41a75d53f160e736851f65c8de40950b90c6519e6d2828e12f468db0af658dde30e938896f1c39eec91
|
||||
checksum: 10/5d624f3c8832b287d76ebb5f57c01327641875c12c58ada2a97f958dc4df8c3bb0a1ad08ed370300a4a929ee8d5c7f14a397449a0d075ac3129d60d85f077441
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -336,14 +336,14 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.34.4, @codemirror/view@npm:^6.35.0":
|
||||
version: 6.39.16
|
||||
resolution: "@codemirror/view@npm:6.39.16"
|
||||
version: 6.41.1
|
||||
resolution: "@codemirror/view@npm:6.41.1"
|
||||
dependencies:
|
||||
"@codemirror/state": "npm:^6.5.0"
|
||||
"@codemirror/state": "npm:^6.6.0"
|
||||
crelt: "npm:^1.0.6"
|
||||
style-mod: "npm:^4.1.0"
|
||||
w3c-keyname: "npm:^2.2.4"
|
||||
checksum: 10/199576febda2a91fe7676b8708627ed2e38d7e964ec8258331422fe7c7f89003eee2de7dec828e09c046de005742fd476cae6ceebc7bd994744f771253bfcbf3
|
||||
checksum: 10/ab8156db1012f94ac39d603da4c397ab1de8877f941d0cf67f79cd09fffe9f39d5de8a10611a788ce9d5a88cad2633445880955fd0dc1ad67813cbf9be97774a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -928,10 +928,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/env@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/env@npm:16.2.1"
|
||||
checksum: 10/c4f19f1767d7a1e8e9ff93cdee7e3b6a923d26d9d71f44124a797f03703ab9a18508b5ede997cc99d0307f2e0d0d1c426e9673a6c11ea10e170b87462a572236
|
||||
"@next/env@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/env@npm:16.2.3"
|
||||
checksum: 10/30ed128d8ffae47e58732ee134b78da36e2d6942da7479ec5e640d205b7822224daf2f07d7a69352dc362908eb260fc9fa7eaba1ce5e6311abeacc6ffb0fe90a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -961,58 +961,58 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-arm64@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-darwin-arm64@npm:16.2.1"
|
||||
"@next/swc-darwin-arm64@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-darwin-arm64@npm:16.2.3"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-x64@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-darwin-x64@npm:16.2.1"
|
||||
"@next/swc-darwin-x64@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-darwin-x64@npm:16.2.3"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-gnu@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:16.2.1"
|
||||
"@next/swc-linux-arm64-gnu@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:16.2.3"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-musl@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:16.2.1"
|
||||
"@next/swc-linux-arm64-musl@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:16.2.3"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-gnu@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:16.2.1"
|
||||
"@next/swc-linux-x64-gnu@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:16.2.3"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-musl@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-linux-x64-musl@npm:16.2.1"
|
||||
"@next/swc-linux-x64-musl@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-linux-x64-musl@npm:16.2.3"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-arm64-msvc@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:16.2.1"
|
||||
"@next/swc-win32-arm64-msvc@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:16.2.3"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-x64-msvc@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:16.2.1"
|
||||
"@next/swc-win32-x64-msvc@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:16.2.3"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -1175,12 +1175,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@remixicon/react@npm:^4.6.0":
|
||||
version: 4.9.0
|
||||
resolution: "@remixicon/react@npm:4.9.0"
|
||||
"@remixicon/react@npm:>=4.6.0 <4.9.0":
|
||||
version: 4.8.0
|
||||
resolution: "@remixicon/react@npm:4.8.0"
|
||||
peerDependencies:
|
||||
react: ">=18.2.0"
|
||||
checksum: 10/3d8f1d86b2bb20ab5e44d15f18811e928b0886f7710eb7a1516afb9913ba72e46facec5dfee382825139d800bcbb6704c15d0c760d0f977c12257d4af8db3295
|
||||
checksum: 10/10241f2e07826ce7d595cf9687a35c96cc9cf9eb68a9431d7dfa1b9df479dbef302874d28c0502cee2a536cf34722de7c49ed12d10a2b071e4e42ba4a278c516
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1534,9 +1534,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@uiw/codemirror-extensions-basic-setup@npm:4.25.8":
|
||||
version: 4.25.8
|
||||
resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.25.8"
|
||||
"@uiw/codemirror-extensions-basic-setup@npm:4.25.9":
|
||||
version: 4.25.9
|
||||
resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.25.9"
|
||||
dependencies:
|
||||
"@codemirror/autocomplete": "npm:^6.0.0"
|
||||
"@codemirror/commands": "npm:^6.0.0"
|
||||
@@ -1553,13 +1553,13 @@ __metadata:
|
||||
"@codemirror/search": ">=6.0.0"
|
||||
"@codemirror/state": ">=6.0.0"
|
||||
"@codemirror/view": ">=6.0.0"
|
||||
checksum: 10/a8d83465f9f3393b6e95d98ae7f3616ad57f819bce64224831d3db19647524538fc013973074a63551afa69daad9a8ab05f2e5c7441db7e30e722495d7e991d3
|
||||
checksum: 10/bab06a40bdd8fb99a0af5115511cdb812c93aac2b93ccd8a02bdf8ea06098d6be2ce1302efc560a3b577171a5cd87d34c3215e523f21450ba100b147dfcb975c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@uiw/codemirror-themes@npm:^4.23.7":
|
||||
version: 4.25.8
|
||||
resolution: "@uiw/codemirror-themes@npm:4.25.8"
|
||||
version: 4.25.9
|
||||
resolution: "@uiw/codemirror-themes@npm:4.25.9"
|
||||
dependencies:
|
||||
"@codemirror/language": "npm:^6.0.0"
|
||||
"@codemirror/state": "npm:^6.0.0"
|
||||
@@ -1568,19 +1568,19 @@ __metadata:
|
||||
"@codemirror/language": ">=6.0.0"
|
||||
"@codemirror/state": ">=6.0.0"
|
||||
"@codemirror/view": ">=6.0.0"
|
||||
checksum: 10/e9983b0f6e663ca200d36437b6b52b4061ce5ccefece6f738b15370a8a7ac6774e7139a82e9e28ae273692e25d0c0804693587ea0967e163a1c7ac8cf3859cd1
|
||||
checksum: 10/4a4fe7ae5f6c2bd37170b46c75ccabb67b47b7d3cee0de45c63fafe4f2b7569461b009e0ff5386480574e651627ed03c077833d53b6d3391102b79107ae39d15
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@uiw/react-codemirror@npm:^4.23.7":
|
||||
version: 4.25.8
|
||||
resolution: "@uiw/react-codemirror@npm:4.25.8"
|
||||
version: 4.25.9
|
||||
resolution: "@uiw/react-codemirror@npm:4.25.9"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.18.6"
|
||||
"@codemirror/commands": "npm:^6.1.0"
|
||||
"@codemirror/state": "npm:^6.1.1"
|
||||
"@codemirror/theme-one-dark": "npm:^6.0.0"
|
||||
"@uiw/codemirror-extensions-basic-setup": "npm:4.25.8"
|
||||
"@uiw/codemirror-extensions-basic-setup": "npm:4.25.9"
|
||||
codemirror: "npm:^6.0.0"
|
||||
peerDependencies:
|
||||
"@babel/runtime": ">=7.11.0"
|
||||
@@ -1590,7 +1590,7 @@ __metadata:
|
||||
codemirror: ">=6.0.0"
|
||||
react: ">=17.0.0"
|
||||
react-dom: ">=17.0.0"
|
||||
checksum: 10/8c974e22dad1ad6231f33f7db42cd5b68caaf9bea545539b06b8a89dda3427eebadf47c8f48ee0d74cdf5a25000a8fcc02bac9fe560b624955eedf1f9bb47a85
|
||||
checksum: 10/02c6ababa9307cf10aee5b32db1a0e5885485960819d708cd154bc218cf4d8b182b5fb49fa7386014401d71c8391ce211b29b2de201ad1fdece2c1716d09a74d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2338,7 +2338,7 @@ __metadata:
|
||||
"@mdx-js/react": "npm:^3.1.0"
|
||||
"@next/mdx": "npm:16.2.1"
|
||||
"@octokit/rest": "npm:^22.0.1"
|
||||
"@remixicon/react": "npm:^4.6.0"
|
||||
"@remixicon/react": "npm:>=4.6.0 <4.9.0"
|
||||
"@shikijs/transformers": "npm:^3.13.0"
|
||||
"@types/mdx": "npm:^2.0.13"
|
||||
"@types/node": "npm:^22.13.14"
|
||||
@@ -2351,9 +2351,9 @@ __metadata:
|
||||
eslint-config-next: "npm:16.2.1"
|
||||
html-react-parser: "npm:^5.2.5"
|
||||
motion: "npm:^12.4.1"
|
||||
next: "npm:16.2.1"
|
||||
next: "npm:16.2.3"
|
||||
next-mdx-remote-client: "npm:^2.1.2"
|
||||
postcss: "npm:^8.5.6"
|
||||
postcss: "npm:^8.5.10"
|
||||
postcss-import: "npm:^16.1.1"
|
||||
prop-types: "npm:^15.8.1"
|
||||
react: "npm:19.2.4"
|
||||
@@ -3103,12 +3103,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"framer-motion@npm:^12.35.2":
|
||||
version: 12.35.2
|
||||
resolution: "framer-motion@npm:12.35.2"
|
||||
"framer-motion@npm:^12.38.0":
|
||||
version: 12.38.0
|
||||
resolution: "framer-motion@npm:12.38.0"
|
||||
dependencies:
|
||||
motion-dom: "npm:^12.35.2"
|
||||
motion-utils: "npm:^12.29.2"
|
||||
motion-dom: "npm:^12.38.0"
|
||||
motion-utils: "npm:^12.36.0"
|
||||
tslib: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
"@emotion/is-prop-valid": "*"
|
||||
@@ -3121,7 +3121,7 @@ __metadata:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 10/10af699ff1e35a166ef60ceab464479b81624ef74de3ec9e11b427f86bd7bf2c8c8a9f24fb0646288b2d4b0c1b219203da351821fc568c7b91c6821594af4a3f
|
||||
checksum: 10/4d529d1648a8e31ec9859e7ff1296b7e4ef0028eb09cbc7d626068766ab53e486038b431fac33b1438a1cc076a244e6843c5a8c0f38442885832308452b4b25e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4500,27 +4500,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"motion-dom@npm:^12.35.2":
|
||||
version: 12.35.2
|
||||
resolution: "motion-dom@npm:12.35.2"
|
||||
"motion-dom@npm:^12.38.0":
|
||||
version: 12.38.0
|
||||
resolution: "motion-dom@npm:12.38.0"
|
||||
dependencies:
|
||||
motion-utils: "npm:^12.29.2"
|
||||
checksum: 10/dd009e58b178dd80b123a86199ae78ecd6b2fc6c8e03464b2daf43b4218dfcc36042ec0af8fad2c6c157198f56849f90dc033b58f46478b45fbaeaefcc2710ad
|
||||
motion-utils: "npm:^12.36.0"
|
||||
checksum: 10/78c040b46d93273932cf80c70e39845be5a442dcaf18d4345b45a9193de9dfa87c885b609943cb652115e4eac5d46ef40b452185073dd43fc328b134f9975e90
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"motion-utils@npm:^12.29.2":
|
||||
version: 12.29.2
|
||||
resolution: "motion-utils@npm:12.29.2"
|
||||
checksum: 10/ae5f9be58c07939af72334894ed1a18653d724946182a718dc3d11268ef26e63804c3f16dee5a6110596d4406b539c4513822b74f86adebef9488601c34b18b7
|
||||
"motion-utils@npm:^12.36.0":
|
||||
version: 12.36.0
|
||||
resolution: "motion-utils@npm:12.36.0"
|
||||
checksum: 10/c4a2a7ffac48ca44082d6d31b115f245025060a7e69d70dac062646d8f96c39e5662a7c8a51f255566fdf8e719ef1269a8e9aa3a04fc263bb65b5a7b61331901
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"motion@npm:^12.4.1":
|
||||
version: 12.35.2
|
||||
resolution: "motion@npm:12.35.2"
|
||||
version: 12.38.0
|
||||
resolution: "motion@npm:12.38.0"
|
||||
dependencies:
|
||||
framer-motion: "npm:^12.35.2"
|
||||
framer-motion: "npm:^12.38.0"
|
||||
tslib: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
"@emotion/is-prop-valid": "*"
|
||||
@@ -4533,7 +4533,7 @@ __metadata:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 10/3d99a53816634cbee1b38ed8a9a5d88bafbd29eb3bc02e78fc741c604972b4b88d317cf374bba30a1486f727bb1657ef8826f83e669a3b04fd1ec3ef75bfb62d
|
||||
checksum: 10/d7ae2ba3cc112c4467822956b92065239640b9c62204d3bee1780da9fc0147185373534138d39975e82bf73b5f1b28d3fb3581031e4e7e0cfb230472767bd10d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4587,19 +4587,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "next@npm:16.2.1"
|
||||
"next@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "next@npm:16.2.3"
|
||||
dependencies:
|
||||
"@next/env": "npm:16.2.1"
|
||||
"@next/swc-darwin-arm64": "npm:16.2.1"
|
||||
"@next/swc-darwin-x64": "npm:16.2.1"
|
||||
"@next/swc-linux-arm64-gnu": "npm:16.2.1"
|
||||
"@next/swc-linux-arm64-musl": "npm:16.2.1"
|
||||
"@next/swc-linux-x64-gnu": "npm:16.2.1"
|
||||
"@next/swc-linux-x64-musl": "npm:16.2.1"
|
||||
"@next/swc-win32-arm64-msvc": "npm:16.2.1"
|
||||
"@next/swc-win32-x64-msvc": "npm:16.2.1"
|
||||
"@next/env": "npm:16.2.3"
|
||||
"@next/swc-darwin-arm64": "npm:16.2.3"
|
||||
"@next/swc-darwin-x64": "npm:16.2.3"
|
||||
"@next/swc-linux-arm64-gnu": "npm:16.2.3"
|
||||
"@next/swc-linux-arm64-musl": "npm:16.2.3"
|
||||
"@next/swc-linux-x64-gnu": "npm:16.2.3"
|
||||
"@next/swc-linux-x64-musl": "npm:16.2.3"
|
||||
"@next/swc-win32-arm64-msvc": "npm:16.2.3"
|
||||
"@next/swc-win32-x64-msvc": "npm:16.2.3"
|
||||
"@swc/helpers": "npm:0.5.15"
|
||||
baseline-browser-mapping: "npm:^2.9.19"
|
||||
caniuse-lite: "npm:^1.0.30001579"
|
||||
@@ -4643,7 +4643,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
next: dist/bin/next
|
||||
checksum: 10/319c0b18173a90e53b5e5ffafa8a8fecb7cc340b77728796743edd996c7ee7652201892bff60c32f6a3b75abdff1449b77f13f6fab8fd56d4f9da47cf0fb9299
|
||||
checksum: 10/5164885daacbb36a771380e1b5efba524863e1bdf2b5a6c80413cbf1e3ab4e8ddab5716cd91ff94ca5c5c5deb2a12d1312d6d6ae994e16ebfa985fdda6134bc6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4857,9 +4857,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"picomatch@npm:^2.3.1":
|
||||
version: 2.3.1
|
||||
resolution: "picomatch@npm:2.3.1"
|
||||
checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc
|
||||
version: 2.3.2
|
||||
resolution: "picomatch@npm:2.3.2"
|
||||
checksum: 10/b788ef8148a2415b9dec12f0bb350ae6a5830f8f1950e472abc2f5225494debf7d1b75eb031df0ceaea9e8ec3e7bad599e8dbf3c60d61b42be429ba41bff4426
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4915,14 +4915,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss@npm:^8.5.6":
|
||||
version: 8.5.8
|
||||
resolution: "postcss@npm:8.5.8"
|
||||
"postcss@npm:^8.5.10":
|
||||
version: 8.5.10
|
||||
resolution: "postcss@npm:8.5.10"
|
||||
dependencies:
|
||||
nanoid: "npm:^3.3.11"
|
||||
picocolors: "npm:^1.1.1"
|
||||
source-map-js: "npm:^1.2.1"
|
||||
checksum: 10/cbacbfd7f767e2c820d4bf09a3a744834dd7d14f69ff08d1f57b1a7defce9ae5efcf31981890d9697a972a64e9965de677932ef28e4c8ba23a87aad45b82c459
|
||||
checksum: 10/7eac6169e535b63c8412e94d4f6047fc23efa3e9dde804b541940043c831b25f1cd867d83cd2c4371ad2450c8abcb42c208aa25668c1f0f3650d7f72faf711a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6241,11 +6241,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"yaml@npm:^2.0.0":
|
||||
version: 2.8.1
|
||||
resolution: "yaml@npm:2.8.1"
|
||||
version: 2.8.3
|
||||
resolution: "yaml@npm:2.8.3"
|
||||
bin:
|
||||
yaml: bin.mjs
|
||||
checksum: 10/eae07b3947d405012672ec17ce27348aea7d1fa0534143355d24a43a58f5e05652157ea2182c4fe0604f0540be71f99f1173f9d61018379404507790dff17665
|
||||
checksum: 10/ecad41d39d34fae5cc17ea2d4b7f7f55faacd45cbce8983ba22d48d1ed1a92ed242ea49ea813a79ac39a69f75f9c5a03e7b5395fd954d55476f25e21a47c141d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -33,3 +33,7 @@ This is a (non-exhaustive) list of actions that are known to be part of the Acti
|
||||
- `scaffolder.list-scaffolder-tasks` (List Scaffolder Tasks): This allows you to list scaffolder tasks that have been created.
|
||||
- `scaffolder.execute-template` (Execute Scaffolder Template): Executes a Scaffolder template with its template ref and input parameter values.
|
||||
- `scaffolder.get-scaffolder-task-logs` (Get Scaffolder Task Logs): This allows you to fetch the logs of a given scaffolder task.
|
||||
|
||||
### Search
|
||||
|
||||
- `search.query` (Query Search Engine): Query the Backstage search engine for documents across all or selected document types.
|
||||
|
||||
@@ -228,6 +228,14 @@ helps downstream localization.
|
||||
- Use (`-`) for unordered lists.
|
||||
- Leave a blank line after each list.
|
||||
- Indent nested lists with two spaces.
|
||||
- Use a numbered list for a sequence of steps rather than prose with
|
||||
"First", "Then", and "Finally". Numbered lists are easier to scan, make
|
||||
the order explicit, and give readers a clear way to reference a specific
|
||||
step.
|
||||
|
||||
| Do | Don't |
|
||||
| :----------------------------------------------------------------- | :------------------------------------------------------------------------------ |
|
||||
| 1) Install the package. 2) Run the migration. 3) Start the server. | First, install the package. Then, run the migration. Finally, start the server. |
|
||||
|
||||
### Tables
|
||||
|
||||
|
||||
@@ -746,6 +746,75 @@ input:
|
||||
|
||||
When `each` is used, the outputs of a repeated step are returned as an array of outputs from each iteration.
|
||||
|
||||
### Status Check Functions - `always()` and `failure()`
|
||||
|
||||
By default, when a step fails during a scaffolder run, all subsequent steps are skipped and the task is marked as failed. This can be problematic when your template creates external resources (repositories, cloud infrastructure, deployments) that need to be cleaned up if a later step fails.
|
||||
|
||||
Status check functions give you control over which steps run even after a failure. You use them inside a `${{ ... }}` template expression in the `if` field of a step.
|
||||
|
||||
| Function | Description |
|
||||
| ----------- | ---------------------------------------------------------------------------- |
|
||||
| `always()` | Always runs the step, regardless of whether previous steps passed or failed. |
|
||||
| `failure()` | Runs the step only when a previous step has failed. |
|
||||
|
||||
These functions must be used as template expressions such as `${{ always() }}` or `${{ failure() }}`.
|
||||
|
||||
After a step has failed, the scaffolder only attempts later steps whose `if` expression invokes one of these status check functions.
|
||||
|
||||
#### Usage
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- id: cleanup
|
||||
name: Cleanup Resources
|
||||
action: my:cleanup:action
|
||||
if: ${{ always() }}
|
||||
```
|
||||
|
||||
#### Example: Cleanup on failure
|
||||
|
||||
A common pattern is to create resources in early steps and add cleanup steps
|
||||
that only run if something goes wrong:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- id: create-repo
|
||||
name: Create Repository
|
||||
action: publish:github
|
||||
input:
|
||||
repoUrl: ${{ parameters.repoUrl }}
|
||||
|
||||
- id: deploy
|
||||
name: Deploy to Kubernetes
|
||||
action: deploy:kubernetes
|
||||
input:
|
||||
manifest: ./k8s/deployment.yaml
|
||||
|
||||
# Only runs when a previous step failed — cleans up the repository
|
||||
- id: cleanup-repo
|
||||
name: Delete Repository
|
||||
action: github:repo:delete
|
||||
if: ${{ failure() }}
|
||||
input:
|
||||
repoUrl: ${{ parameters.repoUrl }}
|
||||
|
||||
# Always runs — post an audit event regardless of outcome
|
||||
- id: audit
|
||||
name: Post Audit Event
|
||||
action: debug:log
|
||||
if: ${{ always() }}
|
||||
input:
|
||||
message: 'Scaffolder run completed for ${{ parameters.repoUrl }}'
|
||||
|
||||
# Does not run after a failure, because it does not invoke a status check function
|
||||
- id: plain-truthy-condition
|
||||
name: Plain Truthy Condition
|
||||
action: debug:log
|
||||
if: ${{ true }}
|
||||
input:
|
||||
message: 'This step is skipped after a previous failure'
|
||||
```
|
||||
|
||||
## Outputs
|
||||
|
||||
Each individual step can output some variables that can be used in the
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user