diff --git a/.changeset/README.md b/.changeset/README.md index 4f3b76b096..6c68425e59 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -6,3 +6,58 @@ find the full documentation for it [in our repository](https://github.com/change We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) + +--- + +## Backstage UI Changesets + +For `@backstage/ui` changesets, use this format: + +```markdown +--- +'@backstage/ui': patch +--- + +Brief summary + +Optional description with code examples. + +**Migration:** + +Migration instructions (breaking changes only). + +**Affected components:** button, card +``` + +**Required:** + +- End with `**Affected components:**` + comma-separated component names +- For breaking changes: Add `**Migration:**` section +- No headings (`##`, `###`) inside - use bold markers + +**Examples:** + +```markdown +Fixed button hover state + +**Affected components:** button +``` + +````markdown +**BREAKING**: New Table API + +**Migration:** + +Update imports: + +```diff +- import { Table } from '@backstage/ui'; ++ import { Table, type ColumnConfig } from '@backstage/ui'; +``` +```` + +**Affected components:** table + +``` + +``` diff --git a/.changeset/action-filtering-feature.md b/.changeset/action-filtering-feature.md deleted file mode 100644 index a397d8d11a..0000000000 --- a/.changeset/action-filtering-feature.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -'@backstage/backend-defaults': minor ---- - -Added action filtering support with glob patterns and attribute constraints. - -The `ActionsService` now supports filtering actions based on configuration. This allows controlling which actions are exposed to consumers like the MCP backend. - -Configuration example: - -```yaml -backend: - actions: - pluginSources: - - catalog - - scaffolder - filter: - include: - - id: 'catalog:*' - attributes: - destructive: false - - id: 'scaffolder:*' - exclude: - - id: '*:delete-*' - - attributes: - readOnly: false -``` - -Filtering logic: - -- `include`: Rules for actions to include. Each rule can specify an `id` glob pattern and/or `attributes` constraints. An action must match at least one rule to be included. If no include rules are specified, all actions are included by default. -- `exclude`: Rules for actions to exclude. Takes precedence over include rules. -- Each rule combines `id` and `attributes` with AND logic (both must match if specified). diff --git a/.changeset/angry-hornets-clap.md b/.changeset/angry-hornets-clap.md new file mode 100644 index 0000000000..bd590e71d9 --- /dev/null +++ b/.changeset/angry-hornets-clap.md @@ -0,0 +1,30 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Removed gray scale tokens and renamed background surface tokens to neutral tokens + +The `--bui-gray-1` through `--bui-gray-8` tokens have been removed. The `--bui-bg-surface-*` and `--bui-bg-neutral-on-surface-*` tokens have been replaced by a unified `--bui-bg-neutral-*` scale. + +**Migration:** + +Replace surface tokens directly: + +```diff +- background: var(--bui-bg-surface-0); ++ background: var(--bui-bg-neutral-0); +``` + +Replace on-surface tokens shifted by +1: + +```diff +- background: var(--bui-bg-neutral-on-surface-0); ++ background: var(--bui-bg-neutral-1); +``` + +Replace gray tokens 1-4 with neutral equivalents (`--bui-gray-5` through `--bui-gray-8` have no direct replacement): + +```diff +- background: var(--bui-gray-1); ++ background: var(--bui-bg-neutral-1); +``` diff --git a/.changeset/api-override-conflict-error-defaults.md b/.changeset/api-override-conflict-error-defaults.md new file mode 100644 index 0000000000..703f76fb6e --- /dev/null +++ b/.changeset/api-override-conflict-error-defaults.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': minor +--- + +**BREAKING**: The `API_FACTORY_CONFLICT` warning is now treated as an error and will prevent the app from starting. diff --git a/.changeset/api-override-conflict-error.md b/.changeset/api-override-conflict-error.md new file mode 100644 index 0000000000..ed88452ccf --- /dev/null +++ b/.changeset/api-override-conflict-error.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +**BREAKING**: Updated the behavior of the new API override logic to reject the override and block app startup instead of just logging a deprecation warning. diff --git a/.changeset/api-override-test-utils.md b/.changeset/api-override-test-utils.md new file mode 100644 index 0000000000..6ea60f9e1b --- /dev/null +++ b/.changeset/api-override-test-utils.md @@ -0,0 +1,41 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Added an `apis` option to `createExtensionTester`, `renderInTestApp`, and `renderTestApp` to override APIs when testing extensions. Use the `mockApis` helpers to create mock implementations: + +```typescript +import { identityApiRef } from '@backstage/frontend-plugin-api'; +import { mockApis } from '@backstage/frontend-test-utils'; + +// Override APIs in createExtensionTester +const tester = createExtensionTester(myExtension, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], +}); + +// Override APIs in renderInTestApp +renderInTestApp(, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], +}); + +// Override APIs in renderTestApp +renderTestApp({ + extensions: [myExtension], + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], +}); +``` diff --git a/.changeset/backend-process-listener-cleanup.md b/.changeset/backend-process-listener-cleanup.md new file mode 100644 index 0000000000..eb4dc52937 --- /dev/null +++ b/.changeset/backend-process-listener-cleanup.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Fixed memory leak by properly cleaning up process event listeners on backend shutdown. diff --git a/.changeset/backend-test-utils-create-service-mock.md b/.changeset/backend-test-utils-create-service-mock.md new file mode 100644 index 0000000000..b35b647fd8 --- /dev/null +++ b/.changeset/backend-test-utils-create-service-mock.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': minor +--- + +Added `createServiceMock`, a public utility for creating `ServiceMock` instances for custom service refs. This allows plugin authors to define mock creators for their own services following the same pattern as the built-in `mockServices` mocks. diff --git a/.changeset/backend-test-utils-extension-points.md b/.changeset/backend-test-utils-extension-points.md new file mode 100644 index 0000000000..fb26a282e3 --- /dev/null +++ b/.changeset/backend-test-utils-extension-points.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Updated `startTestBackend` to support factory-based extension points (v1.1 format) in addition to the existing direct implementation format. diff --git a/.changeset/beige-crabs-share.md b/.changeset/beige-crabs-share.md new file mode 100644 index 0000000000..6a1a42ffd8 --- /dev/null +++ b/.changeset/beige-crabs-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': minor +--- + +**BREAKING**: Removed the `TestApiRegistry` class, use `TestApiProvider` directly instead, storing reused APIs in a variable, e.g. `const apis = [...] as const`. diff --git a/.changeset/better-eggs-slide.md b/.changeset/better-eggs-slide.md new file mode 100644 index 0000000000..cf3856bb86 --- /dev/null +++ b/.changeset/better-eggs-slide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Allow setting optional description on group creation diff --git a/.changeset/better-ghosts-tell.md b/.changeset/better-ghosts-tell.md deleted file mode 100644 index af66b4fc6e..0000000000 --- a/.changeset/better-ghosts-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixes app background color on dark mode. diff --git a/.changeset/big-rabbits-think.md b/.changeset/big-rabbits-think.md new file mode 100644 index 0000000000..bd0ae32b5e --- /dev/null +++ b/.changeset/big-rabbits-think.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fixes a bug where the `EntityListProvider` would not correctly hydrate query parameters if more than 20 were provided for the same key. diff --git a/.changeset/bright-pans-greet.md b/.changeset/bright-pans-greet.md new file mode 100644 index 0000000000..89ac3a8bc6 --- /dev/null +++ b/.changeset/bright-pans-greet.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-visualizer': patch +--- + +Bump react-aria-components to v1.14.0 diff --git a/.changeset/bright-pumas-cut.md b/.changeset/bright-pumas-cut.md deleted file mode 100644 index 3cb4d7d0b5..0000000000 --- a/.changeset/bright-pumas-cut.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-app-backend': patch -'@backstage/plugin-app-node': patch ---- - -Updated plugin metadata. diff --git a/.changeset/brown-grapes-fold.md b/.changeset/brown-grapes-fold.md new file mode 100644 index 0000000000..d8ab0a23f3 --- /dev/null +++ b/.changeset/brown-grapes-fold.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-auth0-provider': patch +--- + +Add support for organizational invites in auth0 strategy diff --git a/.changeset/bumpy-carrots-stare.md b/.changeset/bumpy-carrots-stare.md deleted file mode 100644 index 9b3c39fcf1..0000000000 --- a/.changeset/bumpy-carrots-stare.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Added missing `aria-label` attributes to `SearchField` components in `Select`, `MenuAutocomplete`, and `MenuAutocompleteListbox` to fix accessibility warnings. - -Affected components: Select, MenuAutocomplete, MenuAutocompleteListbox diff --git a/.changeset/busy-hairs-attend.md b/.changeset/busy-hairs-attend.md deleted file mode 100644 index 523396a5d4..0000000000 --- a/.changeset/busy-hairs-attend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Updated documentation for `createApiRef` to clarify the role of the ID in specifying the owning plugin of an API. diff --git a/.changeset/calm-knives-glow.md b/.changeset/calm-knives-glow.md new file mode 100644 index 0000000000..f2e1d5805c --- /dev/null +++ b/.changeset/calm-knives-glow.md @@ -0,0 +1,6 @@ +--- +'@backstage/integration': patch +'@backstage/plugin-catalog-backend-module-azure': patch +--- + +Added support for `{org}.visualstudio.com` domains used by Azure DevOps diff --git a/.changeset/catalog-graph-test-deps.md b/.changeset/catalog-graph-test-deps.md new file mode 100644 index 0000000000..3c47695696 --- /dev/null +++ b/.changeset/catalog-graph-test-deps.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-org': patch +--- + +Added `@backstage/frontend-test-utils` dev dependency. diff --git a/.changeset/catalog-modules-update-extension-points.md b/.changeset/catalog-modules-update-extension-points.md new file mode 100644 index 0000000000..dba229482f --- /dev/null +++ b/.changeset/catalog-modules-update-extension-points.md @@ -0,0 +1,23 @@ +--- +'@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-gcp': 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-github-org': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-catalog-backend-module-gitlab-org': 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-openapi': patch +'@backstage/plugin-catalog-backend-module-puppetdb': patch +'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': patch +--- + +Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. diff --git a/.changeset/catalog-node-extension-points-to-stable.md b/.changeset/catalog-node-extension-points-to-stable.md new file mode 100644 index 0000000000..7e8e22dfc5 --- /dev/null +++ b/.changeset/catalog-node-extension-points-to-stable.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Promoted stable catalog extension points from alpha to main export. The following extension points are now exported from `@backstage/plugin-catalog-node` instead of `@backstage/plugin-catalog-node/alpha`: + +- `catalogLocationsExtensionPoint` and `CatalogLocationsExtensionPoint` +- `catalogProcessingExtensionPoint` and `CatalogProcessingExtensionPoint` +- `catalogAnalysisExtensionPoint` and `CatalogAnalysisExtensionPoint` + +The old alpha exports for these extension points are now deprecated with `@deprecated` markers pointing to the new stable exports. Please update your imports from `@backstage/plugin-catalog-node/alpha` to `@backstage/plugin-catalog-node`. + +Note: The `catalogModelExtensionPoint`, `catalogPermissionExtensionPoint`, and related types remain in alpha. diff --git a/.changeset/catalog-node-use-create-service-mock.md b/.changeset/catalog-node-use-create-service-mock.md new file mode 100644 index 0000000000..5032484ebb --- /dev/null +++ b/.changeset/catalog-node-use-create-service-mock.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': patch +--- + +Updated `catalogServiceMock.mock` to use `createServiceMock` from `@backstage/backend-test-utils`, replacing the internal copy of `simpleMock`. Added `@backstage/backend-test-utils` as an optional peer dependency. diff --git a/.changeset/catalog-provider-module-failures.md b/.changeset/catalog-provider-module-failures.md new file mode 100644 index 0000000000..c7cb5a3571 --- /dev/null +++ b/.changeset/catalog-provider-module-failures.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Failures to connect catalog providers are now attributed to the module that provided the failing provider. This means that such failures will be reported as module startup failures rather than a failure to start the catalog plugin, and will therefore respect `onPluginModuleBootFailure` configuration instead. diff --git a/.changeset/catalog-react-catalog-api-mock-shorthand.md b/.changeset/catalog-react-catalog-api-mock-shorthand.md new file mode 100644 index 0000000000..0761dffa0e --- /dev/null +++ b/.changeset/catalog-react-catalog-api-mock-shorthand.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +The `catalogApiMock` test utility now returns a `MockWithApiFactory`, allowing it to be passed directly to test utilities like `renderTestApp` and `TestApiProvider` without needing the `[catalogApiRef, catalogApiMock()]` tuple. diff --git a/.changeset/chatty-tips-stop.md b/.changeset/chatty-tips-stop.md new file mode 100644 index 0000000000..7b4c435ffc --- /dev/null +++ b/.changeset/chatty-tips-stop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-auth0-provider': minor +--- + +feat: Added organization option to authorization params of the strategy diff --git a/.changeset/chilly-maps-heal.md b/.changeset/chilly-maps-heal.md deleted file mode 100644 index f4f583c9ee..0000000000 --- a/.changeset/chilly-maps-heal.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixes disabled state in primary and secondary buttons in Backstage UI. diff --git a/.changeset/clean-suits-follow.md b/.changeset/clean-suits-follow.md new file mode 100644 index 0000000000..992b3e2947 --- /dev/null +++ b/.changeset/clean-suits-follow.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Add newline to OpenAPI license template files. diff --git a/.changeset/clever-streets-roll.md b/.changeset/clever-streets-roll.md deleted file mode 100644 index 7efa961533..0000000000 --- a/.changeset/clever-streets-roll.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Implementing `readTree` for `GoogleGcsReader` diff --git a/.changeset/cli-template-catalog-node-stable.md b/.changeset/cli-template-catalog-node-stable.md new file mode 100644 index 0000000000..77b06dc8f1 --- /dev/null +++ b/.changeset/cli-template-catalog-node-stable.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated catalog provider module template to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of alpha exports. diff --git a/.changeset/cold-dogs-study.md b/.changeset/cold-dogs-study.md deleted file mode 100644 index a27309fc15..0000000000 --- a/.changeset/cold-dogs-study.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch ---- - -Some AWS `publisher` config options such as `region`, `endpoint`, `accountId` are now marked as `@visibility backend` instead of `secret`. diff --git a/.changeset/create-app-1768306186.md b/.changeset/create-app-1768306186.md deleted file mode 100644 index b50d431d4b..0000000000 --- a/.changeset/create-app-1768306186.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Bumped create-app version. diff --git a/.changeset/create-app-1766498726.md b/.changeset/create-app-1770128583.md similarity index 100% rename from .changeset/create-app-1766498726.md rename to .changeset/create-app-1770128583.md diff --git a/.changeset/create-app-1767101113.md b/.changeset/create-app-1770740016.md similarity index 100% rename from .changeset/create-app-1767101113.md rename to .changeset/create-app-1770740016.md diff --git a/.changeset/css-exports-support.md b/.changeset/css-exports-support.md new file mode 100644 index 0000000000..17c1a0d8b7 --- /dev/null +++ b/.changeset/css-exports-support.md @@ -0,0 +1,7 @@ +--- +'@backstage/cli': patch +--- + +Added support for CSS exports in package builds. When a package declares a CSS file in its `exports` field (e.g., `"./styles.css": "./src/styles.css"`), the CLI will automatically bundle it during `backstage-cli package build`, resolving any `@import` statements. The export path is rewritten from `src/` to `dist/` at publish time. + +Fixed `backstage-cli repo fix` to not add `typesVersions` entries for non-script exports like CSS files. diff --git a/.changeset/curvy-zoos-drop.md b/.changeset/curvy-zoos-drop.md deleted file mode 100644 index b58eff1065..0000000000 --- a/.changeset/curvy-zoos-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-react': minor ---- - -Initial release of this web library for `@backstage/plugin-app`. diff --git a/.changeset/custom-playwright-channel.md b/.changeset/custom-playwright-channel.md new file mode 100644 index 0000000000..94adf89c0e --- /dev/null +++ b/.changeset/custom-playwright-channel.md @@ -0,0 +1,15 @@ +--- +'@backstage/e2e-test-utils': patch +--- + +Added optional `channel` option to `generateProjects()` to allow customizing the Playwright browser channel for testing against different browsers variants. When not provided, the function defaults to 'chrome' to maintain backward compatibility. + +Example usage: + +```ts +import { generateProjects } from '@backstage/e2e-test-utils'; + +export default defineConfig({ + projects: generateProjects({ channel: 'msedge' }), +}); +``` diff --git a/.changeset/cute-parts-smash.md b/.changeset/cute-parts-smash.md new file mode 100644 index 0000000000..166e0fc528 --- /dev/null +++ b/.changeset/cute-parts-smash.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed Box component to forward HTML attributes to the underlying div element. + +**Affected components:** Box diff --git a/.changeset/dependabot-1e2091a.md b/.changeset/dependabot-1e2091a.md deleted file mode 100644 index 7ddb741ec1..0000000000 --- a/.changeset/dependabot-1e2091a.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-mcp-actions-backend': patch ---- - -build(deps): bump `@modelcontextprotocol/sdk` from 1.24.3 to 1.25.2 diff --git a/.changeset/dependabot-aed8e01.md b/.changeset/dependabot-aed8e01.md deleted file mode 100644 index 8606ea8b91..0000000000 --- a/.changeset/dependabot-aed8e01.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -build(deps-dev): bump `storybook` from 10.1.9 to 10.1.10 diff --git a/.changeset/dull-ants-pull.md b/.changeset/dull-ants-pull.md new file mode 100644 index 0000000000..66adf0898f --- /dev/null +++ b/.changeset/dull-ants-pull.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Migrated `UnregisterEntityDialog` from Material UI to Backstage UI components. diff --git a/.changeset/dull-hounds-return.md b/.changeset/dull-hounds-return.md deleted file mode 100644 index 9123920984..0000000000 --- a/.changeset/dull-hounds-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Clean up process event listeners on backend stop to prevent leaks diff --git a/.changeset/eager-spies-wish.md b/.changeset/eager-spies-wish.md deleted file mode 100644 index a56c64a754..0000000000 --- a/.changeset/eager-spies-wish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Added new Popover component for Backstage UI with automatic overflow handling, and full placement support. Also introduced `--bui-shadow` token for consistent elevation styling across overlay components (Popover, Tooltip, Menu). diff --git a/.changeset/early-boxes-work.md b/.changeset/early-boxes-work.md deleted file mode 100644 index 11fc3ba134..0000000000 --- a/.changeset/early-boxes-work.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Header in EntityLayout should always be shown. -Monitoring the loading status caused flickering when the refresh() method of the Async Entity was invoked. diff --git a/.changeset/early-kids-see.md b/.changeset/early-kids-see.md new file mode 100644 index 0000000000..f2732c2f1a --- /dev/null +++ b/.changeset/early-kids-see.md @@ -0,0 +1,80 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +'@backstage/plugin-search-backend-module-stack-overflow-collator': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-auth-backend-module-azure-easyauth-provider': patch +'@backstage/plugin-auth-backend-module-oauth2-proxy-provider': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-auth-backend-module-pinniped-provider': patch +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-auth-backend-module-oidc-provider': patch +'@backstage/backend-dynamic-feature-service': patch +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/plugin-scaffolder-backend-module-sentry': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +'@backstage/plugin-scaffolder-backend-module-gitea': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +'@backstage/plugin-catalog-backend-module-openapi': patch +'@backstage/plugin-search-backend-module-techdocs': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-search-backend-module-catalog': patch +'@backstage/plugin-search-backend-module-explore': patch +'@backstage/plugin-catalog-backend-module-ldap': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-scaffolder-node-test-utils': patch +'@backstage/plugin-techdocs-addons-test-utils': patch +'@backstage/plugin-search-backend-module-pg': patch +'@backstage/backend-openapi-utils': patch +'@backstage/plugin-bitbucket-cloud-common': patch +'@backstage/integration-aws-node': patch +'@backstage/plugin-notifications-backend': patch +'@backstage/backend-plugin-api': patch +'@backstage/backend-test-utils': patch +'@backstage/plugin-search-backend-node': patch +'@backstage/backend-dev-utils': patch +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-defaults': patch +'@backstage/core-components': patch +'@backstage/plugin-devtools-backend': patch +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-techdocs-backend': patch +'@backstage/e2e-test-utils': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-kubernetes-node': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/plugin-signals-backend': patch +'@backstage/config-loader': patch +'@backstage/eslint-plugin': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-search-backend': patch +'@techdocs/cli': patch +'@backstage/plugin-proxy-backend': patch +'@backstage/plugin-search-common': patch +'@backstage/plugin-techdocs-node': patch +'@backstage/integration': patch +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-catalog-node': patch +'@backstage/cli-common': patch +'@backstage/create-app': patch +'@backstage/repo-tools': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-scaffolder': patch +'@backstage/cli-node': patch +'@backstage/codemods': patch +'@backstage/plugin-auth-node': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-app-node': patch +'@backstage/cli': patch +'@backstage/ui': patch +--- + +Use node prefix on native imports diff --git a/.changeset/easy-deer-eat.md b/.changeset/easy-deer-eat.md new file mode 100644 index 0000000000..10f21e9b29 --- /dev/null +++ b/.changeset/easy-deer-eat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Changed `gitlab:group:ensureExists` action to use `Groups.show` API instead of `Groups.search` for checking if a group path exists. This is more efficient as it directly retrieves the group by path rather than searching and filtering results. diff --git a/.changeset/easy-maps-appear.md b/.changeset/easy-maps-appear.md deleted file mode 100644 index 67c97c20f6..0000000000 --- a/.changeset/easy-maps-appear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-home': minor ---- - -Widget configurations are now only saved to storage when the Save button is explicitly clicked. Added a Cancel button that allows users to discard unsaved changes and revert to the last saved state. diff --git a/.changeset/eight-aliens-warn.md b/.changeset/eight-aliens-warn.md deleted file mode 100644 index f602b288d0..0000000000 --- a/.changeset/eight-aliens-warn.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixed Table sorting indicator not being visible when a column is actively sorted. - -Affected components: Table, Column diff --git a/.changeset/eighty-steaks-brake.md b/.changeset/eighty-steaks-brake.md new file mode 100644 index 0000000000..bb798a838f --- /dev/null +++ b/.changeset/eighty-steaks-brake.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Updated the browser tab title on the template wizard page to display the specific template title instead of the generic "Create a new component" text. diff --git a/.changeset/elasticsearch-auth-extension-point.md b/.changeset/elasticsearch-auth-extension-point.md new file mode 100644 index 0000000000..e2e1152e65 --- /dev/null +++ b/.changeset/elasticsearch-auth-extension-point.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': minor +--- + +Added `elasticsearchAuthExtensionPoint` to enable dynamic authentication mechanisms such as bearer tokens with automatic rotation. diff --git a/.changeset/empty-games-hug.md b/.changeset/empty-games-hug.md new file mode 100644 index 0000000000..e5bb0b4a95 --- /dev/null +++ b/.changeset/empty-games-hug.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Add missing translation entries for catalog UI text. + +This change adds translation keys and updates relevant UI components to use the correct localized labels and text in the catalog plugin. It ensures that catalog screens such as entity layout, tabs, search result items, table labels, and other UI elements correctly reference the i18n system for translation. + +No functional behavior is changed aside from the improved internationalization support. diff --git a/.changeset/every-bears-add.md b/.changeset/every-bears-add.md deleted file mode 100644 index 83c6d6b06d..0000000000 --- a/.changeset/every-bears-add.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Bump `@swc/core` to support `ES2023` and `ES2024` diff --git a/.changeset/every-lions-pay.md b/.changeset/every-lions-pay.md deleted file mode 100644 index 9c5a32f4e4..0000000000 --- a/.changeset/every-lions-pay.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -'@backstage/ui': minor ---- - -**BREAKING**: Redesigned Table component with new `useTable` hook API. - -- The `Table` component (React Aria wrapper) is renamed to `TableRoot` -- New high-level `Table` component that handles data display, pagination, sorting, and selection -- The `useTable` hook is completely redesigned with a new API supporting three pagination modes (complete, offset, cursor) -- New types: `ColumnConfig`, `TableProps`, `TableItem`, `UseTableOptions`, `UseTableResult` - -New features include unified pagination modes, debounced query changes, stale data preservation during reloads, and row selection with toggle/replace behaviors. - -**Migration guide:** - -1. Update imports and use the new `useTable` hook: - -```diff --import { Table, useTable } from '@backstage/ui'; --const { data, paginationProps } = useTable({ data: items, pagination: {...} }); -+import { Table, useTable, type ColumnConfig } from '@backstage/ui'; -+const { tableProps } = useTable({ -+ mode: 'complete', -+ getData: () => items, -+}); -``` - -2. Define columns and render with the new Table API: - -```diff -- -- ... -- ... --
-- -+const columns: ColumnConfig[] = [ -+ { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, -+ { id: 'type', label: 'Type', cell: item => }, -+]; -+ -+ -``` - -Affected components: Table, TableRoot, TablePagination diff --git a/.changeset/extension-snapshot-testing.md b/.changeset/extension-snapshot-testing.md new file mode 100644 index 0000000000..7ac281ec43 --- /dev/null +++ b/.changeset/extension-snapshot-testing.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Added `snapshot()` method to `ExtensionTester`, which returns a tree-shaped representation of the resolved extension hierarchy. Convenient to use with `toMatchInlineSnapshot()`. diff --git a/.changeset/fair-ads-decide.md b/.changeset/fair-ads-decide.md new file mode 100644 index 0000000000..ddf63d9e19 --- /dev/null +++ b/.changeset/fair-ads-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graph': patch +--- + +Fix large icon rendering in catalog graph nodes diff --git a/.changeset/famous-phones-chew.md b/.changeset/famous-phones-chew.md new file mode 100644 index 0000000000..29c9056743 --- /dev/null +++ b/.changeset/famous-phones-chew.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Removed `/alpha` from `scaffolderActionsExtensionPoint` import diff --git a/.changeset/few-teams-punch.md b/.changeset/few-teams-punch.md deleted file mode 100644 index 4443c43170..0000000000 --- a/.changeset/few-teams-punch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -The MarkdownContent component now handles HTML content the same way as GitHub when rendering GitHub-flavored Markdown diff --git a/.changeset/fine-lines-warn.md b/.changeset/fine-lines-warn.md deleted file mode 100644 index 25bd3e7491..0000000000 --- a/.changeset/fine-lines-warn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixed Menu component trigger button not toggling correctly. Removed custom click-outside handler that was interfering with React Aria's built-in state management, allowing the menu to properly open and close when clicking the trigger button. diff --git a/.changeset/five-bananas-retire.md b/.changeset/five-bananas-retire.md deleted file mode 100644 index 88b1d3d6dd..0000000000 --- a/.changeset/five-bananas-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Adds the Resource kind to the `useGetEntities` fallback so that Resources are included by default when no specific kinds are provided in the group ownership card. diff --git a/.changeset/fix-catalog-tags-spacing.md b/.changeset/fix-catalog-tags-spacing.md new file mode 100644 index 0000000000..e611273a04 --- /dev/null +++ b/.changeset/fix-catalog-tags-spacing.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Fixed vertical spacing between tags in the catalog table. diff --git a/.changeset/fix-nested-accordion-icon-state.md b/.changeset/fix-nested-accordion-icon-state.md new file mode 100644 index 0000000000..56ef097163 --- /dev/null +++ b/.changeset/fix-nested-accordion-icon-state.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed nested Accordion icon state issue where the inner accordion's arrow icon would incorrectly show as expanded when only the outer accordion was expanded. The CSS selector now uses a direct parent selector to ensure the icon only responds to its own accordion's expanded state. + +Affected components: Accordion diff --git a/.changeset/fix-nextjs16-css-modules.md b/.changeset/fix-nextjs16-css-modules.md new file mode 100644 index 0000000000..faa60fc06a --- /dev/null +++ b/.changeset/fix-nextjs16-css-modules.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed CSS Module syntax to comply with Next.js 16 Turbopack validation by flattening nested dark theme selectors. + +**Affected components:** Popover, Tooltip diff --git a/.changeset/fix-opensearch-bulk-refresh.md b/.changeset/fix-opensearch-bulk-refresh.md new file mode 100644 index 0000000000..17a791519d --- /dev/null +++ b/.changeset/fix-opensearch-bulk-refresh.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Fixed bulk indexing to refresh only the target index instead of all indexes, improving performance in multi-index deployments. diff --git a/.changeset/fix-theme-language-selector-leak.md b/.changeset/fix-theme-language-selector-leak.md new file mode 100644 index 0000000000..426127b687 --- /dev/null +++ b/.changeset/fix-theme-language-selector-leak.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Fixed memory leak caused by duplicate `AppThemeSelector` instances and missing cleanup in `AppThemeSelector` and `AppLanguageSelector`. Added `dispose()` method to both selectors for proper resource cleanup. diff --git a/.changeset/floppy-actors-show.md b/.changeset/floppy-actors-show.md new file mode 100644 index 0000000000..95bb711660 --- /dev/null +++ b/.changeset/floppy-actors-show.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Added `destructive` prop to Button for dangerous actions like delete or remove. Works with all variants (primary, secondary, tertiary). + +**Affected components:** Button diff --git a/.changeset/floppy-parks-decide.md b/.changeset/floppy-parks-decide.md new file mode 100644 index 0000000000..30c1cfc9a5 --- /dev/null +++ b/.changeset/floppy-parks-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +--- + +Fully enable API token functionality for Bitbucket-Cloud. diff --git a/.changeset/fluffy-bushes-remain.md b/.changeset/fluffy-bushes-remain.md deleted file mode 100644 index 5199218f0a..0000000000 --- a/.changeset/fluffy-bushes-remain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-puppetdb': patch ---- - -Fixed crash when `latest_report_status` is undefined diff --git a/.changeset/frontend-mock-apis-alert-featureflags.md b/.changeset/frontend-mock-apis-alert-featureflags.md new file mode 100644 index 0000000000..59fa8f59a1 --- /dev/null +++ b/.changeset/frontend-mock-apis-alert-featureflags.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': minor +--- + +Added `MockAlertApi` and `MockFeatureFlagsApi` implementations to the `mockApis` namespace. The mock implementations include useful testing methods like `clearAlerts()`, `waitForAlert()`, `getState()`, `setState()`, and `clearState()` for better test ergonomics. diff --git a/.changeset/frontend-test-utils-mock-apis-and-shorthand.md b/.changeset/frontend-test-utils-mock-apis-and-shorthand.md new file mode 100644 index 0000000000..c864cba776 --- /dev/null +++ b/.changeset/frontend-test-utils-mock-apis-and-shorthand.md @@ -0,0 +1,21 @@ +--- +'@backstage/frontend-test-utils': minor +--- + +**BREAKING**: The `mockApis` namespace is no longer a re-export from `@backstage/test-utils`. It's now a standalone namespace with mock implementations of most core APIs. Mock API instances can be passed directly to `TestApiProvider`, `renderInTestApp`, and `renderTestApp` without needing `[apiRef, impl]` tuples. As part of this change, the `.factory()` method on some mocks has been removed, since it's now redundant. + +```tsx +// Before +import { mockApis } from '@backstage/frontend-test-utils'; + +renderInTestApp(, { + apis: [[identityApiRef, mockApis.identity()]], +}); + +// After - mock APIs can be passed directly +renderInTestApp(, { + apis: [mockApis.identity()], +}); +``` + +This change also adds `createApiMock`, a public utility for creating mock API factories, intended for plugin authors to create their own `.mock()` variants. diff --git a/.changeset/fuzzy-lights-start.md b/.changeset/fuzzy-lights-start.md new file mode 100644 index 0000000000..b1be85f96d --- /dev/null +++ b/.changeset/fuzzy-lights-start.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Added experimental support for refresh tokens via the `auth.experimentalRefreshToken.enabled` configuration option. When enabled, clients can request the `offline_access` scope to receive refresh tokens that can be used to obtain new access tokens without re-authentication. diff --git a/.changeset/fuzzy-shrimps-refuse.md b/.changeset/fuzzy-shrimps-refuse.md new file mode 100644 index 0000000000..c2814da631 --- /dev/null +++ b/.changeset/fuzzy-shrimps-refuse.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Implemented support for the `internal` extension input option. diff --git a/.changeset/gentle-onions-occur.md b/.changeset/gentle-onions-occur.md new file mode 100644 index 0000000000..29653e39c2 --- /dev/null +++ b/.changeset/gentle-onions-occur.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-org': patch +--- + +Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions. diff --git a/.changeset/gold-lions-stick.md b/.changeset/gold-lions-stick.md new file mode 100644 index 0000000000..da18dd6cf6 --- /dev/null +++ b/.changeset/gold-lions-stick.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixed `useDefinition` hook adding literal "undefined" class name when no className prop was passed. diff --git a/.changeset/good-cameras-repair.md b/.changeset/good-cameras-repair.md new file mode 100644 index 0000000000..e994112e94 --- /dev/null +++ b/.changeset/good-cameras-repair.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-common': patch +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-kubernetes-node': patch +--- + +Add PersistentVolume and PersistentVolumeClaims Rendering diff --git a/.changeset/good-chicken-check.md b/.changeset/good-chicken-check.md deleted file mode 100644 index 27d311bae9..0000000000 --- a/.changeset/good-chicken-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Update colour token again in ItemCardHeader to respect theme definition. diff --git a/.changeset/good-eggs-tell.md b/.changeset/good-eggs-tell.md new file mode 100644 index 0000000000..04ce63db7d --- /dev/null +++ b/.changeset/good-eggs-tell.md @@ -0,0 +1,9 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Updated error reporting and app tree resolution logic to attribute errors to the correct extension and allow app startup to proceed more optimistically: + +- If an attachment fails to provide the required input data, the error is now attributed to the attachment rather than the parent extension. +- Singleton extension inputs will now only forward attachment errors if the input is required. +- Array extension inputs will now filter out failed attachments instead of failing the entire app tree resolution. diff --git a/.changeset/green-bags-shave.md b/.changeset/green-bags-shave.md new file mode 100644 index 0000000000..17211b78f0 --- /dev/null +++ b/.changeset/green-bags-shave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +cleaned up repo creation to make the unique portions explicit diff --git a/.changeset/cuddly-colts-dress.md b/.changeset/green-flowers-brush.md similarity index 51% rename from .changeset/cuddly-colts-dress.md rename to .changeset/green-flowers-brush.md index 5e541ebccb..bf57f18dcb 100644 --- a/.changeset/cuddly-colts-dress.md +++ b/.changeset/green-flowers-brush.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend': patch --- -Remove unused abort controller in debug:wait action +Removed unused dependency diff --git a/.changeset/green-llamas-wink.md b/.changeset/green-llamas-wink.md new file mode 100644 index 0000000000..0590b59ef3 --- /dev/null +++ b/.changeset/green-llamas-wink.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-compat-api': patch +'@backstage/plugin-app-visualizer': patch +--- + +Internal updates for blueprint moves to `@backstage/plugin-app-react`. diff --git a/.changeset/grumpy-birds-teach.md b/.changeset/grumpy-birds-teach.md new file mode 100644 index 0000000000..a5eb1adb77 --- /dev/null +++ b/.changeset/grumpy-birds-teach.md @@ -0,0 +1,5 @@ +--- +'@backstage/theme': patch +--- + +add square shape diff --git a/.changeset/grumpy-signs-deny.md b/.changeset/grumpy-signs-deny.md new file mode 100644 index 0000000000..4654fed83e --- /dev/null +++ b/.changeset/grumpy-signs-deny.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Allow `ref` as a prop on the `Tag` component + +Affected components: Tag diff --git a/.changeset/heavy-regions-camp.md b/.changeset/heavy-regions-camp.md deleted file mode 100644 index 624758548d..0000000000 --- a/.changeset/heavy-regions-camp.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Added support for column width configuration in Table component. Columns now accept `width`, `defaultWidth`, `minWidth`, and `maxWidth` props for responsive layout control. - -Affected components: Table, Column diff --git a/.changeset/hip-eyes-mate.md b/.changeset/hip-eyes-mate.md new file mode 100644 index 0000000000..3527193d0b --- /dev/null +++ b/.changeset/hip-eyes-mate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +fixed bug in `UserListPicker` by getting the `kindParamater` from the `filters` rather than from the `queryParameters` diff --git a/.changeset/honest-waves-rest.md b/.changeset/honest-waves-rest.md deleted file mode 100644 index d6be53903a..0000000000 --- a/.changeset/honest-waves-rest.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-mui-to-bui': patch ---- - -Fix installation command diff --git a/.changeset/hot-colts-float.md b/.changeset/hot-colts-float.md deleted file mode 100644 index 093dcfb12a..0000000000 --- a/.changeset/hot-colts-float.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers). - -Affected components: SearchField diff --git a/.changeset/hungry-mugs-fall.md b/.changeset/hungry-mugs-fall.md deleted file mode 100644 index 4332d92cc3..0000000000 --- a/.changeset/hungry-mugs-fall.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Implementing `ScmIntegration` for `GoogleGcs` diff --git a/.changeset/improve-in-memory-catalog-client.md b/.changeset/improve-in-memory-catalog-client.md new file mode 100644 index 0000000000..9830b259ec --- /dev/null +++ b/.changeset/improve-in-memory-catalog-client.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-client': patch +--- + +Improved the `InMemoryCatalogClient` test utility to support ordering, pagination, full-text search, and field projection for entity query methods. Also fixed `getEntityFacets` to correctly handle multi-valued fields. diff --git a/.changeset/lazy-groups-hunt.md b/.changeset/lazy-groups-hunt.md new file mode 100644 index 0000000000..56e0b19035 --- /dev/null +++ b/.changeset/lazy-groups-hunt.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Minor internal restructure of the postgres config loading code diff --git a/.changeset/legal-impalas-shine.md b/.changeset/legal-impalas-shine.md new file mode 100644 index 0000000000..f9d54690dc --- /dev/null +++ b/.changeset/legal-impalas-shine.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-visualizer': patch +--- + +Improved rendering performance of the details page. diff --git a/.changeset/lemon-eyes-grin.md b/.changeset/lemon-eyes-grin.md new file mode 100644 index 0000000000..7cbd4b29f1 --- /dev/null +++ b/.changeset/lemon-eyes-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Switched `next-app` template to use blueprint from `@backstage/plugin-app-react`. diff --git a/.changeset/lemon-lines-give.md b/.changeset/lemon-lines-give.md new file mode 100644 index 0000000000..215f9f1a0e --- /dev/null +++ b/.changeset/lemon-lines-give.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': minor +--- + +The AzureUrl class in the @backstage/integration package is now able to process BOTH git branches and git tags. Initially this class only processed git branches and threw an error when non-branch Azure URLs were passed in. diff --git a/.changeset/light-meals-cover.md b/.changeset/light-meals-cover.md new file mode 100644 index 0000000000..b0cc56d36c --- /dev/null +++ b/.changeset/light-meals-cover.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-catalog': minor +--- + +Add the ability to show icons for the tabs on the entity page (new frontend) diff --git a/.changeset/long-months-laugh.md b/.changeset/long-months-laugh.md new file mode 100644 index 0000000000..c6c52abb2d --- /dev/null +++ b/.changeset/long-months-laugh.md @@ -0,0 +1,9 @@ +--- +'@backstage/ui': patch +--- + +Allow data to be passed directly to the `useTable` hook using the property `data` instead of `getData()` for mode `"complete"`. + +This simplifies usage as data changes, rather than having to perform a `useEffect` when data changes, and then reloading the data. It also happens immediately, so stale data won't remain until a rerender (with an internal async state change), so less flickering. + +Affected components: Table diff --git a/.changeset/lucky-masks-doubt.md b/.changeset/lucky-masks-doubt.md new file mode 100644 index 0000000000..b20633308f --- /dev/null +++ b/.changeset/lucky-masks-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-pg': patch +--- + +Return `numberOfResults` count with search query responses diff --git a/.changeset/major-mangos-shine.md b/.changeset/major-mangos-shine.md deleted file mode 100644 index f42fcf5d0e..0000000000 --- a/.changeset/major-mangos-shine.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-openapi-utils': patch ---- - -Internal update to use native feature from our request validation library for handling base path determination. diff --git a/.changeset/many-bags-brake.md b/.changeset/many-bags-brake.md new file mode 100644 index 0000000000..91b48d63f4 --- /dev/null +++ b/.changeset/many-bags-brake.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added a new `internal` option to `createExtensionInput` that marks the input as only allowing attachments from the same plugin. diff --git a/.changeset/many-ravens-move.md b/.changeset/many-ravens-move.md new file mode 100644 index 0000000000..acf8733a63 --- /dev/null +++ b/.changeset/many-ravens-move.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed changing columns after first render from crashing. It now renders the table with the new column layout as columns change. + +Affected components: Table diff --git a/.changeset/mighty-flowers-travel.md b/.changeset/mighty-flowers-travel.md deleted file mode 100644 index 86c5706b90..0000000000 --- a/.changeset/mighty-flowers-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': minor ---- - -Fix `user_created_at` migration causing `SQLiteError` regarding use of non-constants for defaults diff --git a/.changeset/migrate-entity-filters-to-predicates.md b/.changeset/migrate-entity-filters-to-predicates.md new file mode 100644 index 0000000000..452372e657 --- /dev/null +++ b/.changeset/migrate-entity-filters-to-predicates.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-org': patch +--- + +Updated usage of deprecated APIs in the new frontend system. diff --git a/.changeset/modern-drinks-battle.md b/.changeset/modern-drinks-battle.md deleted file mode 100644 index a72e74cedb..0000000000 --- a/.changeset/modern-drinks-battle.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/ui': minor ---- - -**Breaking** Updating color tokens to match the new neutral style on different surfaces. - -## Migration notes - -There's no direct replacement for the old tint tokens but you can use the new neutral set of color tokens on surface 0 or 1 as a replacement. - -- `--bui-bg-tint` can be replaced by `--bui-bg-neutral-on-surface-0` -- `--bui-bg-tint-hover` can be replaced by `--bui-bg-neutral-on-surface-0-hover` -- `--bui-bg-tint-pressed` can be replaced by `--bui-bg-neutral-on-surface-0-pressed` -- `--bui-bg-tint-disabled` can be replaced by `--bui-bg-neutral-on-surface-0-disabled` diff --git a/.changeset/module-failure-reporting.md b/.changeset/module-failure-reporting.md new file mode 100644 index 0000000000..30acaf74d3 --- /dev/null +++ b/.changeset/module-failure-reporting.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': minor +--- + +Added support for extension point factories, along with the ability to report module startup failures via the extension point factory context. diff --git a/.changeset/module-handle-api.md b/.changeset/module-handle-api.md new file mode 100644 index 0000000000..28b0134ec1 --- /dev/null +++ b/.changeset/module-handle-api.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Added support for extension point factories. This makes it possible to call `registerExtensionPoint` with a single options argument and provide a factory for the extension point rather than a direct implementation. The factory is passed a context with a `reportModuleStartupFailure` method that makes it possible for plugins to report and attribute startup errors to the module that consumed the extension point. diff --git a/.changeset/new-windows-reply.md b/.changeset/new-windows-reply.md deleted file mode 100644 index e15ea2f1de..0000000000 --- a/.changeset/new-windows-reply.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixed `Link` component causing hard page refreshes for internal routes. The component now properly uses React Router's navigation instead of full page reloads. diff --git a/.changeset/odd-eagles-guess.md b/.changeset/odd-eagles-guess.md new file mode 100644 index 0000000000..d40ab80640 --- /dev/null +++ b/.changeset/odd-eagles-guess.md @@ -0,0 +1,8 @@ +--- +'@backstage/backend-defaults': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/repo-tools': patch +'@backstage/cli': patch +--- + +Bump to tar v7 diff --git a/.changeset/odd-lemons-occur.md b/.changeset/odd-lemons-occur.md new file mode 100644 index 0000000000..695ed3c80c --- /dev/null +++ b/.changeset/odd-lemons-occur.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +Plugin IDs that do not match the standard format are deprecated (letters, digits, and dashes only, starting with a letter). Plugin IDs that do no match this format will be rejected in a future release. diff --git a/.changeset/peer-modules-cli-support.md b/.changeset/peer-modules-cli-support.md new file mode 100644 index 0000000000..cfe8631995 --- /dev/null +++ b/.changeset/peer-modules-cli-support.md @@ -0,0 +1,6 @@ +--- +'@backstage/cli': patch +'@backstage/cli-node': patch +--- + +Added support for the new `peerModules` metadata field in `package.json`. This field allows plugin packages to declare modules that should be installed alongside them for cross-plugin integrations. The field is validated by `backstage-cli repo fix --publish`. diff --git a/.changeset/peer-modules-plugin-metadata.md b/.changeset/peer-modules-plugin-metadata.md new file mode 100644 index 0000000000..dde81c84be --- /dev/null +++ b/.changeset/peer-modules-plugin-metadata.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Added `peerModules` metadata declaring recommended modules for cross-plugin integrations. diff --git a/.changeset/plenty-dryers-tan.md b/.changeset/plenty-dryers-tan.md new file mode 100644 index 0000000000..719e151159 --- /dev/null +++ b/.changeset/plenty-dryers-tan.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-defaults': patch +'@backstage/frontend-app-api': patch +'@backstage/cli': patch +--- + +Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. diff --git a/.changeset/plenty-monkeys-share.md b/.changeset/plenty-monkeys-share.md new file mode 100644 index 0000000000..214b0e8546 --- /dev/null +++ b/.changeset/plenty-monkeys-share.md @@ -0,0 +1,14 @@ +--- +'@backstage/ui': patch +--- + +Added new `Alert` component with support for status variants (info, success, warning, danger), icons, loading states, and custom actions. + +Updated status color tokens for improved contrast and consistency across light and dark themes: + +- Added new `--bui-bg-info` and `--bui-fg-info` tokens for info status +- Updated `--bui-bg-danger`, `--bui-fg-danger` tokens +- Updated `--bui-bg-warning`, `--bui-fg-warning` tokens +- Updated `--bui-bg-success`, `--bui-fg-success` tokens + +**Affected components**: Alert diff --git a/.changeset/common-heads-start.md b/.changeset/polite-glasses-throw.md similarity index 52% rename from .changeset/common-heads-start.md rename to .changeset/polite-glasses-throw.md index 15ef218b12..67f554591a 100644 --- a/.changeset/common-heads-start.md +++ b/.changeset/polite-glasses-throw.md @@ -1,8 +1,8 @@ --- -'@backstage/plugin-app-react': patch +'@backstage/frontend-plugin-api': minor --- -Moved the following blueprints from `@backstage/frontend-plugin-api`: +**BREAKING**: The following blueprints have been removed and are now only available from `@backstage/plugin-app-react`: - `IconBundleBlueprint` - `NavContentBlueprint` diff --git a/.changeset/polite-pillows-accept.md b/.changeset/polite-pillows-accept.md deleted file mode 100644 index ece7c02ee1..0000000000 --- a/.changeset/polite-pillows-accept.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Rollback the lowercase replacing in GitHub integration config diff --git a/.changeset/polite-symbols-act.md b/.changeset/polite-symbols-act.md new file mode 100644 index 0000000000..9c3489f670 --- /dev/null +++ b/.changeset/polite-symbols-act.md @@ -0,0 +1,15 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Changed CSS selectors for `ButtonIcon` and `ButtonLink` components. Custom styles targeting `.bui-Button` to style these components must be updated to use `.bui-ButtonIcon` or `.bui-ButtonLink` respectively. + +```diff +-/* This no longer styles ButtonIcon or ButtonLink */ +-.bui-Button[data-variant="primary"] { ... } ++/* Use component-specific selectors */ ++.bui-ButtonIcon[data-variant="primary"] { ... } ++.bui-ButtonLink[data-variant="primary"] { ... } +``` + +Affected components: ButtonIcon, ButtonLink diff --git a/.changeset/pre.json b/.changeset/pre.json index db6fe1b381..acd2a6fc54 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,69 +1,70 @@ { - "mode": "exit", + "mode": "pre", "tag": "next", "initialVersions": { - "example-app": "0.2.116", - "@backstage/app-defaults": "1.7.3", - "example-app-next": "0.0.30", - "app-next-example-plugin": "0.0.30", - "example-backend": "0.0.45", - "@backstage/backend-app-api": "1.4.0", - "@backstage/backend-defaults": "0.14.0", + "example-app-legacy": "0.2.117", + "@backstage/app-defaults": "1.7.4", + "example-app": "0.0.31", + "app-example-plugin": "0.0.31", + "example-backend": "0.0.46", + "@backstage/backend-app-api": "1.4.1", + "@backstage/backend-defaults": "0.15.0", "@backstage/backend-dev-utils": "0.1.6", - "@backstage/backend-dynamic-feature-service": "0.7.7", - "@backstage/backend-openapi-utils": "0.6.4", - "@backstage/backend-plugin-api": "1.6.0", - "@backstage/backend-test-utils": "1.10.2", + "@backstage/backend-dynamic-feature-service": "0.7.8", + "@backstage/backend-openapi-utils": "0.6.5", + "@backstage/backend-plugin-api": "1.6.1", + "@backstage/backend-test-utils": "1.10.3", "@backstage/catalog-client": "1.12.1", "@backstage/catalog-model": "1.7.6", - "@backstage/cli": "0.35.0", - "@backstage/cli-common": "0.1.16", + "@backstage/cli": "0.35.2", + "@backstage/cli-common": "0.1.17", "@backstage/cli-node": "0.2.16", "@backstage/codemods": "0.1.53", "@backstage/config": "1.3.6", "@backstage/config-loader": "1.10.7", "@backstage/core-app-api": "1.19.3", - "@backstage/core-compat-api": "0.5.5", - "@backstage/core-components": "0.18.4", + "@backstage/core-compat-api": "0.5.6", + "@backstage/core-components": "0.18.5", "@backstage/core-plugin-api": "1.12.1", - "@backstage/create-app": "0.7.7", - "@backstage/dev-utils": "1.1.18", - "e2e-test": "0.2.35", + "@backstage/create-app": "0.7.8", + "@backstage/dev-utils": "1.1.19", + "e2e-test": "0.2.36", "@backstage/e2e-test-utils": "0.1.1", "@backstage/errors": "1.2.7", "@backstage/eslint-plugin": "0.2.0", - "@backstage/frontend-app-api": "0.13.3", - "@backstage/frontend-defaults": "0.3.4", + "@backstage/frontend-app-api": "0.14.0", + "@backstage/frontend-defaults": "0.3.5", "@backstage/frontend-dynamic-feature-loader": "0.1.8", "@internal/frontend": "0.0.16", - "@backstage/frontend-plugin-api": "0.13.2", - "@backstage/frontend-test-utils": "0.4.2", - "@backstage/integration": "1.19.0", + "@backstage/frontend-plugin-api": "0.13.3", + "@backstage/frontend-test-utils": "0.4.3", + "@backstage/integration": "1.19.2", "@backstage/integration-aws-node": "0.1.19", - "@backstage/integration-react": "1.2.13", + "@backstage/integration-react": "1.2.14", "@internal/opaque": "0.0.1", "@backstage/release-manifests": "0.0.13", - "@backstage/repo-tools": "0.16.1", - "@internal/scaffolder": "0.0.16", - "@techdocs/cli": "1.10.3", - "techdocs-cli-embedded-app": "0.2.115", + "@backstage/repo-tools": "0.16.2", + "@internal/scaffolder": "0.0.17", + "@techdocs/cli": "1.10.4", + "techdocs-cli-embedded-app": "0.2.116", "@backstage/test-utils": "1.7.14", "@backstage/theme": "0.7.1", "@backstage/types": "1.2.2", - "@backstage/ui": "0.10.0", + "@backstage/ui": "0.11.0", "@backstage/version-bridge": "1.0.11", "yarn-plugin-backstage": "0.0.8", - "@backstage/plugin-api-docs": "0.13.2", + "@backstage/plugin-api-docs": "0.13.3", "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.10", - "@backstage/plugin-app": "0.3.3", - "@backstage/plugin-app-backend": "0.5.9", - "@backstage/plugin-app-node": "0.1.40", - "@backstage/plugin-app-visualizer": "0.1.26", - "@backstage/plugin-auth": "0.1.3", - "@backstage/plugin-auth-backend": "0.25.7", + "@backstage/plugin-app": "0.3.4", + "@backstage/plugin-app-backend": "0.5.10", + "@backstage/plugin-app-node": "0.1.41", + "@backstage/plugin-app-react": "0.1.0", + "@backstage/plugin-app-visualizer": "0.1.27", + "@backstage/plugin-auth": "0.1.4", + "@backstage/plugin-auth-backend": "0.26.0", "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.10", "@backstage/plugin-auth-backend-module-auth0-provider": "0.2.10", - "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.10", + "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.11", "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.15", "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.10", "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.10", @@ -76,173 +77,283 @@ "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.10", "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.10", "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.15", - "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.10", + "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.11", "@backstage/plugin-auth-backend-module-okta-provider": "0.2.10", "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.10", "@backstage/plugin-auth-backend-module-openshift-provider": "0.1.3", "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.10", "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.10", - "@backstage/plugin-auth-node": "0.6.10", - "@backstage/plugin-auth-react": "0.1.22", - "@backstage/plugin-bitbucket-cloud-common": "0.3.5", - "@backstage/plugin-catalog": "1.32.1", - "@backstage/plugin-catalog-backend": "3.3.0", - "@backstage/plugin-catalog-backend-module-aws": "0.4.18", - "@backstage/plugin-catalog-backend-module-azure": "0.3.12", - "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.9", - "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.6", - "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.6", + "@backstage/plugin-auth-node": "0.6.11", + "@backstage/plugin-auth-react": "0.1.23", + "@backstage/plugin-bitbucket-cloud-common": "0.3.6", + "@backstage/plugin-catalog": "1.32.2", + "@backstage/plugin-catalog-backend": "3.3.1", + "@backstage/plugin-catalog-backend-module-aws": "0.4.19", + "@backstage/plugin-catalog-backend-module-azure": "0.3.13", + "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.10", + "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.7", + "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.7", "@backstage/plugin-catalog-backend-module-gcp": "0.3.15", - "@backstage/plugin-catalog-backend-module-gerrit": "0.3.9", - "@backstage/plugin-catalog-backend-module-gitea": "0.1.7", - "@backstage/plugin-catalog-backend-module-github": "0.12.0", - "@backstage/plugin-catalog-backend-module-github-org": "0.3.17", - "@backstage/plugin-catalog-backend-module-gitlab": "0.7.6", - "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.16", - "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.7", + "@backstage/plugin-catalog-backend-module-gerrit": "0.3.10", + "@backstage/plugin-catalog-backend-module-gitea": "0.1.8", + "@backstage/plugin-catalog-backend-module-github": "0.12.1", + "@backstage/plugin-catalog-backend-module-github-org": "0.3.18", + "@backstage/plugin-catalog-backend-module-gitlab": "0.7.7", + "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.17", + "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.8", "@backstage/plugin-catalog-backend-module-ldap": "0.12.1", - "@backstage/plugin-catalog-backend-module-logs": "0.1.17", - "@backstage/plugin-catalog-backend-module-msgraph": "0.8.3", - "@backstage/plugin-catalog-backend-module-openapi": "0.2.17", - "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.17", - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.15", + "@backstage/plugin-catalog-backend-module-logs": "0.1.18", + "@backstage/plugin-catalog-backend-module-msgraph": "0.8.4", + "@backstage/plugin-catalog-backend-module-openapi": "0.2.18", + "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.18", + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.16", "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.7", "@backstage/plugin-catalog-common": "1.1.7", - "@backstage/plugin-catalog-graph": "0.5.4", - "@backstage/plugin-catalog-import": "0.13.8", + "@backstage/plugin-catalog-graph": "0.5.6", + "@backstage/plugin-catalog-import": "0.13.9", "@backstage/plugin-catalog-node": "1.20.1", - "@backstage/plugin-catalog-react": "1.21.4", - "@backstage/plugin-catalog-unprocessed-entities": "0.2.24", + "@backstage/plugin-catalog-react": "1.21.5", + "@backstage/plugin-catalog-unprocessed-entities": "0.2.25", "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.12", - "@backstage/plugin-config-schema": "0.1.75", - "@backstage/plugin-devtools": "0.1.34", - "@backstage/plugin-devtools-backend": "0.5.12", - "@backstage/plugin-devtools-common": "0.1.20", - "@backstage/plugin-events-backend": "0.5.9", + "@backstage/plugin-config-schema": "0.1.76", + "@backstage/plugin-devtools": "0.1.35", + "@backstage/plugin-devtools-backend": "0.5.13", + "@backstage/plugin-devtools-common": "0.1.21", + "@backstage/plugin-devtools-react": "0.1.0", + "@backstage/plugin-events-backend": "0.5.10", "@backstage/plugin-events-backend-module-aws-sqs": "0.4.18", "@backstage/plugin-events-backend-module-azure": "0.2.27", "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.27", "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.8", "@backstage/plugin-events-backend-module-gerrit": "0.2.27", - "@backstage/plugin-events-backend-module-github": "0.4.7", + "@backstage/plugin-events-backend-module-github": "0.4.8", "@backstage/plugin-events-backend-module-gitlab": "0.3.8", "@backstage/plugin-events-backend-module-google-pubsub": "0.1.7", - "@backstage/plugin-events-backend-module-kafka": "0.2.0", + "@backstage/plugin-events-backend-module-kafka": "0.3.0", "@backstage/plugin-events-backend-test-utils": "0.1.51", "@backstage/plugin-events-node": "0.4.18", - "@internal/plugin-todo-list": "1.0.46", + "@internal/plugin-todo-list": "1.0.47", "@internal/plugin-todo-list-backend": "1.0.46", "@internal/plugin-todo-list-common": "1.0.28", "@backstage/plugin-gateway-backend": "1.1.1", - "@backstage/plugin-home": "0.8.15", - "@backstage/plugin-home-react": "0.1.33", - "@backstage/plugin-kubernetes": "0.12.14", + "@backstage/plugin-home": "0.9.0", + "@backstage/plugin-home-react": "0.1.34", + "@backstage/plugin-kubernetes": "0.12.15", "@backstage/plugin-kubernetes-backend": "0.21.0", - "@backstage/plugin-kubernetes-cluster": "0.0.32", + "@backstage/plugin-kubernetes-cluster": "0.0.33", "@backstage/plugin-kubernetes-common": "0.9.9", "@backstage/plugin-kubernetes-node": "0.4.0", - "@backstage/plugin-kubernetes-react": "0.5.14", - "@backstage/plugin-mcp-actions-backend": "0.1.6", - "@backstage/plugin-mui-to-bui": "0.2.2", - "@backstage/plugin-notifications": "0.5.12", + "@backstage/plugin-kubernetes-react": "0.5.15", + "@backstage/plugin-mcp-actions-backend": "0.1.7", + "@backstage/plugin-mui-to-bui": "0.2.3", + "@backstage/plugin-notifications": "0.5.13", "@backstage/plugin-notifications-backend": "0.6.1", "@backstage/plugin-notifications-backend-module-email": "0.3.17", "@backstage/plugin-notifications-backend-module-slack": "0.3.0", "@backstage/plugin-notifications-common": "0.2.0", "@backstage/plugin-notifications-node": "0.2.22", - "@backstage/plugin-org": "0.6.47", - "@backstage/plugin-org-react": "0.1.45", + "@backstage/plugin-org": "0.6.48", + "@backstage/plugin-org-react": "0.1.46", "@backstage/plugin-permission-backend": "0.7.7", "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.15", - "@backstage/plugin-permission-common": "0.9.3", - "@backstage/plugin-permission-node": "0.10.7", + "@backstage/plugin-permission-common": "0.9.4", + "@backstage/plugin-permission-node": "0.10.8", "@backstage/plugin-permission-react": "0.4.39", "@backstage/plugin-proxy-backend": "0.6.9", "@backstage/plugin-proxy-node": "0.1.11", - "@backstage/plugin-scaffolder": "1.35.0", - "@backstage/plugin-scaffolder-backend": "3.1.0", - "@backstage/plugin-scaffolder-backend-module-azure": "0.2.16", - "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.3.17", - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.3.0", - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.16", - "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.16", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.18", - "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.16", - "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.16", - "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.16", - "@backstage/plugin-scaffolder-backend-module-github": "0.9.3", - "@backstage/plugin-scaffolder-backend-module-gitlab": "0.11.0", - "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.17", - "@backstage/plugin-scaffolder-backend-module-rails": "0.5.16", - "@backstage/plugin-scaffolder-backend-module-sentry": "0.2.16", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.17", - "@backstage/plugin-scaffolder-common": "1.7.4", - "@backstage/plugin-scaffolder-node": "0.12.2", - "@backstage/plugin-scaffolder-node-test-utils": "0.3.6", - "@backstage/plugin-scaffolder-react": "1.19.4", - "@backstage/plugin-search": "1.5.1", - "@backstage/plugin-search-backend": "2.0.9", + "@backstage/plugin-scaffolder": "1.35.1", + "@backstage/plugin-scaffolder-backend": "3.1.1", + "@backstage/plugin-scaffolder-backend-module-azure": "0.2.17", + "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.3.18", + "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.3.1", + "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.17", + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.17", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.19", + "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.17", + "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.17", + "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.17", + "@backstage/plugin-scaffolder-backend-module-github": "0.9.4", + "@backstage/plugin-scaffolder-backend-module-gitlab": "0.11.1", + "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.18", + "@backstage/plugin-scaffolder-backend-module-rails": "0.5.17", + "@backstage/plugin-scaffolder-backend-module-sentry": "0.3.0", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.18", + "@backstage/plugin-scaffolder-common": "1.7.5", + "@backstage/plugin-scaffolder-node": "0.12.3", + "@backstage/plugin-scaffolder-node-test-utils": "0.3.7", + "@backstage/plugin-scaffolder-react": "1.19.5", + "@backstage/plugin-search": "1.5.3", + "@backstage/plugin-search-backend": "2.0.10", "@backstage/plugin-search-backend-module-catalog": "0.3.11", "@backstage/plugin-search-backend-module-elasticsearch": "1.7.9", "@backstage/plugin-search-backend-module-explore": "0.3.10", "@backstage/plugin-search-backend-module-pg": "0.5.51", "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.16", - "@backstage/plugin-search-backend-module-techdocs": "0.4.9", + "@backstage/plugin-search-backend-module-techdocs": "0.4.10", "@backstage/plugin-search-backend-node": "1.4.0", "@backstage/plugin-search-common": "1.2.21", - "@backstage/plugin-search-react": "1.10.1", - "@backstage/plugin-signals": "0.0.26", + "@backstage/plugin-search-react": "1.10.2", + "@backstage/plugin-signals": "0.0.27", "@backstage/plugin-signals-backend": "0.3.11", "@backstage/plugin-signals-node": "0.1.27", "@backstage/plugin-signals-react": "0.0.18", - "@backstage/plugin-techdocs": "1.16.1", - "@backstage/plugin-techdocs-addons-test-utils": "2.0.0", - "@backstage/plugin-techdocs-backend": "2.1.3", + "@backstage/plugin-techdocs": "1.16.2", + "@backstage/plugin-techdocs-addons-test-utils": "2.0.1", + "@backstage/plugin-techdocs-backend": "2.1.4", "@backstage/plugin-techdocs-common": "0.1.1", - "@backstage/plugin-techdocs-module-addons-contrib": "1.1.31", - "@backstage/plugin-techdocs-node": "1.13.10", - "@backstage/plugin-techdocs-react": "1.3.6", - "@backstage/plugin-user-settings": "0.8.30", - "@backstage/plugin-user-settings-backend": "0.3.9", - "@backstage/plugin-user-settings-common": "0.0.1" + "@backstage/plugin-techdocs-module-addons-contrib": "1.1.32", + "@backstage/plugin-techdocs-node": "1.14.0", + "@backstage/plugin-techdocs-react": "1.3.7", + "@backstage/plugin-user-settings": "0.8.31", + "@backstage/plugin-user-settings-backend": "0.3.10", + "@backstage/plugin-user-settings-common": "0.0.1", + "@backstage/filter-predicates": "0.0.0" }, "changesets": [ - "action-filtering-feature", - "better-ghosts-tell", - "chilly-maps-heal", - "clever-streets-roll", - "cold-dogs-study", - "create-app-1766498726", - "create-app-1767101113", - "create-app-1768306186", - "dependabot-1e2091a", - "dependabot-aed8e01", - "early-boxes-work", - "easy-maps-appear", - "every-bears-add", - "every-lions-pay", - "few-teams-punch", - "fluffy-bushes-remain", - "hot-colts-float", - "hungry-mugs-fall", - "major-mangos-shine", - "mighty-flowers-travel", - "modern-drinks-battle", - "new-windows-reply", - "polite-pillows-accept", - "proud-streets-switch", - "quick-cars-poke", - "renovate-ccf3e66", - "rude-trains-marry", - "shaggy-bats-film", - "slick-laws-push", - "slow-clouds-tie", - "ten-vans-agree", - "thin-flies-travel", - "thin-teams-roll", - "tricky-tips-invite", - "twenty-candles-open", - "weak-lemons-create", - "wild-toys-retire" + "angry-hornets-clap", + "api-override-conflict-error-defaults", + "api-override-conflict-error", + "api-override-test-utils", + "backend-process-listener-cleanup", + "backend-test-utils-create-service-mock", + "backend-test-utils-extension-points", + "beige-crabs-share", + "better-eggs-slide", + "big-rabbits-think", + "bright-pans-greet", + "brown-grapes-fold", + "calm-knives-glow", + "catalog-graph-test-deps", + "catalog-modules-update-extension-points", + "catalog-node-extension-points-to-stable", + "catalog-node-use-create-service-mock", + "catalog-provider-module-failures", + "catalog-react-catalog-api-mock-shorthand", + "chatty-tips-stop", + "clean-suits-follow", + "cli-template-catalog-node-stable", + "create-app-1770128583", + "create-app-1770740016", + "css-exports-support", + "custom-playwright-channel", + "cute-parts-smash", + "dull-ants-pull", + "early-kids-see", + "easy-deer-eat", + "eighty-steaks-brake", + "elasticsearch-auth-extension-point", + "empty-games-hug", + "extension-snapshot-testing", + "fair-ads-decide", + "famous-phones-chew", + "fix-catalog-tags-spacing", + "fix-nested-accordion-icon-state", + "fix-nextjs16-css-modules", + "fix-opensearch-bulk-refresh", + "fix-theme-language-selector-leak", + "floppy-actors-show", + "floppy-parks-decide", + "frontend-mock-apis-alert-featureflags", + "frontend-test-utils-mock-apis-and-shorthand", + "fuzzy-lights-start", + "fuzzy-shrimps-refuse", + "gentle-onions-occur", + "gold-lions-stick", + "good-cameras-repair", + "good-eggs-tell", + "green-bags-shave", + "green-flowers-brush", + "green-llamas-wink", + "grumpy-birds-teach", + "grumpy-signs-deny", + "hip-eyes-mate", + "improve-in-memory-catalog-client", + "lazy-groups-hunt", + "legal-impalas-shine", + "lemon-eyes-grin", + "lemon-lines-give", + "light-meals-cover", + "long-months-laugh", + "lucky-masks-doubt", + "many-bags-brake", + "many-ravens-move", + "migrate-entity-filters-to-predicates", + "module-failure-reporting", + "module-handle-api", + "odd-eagles-guess", + "odd-lemons-occur", + "peer-modules-cli-support", + "peer-modules-plugin-metadata", + "plenty-dryers-tan", + "plenty-monkeys-share", + "polite-glasses-throw", + "polite-symbols-act", + "proud-stars-grow", + "public-keys-sip", + "quiet-carpets-arrive", + "quiet-coats-sleep", + "quiet-humans-hammer", + "rare-papers-decide", + "red-chicken-juggle", + "red-rivers-make", + "remove-array-attachto-type", + "remove-backend-defaults-from-plugins", + "remove-multiple-attachment-points-frontend-app-api", + "remove-multiple-attachment-points-frontend-plugin-api", + "remove-summary-card-type", + "render-test-app-mounted-routes", + "renovate-02f9012", + "renovate-4e9885a", + "renovate-5d0b26d", + "renovate-deeb14e", + "renovate-fc2561e", + "repo-tools-app-rename", + "repo-tools-type-deps-ambient-jest", + "ripe-women-follow", + "rotten-paths-pump", + "scaffolder-form-fields-api-migration-react", + "scaffolder-form-fields-api-migration", + "scaffolder-react-test-utils-dep", + "scaffolder-test-utils-dep", + "seven-states-sleep", + "sharp-doodles-retire", + "slimy-dots-cross", + "slimy-dots-sing", + "slimy-zebras-lie", + "slow-numbers-study", + "small-jars-lick", + "small-rice-arrive", + "small-shirts-lose", + "smart-spoons-scream", + "smart-ties-attend", + "smooth-pants-wave", + "solid-dryers-leave", + "spicy-coins-switch", + "stupid-flies-shake", + "summary-card-backward-compat", + "tangy-wasps-invent", + "tasty-crabs-occur", + "techdocs-addons-api-migration-react", + "techdocs-addons-api-migration", + "ten-mammals-invite", + "test-entity-page-utility", + "test-utils-jest-peer-dep", + "thirty-meals-rest", + "thirty-mirrors-own", + "three-rooms-enjoy", + "tiny-lamps-give", + "tired-queens-cross", + "tired-sides-share", + "twelve-dolls-tap", + "twenty-clubs-itch", + "ui-standard-build", + "vast-rockets-dig", + "wet-cups-juggle", + "wicked-walls-accept", + "wild-emus-write", + "wise-rabbits-double", + "yellow-ties-dream", + "yellow-years-run", + "young-pens-wash" ] } diff --git a/.changeset/proud-sides-tan.md b/.changeset/proud-sides-tan.md deleted file mode 100644 index 0790d0f72e..0000000000 --- a/.changeset/proud-sides-tan.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Added support for custom pagination options in `useTable` hook and `Table` component. You can now configure `pageSizeOptions` to customize the page size dropdown, and hook into pagination events via `onPageSizeChange`, `onNextPage`, and `onPreviousPage` callbacks. When `pageSize` doesn't match any option, the first option is used and a warning is logged. - -Affected components: Table, TablePagination diff --git a/.changeset/proud-stars-grow.md b/.changeset/proud-stars-grow.md new file mode 100644 index 0000000000..e8f0bc16c4 --- /dev/null +++ b/.changeset/proud-stars-grow.md @@ -0,0 +1,9 @@ +--- +'@backstage/ui': patch +--- + +Fixed client-side navigation for container components by wrapping the container (not individual items) in RouterProvider. Components now conditionally provide routing context only when children have internal links, removing the Router context requirement when not needed. This also removes the need to wrap these components in MemoryRouter during tests when they are not using the `href` prop. + +Additionally, when multiple tabs match the current URL via prefix matching, the tab with the most specific path (highest segment count) is now selected. For example, with URL `/catalog/users/john`, a tab with path `/catalog/users` is now selected over a tab with path `/catalog`. + +Affected components: Tabs, Tab, TagGroup, Tag, Menu, MenuItem, MenuAutocomplete diff --git a/.changeset/proud-streets-switch.md b/.changeset/proud-streets-switch.md deleted file mode 100644 index 188b66f178..0000000000 --- a/.changeset/proud-streets-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-oidc-provider': patch ---- - -Added support for custom start URL search parameters (with the new `startUrlSearchParams` config property) diff --git a/.changeset/public-keys-sip.md b/.changeset/public-keys-sip.md new file mode 100644 index 0000000000..8254ac54d5 --- /dev/null +++ b/.changeset/public-keys-sip.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Support Prettier v3 for api-reports diff --git a/.changeset/quick-cars-poke.md b/.changeset/quick-cars-poke.md deleted file mode 100644 index a7338d8363..0000000000 --- a/.changeset/quick-cars-poke.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-github': patch ---- - -Added options to set [workflow access level][access-level] for repositories to `github:repo:create` - -This is useful when creating repositories for GitHub Actions to manage access -to the workflows during creation. - -```diff - - action: github:repo:create - id: create-repo - input: - repoUrl: github.com?owner=owner&repo=repo - visibility: private -+ workflowAccess: organization -``` - -[access-level]: https://docs.github.com/en/rest/actions/permissions?apiVersion=2022-11-28#set-the-level-of-access-for-workflows-outside-of-the-repository diff --git a/.changeset/quiet-carpets-arrive.md b/.changeset/quiet-carpets-arrive.md new file mode 100644 index 0000000000..e0104cc286 --- /dev/null +++ b/.changeset/quiet-carpets-arrive.md @@ -0,0 +1,22 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: The `cell` and `header` properties in `ColumnConfig` now return `ReactElement` instead of `ReactNode`. + +This fixes an issue where React Aria's Collection component would inject an `id` prop into Fragment wrappers, causing "Invalid prop `id` supplied to `React.Fragment`" errors on render. + +Migration: + +```diff +const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', +- cell: (item) => item.name, ++ cell: (item) => , +- header: () => 'Name', ++ header: () => Name, + }, +]; +``` diff --git a/.changeset/quiet-coats-sleep.md b/.changeset/quiet-coats-sleep.md new file mode 100644 index 0000000000..2656a1eb8d --- /dev/null +++ b/.changeset/quiet-coats-sleep.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Added new `gitlab:user:info` scaffolder action that retrieves information about a GitLab user. The action can fetch either the current authenticated user or a specific user by ID. diff --git a/.changeset/quiet-coins-protect.md b/.changeset/quiet-coins-protect.md deleted file mode 100644 index 758050232a..0000000000 --- a/.changeset/quiet-coins-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-app-api': minor ---- - -BREAKING: The ability for plugins to override APIs has been restricted to only allow overrides of APIs within the same plugin. For example, a plugin can no longer override any of the core APIs provided by the `app` plugin, this must be done with an `app` module instead. diff --git a/.changeset/quiet-humans-hammer.md b/.changeset/quiet-humans-hammer.md new file mode 100644 index 0000000000..d47fd647f1 --- /dev/null +++ b/.changeset/quiet-humans-hammer.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-app': minor +--- + +**BREAKING**: Extensions created with the following blueprints must now be provided via an override or a module for the `app` plugin. Extensions from other plugins will now trigger a warning in the app and be ignored. + +- `IconBundleBlueprint` +- `NavContentBlueprint` +- `RouterBlueprint` +- `SignInPageBlueprint` +- `SwappableComponentBlueprint` +- `ThemeBlueprint` +- `TranslationBlueprint` diff --git a/.changeset/rare-papers-decide.md b/.changeset/rare-papers-decide.md new file mode 100644 index 0000000000..ff5b0d3c36 --- /dev/null +++ b/.changeset/rare-papers-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixed an infinite render loop in Tabs when navigating to a URL that doesn't match any tab `href`. diff --git a/.changeset/red-chicken-juggle.md b/.changeset/red-chicken-juggle.md new file mode 100644 index 0000000000..0eb2df8b65 --- /dev/null +++ b/.changeset/red-chicken-juggle.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +**BREAKING ALPHA**: All of the predicate types and functions have been moved to the `@backstage/filter-predicates` package. + +When moving into the more general package, they were renamed as follows: + +- `EntityPredicate` -> `FilterPredicate` +- `EntityPredicateExpression` -> `FilterPredicateExpression` +- `EntityPredicatePrimitive` -> `FilterPredicatePrimitive` +- `entityPredicateToFilterFunction` -> `filterPredicateToFilterFunction` +- `EntityPredicateValue` -> `FilterPredicateValue` diff --git a/.changeset/red-rivers-make.md b/.changeset/red-rivers-make.md new file mode 100644 index 0000000000..0c3a1835a4 --- /dev/null +++ b/.changeset/red-rivers-make.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': minor +--- + +allow entity discoverability via gitlab search API diff --git a/.changeset/remove-array-attachto-type.md b/.changeset/remove-array-attachto-type.md new file mode 100644 index 0000000000..99b3513bc9 --- /dev/null +++ b/.changeset/remove-array-attachto-type.md @@ -0,0 +1,9 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed type support for multiple attachment points in the `ExtensionDefinitionAttachTo` type. Extensions can no longer specify an array of attachment points in the `attachTo` property. + +The runtime still supports multiple attachment points for backward compatibility with existing compiled code, but new code will receive type errors if attempting to use this pattern. + +Extensions that previously used multiple attachment points should migrate to using a Utility API pattern instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended approach. diff --git a/.changeset/remove-backend-defaults-from-plugins.md b/.changeset/remove-backend-defaults-from-plugins.md new file mode 100644 index 0000000000..3d322b2554 --- /dev/null +++ b/.changeset/remove-backend-defaults-from-plugins.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-devtools-backend': patch +'@backstage/plugin-mcp-actions-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-search-backend': patch +'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-user-settings-backend': patch +--- + +Moved `@backstage/backend-defaults` from `dependencies` to `devDependencies`. diff --git a/.changeset/remove-multiple-attachment-points-frontend-app-api.md b/.changeset/remove-multiple-attachment-points-frontend-app-api.md new file mode 100644 index 0000000000..4d54ef599f --- /dev/null +++ b/.changeset/remove-multiple-attachment-points-frontend-app-api.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +**DEPRECATED**: Deprecated support for multiple attachment points. diff --git a/.changeset/remove-multiple-attachment-points-frontend-plugin-api.md b/.changeset/remove-multiple-attachment-points-frontend-plugin-api.md new file mode 100644 index 0000000000..b0d5739ea3 --- /dev/null +++ b/.changeset/remove-multiple-attachment-points-frontend-plugin-api.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +**DEPRECATED**: Multiple attachment points for extensions have been deprecated. The functionality continues to work for backward compatibility, but will log a deprecation warning and be removed in a future release. + +Extensions using array attachment points should migrate to using Utility APIs instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended pattern. diff --git a/.changeset/remove-summary-card-type.md b/.changeset/remove-summary-card-type.md new file mode 100644 index 0000000000..8052445e69 --- /dev/null +++ b/.changeset/remove-summary-card-type.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +**BREAKING**: Removed the 'summary' entity card type from `EntityCardType`. Users should migrate to using 'content' or 'info' card types instead. + +TypeScript will now show errors if you try to use `type: 'summary'` when creating entity cards. diff --git a/.changeset/render-test-app-mounted-routes.md b/.changeset/render-test-app-mounted-routes.md new file mode 100644 index 0000000000..bffaff3c83 --- /dev/null +++ b/.changeset/render-test-app-mounted-routes.md @@ -0,0 +1,14 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Added `mountedRoutes` option to `renderTestApp` for binding route refs to paths, matching the existing option in `renderInTestApp`: + +```typescript +renderTestApp({ + extensions: [...], + mountedRoutes: { + '/my-path': myRouteRef, + }, +}); +``` diff --git a/.changeset/renovate-02f9012.md b/.changeset/renovate-02f9012.md new file mode 100644 index 0000000000..49b72db157 --- /dev/null +++ b/.changeset/renovate-02f9012.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-explore': patch +--- + +Updated dependency `@backstage-community/plugin-explore-common` to `^0.12.0`. diff --git a/.changeset/renovate-4e9885a.md b/.changeset/renovate-4e9885a.md new file mode 100644 index 0000000000..65ea142cc7 --- /dev/null +++ b/.changeset/renovate-4e9885a.md @@ -0,0 +1,5 @@ +--- +'@techdocs/cli': patch +--- + +Updated dependency `find-process` to `^2.0.0`. diff --git a/.changeset/renovate-5d0b26d.md b/.changeset/renovate-5d0b26d.md new file mode 100644 index 0000000000..6de573384d --- /dev/null +++ b/.changeset/renovate-5d0b26d.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes-react': patch +--- + +Updated dependency `@xterm/addon-attach` to `^0.12.0`. +Updated dependency `@xterm/addon-fit` to `^0.11.0`. diff --git a/.changeset/renovate-ccf3e66.md b/.changeset/renovate-ccf3e66.md deleted file mode 100644 index 920da9b987..0000000000 --- a/.changeset/renovate-ccf3e66.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Updated dependency `@types/swagger-ui-react` to `^5.0.0`. diff --git a/.changeset/renovate-deeb14e.md b/.changeset/renovate-deeb14e.md new file mode 100644 index 0000000000..431a3c401a --- /dev/null +++ b/.changeset/renovate-deeb14e.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated dependency `webpack` to `~5.104.0`. diff --git a/.changeset/renovate-fc2561e.md b/.changeset/renovate-fc2561e.md new file mode 100644 index 0000000000..d89e5b17c0 --- /dev/null +++ b/.changeset/renovate-fc2561e.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools-react': patch +--- + +Updated dependency `@testing-library/react` to `^16.0.0`. diff --git a/.changeset/repo-tools-app-rename.md b/.changeset/repo-tools-app-rename.md new file mode 100644 index 0000000000..e4f2892856 --- /dev/null +++ b/.changeset/repo-tools-app-rename.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Updated package-docs exclude list to reflect renamed example app packages. diff --git a/.changeset/repo-tools-type-deps-ambient-jest.md b/.changeset/repo-tools-type-deps-ambient-jest.md new file mode 100644 index 0000000000..d1a0feed01 --- /dev/null +++ b/.changeset/repo-tools-type-deps-ambient-jest.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +The `type-deps` command now follows relative imports and re-exports into declaration chunk files, and detects ambient global types such as the `jest` namespace. diff --git a/.changeset/ripe-women-follow.md b/.changeset/ripe-women-follow.md new file mode 100644 index 0000000000..1f2c50d78a --- /dev/null +++ b/.changeset/ripe-women-follow.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +export PasswordField component diff --git a/.changeset/rotten-paths-pump.md b/.changeset/rotten-paths-pump.md new file mode 100644 index 0000000000..3d0031edfe --- /dev/null +++ b/.changeset/rotten-paths-pump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Migrate audit events reference docs to http://backstage.io/docs. diff --git a/.changeset/rude-trains-marry.md b/.changeset/rude-trains-marry.md deleted file mode 100644 index 0246ff6c9e..0000000000 --- a/.changeset/rude-trains-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': minor ---- - -Introduce new `ToggleButton` & `ToggleButtonGroup` components in Backstage UI diff --git a/.changeset/scaffolder-form-fields-api-migration-react.md b/.changeset/scaffolder-form-fields-api-migration-react.md new file mode 100644 index 0000000000..4d640c58fc --- /dev/null +++ b/.changeset/scaffolder-form-fields-api-migration-react.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + +As part of this change, the following alpha exports were removed: + +- `formFieldsApi` +- `formFieldsApiRef` +- `ScaffolderFormFieldsApi` diff --git a/.changeset/scaffolder-form-fields-api-migration.md b/.changeset/scaffolder-form-fields-api-migration.md new file mode 100644 index 0000000000..cfbed3b749 --- /dev/null +++ b/.changeset/scaffolder-form-fields-api-migration.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + +As part of this change, the following alpha exports were removed: + +- `formFieldsApiRef` +- `ScaffolderFormFieldsApi` diff --git a/.changeset/scaffolder-react-test-utils-dep.md b/.changeset/scaffolder-react-test-utils-dep.md new file mode 100644 index 0000000000..88d3ee6309 --- /dev/null +++ b/.changeset/scaffolder-react-test-utils-dep.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Added `@backstage/frontend-test-utils` as a dev dependency for mock API usage in tests. diff --git a/.changeset/scaffolder-test-utils-dep.md b/.changeset/scaffolder-test-utils-dep.md new file mode 100644 index 0000000000..c9b092bc26 --- /dev/null +++ b/.changeset/scaffolder-test-utils-dep.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Added `@backstage/frontend-test-utils` as a dev dependency for mock API usage in tests. diff --git a/.changeset/seven-states-sleep.md b/.changeset/seven-states-sleep.md new file mode 100644 index 0000000000..1e612b3346 --- /dev/null +++ b/.changeset/seven-states-sleep.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Fixed Router deprecation warning and switched to using new `RouterBlueprint` from `@backstage/plugin-app-api`. diff --git a/.changeset/shaggy-bats-film.md b/.changeset/shaggy-bats-film.md deleted file mode 100644 index 3e5be9648b..0000000000 --- a/.changeset/shaggy-bats-film.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': minor ---- - -**BREAKING**: Renamed CSS variable `--bui-bg` to `--bui-bg-surface-0` for consistency. diff --git a/.changeset/sharp-doodles-retire.md b/.changeset/sharp-doodles-retire.md new file mode 100644 index 0000000000..2062b7ec34 --- /dev/null +++ b/.changeset/sharp-doodles-retire.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': patch +--- + +Some security fixes diff --git a/.changeset/slick-beans-relax.md b/.changeset/slick-beans-relax.md deleted file mode 100644 index b4fc3c3175..0000000000 --- a/.changeset/slick-beans-relax.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -The following blueprints are being restricted to only be used in app plugin overrides and modules. They are being moved to the `@backstage/plugin-app-react` package and have been deprecated: - -- `IconBundleBlueprint` -- `NavContentBlueprint` -- `RouterBlueprint` -- `SignInPageBlueprint` -- `SwappableComponentBlueprint` -- `ThemeBlueprint` -- `TranslationBlueprint` diff --git a/.changeset/slick-laws-push.md b/.changeset/slick-laws-push.md deleted file mode 100644 index d61e6d387b..0000000000 --- a/.changeset/slick-laws-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Updated Dockerfile to use Node 24 and Debian Trixie diff --git a/.changeset/slimy-dots-cross.md b/.changeset/slimy-dots-cross.md new file mode 100644 index 0000000000..fa1cf18aa2 --- /dev/null +++ b/.changeset/slimy-dots-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': patch +--- + +Dependency update for tests. diff --git a/.changeset/slimy-dots-sing.md b/.changeset/slimy-dots-sing.md new file mode 100644 index 0000000000..a7f3a8a07b --- /dev/null +++ b/.changeset/slimy-dots-sing.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Improve sidebars (nav/TOC) layout and scrolling diff --git a/.changeset/slimy-zebras-lie.md b/.changeset/slimy-zebras-lie.md new file mode 100644 index 0000000000..4ea1ce1791 --- /dev/null +++ b/.changeset/slimy-zebras-lie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-github-provider': minor +--- + +Added the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by their GitHub user ID. diff --git a/.changeset/slow-bobcats-lose.md b/.changeset/slow-bobcats-lose.md deleted file mode 100644 index d837bc32cf..0000000000 --- a/.changeset/slow-bobcats-lose.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/repo-tools': patch ---- - -Fixed help text for `backstage-repo-tools package schema openapi generate` command. diff --git a/.changeset/slow-clouds-tie.md b/.changeset/slow-clouds-tie.md deleted file mode 100644 index 5528ddd4bd..0000000000 --- a/.changeset/slow-clouds-tie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Fix types with new bumped dependency diff --git a/.changeset/slow-numbers-study.md b/.changeset/slow-numbers-study.md new file mode 100644 index 0000000000..fe7203e169 --- /dev/null +++ b/.changeset/slow-numbers-study.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Plugin IDs that do not match the standard format are deprecated (letters, digits, and dashes only, starting with a letter). Plugin IDs that do no match this format will be rejected in a future release. + +In addition, plugin IDs that don't match the legacy pattern that also allows underscores, with be rejected. diff --git a/.changeset/small-jars-lick.md b/.changeset/small-jars-lick.md new file mode 100644 index 0000000000..b2160eb7f7 --- /dev/null +++ b/.changeset/small-jars-lick.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools': patch +--- + +Fixes an issue where a user lacking permission to schedule tasks can now easily see the issue through a custom icon + tooltip. diff --git a/.changeset/small-rice-arrive.md b/.changeset/small-rice-arrive.md new file mode 100644 index 0000000000..a30bec6a82 --- /dev/null +++ b/.changeset/small-rice-arrive.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-mui-to-bui': patch +--- + +Updated MUI to BUI theme converter to align with latest token changes + +**Changes:** + +- Removed generation of deprecated tokens: `--bui-fg-link`, `--bui-fg-link-hover`, `--bui-fg-tint`, `--bui-fg-tint-disabled`, `--bui-bg-tint` and all its variants +- Added generation of new `info` status tokens: `--bui-fg-info`, `--bui-fg-info-on-bg`, `--bui-bg-info`, `--bui-border-info` +- Updated status color mapping to generate both standalone and `-on-bg` variants for danger, warning, success, and info +- Status colors now use `.main` for standalone variants and `.dark` for `-on-bg` variants, providing better visual hierarchy + +The converter now generates tokens that match the updated BUI design system structure, with clear distinction between status colors for standalone use vs. use on colored backgrounds. diff --git a/.changeset/small-shirts-lose.md b/.changeset/small-shirts-lose.md new file mode 100644 index 0000000000..2fe3d6c414 --- /dev/null +++ b/.changeset/small-shirts-lose.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Fixed GitLab search API scope parameter from `'blob'` to `'blobs'`, resolving 400 errors in discovery provider. diff --git a/.changeset/smart-spoons-scream.md b/.changeset/smart-spoons-scream.md new file mode 100644 index 0000000000..5abebc3ec5 --- /dev/null +++ b/.changeset/smart-spoons-scream.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Migrated `DeleteEntityDialog` and `EntityOrphanWarning` components to Backstage UI. + +The `deleteEntity.description` translation key no longer includes "Click here to delete" text. A new `deleteEntity.actionButtonTitle` key was added for the action button. diff --git a/.changeset/smart-ties-attend.md b/.changeset/smart-ties-attend.md new file mode 100644 index 0000000000..b0e4e621d8 --- /dev/null +++ b/.changeset/smart-ties-attend.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': minor +--- + +Log group/user count, tenant ID, execution time as separate fields diff --git a/.changeset/smooth-pants-wave.md b/.changeset/smooth-pants-wave.md new file mode 100644 index 0000000000..d736d4fe58 --- /dev/null +++ b/.changeset/smooth-pants-wave.md @@ -0,0 +1,9 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-app-api': patch +'@backstage/frontend-defaults': patch +'@backstage/core-plugin-api': patch +'@backstage/core-compat-api': patch +--- + +Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. diff --git a/.changeset/solid-dryers-leave.md b/.changeset/solid-dryers-leave.md new file mode 100644 index 0000000000..db1eb86c6c --- /dev/null +++ b/.changeset/solid-dryers-leave.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Support `connection.type: azure` in database client to use Microsoft Entra authentication with Azure database for PostgreSQL diff --git a/.changeset/sour-mails-smoke.md b/.changeset/sour-mails-smoke.md deleted file mode 100644 index f4bb53e025..0000000000 --- a/.changeset/sour-mails-smoke.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-auth-node': patch -'@backstage/cli': patch ---- - -Upgrade `zod-validation-error` to version 4 diff --git a/.changeset/spicy-coins-switch.md b/.changeset/spicy-coins-switch.md new file mode 100644 index 0000000000..744d9d9640 --- /dev/null +++ b/.changeset/spicy-coins-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fixed O(n²) performance bottleneck in `buildEntitySearch` `traverse()` by replacing `Array.some()` linear scan with a `Set` for O(1) duplicate path key detection. diff --git a/.changeset/stupid-flies-shake.md b/.changeset/stupid-flies-shake.md new file mode 100644 index 0000000000..c9e138272e --- /dev/null +++ b/.changeset/stupid-flies-shake.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-user-settings-backend': minor +'@backstage/plugin-user-settings-common': minor +'@backstage/plugin-user-settings': minor +--- + +User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance diff --git a/.changeset/summary-card-backward-compat.md b/.changeset/summary-card-backward-compat.md new file mode 100644 index 0000000000..e76d1426b0 --- /dev/null +++ b/.changeset/summary-card-backward-compat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +The default entity content layout still supports rendering summary cards at runtime for backward compatibility, but logs a console warning when they are detected to help identify where migration is needed. diff --git a/.changeset/tangy-wasps-invent.md b/.changeset/tangy-wasps-invent.md new file mode 100644 index 0000000000..6b07726df0 --- /dev/null +++ b/.changeset/tangy-wasps-invent.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-home-react': patch +'@backstage/plugin-home': patch +--- + +Support new frontend system in the homepage plugin diff --git a/.changeset/tasty-crabs-occur.md b/.changeset/tasty-crabs-occur.md new file mode 100644 index 0000000000..22036e3c34 --- /dev/null +++ b/.changeset/tasty-crabs-occur.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed components to not require a Router context when rendering without internal links. + +Affected components: Link, ButtonLink, Row diff --git a/.changeset/techdocs-addons-api-migration-react.md b/.changeset/techdocs-addons-api-migration-react.md new file mode 100644 index 0000000000..9a2c5f1da9 --- /dev/null +++ b/.changeset/techdocs-addons-api-migration-react.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-techdocs-react': patch +--- + +TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + +As part of this change, the `techDocsAddonDataRef` alpha export was removed. diff --git a/.changeset/techdocs-addons-api-migration.md b/.changeset/techdocs-addons-api-migration.md new file mode 100644 index 0000000000..f2e481de7e --- /dev/null +++ b/.changeset/techdocs-addons-api-migration.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. diff --git a/.changeset/ten-mammals-invite.md b/.changeset/ten-mammals-invite.md new file mode 100644 index 0000000000..a29e38d476 --- /dev/null +++ b/.changeset/ten-mammals-invite.md @@ -0,0 +1,56 @@ +--- +'@backstage/frontend-dynamic-feature-loader': patch +'@backstage/plugin-api-docs-module-protoc-gen-doc': patch +'@backstage/plugin-techdocs-module-addons-contrib': patch +'@backstage/plugin-catalog-unprocessed-entities': patch +'@backstage/plugin-scaffolder-node-test-utils': patch +'@backstage/plugin-techdocs-addons-test-utils': patch +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +'@backstage/frontend-defaults': patch +'@backstage/integration-react': patch +'@backstage/plugin-kubernetes-cluster': patch +'@backstage/frontend-app-api': patch +'@backstage/core-compat-api': patch +'@backstage/core-components': patch +'@backstage/core-plugin-api': patch +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-permission-react': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/version-bridge': patch +'@backstage/plugin-app-visualizer': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-techdocs-react': patch +'@backstage/app-defaults': patch +'@backstage/core-app-api': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-config-schema': patch +'@backstage/plugin-notifications': patch +'@backstage/plugin-signals-react': patch +'@backstage/plugin-user-settings': patch +'@backstage/plugin-search-react': patch +'@backstage/repo-tools': patch +'@backstage/test-utils': patch +'@backstage/dev-utils': patch +'@backstage/plugin-auth-react': patch +'@backstage/plugin-home-react': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-mui-to-bui': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-org-react': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-devtools': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-signals': patch +'@backstage/theme': patch +'@backstage/plugin-search': patch +'@backstage/plugin-auth': patch +'@backstage/plugin-home': patch +'@backstage/ui': patch +'@backstage/plugin-app': patch +'@backstage/plugin-org': patch +--- + +Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components. diff --git a/.changeset/ten-vans-agree.md b/.changeset/ten-vans-agree.md deleted file mode 100644 index 69e1d85e02..0000000000 --- a/.changeset/ten-vans-agree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github': patch ---- - -Improved efficiency of `GithubOrgEntityProvider` membership event handling and edit team. The provider now fetches only the specific user's teams instead of all organization users when processing membership events, and uses `addEntitiesOperation` instead of `replaceEntitiesOperation` to avoid unnecessary entity deletions. diff --git a/.changeset/test-entity-page-utility.md b/.changeset/test-entity-page-utility.md new file mode 100644 index 0000000000..775370ce9e --- /dev/null +++ b/.changeset/test-entity-page-utility.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Added `createTestEntityPage` test utility for testing entity cards and content extensions in the new frontend system. This utility creates a test page extension that provides `EntityProvider` context and accepts entity extensions through input redirects: + +```typescript +import { renderTestApp } from '@backstage/frontend-test-utils'; +import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils'; + +renderTestApp({ + extensions: [createTestEntityPage({ entity: myEntity }), myEntityCard], +}); +``` diff --git a/.changeset/test-utils-jest-peer-dep.md b/.changeset/test-utils-jest-peer-dep.md new file mode 100644 index 0000000000..39c8f1b8f6 --- /dev/null +++ b/.changeset/test-utils-jest-peer-dep.md @@ -0,0 +1,6 @@ +--- +'@backstage/test-utils': patch +'@backstage/backend-test-utils': patch +--- + +Added `@types/jest` as an optional peer dependency, since jest types are exposed in the public API surface. diff --git a/.changeset/thin-flies-travel.md b/.changeset/thin-flies-travel.md deleted file mode 100644 index 4dc29933e8..0000000000 --- a/.changeset/thin-flies-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-events-backend-module-kafka': minor ---- - -Add support for Kafka offset configuration (`fromBeginning`) and `autoCommit` diff --git a/.changeset/thin-insects-cough.md b/.changeset/thin-insects-cough.md deleted file mode 100644 index db24462503..0000000000 --- a/.changeset/thin-insects-cough.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Added `className` and `style` props to the `Table` component. - -Affected components: Table diff --git a/.changeset/thin-teams-roll.md b/.changeset/thin-teams-roll.md deleted file mode 100644 index f0c395410a..0000000000 --- a/.changeset/thin-teams-roll.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Fixed a spelling mistake in root health service shutdown response. diff --git a/.changeset/thirty-meals-rest.md b/.changeset/thirty-meals-rest.md new file mode 100644 index 0000000000..7972167c00 --- /dev/null +++ b/.changeset/thirty-meals-rest.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Internal update to simplify testing utility implementations. diff --git a/.changeset/thirty-mirrors-own.md b/.changeset/thirty-mirrors-own.md new file mode 100644 index 0000000000..629936d8a7 --- /dev/null +++ b/.changeset/thirty-mirrors-own.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +The Table component now wraps the react-aria-components `Table` with a `ResizableTableContainer` only if any column has a width property set. This means that column widths can adapt to the content otherwise (if no width is explicitly set). + +Affected components: Table diff --git a/.changeset/three-rooms-enjoy.md b/.changeset/three-rooms-enjoy.md new file mode 100644 index 0000000000..9856aba0e8 --- /dev/null +++ b/.changeset/three-rooms-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': minor +--- + +Added support for configuring the default search type in the search page via the `search.defaultType` option in `app-config.yaml`. This applies to both the legacy and new frontend systems. If not set, the default is empty, which means searching for "all" types. diff --git a/.changeset/tiny-lamps-give.md b/.changeset/tiny-lamps-give.md new file mode 100644 index 0000000000..047c690f3f --- /dev/null +++ b/.changeset/tiny-lamps-give.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Bump react-aria-components to v1.14.0 diff --git a/.changeset/tired-queens-cross.md b/.changeset/tired-queens-cross.md new file mode 100644 index 0000000000..2f1be86f15 --- /dev/null +++ b/.changeset/tired-queens-cross.md @@ -0,0 +1,14 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Removed `large` size variant from Button component as it was never implemented. + +**Migration:** + +```diff +- ++ +``` + +**Affected components:** Button diff --git a/.changeset/tired-sides-share.md b/.changeset/tired-sides-share.md new file mode 100644 index 0000000000..661a7aee50 --- /dev/null +++ b/.changeset/tired-sides-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-react': patch +--- + +Internal refactor to move implementation of blueprints from `@backstage/frontend-plugin-api` to this package. diff --git a/.changeset/tricky-tips-invite.md b/.changeset/tricky-tips-invite.md deleted file mode 100644 index 6c0ff236ae..0000000000 --- a/.changeset/tricky-tips-invite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Fixed bug in RepoUrlPickerComponent component where repository names were not being autocompleted. diff --git a/.changeset/twelve-dolls-tap.md b/.changeset/twelve-dolls-tap.md new file mode 100644 index 0000000000..33c184b39d --- /dev/null +++ b/.changeset/twelve-dolls-tap.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixed React 17 compatibility by using `useId` from `react-aria` instead of the built-in React hook which is only available in React 18+. diff --git a/.changeset/twenty-candles-open.md b/.changeset/twenty-candles-open.md deleted file mode 100644 index 94050dea05..0000000000 --- a/.changeset/twenty-candles-open.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-graph': patch -'@backstage/plugin-search': patch ---- - -Update for the `qs` library bump: the old array limit setting has changed to be more strict; you can no longer just give a zero to mean unlimited. So we choose an arbitrary high value, to at least go higher than the default 20. diff --git a/.changeset/twenty-clubs-itch.md b/.changeset/twenty-clubs-itch.md new file mode 100644 index 0000000000..c69aabec7d --- /dev/null +++ b/.changeset/twenty-clubs-itch.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed Switch component disabled state styling to show `not-allowed` cursor and disabled text color. + +**Affected components:** Switch diff --git a/.changeset/ui-standard-build.md b/.changeset/ui-standard-build.md new file mode 100644 index 0000000000..d0d8bd7f65 --- /dev/null +++ b/.changeset/ui-standard-build.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Migrated to use the standard `backstage-cli package build` for CSS bundling instead of a custom build script. diff --git a/.changeset/vast-rockets-dig.md b/.changeset/vast-rockets-dig.md new file mode 100644 index 0000000000..3340fabb2c --- /dev/null +++ b/.changeset/vast-rockets-dig.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-auth-backend-module-gitlab-provider': minor +'@backstage/plugin-catalog-backend-module-gitlab': minor +--- + +Added the `{gitlab-integration-host}/user-id` annotation to store GitLab's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. diff --git a/.changeset/warm-pets-accept.md b/.changeset/warm-pets-accept.md deleted file mode 100644 index 5bf62cbb1a..0000000000 --- a/.changeset/warm-pets-accept.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/plugin-app': patch ---- - -The following blueprints are being restricted to only be used in app plugin overrides and modules. They will now produce a deprecation warning when used outside of the app plugin: - -- `IconBundleBlueprint` -- `NavContentBlueprint` -- `RouterBlueprint` -- `SignInPageBlueprint` -- `SwappableComponentBlueprint` -- `ThemeBlueprint` -- `TranslationBlueprint` diff --git a/.changeset/weak-lemons-create.md b/.changeset/weak-lemons-create.md deleted file mode 100644 index 344a6660ce..0000000000 --- a/.changeset/weak-lemons-create.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Bump minimum required `@swc/core` to avoid transpilation bug diff --git a/.changeset/wet-cups-juggle.md b/.changeset/wet-cups-juggle.md new file mode 100644 index 0000000000..5e1185f7df --- /dev/null +++ b/.changeset/wet-cups-juggle.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Removed link styles from LinkButton to avoid styling inconsistencies related to import order. diff --git a/.changeset/whole-dodos-matter.md b/.changeset/whole-dodos-matter.md deleted file mode 100644 index df65f33ed7..0000000000 --- a/.changeset/whole-dodos-matter.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@backstage/plugin-techdocs-node': minor ---- - -**BREAKING:** It's now possible to use the credentials from the `integrations.awsS3` config to authenticate with AWS S3. The new priority is: - -1. `aws.accounts` -2. `techdocs.publisher.awsS3.credentials` -3. `integrations.awsS3` -4. Default credential chain - -In case of multiple `integrations.awsS3` are present, the target integration is determined by the `accessKeyId` in `techdocs.publisher.awsS3.credentials` if provided. Otherwise, the default credential chain is used. - -This means that depending on your setup, this feature may break your existing setup. -In general: - -- if you are configuring `aws.accounts`, no action is required -- if you are configuring `techdocs.publisher.awsS3.credentials`, no action is required -- if you are configuring multiple integrations under `integrations.awsS3`, no action is required -- if you are configuring a single integration under `integrations.awsS3`, make sure that the integration has access to the bucket you are using for TechDocs diff --git a/.changeset/whole-yaks-repair.md b/.changeset/whole-yaks-repair.md deleted file mode 100644 index b23fdaa285..0000000000 --- a/.changeset/whole-yaks-repair.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch -'@backstage/backend-test-utils': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/backend-defaults': patch -'@backstage/plugin-permission-common': patch -'@backstage/plugin-scaffolder-react': patch -'@backstage/plugin-permission-node': patch -'@backstage/plugin-scaffolder-node': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-auth-node': patch ---- - -Upgrade `zod-to-json-schema` to latest version diff --git a/.changeset/wicked-walls-accept.md b/.changeset/wicked-walls-accept.md new file mode 100644 index 0000000000..6f16088ed9 --- /dev/null +++ b/.changeset/wicked-walls-accept.md @@ -0,0 +1,59 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Removed link and tint color tokens, added new status foreground tokens, and improved Link component styling + +The following color tokens have been removed: + +- `--bui-fg-link` (and all related tokens: `-hover`, `-pressed`, `-disabled`) +- `--bui-fg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`) +- `--bui-bg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`) +- `--bui-border-tint` (and all related tokens) + +**New Status Tokens:** + +Added dedicated tokens for status colors that distinguish between usage on status backgrounds vs. standalone usage: + +- `--bui-fg-danger-on-bg` / `--bui-fg-danger` +- `--bui-fg-warning-on-bg` / `--bui-fg-warning` +- `--bui-fg-success-on-bg` / `--bui-fg-success` +- `--bui-fg-info-on-bg` / `--bui-fg-info` + +The `-on-bg` variants are designed for text on colored backgrounds, while the base variants are for standalone status indicators with improved visibility and contrast. + +**Migration:** + +For link colors, migrate to one of the following alternatives: + +```diff +.custom-link { +- color: var(--bui-fg-link); ++ color: var(--bui-fg-info); /* For informational links */ ++ /* or */ ++ color: var(--bui-fg-primary); /* For standard text links */ +} +``` + +For tint colors (backgrounds, foregrounds, borders), migrate to appropriate status or neutral colors: + +```diff +.info-section { +- background: var(--bui-bg-tint); ++ background: var(--bui-bg-info); /* For informational sections */ ++ /* or */ ++ background: var(--bui-bg-neutral-1); /* For neutral emphasis */ +} +``` + +If you're using status foreground colors on colored backgrounds, update to the new `-on-bg` tokens: + +```diff +.error-badge { +- color: var(--bui-fg-danger); ++ color: var(--bui-fg-danger-on-bg); + background: var(--bui-bg-danger); +} +``` + +**Affected components:** Link diff --git a/.changeset/wild-emus-write.md b/.changeset/wild-emus-write.md new file mode 100644 index 0000000000..5c86a1d07d --- /dev/null +++ b/.changeset/wild-emus-write.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Migrated `EntityRelationWarning` and `EntityProcessingErrorsPanel` components from Material UI to Backstage UI. diff --git a/.changeset/wild-toys-retire.md b/.changeset/wild-toys-retire.md deleted file mode 100644 index f7179e2527..0000000000 --- a/.changeset/wild-toys-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-mui-to-bui': patch ---- - -Updated tokens from `--bui-bg` to `--bui-bg-surface-0` diff --git a/.changeset/wise-rabbits-double.md b/.changeset/wise-rabbits-double.md new file mode 100644 index 0000000000..6164c6e95c --- /dev/null +++ b/.changeset/wise-rabbits-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/filter-predicates': minor +--- + +Introduced package, basically as the extracted predicate types from `@backstage/plugin-catalog-react/alpha` diff --git a/.changeset/yellow-ties-dream.md b/.changeset/yellow-ties-dream.md new file mode 100644 index 0000000000..8326b28319 --- /dev/null +++ b/.changeset/yellow-ties-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Migrate audit events reference docs to http://backstage.io/docs. diff --git a/.changeset/yellow-years-run.md b/.changeset/yellow-years-run.md new file mode 100644 index 0000000000..fc57664911 --- /dev/null +++ b/.changeset/yellow-years-run.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Updated README for backend-dynamic-feature-service diff --git a/.changeset/young-pens-wash.md b/.changeset/young-pens-wash.md new file mode 100644 index 0000000000..0deeb40a65 --- /dev/null +++ b/.changeset/young-pens-wash.md @@ -0,0 +1,50 @@ +--- +'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch +'@backstage/plugin-auth-backend-module-bitbucket-server-provider': patch +'@backstage/plugin-auth-backend-module-azure-easyauth-provider': patch +'@backstage/plugin-auth-backend-module-oauth2-proxy-provider': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-auth-backend-module-atlassian-provider': patch +'@backstage/plugin-auth-backend-module-bitbucket-provider': patch +'@backstage/plugin-auth-backend-module-microsoft-provider': patch +'@backstage/plugin-auth-backend-module-onelogin-provider': patch +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +'@backstage/plugin-auth-backend-module-gcp-iap-provider': patch +'@backstage/plugin-auth-backend-module-github-provider': patch +'@backstage/plugin-auth-backend-module-gitlab-provider': patch +'@backstage/plugin-auth-backend-module-google-provider': patch +'@backstage/plugin-auth-backend-module-oauth2-provider': patch +'@backstage/plugin-auth-backend-module-oidc-provider': patch +'@backstage/plugin-auth-backend-module-okta-provider': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +'@backstage/backend-plugin-api': patch +'@backstage/backend-test-utils': patch +'@backstage/plugin-mcp-actions-backend': patch +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-defaults': patch +'@backstage/frontend-app-api': patch +'@backstage/plugin-permission-common': patch +'@backstage/core-compat-api': patch +'@backstage/core-components': patch +'@backstage/core-plugin-api': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-permission-node': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/plugin-search-backend': patch +'@backstage/core-app-api': patch +'@backstage/plugin-catalog-react': patch +'@backstage/repo-tools': patch +'@backstage/plugin-scaffolder': patch +'@backstage/cli-node': patch +'@backstage/plugin-auth-node': patch +'@backstage/cli': patch +'@backstage/plugin-home': patch +'@backstage/plugin-app': patch +--- + +Bump to latest zod to ensure it has the latest features diff --git a/.changeset/yummy-clowns-fetch.md b/.changeset/yummy-clowns-fetch.md deleted file mode 100644 index 3042490d0e..0000000000 --- a/.changeset/yummy-clowns-fetch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-home': patch ---- - -Updated WidgetOverlay color to use `alpha(theme.palette.background.paper, 0.93)` for better theme alignment instead of hardcoded RGBA diff --git a/contrib/.devcontainer/Dockerfile b/.devcontainer/Dockerfile similarity index 78% rename from contrib/.devcontainer/Dockerfile rename to .devcontainer/Dockerfile index 6407aa26dd..9fcd4ba572 100644 --- a/contrib/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/devcontainers/typescript-node:20 +FROM mcr.microsoft.com/devcontainers/typescript-node:22 RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install chromium \ diff --git a/.devcontainer/devcontainer.env b/.devcontainer/devcontainer.env new file mode 100644 index 0000000000..65350b61d4 --- /dev/null +++ b/.devcontainer/devcontainer.env @@ -0,0 +1 @@ +NODE_OPTIONS=--no-node-snapshot diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..e1cece8896 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,61 @@ +{ + "name": "Backstage", + "forwardPorts": [3000, 7007], + "build": { "dockerfile": "Dockerfile" }, + "features": { + "ghcr.io/devcontainers/features/common-utils:2.5.6": {}, + "ghcr.io/devcontainers-contrib/features/mkdocs:2": {} + }, + "postCreateCommand": "bash .devcontainer/setup.sh", + "hostRequirements": { + "cpus": 2, + "memory": "4gb", + "storage": "32gb" + }, + "runArgs": [ + "--env-file", + ".devcontainer/devcontainer.env", + "--sysctl", + "net.ipv6.conf.all.disable_ipv6=1" + ], + "portsAttributes": { + "3000": { + "label": "Frontend port", + "onAutoForward": "silent", + "requireLocalPort": true + }, + "7007": { + "label": "Backend port", + "onAutoForward": "silent", + "requireLocalPort": true + }, + "9464": { + "onAutoForward": "silent" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "Intility.vscode-backstage" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ], + "files.eol": "\n", + "prettier.endOfLine": "lf", + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.sortMembers": "explicit" + } + } + } + } +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 0000000000..7615e2b3cc --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,28 @@ +#!/bin/bash +echo "Installing Backstage dependencies..." + +yarn install +export VIRTUAL_ENV=$HOME/venv +python3 -m venv $VIRTUAL_ENV +export PATH="$VIRTUAL_ENV/bin:$PATH" +python3 -m pip install mkdocs-techdocs-core + +echo "" +echo "╔════════════════════════════════════════════════════════╗ " +echo "║ 🚀 Setup Complete! Ready to launch Backstage! ║ " +echo "╠════════════════════════════════════════════════════════╣ " +echo "║ ║ " +echo "║ Open a new terminal and run: ║ " +echo "║ ║ " +echo "║ yarn start ║ " +echo "║ ║ " +echo "║ Then access Backstage at: ║ " +echo "║ ║ " +echo "║ http://localhost:3000 ║ " +echo "║ ║ " +echo "║ You might need to refresh the page once backend ║ " +echo "║ is ready. ║ " +echo "║ ║ " +echo "║ Happy coding! 🎉 ║ " +echo "╚════════════════════════════════════════════════════════╝ " +echo "" \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 54f9e69576..4f7c5afa5e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,12 +14,13 @@ * limitations under the License. */ -var path = require('path'); +var path = require('node:path'); module.exports = { root: true, - plugins: ['@spotify', 'notice', 'react', 'testing-library', '@backstage'], + plugins: ['@spotify', 'notice', 'react', 'testing-library', '@backstage', 'node-import'], rules: { + 'node-import/prefer-node-protocol': 1, '@backstage/no-mixed-plugin-imports': [ 'error', { diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bb11c2f9fc..725c6c676b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,7 +4,7 @@ # The last matching pattern takes precedence. # https://help.github.com/articles/about-codeowners/ -* @backstage/maintainers @backstage/reviewers +* @backstage/maintainers yarn.lock @backstage/maintainers @backstage-service */yarn.lock @backstage/maintainers @backstage-service /.changeset @backstage/operations-maintainers @@ -29,7 +29,7 @@ yarn.lock @backstage/maintainers @backst /microsite/data/plugins @backstage/maintainers /microsite/static @backstage/maintainers @backstage/documentation-maintainers /packages @backstage/framework-maintainers -/packages/backend-openapi-utils @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers +/packages/backend-openapi-utils @backstage/maintainers @backstage/openapi-tooling-maintainers /packages/catalog-client @backstage/catalog-maintainers /packages/catalog-model @backstage/catalog-maintainers /packages/cli @backstage/tooling-maintainers @@ -47,21 +47,21 @@ yarn.lock @backstage/maintainers @backst /plugins/app-* @backstage/framework-maintainers /plugins/auth @backstage/auth-maintainers /plugins/auth-* @backstage/auth-maintainers -/plugins/api-docs @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers -/plugins/bitbucket-cloud-common @backstage/maintainers @backstage/reviewers @pjungermann -/plugins/catalog @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers -/plugins/catalog-* @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers -/plugins/catalog-backend-module-aws @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann -/plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann -/plugins/catalog-backend-module-backstage-openapi @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers -/plugins/catalog-backend-module-gitea @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers -/plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann -/plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers -/plugins/catalog-graph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @backstage/sda-se-reviewers -/plugins/devtools @backstage/maintainers @backstage/reviewers @awanlin -/plugins/devtools-backend @backstage/maintainers @backstage/reviewers @awanlin -/plugins/devtools-common @backstage/maintainers @backstage/reviewers @awanlin -/plugins/events-* @backstage/maintainers @backstage/reviewers @backstage/events-maintainers +/plugins/api-docs @backstage/maintainers @backstage/sda-se-reviewers +/plugins/bitbucket-cloud-common @backstage/maintainers @pjungermann +/plugins/catalog @backstage/maintainers @backstage/catalog-maintainers +/plugins/catalog-* @backstage/maintainers @backstage/catalog-maintainers +/plugins/catalog-backend-module-aws @backstage/maintainers @backstage/catalog-maintainers @pjungermann +/plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/catalog-maintainers @pjungermann +/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-puppetdb @backstage/maintainers @backstage/catalog-maintainers +/plugins/catalog-graph @backstage/maintainers @backstage/catalog-maintainers @backstage/sda-se-reviewers +/plugins/devtools @backstage/maintainers @awanlin +/plugins/devtools-backend @backstage/maintainers @awanlin +/plugins/devtools-common @backstage/maintainers @awanlin +/plugins/events-* @backstage/maintainers @backstage/events-maintainers /plugins/home @backstage/home-maintainers /plugins/home-* @backstage/home-maintainers /plugins/kubernetes @backstage/kubernetes-maintainers @@ -70,17 +70,17 @@ yarn.lock @backstage/maintainers @backst /plugins/notifications-* @backstage/maintainers @backstage/notifications-maintainers /plugins/scaffolder-backend-module-notifications @backstage/maintainers @backstage/notifications-maintainers /plugins/permission-* @backstage/permission-maintainers -/plugins/scaffolder @backstage/maintainers @backstage/reviewers @backstage/scaffolder-maintainers -/plugins/scaffolder-* @backstage/maintainers @backstage/reviewers @backstage/scaffolder-maintainers +/plugins/scaffolder @backstage/maintainers @backstage/scaffolder-maintainers +/plugins/scaffolder-* @backstage/maintainers @backstage/scaffolder-maintainers /plugins/search @backstage/search-maintainers /plugins/search-* @backstage/search-maintainers /plugins/signals @backstage/maintainers @backstage/notifications-maintainers /plugins/signals-* @backstage/maintainers @backstage/notifications-maintainers /plugins/techdocs @backstage/techdocs-maintainers /plugins/techdocs-* @backstage/techdocs-maintainers -/plugins/user-settings @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers -/plugins/user-settings-backend @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers -/plugins/user-settings-common @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers +/plugins/user-settings @backstage/maintainers @backstage/sda-se-reviewers +/plugins/user-settings-backend @backstage/maintainers @backstage/sda-se-reviewers +/plugins/user-settings-common @backstage/maintainers @backstage/sda-se-reviewers /scripts @backstage/operations-maintainers diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ee3fe6fda0..2157025b7b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -5,7 +5,8 @@ Backstage is an open platform for building developer portals. This is a TypeScri - `/packages`: Core framework packages (prefixed `@backstage/`) - `/plugins`: Plugin packages (prefixed `@backstage/plugin-*`) - `/packages/app` and `/packages/backend`: Example app for local development -- `/packages/app-next`: Example app using the new frontend system (NFS) +- `/packages/app`: Main example app using the new frontend system +- `/packages/app-legacy`: Example app using the old frontend system - `/docs`: Documentation files Packages prefixed with `core-` (e.g., `@backstage/core-plugin-api`) are part of the old frontend system. Packages prefixed with `frontend-` (e.g., `@backstage/frontend-plugin-api`) are part of the new frontend system (NFS). Packages prefixed with `backend-` (e.g., `@backstage/backend-plugin-api`) are part of the backend system. @@ -42,3 +43,15 @@ Never update ESLint, Prettier, or TypeScript configuration files unless specific ## Repository Structure See `/docs/contribute/project-structure.md` for a detailed description of the repository structure. + +## Pull Request Reviews + +When reviewing a pull request you MUST calculate a priority score between 0 and 100, where a higher score indicates higher priority. You MUST include the score as an HTML comment at the very beginning of the "PR Overview" section, using the following format: ``. + +The score MUST be calculated as a sum of the following: + +- **60% Guideline Adherence**: How well does the pull request follow the project guidelines in `/STYLE.md`, `/REVIEWING.md`, `/CONTRIBUTING.md`, and `/SECURITY.md`? A pull request that fully adheres to all guidelines scores 60 points, while one that violates multiple guidelines scores closer to 0. + +- **40% Complexity**: How straightforward is the pull request? A simple bug fix or documentation update scores the full 40 points. A complex API change, new feature, or architectural modification scores closer to 0. + +Higher priority pull requests (higher scores) are those that are both well-crafted according to project standards AND easy to review and merge quickly. diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 62277b26d6..68c0fb0d52 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -53,7 +53,6 @@ Chai Chainguard changeset changesets -Changesets chanwit Chanwit CI/CD @@ -68,12 +67,10 @@ Cobertura codeblocks Codecov codehilite -Codehilite codemod codemods codeowners codescene -CodeScene composability composable config @@ -125,7 +122,6 @@ devs dialogs disambiguator discoverability -Discoverability dls Dockerfile dockerfiles @@ -136,7 +132,6 @@ Dominik DOMPurify don'ts dynatrace -Dynatrace dyno ecco elasticsearch @@ -157,12 +152,10 @@ faqs featureful Figma firehydrant -FireHydrant Firekube Firestore Fiverr flightcontrol -Flightcontrol Francesco Frontside Gaurav @@ -188,7 +181,6 @@ haproxy hardcoded hardcoding harness -Harness Helidon Henneke Heroku @@ -281,7 +273,6 @@ microsite microtasks middleware minikube -Minikube Minio misattributed misconfiguration @@ -290,7 +281,6 @@ mkdocs Mkdocs modularization monorepo -Monorepo monorepos monospace morgan @@ -327,7 +317,6 @@ Olausson Oldsberg onboarded onboarding -Onboarding onelogin openapi OpenSearch @@ -335,7 +324,6 @@ OpenShift openssl orgs overridable -Overridable padding paddings pagerduty @@ -345,12 +333,13 @@ parallelization param params parseable +passthrough +passwordless Patrik pattison Peloton PEP performant -Performant periskop Periskop permissioned @@ -527,9 +516,12 @@ transpilers trixie truthy tsconfig +TTLs +Turbopack TSDoc typeahead ui +unassign unbreak Unconference undici @@ -539,6 +531,7 @@ unmount unregister unregistering unregistration +unreviewed unstarred untemplated untracked diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml index 079c946173..aca727743a 100644 --- a/.github/workflows/api-breaking-changes-comment.yml +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -23,7 +23,7 @@ jobs: comment-cache-key: ${{ steps.hash.outputs.COMMENT_FILE_HASH }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: disable-sudo: true egress-policy: block @@ -74,7 +74,7 @@ jobs: - name: Cache Comment if: ${{ steps.event.outputs.ACTION != 'closed' }} - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: comment.md key: ${{ steps.hash.outputs.COMMENT_FILE_HASH }} @@ -99,11 +99,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Fetch cached Manifests File id: cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: comment.md key: ${{ needs.setup.outputs.comment-cache-key }} diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 7525ea3a07..faa8ebc507 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -14,11 +14,11 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history @@ -27,13 +27,13 @@ jobs: run: git fetch --depth 1 origin ${{ github.base_ref }} - name: setup-node - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: linux-v22 diff --git a/.github/workflows/automate_area-labels.yml b/.github/workflows/automate_area-labels.yml index e85a9cf11f..cdf4a28b55 100644 --- a/.github/workflows/automate_area-labels.yml +++ b/.github/workflows/automate_area-labels.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index 257db3929a..d7ee3911c8 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -23,18 +23,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history ref: 'refs/pull/${{ github.event.pull_request.number }}/merge' - name: fetch base run: git fetch --depth 1 origin ${{ github.base_ref }} - - uses: backstage/actions/changeset-feedback@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + - uses: backstage/actions/changeset-feedback@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 name: Generate feedback with: diff-ref: 'origin/master' diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index 83a1e32fcf..c1ecde9194 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -24,11 +24,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: '${{ github.event.pull_request.merge_commit_sha }}' diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml index a7eef4fae4..c9ee8f8078 100644 --- a/.github/workflows/automate_stale.yml +++ b/.github/workflows/automate_stale.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit diff --git a/.github/workflows/ci-noop.yml b/.github/workflows/ci-noop.yml index 12aafe18ee..d9851778c6 100644 --- a/.github/workflows/ci-noop.yml +++ b/.github/workflows/ci-noop.yml @@ -40,7 +40,7 @@ jobs: name: Test ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55e01009c5..3524a903d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,20 +28,32 @@ jobs: name: Install ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + # Store the PR number for the Sync Pull Requests workflow + - name: Save PR context + if: matrix.node-version == '22.x' + run: | + mkdir -p ./context + echo "${{ github.event.pull_request.number }}" > ./context/pr-number + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: matrix.node-version == '22.x' + with: + name: pr-context + path: context/ + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -64,20 +76,20 @@ jobs: name: Verify ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -121,12 +133,19 @@ jobs: - name: verify doc links run: node scripts/verify-links.js + - name: verify plugin directory + run: node scripts/verify-plugin-directory.js - name: build all packages run: yarn backstage-cli repo build --all - # For now BUI has a custom build script and needs to be built separately - - name: build BUI - run: yarn --cwd packages/ui build + # Build docs-ui to ensure documentation site builds successfully + - name: install docs-ui dependencies + working-directory: docs-ui + run: yarn install + + - name: build docs-ui + working-directory: docs-ui + run: yarn build - name: verify type dependencies run: yarn lint:type-deps @@ -210,7 +229,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: fetch master branch run: git fetch origin master @@ -220,13 +239,13 @@ jobs: run: git fetch origin ${{ github.event.pull_request.base.ref }} - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -236,7 +255,7 @@ jobs: # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore backstage-cli cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: .cache/backstage-cli key: ${{ runner.os }}-v${{ matrix.node-version }}-backstage-cli-${{ github.run_id }} @@ -260,7 +279,7 @@ jobs: # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save backstage-cli cache - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 if: always() with: path: .cache/backstage-cli diff --git a/.github/workflows/cleanup_patch-files.yml b/.github/workflows/cleanup_patch-files.yml deleted file mode 100644 index 3dc0e27837..0000000000 --- a/.github/workflows/cleanup_patch-files.yml +++ /dev/null @@ -1,160 +0,0 @@ -name: Cleanup Patch Files -on: - push: - branches: - - 'patch/v*' - -concurrency: - group: cleanup-patch-files - cancel-in-progress: false - -jobs: - cleanup: - name: Cleanup Patch Files - runs-on: ubuntu-latest - if: github.repository == 'backstage/backstage' - permissions: - contents: write - steps: - - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - - - name: Configure Git - run: | - git config --global user.email noreply@backstage.io - git config --global user.name 'Github patch cleanup workflow' - - - name: Extract PR numbers from commit messages - id: extract-pr-numbers - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - script: | - const { execSync } = require('child_process'); - - // Extract PR numbers from commits pushed to the patch branch - // Commits have messages in format: "Patch from PR #123" - const beforeSha = context.payload.before; - const afterSha = context.payload.after; - - let commitRange; - if (beforeSha && beforeSha !== '0000000000000000000000000000000000000000') { - commitRange = `${beforeSha}..${afterSha}`; - } else { - // First push to branch, check recent commits - commitRange = `-n 20 ${afterSha}`; - } - - // Get commit messages from git log - let commitMessages; - try { - commitMessages = execSync( - `git log --format=%B ${commitRange}`, - { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] } - ); - } catch (error) { - console.log('No commits found in range'); - commitMessages = ''; - } - - // Extract PR numbers from commit messages matching "Patch from PR #123" - const prNumbers = new Set(); - const prPattern = /Patch from PR #(\d+)/g; - let match; - - while ((match = prPattern.exec(commitMessages)) !== null) { - prNumbers.add(parseInt(match[1], 10)); - } - - // Convert to sorted array - const prNumbersArray = Array.from(prNumbers).sort((a, b) => a - b); - - if (prNumbersArray.length > 0) { - console.log(`Found PR numbers: ${prNumbersArray.join(', ')}`); - core.setOutput('pr_numbers', JSON.stringify(prNumbersArray)); - core.setOutput('has_pr_numbers', 'true'); - } else { - console.log('No PR numbers found in commit messages'); - core.setOutput('pr_numbers', '[]'); - core.setOutput('has_pr_numbers', 'false'); - } - - - name: Checkout master - if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - ref: master - token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - - - name: Delete patch files - if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - PR_NUMBERS: ${{ steps.extract-pr-numbers.outputs.pr_numbers }} - REF_NAME: ${{ github.ref_name }} - with: - github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - script: | - const { execSync } = require('child_process'); - const fs = require('fs'); - const path = require('path'); - - const prNumbers = JSON.parse(process.env.PR_NUMBERS); - const patchesDir = path.join(process.cwd(), '.patches'); - - if (!fs.existsSync(patchesDir)) { - console.log('No .patches directory found, nothing to clean up'); - return; - } - - const deletedFiles = []; - - // Delete patch files for each PR number - for (const prNum of prNumbers) { - const patchFile = path.join(patchesDir, `pr-${prNum}.txt`); - if (fs.existsSync(patchFile)) { - fs.unlinkSync(patchFile); - deletedFiles.push(patchFile); - console.log(`Deleted ${patchFile}`); - } else { - console.log(`Patch file ${patchFile} not found, skipping`); - } - } - - if (deletedFiles.length === 0) { - console.log('No patch files to delete'); - return; - } - - - name: Commit and push changes - if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' - run: | - # Check if there are any changes to commit - if git diff --quiet && git diff --cached --quiet; then - echo "No changes to commit" - exit 0 - fi - - git add .patches/ - git commit -m "chore: remove applied patch files after patch release merge - - Removed patch files for PRs included in patch release to ${{ github.ref_name }}" - - git push origin master - - - name: Summary - if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' - run: | - echo "## Patch Files Cleanup Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Patch release to ${{ github.ref_name }} has been merged." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Patch files have been removed from the master branch." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index b695c88c90..1fe718fabb 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -10,11 +10,11 @@ jobs: timeout-minutes: 10 steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: backstage/actions/cron@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + - uses: backstage/actions/cron@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index f7bb55c1b9..7dd7bbebd6 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -20,24 +20,24 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: backstage ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }} - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -52,14 +52,14 @@ jobs: working-directory: ./example-app - name: Login to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Build and push uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 52684d5529..d7f5509c85 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit @@ -54,55 +54,44 @@ jobs: result-encoding: string - name: checkout latest release - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: refs/tags/${{ steps.find-release.outputs.result }} - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v22.x - - name: build API reference - run: yarn build:api-docs - - - name: upload API reference - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: stable-reference - path: docs/reference/ - if-no-files-found: error - retention-days: 1 - # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }} restore-keys: | ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable- - - name: build API reference (beta) + - name: build API reference run: yarn backstage-repo-tools package-docs - - name: upload API reference (beta) + - name: upload API reference uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: stable-reference-beta + name: stable-reference path: type-docs/ if-no-files-found: error retention-days: 1 # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 if: always() with: path: .cache/package-docs @@ -135,58 +124,47 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: checkout master - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v22.x - - name: build API reference - run: yarn build:api-docs - - - name: upload API reference - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: next-reference - path: docs/reference/ - if-no-files-found: error - retention-days: 1 - # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-${{ github.run_id }} restore-keys: | ${{ runner.os }}-v${{ matrix.node-version }}-package-docs- - - name: build API reference (beta) + - name: build API reference run: yarn backstage-repo-tools package-docs - - name: upload API reference (beta) + - name: upload API reference uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: next-reference-beta + name: next-reference path: type-docs/ if-no-files-found: error retention-days: 1 # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 if: always() with: path: .cache/package-docs @@ -240,19 +218,19 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth # Stable docs - name: checkout latest release - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: refs/tags/${{ needs.stable.outputs.release }} @@ -260,23 +238,31 @@ jobs: run: yarn install --immutable working-directory: microsite - - name: download stable reference - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: stable-reference - path: docs/reference - - name: download stable OpenAPI API docs uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-openapi-docs path: docs - - name: grab lastest releases docs + - name: grab latest releases docs run: | git fetch origin master --depth 1 git checkout FETCH_HEAD -- docs/releases + - name: create missing generated file + run: | + mkdir -p docs/reference + cat > docs/reference/index.md < ./pr/pr_number - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: pr_number-${{ github.event.pull_request.number }} - path: pr/ - overwrite: true diff --git a/.github/workflows/pr-review-comment.yaml b/.github/workflows/pr-review-comment.yaml deleted file mode 100644 index bb7bd26c53..0000000000 --- a/.github/workflows/pr-review-comment.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# This workflow is triggered by "PR Review Comment Trigger" - -name: PR Review Comment -on: - workflow_run: - workflows: [PR Review Comment Trigger] - types: - - completed - -jobs: - re-review: - runs-on: ubuntu-latest - - # The triggering workflow will report success if the PR needs re-review - if: github.repository == 'backstage/backstage' && github.event.workflow_run.conclusion == 'success' - - steps: - # Inspired by https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow - - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 - with: - egress-policy: audit - - - name: Read PR Number - id: pr-number - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - const [artifact] = allArtifacts.data.artifacts.filter(artifact => artifact.name.startsWith('pr_number-')) - if (!artifact) { - throw new Error('No PR Number artifact available') - } - - const prNumber = artifact.name.slice('pr_number-'.length) - core.setOutput('pr-number', prNumber); - - - uses: backstage/actions/re-review@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 - with: - app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} - private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} - installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} - project-id: PVT_kwDOBFKqdc02LQ - issue-number: ${{ steps.pr-number.outputs.pr-number }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml deleted file mode 100644 index 36988f7d07..0000000000 --- a/.github/workflows/pr.yaml +++ /dev/null @@ -1,34 +0,0 @@ -name: PR -on: - pull_request_target: - types: - - opened - - synchronize - - reopened - - closed - issue_comment: - types: - - created - -jobs: - sync: - runs-on: ubuntu-latest - - # Avoid running on issue comments - if: github.repository == 'backstage/backstage' && ( github.event.pull_request || github.event.issue.pull_request ) - steps: - - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 - with: - egress-policy: audit - - - name: PR sync - uses: backstage/actions/pr-sync@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 - with: - github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} - private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} - installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} - project-id: PVT_kwDOBFKqdc02LQ - auto-assign: false - owning-teams: '@backstage/techdocs-core' diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 077eb2d856..9e76652668 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -29,12 +29,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: 'Checkout code' - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8 + uses: github/codeql-action/upload-sarif@2588666de8825e1e9dc4e2329a4c985457d55b32 # v3.32.1 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_canon.yml b/.github/workflows/sync_bui-docs.yml similarity index 76% rename from .github/workflows/sync_canon.yml rename to .github/workflows/sync_bui-docs.yml index ea6d47bbf5..86d9feafc8 100644 --- a/.github/workflows/sync_canon.yml +++ b/.github/workflows/sync_bui-docs.yml @@ -8,26 +8,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v22.x - name: Checkout backstage/docs-ui - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: backstage/docs-ui path: bui-external-docs diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index 7311d52ae4..d9696acd14 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -10,22 +10,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Fetch changes to previous commit - required for 'only_changed' in Prettier action fetch-depth: 0 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index 9e894631cc..fb51949fd1 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -11,12 +11,12 @@ jobs: if: github.actor == 'dependabot[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_patch-release.yml b/.github/workflows/sync_patch-release.yml index 4e3a622007..e126fe2ab9 100644 --- a/.github/workflows/sync_patch-release.yml +++ b/.github/workflows/sync_patch-release.yml @@ -5,11 +5,6 @@ on: - master paths: - '.patches/**' - pull_request: - branches: - - master - paths: - - '.patches/**' concurrency: group: sync-patch-release @@ -25,19 +20,19 @@ jobs: pull-requests: write steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 20000 fetch-tags: true token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/sync_pull-requests-trigger.yml b/.github/workflows/sync_pull-requests-trigger.yml new file mode 100644 index 0000000000..152837b811 --- /dev/null +++ b/.github/workflows/sync_pull-requests-trigger.yml @@ -0,0 +1,51 @@ +name: Sync Pull Requests Trigger + +on: + pull_request_target: + types: + - opened + - closed + - reopened + - synchronize + - labeled + - unlabeled + - ready_for_review + - converted_to_draft + pull_request_review: + types: [submitted, dismissed] + issue_comment: + types: [created] + +jobs: + trigger: + if: > + github.repository == 'backstage/backstage' && + github.event.sender.type != 'Bot' && + (github.event.pull_request || github.event.issue.pull_request) + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 + with: + egress-policy: audit + + - name: Save PR context + env: + PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} + LABEL_ADDED: ${{ github.event.action == 'labeled' && github.event.label.name || '' }} + REVIEW_STATE: ${{ github.event.review.state }} + ACTOR: ${{ github.actor }} + ACTION: ${{ github.event.action }} + run: | + mkdir -p ./context + echo "$PR_NUMBER" > ./context/pr-number + echo "$LABEL_ADDED" > ./context/label-added + echo "$REVIEW_STATE" > ./context/review-state + echo "$ACTOR" > ./context/actor + echo "$ACTION" > ./context/action + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: pr-context + path: context/ + overwrite: true diff --git a/.github/workflows/sync_pull-requests.yml b/.github/workflows/sync_pull-requests.yml new file mode 100644 index 0000000000..4226b57458 --- /dev/null +++ b/.github/workflows/sync_pull-requests.yml @@ -0,0 +1,70 @@ +name: Sync Pull Requests + +on: + workflow_run: + workflows: ['Sync Pull Requests Trigger', 'CI', 'E2E Linux'] + types: + - completed + +jobs: + run: + runs-on: ubuntu-latest + # Only run on the trigger workflow, or if the source workflow was triggered by a pull request + if: > + github.event.workflow_run.name == 'Sync Pull Requests Trigger' || + github.event.workflow_run.event == 'pull_request' + steps: + - name: Harden Runner + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 + with: + egress-policy: audit + + - name: Download PR context + id: download + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + continue-on-error: true + with: + name: pr-context + path: ./context + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Read context + if: steps.download.outcome == 'success' + id: context + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const fs = require('fs'); + function read(path, fallback) { + try { return fs.readFileSync(path, 'utf8').trim(); } + catch { return fallback; } + } + core.setOutput('pr-number', read('./context/pr-number', '')); + core.setOutput('label-added', read('./context/label-added', '')); + core.setOutput('review-state', read('./context/review-state', '')); + core.setOutput('actor', read('./context/actor', '')); + core.setOutput('action', read('./context/action', 'synchronize')); + + - name: Backstage PR automation + if: steps.context.outputs.pr-number + uses: backstage/actions/pr-automation@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 + with: + app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} + private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} + installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} + project-owner: backstage + project-number: '14' + pr-number: ${{ steps.context.outputs.pr-number }} + label-added: ${{ steps.context.outputs.label-added }} + review-state: ${{ steps.context.outputs.review-state }} + actor: ${{ steps.context.outputs.actor }} + action: ${{ steps.context.outputs.action }} + required-checks: | + DCO + E2E Linux 22.x + E2E Linux 24.x + Test 22.x + Test 24.x + Verify 22.x + Verify 24.x diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index 871b8b9712..b34615dd6a 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -8,24 +8,24 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # 'v' prefix is added here for the tag, we keep it out of the manifest logic ref: v${{ github.event.client_payload.version }} - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v22.x @@ -35,7 +35,7 @@ jobs: # Checkout backstage/versions into /backstage/versions, which is where store the output - name: Checkout versions - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: backstage/versions path: versions diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index 07f2e24697..8575cab760 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -11,12 +11,12 @@ jobs: if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 0d1233020c..ea93ac6334 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -12,19 +12,19 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v22.x diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 632d03c35d..6b91c1b9dd 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -25,11 +25,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Monitor and Synchronize Snyk Policies uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master with: @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8 + uses: github/codeql-action/upload-sarif@2588666de8825e1e9dc4e2329a4c985457d55b32 # v3.32.1 with: sarif_file: snyk.sarif diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 3ba9833645..7df18100b0 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -13,20 +13,24 @@ jobs: name: Create Changeset PR runs-on: ubuntu-latest + env: + CI: true + NODE_OPTIONS: --max-old-space-size=8192 + steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 20000 fetch-tags: true token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/verify_accessibility-noop.yml b/.github/workflows/verify_accessibility-noop.yml index ee696ebc7f..51caba5acd 100644 --- a/.github/workflows/verify_accessibility-noop.yml +++ b/.github/workflows/verify_accessibility-noop.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 8f1b611f1f..c9f92103bf 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -20,17 +20,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v22.x - name: run Lighthouse CI diff --git a/.github/workflows/verify_chromatic-noop.yml b/.github/workflows/verify_chromatic-noop.yml index 35015def52..cb4c96902a 100644 --- a/.github/workflows/verify_chromatic-noop.yml +++ b/.github/workflows/verify_chromatic-noop.yml @@ -20,7 +20,7 @@ jobs: name: Chromatic steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit diff --git a/.github/workflows/verify_chromatic.yml b/.github/workflows/verify_chromatic.yml index f08441bcc1..716209bdf6 100644 --- a/.github/workflows/verify_chromatic.yml +++ b/.github/workflows/verify_chromatic.yml @@ -24,23 +24,23 @@ jobs: name: Chromatic steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 10000 # Required to retrieve git history - name: Use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: Install dependencies - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 5d976f8583..fd531c2be8 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -42,12 +42,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8 + uses: github/codeql-action/init@2588666de8825e1e9dc4e2329a4c985457d55b32 # v3.32.1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8 + uses: github/codeql-action/autobuild@2588666de8825e1e9dc4e2329a4c985457d55b32 # v3.32.1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8 + uses: github/codeql-action/analyze@2588666de8825e1e9dc4e2329a4c985457d55b32 # v3.32.1 diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 8810f05a16..af42f25390 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -12,11 +12,11 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Vale does not support file excludes, so we use the script to generate a list of files instead # The action also does not allow args or a local config file to be passed in, so the files array diff --git a/.github/workflows/verify_e2e-linux-noop.yml b/.github/workflows/verify_e2e-linux-noop.yml index 152f4cc2c0..38ee55f72c 100644 --- a/.github/workflows/verify_e2e-linux-noop.yml +++ b/.github/workflows/verify_e2e-linux-noop.yml @@ -29,7 +29,7 @@ jobs: name: E2E Linux ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index 55d92b30b3..e8f5660d69 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -43,11 +43,23 @@ jobs: name: E2E Linux ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + # Store the PR number for the Sync Pull Requests workflow + - name: Save PR context + if: matrix.node-version == '22.x' && github.event_name == 'pull_request' + run: | + mkdir -p ./context + echo "${{ github.event.pull_request.number }}" > ./context/pr-number + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: matrix.node-version == '22.x' && github.event_name == 'pull_request' + with: + name: pr-context + path: context/ + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Configure Git run: | @@ -55,12 +67,12 @@ jobs: git config --global user.name 'GitHub e2e user' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index fce6691e7f..373e891d48 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -32,21 +32,21 @@ jobs: name: Techdocs steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.9' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_e2e-windows-noop.yml b/.github/workflows/verify_e2e-windows-noop.yml index f04748a950..4759555905 100644 --- a/.github/workflows/verify_e2e-windows-noop.yml +++ b/.github/workflows/verify_e2e-windows-noop.yml @@ -25,7 +25,7 @@ jobs: name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index ce037f8c01..fdfdeabe1d 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -33,7 +33,7 @@ jobs: name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit @@ -44,7 +44,7 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Configure Git run: | @@ -52,13 +52,13 @@ jobs: git config --global user.name 'GitHub e2e user' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: setup python - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.10' @@ -80,7 +80,7 @@ jobs: uses: browser-actions/setup-chrome@803ef6dfb4fdf22089c9563225d95e4a515820a0 # latest - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index 681ac2c07a..84d666f00e 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -14,12 +14,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Fossa run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash" diff --git a/.github/workflows/verify_microsite-noop.yml b/.github/workflows/verify_microsite-noop.yml index 70686e3bd9..acab96962c 100644 --- a/.github/workflows/verify_microsite-noop.yml +++ b/.github/workflows/verify_microsite-noop.yml @@ -21,7 +21,7 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index b11ef3172c..8b94b7c942 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit @@ -59,53 +59,42 @@ jobs: result-encoding: string - name: checkout latest release - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: refs/tags/${{ steps.find-release.outputs.result }} - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v22.x - - name: build API reference - run: yarn build:api-docs - - - name: upload API reference - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: stable-reference - path: docs/reference/ - if-no-files-found: error - retention-days: 1 - # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }} restore-keys: | ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable- - - name: build API reference (beta) + - name: build API reference run: yarn backstage-repo-tools package-docs - - name: upload API reference (beta) + - name: upload API reference uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: stable-reference-beta + name: stable-reference path: type-docs/ # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 if: always() with: path: .cache/package-docs @@ -137,56 +126,45 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - name: checkout master - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + uses: backstage/actions/yarn-install@a3895cac2a515224a101c4a2b962b216ed0c9ac8 # v0.7.5 with: cache-prefix: ${{ runner.os }}-v22.x - - name: build API reference - run: yarn build:api-docs - - - name: upload API reference - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: next-reference - path: docs/reference/ - if-no-files-found: error - retention-days: 1 - # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 with: path: .cache/package-docs key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-next-${{ github.run_id }} restore-keys: | ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-next- - - name: build API reference (beta) + - name: build API reference run: yarn backstage-repo-tools package-docs - - name: upload API reference (beta) + - name: upload API reference uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: next-reference-beta + name: next-reference path: type-docs/ # Always save success cache even if there were failures, that way it can be used in re-triggered builds - name: save package-docs cache - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5 if: always() with: path: .cache/package-docs @@ -234,17 +212,17 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x - - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: '3.9' @@ -267,23 +245,31 @@ jobs: - name: verify yarn dependency duplicates run: node scripts/verify-lockfile-duplicates.js - - name: download stable reference - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: stable-reference - path: docs/reference - - name: download stable OpenAPI API docs uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-openapi-docs path: docs - - name: grab lastest releases docs + - name: grab latest releases docs run: | git fetch origin master --depth 1 git checkout FETCH_HEAD -- docs/releases + - name: create missing generated file + run: | + mkdir -p docs/reference + cat > docs/reference/index.md < ` in the root of the repository. +Once a patch has been applied and merged, manually delete the corresponding patch file from this directory. The patch script will automatically skip patches that have already been applied to the target branch, so it's safe to re-run the script even if some patches are already present. + ## GitHub Workflow A GitHub workflow automatically keeps a "Patch Release" PR in sync with the patches listed in this directory. When you add, modify, or remove patch files, the workflow will: diff --git a/.patches/create-pr-patch.js b/.patches/create-pr-patch.js index 528d4fc404..381810a6ff 100755 --- a/.patches/create-pr-patch.js +++ b/.patches/create-pr-patch.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -const fs = require('fs'); -const path = require('path'); +const fs = require('node:fs'); +const path = require('node:path'); const prNumber = process.argv[2]; const description = process.argv.slice(3).join(' '); diff --git a/.patches/pr-32409.txt b/.patches/pr-32409.txt new file mode 100644 index 0000000000..a14d9feb37 --- /dev/null +++ b/.patches/pr-32409.txt @@ -0,0 +1 @@ +Fixes a deprecation warning for Router extension in tests \ No newline at end of file diff --git a/.patches/pr-32428.txt b/.patches/pr-32428.txt new file mode 100644 index 0000000000..77136cf4c8 --- /dev/null +++ b/.patches/pr-32428.txt @@ -0,0 +1 @@ +fix(ui): restore React 17 compatibility for useId \ No newline at end of file diff --git a/.patches/pr-32524.txt b/.patches/pr-32524.txt new file mode 100644 index 0000000000..d5aa035f3f --- /dev/null +++ b/.patches/pr-32524.txt @@ -0,0 +1 @@ +Depend on a version of the zod library that has the version 3 and 4 exports \ No newline at end of file diff --git a/.storybook/main.ts b/.storybook/main.ts index 6a13b1b0a7..f891cd9692 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -26,16 +26,14 @@ const allStories = isChromatic ]; const rootPath = '../'; -const storiesSrcMdx = 'src/**/*.mdx'; const storiesSrcGlob = 'src/**/*.stories.@(js|jsx|mjs|ts|tsx)'; const getStoriesPath = (element: string, pattern: string) => posix.join(rootPath, element, pattern); -const stories = allStories.flatMap(element => [ - getStoriesPath(element, storiesSrcMdx), +const stories = allStories.map(element => getStoriesPath(element, storiesSrcGlob), -]); +); // Resolve absolute path of a package. Needed in monorepos. function getAbsolutePath(value: string): any { diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index ccd7adc57f..e67522fd87 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -33,7 +33,6 @@ export default definePreview({ { value: 'light', icon: 'circlehollow', title: 'Light' }, { value: 'dark', icon: 'circle', title: 'Dark' }, ], - showName: true, dynamicTitle: true, }, }, @@ -47,7 +46,6 @@ export default definePreview({ { value: 'backstage', title: 'Backstage' }, { value: 'spotify', title: 'Spotify' }, ], - showName: true, dynamicTitle: true, }, }, @@ -136,11 +134,11 @@ export default definePreview({ }; }, [selectedTheme, selectedThemeName]); - document.body.style.backgroundColor = 'var(--bui-bg-surface-0)'; + document.body.style.backgroundColor = 'var(--bui-bg-neutral-0)'; const docsStoryElements = document.getElementsByClassName('docs-story'); Array.from(docsStoryElements).forEach(element => { (element as HTMLElement).style.backgroundColor = - 'var(--bui-bg-surface-0)'; + 'var(--bui-bg-neutral-0)'; }); return ( diff --git a/.storybook/storybook.css b/.storybook/storybook.css index e69c4a8eb1..617af88ba2 100644 --- a/.storybook/storybook.css +++ b/.storybook/storybook.css @@ -13,7 +13,7 @@ } [data-theme-mode='dark'] { - --sb-sidebar-bg: var(--bui-bg-surface-1); + --sb-sidebar-bg: var(--bui-bg-neutral-1); } [data-theme-name='spotify'] { @@ -24,7 +24,7 @@ --sb-panel-right: 8px; --sb-sidebar-border: none; --sb-sidebar-border-right: none; - --sb-sidebar-bg: var(--bui-bg-surface-1); + --sb-sidebar-bg: var(--bui-bg-neutral-1); --sb-options-border: none; --sb-options-border-left: none; --sb-content-padding-inline: 258px; diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css index 352c633a15..c52062fe96 100644 --- a/.storybook/themes/spotify.css +++ b/.storybook/themes/spotify.css @@ -222,11 +222,11 @@ --bui-bg-solid-disabled: #0f6c30; --bui-fg-primary: var(--bui-black); - --bui-fg-secondary: var(--bui-gray-7); + --bui-fg-secondary: #757575; --bui-fg-solid: var(--bui-black); --bui-fg-solid-disabled: #62ab7c; - --bui-border: var(--bui-gray-3); + --bui-border: #d9d9d9; --bui-border-hover: rgba(0, 0, 0, 0.3); --bui-border-pressed: rgba(0, 0, 0, 0.5); --bui-border-disabled: rgba(0, 0, 0, 0.1); @@ -246,7 +246,7 @@ } [data-theme-mode='dark'][data-theme-name='spotify'] { - --bui-bg-surface-0: var(--bui-black); + --bui-bg-neutral-0: var(--bui-black); --bui-bg-solid: #1ed760; --bui-bg-solid-hover: #3be477; @@ -258,19 +258,15 @@ --bui-bg-success: #132d21; --bui-fg-primary: var(--bui-white); - --bui-fg-secondary: var(--bui-gray-7); - --bui-fg-link: var(--bui-white); - --bui-fg-link-hover: var(--bui-white); - --bui-fg-disabled: var(--bui-gray-5); + --bui-fg-secondary: #9e9e9e; + --bui-fg-disabled: #575757; --bui-fg-solid: var(--bui-black); --bui-fg-solid-disabled: #072f15; - --bui-fg-tint: var(--bui-white); - --bui-fg-tint-disabled: var(--bui-gray-5); --bui-fg-danger: #e22b2b; --bui-fg-warning: #e36d05; --bui-fg-success: #1db954; - --bui-border: var(--bui-gray-3); + --bui-border: #373737; --bui-border-hover: rgba(255, 255, 255, 0.4); --bui-border-pressed: rgba(255, 255, 255, 0.5); --bui-border-disabled: rgba(255, 255, 255, 0.2); diff --git a/ADOPTERS.md b/ADOPTERS.md index 2b3cad0b25..9a0e2fb0fd 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -91,7 +91,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [HP Inc](https://www.hp.com) | [Damon Kaswell](https://github.com/dekoding) | DevEx engagement hub (dev portal: docs, standards, Q&A) and extensive assets catalog (APIs, services, code, data, etc.) for the pan-HP internal developer community. | | [VMware](https://www.vmware.com) | [Waldir Montoya](https://github.com/waldirmontoya25), [Kris Applegate](https://github.com/krisapplegate), [Jamie Klassen](https://github.com/jamieklassen) | Part of [Tanzu Application Platform](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/index.html) offering; internal developer portal | | [Ualá](https://www.uala.com.ar/) | [Santiago Bernal](https://github.com/sabernal) | Initial work being done to centralize documentation for all our microservices and APIs, as well as scaffolding new services and tracking code quality | -| [IKEA IT AB](https://www.ingka.com) | [@bjornramberg](https://github.com/bjornramberg), [@supriyachitale](https://github.com/supriyachitale) | Supporting engineers at scale with self serve access and connecting the dots of our engineering platform and services, enabling product teams to move faster and go further, and unleashing innovation, reuse and co-creation across the organisation. | +| [IKEA Retail](https://www.ingka.com) | [Björn Ramberg](https://github.com/bjornramberg), [Martin Norin](https://github.com/martin-norin) | Centralized developer portal, supporting the internal engineering community with self serve access, documentation, templates, catalog and more. | | [Invitae](https://www.invitae.com/en) | [@ryan-hanchett](https://github.com/ryan-hanchett), [@gmandler42](https://github.com/gmandler42) | Centralized Developer Experience portal, putting all of our tooling behind a single pane of glass and creating a living service catalog. | | [Fortum](https://www.fortum.com/) | [@brunoamaroalmeida](https://github.com/brunoamaroalmeida), [@dhaval-vithalani](https://github.com/dhaval-vithalani), [@sunilkumarmohanty](https://github.com/sunilkumarmohanty) | A central portal containing information about our applications, services, processes and other software assets to be used by Fortum software engineering community. | | [Procore](https://www.procore.com/jobs/engineering) | [@shayon](https://github.com/Shayon), [@kurtaking](https://github.com/kurtaking), [@pc-bob](https://github.com/pc-bob) | Developer portal - centralized software catalog and templates to enable self serve and reduce cognitive load. | @@ -288,3 +288,5 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Sophotech](https://sopho.tech) | [@archy-rock3t-cloud](https://github.com/archy-rock3t-cloud), [Artem Muterko](mailto:artem@sopho.tech) | Custom Developer Platform based on Backstage, providing a service catalog, infrastructure templates, and integrated tooling to give developers a self-service experience. | | [Swiss Mobiliar Insurance Company Ltd.](https://www.mobiliar.ch/) | [Patrick Wyler](mailto:patrick.wyler@mobiliar.ch) and [Beat Winistörfer](mailto:beat.winistoerfer@mobiliar.ch) | The portal provides a unified interface for accessing all relevant DevOps information previously scattered across various locations, enhancing accessibility and clarity for all IT employees. It relies on an internal graph database that enhances the Backstage software catalog with many additional elements. Significant effort has been invested in the visual representation of information through graphs and diagrams, facilitating analysis and improving the understanding of dependencies. | | [DB Systel GmbH](https://www.dbsystel.de/dbsystel-en/) | [DB Systel GmbH](https://github.com/dbsystel) | Deutsche Bahn's Internal Developer Portal leverages Backstage and Crossplane.io to deliver a fully GitOps-driven onboarding experience for engineering teams across the company. Our Scaffolder ecosystem accelerates platform setup (Artifactory, GitLab, OpenShift), service bootstrapping, automated testing, AI-ready backends, and modern frontend development. We also make heavy use of custom catalog modules and frontend plugins that support developers in checking their provisioned resources or security and compliance of their code. | +| [Danske StatsBaner (DSB)](https://www.dsb.dk/om-dsb/job-og-karriere/) | [Isaac Livingston](mailto:isjl@dsb.dk), [Paula-Catalina Curca](mailto:PACU@DSB.DK), [Muhammad Salman Khan](mailto:MUSK@DSB.DK) | DSB's portal is a central catalog of all our microservices, APIs, and systems. Developers can easily create new components with templates that run in our CNCF based developer platform | +| [NN Group](https://www.nn-group.com/) | [Flavia Naezer (Product Owner)](mailto:flavia.naezer@nn-group.com), [Vijai Ramcharan (Principal Engineer)](mailto:vijai.ramcharan@nn-group.com) | Our Backstage powered IDP supports engineers and other makers and shapers in our large financial enterprise with the Software Catalog, a Store for infrastructure, TechDocs and Portal Platform with mini App-Store.| \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80381417a0..adf04c48e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -381,7 +381,7 @@ Or from the Settings UI look for the "Git: Always Sign Off" setting and check th ## API Reports -Backstage uses [API Extractor](https://api-extractor.com/) and TSDoc comments to generate API Reports in Markdown format. These reports are what drive the [API Reference documentation](https://backstage.io/docs/reference/). What this means is that if you are making changes to the API or adding a new plugin then you will need either generate a new API Report or update an existing API Report. If you don't do this the CI build will fail when you create your Pull Request. +Backstage uses [API Extractor](https://api-extractor.com/) and TSDoc comments to generate API Reports in Markdown format. What this means is that if you are making changes to the API or adding a new plugin then you will need either generate a new API Report or update an existing API Report. If you don't do this the CI build will fail when you create your Pull Request. There are two ways you can do this: @@ -432,7 +432,7 @@ Once you've submitted a Pull Request (PR) the various bots will come out and do - checking for commits for their DCO (Developer Certificate of Origin) - kick off the various CI builds -Once these steps are completed, it's just a matter of being patient. As the reviewers have time, they will begin reviewing your PR. When the review process begins, there may be a few layers to this, but the general rule is that you need approval from one of the core maintainers and one from the specific area impacted by your PR. You may also have someone from the community review your changes. This can really help speed things up as they may catch some early items making the review for the maintainers simpler. Once you have the two (2) approvals, it's ready to be merged, a task that is also performed by the maintainers. +Once these steps are completed, it's just a matter of being patient. Reviews are coordinated by maintainers and reviewers based on project area ownership. Prioritization is driven by a number of different factors, but some important ones are size (smaller get higher priority) and alignment with current roadmap priorities. Timely responses to review comments also help keep the pull request moving faster. You may also have someone from the reviewers group review your pull request and request changes. Approval from a member of the reviewers group will greatly increase the priority of your pull request. ### Review Tips diff --git a/LABELS.md b/LABELS.md index f1af06d4fa..cd2d19e6d5 100644 --- a/LABELS.md +++ b/LABELS.md @@ -97,6 +97,27 @@ These labels indicate a workflow status for the owners. - `stale` - The issue or pull request has not seen any activity for a while and will be closed if no further activity is seen. - `no stale` - The issue or pull request should not be closed due to inactivity. +## Pull Request Labels + +The following labels indicate the status of a pull request: + +- `waiting-for:review` - The pull request needs a review and will be visible in the review queue unless already assigned an owner. +- `waiting-for:author` - Changes have been requested by a reviewer and the pull request will not receive a review until the changes are made. A comment made on the pull request by the author will also push it back into the review queue. +- `waiting-for:decision` - The pull request has been marked as more complex and needs a decision from the owners. Progress can still be made and discussion can continue, but expect the review to take longer. These pull requests are often good candidates to bring to a [SIG](https://github.com/backstage/community/tree/main/sigs) meeting. +- `waiting-for:merge` - The pull request has been approved and is awaiting merge. If you have write access and authored the pull request you can merge it yourself. If you do not have access to merge and the pull request has not been merged within a day of approval, please notify the assigned reviewer. + +The following labels indicate the size of a pull request: + +- `size:tiny` - Tiny pull requests receive a higher priority for reviews. +- `size:small` - Small pull requests receive a slightly higher priority for reviews. +- `size:medium` - Medium-sized pull requests receive no change in priority for reviews. +- `size:large` - Large pull requests receive a slightly lower priority for reviews. +- `size:huge` - Huge pull requests receive a lower priority for reviews. + +The following additional labels also apply to pull requests: + +- `reviewer-approved` - The pull request has been approved by a member of the reviewers group. This pull request receives a much higher priority for reviews. + ## Common Issue Filters This is a collection of common issue filters that can help you find issues that you are looking for or that match your interests and skills. @@ -106,7 +127,7 @@ This is a collection of common issue filters that can help you find issues that These issues are ideal for new contributors to get started and don't require much familiarity with Backstage. - [Backend](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Abackend%22%20) -- [Documentation](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Adocs%22%20) +- [Documentation](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22area%3Adocumentation%22) - [Tooling](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Atooling%22%20) - [Web](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Aweb%22%20) @@ -115,7 +136,7 @@ These issues are ideal for new contributors to get started and don't require muc These issues generally require some familiarity with Backstage and the codebase, and are either open for or require contributions from the community. - [Backend]() -- [Documentation]() +- [Documentation]() - [Tooling]() - [Web]() diff --git a/OWNERS.md b/OWNERS.md index 764237d9c1..e09a0a24c3 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -19,7 +19,7 @@ These are the separate project areas of Backstage, each with their own project a ### Auth -Team: @backstage/auth-maintainers +Team: @backstage/auth-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aauth-maintainers) Scope: The Backstage auth plugin and modules, as well as client-side implementations. @@ -34,7 +34,7 @@ Scope: The Backstage auth plugin and modules, as well as client-side implementat ### Catalog -Team: @backstage/catalog-maintainers +Team: @backstage/catalog-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Acatalog-maintainers) Scope: The catalog plugin and catalog model @@ -63,7 +63,7 @@ Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugi ### Design System -Team: @backstage/design-system-maintainers +Team: @backstage/design-system-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Adesign-system-maintainers) Scope: The Backstage design system, component library, as well as surrounding tooling such as Storybook. @@ -71,10 +71,11 @@ Scope: The Backstage design system, component library, as well as surrounding to | ------------------- | ------------ | ------------- | --------------------------------------------- | ------------- | | Charles de Dreuille | Spotify | | [cdedreuille](https://github.com/cdedreuille) | `cdedreuille` | | Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip` | +| Johan Persson | Spotify | | [Naycon](https://github.com/Naycon) | `naycon` | ### Documentation -Team: @backstage/documentation-maintainers +Team: @backstage/documentation-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Adocumentation-maintainers) Scope: The Backstage Documentation and Microsite, excluding the plugins listing @@ -86,7 +87,7 @@ Scope: The Backstage Documentation and Microsite, excluding the plugins listing ### Framework -Team: @backstage/framework-maintainers +Team: @backstage/framework-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aframework-maintainers) Scope: The Backstage core framework, including all revisions of the frontend and backend systems, as well as the App plugin. @@ -113,7 +114,7 @@ Scope: The Backstage [Helm Chart(s)](https://github.com/backstage/charts). ### Home -Team: @backstage/home-maintainers +Team: @backstage/home-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Ahome-maintainers) Scope: The Backstage home page and information architecture @@ -128,7 +129,7 @@ Scope: The Backstage home page and information architecture ### Kubernetes -Team: @backstage/kubernetes-maintainers +Team: @backstage/kubernetes-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Akubernetes-maintainers) Scope: The Kubernetes plugin and the base it provides for other plugins to build upon. @@ -138,7 +139,7 @@ Scope: The Kubernetes plugin and the base it provides for other plugins to build ### Operations -Team: @backstage/operations-maintainers +Team: @backstage/operations-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aoperations-maintainers) Scope: The management and operation of the main Backstage repository and release process, along with the surrounding tooling. @@ -153,7 +154,7 @@ Scope: The management and operation of the main Backstage repository and release ### Permission Framework -Team: @backstage/permission-maintainers +Team: @backstage/permission-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Apermission-maintainers) Scope: The Permission Framework and plugins integrating with the permission framework @@ -168,7 +169,7 @@ Scope: The Permission Framework and plugins integrating with the permission fram ### Search -Team: @backstage/search-maintainers +Team: @backstage/search-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Asearch-maintainers) Scope: The Backstage Search plugin @@ -180,7 +181,7 @@ Scope: The Backstage Search plugin ### TechDocs -Team: @backstage/techdocs-maintainers +Team: @backstage/techdocs-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Atechdocs-maintainers) Scope: The TechDocs plugin and related tooling @@ -194,7 +195,7 @@ Scope: The TechDocs plugin and related tooling ### Tooling -Team: @backstage/tooling-maintainers +Team: @backstage/tooling-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Atooling-maintainers) Scope: All published Backstage CLI tools in the main `backstage` repository that do not belong to other areas, including `@backstage/cli` and `@backstage/repo-tools`. @@ -214,7 +215,7 @@ These incubating project areas have shared ownership with @backstage/maintainers ### Auditor -Team: @backstage/auditor-maintainers +Team: @backstage/auditor-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aauditor-maintainers) Scope: The auditor core service, along with auditor usage in the main repository @@ -224,7 +225,7 @@ Scope: The auditor core service, along with auditor usage in the main repository ### Events -Team: @backstage/events-maintainers +Team: @backstage/events-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aevents-maintainers) Scope: The Events plugin and library, along with events modules in the main repository @@ -234,7 +235,7 @@ Scope: The Events plugin and library, along with events modules in the main repo ### Notifications -Team: @backstage/notifications-maintainers +Team: @backstage/notifications-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Anotifications-maintainers) Scope: The Notifications and Signals plugins and libraries @@ -244,7 +245,7 @@ Scope: The Notifications and Signals plugins and libraries ### OpenAPI Tooling -Team: @backstage/openapi-tooling-maintainers +Team: @backstage/openapi-tooling-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Aopenapi-tooling-maintainers) Scope: Tooling for frontend and backend schema-first OpenAPI development. @@ -254,7 +255,7 @@ Scope: Tooling for frontend and backend schema-first OpenAPI development. ### Scaffolder -Team: @backstage/scaffolder-maintainers +Team: @backstage/scaffolder-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+reviewers%3Ascaffolder-maintainers) Scope: The Scaffolder frontend and backend plugins, and related tooling. @@ -282,6 +283,7 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling. | Carlos Esteban Lopez Jaramillo | VMWare | [luchillo17](https://github.com/luchillo17) | `luchillo17#8777` | | David Tuite | Roadie.io | [dtuite](https://github.com/dtuite) | `David Tuite (roadie.io)#1010` | | Deepankumar Loganathan | | [deepan10](https://github.com/deepan10) | `deepan10` | +| Gabriel Dugny | Believe | [GabDug](https://github.com/GabDug) | `GabDug` | | Heikki Hellgren | OP Financial Group | [drodil](https://github.com/drodil) | `deathammer` | | Himanshu Mishra | Harness.io | [OrkoHunter](https://github.com/OrkoHunter) | `OrkoHunter#1520` | | Irma Solakovic | Roadie.io | [Irma12](https://github.com/Irma12) | `Irma#7629` | diff --git a/README.md b/README.md index e3e9bd77dd..68931a3fde 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ See the [GOVERNANCE.md](https://github.com/backstage/community/blob/main/GOVERNA ## License -Copyright 2020-2025 © The Backstage Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage +Copyright 2020-2026 © The Backstage Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/REVIEWING.md b/REVIEWING.md index 66af880f43..75f6a67eeb 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -2,6 +2,85 @@ This file provides pointers for reviewing pull requests. While the main audience are reviewers, this can also be useful if you are contributing to this repository. +## Quick Links + +- [Incoming Reviews](https://github.com/orgs/backstage/projects/14/views/1) +- [Personal Reviews](https://github.com/orgs/backstage/projects/14/views/2) +- Project area boards in [OWNERS.md](./OWNERS.md) + +## Review Workflow + +Pull request reviews are coordinated by maintainers and reviewers based on project area ownership. Reviews are prioritized and tracked using automated labels and a GitHub Project board. This review process applies to all pull requests in the [main Backstage repository](https://github.com/backstage/backstage), other repositories may have their own review processes. + +All incoming pull request reviews are tracked on the [Incoming Reviews board](https://github.com/orgs/backstage/projects/14/views/1). This board can be used by members of the `@backstage/reviewers` group to find pull requests to review, as well as maintainers that want an overview of all incoming pull requests. + +Project area maintainers can use the same board but with additional filters applied to only show incoming pull requests for their project area. These filtered boards are linked to for each project area in [OWNERS.md](./OWNERS.md). There is also a [personal review board](https://github.com/orgs/backstage/projects/14/views/2) that can be used to track reviews that you have been assigned to. + +### Submitting Reviews + +When reviewing a pull request, always submit a formal review using either **"Approve"** or **"Request changes"**. Do not use "Comment" as a substitute for either of these — the review automation relies on the review state to manage labels and the review board, and only formal approvals and change requests are tracked. + +- Use **"Approve"** when you are satisfied with the changes and believe they are ready to be merged (for your area, at least). +- Use **"Request changes"** when changes are necessary before the pull request can be merged. This signals clearly to the author what is expected and moves the pull request out of the review queue until the author responds. +- Use **"Comment"** only to contribute to discussion, ask questions, or leave optional or minor suggestions that do not block the pull request. + +Being explicit with "Approve" or "Request changes" is important both for transparent communication with the author and for the automation to function correctly. The automation reads these review states to update labels and the review board accordingly, meaning a "Comment" review will not cause any status change. + +### Closing Pull Requests + +Pull requests that have been superseded, are no longer relevant, or otherwise are not worth moving forward should be closed with a comment explaining why. Closed pull requests are automatically removed from the review board by the automation. + +### Review Process for @backstage/reviewers + +Members of the `@backstage/reviewers` do not have any specific areas that they should focus on. They can choose to filter and focus on reviews from any part of the project. They do not assign themselves specific pull requests, but instead leave reviews directly on pull requests without any further process. + +Reviews from this group still have meaningful impact on the review process, as they are picked up by the automation. Approving reviews from a member of this group will add the `reviewer-approved` label to the pull request, which greatly increases its priority and visibility for project area maintainers. Likewise, requesting changes will add the `waiting-for:author` label to the pull request, removing it from the review queue until the author has commented or pushed new changes. + +As a reward for helping out with reviews, members who actively review pull requests will have their own pull requests prioritized higher on the review board. The more reviews you contribute, the higher the priority boost your own pull requests receive. + +### Review Process for Project Area Maintainers + +Project area maintainers are responsible for reviewing and ultimately merging or closing pull requests that target their project area. They should use the global or filtered board for their project area to find pull requests to review. When they find a pull request that they want to review, they should assign themselves to the pull request, removing it from the review queue and placing it on their personal review board. + +Once a pull request has been assigned a single owner, it is their responsibility to review and eventually merge or close the pull request. They manage all ongoing requests on their personal review board, typically prioritizing the ones at the top of the board marked with `waiting-for:review`. If a pull request remains assigned but unreviewed for 14 days, the automation will automatically unassign the reviewer and return the pull request to the review queue. + +### Merging Pull Requests + +When a pull request has been approved, the way it gets merged depends on who the author is: + +- **Outside contributions:** Project area maintainers and core maintainers should merge the pull request themselves once approved, since outside contributors do not have merge access. +- **Maintainer contributions:** Within maintainer teams, it is common to let the author choose when to merge the pull request themselves, unless there is a reason to merge it immediately. The `waiting-for:merge` label will be applied automatically when the pull request is approved. + +### Cross-Area Pull Requests + +Some pull requests may require review from multiple project areas. In these cases the most relevant owner should assign themselves and coordinate with the other owners for additional reviews. If the most relevant owner is not clear, this is preferably solved in a discussion among the owners. Frequent conflicts should lead to a discussion whether `CODEOWNERS` should be updated to simplify the review process. If some owners are currently unavailable, other owners can assign themselves to the pull request and bring it to the point where they approve the changes for their area, and then send the pull request back to the review queue. + +## Pull Request Automation + +Pull request labels and the [review board](https://github.com/orgs/backstage/projects/14) are fully managed by automation. All fields on the review board are computed automatically and **should never be updated manually**. Likewise, pull request labels that are part of the automation should not be added or removed by hand, with one exception described below. + +The automation is triggered by pull request events (opened, updated, reopened, labeled, unlabeled), pull request reviews (submitted, dismissed), issue comments, and CI workflow completions. The source code is available in the [`backstage/actions`](https://github.com/backstage/actions) repository, with the workflow definitions in [`.github/workflows/sync_pull-requests-trigger.yml`](.github/workflows/sync_pull-requests-trigger.yml) and [`.github/workflows/sync_pull-requests.yml`](.github/workflows/sync_pull-requests.yml). + +### Labels + +The `waiting-for:*` status labels, `reviewer-approved` label, and `size:*` labels are all **automated** based on pull request reviews, author activity, and pull request size. See [LABELS.md](./LABELS.md#pull-request-labels) for the meaning of each label. + +The only exception is `waiting-for:decision`, which can and should be set **manually**. Use it when discussion or a decision is needed before the pull request can move forward, for example when the approach needs to be discussed in a [SIG](https://github.com/backstage/community/tree/main/sigs) meeting. Once set, this label will **stick until manually removed** — the automation will not override it. Only remove it once a decision has been made and the pull request is ready to proceed. + +### Review Board Priority + +The priority field on the review board is a number calculated automatically by the automation. It determines the sort order of pull requests on the board. The priority is influenced by several factors: + +- **Pull request size** — Smaller pull requests receive higher priority, where the priority reduces significantly for roughly every 500 lines added. +- **Reviewer approval** — Pull requests with the `reviewer-approved` label receive a large priority boost. +- **Author review contributions** — Authors who have themselves reviewed other pull requests in the repository receive a priority boost proportional to their review activity. +- **Draft status** — Draft pull requests receive significantly reduced priority. +- **CI status** — Pull requests where required CI checks are failing or still pending receive reduced priority. The required checks include DCO, tests, verification, and E2E tests. + +### Stale Review Handling + +If a pull request with the `waiting-for:review` label has been assigned to a reviewer but has not received a review for 14 days, the automation will automatically unassign the reviewer and return the pull request to the review queue. This ensures that pull requests do not get stuck due to reviewer unavailability. + ## Code Style See our code style documented at [STYLE.md](./STYLE.md). @@ -62,6 +141,37 @@ Some things that changeset should NOT contain are: - Documentation - changesets can describe new features, but it should not be relied on for documenting them. Documentation should either be placed in [TSDoc](https://tsdoc.org) comments, package README, or [./docs/](./docs/). - Diffs of internal code, for example mirroring what the pull request changes _inside_ a plugin rather than public surfaces. This is not of interest to the reader of a package changelog. Sometimes, however, a small and concise diff can be used in a changeset to illustrate changes that the user will have to make in _their own_ Backstage installation as part of an upgrade, specifically when breaking changes are made to a package. +### Backstage UI Changeset Format + +Changesets for `@backstage/ui` must follow a standardized format to enable proper documentation generation. See [`.changeset/README.md`](.changeset/README.md#backstage-ui-changeset-format) for the complete guide. + +**Required structure:** + +```markdown +--- +'@backstage/ui': patch +--- + +Brief one-line summary of the change + +Optional detailed description with any markdown content. + +**Migration:** + +Migration instructions (only for breaking changes) + +**Affected components:** Button, ButtonIcon +``` + +**Key requirements:** + +1. **Affected components marker** - Must end with `**Affected components:**` followed by comma-separated component names +2. **Migration marker** - Use `**Migration:**` (bold, with colon) for breaking changes only +3. **No headings** - Never use `##`, `###`, or `####` inside changeset entries (these break semantic structure when the entry becomes a list item in CHANGELOG.md) +4. **Bold markers** - Use bold text markers, not plain text, for reliable parsing + +These markers enable the documentation system to extract metadata for per-component changelogs and separate migration guides. + ### When is a changeset needed? In general our changeset feedback bot will take care of informing whether a changeset is needed or not, but there are some edge cases. Whether a changeset is needed depends mostly on what files have been changed, but sometimes also on the kind of change that has been made. diff --git a/SECURITY.md b/SECURITY.md index 5c6c8106d5..46f36f0a67 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,7 +8,7 @@ See our [Release & Versioning Policy](https://backstage.io/docs/overview/version Please report sensitive security issues via Spotify's [bug-bounty program](https://hackerone.com/spotify) rather than GitHub. -If you have questions about a potential vulnerability, please reach out on Discord by asking for a maintainer in the `#support` channel, or via direct message to a maintainer. +If you have questions about a potential vulnerability, please reach out on Discord by asking for a maintainer in the `#security` channel, or via direct message to a maintainer. ## Remediation and Notification Process diff --git a/beps/0001-notifications-system/notifications-architecture.drawio.svg b/beps/0001-notifications-system/notifications-architecture.drawio.svg index 9c6bb00119..25b27642ca 100644 --- a/beps/0001-notifications-system/notifications-architecture.drawio.svg +++ b/beps/0001-notifications-system/notifications-architecture.drawio.svg @@ -1,537 +1 @@ - - - - - - - - - - -
-
-
- Notification Backend -
-
-
-
- - Notification Backend - -
-
- - - - - - - - -
-
-
- Notification Frontend -
-
-
-
- - Notification Frontend - -
-
- - - - -
-
-
- Signal Backend -
-
-
-
- - Signal Backend - -
-
- - - - - - - - - - -
-
-
- Signal Frontend -
-
-
-
- - Signal Frontend - -
-
- - - - - - -
-
-
- Plugin X Backend -
-
-
-
- - Plugin X Backend - -
-
- - - - -
-
-
- Plugin X Frontend -
-
-
-
- - Plugin X Frontend - -
-
- - - - - - - - - - - - -
-
-
- EventsService -
-
-
-
- - EventsService - -
-
- - - - - - -
-
-
- ... -
-
-
-
- - ... - -
-
- - - - -
-
-
- ... -
-
-
-
- - ... - -
-
- - - - -
-
-
- publish event -
-
-
-
- - publish event - -
-
- - - - -
-
-
- broadcast -
-
-
-
- - broadcast - -
-
- - - - -
-
-
- ??? -
-
-
-
- - ??? - -
-
- - - - -
-
-
- server push -
-
-
-
- - server push - -
-
- - - - -
-
-
- POST -
-
-
-
- - POST - -
-
- - - - - - -
-
-
- External System -
-
-
-
- - External System - -
-
- - - - -
-
-
- email/slack/teams, etc. -
-
-
-
- - email/slack/teams, etc. - -
-
- - - - -
-
-
- Render -
-
-
-
- - Render - -
-
- - - - -
-
-
- - Processor - -
-
-
-
- - Processor - -
-
- - - - -
-
-
- GET -
-
-
-
- - GET - -
-
- - - - -
-
-
- 1 -
-
-
-
- - 1 - -
-
- - - - -
-
-
- 3 -
-
-
-
- - 3 - -
-
- - - - -
-
-
- 1b -
-
-
-
- - 1b - -
-
- - - - -
-
-
- 4 -
-
-
-
- - 4 - -
-
- - - - -
-
-
- 5 -
-
-
-
- - 5 - -
-
- - - - -
-
-
- 6 -
-
-
-
- - 6 - -
-
- - - - -
-
-
- 7b -
-
-
-
- - 7b - -
-
- - - - -
-
-
- 7 -
-
-
-
- - 7 - -
-
- - - - - - -
-
-
- 2 -
-
-
-
- - 2 - -
-
- - - - -
-
-
- Channel Callback -
-
-
-
- - Channel Callback - -
-
-
- - - - - Text is not SVG - cannot display - - - -
+
Notification Backend
Notification Backend
Notification Frontend
Notification Frontend
Signal Backend
Signal Backend
Signal Frontend
Signal Frontend
Plugin X Backend
Plugin X Backend
Plugin X Frontend
Plugin X Frontend
EventsService
EventsService
...
...
...
...
publish event
publish event
broadcast
broadcast
???
???
server push
server push
POST
POST
External System
External System
email/slack/teams, etc.
email/slack/teams, etc.
Render
Render
Processor
Processor
GET
GET
1
1
3
3
1b
1b
4
4
5
5
6
6
7b
7b
7
7
2
2
Channel Callback
Channel Callback
Text is not SVG - cannot display
\ No newline at end of file diff --git a/beps/0002-dynamic-frontend-plugins/README.md b/beps/0002-dynamic-frontend-plugins/README.md index 4787f0fb7a..c783cf052e 100644 --- a/beps/0002-dynamic-frontend-plugins/README.md +++ b/beps/0002-dynamic-frontend-plugins/README.md @@ -301,7 +301,7 @@ An example of [integration with scalprum](https://github.com/backstage/backstage **Dynamic Feature configuration** -The dynamic remote loading can be added directly into the [`createApp`](https://backstage.io/docs/reference/frontend-defaults.createapp) function. +The dynamic remote loading can be added directly into the [`createApp`](https://backstage.io/api/stable/functions/_backstage_frontend-defaults.createApp.html) function. The current `feature` type can be expanded with a `DynamicFrontendFeature` type: @@ -351,7 +351,7 @@ const scalprum = initialize({ }); ``` -Because the [`appLoader`](https://backstage.io/docs/reference/frontend-defaults.createapp) is already async, it is a perfect place to load the plugin registry and init the dynamic plugins. +Because the [`appLoader`](https://backstage.io/api/stable/functions/_backstage_frontend-defaults.createApp.html) is already async, it is a perfect place to load the plugin registry and init the dynamic plugins. Initializing the dynamic feature is just a case of mapping the `DynamicFrontendFeature` to `FrontendFeature` via Scalprum: diff --git a/beps/0003-auth-architecture-evolution/token-sequence-cookie.drawio.svg b/beps/0003-auth-architecture-evolution/token-sequence-cookie.drawio.svg index b20b528c35..29d95b44bf 100644 --- a/beps/0003-auth-architecture-evolution/token-sequence-cookie.drawio.svg +++ b/beps/0003-auth-architecture-evolution/token-sequence-cookie.drawio.svg @@ -1,203 +1 @@ - - - - - - - - -
-
-
- cookie flow -
-
-
-
- - cookie flow - -
-
- - - - - -
-
-
- Browser -
-
-
-
- - Browser - -
-
- - - - - - -
-
-
- Techdocs -
- Backend -
-
-
-
- - Techdocs... - -
-
- - - - - - -
-
-
- GET /cookie -
-
-
-
- - GET /cookie - -
-
- - - - - -
-
-
- set cookie on response -
-
-
-
- - set cookie on response - -
-
- - - - - -
-
-
- browser static content requests -
-
-
-
- - browser static content requests - -
-
- - - - -
-
-
- cookie token -
-
-
-
- - cookie token - -
-
- - - - -
-
-
- cookie token -
-
-
-
- - cookie token - -
-
- - - - -
-
-
- user identity token -
-
-
-
- - user identity token - -
-
- - - - - -
-
-
- - acquire cookie token -
- based on user token -
-
-
-
-
-
- - acquire cookie token... - -
-
- - - - -
- - - - - Text is not SVG - cannot display - - - -
+
cookie flow
cookie flow
Browser
Browser
Techdocs
Backend
Techdocs...
GET /cookie
GET /cookie
set cookie on response
set cookie on response
browser static content requests
browser static content requests
cookie token
cookie token
cookie token
cookie token
user identity token
user identity token
acquire cookie token
based on user token
acquire cookie token...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/beps/0003-auth-architecture-evolution/token-sequence-obo.drawio.svg b/beps/0003-auth-architecture-evolution/token-sequence-obo.drawio.svg index f337f8b572..517d2c4d19 100644 --- a/beps/0003-auth-architecture-evolution/token-sequence-obo.drawio.svg +++ b/beps/0003-auth-architecture-evolution/token-sequence-obo.drawio.svg @@ -1,203 +1 @@ - - - - - - - - -
-
-
- Browser -
-
-
-
- - Browser - -
-
- - - - - - -
-
-
- Scaffolder -
- Backend -
-
-
-
- - Scaffolder... - -
-
- - - - - - -
-
-
- - acquire service obo token -
- based on user token -
-
-
-
-
-
- - acquire service obo token... - -
-
- - - - - -
-
-
- POST /tasks -
-
-
-
- - POST /tasks - -
-
- - - - - -
-
-
- success -
-
-
-
- - success - -
-
- - - - - -
-
-
- Catalog -
- Backend -
-
-
-
- - Catalog... - -
-
- - - - - - -
-
-
- GET /entities/... -
-
-
-
- - GET /entities/... - -
-
- - - - - -
-
-
- template entity -
-
-
-
- - template entity - -
-
- - - - -
-
-
- user identity token -
-
-
-
- - user identity token - -
-
- - - - -
-
-
- service obo token -
-
-
-
- - service obo token - -
-
-
- - - - - Text is not SVG - cannot display - - - -
+
Browser
Browser
Scaffolder
Backend
Scaffolder...
acquire service obo token
based on user token
acquire service obo token...
POST /tasks
POST /tasks
success
success
Catalog
Backend
Catalog...
GET /entities/...
GET /entities/...
template entity
template entity
user identity token
user identity token
service obo token
service obo token
Text is not SVG - cannot display
\ No newline at end of file diff --git a/contrib/.devcontainer/README.md b/contrib/.devcontainer/README.md deleted file mode 100644 index 4fca39b3ad..0000000000 --- a/contrib/.devcontainer/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Devcontainer Configuration - -[Devcontainer](https://code.visualstudio.com/docs/devcontainers/containers) is a VSCode feature that allows developers -to share a common workspace definition, by leveraging Docker to run the workspace, and using VSCode Remote Development extension, you can make sure the developer experience is the same across different computers - -## Benefits - -- Developers only need to have VSCode and Docker installed -- All dependencies necessary to work in the repository are contained in the docker image -- Makes it easier for new developers to start contributing to the project -- No extra setup required - -## Known Issues - -- Performance is a known issue. Working inside devcontainers require developers to have a powerful computer. See [System Requirements](https://code.visualstudio.com/docs/devcontainers/containers#_system-requirements) - -## Usage - -Just copy the .devcontainer folder to the root of the directory, and VSCode will automatically detect the configuration and suggest that you re-open the repository using Devcontainer diff --git a/contrib/.devcontainer/devcontainer.json b/contrib/.devcontainer/devcontainer.json deleted file mode 100644 index 14e7d7f0ae..0000000000 --- a/contrib/.devcontainer/devcontainer.json +++ /dev/null @@ -1,27 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node -{ - "name": "Base Backstage Workspace", - "build": { "dockerfile": "Dockerfile" }, - "features": { - "ghcr.io/devcontainers/features/common-utils:1": {}, - "ghcr.io/devcontainers/features/docker-from-docker:1": {}, - "ghcr.io/devcontainers-contrib/features/mkdocs:2": {} - }, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [3000, 7007], - - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "bash .devcontainer/postCreate.sh", - - // Configure tool-specific properties. - "customizations": { - "vscode": { - "extensions": ["Intility.vscode-backstage"] - } - } - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/contrib/.devcontainer/postCreate.sh b/contrib/.devcontainer/postCreate.sh deleted file mode 100755 index 88f403de5e..0000000000 --- a/contrib/.devcontainer/postCreate.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -yarn install -export VIRTUAL_ENV=$HOME/venv -python3 -m venv $VIRTUAL_ENV -export PATH="$VIRTUAL_ENV/bin:$PATH" -python3 -m pip install mkdocs-techdocs-core \ No newline at end of file diff --git a/contrib/catalog/ImmediateEntityProvider.ts b/contrib/catalog/ImmediateEntityProvider.ts index 9f9f986567..ba17f19c9d 100644 --- a/contrib/catalog/ImmediateEntityProvider.ts +++ b/contrib/catalog/ImmediateEntityProvider.ts @@ -16,7 +16,7 @@ import { EntityProviderConnection, } from '@backstage/plugin-catalog-node'; import { parseEntityYaml } from '@backstage/plugin-catalog-node'; -import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; import bodyParser from 'body-parser'; import express from 'express'; import Router from 'express-promise-router'; diff --git a/docs-ui/.eslintrc.json b/docs-ui/.eslintrc.json deleted file mode 100644 index be99640f75..0000000000 --- a/docs-ui/.eslintrc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": ["next/core-web-vitals", "next/typescript"], - "rules": { - "notice/notice": "off", - "react/forbid-elements": "off", - "jsx-a11y/alt-text": "off" - } -} diff --git a/docs-ui/eslint.config.mjs b/docs-ui/eslint.config.mjs new file mode 100644 index 0000000000..2cb66fd509 --- /dev/null +++ b/docs-ui/eslint.config.mjs @@ -0,0 +1,16 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import nextCoreWebVitals from 'eslint-config-next/core-web-vitals'; + +const eslintConfig = defineConfig([ + ...nextCoreWebVitals, + globalIgnores(['.next/**', 'dist/**', 'node_modules/**']), + { + rules: { + 'notice/notice': 'off', + 'react/forbid-elements': 'off', + 'jsx-a11y/alt-text': 'off', + }, + }, +]); + +export default eslintConfig; diff --git a/docs-ui/package.json b/docs-ui/package.json index ce9b8d070f..bb0b1a393d 100644 --- a/docs-ui/package.json +++ b/docs-ui/package.json @@ -5,17 +5,18 @@ "scripts": { "prebuild": "yarn sync:css", "build": "next build", - "lint": "next lint", + "lint": "eslint .", "prestart": "yarn sync:css", "start": "next dev", "sync:changelog": "node scripts/sync-changelog.mjs", "sync:changelog:dry-run": "node scripts/sync-changelog.mjs --dry-run", + "sync:changelog:force": "node scripts/sync-changelog.mjs --force", "sync:css": "node scripts/sync-css.js", "sync:css:watch": "node scripts/sync-css.js --watch" }, "resolutions": { - "@types/react": "19.1.9", - "@types/react-dom": "19.1.7" + "@types/react": "19.2.10", + "@types/react-dom": "19.2.3" }, "dependencies": { "@base-ui-components/react": "^1.0.0-beta.4", @@ -24,18 +25,18 @@ "@lezer/highlight": "^1.2.1", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", - "@next/mdx": "15.5.7", + "@next/mdx": "16.1.6", "@remixicon/react": "^4.6.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": "15.5.9", + "next": "16.1.6", "next-mdx-remote-client": "^2.1.2", "prop-types": "^15.8.1", - "react": "19.1.1", - "react-dom": "19.1.1", + "react": "19.2.4", + "react-dom": "19.2.4", "shiki": "^3.13.0" }, "devDependencies": { @@ -43,11 +44,11 @@ "@shikijs/transformers": "^3.13.0", "@types/mdx": "^2.0.13", "@types/node": "^22.13.14", - "@types/react": "19.1.9", - "@types/react-dom": "19.1.7", + "@types/react": "19.2.10", + "@types/react-dom": "19.2.3", "chokidar": "^3.6.0", - "eslint": "^8", - "eslint-config-next": "15.5.7", + "eslint": "^9", + "eslint-config-next": "16.1.6", "lightningcss": "^1.28.2", "typescript": "^5", "unified": "^11.0.4" diff --git a/docs-ui/public/theme-backstage.css b/docs-ui/public/theme-backstage.css deleted file mode 100644 index 9dd84816cf..0000000000 --- a/docs-ui/public/theme-backstage.css +++ /dev/null @@ -1 +0,0 @@ -@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg-surface-0:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-1);--bui-bg-surface-3:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#163a66;--bui-bg-neutral-on-surface-0:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-0-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-0-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-0-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-1-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-2-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-3-disabled:oklch(0% 0 0/.06);--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#98a8bc;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg-surface-0:#333;--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-surface-3:var(--bui-gray-3);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#1b3d68;--bui-bg-neutral-on-surface-0:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-0-hover:oklch(100% 0 0/.14);--bui-bg-neutral-on-surface-0-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-0-disabled:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1-pressed:oklch(100% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-2-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-2-disabled:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-3-disabled:oklch(100% 0 0/.08);--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:#6191cc;--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757;--bui-shadow:none}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg-surface-0);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-theme-mode=dark]{color-scheme:dark}[data-theme-mode=light]{color-scheme:light}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}} \ No newline at end of file diff --git a/docs-ui/public/theme-spotify.css b/docs-ui/public/theme-spotify.css deleted file mode 100644 index 22eda0cabe..0000000000 --- a/docs-ui/public/theme-spotify.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:CircularSpTitle;font-weight:900;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Black-4588c99025b967475c31695b0743dd1a.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Black-506746f387a26f25aa3d023b3e501d34.woff)format("woff")}@font-face{font-family:CircularSpTitle;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-b2586b06a2e1522e9d879d84c2792a58.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-1624fb2df28c20d7203d7fb86ce8b481.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:400;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Book-a00e99ef9996a3a157fb6b746856d04f.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Book-3f73da7d35bd81c706bce7bbb84964de.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}[data-theme-name=spotify]{--bui-font-text:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-title:CircularSpTitle,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-regular:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;& .bui-Button{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3)}& .bui-ButtonIcon{padding:0}& .bui-MenuPopup{box-sizing:border-box;max-width:21.25rem}& .bui-MenuSubmenuTrigger,& .bui-MenuItem{height:auto;min-height:2rem}& .bui-Text{font-family:var(--bui-font-text)}& .bui-Heading{font-family:var(--bui-font-title)}& .bui-TableRow{border-radius:var(--bui-radius-4);border:none}& .bui-TableRow:hover td:first-child{border-top-left-radius:var(--bui-radius-2);border-bottom-left-radius:var(--bui-radius-2)}& .bui-TableRow:hover td:last-child{border-top-right-radius:var(--bui-radius-2);border-bottom-right-radius:var(--bui-radius-2)}& .bui-TableBody:before,& .bui-TableBody:after{line-height:var(--bui-space-1);content:"‌";display:block}& .bui-TableHeader .bui-TableRow{border-bottom:1px solid var(--bui-border)}& .bui-TableHead{font-size:var(--bui-font-size-2);color:var(--bui-fg-secondary);font-weight:var(--bui-font-weight-regular)}& .bui-HeaderToolbar{padding-top:var(--bui-space-2);padding-inline:var(--bui-space-2)}& .bui-HeaderToolbarWrapper{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3);border:none}& .bui-HeaderToolbarControls{right:calc(var(--bui-space-3) - 1px)}& .bui-HeaderTabsWrapper{margin-top:var(--bui-space-2);margin-inline:var(--bui-space-2);border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-1);border:none}& .bui-Input{border-radius:var(--bui-radius-3)}& .bui-Tag{border-radius:var(--bui-radius-full)}}[data-theme-mode=light][data-theme-name=spotify]{--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#62ab7c;--bui-border:var(--bui-gray-3);--bui-border-hover:#0000004d;--bui-border-pressed:#00000080;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#0003;& .bui-HeaderToolbarWrapper,& .bui-HeaderTabsWrapper{border:1px solid var(--bui-border)}}[data-theme-mode=dark][data-theme-name=spotify]{--bui-bg-surface-0:var(--bui-black);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:var(--bui-white);--bui-fg-link-hover:var(--bui-white);--bui-fg-disabled:var(--bui-gray-5);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#072f15;--bui-fg-tint:var(--bui-white);--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:var(--bui-gray-3);--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#fff3} \ No newline at end of file diff --git a/docs-ui/scripts/sync-changelog.mjs b/docs-ui/scripts/sync-changelog.mjs index 5eab3616dd..fa59db741b 100644 --- a/docs-ui/scripts/sync-changelog.mjs +++ b/docs-ui/scripts/sync-changelog.mjs @@ -1,5 +1,4 @@ #!/usr/bin/env node -/* eslint-disable no-restricted-syntax */ import fs from 'fs'; import path from 'path'; @@ -13,22 +12,18 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); /** - * Compare two semantic versions. + * Compare two semantic versions for sorting. * Returns: 1 if versionA > versionB, -1 if versionA < versionB, 0 if equal - * Handles pre-release versions (e.g., "0.9.0-next.2") + * Example: compareVersions("0.11.0", "0.10.0") => 1 */ function compareVersions(versionA, versionB) { // Strip 'v' prefix if present versionA = versionA.replace(/^v/, ''); versionB = versionB.replace(/^v/, ''); - // Split into [major.minor.patch, prerelease] - const [versionABase, versionAPre] = versionA.split('-'); - const [versionBBase, versionBPre] = versionB.split('-'); - - // Compare base versions - const versionAParts = versionABase.split('.').map(Number); - const versionBParts = versionBBase.split('.').map(Number); + // Compare major.minor.patch versions + const versionAParts = versionA.split('.').map(Number); + const versionBParts = versionB.split('.').map(Number); for ( let i = 0; @@ -42,13 +37,7 @@ function compareVersions(versionA, versionB) { if (versionAPart < versionBPart) return -1; } - // If base versions equal, compare pre-release - if (!versionAPre && !versionBPre) return 0; - if (!versionAPre) return 1; // versionA is release, versionB is pre-release - if (!versionBPre) return -1; // versionA is pre-release, versionB is release - - // Both have pre-release, compare them - return versionAPre.localeCompare(versionBPre); + return 0; } /** @@ -58,15 +47,6 @@ function isNewerVersion(versionA, versionB) { return compareVersions(versionA, versionB) > 0; } -/** - * Normalize version to base version (remove pre-release suffix) - * "0.9.0-next.3" → "0.9.0" - * "0.9.0" → "0.9.0" - */ -function normalizeVersion(version) { - return version.split('-')[0]; -} - /** * Read the existing changelog.ts and find the highest version number * by scanning the changelogs/ directory for version files @@ -169,6 +149,7 @@ function getValidComponents(changelogPath) { /** * Parse CHANGELOG.md and extract entries newer than sinceVersion + * Only processes main release versions (e.g., 0.11.0), skips pre-releases (e.g., 0.11.0-next.1) */ async function parseChangelogMd(changelogPath, sinceVersion, validComponents) { const content = fs.readFileSync(changelogPath, 'utf-8'); @@ -181,14 +162,17 @@ async function parseChangelogMd(changelogPath, sinceVersion, validComponents) { // Walk through the markdown AST async function walk(node, depth = 0) { - // Version headers (## 0.9.0) + // Version headers (## 0.11.0) if (node.type === 'heading' && node.depth === 2) { const versionText = extractText(node).trim(); - // Validate that this is actually a version number (X.Y.Z or X.Y.Z-pre.N) - // Skip headings that are just markdown content within changelog entries - if (/^\d+\.\d+\.\d+/.test(versionText)) { + // Only process main release versions (X.Y.Z format) + if (/^\d+\.\d+\.\d+$/.test(versionText)) { currentVersion = versionText; currentSection = null; + } else { + // Skip pre-release versions (e.g., 0.11.0-next.1) and non-version headings + currentVersion = null; + currentSection = null; } return; } @@ -301,9 +285,11 @@ async function parseListItem( // Remove the commit SHA prefix from markdown let description = fullMarkdown.replace(/^-?\s*[a-f0-9]+:\s*/, '').trim(); - // Auto-detect components from "Affected components:" line + // Extract components using bold marker (standard format) let components = []; - const componentMatch = description.match(/Affected components?:\s*([^\n]+)/i); + const componentMatch = description.match( + /\*\*Affected components:\*\*\s*([^\n]+)/, + ); if (componentMatch) { const componentNames = componentMatch[1] .split(',') @@ -314,52 +300,64 @@ async function parseListItem( .map(name => mapComponentName(name, validComponents)) .filter(Boolean); - // Optionally strip "Affected components:" line from description + // Strip "**Affected components:**" line from description description = description - .replace(/\n*Affected components?:[ \t]*[^\n]+/i, '') + .replace(/\n*\*\*Affected components:\*\*[ \t]*[^\n]+/g, '') + .trim(); + } + + // Extract migration notes using bold marker (standard format) + let migration = null; + const migrationMatch = description.match( + /\*\*Migration:\*\*\s*\n([\s\S]+?)(?=\n\s*$|$)/, + ); + if (migrationMatch) { + // Clean up indentation from list format (remove leading 2 spaces from each line) + migration = migrationMatch[1] + .split('\n') + .map(line => line.replace(/^ /, '')) + .join('\n') + .trim(); + // Strip migration section from description + description = description + .replace(/\n*\*\*Migration:\*\*[\s\S]+$/, '') .trim(); } const prs = []; // Will be populated later by fetchPRNumbers() - // Infer type from section and description - const type = inferChangeType(section, description, version); + // Check if this is a breaking change + const breaking = isBreakingChange(section, description, version); return { - version: normalizeVersion(version), + version, section, commitSha, description, components, prs, - type, + breaking, + migration, }; } /** - * Infer change type from section and description + * Determine if a change is breaking based on semver rules + * Breaking change rules (semver): + * - version >= 1.0.0: Major changes are breaking + * - version < 1.0.0: Major and Minor changes are breaking */ -function inferChangeType(section, description, version) { - const isPre1 = version.startsWith('0.'); +function isBreakingChange(section, description, version) { + // Parse version to determine breaking change rules based on semver + const [major] = version.split('.').map(Number); - // Check description keywords first - if (description.match(/^(New|Add(ed)?)\s/i)) { - return 'new'; - } - if (description.includes('BREAKING')) { - return 'breaking'; + // Version >= 1.0.0: Only Major Changes are breaking + if (major >= 1) { + return section === 'Major Changes'; } - // Infer from section - if (section === 'Minor Changes') { - return isPre1 ? 'breaking' : undefined; - } else if (section === 'Major Changes') { - return 'breaking'; - } else if (section === 'Patch Changes') { - return 'fix'; - } - - return undefined; + // Version < 1.0.0: Both Major and Minor Changes are breaking + return section === 'Major Changes' || section === 'Minor Changes'; } /** @@ -414,8 +412,13 @@ async function fetchPRNumbers(entries, dryRun = false) { /** * Generate per-version changelog files in changelogs/ directory */ -async function generateVersionFiles(entries, changelogsDir, dryRun = false) { - // Group entries by normalized version +async function generateVersionFiles( + entries, + changelogsDir, + dryRun = false, + force = false, +) { + // Group entries by version const byVersion = {}; entries.forEach(entry => { const version = entry.version; @@ -434,8 +437,8 @@ async function generateVersionFiles(entries, changelogsDir, dryRun = false) { const filePath = path.join(changelogsDir, fileName); const varName = `changelog_${version.replace(/\./g, '_')}`; - // Check if file already exists - skip if it does - if (fs.existsSync(filePath)) { + // Check if file already exists - skip if it does (unless force flag) + if (fs.existsSync(filePath) && !force) { skippedVersions.push(version); // Still add to versionFiles array so main changelog.ts can import it versionFiles.push({ @@ -456,7 +459,7 @@ async function generateVersionFiles(entries, changelogsDir, dryRun = false) { .map(c => `'${c}'`) .join(', ')}]`; const prsStr = `[${entry.prs.map(pr => `'${pr}'`).join(', ')}]`; - const typeStr = entry.type ? `type: '${entry.type}',` : ''; + const breakingStr = entry.breaking ? `breaking: true,` : ''; const shaStr = entry.commitSha ? `commitSha: '${entry.commitSha}',` : ''; @@ -467,12 +470,20 @@ async function generateVersionFiles(entries, changelogsDir, dryRun = false) { .replace(/`/g, '\\`') .replace(/\${/g, '\\${'); + // Escape migration notes if present + const migrationStr = entry.migration + ? `migration: \`${entry.migration + .replace(/\\/g, '\\\\') + .replace(/`/g, '\\`') + .replace(/\${/g, '\\${')}\`,\n ` + : ''; + return ` { components: ${componentsStr}, version: '${entry.version}', prs: ${prsStr}, description: \`${descEscaped}\`, - ${typeStr} + ${migrationStr}${breakingStr} ${shaStr} }`; }) @@ -802,6 +813,7 @@ async function findPRNumber(commitSha) { async function main() { const args = process.argv.slice(2); const dryRun = args.includes('--dry-run'); + const force = args.includes('--force'); const changelogTsPath = path.join(__dirname, '../src/utils/changelog.ts'); const changelogMdPath = path.join( @@ -818,10 +830,15 @@ async function main() { } console.log('📋 Syncing UI component changelogs...\n'); + if (force) { + console.log('⚠️ Force mode: Will overwrite existing version files\n'); + } - // Get last synced version - const lastVersion = getLastSyncedVersion(changelogTsPath); - console.log(`Last synced version: ${lastVersion || '(none)'}`); + // Get last synced version (null if force mode to process all) + const lastVersion = force ? null : getLastSyncedVersion(changelogTsPath); + console.log( + `Last synced version: ${lastVersion || '(none - processing all versions)'}`, + ); // Get valid components const validComponents = getValidComponents(changelogTsPath); @@ -834,26 +851,26 @@ async function main() { lastVersion, validComponents, ); - console.log(`Found ${allEntries.length} total entries since ${lastVersion}`); + console.log( + `Found ${allEntries.length} total entries${ + force ? '' : ` since ${lastVersion}` + }`, + ); // Read existing changelog content for duplicate detection const existingContent = fs.readFileSync(changelogTsPath, 'utf-8'); // Filter to only new, non-duplicate entries - const relevantEntries = allEntries.filter(e => { - const hasComponents = e.components.length > 0 || e.components.length === 0; - const notDuplicate = !isDuplicate(e, existingContent); - return hasComponents && notDuplicate; - }); + const relevantEntries = allEntries.filter( + e => !isDuplicate(e, existingContent), + ); const duplicatesCount = allEntries.length - relevantEntries.length; if (duplicatesCount > 0) { console.log(`Skipped ${duplicatesCount} duplicate entries`); } - console.log( - `Relevant entries (with or without components): ${relevantEntries.length}`, - ); + console.log(`Relevant entries: ${relevantEntries.length}`); if (relevantEntries.length === 0) { console.log('\n✅ No new entries to sync'); @@ -920,6 +937,7 @@ async function main() { relevantEntries, changelogsDir, dryRun, + force, ); // Generate main changelog.ts diff --git a/docs-ui/scripts/sync-css.js b/docs-ui/scripts/sync-css.js index 4763fdecc1..a9c228ac27 100644 --- a/docs-ui/scripts/sync-css.js +++ b/docs-ui/scripts/sync-css.js @@ -1,12 +1,12 @@ -const fs = require('fs'); -const path = require('path'); +const fs = require('node:fs'); +const path = require('node:path'); const { bundle } = require('lightningcss'); const chokidar = require('chokidar'); // Configuration const config = { UIPath: '../../packages/ui', - publicPath: '../public', + outputPath: '../src/css', files: [ { source: 'css/styles.css', @@ -24,13 +24,13 @@ const config = { class CSSSync { constructor() { this.UIPath = path.resolve(__dirname, config.UIPath); - this.publicPath = path.resolve(__dirname, config.publicPath); + this.outputPath = path.resolve(__dirname, config.outputPath); this.isWatching = process.argv.includes('--watch'); } async syncFile(fileConfig) { const sourcePath = path.join(this.UIPath, fileConfig.source); - const destPath = path.join(this.publicPath, fileConfig.destination); + const destPath = path.join(this.outputPath, fileConfig.destination); try { // Check if source file exists @@ -76,9 +76,9 @@ class CSSSync { ); if (successCount > 0) { - console.log('\n📁 Available CSS files in public/:'); + console.log('\n📁 Available CSS files in src/css/:'); config.files.forEach(file => { - const destPath = path.join(this.publicPath, file.destination); + const destPath = path.join(this.outputPath, file.destination); if (fs.existsSync(destPath)) { const stats = fs.statSync(destPath); const size = (stats.size / 1024).toFixed(2); @@ -129,7 +129,7 @@ class CSSSync { async run() { console.log('🎨 BUI CSS Sync Tool\n'); console.log(`📂 BUI path: ${this.UIPath}`); - console.log(`📂 Public path: ${this.publicPath}\n`); + console.log(`📂 Output path: ${this.outputPath}\n`); // Initial sync await this.syncAll(); diff --git a/docs-ui/src/app/components/accordion/page.mdx b/docs-ui/src/app/components/accordion/page.mdx index 83d650507b..486c93ec80 100644 --- a/docs-ui/src/app/components/accordion/page.mdx +++ b/docs-ui/src/app/components/accordion/page.mdx @@ -28,10 +28,16 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { AccordionDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + disclosure: 'https://react-aria.adobe.com/Disclosure', + disclosureGroup: 'https://react-aria.adobe.com/DisclosureGroup', +}; ` element. + + ### AccordionTrigger Trigger component with built-in animated chevron icon. Renders a heading element (defaults to `

`, configurable via `level` prop) wrapping a ` + } + /> + } + aria-label="Close" + /> + } + /> + + ); +}; + +export const WithActionsAndDescriptions = () => { + return ( + + + + + } + /> + ); +}; + +export const LoadingStates = () => { + return ( + + + + + + ); +}; + +export const WithoutIcons = () => { + return ( + + + + + ); +}; + +export const CustomIcon = () => { + return ( + } + title="This alert uses a custom cloud icon instead of the default info icon." + /> + ); +}; diff --git a/docs-ui/src/app/components/alert/page.mdx b/docs-ui/src/app/components/alert/page.mdx new file mode 100644 index 0000000000..cdfdf184e0 --- /dev/null +++ b/docs-ui/src/app/components/alert/page.mdx @@ -0,0 +1,120 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { alertPropDefs } from './props-definition'; +import { + alertUsageSnippet, + defaultSnippet, + statusVariantsSnippet, + withDescriptionSnippet, + withActionsSnippet, + loadingStatesSnippet, + withoutIconsSnippet, + customIconSnippet, +} from './snippets'; +import { + Default, + StatusVariants, + WithDescription, + WithActions, + LoadingStates, + WithoutIcons, + CustomIcon, +} from './components'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { AlertDefinition } from '../../../utils/definitions'; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + +## Examples + +### Status Variants + +The Alert component supports four status variants, each with its own color theme. + +} + code={statusVariantsSnippet} +/> + +### With Description + +Add a description to provide additional context or details. + +} + code={withDescriptionSnippet} +/> + +### With Actions + +Include custom actions like buttons for interactive alerts. + +} + code={withActionsSnippet} +/> + +### Loading States + +The loading spinner replaces the icon to indicate an ongoing process. + +} + code={loadingStatesSnippet} +/> + +### Without Icons + +Disable icons for a simpler appearance. + +} + code={withoutIconsSnippet} +/> + +### Custom Icon + +Provide a custom icon element instead of the default status icon. + +} + code={customIconSnippet} +/> + + + + diff --git a/docs-ui/src/app/components/alert/props-definition.ts b/docs-ui/src/app/components/alert/props-definition.ts new file mode 100644 index 0000000000..6f2650fe42 --- /dev/null +++ b/docs-ui/src/app/components/alert/props-definition.ts @@ -0,0 +1,76 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const alertPropDefs: Record = { + status: { + type: 'enum', + values: ['info', 'success', 'warning', 'danger'], + responsive: true, + default: 'info', + }, + icon: { + type: 'enum', + values: ['boolean', 'React.ReactElement'], + responsive: false, + }, + loading: { + type: 'enum', + values: ['boolean'], + responsive: false, + }, + title: { + type: 'enum', + values: ['React.ReactNode'], + responsive: false, + }, + description: { + type: 'enum', + values: ['React.ReactNode'], + responsive: false, + }, + customActions: { + type: 'enum', + values: ['React.ReactNode'], + responsive: false, + }, + m: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mx: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + my: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mt: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mb: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + ml: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mr: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/alert/snippets.ts b/docs-ui/src/app/components/alert/snippets.ts new file mode 100644 index 0000000000..739dfde9db --- /dev/null +++ b/docs-ui/src/app/components/alert/snippets.ts @@ -0,0 +1,131 @@ +export const alertUsageSnippet = `import { Alert } from '@backstage/ui'; + +`; + +export const defaultSnippet = ``; + +export const statusVariantsSnippet = ` + + + + +`; + +export const withDescriptionSnippet = ` + + + + +`; + +export const withActionsSnippet = ` + + Dismiss + + } + /> + } + aria-label="Close" + /> + } + /> +`; + +export const withActionsAndDescriptionsSnippet = ` + + + + } +/>`; + +export const loadingStatesSnippet = ` + + + +`; + +export const withoutIconsSnippet = ` + + +`; + +export const customIconSnippet = `import { RiCloudLine } from '@remixicon/react'; + +} + title="This alert uses a custom cloud icon instead of the default info icon." +/>`; diff --git a/docs-ui/src/app/components/avatar/page.mdx b/docs-ui/src/app/components/avatar/page.mdx index 0074eda5b8..7020eb90c4 100644 --- a/docs-ui/src/app/components/avatar/page.mdx +++ b/docs-ui/src/app/components/avatar/page.mdx @@ -17,7 +17,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; } code={defaultSnippet} /> @@ -30,13 +30,22 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; +Avatar also accepts all standard HTML div attributes (`onClick`, `onMouseEnter`, etc.) since it extends `React.ComponentPropsWithoutRef<'div'>`. + ## Examples ### Sizes Avatar sizes can be set using the `size` prop. -} code={sizesSnippet} /> +} + code={sizesSnippet} + layout="side-by-side" +/> ### Fallback @@ -48,6 +57,7 @@ If the image is not available, the avatar will show the initials of the name. open preview={} code={fallbackSnippet} + layout="side-by-side" /> ### The `purpose` prop diff --git a/docs-ui/src/app/components/avatar/props-definition.ts b/docs-ui/src/app/components/avatar/props-definition.ts deleted file mode 100644 index 1a6de676c1..0000000000 --- a/docs-ui/src/app/components/avatar/props-definition.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const avatarPropDefs: Record = { - src: { - type: 'string', - }, - name: { - type: 'string', - required: true, - }, - size: { - type: 'enum', - values: ['x-small', 'small', 'medium', 'large', 'x-large'], - default: 'medium', - responsive: true, - }, - purpose: { - type: 'enum', - values: ['informative', 'decoration'], - default: 'informative', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/docs-ui/src/app/components/avatar/props-definition.tsx b/docs-ui/src/app/components/avatar/props-definition.tsx new file mode 100644 index 0000000000..4b56b94be5 --- /dev/null +++ b/docs-ui/src/app/components/avatar/props-definition.tsx @@ -0,0 +1,41 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const avatarPropDefs: Record = { + src: { + type: 'string', + description: + 'URL of the image to display. Pass an empty string to show initials fallback. Falls back to initials if the image fails to load.', + }, + name: { + type: 'string', + required: true, + description: + 'Name of the person. Used for generating initials fallback and accessibility label.', + }, + size: { + type: 'enum', + values: ['x-small', 'small', 'medium', 'large', 'x-large'], + default: 'medium', + responsive: true, + description: + 'Visual size. Smaller sizes show 1 initial, larger sizes show 2.', + }, + purpose: { + type: 'enum', + values: ['informative', 'decoration'], + default: 'informative', + description: ( + <> + Accessibility behavior. Use decoration when name appears in + adjacent text. + + ), + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/avatar/snippets.ts b/docs-ui/src/app/components/avatar/snippets.ts index ba3254dcd9..2deb8037a6 100644 --- a/docs-ui/src/app/components/avatar/snippets.ts +++ b/docs-ui/src/app/components/avatar/snippets.ts @@ -1,6 +1,6 @@ export const avatarUsageSnippet = `import { Avatar } from '@backstage/ui'; -`; +`; export const defaultSnippet = ` - - - - - + + + + + - - - - - + + + + + `; diff --git a/docs-ui/src/app/components/box/components.tsx b/docs-ui/src/app/components/box/components.tsx index 466ecd3708..dac66f1f59 100644 --- a/docs-ui/src/app/components/box/components.tsx +++ b/docs-ui/src/app/components/box/components.tsx @@ -1,34 +1,43 @@ 'use client'; import { Box } from '../../../../../packages/ui/src/components/Box/Box'; - -const diagonalStripePattern = (() => { - const svg = ` - - - - - - `.trim(); - return `data:image/svg+xml,${encodeURIComponent(svg)}`; -})(); +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { DecorativeBox } from '@/components/DecorativeBox'; export const Default = () => { return ( - + + + + ); +}; + +export const Surface = () => { + return ( + + + Surface 0 + + + Surface 1 + + + Surface 2 + + + Surface 3 + + + ); +}; + +export const Responsive = () => { + return ( + + Resize to see change + ); }; diff --git a/docs-ui/src/app/components/box/page.mdx b/docs-ui/src/app/components/box/page.mdx index cc8641580f..9b60e5244a 100644 --- a/docs-ui/src/app/components/box/page.mdx +++ b/docs-ui/src/app/components/box/page.mdx @@ -5,11 +5,10 @@ import { boxPropDefs } from './props-definition'; import { snippetUsage, defaultSnippet, - boxSimpleSnippet, + boxSurfaceSnippet, boxResponsiveSnippet, } from './snippets'; -import { Default } from './components'; -import { spacingPropDefs } from '@/utils/propDefs'; +import { Default, Surface, Responsive } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { BoxDefinition } from '../../../utils/definitions'; @@ -18,10 +17,10 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; -} code={defaultSnippet} align="center" /> +} code={defaultSnippet} align="center" /> ## Usage @@ -29,32 +28,25 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; ## API reference -### Box - -This is the Box component, our lowest-level component. Here are all the -available properties. - -Padding and margin are used to create space around your component using our -predefined spacing tokens. We would recommend to use padding over margin to -avoid collapsing margins but both are available. - - - ## Examples -### Simple example +### Surface -A simple example of how to use the Box component. +Use surface levels to create visual hierarchy. - +} code={boxSurfaceSnippet} layout="side-by-side" /> -### Responsive +### Responsive props -Here's a view when buttons are responsive. +Props can accept breakpoint objects for responsive behavior. - +} + code={boxResponsiveSnippet} + layout="side-by-side" +/> diff --git a/docs-ui/src/app/components/box/props-definition.ts b/docs-ui/src/app/components/box/props-definition.ts deleted file mode 100644 index a18a0f272e..0000000000 --- a/docs-ui/src/app/components/box/props-definition.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - classNamePropDefs, - displayPropDefs, - heightPropDefs, - positionPropDefs, - stylePropDefs, - widthPropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const boxPropDefs: Record = { - as: { - type: 'enum', - values: ['div', 'span'], - default: 'div', - responsive: true, - }, - ...widthPropDefs, - ...heightPropDefs, - ...positionPropDefs, - ...displayPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/docs-ui/src/app/components/box/props-definition.tsx b/docs-ui/src/app/components/box/props-definition.tsx new file mode 100644 index 0000000000..3809bb9993 --- /dev/null +++ b/docs-ui/src/app/components/box/props-definition.tsx @@ -0,0 +1,48 @@ +import { + classNamePropDefs, + heightPropDefs, + positionPropDefs, + stylePropDefs, + widthPropDefs, + spacingGroupAll, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const boxPropDefs: Record = { + as: { + type: 'string', + default: 'div', + description: + 'HTML element to render. Accepts any valid HTML tag (div, span, section, etc.).', + }, + surface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: + 'Background surface level for visual hierarchy. Higher numbers create elevation.', + }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Content to render inside the box.', + }, + ...widthPropDefs, + ...heightPropDefs, + ...positionPropDefs, + display: { + type: 'enum', + values: ['none', 'flex', 'block', 'inline'], + responsive: true, + description: ( + <> + Controls layout behavior. Use flex for flexbox layouts,{' '} + none to hide. + + ), + }, + ...classNamePropDefs, + ...stylePropDefs, + spacing: spacingGroupAll, +}; diff --git a/docs-ui/src/app/components/box/snippets.ts b/docs-ui/src/app/components/box/snippets.ts index 5c19bbe59b..e9af07dec5 100644 --- a/docs-ui/src/app/components/box/snippets.ts +++ b/docs-ui/src/app/components/box/snippets.ts @@ -1,21 +1,23 @@ export const snippetUsage = `import { Box } from '@backstage/ui'; -`; + + Content with padding and background +`; -export const defaultSnippet = ``; +export const defaultSnippet = ` + +`; -export const boxSimpleSnippet = `Hello World`; +export const boxSurfaceSnippet = ` + Surface 0 + Surface 1 + Surface 2 + Surface 3 +`; export const boxResponsiveSnippet = ` - Hello World + p={{ initial: '2', md: '4' }} + display={{ initial: 'block', md: 'flex' }} +> + Content `; diff --git a/docs-ui/src/app/components/button-icon/page.mdx b/docs-ui/src/app/components/button-icon/page.mdx index 776b016671..51ff0ea5a0 100644 --- a/docs-ui/src/app/components/button-icon/page.mdx +++ b/docs-ui/src/app/components/button-icon/page.mdx @@ -14,10 +14,15 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ButtonIconDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + button: 'https://react-aria.adobe.com/Button', +}; } code={variantsSnippet} /> @@ -30,12 +35,12 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; + + ## Examples ### Variants -Here's a view when buttons have different variants. - } code={sizesSnippet} /> ### Disabled -Here's a view when buttons are disabled. - = { - variant: { - type: 'enum', - values: ['primary', 'secondary', 'tertiary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - icon: { type: 'enum', values: ['ReactNode'], responsive: false }, - isDisabled: { type: 'boolean', default: 'false', responsive: false }, - loading: { type: 'boolean', default: 'false', responsive: false }, - type: { - type: 'enum', - values: ['button', 'submit', 'reset'], - default: 'button', - responsive: false, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/docs-ui/src/app/components/button-icon/props-definition.tsx b/docs-ui/src/app/components/button-icon/props-definition.tsx new file mode 100644 index 0000000000..05917cc2b4 --- /dev/null +++ b/docs-ui/src/app/components/button-icon/props-definition.tsx @@ -0,0 +1,64 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const buttonIconPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary', 'tertiary'], + default: 'primary', + responsive: true, + description: ( + <> + Visual style. Use primary for main actions,{' '} + secondary for alternatives, tertiary for + low-emphasis. + + ), + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Button size. Use small for toolbars, medium{' '} + for standalone actions. + + ), + }, + icon: { + type: 'enum', + values: ['ReactElement'], + description: + 'Icon element to display. Required for accessibility via aria-label.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: 'Prevents interaction and applies disabled styling.', + }, + loading: { + type: 'boolean', + default: 'false', + description: 'Shows a spinner and disables the button.', + }, + type: { + type: 'enum', + values: ['button', 'submit', 'reset'], + default: 'button', + description: 'HTML button type attribute.', + }, + onSurface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: 'Surface context for correct color contrast.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/button-icon/snippets.ts b/docs-ui/src/app/components/button-icon/snippets.ts index 1ea9acc169..b1ab27b77b 100644 --- a/docs-ui/src/app/components/button-icon/snippets.ts +++ b/docs-ui/src/app/components/button-icon/snippets.ts @@ -1,6 +1,7 @@ export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/ui'; +import { RiCloseLine } from '@remixicon/react'; -`; +} aria-label="Close" />`; export const variantsSnippet = ` } variant="primary" aria-label="Cloud" /> diff --git a/docs-ui/src/app/components/button-link/page.mdx b/docs-ui/src/app/components/button-link/page.mdx index 2da3177c55..35e4de68c5 100644 --- a/docs-ui/src/app/components/button-link/page.mdx +++ b/docs-ui/src/app/components/button-link/page.mdx @@ -14,10 +14,15 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ButtonLinkDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + link: 'https://react-aria.adobe.com/Link', +}; } code={variantsSnippet} /> @@ -30,30 +35,16 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; + + ## Examples -### Variants - -Here's a view when buttons have different variants. - -} - code={variantsSnippet} -/> - ### Sizes -Here's a view when buttons have different sizes. - } code={sizesSnippet} /> ### With Icons -Here's a view when buttons have icons. - = { - variant: { - type: 'enum', - values: ['primary', 'secondary', 'tertiary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - iconStart: { type: 'enum', values: ['ReactNode'], responsive: false }, - iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false }, - isDisabled: { type: 'boolean', default: 'false', responsive: false }, - href: { type: 'string', required: true }, - target: { - type: 'enum', - values: ['_self', '_blank', '_parent', '_top'], - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/docs-ui/src/app/components/button-link/props-definition.tsx b/docs-ui/src/app/components/button-link/props-definition.tsx new file mode 100644 index 0000000000..deae5364df --- /dev/null +++ b/docs-ui/src/app/components/button-link/props-definition.tsx @@ -0,0 +1,74 @@ +import { + classNamePropDefs, + stylePropDefs, + childrenPropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const buttonLinkPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary', 'tertiary'], + default: 'primary', + responsive: true, + description: ( + <> + Visual style. Use primary for main actions,{' '} + secondary for alternatives, tertiary for + low-emphasis. + + ), + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Link size. Use small for inline contexts,{' '} + medium for standalone. + + ), + }, + iconStart: { + type: 'enum', + values: ['ReactElement'], + description: 'Icon displayed before the link text.', + }, + iconEnd: { + type: 'enum', + values: ['ReactElement'], + description: 'Icon displayed after the link text.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: 'Prevents interaction and applies disabled styling.', + }, + href: { + type: 'string', + required: true, + description: 'URL the link navigates to.', + }, + target: { + type: 'enum', + values: ['_self', '_blank', '_parent', '_top'], + description: ( + <> + Where to open the linked URL. Use _blank for external + links. + + ), + }, + onSurface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: 'Surface context for correct color contrast.', + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/button/components.tsx b/docs-ui/src/app/components/button/components.tsx index 558c852e9c..1fe13f6630 100644 --- a/docs-ui/src/app/components/button/components.tsx +++ b/docs-ui/src/app/components/button/components.tsx @@ -59,6 +59,22 @@ export const Disabled = () => { ); }; +export const Destructive = () => { + return ( + + + + + + ); +}; + export const Loading = () => { return ( `; export const buttonResponsiveSnippet = ``; export const variantsSnippet = ` - - - - `; + + + +`; export const sizesSnippet = ` - - - `; + + +`; export const withIconsSnippet = ` - - - - `; + + + +`; export const disabledSnippet = ` - - - - `; + + + +`; + +export const destructiveSnippet = ` + + + +`; export const loadingSnippet = ``; + Load more items +`; export const asLinkSnippet = ` - - Button - - `; + + Button + +`; diff --git a/docs-ui/src/app/components/card/components.tsx b/docs-ui/src/app/components/card/components.tsx index 08fa940be8..d10e4d7bce 100644 --- a/docs-ui/src/app/components/card/components.tsx +++ b/docs-ui/src/app/components/card/components.tsx @@ -18,7 +18,7 @@ export const Default = () => { ); }; -export const CustomSize = () => { +export const HeaderAndBody = () => { return ( { }} > Header - Body - Footer + Body content without a footer ); }; diff --git a/docs-ui/src/app/components/card/page.mdx b/docs-ui/src/app/components/card/page.mdx index e0b6e81fff..6f410fcd97 100644 --- a/docs-ui/src/app/components/card/page.mdx +++ b/docs-ui/src/app/components/card/page.mdx @@ -10,10 +10,10 @@ import { import { cardUsageSnippet, defaultSnippet, - customSizeSnippet, + headerAndBodySnippet, withLongBodySnippet, } from './snippets'; -import { Default, CustomSize, WithLongBody } from './components'; +import { Default, HeaderAndBody, WithLongBody } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { CardDefinition } from '../../../utils/definitions'; @@ -21,7 +21,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; } code={defaultSnippet} /> @@ -32,54 +32,53 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; ## API reference -### Card +All Card components extend `HTMLDivElement` attributes. -A card component that can be used to display content in a box. +### Card ### CardHeader -To display a header in a card, use the `CardHeader` component. This will be fixed at the top of the card. +Fixed at the top of the card. ### CardBody -To display content in a card, use the `CardBody` component. This will automatically fill the card. +Scrollable content area that fills available space. ### CardFooter -To display a footer in a card, use the `CardFooter` component. This will be fixed at the bottom of the card. +Fixed at the bottom of the card. ## Examples -### Custom size +### Header and body only -Here's a view when card has a custom size. +Cards can omit the footer section. } - code={customSizeSnippet} - open + layout="side-by-side" + preview={} + code={headerAndBodySnippet} /> -### With long body +### Scrollable body -Here's a view when card has a long body. +When body content exceeds the available height, CardBody scrolls while header and footer remain fixed. } code={withLongBodySnippet} - open /> diff --git a/docs-ui/src/app/components/card/props-definition.ts b/docs-ui/src/app/components/card/props-definition.ts index bb7bdad0dd..b3e3776648 100644 --- a/docs-ui/src/app/components/card/props-definition.ts +++ b/docs-ui/src/app/components/card/props-definition.ts @@ -1,30 +1,38 @@ import { classNamePropDefs, stylePropDefs, - childrenPropDefs, type PropDef, } from '@/utils/propDefs'; +const optionalChildrenPropDef: Record = { + children: { + type: 'enum', + values: ['ReactNode'], + responsive: false, + description: 'Content to display inside the component.', + }, +}; + export const cardPropDefs: Record = { - ...childrenPropDefs, + ...optionalChildrenPropDef, ...classNamePropDefs, ...stylePropDefs, }; export const cardHeaderPropDefs: Record = { - ...childrenPropDefs, + ...optionalChildrenPropDef, ...classNamePropDefs, ...stylePropDefs, }; export const cardBodyPropDefs: Record = { - ...childrenPropDefs, + ...optionalChildrenPropDef, ...classNamePropDefs, ...stylePropDefs, }; export const cardFooterPropDefs: Record = { - ...childrenPropDefs, + ...optionalChildrenPropDef, ...classNamePropDefs, ...stylePropDefs, }; diff --git a/docs-ui/src/app/components/card/snippets.ts b/docs-ui/src/app/components/card/snippets.ts index b5de9a7008..6f956e5518 100644 --- a/docs-ui/src/app/components/card/snippets.ts +++ b/docs-ui/src/app/components/card/snippets.ts @@ -6,33 +6,40 @@ export const cardUsageSnippet = `import { Card, CardHeader, CardBody, CardFooter Footer `; -export const defaultSnippet = ` +export const defaultSnippet = ` Header Body Footer `; -export const customSizeSnippet = ` +export const headerAndBodySnippet = ` Header - Body - Footer + Body content without a footer `; -export const withLongBodySnippet = ` +export const withLongBodySnippet = `import { Text } from '@backstage/ui'; + + Header This is the first paragraph of a long body text that demonstrates how - the Card component handles extensive content. + the Card component handles extensive content. The card should adjust + accordingly to display all the text properly while maintaining its + structure. Here's a second paragraph that adds more content to our card body. + Having multiple paragraphs helps to visualize how spacing works within + the card component. This third paragraph continues to add more text to ensure we have a - proper demonstration of a card with significant content. + proper demonstration of a card with significant content. This makes it + easier to test scrolling behavior and overall layout when content + exceeds the initial view. diff --git a/docs-ui/src/app/components/checkbox/page.mdx b/docs-ui/src/app/components/checkbox/page.mdx index aaf73d7210..9657b471da 100644 --- a/docs-ui/src/app/components/checkbox/page.mdx +++ b/docs-ui/src/app/components/checkbox/page.mdx @@ -12,10 +12,15 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { CheckboxDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + checkbox: 'https://react-aria.adobe.com/Checkbox', +}; } code={defaultSnippet} /> @@ -28,12 +33,12 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; + + ## Examples ### All variants -Here's a view when checkboxes have different variants. - = { children: { type: 'enum', - values: ['React.ReactNode'], - responsive: false, + values: ['ReactNode'], + required: true, + description: 'Label displayed next to the checkbox.', }, isSelected: { - type: 'enum', - values: ['boolean'], - responsive: false, + type: 'boolean', + description: 'Controls checked state (controlled mode).', }, defaultSelected: { - type: 'enum', - values: ['boolean'], - responsive: false, + type: 'boolean', + description: 'Initial checked state (uncontrolled mode).', }, onChange: { type: 'enum', values: ['(isSelected: boolean) => void'], - responsive: false, + description: 'Called when the checked state changes.', }, isDisabled: { - type: 'enum', - values: ['boolean'], - responsive: false, + type: 'boolean', + description: 'Prevents interaction and applies disabled styling.', }, isRequired: { - type: 'enum', - values: ['boolean'], - responsive: false, + type: 'boolean', + description: 'Marks the checkbox as required for form validation.', + }, + isIndeterminate: { + type: 'boolean', + description: 'Shows a mixed state, typically for "select all" checkboxes.', }, name: { type: 'string', - responsive: false, + description: 'Name attribute for form submission.', }, value: { type: 'string', - responsive: false, + description: 'Value attribute for form submission.', }, ...classNamePropDefs, ...stylePropDefs, diff --git a/docs-ui/src/app/components/container/components.tsx b/docs-ui/src/app/components/container/components.tsx index 7032b3f620..a11bbd9463 100644 --- a/docs-ui/src/app/components/container/components.tsx +++ b/docs-ui/src/app/components/container/components.tsx @@ -1,24 +1,20 @@ 'use client'; -import { Box } from '../../../../../packages/ui/src/components/Box/Box'; +import { Container } from '../../../../../packages/ui/src/components/Container/Container'; +import { DecorativeBox } from '@/components/DecorativeBox'; -const DecorativeBox = () => ( - -); - -export const Preview = () => { +export const Default = () => { return ( -
- -
+ + Page content goes here + + ); +}; + +export const ResponsiveSpacing = () => { + return ( + + Content with vertical spacing + ); }; diff --git a/docs-ui/src/app/components/container/page.mdx b/docs-ui/src/app/components/container/page.mdx index 87bc55829e..e0f4014a07 100644 --- a/docs-ui/src/app/components/container/page.mdx +++ b/docs-ui/src/app/components/container/page.mdx @@ -4,11 +4,10 @@ import { Snippet } from '@/components/Snippet'; import { containerPropDefs } from './props-definition'; import { containerUsageSnippet, - previewSnippet, - containerSimpleSnippet, + defaultSnippet, containerResponsiveSnippet, } from './snippets'; -import { Preview } from './components'; +import { Default, ResponsiveSpacing } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ContainerDefinition } from '../../../utils/definitions'; @@ -17,33 +16,30 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; -} code={previewSnippet} /> +} code={defaultSnippet} /> ## Usage -## API reference +## Core Concepts + +Container provides the standard page layout for plugin content. It constrains content to a maximum width, centers it horizontally, and adds consistent horizontal gutters. Use it once per page to wrap your main content area. + +## API Reference ## Examples -### Simple +### Responsive spacing -A simple example of how to use the Container component. +Vertical spacing props accept breakpoint objects. - - -### Responsive padding & margin - -The Container component also supports responsive values, making it easy to -create responsive designs. - - +} code={containerResponsiveSnippet} /> diff --git a/docs-ui/src/app/components/container/props-definition.ts b/docs-ui/src/app/components/container/props-definition.ts index 0980520365..89a51eac5c 100644 --- a/docs-ui/src/app/components/container/props-definition.ts +++ b/docs-ui/src/app/components/container/props-definition.ts @@ -1,12 +1,20 @@ import { classNamePropDefs, stylePropDefs, - gapPropDefs, + createSpacingGroup, type PropDef, } from '@/utils/propDefs'; export const containerPropDefs: Record = { - ...gapPropDefs, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Content to render inside the container.', + }, ...classNamePropDefs, ...stylePropDefs, + spacing: createSpacingGroup( + ['my', 'mt', 'mb', 'py', 'pt', 'pb'], + 'Vertical spacing properties for controlling margin and padding.', + ), }; diff --git a/docs-ui/src/app/components/container/snippets.ts b/docs-ui/src/app/components/container/snippets.ts index 83f4dabbbc..a314b52387 100644 --- a/docs-ui/src/app/components/container/snippets.ts +++ b/docs-ui/src/app/components/container/snippets.ts @@ -1,19 +1,13 @@ export const containerUsageSnippet = `import { Container } from "@backstage/ui"; -Hello World!`; - -export const previewSnippet = `
- -
`; - -export const containerSimpleSnippet = ` - Hello World - Hello World - Hello World + + {/* Your plugin's main content */} `; -export const containerResponsiveSnippet = ` - Hello World - Hello World - Hello World +export const defaultSnippet = ` + Page content goes here +`; + +export const containerResponsiveSnippet = ` + Content with vertical spacing `; diff --git a/docs-ui/src/app/components/dialog/components.tsx b/docs-ui/src/app/components/dialog/components.tsx index 3a1fd8c2c9..18ab1bf9c5 100644 --- a/docs-ui/src/app/components/dialog/components.tsx +++ b/docs-ui/src/app/components/dialog/components.tsx @@ -58,6 +58,20 @@ export const PreviewFixedWidthAndHeight = () => ( quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. + + Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut + fugit, sed quia consequuntur magni dolores eos qui ratione + voluptatem sequi nesciunt. + + + Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, + consectetur, adipisci velit, sed quia non numquam eius modi tempora + incidunt ut labore et dolore magnam aliquam quaerat voluptatem. + + + Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis + suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur. + diff --git a/docs-ui/src/app/components/dialog/page.mdx b/docs-ui/src/app/components/dialog/page.mdx index 1910cef5a8..12ce2d4135 100644 --- a/docs-ui/src/app/components/dialog/page.mdx +++ b/docs-ui/src/app/components/dialog/page.mdx @@ -12,7 +12,6 @@ import { dialogHeaderPropDefs, dialogBodyPropDefs, dialogFooterPropDefs, - dialogClosePropDefs, } from './props-definition'; import { dialogUsageSnippet, @@ -26,10 +25,16 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { DialogDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + dialogTrigger: 'https://react-aria.adobe.com/Modal#dialogtrigger', + modal: 'https://react-aria.adobe.com/Modal', +}; + + ### Dialog The main dialog container that renders as a modal overlay. + + ### DialogHeader Displays the dialog title with a built-in close button. + + ### DialogBody The main content area of the dialog with optional scrolling. @@ -94,7 +108,7 @@ Dialog with a fixed height body that scrolls when content overflows. ### Dialog with Form -Dialog containing form elements for user input. +Forms can be embedded in the dialog body. = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Trigger element and dialog content.', + }, isOpen: { type: 'boolean', description: 'Whether the overlay is open by default (controlled).', @@ -23,66 +27,66 @@ export const dialogTriggerPropDefs: Record = { }; export const dialogPropDefs: Record = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Dialog content (DialogHeader, DialogBody, DialogFooter).', + }, isOpen: { type: 'boolean', - description: 'Whether the overlay is open by default (controlled).', + description: 'Whether the overlay is open (controlled mode).', }, defaultOpen: { type: 'boolean', - description: 'Whether the overlay is open by default (uncontrolled).', + description: 'Initial open state (uncontrolled mode).', }, onOpenChange: { type: 'enum', values: ['(isOpen: boolean) => void'], - description: - "Handler that is called when the overlay's open state changes.", + description: 'Called when the open state changes.', }, width: { type: 'enum', values: ['number', 'string'], - responsive: false, + default: '400', + description: 'Fixed width in pixels (number) or CSS units (string).', }, height: { type: 'enum', values: ['number', 'string'], - responsive: false, + default: 'auto', + description: 'Fixed height in pixels (number) or CSS units (string).', }, ...classNamePropDefs, ...stylePropDefs, }; export const dialogHeaderPropDefs: Record = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Dialog title text.', + }, ...classNamePropDefs, ...stylePropDefs, }; export const dialogBodyPropDefs: Record = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, - height: { + children: { type: 'enum', - values: ['number', 'string'], - responsive: false, + values: ['ReactNode'], + description: 'Main content of the dialog.', }, ...classNamePropDefs, ...stylePropDefs, }; export const dialogFooterPropDefs: Record = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const dialogClosePropDefs: Record = { - variant: { + children: { type: 'enum', - values: ['primary', 'secondary', 'tertiary'], - default: 'secondary', - responsive: false, + values: ['ReactNode'], + description: 'Action buttons or footer content.', }, - children: { type: 'enum', values: ['ReactNode'], responsive: false }, ...classNamePropDefs, ...stylePropDefs, }; diff --git a/docs-ui/src/app/components/dialog/snippets.ts b/docs-ui/src/app/components/dialog/snippets.ts index c1ad50578b..440ff417e8 100644 --- a/docs-ui/src/app/components/dialog/snippets.ts +++ b/docs-ui/src/app/components/dialog/snippets.ts @@ -53,11 +53,14 @@ export const dialogWithFormSnippet = ` - + `; diff --git a/docs-ui/src/app/components/skeleton/components.tsx b/docs-ui/src/app/components/skeleton/components.tsx index 277aa17ae3..9f83a013db 100644 --- a/docs-ui/src/app/components/skeleton/components.tsx +++ b/docs-ui/src/app/components/skeleton/components.tsx @@ -2,30 +2,43 @@ import { Skeleton } from '../../../../../packages/ui/src/components/Skeleton/Skeleton'; import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Box } from '../../../../../packages/ui/src/components/Box/Box'; -export const Demo1 = () => { +export const CardPlaceholder = () => { return ( - - + - - - - - - + + + + + + ); +}; + +export const AvatarWithText = () => { + return ( + + + + + + + - +
); }; -export const Demo2 = () => { +export const Rounded = () => { return ( - - - - - + + + + + + + ); }; diff --git a/docs-ui/src/app/components/skeleton/page.mdx b/docs-ui/src/app/components/skeleton/page.mdx index 42f8975b5a..b7d218af89 100644 --- a/docs-ui/src/app/components/skeleton/page.mdx +++ b/docs-ui/src/app/components/skeleton/page.mdx @@ -2,8 +2,13 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { skeletonPropDefs } from './props-definition'; -import { skeletonUsageSnippet, demo1Snippet, demo2Snippet } from './snippets'; -import { Demo1, Demo2 } from './components'; +import { + skeletonUsageSnippet, + cardPlaceholderSnippet, + avatarWithTextSnippet, + roundedSnippet, +} from './snippets'; +import { CardPlaceholder, AvatarWithText, Rounded } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; @@ -14,7 +19,12 @@ import { SkeletonDefinition } from '../../../utils/definitions'; description="Use to show a placeholder while content is loading." /> -} code={demo2Snippet} /> +} + code={cardPlaceholderSnippet} +/> ## Usage @@ -22,21 +32,29 @@ import { SkeletonDefinition } from '../../../utils/definitions'; ## API reference +Skeleton extends standard HTML div attributes. + ## Examples -### Demo 1 +### Rounded -You can use a mix of different sizes to create a more complex skeleton. +} + code={roundedSnippet} +/> -} code={demo1Snippet} open /> +### Avatar with text -### Demo 2 - -You can use a mix of different sizes to create a more complex skeleton. - -} code={demo2Snippet} open /> +} + code={avatarWithTextSnippet} +/> diff --git a/docs-ui/src/app/components/skeleton/props-definition.ts b/docs-ui/src/app/components/skeleton/props-definition.ts index bb80024bcd..8c14c4c0ec 100644 --- a/docs-ui/src/app/components/skeleton/props-definition.ts +++ b/docs-ui/src/app/components/skeleton/props-definition.ts @@ -6,19 +6,22 @@ import { export const skeletonPropDefs: Record = { width: { - type: 'number', + type: 'string', default: '80', - responsive: false, + description: + 'The width of the skeleton. Accepts a number (pixels) or CSS string value.', }, height: { - type: 'number', + type: 'string', default: '24', - responsive: false, + description: + 'The height of the skeleton. Accepts a number (pixels) or CSS string value.', }, rounded: { type: 'boolean', default: 'false', - responsive: false, + description: + 'Whether to apply fully rounded corners (for circular shapes).', }, ...classNamePropDefs, ...stylePropDefs, diff --git a/docs-ui/src/app/components/skeleton/snippets.ts b/docs-ui/src/app/components/skeleton/snippets.ts index 981ac5ff0c..84bbec1616 100644 --- a/docs-ui/src/app/components/skeleton/snippets.ts +++ b/docs-ui/src/app/components/skeleton/snippets.ts @@ -1,26 +1,30 @@ -export const skeletonUsageSnippet = `import { Flex, Skeleton } from '@backstage/ui'; +export const skeletonUsageSnippet = `import { Skeleton } from '@backstage/ui'; - - - - -`; +`; -export const demo1Snippet = ` - +export const cardPlaceholderSnippet = ` - - - - - - + + + + +`; + +export const avatarWithTextSnippet = ` + + + + + + -`; +
`; -export const demo2Snippet = ` - - - -`; +export const roundedSnippet = ` + + + + + +`; diff --git a/docs-ui/src/app/components/switch/page.mdx b/docs-ui/src/app/components/switch/page.mdx index 494a046f47..1f893aea36 100644 --- a/docs-ui/src/app/components/switch/page.mdx +++ b/docs-ui/src/app/components/switch/page.mdx @@ -1,6 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; import { switchPropDefs } from './props-definition'; import { snippetUsage, defaultSnippet, disabledSnippet } from './snippets'; import { Default, Disabled } from './components'; @@ -9,9 +10,13 @@ import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { SwitchDefinition } from '../../../utils/definitions'; +export const reactAriaUrls = { + switch: 'https://react-aria.adobe.com/Switch', +}; + } code={defaultSnippet} /> @@ -24,19 +29,13 @@ import { SwitchDefinition } from '../../../utils/definitions'; + + ## Examples ### Disabled -A switch can be disabled using the `isDisabled` prop. - -} - code={disabledSnippet} -/> +} code={disabledSnippet} /> diff --git a/docs-ui/src/app/components/switch/props-definition.ts b/docs-ui/src/app/components/switch/props-definition.ts index 769c93aac8..eeab3e64b8 100644 --- a/docs-ui/src/app/components/switch/props-definition.ts +++ b/docs-ui/src/app/components/switch/props-definition.ts @@ -2,66 +2,44 @@ import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; import type { PropDef } from '@/utils/propDefs'; export const switchPropDefs: Record = { - autoFocus: { - type: 'boolean', - }, - defaultSelected: { - type: 'boolean', - }, - ...classNamePropDefs, - isDisabled: { - type: 'boolean', - }, - isReadOnly: { - type: 'boolean', + label: { + type: 'string', + description: 'Text label displayed next to the switch.', }, isSelected: { type: 'boolean', + description: + 'Controlled selected state. Use with onChange for controlled behavior.', }, - label: { - type: 'string', - }, - name: { - type: 'string', + defaultSelected: { + type: 'boolean', + description: 'Initial selected state for uncontrolled usage.', }, onChange: { type: 'enum', values: ['(isSelected: boolean) => void'], + description: 'Called when the switch state changes.', }, - onFocus: { - type: 'enum', - values: ['(e: FocusEvent) => void'], + isDisabled: { + type: 'boolean', + description: 'Prevents user interaction when true.', }, - onBlur: { - type: 'enum', - values: ['(e: FocusEvent) => void'], + isReadOnly: { + type: 'boolean', + description: 'Makes the switch non-interactive but still focusable.', }, - onFocusChange: { - type: 'enum', - values: ['(isFocused: boolean) => void'], + name: { + type: 'string', + description: 'Form field name for form submission.', }, - onKeyDown: { - type: 'enum', - values: ['(e: KeyboardEvent) => void'], - }, - onKeyUp: { - type: 'enum', - values: ['(e: KeyboardEvent) => void'], - }, - onHoverStart: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - }, - onHoverEnd: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - }, - onHoverChange: { - type: 'enum', - values: ['(isHovered: boolean) => void'], - }, - ...stylePropDefs, value: { type: 'string', + description: 'Form field value submitted when selected.', }, + autoFocus: { + type: 'boolean', + description: 'Focuses the switch on mount.', + }, + ...classNamePropDefs, + ...stylePropDefs, }; diff --git a/docs-ui/src/app/components/table/components.tsx b/docs-ui/src/app/components/table/components.tsx index cb158579a1..d26deeb5d3 100644 --- a/docs-ui/src/app/components/table/components.tsx +++ b/docs-ui/src/app/components/table/components.tsx @@ -1,31 +1,46 @@ 'use client'; +import { useState } from 'react'; import { Table, + TableRoot, + TableHeader, + TableBody, + Column, + Row, CellProfile, CellText, - type ColumnConfig, useTable, + type ColumnConfig, } from '../../../../../packages/ui/src/components/Table'; import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; import { Text } from '../../../../../packages/ui/src/components/Text/Text'; -import { RadioGroup } from '../../../../../packages/ui/src/components/RadioGroup/RadioGroup'; -import { Radio } from '../../../../../packages/ui/src/components/RadioGroup'; -import { data as data4 } from '../../../../../packages/ui/src/components/Table/stories/mocked-data4'; -import { useState } from 'react'; +import { SearchField } from '../../../../../packages/ui/src/components/SearchField/SearchField'; import { - selectionData, - selectionColumns, -} from '../../../../../packages/ui/src/components/Table/stories/utils'; + RadioGroup, + Radio, +} from '../../../../../packages/ui/src/components/RadioGroup'; +import { data as rockBandData } from '../../../../../packages/ui/src/components/Table/stories/mocked-data4'; +import { data as catalogData } from '../../../../../packages/ui/src/components/Table/stories/mocked-data1'; import { MemoryRouter } from 'react-router-dom'; -type Data4Item = (typeof data4)[0]; +// ============================================================================= +// Types +// ============================================================================= -const columns: ColumnConfig[] = [ +type RockBandItem = (typeof rockBandData)[0]; +type CatalogItem = (typeof catalogData)[0]; + +// ============================================================================= +// Hero Example +// ============================================================================= + +const heroColumns: ColumnConfig[] = [ { id: 'name', label: 'Band name', isRowHeader: true, + defaultWidth: '3fr', cell: item => ( ), @@ -33,112 +48,183 @@ const columns: ColumnConfig[] = [ { id: 'genre', label: 'Genre', + defaultWidth: '3fr', cell: item => , }, { id: 'yearFormed', label: 'Year formed', + defaultWidth: '1fr', cell: item => , }, { id: 'albums', label: 'Albums', + defaultWidth: '1fr', cell: item => , }, ]; -export const TableRockBand = () => { +export function HeroExample() { const { tableProps } = useTable({ mode: 'complete', - getData: () => data4, + getData: () => rockBandData, paginationOptions: { pageSize: 5 }, }); return ( -

+
); -}; +} -export const SelectionToggleWithActions = () => { - const [selected, setSelected] = useState | 'all'>( - new Set(), - ); +// ============================================================================= +// Sorting Example +// ============================================================================= +const sortingColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + isSortable: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + isSortable: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + isSortable: true, + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + isSortable: true, + cell: item => , + }, +]; + +export function SortingExample() { const { tableProps } = useTable({ mode: 'complete', - getData: () => selectionData, - paginationOptions: { pageSize: 10 }, + getData: () => catalogData, + paginationOptions: { pageSize: 5 }, + initialSort: { column: 'name', direction: 'ascending' }, + sortFn: (items, { column, direction }) => { + return [...items].sort((a, b) => { + let aVal: string; + let bVal: string; + if (column === 'owner') { + aVal = a.owner.name; + bVal = b.owner.name; + } else { + aVal = String(a[column as keyof CatalogItem]); + bVal = String(b[column as keyof CatalogItem]); + } + const cmp = aVal.localeCompare(bVal); + return direction === 'descending' ? -cmp : cmp; + }); + }, }); return ( -
alert(`Clicked: ${item.name}`) }} - /> +
); -}; +} -export const SelectionModePlayground = () => { - const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>( - 'multiple', - ); - const [selected, setSelected] = useState | 'all'>( - new Set(), - ); +// ============================================================================= +// Search Example +// ============================================================================= - const { tableProps } = useTable({ +const searchColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +export function SearchExample() { + const { tableProps, search } = useTable({ mode: 'complete', - getData: () => selectionData, - paginationOptions: { pageSize: 10 }, + getData: () => catalogData, + paginationOptions: { pageSize: 5 }, + searchFn: (items, query) => { + const lowerQuery = query.toLocaleLowerCase('en-US'); + return items.filter( + item => + item.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.owner.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.type.toLocaleLowerCase('en-US').includes(lowerQuery), + ); + }, }); return ( - -
+ +
No results found for "{search.value}" + ) : ( + No data available + ) + } + {...tableProps} /> -
- - Selection mode: - - { - setSelectionMode(value as 'single' | 'multiple'); - setSelected(new Set()); - }} - > - single - multiple - -
); -}; +} -export const SelectionBehaviorPlayground = () => { +// ============================================================================= +// Selection Example +// ============================================================================= + +const selectionColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +export function SelectionExample() { + const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>( + 'multiple', + ); const [selectionBehavior, setSelectionBehavior] = useState< 'toggle' | 'replace' >('toggle'); @@ -148,41 +234,321 @@ export const SelectionBehaviorPlayground = () => { const { tableProps } = useTable({ mode: 'complete', - getData: () => selectionData, - paginationOptions: { pageSize: 10 }, + getData: () => catalogData.slice(0, 5), }); return ( - +
-
- - Selection behavior: - - { - setSelectionBehavior(value as 'toggle' | 'replace'); - setSelected(new Set()); - }} - > - toggle - replace - -
+ +
+ + Selection mode: + + { + setSelectionMode(value as 'single' | 'multiple'); + setSelected(new Set()); + }} + > + single + multiple + +
+
+ + Selection behavior: + + { + setSelectionBehavior(value as 'toggle' | 'replace'); + setSelected(new Set()); + }} + > + toggle + replace + +
+
); -}; +} + +// ============================================================================= +// Row Actions Example +// ============================================================================= + +export function RowActionsExample() { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => catalogData.slice(0, 5), + }); + + return ( + +
alert(`Clicked: ${item.name}`), + }} + selection={{ + mode: 'multiple', + behavior: 'toggle', + selected, + onSelectionChange: setSelected, + }} + {...tableProps} + /> + + ); +} + +// ============================================================================= +// Empty State Example +// ============================================================================= + +const emptyStateColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +export function EmptyStateExample() { + const emptyData: CatalogItem[] = []; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => emptyData, + }); + + return ( + +
No items yet. Create one to get started.} + {...tableProps} + /> + + ); +} + +// ============================================================================= +// Combined Example +// ============================================================================= + +const combinedColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + isSortable: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + isSortable: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + isSortable: true, + cell: item => , + }, +]; + +export function CombinedExample() { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps, search } = useTable({ + mode: 'offset', + getData: async ({ offset, pageSize, search: query, sort }) => { + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Filter data + let filtered = catalogData; + if (query) { + const lowerQuery = query.toLocaleLowerCase('en-US'); + filtered = filtered.filter( + item => + item.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.owner.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.type.toLocaleLowerCase('en-US').includes(lowerQuery), + ); + } + + // Sort data + if (sort?.column) { + filtered = [...filtered].sort((a, b) => { + let aVal: string; + let bVal: string; + if (sort.column === 'owner') { + aVal = a.owner.name; + bVal = b.owner.name; + } else { + aVal = String(a[sort.column as keyof CatalogItem]); + bVal = String(b[sort.column as keyof CatalogItem]); + } + const cmp = aVal.localeCompare(bVal); + return sort.direction === 'descending' ? -cmp : cmp; + }); + } + + // Paginate + const data = filtered.slice(offset, offset + pageSize); + + return { data, totalCount: filtered.length }; + }, + paginationOptions: { pageSize: 5, pageSizeOptions: [5, 10, 20] }, + initialSort: { column: 'name', direction: 'ascending' }, + }); + + return ( + + + +
alert(`Clicked: ${item.name}`), + }} + selection={{ + mode: 'multiple', + behavior: 'toggle', + selected, + onSelectionChange: setSelected, + }} + emptyState={ + search.value ? ( + No results match your search + ) : ( + No items available + ) + } + {...tableProps} + /> + + + ); +} + +// ============================================================================= +// Custom Row Example +// ============================================================================= + +const customRowColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + cell: item => , + }, +]; + +export function CustomRowExample() { + const { tableProps } = useTable({ + mode: 'complete', + getData: () => catalogData.slice(0, 5), + }); + + return ( + +
( + + {column => column.cell(item)} + + )} + {...tableProps} + /> + + ); +} + +// ============================================================================= +// Primitives Example +// ============================================================================= + +export function PrimitivesExample() { + const items = catalogData.slice(0, 5); + + return ( + + + + Name + Owner + Type + + + {items.map(item => ( + + + + + + ))} + + + + ); +} diff --git a/docs-ui/src/app/components/table/page.mdx b/docs-ui/src/app/components/table/page.mdx index f9b27cd8d3..6eb280cbe2 100644 --- a/docs-ui/src/app/components/table/page.mdx +++ b/docs-ui/src/app/components/table/page.mdx @@ -2,161 +2,293 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { - TableRockBand, - SelectionModePlayground, - SelectionBehaviorPlayground, - SelectionToggleWithActions, + HeroExample, + SortingExample, + SearchExample, + SelectionExample, + RowActionsExample, + EmptyStateExample, + CombinedExample, + CustomRowExample, + PrimitivesExample, } from './components'; import { + tableReturnColumns, + useTableOptionsPropDefs, + useTableReturnPropDefs, tablePropDefs, - tableHeaderPropDefs, - tableBodyPropDefs, tablePaginationPropDefs, + columnConfigPropDefs, + cellTextPropDefs, + cellProfilePropDefs, + tableRootPropDefs, columnPropDefs, rowPropDefs, - cellPropDefs, } from './props-definition'; import { tableUsageSnippet, - tableBasicSnippet, - tableSelectionActionsSnippet, - tableSelectionModeSnippet, - tableSelectionBehaviorSnippet, + tableHeroSnippet, + tableConceptsSnippet, + tableSortingSnippet, + tablePaginationSnippet, + tableSearchSnippet, + tableSelectionSnippet, + tableRowActionsHrefSnippet, + tableRowActionsClickSnippet, + tableRowActionsDisabledSnippet, + tableEmptyStateSnippet, + tableOffsetPaginationSnippet, + tableCursorPaginationSnippet, + tableCombinedSnippet, + tableCustomRowSnippet, + tablePrimitivesSnippet, } from './snippets'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; import { TableDefinition } from '../../../utils/definitions'; +export const reactAriaUrls = { + table: 'https://react-aria.adobe.com/Table#table', + tableHeader: 'https://react-aria.adobe.com/Table#tableheader', + tableBody: 'https://react-aria.adobe.com/Table#tablebody', + column: 'https://react-aria.adobe.com/Table#column', + row: 'https://react-aria.adobe.com/Table#row', + cell: 'https://react-aria.adobe.com/Table#cell', +}; + -} code={tableBasicSnippet} /> +} code={tableHeroSnippet} /> ## Usage -## API reference +## Core Concepts -### Table +The Table component is designed around a hook + component pattern: -The main table component that renders data in a structured grid format. +- **`useTable`** manages data fetching, pagination state, sorting, and filtering. It returns `tableProps` that you spread onto the Table component. +- **`Table`** handles rendering - columns, rows, cells, and interactions. - +The hook supports three modes for different data scenarios: -### TableHeader +- **`complete`** - You have all data available upfront (client-side) +- **`offset`** - Your API uses offset/limit pagination +- **`cursor`** - Your API uses cursor-based pagination -The header section of the table that contains the column definitions. + - +**Controlled vs uncontrolled state** -### Column +By default, `useTable` manages sort, search, and filter state internally. Pass `initialSort`, `initialSearch`, or `initialFilter` to set starting values. -A column definition that describes how a column should be rendered. +For full control over state, use the controlled props instead: - +- `sort` / `onSortChange` +- `search` / `onSearchChange` +- `filter` / `onFilterChange` -### TableBody - -The body section of the table that contains the rows. - - - -### Row - -A row definition that describes how a row should be rendered. - - - -### Cell - -A cell definition that describes how a cell should be rendered. - - - -### TablePagination - -A pagination component designed to work with the Table component. - - - -## Examples - -### Basic Table - -Coming soon. - -### Row selection - -Tables support row selection with two configuration options: selection mode and selection behavior. - -#### Selection mode - -Use `selectionMode` to control how many rows can be selected. With `single`, only one row can be selected at a time. With `multiple`, any number of rows can be selected, and a header checkbox provides select-all functionality. - -} - code={tableSelectionModeSnippet} -/> - -#### Selection behavior - -Use `selectionBehavior` to control how selection is indicated and interacted with. With `toggle`, checkboxes appear for selection. With `replace`, selection is indicated by row background color—click to select, Cmd/Ctrl+click for multiple. - -} - code={tableSelectionBehaviorSnippet} -/> - -#### With row actions - -With toggle behavior, clicking a row triggers its action when nothing is selected. Once any row is selected, clicking toggles selection instead. - -With replace behavior, clicking selects the row and double-clicking triggers the action. - -} - code={tableSelectionActionsSnippet} -/> - -### Row Clicks - -Coming soon. - -### Pagination - -Coming soon. +## Common Patterns ### Sorting -Coming soon. +Columns can be made sortable by adding `isSortable: true` to the column configuration. When sortable, clicking a column header cycles through ascending, descending, and unsorted states. A visual indicator shows the current sort direction. -### Asynchronous loading +With `mode: 'complete'`, sorting happens client-side. Provide a `sortFn` that receives the full dataset and the current sort descriptor, and returns the sorted array. You can also set an `initialSort` to define the default sort when the table first renders. For server-side sorting with `offset` or `cursor` modes, see [Server-Side Data](#server-side-data). -Coming soon. +} code={tableSortingSnippet} /> -### Empty state +### Pagination -Coming soon. +Configure page size and available options through `paginationOptions`. The table displays navigation controls automatically. -### Column resizing + -This feature is not available yet — let us know if you'd like us to explore it! +### Search -### Column reordering +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. -This feature is not available yet — let us know if you'd like us to explore it! +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. -### Column pinning +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). -This feature is not available yet — let us know if you'd like us to explore it! +} code={tableSearchSnippet} /> -### Column visibility +### Row Selection -This feature is not available yet — let us know if you'd like us to explore it! +Tables support row selection with two configuration options: `mode` and `behavior`. + +Selection mode controls how many rows can be selected: + +- `single` - Only one row can be selected at a time +- `multiple` - Any number of rows can be selected, with a header checkbox for select-all + +Selection behavior controls the interaction style: + +- `toggle` - Checkboxes appear for selection. Click a checkbox to select/deselect. +- `replace` - No checkboxes. Click a row to select it (replacing previous selection). Use Cmd/Ctrl+click to select multiple rows. + +Selection state can be managed in two ways: + +- **Controlled** - Pass both `selected` and `onSelectionChange` to fully manage state externally +- **Uncontrolled** - Pass only `onSelectionChange` to let the Table manage state while receiving change notifications + +} code={tableSelectionSnippet} /> + +### Row Actions + +Rows can respond to user interaction in two ways: navigating to a URL or triggering a callback. + +Use `getHref` when rows should link to detail pages. The entire row becomes clickable and navigates on click. Links within cells remain independently clickable. + + + +Use `onClick` for custom actions like opening a panel or triggering a dialog. + + + +When combining row actions with selection, the interaction depends on selection behavior: + +- **`toggle`**: Clicking a row triggers its action when nothing is selected. Once any row is selected, clicking anywhere on the row toggles selection instead. +- **`replace`**: Single click selects the row. Double-click triggers the row action. + +You can also disable specific rows from being clicked using `getIsDisabled`: + + + +} code={tableRowActionsHrefSnippet} /> + +### Empty State + +When the table has no data to display, provide an `emptyState` to show a helpful message instead of an empty table body. Consider providing different messages depending on context - an empty search result is different from a table with no data at all. + +} code={tableEmptyStateSnippet} /> + +## Server-Side Data + +When your data comes from an API with server-side pagination, use `offset` or `cursor` mode instead of `complete`. The key difference: instead of providing all data upfront, you provide a `getData` function that fetches data for the current page. + +### Offset Pagination + +Use `mode: 'offset'` when your API accepts `offset` and `limit` (or `skip` and `take`) parameters. + + + +The `signal` parameter is an `AbortSignal` - pass it to `fetch` so in-flight requests are cancelled when the user navigates away or triggers a new query. + +### Cursor Pagination + +Use `mode: 'cursor'` when your API uses cursor-based pagination (common with GraphQL or APIs that return `nextCursor`/`prevCursor` tokens). + + + +### Loading 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. + +You can access these states via `tableProps.loading` and `tableProps.isStale` if you need to render additional loading indicators. + +## Combining Features + +Real-world tables often combine multiple features. Here's an example with search, sorting, pagination, and row selection working together. + +} code={tableCombinedSnippet} /> + +## Custom Tables + +For most use cases, `useTable` + `Table` provides everything you need. When you need more control, there are several ways you can customize your table. + +### Custom Row and Header Rendering + +Use a render function for `rowConfig` when you need to customize individual rows based on their data - for example, highlighting rows that require attention. Use `header` in column config to customize header cell content. + +} code={tableCustomRowSnippet} /> + +### Using Primitives Directly + +When the `Table` component doesn't support your use case, you can compose with the lower-level primitives: `TableRoot`, `TableHeader`, `TableBody`, `Column`, and `Row`. + +} code={tablePrimitivesSnippet} /> + +## API Reference + +### useTable + +The `useTable` hook manages data fetching, pagination, sorting, and filtering. + +**Options** + + + +**Return Value** + + + +### Table + +The main table component. + + + +### ColumnConfig + + + +### CellText + + + + + +### CellProfile + + + + + +### TablePagination + + + +### Primitives + +Low-level components for building custom table layouts. + +#### TableRoot + + + + + +#### TableHeader + + + +#### TableBody + + + +#### Column + + + + + +#### Row + + + + + +#### Cell + + diff --git a/docs-ui/src/app/components/table/props-definition.ts b/docs-ui/src/app/components/table/props-definition.ts deleted file mode 100644 index e4ef0ffe8b..0000000000 --- a/docs-ui/src/app/components/table/props-definition.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const tablePropDefs: Record = { - selectionBehavior: { - type: 'enum', - values: ['toggle', 'replace'], - default: 'toggle', - description: 'How multiple selection should behave in the collection.', - }, - disabledBehavior: { - type: 'enum', - values: ['selection', 'all'], - default: 'selection', - description: - 'Whether disabledKeys applies to all interactions, or only selection.', - }, - disabledKeys: { - type: 'enum', - values: ['Iterable'], - description: 'A list of row keys to disable.', - }, - selectionMode: { - type: 'enum', - values: ['single', 'multiple'], - description: 'The type of selection that is allowed in the collection.', - }, - selectedKeys: { - type: 'enum', - values: ['all', 'Iterable'], - description: 'The currently selected keys in the collection (controlled).', - }, - defaultSelectedKeys: { - type: 'enum', - values: ['all', 'Iterable'], - description: 'The initial selected keys in the collection (uncontrolled).', - }, - onRowAction: { - type: 'enum', - values: ['(key: Key) => void'], - description: - 'Handler that is called when a user performs an action on the row.', - }, - onSelectionChange: { - type: 'enum', - values: ['(keys: Selection) => void'], - description: 'Handler that is called when the selection changes.', - }, - onSortChange: { - type: 'enum', - values: ['(descriptor: SortDescriptor) => any'], - description: - 'Handler that is called when the sorted column or direction changes.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const tableHeaderPropDefs: Record = { - onHoverStart: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction starts.', - }, - onHoverEnd: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction ends.', - }, - onHoverChange: { - type: 'enum', - values: ['(isHovering: boolean) => void'], - description: 'Handler that is called when the hover state changes.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const columnPropDefs: Record = { - id: { - type: 'enum', - values: ['Key'], - description: 'The unique id of the column.', - }, - allowsSorting: { - type: 'boolean', - description: 'Whether the column allows sorting.', - }, - isRowHeader: { - type: 'boolean', - description: - 'Whether a column is a row header and should be announced by assistive technology during row navigation.', - }, - textValue: { - type: 'string', - description: - "A string representation of the column's contents, used for accessibility announcements.", - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const tableBodyPropDefs: Record = { - renderEmptyState: { - type: 'enum', - values: ['(props) => ReactNode'], - description: - 'Provides content to display when there are no rows in the table.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const rowPropDefs: Record = { - textValue: { - type: 'string', - description: - "A string representation of the row's contents, used for accessibility announcements.", - }, - isDisabled: { - type: 'boolean', - description: 'Whether the row is disabled.', - }, - id: { - type: 'enum', - values: ['Key'], - description: 'The unique id of the row.', - }, - href: { - type: 'string', - description: 'The URL to navigate to when the row is clicked.', - }, - hrefLang: { - type: 'string', - description: - 'The language of the URL to navigate to when the row is clicked.', - }, - target: { - type: 'string', - description: - 'The target of the URL to navigate to when the row is clicked.', - }, - rel: { - type: 'string', - description: - 'The relationship of the URL to navigate to when the row is clicked.', - }, - onAction: { - type: 'enum', - values: ['() => void'], - description: - "Handler that is called when a user performs an action on the row. The exact user event depends on the collection's selectionBehavior prop and the interaction modality.", - }, - onHoverStart: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction starts.', - }, - onHoverEnd: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction ends.', - }, - onHoverChange: { - type: 'enum', - values: ['(isHovering: boolean) => void'], - description: 'Handler that is called when the hover state changes.', - }, - onPress: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: - 'Handler that is called when the press is released over the target.', - }, - onPressStart: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: 'Handler that is called when a press interaction starts.', - }, - onPressEnd: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: - 'Handler that is called when a press interaction ends, either over the target or when the pointer leaves the target.', - }, - onPressChange: { - type: 'enum', - values: ['(isPressed: boolean) => void'], - description: 'Handler that is called when the press state changes.', - }, - onPressUp: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: - 'Handler that is called when a press is released over the target, regardless of whether it started on the target or not.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const cellPropDefs: Record = { - id: { - type: 'enum', - values: ['Key'], - description: 'The unique id of the cell.', - }, - textValue: { - type: 'string', - description: - "A string representation of the cell's contents, used for features like typeahead.", - }, - leadingIcon: { - type: 'enum', - values: ['ReactNode'], - description: 'Optional icon to display before the cell content.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const tablePaginationPropDefs: Record = { - offset: { - type: 'number', - description: 'The current offset (starting index) for pagination.', - }, - pageSize: { - type: 'number', - description: 'The number of items per page.', - }, - setOffset: { - type: 'enum', - values: ['(offset: number) => void'], - description: 'Handler that is called when the offset changes.', - }, - setPageSize: { - type: 'enum', - values: ['(pageSize: number) => void'], - description: 'Handler that is called when the page size changes.', - }, - rowCount: { - type: 'number', - description: 'The total number of rows in the table.', - }, - onNextPage: { - type: 'enum', - values: ['() => void'], - description: 'Handler that is called when the next page is requested.', - }, - onPreviousPage: { - type: 'enum', - values: ['() => void'], - description: 'Handler that is called when the previous page is requested.', - }, - onPageSizeChange: { - type: 'enum', - values: ['(pageSize: number) => void'], - description: 'Handler that is called when the page size changes.', - }, - showPageSizeOptions: { - type: 'boolean', - description: 'Whether to show the page size options.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/docs-ui/src/app/components/table/props-definition.tsx b/docs-ui/src/app/components/table/props-definition.tsx new file mode 100644 index 0000000000..be24e86bf0 --- /dev/null +++ b/docs-ui/src/app/components/table/props-definition.tsx @@ -0,0 +1,461 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +// ============================================================================= +// PropsTable Column Configuration (Table docs use description instead of responsive) +// ============================================================================= + +// For return values (no default column) +export const tableReturnColumns = [ + { key: 'prop' as const, width: '15%' }, + { key: 'type' as const, width: '25%' }, + { key: 'description' as const, width: '60%' }, +]; + +// ============================================================================= +// useTable Hook +// ============================================================================= + +export const useTableOptionsPropDefs: Record = { + mode: { + type: 'enum', + values: ['complete', 'offset', 'cursor'], + description: ( + <> + Data fetching strategy (required). Use complete for + client-side data, offset for offset/limit APIs,{' '} + cursor for cursor-based pagination. + + ), + }, + getData: { + type: 'enum', + values: ['function'], + description: + 'Function that returns or fetches data (required for "offset" and "cursor" modes). For the "complete" mode, either this or `data` must be provided. Signature varies by mode.', + }, + data: { + type: 'enum', + values: ['T[]'], + description: + 'The data for the table. Only applicable for "complete" mode, and either this or `getData` must be provided.', + }, + paginationOptions: { + type: 'enum', + values: ['object'], + description: ( + <> + Pagination configuration including pageSize,{' '} + pageSizeOptions, and initialOffset. + + ), + }, + // Uncontrolled state + initialSort: { + type: 'enum', + values: ['SortDescriptor'], + description: 'Default sort configuration on first render (uncontrolled).', + }, + initialSearch: { + type: 'string', + description: 'Default search value on first render (uncontrolled).', + }, + initialFilter: { + type: 'enum', + values: ['TFilter'], + description: 'Default filter value on first render (uncontrolled).', + }, + // Controlled state + sort: { + type: 'enum', + values: ['SortDescriptor'], + description: 'Current sort state (controlled).', + }, + onSortChange: { + type: 'enum', + values: ['(sort: SortDescriptor) => void'], + description: 'Sort change handler (controlled).', + }, + search: { + type: 'string', + description: 'Current search value (controlled).', + }, + onSearchChange: { + type: 'enum', + values: ['(search: string) => void'], + description: 'Search change handler (controlled).', + }, + filter: { + type: 'enum', + values: ['TFilter'], + description: 'Current filter value (controlled).', + }, + onFilterChange: { + type: 'enum', + values: ['(filter: TFilter) => void'], + description: 'Filter change handler (controlled).', + }, + // Client-side functions + sortFn: { + type: 'enum', + values: ['(data, sort) => data'], + description: ( + <> + Client-side sort function. Only used with complete mode. + + ), + }, + searchFn: { + type: 'enum', + values: ['(data, query) => data'], + description: ( + <> + Client-side search function. Only used with complete mode. + + ), + }, + filterFn: { + type: 'enum', + values: ['(data, filter) => data'], + description: ( + <> + Client-side filter function. Only used with complete mode. + + ), + }, +}; + +export const useTableReturnPropDefs: Record = { + tableProps: { + type: 'enum', + values: ['object'], + description: ( + <> + Props to spread onto the Table component. Includes data, + loading, error, pagination, and sort state. + + ), + }, + reload: { + type: 'enum', + values: ['() => void'], + description: 'Function to trigger a data refetch.', + }, + search: { + type: 'enum', + values: ['{ value, onChange }'], + description: ( + <> + Search state object for binding to a SearchField component. + + ), + }, + filter: { + type: 'enum', + values: ['{ value, onChange }'], + description: 'Filter state object for binding to filter controls.', + }, +}; + +// ============================================================================= +// Table Component +// ============================================================================= + +export const tablePropDefs: Record = { + columnConfig: { + type: 'enum', + values: ['ColumnConfig[]'], + description: + 'Array of column configurations defining how each column renders.', + }, + data: { + type: 'enum', + values: ['T[]'], + description: 'Array of data items to display in the table.', + }, + loading: { + type: 'boolean', + default: 'false', + description: 'Whether the table is in a loading state.', + }, + isStale: { + type: 'boolean', + default: 'false', + description: + 'Whether the displayed data is stale while new data is loading.', + }, + error: { + type: 'enum', + values: ['Error'], + description: 'Error object if data fetching failed.', + }, + pagination: { + type: 'enum', + values: ['TablePaginationType'], + description: ( + <> + Pagination configuration (required). Use{' '} + {'{ type: "none" }'} to disable or{' '} + {'{ type: "page", ...props }'} for pagination. + + ), + }, + sort: { + type: 'enum', + values: ['SortState'], + description: 'Sort state including current descriptor and change handler.', + }, + rowConfig: { + type: 'enum', + values: ['RowConfig | RowRenderFn'], + description: ( + <> + Row configuration object with getHref, onClick + , getIsDisabled, or a render function for custom rows. + + ), + }, + selection: { + type: 'enum', + values: ['TableSelection'], + description: + 'Selection configuration including mode, behavior, selected keys, and change handler.', + }, + emptyState: { + type: 'enum', + values: ['ReactNode'], + description: 'Content to display when the table has no data.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +// ============================================================================= +// ColumnConfig +// ============================================================================= + +export const columnConfigPropDefs: Record = { + id: { + type: 'string', + description: 'Unique identifier for the column.', + }, + label: { + type: 'string', + description: 'Display label for the column header.', + }, + cell: { + type: 'enum', + values: ['(item) => ReactNode'], + description: 'Render function for cell content.', + }, + header: { + type: 'enum', + values: ['() => ReactNode'], + description: 'Optional custom render function for the header cell.', + }, + isSortable: { + type: 'boolean', + default: 'false', + description: 'Whether the column supports sorting.', + }, + isHidden: { + type: 'boolean', + default: 'false', + description: 'Whether the column is hidden.', + }, + isRowHeader: { + type: 'boolean', + default: 'false', + description: 'Whether this column is the row header for accessibility.', + }, + width: { + type: 'enum', + values: ['ColumnSize'], + description: 'Current width of the column.', + }, + defaultWidth: { + type: 'enum', + values: ['ColumnSize'], + description: ( + <> + Default width of the column (e.g., 1fr, 200px + ). + + ), + }, + minWidth: { + type: 'enum', + values: ['ColumnStaticSize'], + description: 'Minimum width of the column.', + }, + maxWidth: { + type: 'enum', + values: ['ColumnStaticSize'], + description: 'Maximum width of the column.', + }, +}; + +// ============================================================================= +// CellText +// Note: Extends React Aria Cell props +// ============================================================================= + +export const cellTextPropDefs: Record = { + title: { + type: 'string', + description: 'Primary text content of the cell.', + }, + description: { + type: 'string', + description: 'Secondary description text displayed below the title.', + }, + color: { + type: 'enum', + values: ['TextColors'], + description: 'Text color variant.', + }, + leadingIcon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon displayed before the text content.', + }, + href: { + type: 'string', + description: 'URL to navigate to when the cell is clicked.', + }, +}; + +// ============================================================================= +// CellProfile +// Note: Extends React Aria Cell props +// ============================================================================= + +export const cellProfilePropDefs: Record = { + name: { + type: 'string', + description: 'Name to display.', + }, + src: { + type: 'string', + description: 'URL of the avatar image.', + }, + description: { + type: 'string', + description: 'Secondary text displayed below the name.', + }, + href: { + type: 'string', + description: 'URL to navigate to when clicked.', + }, + color: { + type: 'enum', + values: ['TextColors'], + description: 'Text color variant.', + }, +}; + +// ============================================================================= +// TablePagination +// ============================================================================= + +export const tablePaginationPropDefs: Record = { + pageSize: { + type: 'number', + description: 'Number of items per page.', + }, + pageSizeOptions: { + type: 'enum', + values: ['number[]'], + description: 'Available page size options for the dropdown.', + }, + offset: { + type: 'number', + description: 'Current offset (starting index) in the data.', + }, + totalCount: { + type: 'number', + description: 'Total number of items across all pages.', + }, + hasNextPage: { + type: 'boolean', + description: 'Whether there is a next page available.', + }, + hasPreviousPage: { + type: 'boolean', + description: 'Whether there is a previous page available.', + }, + onNextPage: { + type: 'enum', + values: ['() => void'], + description: 'Handler called when navigating to the next page.', + }, + onPreviousPage: { + type: 'enum', + values: ['() => void'], + description: 'Handler called when navigating to the previous page.', + }, + onPageSizeChange: { + type: 'enum', + values: ['(size: number) => void'], + description: 'Handler called when the page size changes.', + }, + showPageSizeOptions: { + type: 'boolean', + default: 'true', + description: 'Whether to show the page size dropdown.', + }, + getLabel: { + type: 'enum', + values: ['(props) => string'], + description: 'Custom function to generate the pagination label text.', + }, +}; + +// ============================================================================= +// Primitives +// ============================================================================= + +export const tableRootPropDefs: Record = { + stale: { + type: 'boolean', + default: 'false', + description: ( + <> + Whether the table data is stale (e.g., while fetching new data). Adds{' '} + aria-busy attribute. + + ), + }, +}; + +export const columnPropDefs: Record = { + isRowHeader: { + type: 'boolean', + description: 'Whether this column is a row header for accessibility.', + }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Column header content.', + }, +}; + +export const rowPropDefs: Record = { + id: { + type: 'enum', + values: ['string | number'], + description: 'Unique identifier for the row.', + }, + children: { + type: 'enum', + values: ['ReactNode | ((column) => ReactNode)'], + description: + 'Row content. Can be a render function receiving column config.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/table/snippets.ts b/docs-ui/src/app/components/table/snippets.ts index 0c8e029330..ab4bf53a58 100644 --- a/docs-ui/src/app/components/table/snippets.ts +++ b/docs-ui/src/app/components/table/snippets.ts @@ -1,108 +1,359 @@ -export const tableUsageSnippet = `import { Cell, CellText, ..., TableHeader, TablePagination } from '@backstage/ui'; +// ============================================================================= +// Usage +// ============================================================================= -
- - - - - - - - - -
-`; +export const tableUsageSnippet = `import { Table, useTable, CellText, type ColumnConfig } from '@backstage/ui'; -export const tableBasicSnippet = `import { Table, TableHeader, Column, TableBody, Row, CellText, CellProfile, TablePagination, useTable } from '@backstage/ui'; - -const data = [ - { - name: 'The Beatles', - image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-...jpg', - genre: 'Rock, Pop, Psychedelic Rock', - yearFormed: 1960, - albums: 13 - }, - // ... more data +const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + { id: 'owner', label: 'Owner', cell: item => }, ]; -// Uncontrolled pagination (easiest) -const { data: paginatedData, paginationProps } = useTable({ +function MyTable() { + const { tableProps } = useTable({ + mode: 'complete', + data, + }); + + return ; +}`; + +// ============================================================================= +// Hero / Quick Start +// ============================================================================= + +export const tableHeroSnippet = `import { Table, CellText, useTable, type ColumnConfig } from '@backstage/ui'; + +const data = [ + { id: 1, name: 'Service A', owner: 'Team Alpha', type: 'service' }, + { id: 2, name: 'Service B', owner: 'Team Beta', type: 'website' }, + { id: 3, name: 'Library C', owner: 'Team Gamma', type: 'library' }, +]; + +const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + { id: 'owner', label: 'Owner', cell: item => }, + { id: 'type', label: 'Type', cell: item => }, +]; + +function MyTable() { + const { tableProps } = useTable({ + mode: 'complete', + data, + }); + + return
; +}`; + +// ============================================================================= +// Core Concepts +// ============================================================================= + +export const tableConceptsSnippet = `// What useTable returns +const { + tableProps, // Spread onto
+ reload, // Trigger data refetch + search, // { value, onChange } for search input + filter, // { value, onChange } for filters +} = useTable({ ... });`; + +// ============================================================================= +// Common Patterns +// ============================================================================= + +export const tableSortingSnippet = `const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isSortable: true, cell: item => }, + { id: 'owner', label: 'Owner', isSortable: true, cell: item => }, + { id: 'type', label: 'Type', cell: item => }, +]; + +const { tableProps } = useTable({ + mode: 'complete', data, - pagination: { - defaultPageSize: 5, + initialSort: { column: 'name', direction: 'ascending' }, + sortFn: (items, { column, direction }) => { + return [...items].sort((a, b) => { + const aVal = String(a[column]); + const bVal = String(b[column]); + const cmp = aVal.localeCompare(bVal); + return direction === 'descending' ? -cmp : cmp; + }); }, }); -
- - Band name - Genre - Year formed - Albums - - - {paginatedData?.map(item => ( - - - - - - - ))} - -
-`; +return ;`; -export const tableSelectionActionsSnippet = `import { Table, TableHeader, TableBody, Column, Row, CellText } from '@backstage/ui'; +export const tablePaginationSnippet = `const { tableProps } = useTable({ + mode: 'complete', + data, + paginationOptions: { + pageSize: 10, + pageSizeOptions: [10, 25, 50], + }, +});`; -function MyTable() { - const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); +export const tableSearchSnippet = `const { tableProps, search } = useTable({ + mode: 'complete', + data, + searchFn: (items, query) => { + const lowerQuery = query.toLowerCase(); + return items.filter(item => + item.name.toLowerCase().includes(lowerQuery) || + item.owner.toLowerCase().includes(lowerQuery) + ); + }, +}); + +return ( + <> + +
+ +);`; + +export const tableSelectionSnippet = `const [selected, setSelected] = useState | 'all'>(new Set()); + +const { tableProps } = useTable({ + mode: 'complete', + data, +}); + +return ( +
+);`; + +export const tableRowActionsHrefSnippet = `
\`/catalog/\${item.namespace}/\${item.name}\` + }} + {...tableProps} +/>`; + +export const tableRowActionsClickSnippet = `
openDetailPanel(item) + }} + {...tableProps} +/>`; + +export const tableRowActionsDisabledSnippet = `
openDetailPanel(item), + getIsDisabled: item => item.status === 'archived', + }} + {...tableProps} +/>`; + +export const tableEmptyStateSnippet = `const { tableProps, search } = useTable({ + mode: 'complete', + data, + searchFn: (items, query) => { /* ... */ }, +}); + +return ( +
No results match "{search.value}" + : No items yet. Create one to get started. + } + {...tableProps} + /> +);`; + +// ============================================================================= +// Server-Side Data +// ============================================================================= + +export const tableOffsetPaginationSnippet = `const { tableProps } = useTable({ + mode: 'offset', + getData: async ({ offset, pageSize, sort, search, filter, signal }) => { + const response = await fetch( + \`/api/items?offset=\${offset}&limit=\${pageSize}&q=\${search}\`, + { signal } + ); + const { items, totalCount } = await response.json(); + + return { + data: items, + totalCount, + }; + }, + paginationOptions: { + pageSize: 20, + pageSizeOptions: [20, 50, 100], + }, +});`; + +export const tableCursorPaginationSnippet = `const { tableProps } = useTable({ + mode: 'cursor', + getData: async ({ cursor, pageSize, sort, search, signal }) => { + const response = await fetch( + \`/api/items?cursor=\${cursor ?? ''}&limit=\${pageSize}&q=\${search}\`, + { signal } + ); + const { items, nextCursor, prevCursor, totalCount } = await response.json(); + + return { + data: items, + nextCursor, + prevCursor, + totalCount, // optional - enables "X of Y" display + }; + }, + paginationOptions: { + pageSize: 20, + pageSizeOptions: [20, 50, 100], + }, +});`; + +// ============================================================================= +// Combining Features +// ============================================================================= + +export const tableCombinedSnippet = `interface TypeFilter { + type: string | null; +} + +const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isRowHeader: true, isSortable: true, + cell: item => }, + { id: 'owner', label: 'Owner', isSortable: true, + cell: item => }, + { id: 'type', label: 'Type', isSortable: true, + cell: item => }, +]; + +function ItemsTable() { + const [selected, setSelected] = useState | 'all'>(new Set()); + + const { tableProps, search, filter } = useTable({ + mode: 'offset', + initialSort: { column: 'name', direction: 'ascending' }, + getData: async ({ offset, pageSize, sort, search, filter, signal }) => { + const params = new URLSearchParams({ + offset: String(offset), + limit: String(pageSize), + q: search, + ...(sort && { sortBy: sort.column, sortDir: sort.direction }), + ...(filter?.type && { type: filter.type }), + }); + + const response = await fetch(\`/api/items?\${params}\`, { signal }); + const { items, totalCount } = await response.json(); + + return { data: items, totalCount }; + }, + }); return ( -
console.log('Opening', key)} - > - - Name - Status - - - - - - - - - - - -
+ + + +