Merge branch 'backstage:master' into feature/catalog-export
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/ui': patch
|
||||
'@backstage/plugin-notifications': patch
|
||||
'@backstage/plugin-scaffolder-backend-module-github': patch
|
||||
---
|
||||
|
||||
Added missing dependencies that were previously only available transitively.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/errors': patch
|
||||
---
|
||||
|
||||
Added explicit `name` property to `ServiceUnavailableError` for consistency with all other error classes, making it resilient to minification.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added `isPending` prop to Alert, Button, ButtonIcon, Table, and TableRoot as a replacement for the `loading` prop, aligning with React Aria naming conventions. The `loading` prop is now deprecated but still supported as an alias. CSS selectors now use `data-ispending` instead of `data-loading` for styling pending states; `data-loading` is still emitted for backward compatibility but will be removed alongside the `loading` prop.
|
||||
|
||||
**Affected components:** Alert, Button, ButtonIcon, Table, TableRoot
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-app': patch
|
||||
---
|
||||
|
||||
Migrated React Aria imports from individual packages (`@react-aria/toast`, `@react-aria/button`, `@react-stately/toast`) to the monopackages (`react-aria`, `react-stately`).
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
'@backstage/plugin-app': patch
|
||||
'@backstage/plugin-app-visualizer': patch
|
||||
'@backstage/plugin-notifications': patch
|
||||
---
|
||||
|
||||
Tightened React Aria dependency version ranges from `^` to `~` to prevent unintended minor version upgrades.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/frontend-app-api': patch
|
||||
---
|
||||
|
||||
Internal cleanup of routing utilities.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/frontend-test-utils': patch
|
||||
---
|
||||
|
||||
Removed internal `mockWithApiFactory` helper in favor of using `attachMockApiFactory` directly.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/backend-app-api': minor
|
||||
---
|
||||
|
||||
Added `ExtensionPointFactoryMiddleware` type and `createExtensionPointFactoryMiddleware` helper to reimplement extension point outputs at backend creation time.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Exported `defaultServiceFactories` to allow use with `createSpecializedBackend` for advanced configuration like `extensionPointFactoryMiddleware`.
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Fixed several database migration `down` functions that were not properly reversible, causing the SQL report to show warnings:
|
||||
|
||||
- `20241003170511_alter_target_in_locations.js`: both `up` and `down` now include `.notNullable()` when altering the `locations.target` column, preventing the `NOT NULL` constraint from being accidentally dropped when widening the column type from `varchar(255)` to `text`.
|
||||
- `20220116144621_remove_legacy.js`: the `down` function now properly recreates the three dropped legacy tables (`entities`, `entities_search`, `entities_relations`) with correct columns and indices.
|
||||
- `20210302150147_refresh_state.js`: the `down` function now drops dependent tables in the correct order (avoiding a FK constraint violation) and fixes a typo where the table was referred to as `references` instead of `refresh_state_references`.
|
||||
- `20201005122705_add_entity_full_name.js`: the `down` function now drops the `full_name` column from `entities` (not `entities_search`), and restores the `entities_unique_name` index with the correct column order `(kind, name, namespace)`.
|
||||
- `20200702153613_entities.js`: the `down` function now uses `table.integer('generation')` instead of `table.string('generation')`, restoring the correct column type.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Fixed dark mode background for Dialog component by correcting the theme attribute selector from `data-theme` to `data-theme-mode`.
|
||||
|
||||
**Affected components:** Dialog
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/cli-module-build': patch
|
||||
---
|
||||
|
||||
Fixed config path resolution for the embedded-postgres database client detection to resolve paths relative to the target package directory rather than the workspace root.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Fixed a performance regression in the `/entity-facets` endpoint when filters or permission conditions are applied, by routing the EXISTS-based filter through `final_entities` instead of correlating against the much larger `search` table.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/frontend-test-utils': patch
|
||||
---
|
||||
|
||||
Added explicit type annotations to `.map()` callback parameters in `renderInTestApp` to avoid implicit `any` errors with newer TypeScript versions.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Fixed scheduler `sleep` firing immediately for durations longer than ~24.8 days, caused by Node.js `setTimeout` overflowing its 32-bit millisecond limit.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Fixed an issue where the active tab indicator would disappear shortly after page load for uncontrolled Tabs.
|
||||
|
||||
**Affected components:** Tabs
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-home': patch
|
||||
---
|
||||
|
||||
Fixed widgets not being movable or resizable after saved edits. Previously, entering edit mode didn't restore `isDraggable` and `isResizable`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Made the TechDocs sidebar positioning at tablet breakpoints configurable via CSS custom properties, allowing apps with custom sidebar widths to override the defaults. The available properties are `--techdocs-sidebar-closed-offset-pinned`, `--techdocs-sidebar-closed-offset-collapsed`, and `--techdocs-sidebar-open-translate`.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/ui': minor
|
||||
---
|
||||
|
||||
Add support for flex item props (`grow`, `shrink`, and `basis`) to `Box`, `Card`, `Grid`, and `Flex` itself.
|
||||
|
||||
**Affected components:** Box, Card, Grid, Flex
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Updated React Aria dependencies to v1.17.0 and migrated imports from individual `@react-aria/*` and `@react-stately/*` packages to the monopackages (`react-aria`, `react-stately`). This fixes a type resolution error for `@react-types/table` that occurred in new app installations.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Moved `registerMswTestHooks` to test files.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Simplified the `OwnerEntityColumn` in the task list to rely on `EntityRefLink` and the entity presentation API instead of manually fetching entities from the catalog.
|
||||
+214
-246
@@ -2,262 +2,230 @@
|
||||
"mode": "pre",
|
||||
"tag": "next",
|
||||
"initialVersions": {
|
||||
"example-app": "0.0.34",
|
||||
"@backstage/app-defaults": "1.7.7",
|
||||
"app-example-plugin": "0.0.34",
|
||||
"example-app-legacy": "0.2.120",
|
||||
"example-backend": "0.0.49",
|
||||
"@backstage/backend-app-api": "1.6.1",
|
||||
"@backstage/backend-defaults": "0.17.0",
|
||||
"example-app": "0.0.35",
|
||||
"@backstage/app-defaults": "1.7.8",
|
||||
"app-example-plugin": "0.0.35",
|
||||
"example-app-legacy": "0.2.121",
|
||||
"example-backend": "0.0.50",
|
||||
"@backstage/backend-app-api": "1.7.0",
|
||||
"@backstage/backend-defaults": "0.17.1",
|
||||
"@backstage/backend-dev-utils": "0.1.7",
|
||||
"@backstage/backend-dynamic-feature-service": "0.8.1",
|
||||
"@backstage/backend-dynamic-feature-service": "0.8.2",
|
||||
"@internal/backend": "0.0.1",
|
||||
"@backstage/backend-openapi-utils": "0.6.8",
|
||||
"@backstage/backend-plugin-api": "1.9.0",
|
||||
"@backstage/backend-test-utils": "1.11.2",
|
||||
"@backstage/catalog-client": "1.15.0",
|
||||
"@backstage/catalog-model": "1.8.0",
|
||||
"@backstage/cli": "0.36.1",
|
||||
"@backstage/cli-common": "0.2.1",
|
||||
"@backstage/cli-defaults": "0.1.1",
|
||||
"@internal/cli": "0.0.3",
|
||||
"@backstage/cli-module-actions": "0.1.0",
|
||||
"@backstage/cli-module-auth": "0.1.1",
|
||||
"@backstage/cli-module-build": "0.1.1",
|
||||
"@backstage/cli-module-config": "0.1.1",
|
||||
"@backstage/cli-module-github": "0.1.1",
|
||||
"@backstage/cli-module-info": "0.1.1",
|
||||
"@backstage/cli-module-lint": "0.1.1",
|
||||
"@backstage/cli-module-maintenance": "0.1.1",
|
||||
"@backstage/cli-module-migrate": "0.1.1",
|
||||
"@backstage/cli-module-new": "0.1.2",
|
||||
"@backstage/cli-module-test-jest": "0.1.1",
|
||||
"@backstage/cli-module-translations": "0.1.1",
|
||||
"@backstage/cli-node": "0.3.1",
|
||||
"@backstage/codemods": "0.1.56",
|
||||
"@backstage/config": "1.3.7",
|
||||
"@backstage/config-loader": "1.10.10",
|
||||
"@backstage/core-app-api": "1.20.0",
|
||||
"@backstage/core-compat-api": "0.5.10",
|
||||
"@backstage/core-components": "0.18.9",
|
||||
"@backstage/core-plugin-api": "1.12.5",
|
||||
"@backstage/create-app": "0.8.2",
|
||||
"@backstage/dev-utils": "1.1.22",
|
||||
"e2e-test": "0.2.39",
|
||||
"@backstage/backend-openapi-utils": "0.6.9",
|
||||
"@backstage/backend-plugin-api": "1.9.1",
|
||||
"@backstage/backend-test-utils": "1.11.3",
|
||||
"@backstage/catalog-client": "1.15.1",
|
||||
"@backstage/catalog-model": "1.9.0",
|
||||
"@backstage/cli": "0.36.2",
|
||||
"@backstage/cli-common": "0.2.2",
|
||||
"@backstage/cli-defaults": "0.1.2",
|
||||
"@internal/cli": "0.0.4",
|
||||
"@backstage/cli-module-actions": "0.1.1",
|
||||
"@backstage/cli-module-auth": "0.1.2",
|
||||
"@backstage/cli-module-build": "0.1.3",
|
||||
"@backstage/cli-module-config": "0.1.2",
|
||||
"@backstage/cli-module-github": "0.1.2",
|
||||
"@backstage/cli-module-info": "0.1.2",
|
||||
"@backstage/cli-module-lint": "0.1.2",
|
||||
"@backstage/cli-module-maintenance": "0.1.2",
|
||||
"@backstage/cli-module-migrate": "0.1.2",
|
||||
"@backstage/cli-module-new": "0.1.3",
|
||||
"@backstage/cli-module-test-jest": "0.1.2",
|
||||
"@backstage/cli-module-translations": "0.1.2",
|
||||
"@backstage/cli-node": "0.3.2",
|
||||
"@backstage/codemods": "0.1.57",
|
||||
"@backstage/config": "1.3.8",
|
||||
"@backstage/config-loader": "1.10.11",
|
||||
"@backstage/core-app-api": "1.20.1",
|
||||
"@backstage/core-compat-api": "0.5.11",
|
||||
"@backstage/core-components": "0.18.10",
|
||||
"@backstage/core-plugin-api": "1.12.6",
|
||||
"@backstage/create-app": "0.8.3",
|
||||
"@backstage/dev-utils": "1.1.23",
|
||||
"e2e-test": "0.2.40",
|
||||
"@backstage/e2e-test-utils": "0.1.2",
|
||||
"@backstage/errors": "1.3.0",
|
||||
"@backstage/eslint-plugin": "0.2.3",
|
||||
"@backstage/filter-predicates": "0.1.2",
|
||||
"@backstage/frontend-app-api": "0.16.2",
|
||||
"@backstage/frontend-defaults": "0.5.1",
|
||||
"@backstage/frontend-dev-utils": "0.1.1",
|
||||
"@backstage/frontend-dynamic-feature-loader": "0.1.11",
|
||||
"@internal/frontend": "0.0.19",
|
||||
"@backstage/frontend-plugin-api": "0.16.0",
|
||||
"@backstage/frontend-test-utils": "0.5.2",
|
||||
"@backstage/integration": "2.0.1",
|
||||
"@backstage/integration-aws-node": "0.1.21",
|
||||
"@backstage/integration-react": "1.2.17",
|
||||
"@backstage/module-federation-common": "0.1.3",
|
||||
"@backstage/errors": "1.3.1",
|
||||
"@backstage/eslint-plugin": "0.3.0",
|
||||
"@backstage/filter-predicates": "0.1.3",
|
||||
"@backstage/frontend-app-api": "0.16.3",
|
||||
"@backstage/frontend-defaults": "0.5.2",
|
||||
"@backstage/frontend-dev-utils": "0.1.2",
|
||||
"@backstage/frontend-dynamic-feature-loader": "0.1.12",
|
||||
"@internal/frontend": "0.0.20",
|
||||
"@backstage/frontend-plugin-api": "0.17.0",
|
||||
"@backstage/frontend-test-utils": "0.6.0",
|
||||
"@backstage/integration": "2.0.2",
|
||||
"@backstage/integration-aws-node": "0.2.0",
|
||||
"@backstage/integration-react": "1.2.18",
|
||||
"@backstage/module-federation-common": "0.1.4",
|
||||
"@internal/opaque": "0.0.1",
|
||||
"@backstage/release-manifests": "0.0.13",
|
||||
"@backstage/repo-tools": "0.17.1",
|
||||
"@internal/scaffolder": "0.0.20",
|
||||
"@techdocs/cli": "1.10.7",
|
||||
"techdocs-cli-embedded-app": "0.2.119",
|
||||
"@backstage/test-utils": "1.7.17",
|
||||
"@backstage/repo-tools": "0.17.2",
|
||||
"@internal/scaffolder": "0.0.21",
|
||||
"@techdocs/cli": "1.11.0",
|
||||
"techdocs-cli-embedded-app": "0.2.120",
|
||||
"@backstage/test-utils": "1.7.18",
|
||||
"@backstage/theme": "0.7.3",
|
||||
"@backstage/types": "1.2.2",
|
||||
"@backstage/ui": "0.14.0",
|
||||
"@backstage/ui": "0.15.0",
|
||||
"@backstage/version-bridge": "1.0.12",
|
||||
"yarn-plugin-backstage": "0.0.11",
|
||||
"@backstage/plugin-api-docs": "0.14.0",
|
||||
"yarn-plugin-backstage": "0.0.12",
|
||||
"@backstage/plugin-api-docs": "0.14.1",
|
||||
"@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.11",
|
||||
"@backstage/plugin-app": "0.4.3",
|
||||
"@backstage/plugin-app-backend": "0.5.13",
|
||||
"@backstage/plugin-app-node": "0.1.44",
|
||||
"@backstage/plugin-app-react": "0.2.2",
|
||||
"@backstage/plugin-app-visualizer": "0.2.2",
|
||||
"@backstage/plugin-auth": "0.1.7",
|
||||
"@backstage/plugin-auth-backend": "0.28.0",
|
||||
"@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.14",
|
||||
"@backstage/plugin-auth-backend-module-auth0-provider": "0.4.0",
|
||||
"@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.15",
|
||||
"@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.19",
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.14",
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.14",
|
||||
"@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.14",
|
||||
"@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.14",
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "0.5.2",
|
||||
"@backstage/plugin-auth-backend-module-gitlab-provider": "0.4.2",
|
||||
"@backstage/plugin-auth-backend-module-google-provider": "0.3.14",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "0.2.18",
|
||||
"@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.14",
|
||||
"@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.14",
|
||||
"@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.19",
|
||||
"@backstage/plugin-auth-backend-module-oidc-provider": "0.4.15",
|
||||
"@backstage/plugin-auth-backend-module-okta-provider": "0.2.14",
|
||||
"@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.14",
|
||||
"@backstage/plugin-auth-backend-module-openshift-provider": "0.1.6",
|
||||
"@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.13",
|
||||
"@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.13",
|
||||
"@backstage/plugin-auth-node": "0.7.0",
|
||||
"@backstage/plugin-auth-react": "0.1.26",
|
||||
"@backstage/plugin-bitbucket-cloud-common": "0.3.9",
|
||||
"@backstage/plugin-catalog": "2.0.2",
|
||||
"@backstage/plugin-catalog-backend": "3.6.0",
|
||||
"@backstage/plugin-catalog-backend-module-aws": "0.4.22",
|
||||
"@backstage/plugin-catalog-backend-module-azure": "0.3.16",
|
||||
"@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.13",
|
||||
"@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.10",
|
||||
"@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.10",
|
||||
"@backstage/plugin-catalog-backend-module-gcp": "0.3.18",
|
||||
"@backstage/plugin-catalog-backend-module-gerrit": "0.3.13",
|
||||
"@backstage/plugin-catalog-backend-module-gitea": "0.1.11",
|
||||
"@backstage/plugin-catalog-backend-module-github": "0.13.1",
|
||||
"@backstage/plugin-catalog-backend-module-github-org": "0.3.21",
|
||||
"@backstage/plugin-catalog-backend-module-gitlab": "0.8.2",
|
||||
"@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.20",
|
||||
"@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.11",
|
||||
"@backstage/plugin-catalog-backend-module-ldap": "0.12.4",
|
||||
"@backstage/plugin-catalog-backend-module-logs": "0.1.21",
|
||||
"@backstage/plugin-catalog-backend-module-msgraph": "0.9.2",
|
||||
"@backstage/plugin-catalog-backend-module-openapi": "0.2.21",
|
||||
"@backstage/plugin-catalog-backend-module-puppetdb": "0.2.21",
|
||||
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.19",
|
||||
"@backstage/plugin-catalog-backend-module-unprocessed": "0.6.10",
|
||||
"@backstage/plugin-catalog-common": "1.1.9",
|
||||
"@backstage/plugin-catalog-graph": "0.6.1",
|
||||
"@backstage/plugin-catalog-import": "0.13.12",
|
||||
"@backstage/plugin-catalog-node": "2.2.0",
|
||||
"@backstage/plugin-catalog-react": "2.1.2",
|
||||
"@backstage/plugin-catalog-unprocessed-entities": "0.2.29",
|
||||
"@backstage/plugin-catalog-unprocessed-entities-common": "0.0.14",
|
||||
"@backstage/plugin-config-schema": "0.1.79",
|
||||
"@backstage/plugin-devtools": "0.1.38",
|
||||
"@backstage/plugin-devtools-backend": "0.5.16",
|
||||
"@backstage/plugin-devtools-common": "0.1.24",
|
||||
"@backstage/plugin-devtools-react": "0.2.1",
|
||||
"@backstage/plugin-events-backend": "0.6.1",
|
||||
"@backstage/plugin-events-backend-module-aws-sqs": "0.4.21",
|
||||
"@backstage/plugin-events-backend-module-azure": "0.2.30",
|
||||
"@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.30",
|
||||
"@backstage/plugin-events-backend-module-bitbucket-server": "0.1.11",
|
||||
"@backstage/plugin-events-backend-module-gerrit": "0.2.30",
|
||||
"@backstage/plugin-events-backend-module-github": "0.4.11",
|
||||
"@backstage/plugin-events-backend-module-gitlab": "0.3.11",
|
||||
"@backstage/plugin-events-backend-module-google-pubsub": "0.2.2",
|
||||
"@backstage/plugin-events-backend-module-kafka": "0.3.3",
|
||||
"@backstage/plugin-events-backend-test-utils": "0.1.54",
|
||||
"@backstage/plugin-events-node": "0.4.21",
|
||||
"@internal/plugin-todo-list": "1.0.50",
|
||||
"@internal/plugin-todo-list-backend": "1.0.49",
|
||||
"@internal/plugin-todo-list-common": "1.0.30",
|
||||
"@backstage/plugin-gateway-backend": "1.1.4",
|
||||
"@backstage/plugin-home": "0.9.4",
|
||||
"@backstage/plugin-home-react": "0.1.37",
|
||||
"@backstage/plugin-kubernetes": "0.12.18",
|
||||
"@backstage/plugin-kubernetes-backend": "0.21.3",
|
||||
"@backstage/plugin-kubernetes-cluster": "0.0.36",
|
||||
"@backstage/plugin-kubernetes-common": "0.9.11",
|
||||
"@backstage/plugin-kubernetes-node": "0.4.3",
|
||||
"@backstage/plugin-kubernetes-react": "0.5.18",
|
||||
"@backstage/plugin-mcp-actions-backend": "0.1.12",
|
||||
"@backstage/plugin-mui-to-bui": "0.2.6",
|
||||
"@backstage/plugin-notifications": "0.5.16",
|
||||
"@backstage/plugin-notifications-backend": "0.6.4",
|
||||
"@backstage/plugin-notifications-backend-module-email": "0.3.20",
|
||||
"@backstage/plugin-notifications-backend-module-slack": "0.4.1",
|
||||
"@backstage/plugin-notifications-common": "0.2.2",
|
||||
"@backstage/plugin-notifications-node": "0.2.25",
|
||||
"@backstage/plugin-org": "0.7.1",
|
||||
"@backstage/plugin-org-react": "0.1.49",
|
||||
"@backstage/plugin-permission-backend": "0.7.11",
|
||||
"@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.18",
|
||||
"@backstage/plugin-permission-common": "0.9.8",
|
||||
"@backstage/plugin-permission-node": "0.10.12",
|
||||
"@backstage/plugin-permission-react": "0.5.0",
|
||||
"@backstage/plugin-proxy-backend": "0.6.12",
|
||||
"@backstage/plugin-proxy-node": "0.1.14",
|
||||
"@backstage/plugin-scaffolder": "1.36.2",
|
||||
"@backstage/plugin-scaffolder-backend": "3.4.0",
|
||||
"@backstage/plugin-scaffolder-backend-module-azure": "0.2.20",
|
||||
"@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.3.5",
|
||||
"@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.20",
|
||||
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.20",
|
||||
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.22",
|
||||
"@backstage/plugin-scaffolder-backend-module-gcp": "0.2.20",
|
||||
"@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.20",
|
||||
"@backstage/plugin-scaffolder-backend-module-gitea": "0.2.20",
|
||||
"@backstage/plugin-scaffolder-backend-module-github": "0.9.8",
|
||||
"@backstage/plugin-scaffolder-backend-module-gitlab": "0.11.5",
|
||||
"@backstage/plugin-scaffolder-backend-module-notifications": "0.1.21",
|
||||
"@backstage/plugin-scaffolder-backend-module-rails": "0.5.20",
|
||||
"@backstage/plugin-scaffolder-backend-module-sentry": "0.3.3",
|
||||
"@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.21",
|
||||
"@backstage/plugin-scaffolder-common": "2.1.0",
|
||||
"@backstage/plugin-scaffolder-node": "0.13.2",
|
||||
"@backstage/plugin-scaffolder-node-test-utils": "0.3.10",
|
||||
"@backstage/plugin-scaffolder-react": "1.20.1",
|
||||
"@backstage/plugin-search": "1.7.1",
|
||||
"@backstage/plugin-search-backend": "2.1.1",
|
||||
"@backstage/plugin-search-backend-module-catalog": "0.3.14",
|
||||
"@backstage/plugin-search-backend-module-elasticsearch": "1.8.2",
|
||||
"@backstage/plugin-search-backend-module-explore": "0.3.13",
|
||||
"@backstage/plugin-search-backend-module-pg": "0.5.54",
|
||||
"@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.19",
|
||||
"@backstage/plugin-search-backend-module-techdocs": "0.4.13",
|
||||
"@backstage/plugin-search-backend-node": "1.4.3",
|
||||
"@backstage/plugin-search-common": "1.2.23",
|
||||
"@backstage/plugin-search-react": "1.11.1",
|
||||
"@backstage/plugin-signals": "0.0.30",
|
||||
"@backstage/plugin-signals-backend": "0.3.14",
|
||||
"@backstage/plugin-signals-node": "0.2.0",
|
||||
"@backstage/plugin-signals-react": "0.0.21",
|
||||
"@backstage/plugin-techdocs": "1.17.3",
|
||||
"@backstage/plugin-techdocs-addons-test-utils": "2.0.4",
|
||||
"@backstage/plugin-techdocs-backend": "2.1.7",
|
||||
"@backstage/plugin-app": "0.4.6",
|
||||
"@backstage/plugin-app-backend": "0.5.14",
|
||||
"@backstage/plugin-app-node": "0.1.45",
|
||||
"@backstage/plugin-app-react": "0.2.3",
|
||||
"@backstage/plugin-app-visualizer": "0.2.4",
|
||||
"@backstage/plugin-auth": "0.1.8",
|
||||
"@backstage/plugin-auth-backend": "0.29.0",
|
||||
"@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.15",
|
||||
"@backstage/plugin-auth-backend-module-auth0-provider": "0.4.1",
|
||||
"@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.16",
|
||||
"@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.20",
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.15",
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.15",
|
||||
"@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.15",
|
||||
"@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.15",
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "0.5.3",
|
||||
"@backstage/plugin-auth-backend-module-gitlab-provider": "0.4.3",
|
||||
"@backstage/plugin-auth-backend-module-google-provider": "0.3.15",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "0.2.19",
|
||||
"@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.15",
|
||||
"@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.15",
|
||||
"@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.20",
|
||||
"@backstage/plugin-auth-backend-module-oidc-provider": "0.4.16",
|
||||
"@backstage/plugin-auth-backend-module-okta-provider": "0.2.15",
|
||||
"@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.15",
|
||||
"@backstage/plugin-auth-backend-module-openshift-provider": "0.1.7",
|
||||
"@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.14",
|
||||
"@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.14",
|
||||
"@backstage/plugin-auth-node": "0.7.1",
|
||||
"@backstage/plugin-auth-react": "0.1.27",
|
||||
"@backstage/plugin-bitbucket-cloud-common": "0.3.10",
|
||||
"@backstage/plugin-catalog": "2.0.5",
|
||||
"@backstage/plugin-catalog-backend": "3.7.0",
|
||||
"@backstage/plugin-catalog-backend-module-ai-model": "0.1.0",
|
||||
"@backstage/plugin-catalog-backend-module-aws": "0.4.23",
|
||||
"@backstage/plugin-catalog-backend-module-azure": "0.3.17",
|
||||
"@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.14",
|
||||
"@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.11",
|
||||
"@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.11",
|
||||
"@backstage/plugin-catalog-backend-module-gcp": "0.3.19",
|
||||
"@backstage/plugin-catalog-backend-module-gerrit": "0.3.14",
|
||||
"@backstage/plugin-catalog-backend-module-gitea": "0.1.12",
|
||||
"@backstage/plugin-catalog-backend-module-github": "0.13.2",
|
||||
"@backstage/plugin-catalog-backend-module-github-org": "0.3.22",
|
||||
"@backstage/plugin-catalog-backend-module-gitlab": "0.8.3",
|
||||
"@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.21",
|
||||
"@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.12",
|
||||
"@backstage/plugin-catalog-backend-module-ldap": "0.12.5",
|
||||
"@backstage/plugin-catalog-backend-module-logs": "0.1.22",
|
||||
"@backstage/plugin-catalog-backend-module-msgraph": "0.10.0",
|
||||
"@backstage/plugin-catalog-backend-module-msgraph-incremental": "0.1.0",
|
||||
"@backstage/plugin-catalog-backend-module-openapi": "0.2.22",
|
||||
"@backstage/plugin-catalog-backend-module-puppetdb": "0.2.22",
|
||||
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.20",
|
||||
"@backstage/plugin-catalog-backend-module-unprocessed": "0.6.12",
|
||||
"@backstage/plugin-catalog-common": "1.1.10",
|
||||
"@backstage/plugin-catalog-graph": "0.6.4",
|
||||
"@backstage/plugin-catalog-import": "0.13.13",
|
||||
"@backstage/plugin-catalog-node": "2.2.1",
|
||||
"@backstage/plugin-catalog-react": "3.0.0",
|
||||
"@backstage/plugin-catalog-unprocessed-entities": "0.2.31",
|
||||
"@backstage/plugin-catalog-unprocessed-entities-common": "0.0.16",
|
||||
"@backstage/plugin-config-schema": "0.1.80",
|
||||
"@backstage/plugin-devtools": "0.1.39",
|
||||
"@backstage/plugin-devtools-backend": "0.5.17",
|
||||
"@backstage/plugin-devtools-common": "0.1.25",
|
||||
"@backstage/plugin-devtools-react": "0.2.2",
|
||||
"@backstage/plugin-events-backend": "0.6.2",
|
||||
"@backstage/plugin-events-backend-module-aws-sqs": "0.4.22",
|
||||
"@backstage/plugin-events-backend-module-azure": "0.2.31",
|
||||
"@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.31",
|
||||
"@backstage/plugin-events-backend-module-bitbucket-server": "0.1.12",
|
||||
"@backstage/plugin-events-backend-module-gerrit": "0.2.31",
|
||||
"@backstage/plugin-events-backend-module-github": "0.4.12",
|
||||
"@backstage/plugin-events-backend-module-gitlab": "0.3.12",
|
||||
"@backstage/plugin-events-backend-module-google-pubsub": "0.2.3",
|
||||
"@backstage/plugin-events-backend-module-kafka": "0.3.4",
|
||||
"@backstage/plugin-events-backend-test-utils": "0.1.55",
|
||||
"@backstage/plugin-events-node": "0.4.22",
|
||||
"@internal/plugin-todo-list": "1.0.51",
|
||||
"@internal/plugin-todo-list-backend": "1.0.50",
|
||||
"@internal/plugin-todo-list-common": "1.0.31",
|
||||
"@backstage/plugin-gateway-backend": "1.1.5",
|
||||
"@backstage/plugin-home": "0.9.6",
|
||||
"@backstage/plugin-home-react": "0.1.38",
|
||||
"@backstage/plugin-kubernetes": "0.12.19",
|
||||
"@backstage/plugin-kubernetes-backend": "0.21.4",
|
||||
"@backstage/plugin-kubernetes-cluster": "0.0.37",
|
||||
"@backstage/plugin-kubernetes-common": "0.9.12",
|
||||
"@backstage/plugin-kubernetes-node": "0.4.4",
|
||||
"@backstage/plugin-kubernetes-react": "0.5.19",
|
||||
"@backstage/plugin-mcp-actions-backend": "0.1.13",
|
||||
"@backstage/plugin-mui-to-bui": "0.2.7",
|
||||
"@backstage/plugin-notifications": "0.5.17",
|
||||
"@backstage/plugin-notifications-backend": "0.6.5",
|
||||
"@backstage/plugin-notifications-backend-module-email": "0.3.21",
|
||||
"@backstage/plugin-notifications-backend-module-slack": "0.4.2",
|
||||
"@backstage/plugin-notifications-common": "0.2.3",
|
||||
"@backstage/plugin-notifications-node": "0.2.26",
|
||||
"@backstage/plugin-org": "0.7.4",
|
||||
"@backstage/plugin-org-react": "0.1.50",
|
||||
"@backstage/plugin-permission-backend": "0.7.12",
|
||||
"@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.19",
|
||||
"@backstage/plugin-permission-common": "0.9.9",
|
||||
"@backstage/plugin-permission-node": "0.11.0",
|
||||
"@backstage/plugin-permission-react": "0.5.1",
|
||||
"@backstage/plugin-proxy-backend": "0.6.13",
|
||||
"@backstage/plugin-proxy-node": "0.1.15",
|
||||
"@backstage/plugin-scaffolder": "1.37.0",
|
||||
"@backstage/plugin-scaffolder-backend": "4.0.0",
|
||||
"@backstage/plugin-scaffolder-backend-module-azure": "0.2.21",
|
||||
"@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.3.6",
|
||||
"@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.21",
|
||||
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.21",
|
||||
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.23",
|
||||
"@backstage/plugin-scaffolder-backend-module-gcp": "0.2.21",
|
||||
"@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.21",
|
||||
"@backstage/plugin-scaffolder-backend-module-gitea": "0.2.21",
|
||||
"@backstage/plugin-scaffolder-backend-module-github": "0.9.9",
|
||||
"@backstage/plugin-scaffolder-backend-module-gitlab": "0.11.6",
|
||||
"@backstage/plugin-scaffolder-backend-module-notifications": "0.1.22",
|
||||
"@backstage/plugin-scaffolder-backend-module-rails": "0.5.21",
|
||||
"@backstage/plugin-scaffolder-backend-module-sentry": "0.3.4",
|
||||
"@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.22",
|
||||
"@backstage/plugin-scaffolder-common": "2.2.0",
|
||||
"@backstage/plugin-scaffolder-node": "0.13.3",
|
||||
"@backstage/plugin-scaffolder-node-test-utils": "0.3.11",
|
||||
"@backstage/plugin-scaffolder-react": "2.0.0",
|
||||
"@backstage/plugin-search": "1.7.4",
|
||||
"@backstage/plugin-search-backend": "2.1.2",
|
||||
"@backstage/plugin-search-backend-module-catalog": "0.3.15",
|
||||
"@backstage/plugin-search-backend-module-elasticsearch": "1.8.3",
|
||||
"@backstage/plugin-search-backend-module-explore": "0.3.14",
|
||||
"@backstage/plugin-search-backend-module-pg": "0.5.55",
|
||||
"@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.20",
|
||||
"@backstage/plugin-search-backend-module-techdocs": "0.4.14",
|
||||
"@backstage/plugin-search-backend-node": "1.4.4",
|
||||
"@backstage/plugin-search-common": "1.2.24",
|
||||
"@backstage/plugin-search-react": "1.11.4",
|
||||
"@backstage/plugin-signals": "0.0.31",
|
||||
"@backstage/plugin-signals-backend": "0.3.15",
|
||||
"@backstage/plugin-signals-node": "0.2.1",
|
||||
"@backstage/plugin-signals-react": "0.0.22",
|
||||
"@backstage/plugin-techdocs": "1.17.6",
|
||||
"@backstage/plugin-techdocs-addons-test-utils": "2.0.5",
|
||||
"@backstage/plugin-techdocs-backend": "2.2.0",
|
||||
"@backstage/plugin-techdocs-common": "0.1.1",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "1.1.35",
|
||||
"@backstage/plugin-techdocs-node": "1.14.5",
|
||||
"@backstage/plugin-techdocs-react": "1.3.10",
|
||||
"@backstage/plugin-user-settings": "0.9.2",
|
||||
"@backstage/plugin-user-settings-backend": "0.4.2",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "1.1.36",
|
||||
"@backstage/plugin-techdocs-node": "1.15.0",
|
||||
"@backstage/plugin-techdocs-react": "1.3.11",
|
||||
"@backstage/plugin-user-settings": "0.9.3",
|
||||
"@backstage/plugin-user-settings-backend": "0.4.3",
|
||||
"@backstage/plugin-user-settings-common": "0.1.0"
|
||||
},
|
||||
"changesets": [
|
||||
"add-missing-transitive-deps",
|
||||
"add-service-unavailable-error-name",
|
||||
"brave-groups-learn",
|
||||
"chubby-candies-cry",
|
||||
"clamp-react-aria-deps",
|
||||
"deduplicate-joinpaths-routing",
|
||||
"delegate-attach-mock-api-factory",
|
||||
"extension-point-middleware-backend-app-api",
|
||||
"extension-point-middleware-backend-defaults",
|
||||
"fix-alter-target-nullability",
|
||||
"fix-dialog-dark-theme-selector",
|
||||
"fix-embedded-postgres-config-paths",
|
||||
"fix-facets-perf-regression",
|
||||
"fix-implicit-any-renderInTestApp",
|
||||
"fix-scheduler-sleep-overflow",
|
||||
"fix-tabs-active-indicator-disappearing",
|
||||
"fix-widget-resize-after-edit",
|
||||
"free-ways-flow",
|
||||
"funny-areas-rescue",
|
||||
"gold-drinks-poke",
|
||||
"move-registermswtesthooks-to-test-utils",
|
||||
"owner-column-cleanup",
|
||||
"remove-duplicate-deps",
|
||||
"remove-portable-schema-deprecated-prop",
|
||||
"remove-unused-deps",
|
||||
"remove-unused-getgithubintegrationconfig",
|
||||
"replace-duplicate-error-utilities",
|
||||
"shy-ways-lay",
|
||||
"slack-scope-message-updates",
|
||||
"ui-date-range-picker",
|
||||
"upgrade-module-federation-v2",
|
||||
"zod-v3-config-schema-docs",
|
||||
"zod-v4-dep-bump"
|
||||
]
|
||||
"changesets": []
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
'@backstage/cli': patch
|
||||
'@backstage/core-compat-api': patch
|
||||
'@backstage/plugin-app-backend': patch
|
||||
'@backstage/plugin-auth-backend-module-oidc-provider': patch
|
||||
'@backstage/plugin-auth-node': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-notifications-backend-module-slack': patch
|
||||
---
|
||||
|
||||
Removed duplicated entries that appeared in both `dependencies` and `devDependencies`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed the deprecated property form of `PortableSchema.schema`. The `schema` member is now a plain method that must be called as `schema()` — direct property access like `schema.type` or `schema.properties` is no longer supported.
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/plugin-app-backend': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-catalog-backend-module-gitlab': patch
|
||||
'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
|
||||
'@backstage/plugin-catalog-graph': patch
|
||||
'@backstage/plugin-devtools-backend': patch
|
||||
'@backstage/plugin-kubernetes-node': patch
|
||||
'@backstage/plugin-notifications-common': patch
|
||||
'@backstage/plugin-notifications-node': patch
|
||||
'@backstage/plugin-permission-backend': patch
|
||||
'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch
|
||||
'@backstage/plugin-scaffolder-backend-module-yeoman': patch
|
||||
'@backstage/plugin-search-backend': patch
|
||||
'@backstage/plugin-signals-node': patch
|
||||
'@backstage/plugin-techdocs-react': patch
|
||||
'@backstage/plugin-user-settings-backend': patch
|
||||
'@techdocs/cli': patch
|
||||
---
|
||||
|
||||
Removed unused dependencies that had no imports in source code.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-import': patch
|
||||
---
|
||||
|
||||
Internal refactor
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@backstage/repo-tools': patch
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Replaced internal error utilities with shared ones from `@backstage/cli-common`.
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
'@backstage/plugin-catalog-graph': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-search-react': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-search': patch
|
||||
'@backstage/plugin-app': patch
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Replaced old config schema values from existing extensions and blueprints.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-notifications-backend-module-slack': patch
|
||||
---
|
||||
|
||||
Added scope-based message update support. When a notification is re-sent with the same `scope` and `notification.updated` is set, the processor now calls `chat.update()` on the existing Slack message instead of sending a duplicate via `chat.postMessage()`. Message timestamps are persisted in a new `slack_message_timestamps` database table with automatic daily cleanup. The processor gracefully degrades to the previous behavior when no database is provided.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Fixed a race condition in the stitch queue and entity processing claim logic where `SELECT FOR UPDATE SKIP LOCKED` row locks were released before the subsequent timestamp bump, allowing multiple workers to claim the same rows. Both the select and update now run inside a single transaction for MySQL and PostgreSQL.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added new `DateRangePicker` component — combines two date fields and a calendar popover for selecting a date range, built on React Aria with full keyboard and screen reader accessibility. Uses BUI design tokens throughout, including auto-incremented backgrounds via the bg consumer pattern.
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
'@backstage/cli-module-build': patch
|
||||
'@backstage/frontend-dynamic-feature-loader': patch
|
||||
'@backstage/module-federation-common': patch
|
||||
'@backstage/backend-dynamic-feature-service': patch
|
||||
---
|
||||
|
||||
Upgraded `@module-federation/enhanced`, `@module-federation/runtime`, and `@module-federation/sdk` from `^0.21.6` to `^2.3.3` to address known vulnerabilities.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
---
|
||||
|
||||
Updated error messages and deprecation warnings to clarify that the `zod/v4` subpath export from the Zod v3 package is not supported by `configSchema`, since it does not include JSON Schema conversion. The `zod` dependency has been bumped to `^4.0.0`.
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-app': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-catalog-graph': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
'@backstage/plugin-search': patch
|
||||
'@backstage/plugin-search-react': patch
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
The `zod` dependency has been bumped from `^3.25.76 || ^4.0.0` to `^4.0.0`, since `configSchema` requires the full Zod v4 package for JSON Schema support.
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../AGENTS.md
|
||||
@@ -8,6 +8,7 @@
|
||||
**/public/**
|
||||
**/microsite/**
|
||||
**/docs-ui/**
|
||||
**/workspaces/**
|
||||
**/templates/**
|
||||
**/sample-templates/**
|
||||
playwright.config.ts
|
||||
|
||||
@@ -58,6 +58,7 @@ yarn.lock @backstage/maintainers @backst
|
||||
/plugins/catalog-backend-module-backstage-openapi @backstage/maintainers @backstage/openapi-tooling-maintainers
|
||||
/plugins/catalog-backend-module-gitea @backstage/maintainers @backstage/catalog-maintainers
|
||||
/plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/catalog-maintainers @pjungermann
|
||||
/plugins/catalog-backend-module-msgraph-incremental @backstage/maintainers @backstage/catalog-maintainers
|
||||
/plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/catalog-maintainers
|
||||
/plugins/catalog-graph @backstage/maintainers @backstage/catalog-maintainers @backstage/sda-se-reviewers
|
||||
/plugins/devtools @backstage/maintainers @awanlin
|
||||
@@ -85,6 +86,7 @@ yarn.lock @backstage/maintainers @backst
|
||||
/plugins/user-settings-common @backstage/maintainers @backstage/sda-se-reviewers
|
||||
|
||||
/scripts @backstage/operations-maintainers
|
||||
/workspaces/ui @backstage/design-system-maintainers
|
||||
|
||||
/packages/backend-plugin-api/src/services/definitions/AuditorService.ts @backstage/maintainers @backstage/auditor-maintainers
|
||||
/packages/backend-defaults/src/entrypoints/auditor @backstage/maintainers @backstage/auditor-maintainers
|
||||
|
||||
@@ -75,6 +75,7 @@ codemod
|
||||
codemods
|
||||
codeowners
|
||||
codescene
|
||||
Combobox
|
||||
composability
|
||||
composable
|
||||
config
|
||||
@@ -102,6 +103,7 @@ dayjs
|
||||
debounce
|
||||
debounced
|
||||
debounces
|
||||
debouncing
|
||||
debuggability
|
||||
declaratively
|
||||
deduplicate
|
||||
@@ -256,6 +258,7 @@ LocalStack
|
||||
lockdown
|
||||
lockfile
|
||||
lockfiles
|
||||
loopback
|
||||
lookbehind
|
||||
lookup
|
||||
lookups
|
||||
@@ -345,6 +348,7 @@ params
|
||||
parseable
|
||||
passthrough
|
||||
passwordless
|
||||
patcher
|
||||
Patrik
|
||||
pattison
|
||||
Peloton
|
||||
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
|
||||
- name: Cache Comment
|
||||
if: ${{ steps.event.outputs.ACTION != 'closed' }}
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: comment.md
|
||||
key: ${{ steps.hash.outputs.COMMENT_FILE_HASH }}
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
|
||||
- name: Fetch cached Manifests File
|
||||
id: cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: comment.md
|
||||
key: ${{ needs.setup.outputs.comment-cache-key }}
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
cat ${{ github.event_path }} > event.json
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: preview-spec
|
||||
path: |
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
sync-labels: true
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p ./context
|
||||
echo "${{ github.event.pull_request.number }}" > ./context/pr-number
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: matrix.node-version == '22.x'
|
||||
with:
|
||||
name: pr-context
|
||||
@@ -257,7 +257,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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: .cache/backstage-cli
|
||||
key: ${{ runner.os }}-v${{ matrix.node-version }}-backstage-cli-${{ github.run_id }}
|
||||
@@ -281,7 +281,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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
if: always()
|
||||
with:
|
||||
path: .cache/backstage-cli
|
||||
|
||||
@@ -72,7 +72,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 package-docs cache
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: .cache/package-docs
|
||||
key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }}
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
run: yarn backstage-repo-tools package-docs
|
||||
|
||||
- name: upload API reference
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: stable-reference
|
||||
path: type-docs/
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
|
||||
# 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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
if: always()
|
||||
with:
|
||||
path: .cache/package-docs
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
run: yarn docusaurus gen-api-docs all
|
||||
|
||||
- name: upload OpenAPI API docs
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: stable-openapi-docs
|
||||
path: docs/**/api/**/*
|
||||
@@ -145,7 +145,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 package-docs cache
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: .cache/package-docs
|
||||
key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-${{ github.run_id }}
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
run: yarn backstage-repo-tools package-docs
|
||||
|
||||
- name: upload API reference
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: next-reference
|
||||
path: type-docs/
|
||||
@@ -165,7 +165,7 @@ jobs:
|
||||
|
||||
# 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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
if: always()
|
||||
with:
|
||||
path: .cache/package-docs
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
run: yarn build-storybook
|
||||
|
||||
- name: Upload Storybook
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: storybook
|
||||
path: dist-storybook
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
run: yarn docusaurus gen-api-docs all
|
||||
|
||||
- name: upload OpenAPI API docs
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: next-openapi-docs
|
||||
path: docs/**/api/**/*
|
||||
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
run: yarn backstage-cli config:check --lax
|
||||
|
||||
- name: backstage-cli cache
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: .cache/backstage-cli
|
||||
key: ${{ runner.os }}-v${{ matrix.node-version }}-backstage-cli-${{ github.run_id }}
|
||||
@@ -118,8 +118,8 @@ jobs:
|
||||
yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --coverage --success-cache --success-cache-dir .cache/backstage-cli
|
||||
env:
|
||||
BACKSTAGE_TEST_DISABLE_DOCKER: 1
|
||||
BACKSTAGE_TEST_DATABASE_postgres18_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres18.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_postgres14_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres14.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES18_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres18.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES14_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres14.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored
|
||||
BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
# These two steps add labels based on user input in the issue form
|
||||
- name: Parse issue form
|
||||
uses: stefanbuck/github-issue-parser@10dcc54158ba4c137713d9d69d70a2da63b6bda3 # v3
|
||||
uses: stefanbuck/github-issue-parser@cb6e97157cbf851e3a393ff8d57c93a484cc323f # v3
|
||||
id: issue-parser
|
||||
with:
|
||||
template-path: .github/ISSUE_TEMPLATE/.common.yaml
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: 'Upload artifact'
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -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@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -5,6 +5,10 @@ on:
|
||||
- '.github/workflows/sync_dependabot-changesets.yml'
|
||||
- '**/yarn.lock'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
generate-changeset:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -16,6 +16,11 @@ on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
actions: none
|
||||
contents: none
|
||||
pull-requests: none
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
if: >
|
||||
@@ -44,7 +49,7 @@ jobs:
|
||||
echo "$ACTOR" > ./context/actor
|
||||
echo "$ACTION" > ./context/action
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: pr-context
|
||||
path: context/
|
||||
|
||||
@@ -5,6 +5,10 @@ on:
|
||||
- '.github/workflows/sync_renovate-changesets.yml'
|
||||
- '**/yarn.lock'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
generate-changeset:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
cache-prefix: ${{ runner.os }}-v22.x
|
||||
|
||||
- name: Create Snyk report
|
||||
uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master
|
||||
uses: snyk/actions/node@9cf6ca713d71123d2d229cc3d7f145b96ea3c518 # master
|
||||
continue-on-error: true # Snyk CLI exits with error when vulnerabilities are found
|
||||
with:
|
||||
args: >
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Monitor and Synchronize Snyk Policies
|
||||
uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master
|
||||
uses: snyk/actions/node@9cf6ca713d71123d2d229cc3d7f145b96ea3c518 # master
|
||||
with:
|
||||
command: monitor
|
||||
args: >
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
# Above we run the `monitor` command, this runs the `test` command which is
|
||||
# the one that generates the SARIF report that we can upload to GitHub.
|
||||
- name: Create Snyk report
|
||||
uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master
|
||||
uses: snyk/actions/node@9cf6ca713d71123d2d229cc3d7f145b96ea3c518 # master
|
||||
continue-on-error: true # To make sure that SARIF upload gets called
|
||||
with:
|
||||
args: >
|
||||
@@ -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@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
sarif_file: snyk.sarif
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Run Chromatic
|
||||
id: chromatic
|
||||
uses: chromaui/action@f191a0224b10e1a38b2091cefb7b7a2337009116 # latest
|
||||
uses: chromaui/action@e3eb8ec36101d8f0253c7c3ae66e5a2b4e2197ba # latest
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# projectToken intentionally shared to allow collaborators to run Chromatic on forks
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
|
||||
- name: Post Chromatic Link in PR Comment
|
||||
if: github.event_name == 'pull_request' && steps.chromatic.outputs.url && github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2
|
||||
uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3
|
||||
with:
|
||||
message: |
|
||||
## 🎨 Visual Testing with Chromatic
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
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@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
|
||||
# ℹ️ 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@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p ./context
|
||||
echo "${{ github.event.pull_request.number }}" > ./context/pr-number
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: matrix.node-version == '22.x' && github.event_name == 'pull_request'
|
||||
with:
|
||||
name: pr-context
|
||||
|
||||
@@ -77,7 +77,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 package-docs cache
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: .cache/package-docs
|
||||
key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }}
|
||||
@@ -88,14 +88,14 @@ jobs:
|
||||
run: yarn backstage-repo-tools package-docs
|
||||
|
||||
- name: upload API reference
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
if: always()
|
||||
with:
|
||||
path: .cache/package-docs
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
run: yarn docusaurus gen-api-docs all
|
||||
|
||||
- name: upload OpenAPI API docs
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: stable-openapi-docs
|
||||
path: docs/**/api/**/*
|
||||
@@ -147,7 +147,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 package-docs cache
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: .cache/package-docs
|
||||
key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-next-${{ github.run_id }}
|
||||
@@ -158,14 +158,14 @@ jobs:
|
||||
run: yarn backstage-repo-tools package-docs
|
||||
|
||||
- name: upload API reference
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
if: always()
|
||||
with:
|
||||
path: .cache/package-docs
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
run: yarn build-storybook
|
||||
|
||||
- name: Upload Storybook
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: storybook
|
||||
path: dist-storybook
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
run: yarn docusaurus gen-api-docs all
|
||||
|
||||
- name: upload OpenAPI API docs
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: next-openapi-docs
|
||||
path: docs/**/api/**/*
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Make TechDocs sidebar positioning configurable via CSS custom properties
|
||||
@@ -1 +0,0 @@
|
||||
Bump zod dependency to v4 for packages using configSchema and clarify that zod/v4 subpath from v3 is not supported
|
||||
@@ -1 +0,0 @@
|
||||
Clamp React Aria dependency ranges to patch-only updates to prevent unintended minor version upgrades
|
||||
@@ -1 +0,0 @@
|
||||
Fix active tab indicator disappearing on uncontrolled Tabs in @backstage/ui
|
||||
@@ -0,0 +1 @@
|
||||
Preserve external hrefs in BUI link components under non-root base path
|
||||
@@ -3,6 +3,7 @@
|
||||
.yarn
|
||||
dist
|
||||
microsite
|
||||
workspaces
|
||||
docs-ui/.next
|
||||
docs-ui/public
|
||||
docs-ui/out
|
||||
|
||||
+18
-31
@@ -50,26 +50,11 @@ export default definePreview({
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
background: {
|
||||
name: 'Background',
|
||||
description: 'Global background for components',
|
||||
defaultValue: 'app',
|
||||
toolbar: {
|
||||
icon: 'contrast',
|
||||
items: [
|
||||
{ value: 'app', title: 'App Background' },
|
||||
{ value: 'neutral-1', title: 'Neutral 1 Background' },
|
||||
{ value: 'neutral-2', title: 'Neutral 2 Background' },
|
||||
{ value: 'neutral-3', title: 'Neutral 3 Background' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initialGlobals: {
|
||||
themeMode: 'light',
|
||||
themeName: 'backstage',
|
||||
background: 'app',
|
||||
},
|
||||
|
||||
parameters: {
|
||||
@@ -143,7 +128,6 @@ export default definePreview({
|
||||
globals.themeMode === 'light' ? themes.light : themes.dark;
|
||||
const selectedThemeMode = globals.themeMode || 'light';
|
||||
const selectedThemeName = globals.themeName || 'backstage';
|
||||
const selectedBackground = globals.background || 'app';
|
||||
const isFullscreen = context.parameters.layout === 'fullscreen';
|
||||
|
||||
useEffect(() => {
|
||||
@@ -155,15 +139,13 @@ export default definePreview({
|
||||
document.body.removeAttribute('data-theme-mode');
|
||||
document.body.removeAttribute('data-theme-name');
|
||||
};
|
||||
}, [selectedTheme, selectedThemeName]);
|
||||
}, [selectedThemeMode, selectedThemeName]);
|
||||
|
||||
useEffect(() => {
|
||||
appThemeApi.setActiveThemeId(selectedThemeMode);
|
||||
}, [selectedThemeMode]);
|
||||
|
||||
document.body.style.backgroundColor = 'var(--bui-bg-app)';
|
||||
document.body.style.padding =
|
||||
isFullscreen && selectedBackground !== 'app' ? '1rem' : '';
|
||||
const docsStoryElements = document.getElementsByClassName('docs-story');
|
||||
Array.from(docsStoryElements).forEach(element => {
|
||||
(element as HTMLElement).style.backgroundColor = 'var(--bui-bg-app)';
|
||||
@@ -174,18 +156,23 @@ export default definePreview({
|
||||
{/* @ts-ignore */}
|
||||
<TestApiProvider apis={apis}>
|
||||
<AlertDisplay />
|
||||
{Array.from({
|
||||
length:
|
||||
selectedBackground === 'app'
|
||||
? 0
|
||||
: parseInt(selectedBackground.split('-')[1], 10),
|
||||
}).reduce<React.ReactNode>(
|
||||
children => (
|
||||
<Box bg="neutral" p="4">
|
||||
{children}
|
||||
</Box>
|
||||
),
|
||||
<Story />,
|
||||
{selectedThemeName === 'spotify' ? (
|
||||
<Box
|
||||
bg="neutral"
|
||||
m={isFullscreen ? '4' : undefined}
|
||||
style={{
|
||||
borderRadius: 'var(--bui-radius-3)',
|
||||
height: isFullscreen
|
||||
? 'calc(100vh - (var(--bui-space-4) * 2))'
|
||||
: undefined,
|
||||
overflow: 'auto',
|
||||
overscrollBehavior: 'none',
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</Box>
|
||||
) : (
|
||||
<Story />
|
||||
)}
|
||||
</TestApiProvider>
|
||||
</UnifiedThemeProvider>
|
||||
|
||||
@@ -190,10 +190,6 @@
|
||||
.bui-Tag {
|
||||
border-radius: var(--bui-radius-full);
|
||||
}
|
||||
|
||||
.bui-Container {
|
||||
padding-inline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme-mode='light'][data-theme-name='spotify'] {
|
||||
@@ -243,24 +239,3 @@
|
||||
|
||||
--bui-ring: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Plugin header (@backstage/ui) and story shell header — kept at the bottom of
|
||||
* this file for easier scanning alongside other component overrides above.
|
||||
*/
|
||||
[data-theme-name='spotify'] {
|
||||
.bui-PluginHeaderToolbar {
|
||||
padding: 0;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
margin-bottom: var(--bui-space-2);
|
||||
}
|
||||
|
||||
.bui-PluginHeaderTabsWrapper {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
margin-left: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
Backstage is an open platform for building developer portals. This is a TypeScript monorepo using Yarn workspaces.
|
||||
|
||||
## Key Directories
|
||||
|
||||
+16
-13
@@ -63,12 +63,10 @@ app:
|
||||
# - apis.plugin.graphiql.browse.gitlab: true
|
||||
# - graphiql-endpoint:graphiql/gitlab: true
|
||||
|
||||
- nav-item:search: false
|
||||
- nav-item:user-settings: false
|
||||
- nav-item:catalog
|
||||
- nav-item:api-docs
|
||||
- nav-item:scaffolder
|
||||
- nav-item:app-visualizer
|
||||
# Opt in to the experimental BUI scaffolder form theme
|
||||
- sub-page:scaffolder/templates:
|
||||
config:
|
||||
enableBackstageUi: true
|
||||
|
||||
# Pages
|
||||
- page:catalog:
|
||||
@@ -99,6 +97,16 @@ app:
|
||||
- custom:
|
||||
title: Custom
|
||||
|
||||
- sub-page:scaffolder/templates:
|
||||
config:
|
||||
groups:
|
||||
- title: Recommended Services
|
||||
filter:
|
||||
spec.type: service
|
||||
- title: Documentation
|
||||
filter:
|
||||
spec.type: documentation
|
||||
|
||||
# Entity page cards
|
||||
- entity-card:catalog/about:
|
||||
config:
|
||||
@@ -195,6 +203,7 @@ backend:
|
||||
pluginSources:
|
||||
- catalog
|
||||
- scaffolder
|
||||
- search
|
||||
# See README.md in the proxy-backend plugin for information on the configuration format
|
||||
proxy:
|
||||
endpoints:
|
||||
@@ -275,6 +284,7 @@ catalog:
|
||||
pullRequestBranchName: backstage-integration
|
||||
rules:
|
||||
- allow:
|
||||
- AiResource
|
||||
- Component
|
||||
- API
|
||||
- Resource
|
||||
@@ -343,15 +353,8 @@ scaffolder:
|
||||
auth:
|
||||
experimentalDynamicClientRegistration:
|
||||
enabled: true
|
||||
allowedRedirectUriPatterns:
|
||||
- cursor://*
|
||||
- http://localhost:*
|
||||
- http://127.0.0.1:*
|
||||
experimentalClientIdMetadataDocuments:
|
||||
enabled: true
|
||||
allowedRedirectUriPatterns:
|
||||
- http://127.0.0.1:*
|
||||
- http://localhost:*
|
||||
|
||||
### Add auth.keyStore.provider to more granularly control how to store JWK data when running
|
||||
# the auth-backend.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Cleanes up orphaned entities for the provided Backstage URL, defaults to the local backend
|
||||
Cleans up orphaned entities for the provided Backstage URL, defaults to the local backend
|
||||
#>
|
||||
param(
|
||||
[string]$backstageUrl = "http://localhost:7007"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Cleanes up orphaned entities for the provided Backstage URL, defaults to the local backend
|
||||
# Cleans up orphaned entities for the provided Backstage URL, defaults to the local backend
|
||||
BACKSTAGE_URL=${1:-'http://localhost:7007'}
|
||||
echo $BACKSTAGE_URL
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"sync:changelog:force": "node scripts/sync-changelog.mjs --force"
|
||||
},
|
||||
"resolutions": {
|
||||
"@remixicon/react": ">=4.6.0 <4.9.0",
|
||||
"@types/react": "19.2.10",
|
||||
"@types/react-dom": "19.2.3"
|
||||
},
|
||||
@@ -22,13 +23,13 @@
|
||||
"@mdx-js/loader": "^3.1.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@next/mdx": "16.2.1",
|
||||
"@remixicon/react": "^4.6.0",
|
||||
"@remixicon/react": ">=4.6.0 <4.9.0",
|
||||
"@uiw/codemirror-themes": "^4.23.7",
|
||||
"@uiw/react-codemirror": "^4.23.7",
|
||||
"clsx": "^2.1.1",
|
||||
"html-react-parser": "^5.2.5",
|
||||
"motion": "^12.4.1",
|
||||
"next": "16.2.1",
|
||||
"next": "16.2.3",
|
||||
"next-mdx-remote-client": "^2.1.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "19.2.4",
|
||||
@@ -44,7 +45,7 @@
|
||||
"@types/react-dom": "19.2.3",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.2.1",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss": "^8.5.10",
|
||||
"postcss-import": "^16.1.1",
|
||||
"typescript": "^5",
|
||||
"unified": "^11.0.4"
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
} from './components';
|
||||
|
||||
export const reactAriaUrls = {
|
||||
button: 'https://react-spectrum.adobe.com/react-aria/Button.html',
|
||||
button: 'https://react-aria.adobe.com/Button',
|
||||
};
|
||||
|
||||
<PageTitle
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
'use client';
|
||||
|
||||
import { Combobox } from '../../../../../packages/ui/src/components/Combobox/Combobox';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { RiCloudLine } from '@remixicon/react';
|
||||
|
||||
const fontOptions = [
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
];
|
||||
|
||||
const countries = [
|
||||
{ value: 'us', label: 'United States' },
|
||||
{ value: 'ca', label: 'Canada' },
|
||||
{ value: 'mx', label: 'Mexico' },
|
||||
{ value: 'uk', label: 'United Kingdom' },
|
||||
{ value: 'fr', label: 'France' },
|
||||
{ value: 'de', label: 'Germany' },
|
||||
{ value: 'it', label: 'Italy' },
|
||||
{ value: 'es', label: 'Spain' },
|
||||
{ value: 'jp', label: 'Japan' },
|
||||
{ value: 'cn', label: 'China' },
|
||||
{ value: 'in', label: 'India' },
|
||||
{ value: 'br', label: 'Brazil' },
|
||||
{ value: 'au', label: 'Australia' },
|
||||
];
|
||||
|
||||
const sectionedFonts = [
|
||||
{
|
||||
title: 'Serif Fonts',
|
||||
options: [
|
||||
{ value: 'times', label: 'Times New Roman' },
|
||||
{ value: 'georgia', label: 'Georgia' },
|
||||
{ value: 'garamond', label: 'Garamond' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sans-Serif Fonts',
|
||||
options: [
|
||||
{ value: 'arial', label: 'Arial' },
|
||||
{ value: 'helvetica', label: 'Helvetica' },
|
||||
{ value: 'verdana', label: 'Verdana' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Monospace Fonts',
|
||||
options: [
|
||||
{ value: 'courier', label: 'Courier New' },
|
||||
{ value: 'consolas', label: 'Consolas' },
|
||||
{ value: 'fira', label: 'Fira Code' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const Preview = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
style={{ maxWidth: 260 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithLabelAndDescription = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
description="Choose a font family for your document"
|
||||
options={fontOptions}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Sizes = () => (
|
||||
<Flex direction="row" gap="2">
|
||||
<Combobox
|
||||
label="Small"
|
||||
size="small"
|
||||
options={fontOptions}
|
||||
name="font-small"
|
||||
placeholder="Pick a font"
|
||||
style={{ maxWidth: 260 }}
|
||||
/>
|
||||
<Combobox
|
||||
label="Medium"
|
||||
size="medium"
|
||||
options={fontOptions}
|
||||
name="font-medium"
|
||||
placeholder="Pick a font"
|
||||
style={{ maxWidth: 260 }}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export const WithIcon = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
icon={<RiCloudLine />}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Disabled = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
isDisabled
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const AllowsCustomValue = () => (
|
||||
<Combobox
|
||||
label="Country"
|
||||
options={countries}
|
||||
placeholder="Type any country"
|
||||
allowsCustomValue
|
||||
name="country"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const DisabledOption = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
disabledKeys={['serif']}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithSections = () => (
|
||||
<Combobox
|
||||
label="Font Family"
|
||||
options={sectionedFonts}
|
||||
placeholder="Pick a font"
|
||||
name="font"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,153 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ReactAriaLink } from '@/components/ReactAriaLink';
|
||||
import {
|
||||
Preview,
|
||||
WithLabelAndDescription,
|
||||
Sizes,
|
||||
WithIcon,
|
||||
Disabled,
|
||||
DisabledOption,
|
||||
AllowsCustomValue,
|
||||
WithSections,
|
||||
} from './components';
|
||||
import { comboboxPropDefs } from './props-definition';
|
||||
import {
|
||||
optionPropDefs,
|
||||
optionSectionPropDefs,
|
||||
} from '../select/props-definition';
|
||||
import {
|
||||
comboboxUsageSnippet,
|
||||
comboboxDefaultSnippet,
|
||||
comboboxDescriptionSnippet,
|
||||
comboboxSizesSnippet,
|
||||
comboboxDisabledSnippet,
|
||||
comboboxResponsiveSnippet,
|
||||
comboboxIconSnippet,
|
||||
comboboxDisabledOptionsSnippet,
|
||||
comboboxCustomValueSnippet,
|
||||
comboboxSectionsSnippet,
|
||||
} from './snippets';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { ComboboxDefinition } from '../../../utils/definitions';
|
||||
|
||||
export const reactAriaUrls = {
|
||||
combobox: 'https://react-aria.adobe.com/ComboBox',
|
||||
};
|
||||
|
||||
<PageTitle
|
||||
title="Combobox"
|
||||
description="A text input paired with a filterable dropdown for selecting or typing a value."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Preview />}
|
||||
code={comboboxDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={comboboxUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={comboboxPropDefs} />
|
||||
|
||||
<ReactAriaLink component="ComboBox" href={reactAriaUrls.combobox} />
|
||||
|
||||
### Option types
|
||||
|
||||
The `options` prop accepts an array containing either of the following shapes.
|
||||
|
||||
#### `Option`
|
||||
|
||||
<PropsTable data={optionPropDefs} />
|
||||
|
||||
#### `OptionSection`
|
||||
|
||||
<PropsTable data={optionSectionPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Label and description
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<WithLabelAndDescription />}
|
||||
code={comboboxDescriptionSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={comboboxSizesSnippet}
|
||||
/>
|
||||
|
||||
### With icon
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<WithIcon />}
|
||||
code={comboboxIconSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={comboboxDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Disabled options
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<DisabledOption />}
|
||||
code={comboboxDisabledOptionsSnippet}
|
||||
/>
|
||||
|
||||
### Custom values
|
||||
|
||||
Allow the user to type a value that is not in the option list by setting `allowsCustomValue`.
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<AllowsCustomValue />}
|
||||
code={comboboxCustomValueSnippet}
|
||||
/>
|
||||
|
||||
### With sections
|
||||
|
||||
Group options under section headings by passing objects with a `title` and a
|
||||
nested `options` array.
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<WithSections />}
|
||||
code={comboboxSectionsSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Size can change at different breakpoints.
|
||||
|
||||
<CodeBlock code={comboboxResponsiveSnippet} />
|
||||
|
||||
<Theming definition={ComboboxDefinition} />
|
||||
|
||||
<ChangelogComponent component="combobox" />
|
||||
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
import { Chip } from '@/components/Chip';
|
||||
|
||||
export const comboboxPropDefs: Record<string, PropDef> = {
|
||||
options: {
|
||||
type: 'enum',
|
||||
values: ['(Option | OptionSection)[]'],
|
||||
description: (
|
||||
<>
|
||||
Options to display in the dropdown. Pass <Chip>Option</Chip> objects
|
||||
directly, or <Chip>OptionSection</Chip> objects to render grouped
|
||||
options under section headings.
|
||||
</>
|
||||
),
|
||||
},
|
||||
allowsCustomValue: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description:
|
||||
'When true, the typed text is accepted as the value on blur or Enter even if it does not match any option.',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
description: 'Controlled selected value.',
|
||||
},
|
||||
defaultValue: {
|
||||
type: 'string',
|
||||
description: 'Initial value for uncontrolled usage.',
|
||||
},
|
||||
onChange: {
|
||||
type: 'enum',
|
||||
values: ['(value: Key | null) => void'],
|
||||
description: 'Called when the selected option changes.',
|
||||
},
|
||||
inputValue: {
|
||||
type: 'string',
|
||||
description: 'Controlled input text.',
|
||||
},
|
||||
defaultInputValue: {
|
||||
type: 'string',
|
||||
description: 'Initial input text for uncontrolled usage.',
|
||||
},
|
||||
onInputChange: {
|
||||
type: 'enum',
|
||||
values: ['(value: string) => void'],
|
||||
description: 'Called when the input text changes.',
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
description: 'Visible label above the combobox.',
|
||||
},
|
||||
secondaryLabel: {
|
||||
type: 'string',
|
||||
description: (
|
||||
<>
|
||||
Secondary text shown next to the label. If not provided and isRequired
|
||||
is true, displays <Chip>Required</Chip>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Helper text displayed below the label.',
|
||||
},
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
description: 'Text shown when the input is empty.',
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'small',
|
||||
responsive: true,
|
||||
description: 'Visual size of the combobox field.',
|
||||
},
|
||||
icon: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
description: 'Icon displayed before the input.',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
description: 'Called when the dropdown opens or closes.',
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
description: 'Prevents user interaction when true.',
|
||||
},
|
||||
disabledKeys: {
|
||||
type: 'enum',
|
||||
values: ['Iterable<Key>'],
|
||||
description: 'Keys of options that should be disabled.',
|
||||
},
|
||||
isRequired: {
|
||||
type: 'boolean',
|
||||
description: 'Marks the field as required for form validation.',
|
||||
},
|
||||
isInvalid: {
|
||||
type: 'boolean',
|
||||
description: 'Displays the combobox in an error state.',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Form field name for form submission.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
export const comboboxUsageSnippet = `import { Combobox } from '@backstage/ui';
|
||||
|
||||
<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const comboboxDefaultSnippet = `<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
placeholder="Pick a font"
|
||||
options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const comboboxDescriptionSnippet = `<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
description="Choose a font family for your document"
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const comboboxIconSnippet = `<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
icon={<RiCloudLine />}
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const comboboxSizesSnippet = `<Flex>
|
||||
<Combobox
|
||||
size="small"
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>
|
||||
<Combobox
|
||||
size="medium"
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>
|
||||
</Flex>`;
|
||||
|
||||
export const comboboxDisabledSnippet = `<Combobox
|
||||
isDisabled
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const comboboxResponsiveSnippet = `<Combobox
|
||||
size={{ initial: 'small', lg: 'medium' }}
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const comboboxDisabledOptionsSnippet = `<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
placeholder="Pick a font"
|
||||
disabledKeys={['cursive', 'serif']}
|
||||
options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const comboboxCustomValueSnippet = `<Combobox
|
||||
name="country"
|
||||
label="Country"
|
||||
allowsCustomValue
|
||||
placeholder="Type any country"
|
||||
options={[
|
||||
{ value: 'us', label: 'United States' },
|
||||
{ value: 'ca', label: 'Canada' },
|
||||
{ value: 'uk', label: 'United Kingdom' },
|
||||
{ value: 'fr', label: 'France' },
|
||||
{ value: 'de', label: 'Germany' },
|
||||
// ... more options
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const comboboxSectionsSnippet = `<Combobox
|
||||
name="font"
|
||||
label="Font Family"
|
||||
options={[
|
||||
{
|
||||
title: 'Serif Fonts',
|
||||
options: [
|
||||
{ value: 'times', label: 'Times New Roman' },
|
||||
{ value: 'georgia', label: 'Georgia' },
|
||||
{ value: 'garamond', label: 'Garamond' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sans-Serif Fonts',
|
||||
options: [
|
||||
{ value: 'arial', label: 'Arial' },
|
||||
{ value: 'helvetica', label: 'Helvetica' },
|
||||
{ value: 'verdana', label: 'Verdana' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>`;
|
||||
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import { DatePicker } from '../../../../../packages/ui/src/components/DatePicker/DatePicker';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { parseDate } from '@internationalized/date';
|
||||
|
||||
export const WithLabel = () => {
|
||||
return <DatePicker label="Date" style={{ maxWidth: '220px' }} />;
|
||||
};
|
||||
|
||||
export const Sizes = () => {
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="4"
|
||||
style={{ width: '100%', maxWidth: '280px' }}
|
||||
>
|
||||
<DatePicker label="Small" size="small" />
|
||||
<DatePicker label="Medium" size="medium" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithDefaultValue = () => {
|
||||
return (
|
||||
<DatePicker
|
||||
label="Booking date"
|
||||
defaultValue={parseDate('2025-02-03')}
|
||||
style={{ maxWidth: '280px' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Disabled = () => {
|
||||
return <DatePicker label="Date" isDisabled style={{ maxWidth: '280px' }} />;
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { datePickerPropDefs } from './props-definition';
|
||||
import {
|
||||
datePickerUsageSnippet,
|
||||
withLabelSnippet,
|
||||
sizesSnippet,
|
||||
withDefaultValueSnippet,
|
||||
disabledSnippet,
|
||||
} from './snippets';
|
||||
import { WithLabel, Sizes, WithDefaultValue, Disabled } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { DatePickerDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ReactAriaLink } from '@/components/ReactAriaLink';
|
||||
|
||||
export const reactAriaUrls = {
|
||||
datePicker: 'https://react-aria.adobe.com/DatePicker',
|
||||
};
|
||||
|
||||
<PageTitle
|
||||
title="DatePicker"
|
||||
description="A date picker that combines a date field and a calendar popover for selecting a date."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<WithLabel />}
|
||||
code={withLabelSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={datePickerUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={datePickerPropDefs} />
|
||||
|
||||
<ReactAriaLink component="DatePicker" href={reactAriaUrls.datePicker} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={sizesSnippet}
|
||||
layout="side-by-side"
|
||||
/>
|
||||
|
||||
### With default value
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithDefaultValue />}
|
||||
code={withDefaultValueSnippet}
|
||||
layout="side-by-side"
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={disabledSnippet}
|
||||
layout="side-by-side"
|
||||
/>
|
||||
|
||||
<Theming definition={DatePickerDefinition} />
|
||||
|
||||
<ChangelogComponent component="date-picker" />
|
||||
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
import { Chip } from '@/components/Chip';
|
||||
|
||||
export const datePickerPropDefs: Record<string, PropDef> = {
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'small',
|
||||
responsive: true,
|
||||
description: (
|
||||
<>
|
||||
Visual size of the picker. Use <Chip>small</Chip> for dense layouts,{' '}
|
||||
<Chip>medium</Chip> for prominent fields.
|
||||
</>
|
||||
),
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
description: 'Visible label displayed above the picker.',
|
||||
},
|
||||
secondaryLabel: {
|
||||
type: 'string',
|
||||
description: (
|
||||
<>
|
||||
Secondary text shown next to the label. If not provided and isRequired
|
||||
is true, displays <Chip>Required</Chip>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Help text displayed below the label.',
|
||||
},
|
||||
value: {
|
||||
type: 'enum',
|
||||
values: ['DateValue'],
|
||||
description: 'Controlled value of the date.',
|
||||
},
|
||||
defaultValue: {
|
||||
type: 'enum',
|
||||
values: ['DateValue'],
|
||||
description: 'Default value for uncontrolled usage.',
|
||||
},
|
||||
onChange: {
|
||||
type: 'enum',
|
||||
values: ['(value: DateValue | null) => void'],
|
||||
description: 'Handler called when the selected date changes.',
|
||||
},
|
||||
granularity: {
|
||||
type: 'enum',
|
||||
values: ['day', 'hour', 'minute', 'second'],
|
||||
default: 'day',
|
||||
description:
|
||||
'Smallest unit displayed. Defaults to "day" for dates and "minute" for times.',
|
||||
},
|
||||
minValue: {
|
||||
type: 'enum',
|
||||
values: ['DateValue'],
|
||||
description: 'Minimum allowed date. Dates before this are disabled.',
|
||||
},
|
||||
maxValue: {
|
||||
type: 'enum',
|
||||
values: ['DateValue'],
|
||||
description: 'Maximum allowed date. Dates after this are disabled.',
|
||||
},
|
||||
isDateUnavailable: {
|
||||
type: 'enum',
|
||||
values: ['(date: DateValue) => boolean'],
|
||||
description:
|
||||
'Callback invoked for each calendar date. Return true to mark a date as unavailable.',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Form field name for the date, submitted as ISO 8601.',
|
||||
},
|
||||
isRequired: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the field is required for form submission.',
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the picker is disabled.',
|
||||
},
|
||||
isReadOnly: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the picker is read-only.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
export const datePickerUsageSnippet = `import { DatePicker } from '@backstage/ui';
|
||||
|
||||
<DatePicker label="Date" />`;
|
||||
|
||||
export const withLabelSnippet = `<DatePicker
|
||||
label="Date"
|
||||
description="Select the date of your event."
|
||||
/>`;
|
||||
|
||||
export const sizesSnippet = `<Flex direction="column" gap="4">
|
||||
<DatePicker label="Small" size="small" />
|
||||
<DatePicker label="Medium" size="medium" />
|
||||
</Flex>`;
|
||||
|
||||
export const withDefaultValueSnippet = `import { parseDate } from '@internationalized/date';
|
||||
|
||||
<DatePicker
|
||||
label="Booking date"
|
||||
defaultValue={parseDate('2025-02-03')}
|
||||
/>`;
|
||||
|
||||
export const disabledSnippet = `<DatePicker
|
||||
label="Date"
|
||||
isDisabled
|
||||
/>`;
|
||||
@@ -1,6 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { Header } from '../../../../../packages/ui/src/components/Header/Header';
|
||||
import { HeaderMetadataUsers } from '../../../../../packages/ui/src/components/Header/HeaderMetadataUsers';
|
||||
import { HeaderMetadataStatus } from '../../../../../packages/ui/src/components/Header/HeaderMetadataStatus';
|
||||
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
|
||||
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
|
||||
import {
|
||||
@@ -11,6 +13,29 @@ import {
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { RiMore2Line } from '@remixicon/react';
|
||||
|
||||
const users = {
|
||||
giles: {
|
||||
name: 'Giles Peyton-Nicoll',
|
||||
src: 'https://i.pravatar.cc/150?u=giles',
|
||||
href: '/users/giles',
|
||||
},
|
||||
alice: {
|
||||
name: 'Alice Johnson',
|
||||
src: 'https://i.pravatar.cc/150?u=alice42',
|
||||
href: '/users/alice',
|
||||
},
|
||||
bob: {
|
||||
name: 'Bob Smith',
|
||||
src: 'https://i.pravatar.cc/150?u=bob',
|
||||
href: '/users/bob',
|
||||
},
|
||||
carol: {
|
||||
name: 'Carol Williams',
|
||||
src: 'https://i.pravatar.cc/150?u=carol',
|
||||
href: '/users/carol',
|
||||
},
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{ id: 'overview', label: 'Overview', href: '/overview' },
|
||||
{ id: 'checks', label: 'Checks', href: '/checks' },
|
||||
@@ -29,12 +54,37 @@ const breadcrumbs = [
|
||||
},
|
||||
];
|
||||
|
||||
const tags = [
|
||||
{ label: 'TypeScript' },
|
||||
{ label: 'Platform', href: '/platform' },
|
||||
];
|
||||
|
||||
const metadataUsers = [
|
||||
{ label: 'Type', value: 'website' },
|
||||
{
|
||||
label: 'Status',
|
||||
value: <HeaderMetadataStatus label="Passing" color="success" />,
|
||||
},
|
||||
{
|
||||
label: 'Owner',
|
||||
value: <HeaderMetadataUsers users={[users.giles]} />,
|
||||
},
|
||||
{
|
||||
label: 'Contributors',
|
||||
value: (
|
||||
<HeaderMetadataUsers users={[users.alice, users.bob, users.carol]} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export const WithEverything = () => (
|
||||
<MemoryRouter initialEntries={['/overview']}>
|
||||
<Header
|
||||
title="Page Title"
|
||||
tags={tags}
|
||||
description="A short description of this page. Supports [inline links](https://backstage.io)."
|
||||
metadata={metadataUsers}
|
||||
tabs={tabs.slice(0, 2)}
|
||||
breadcrumbs={breadcrumbs.slice(0, 2)}
|
||||
customActions={
|
||||
<>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
@@ -45,6 +95,84 @@ export const WithEverything = () => (
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithMetadataUsers = () => (
|
||||
<MemoryRouter>
|
||||
<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{ label: 'Type', value: 'website' },
|
||||
{
|
||||
label: 'Owner',
|
||||
value: <HeaderMetadataUsers users={[users.giles]} />,
|
||||
},
|
||||
{
|
||||
label: 'Contributors',
|
||||
value: (
|
||||
<HeaderMetadataUsers
|
||||
users={[users.alice, users.bob, users.carol]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithTags = () => (
|
||||
<MemoryRouter>
|
||||
<Header title="Page Title" tags={tags} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithDescription = () => (
|
||||
<MemoryRouter>
|
||||
<Header
|
||||
title="Page Title"
|
||||
description="A short description of this page. Supports [inline links](https://backstage.io)."
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithMetadata = () => (
|
||||
<MemoryRouter>
|
||||
<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{ label: 'Owner', value: 'platform-team' },
|
||||
{ label: 'Type', value: 'website' },
|
||||
]}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithMetadataStatus = () => (
|
||||
<MemoryRouter>
|
||||
<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{
|
||||
label: 'Status',
|
||||
value: <HeaderMetadataStatus label="Passing" color="success" />,
|
||||
},
|
||||
{
|
||||
label: 'Build',
|
||||
value: (
|
||||
<HeaderMetadataStatus
|
||||
label="Failed"
|
||||
color="danger"
|
||||
href="/builds/123"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Coverage',
|
||||
value: <HeaderMetadataStatus label="Warning" color="warning" />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithLongBreadcrumbs = () => (
|
||||
<MemoryRouter>
|
||||
<Header title="Page Title" breadcrumbs={breadcrumbs.slice(0, 2)} />
|
||||
|
||||
@@ -3,17 +3,28 @@ import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import {
|
||||
WithEverything,
|
||||
WithLongBreadcrumbs,
|
||||
WithTabs,
|
||||
WithTags,
|
||||
WithDescription,
|
||||
WithMetadata,
|
||||
WithMetadataUsers,
|
||||
WithMetadataStatus,
|
||||
WithCustomActions,
|
||||
WithMenu,
|
||||
} from './components';
|
||||
import { headerPagePropDefs } from './props-definition';
|
||||
import {
|
||||
headerPagePropDefs,
|
||||
headerMetadataUsersPropDefs,
|
||||
} from './props-definition';
|
||||
import {
|
||||
usage,
|
||||
defaultSnippet,
|
||||
withTabs,
|
||||
withBreadcrumbs,
|
||||
withTags,
|
||||
withDescription,
|
||||
withMetadata,
|
||||
withMetadataUsers,
|
||||
withMetadataStatus,
|
||||
withCustomActions,
|
||||
withMenu,
|
||||
} from './snippets';
|
||||
@@ -24,7 +35,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Header"
|
||||
description="A secondary header with title, breadcrumbs, tabs, and actions."
|
||||
description="A secondary header with title, tags, description, metadata, tabs, and actions."
|
||||
/>
|
||||
|
||||
<Snippet py={4} preview={<WithEverything />} code={defaultSnippet} />
|
||||
@@ -39,11 +50,37 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
## Examples
|
||||
|
||||
### Breadcrumbs
|
||||
### Tags
|
||||
|
||||
Labels are truncated at 240px.
|
||||
Tags are rendered above the title. Each tag with an `href` renders as a link; tags without `href` render as plain text. Tags are separated by a small circle divider.
|
||||
|
||||
<Snippet open preview={<WithLongBreadcrumbs />} code={withBreadcrumbs} />
|
||||
<Snippet open preview={<WithTags />} code={withTags} />
|
||||
|
||||
### Description
|
||||
|
||||
The description accepts a markdown string with support for inline links. Bold, italic, and block-level markdown are not rendered.
|
||||
|
||||
<Snippet open preview={<WithDescription />} code={withDescription} />
|
||||
|
||||
### Metadata
|
||||
|
||||
Key-value pairs displayed below the description.
|
||||
|
||||
<Snippet open preview={<WithMetadata />} code={withMetadata} />
|
||||
|
||||
### Metadata with users
|
||||
|
||||
Use `HeaderMetadataUsers` as the metadata value to display users as avatars. A single user shows the avatar with their name beside it. Multiple users show a row of avatars — hover to reveal each name via tooltip. When a user has an `href`, the avatar and name become links.
|
||||
|
||||
<Snippet open preview={<WithMetadataUsers />} code={withMetadataUsers} />
|
||||
|
||||
<PropsTable data={headerMetadataUsersPropDefs} />
|
||||
|
||||
### Metadata with status
|
||||
|
||||
Use `HeaderMetadataStatus` as the metadata value to display a status indicator. The dot colour is driven by the `color` prop which maps to BUI status tokens. Pass an `href` to make the label a link.
|
||||
|
||||
<Snippet open preview={<WithMetadataStatus />} code={withMetadataStatus} />
|
||||
|
||||
### Tabs
|
||||
|
||||
|
||||
@@ -5,6 +5,51 @@ export const headerPagePropDefs: Record<string, PropDef> = {
|
||||
type: 'string',
|
||||
description: 'Page heading displayed in the header.',
|
||||
},
|
||||
tags: {
|
||||
type: 'complex',
|
||||
description:
|
||||
'Items displayed above the title. Each tag renders as a link when href is provided, or as plain text otherwise. Tags are separated by a small circle divider.',
|
||||
complexType: {
|
||||
name: 'HeaderTag[]',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the tag.',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'URL to navigate to when the tag is clicked.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Markdown string rendered below the title. Only inline links are supported. Bold, italic, and block-level markdown are not rendered.',
|
||||
},
|
||||
metadata: {
|
||||
type: 'complex',
|
||||
description: 'Key-value pairs displayed below the description.',
|
||||
complexType: {
|
||||
name: 'HeaderMetadataItem[]',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The key label, displayed in secondary color.',
|
||||
},
|
||||
value: {
|
||||
type: 'string | ReactNode',
|
||||
required: true,
|
||||
description:
|
||||
'The value to display alongside the label. Pass a string for plain text or a ReactNode for custom content such as HeaderMetadataUsers.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
customActions: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
@@ -49,6 +94,7 @@ export const headerPagePropDefs: Record<string, PropDef> = {
|
||||
},
|
||||
breadcrumbs: {
|
||||
type: 'complex',
|
||||
deprecated: true,
|
||||
description: 'Breadcrumb trail displayed above the title.',
|
||||
complexType: {
|
||||
name: 'HeaderBreadcrumb[]',
|
||||
@@ -68,3 +114,33 @@ export const headerPagePropDefs: Record<string, PropDef> = {
|
||||
},
|
||||
...classNamePropDefs,
|
||||
};
|
||||
|
||||
export const headerMetadataUsersPropDefs: Record<string, PropDef> = {
|
||||
users: {
|
||||
type: 'complex',
|
||||
description:
|
||||
'List of users to display. A single user shows the avatar with their name beside it. Multiple users show a row of avatars with names revealed on hover via tooltip.',
|
||||
complexType: {
|
||||
name: 'HeaderMetadataUser[]',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description:
|
||||
'Display name shown beside the avatar (single) or in the tooltip (multiple).',
|
||||
},
|
||||
src: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'URL for the avatar image.',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description:
|
||||
'When provided, the avatar becomes a link and the name is rendered as a Link component.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,15 +2,41 @@ export const usage = `import { Header } from '@backstage/ui';
|
||||
|
||||
<Header title="Page Title" />`;
|
||||
|
||||
export const defaultSnippet = `<Header
|
||||
export const defaultSnippet = `import { Header, HeaderMetadataUsers, HeaderMetadataStatus } from '@backstage/ui';
|
||||
|
||||
<Header
|
||||
title="Page Title"
|
||||
breadcrumbs={[
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Dashboard', href: '/dashboard' },
|
||||
tags={[
|
||||
{ label: 'TypeScript' },
|
||||
{ label: 'Platform', href: '/platform' },
|
||||
]}
|
||||
description="A short description. Supports [inline links](https://backstage.io)."
|
||||
metadata={[
|
||||
{ label: 'Type', value: 'website' },
|
||||
{
|
||||
label: 'Status',
|
||||
value: <HeaderMetadataStatus label="Passing" color="success" />,
|
||||
},
|
||||
{
|
||||
label: 'Owner',
|
||||
value: <HeaderMetadataUsers users={[{ name: 'Giles Peyton-Nicoll', src: '...', href: '/users/giles' }]} />,
|
||||
},
|
||||
{
|
||||
label: 'Contributors',
|
||||
value: (
|
||||
<HeaderMetadataUsers
|
||||
users={[
|
||||
{ name: 'Alice Johnson', src: '...', href: '/users/alice' },
|
||||
{ name: 'Bob Smith', src: '...', href: '/users/bob' },
|
||||
{ name: 'Carol Williams', src: '...', href: '/users/carol' },
|
||||
]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
tabs={[
|
||||
{ id: 'overview', label: 'Overview', href: '/overview' },
|
||||
{ id: 'settings', label: 'Settings', href: '/settings' },
|
||||
{ id: 'checks', label: 'Checks', href: '/checks' },
|
||||
]}
|
||||
customActions={
|
||||
<>
|
||||
@@ -54,3 +80,70 @@ export const withMenu = `<Header
|
||||
</MenuTrigger>
|
||||
}
|
||||
/>`;
|
||||
|
||||
export const withTags = `<Header
|
||||
title="Page Title"
|
||||
tags={[
|
||||
{ label: 'TypeScript' },
|
||||
{ label: 'Platform', href: '/platform' },
|
||||
{ label: 'Gold' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withDescription = `<Header
|
||||
title="Page Title"
|
||||
description="A short description. Supports [inline links](https://backstage.io)."
|
||||
/>`;
|
||||
|
||||
export const withMetadata = `<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{ label: 'Owner', value: 'platform-team' },
|
||||
{ label: 'Type', value: 'website' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withMetadataStatus = `import { Header, HeaderMetadataStatus } from '@backstage/ui';
|
||||
|
||||
<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{
|
||||
label: 'Status',
|
||||
value: <HeaderMetadataStatus label="Passing" color="success" />,
|
||||
},
|
||||
{
|
||||
label: 'Build',
|
||||
value: <HeaderMetadataStatus label="Failed" color="danger" href="/builds/123" />,
|
||||
},
|
||||
{
|
||||
label: 'Coverage',
|
||||
value: <HeaderMetadataStatus label="Warning" color="warning" />,
|
||||
},
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withMetadataUsers = `import { Header, HeaderMetadataUsers } from '@backstage/ui';
|
||||
|
||||
<Header
|
||||
title="Page Title"
|
||||
metadata={[
|
||||
{ label: 'Type', value: 'website' },
|
||||
{
|
||||
label: 'Owner',
|
||||
value: <HeaderMetadataUsers users={[{ name: 'Giles Peyton-Nicoll', src: '...', href: '/users/giles' }]} />,
|
||||
},
|
||||
{
|
||||
label: 'Contributors',
|
||||
value: (
|
||||
<HeaderMetadataUsers
|
||||
users={[
|
||||
{ name: 'Alice Johnson', src: '...', href: '/users/alice' },
|
||||
{ name: 'Bob Smith', src: '...', href: '/users/bob' },
|
||||
{ name: 'Carol Williams', src: '...', href: '/users/carol' },
|
||||
]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>`;
|
||||
|
||||
@@ -40,6 +40,33 @@ const skills = [
|
||||
{ value: 'swift', label: 'Swift' },
|
||||
];
|
||||
|
||||
const sectionedFonts = [
|
||||
{
|
||||
title: 'Serif Fonts',
|
||||
options: [
|
||||
{ value: 'times', label: 'Times New Roman' },
|
||||
{ value: 'georgia', label: 'Georgia' },
|
||||
{ value: 'garamond', label: 'Garamond' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sans-Serif Fonts',
|
||||
options: [
|
||||
{ value: 'arial', label: 'Arial' },
|
||||
{ value: 'helvetica', label: 'Helvetica' },
|
||||
{ value: 'verdana', label: 'Verdana' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Monospace Fonts',
|
||||
options: [
|
||||
{ value: 'courier', label: 'Courier New' },
|
||||
{ value: 'consolas', label: 'Consolas' },
|
||||
{ value: 'fira', label: 'Fira Code' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const Preview = () => (
|
||||
<Select
|
||||
label="Font Family"
|
||||
@@ -148,3 +175,23 @@ export const SearchableMultiple = () => (
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithSections = () => (
|
||||
<Select
|
||||
label="Font Family"
|
||||
options={sectionedFonts}
|
||||
name="font"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const SearchableWithSections = () => (
|
||||
<Select
|
||||
label="Font Family"
|
||||
searchable
|
||||
searchPlaceholder="Search fonts..."
|
||||
options={sectionedFonts}
|
||||
name="font"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -12,8 +12,14 @@ import {
|
||||
Searchable,
|
||||
MultipleSelection,
|
||||
SearchableMultiple,
|
||||
WithSections,
|
||||
SearchableWithSections,
|
||||
} from './components';
|
||||
import { selectPropDefs } from './props-definition';
|
||||
import {
|
||||
selectPropDefs,
|
||||
optionPropDefs,
|
||||
optionSectionPropDefs,
|
||||
} from './props-definition';
|
||||
import {
|
||||
selectUsageSnippet,
|
||||
selectDefaultSnippet,
|
||||
@@ -26,6 +32,8 @@ import {
|
||||
selectMultipleSnippet,
|
||||
selectSearchableMultipleSnippet,
|
||||
selectDisabledOptionsSnippet,
|
||||
selectSectionsSnippet,
|
||||
selectSearchableSectionsSnippet,
|
||||
} from './snippets';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
@@ -58,6 +66,18 @@ export const reactAriaUrls = {
|
||||
|
||||
<ReactAriaLink component="Select" href={reactAriaUrls.select} />
|
||||
|
||||
### Option types
|
||||
|
||||
The `options` prop accepts an array containing either of the following shapes.
|
||||
|
||||
#### `Option`
|
||||
|
||||
<PropsTable data={optionPropDefs} />
|
||||
|
||||
#### `OptionSection`
|
||||
|
||||
<PropsTable data={optionSectionPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Label and description
|
||||
@@ -136,6 +156,29 @@ Combine search and multiple selection.
|
||||
code={selectSearchableMultipleSnippet}
|
||||
/>
|
||||
|
||||
### With sections
|
||||
|
||||
Group options under section headings by passing objects with a `title` and a
|
||||
nested `options` array.
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<WithSections />}
|
||||
code={selectSectionsSnippet}
|
||||
/>
|
||||
|
||||
### Searchable with sections
|
||||
|
||||
Sections are preserved when filtering with `searchable`.
|
||||
|
||||
<Snippet
|
||||
layout="side-by-side"
|
||||
open
|
||||
preview={<SearchableWithSections />}
|
||||
code={selectSearchableSectionsSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Size can change at different breakpoints.
|
||||
|
||||
@@ -5,30 +5,48 @@ import {
|
||||
} from '@/utils/propDefs';
|
||||
import { Chip } from '@/components/Chip';
|
||||
|
||||
export const optionPropDefs: Record<string, PropDef> = {
|
||||
value: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Unique value for the option.',
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the option.',
|
||||
},
|
||||
disabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the option is disabled.',
|
||||
},
|
||||
};
|
||||
|
||||
export const optionSectionPropDefs: Record<string, PropDef> = {
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Heading displayed above the grouped options.',
|
||||
},
|
||||
options: {
|
||||
type: 'enum',
|
||||
values: ['Option[]'],
|
||||
required: true,
|
||||
description: 'Options nested inside the section.',
|
||||
},
|
||||
};
|
||||
|
||||
export const selectPropDefs: Record<string, PropDef> = {
|
||||
options: {
|
||||
type: 'complex',
|
||||
description: 'Array of options to display in the dropdown.',
|
||||
complexType: {
|
||||
name: 'SelectOption[]',
|
||||
properties: {
|
||||
value: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Unique value for the option.',
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the option.',
|
||||
},
|
||||
disabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
description: 'Whether the option is disabled.',
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'enum',
|
||||
values: ['(Option | OptionSection)[]'],
|
||||
description: (
|
||||
<>
|
||||
Options to display in the dropdown. Pass <Chip>Option</Chip> objects
|
||||
directly, or <Chip>OptionSection</Chip> objects to render grouped
|
||||
options under section headings.
|
||||
</>
|
||||
),
|
||||
},
|
||||
selectionMode: {
|
||||
type: 'enum',
|
||||
|
||||
@@ -110,3 +110,49 @@ export const selectDisabledOptionsSnippet = `<Select
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const selectSectionsSnippet = `<Select
|
||||
name="font"
|
||||
label="Font Family"
|
||||
options={[
|
||||
{
|
||||
title: 'Serif Fonts',
|
||||
options: [
|
||||
{ value: 'times', label: 'Times New Roman' },
|
||||
{ value: 'georgia', label: 'Georgia' },
|
||||
{ value: 'garamond', label: 'Garamond' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sans-Serif Fonts',
|
||||
options: [
|
||||
{ value: 'arial', label: 'Arial' },
|
||||
{ value: 'helvetica', label: 'Helvetica' },
|
||||
{ value: 'verdana', label: 'Verdana' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const selectSearchableSectionsSnippet = `<Select
|
||||
name="font"
|
||||
label="Font Family"
|
||||
searchable
|
||||
searchPlaceholder="Search fonts..."
|
||||
options={[
|
||||
{
|
||||
title: 'Serif Fonts',
|
||||
options: [
|
||||
{ value: 'times', label: 'Times New Roman' },
|
||||
{ value: 'georgia', label: 'Georgia' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sans-Serif Fonts',
|
||||
options: [
|
||||
{ value: 'arial', label: 'Arial' },
|
||||
{ value: 'helvetica', label: 'Helvetica' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>`;
|
||||
|
||||
@@ -28,7 +28,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { SliderDefinition } from '../../../utils/definitions';
|
||||
|
||||
export const reactAriaUrls = {
|
||||
slider: 'https://react-spectrum.adobe.com/react-aria/Slider.html',
|
||||
slider: 'https://react-aria.adobe.com/Slider',
|
||||
};
|
||||
|
||||
<PageTitle
|
||||
|
||||
@@ -126,9 +126,9 @@ Configure page size and available options through `paginationOptions`. The table
|
||||
|
||||
### Search
|
||||
|
||||
The `useTable` hook returns a `search` object with `value` and `onChange` properties, ready to connect to a search input. With `mode: 'complete'`, provide a `searchFn` that filters the dataset based on the search query.
|
||||
The `useTable` hook returns a `search` object with `value` and `onChange` properties, ready to connect to a search input. With `mode: 'complete'`, provide a `searchFn` that filters the dataset based on the search query. When the search query changes, pagination resets to the first page automatically.
|
||||
|
||||
The search state is debounced internally, so rapid typing doesn't trigger excessive re-filtering. When the search query changes, pagination resets to the first page automatically.
|
||||
In `complete` mode, set `searchDebounceMs` (and/or `filterDebounceMs`) to defer the filtering pipeline until typing settles — useful for large datasets. Both default to `0` (no debounce). The controlled `search` / `onSearchChange` (and `filter` / `onFilterChange`) surface continues to fire on every change. For `offset` and `cursor` modes, requests are already debounced internally, so these options don't apply.
|
||||
|
||||
For server-side search with `offset` or `cursor` modes, the search query is passed to your `getData` function. See [Server-Side Data](#server-side-data).
|
||||
|
||||
|
||||
@@ -157,6 +157,28 @@ export const useTableOptionsPropDefs: Record<string, PropDef> = {
|
||||
</>
|
||||
),
|
||||
},
|
||||
searchDebounceMs: {
|
||||
type: 'number',
|
||||
description: (
|
||||
<>
|
||||
Trailing-edge debounce delay (ms) applied to the search value before it
|
||||
reaches <Chip>searchFn</Chip>. Defaults to <Chip>0</Chip> (no debounce).
|
||||
Does not affect the controlled <Chip>onSearchChange</Chip> callback.
|
||||
Only used with <Chip>complete</Chip> mode.
|
||||
</>
|
||||
),
|
||||
},
|
||||
filterDebounceMs: {
|
||||
type: 'number',
|
||||
description: (
|
||||
<>
|
||||
Trailing-edge debounce delay (ms) applied to the filter value before it
|
||||
reaches <Chip>filterFn</Chip>. Defaults to <Chip>0</Chip> (no debounce).
|
||||
Does not affect the controlled <Chip>onFilterChange</Chip> callback.
|
||||
Only used with <Chip>complete</Chip> mode.
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const useTableReturnPropDefs: Record<string, PropDef> = {
|
||||
|
||||
@@ -264,6 +264,42 @@ pressed, and disabled variants for interactive states.
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
## Inherited background
|
||||
|
||||
`--bui-bg-inherit` resolves to the bg color of the nearest enclosing element with a
|
||||
`data-bg` attribute (set by `Box`, `Flex`, `Grid`, `Card`, `Accordion`, or any
|
||||
element that explicitly sets `data-bg`). When no such ancestor exists it falls
|
||||
back to `--bui-bg-app`. Use it from CSS when a sticky, fixed, or otherwise
|
||||
overlapping element needs to match its surrounding bg without hardcoding a level.
|
||||
|
||||
<CodeBlock
|
||||
code={`.searchBarContainer {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--bui-bg-inherit);
|
||||
}`}
|
||||
/>
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--bui-bg-inherit</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
Resolves to the bg color of the nearest enclosing `data-bg` ancestor,
|
||||
falling back to `--bui-bg-app`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
## Solid background colors
|
||||
|
||||
<Table.Root>
|
||||
|
||||
@@ -4,12 +4,18 @@ import styles from './styles.module.css';
|
||||
export const Chip = ({
|
||||
children,
|
||||
head = false,
|
||||
deprecated = false,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
head?: boolean;
|
||||
deprecated?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<span className={`${styles.chip} ${head ? styles.head : ''}`}>
|
||||
<span
|
||||
className={`${styles.chip} ${head ? styles.head : ''} ${
|
||||
deprecated ? styles.deprecated : ''
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.deprecated {
|
||||
background-color: #fff4e5;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
[data-theme-mode='dark'] .chip {
|
||||
background-color: #2c2c2c;
|
||||
color: #fff;
|
||||
@@ -22,3 +27,8 @@
|
||||
[data-theme-mode='dark'] .chip.head {
|
||||
background-color: #33405b;
|
||||
}
|
||||
|
||||
[data-theme-mode='dark'] .chip.deprecated {
|
||||
background-color: #3d2a10;
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,12 @@ export const PropsTable = <T extends Record<string, PropData>>({
|
||||
|
||||
switch (column) {
|
||||
case 'prop':
|
||||
return <Chip head>{propName}</Chip>;
|
||||
return (
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.375rem' }}>
|
||||
<Chip head>{propName}</Chip>
|
||||
{propData.deprecated && <Chip deprecated>deprecated</Chip>}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'type':
|
||||
return (
|
||||
|
||||
@@ -49,10 +49,19 @@ export const components: Page[] = [
|
||||
title: 'CheckboxGroup',
|
||||
slug: 'checkbox-group',
|
||||
},
|
||||
{
|
||||
title: 'Combobox',
|
||||
slug: 'combobox',
|
||||
status: 'new',
|
||||
},
|
||||
{
|
||||
title: 'Container',
|
||||
slug: 'container',
|
||||
},
|
||||
{
|
||||
title: 'DatePicker',
|
||||
slug: 'date-picker',
|
||||
},
|
||||
{
|
||||
title: 'DateRangePicker',
|
||||
slug: 'date-range-picker',
|
||||
|
||||
@@ -44,6 +44,7 @@ export type PropDef = {
|
||||
required?: boolean;
|
||||
responsive?: boolean;
|
||||
description?: ReactNode;
|
||||
deprecated?: boolean;
|
||||
};
|
||||
|
||||
export { breakpoints };
|
||||
|
||||
+96
-96
@@ -314,12 +314,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.1, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.5.0":
|
||||
version: 6.5.2
|
||||
resolution: "@codemirror/state@npm:6.5.2"
|
||||
"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.1, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.6.0":
|
||||
version: 6.6.0
|
||||
resolution: "@codemirror/state@npm:6.6.0"
|
||||
dependencies:
|
||||
"@marijn/find-cluster-break": "npm:^1.0.0"
|
||||
checksum: 10/5ccd3acb0c0a5b88e83fb91be39099fceb9f44a5047cc41a75d53f160e736851f65c8de40950b90c6519e6d2828e12f468db0af658dde30e938896f1c39eec91
|
||||
checksum: 10/5d624f3c8832b287d76ebb5f57c01327641875c12c58ada2a97f958dc4df8c3bb0a1ad08ed370300a4a929ee8d5c7f14a397449a0d075ac3129d60d85f077441
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -336,14 +336,14 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.34.4, @codemirror/view@npm:^6.35.0":
|
||||
version: 6.39.16
|
||||
resolution: "@codemirror/view@npm:6.39.16"
|
||||
version: 6.41.1
|
||||
resolution: "@codemirror/view@npm:6.41.1"
|
||||
dependencies:
|
||||
"@codemirror/state": "npm:^6.5.0"
|
||||
"@codemirror/state": "npm:^6.6.0"
|
||||
crelt: "npm:^1.0.6"
|
||||
style-mod: "npm:^4.1.0"
|
||||
w3c-keyname: "npm:^2.2.4"
|
||||
checksum: 10/199576febda2a91fe7676b8708627ed2e38d7e964ec8258331422fe7c7f89003eee2de7dec828e09c046de005742fd476cae6ceebc7bd994744f771253bfcbf3
|
||||
checksum: 10/ab8156db1012f94ac39d603da4c397ab1de8877f941d0cf67f79cd09fffe9f39d5de8a10611a788ce9d5a88cad2633445880955fd0dc1ad67813cbf9be97774a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -928,10 +928,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/env@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/env@npm:16.2.1"
|
||||
checksum: 10/c4f19f1767d7a1e8e9ff93cdee7e3b6a923d26d9d71f44124a797f03703ab9a18508b5ede997cc99d0307f2e0d0d1c426e9673a6c11ea10e170b87462a572236
|
||||
"@next/env@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/env@npm:16.2.3"
|
||||
checksum: 10/30ed128d8ffae47e58732ee134b78da36e2d6942da7479ec5e640d205b7822224daf2f07d7a69352dc362908eb260fc9fa7eaba1ce5e6311abeacc6ffb0fe90a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -961,58 +961,58 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-arm64@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-darwin-arm64@npm:16.2.1"
|
||||
"@next/swc-darwin-arm64@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-darwin-arm64@npm:16.2.3"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-x64@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-darwin-x64@npm:16.2.1"
|
||||
"@next/swc-darwin-x64@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-darwin-x64@npm:16.2.3"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-gnu@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:16.2.1"
|
||||
"@next/swc-linux-arm64-gnu@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:16.2.3"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-musl@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:16.2.1"
|
||||
"@next/swc-linux-arm64-musl@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:16.2.3"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-gnu@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:16.2.1"
|
||||
"@next/swc-linux-x64-gnu@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:16.2.3"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-musl@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-linux-x64-musl@npm:16.2.1"
|
||||
"@next/swc-linux-x64-musl@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-linux-x64-musl@npm:16.2.3"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-arm64-msvc@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:16.2.1"
|
||||
"@next/swc-win32-arm64-msvc@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:16.2.3"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-x64-msvc@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:16.2.1"
|
||||
"@next/swc-win32-x64-msvc@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:16.2.3"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -1175,12 +1175,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@remixicon/react@npm:^4.6.0":
|
||||
version: 4.9.0
|
||||
resolution: "@remixicon/react@npm:4.9.0"
|
||||
"@remixicon/react@npm:>=4.6.0 <4.9.0":
|
||||
version: 4.8.0
|
||||
resolution: "@remixicon/react@npm:4.8.0"
|
||||
peerDependencies:
|
||||
react: ">=18.2.0"
|
||||
checksum: 10/3d8f1d86b2bb20ab5e44d15f18811e928b0886f7710eb7a1516afb9913ba72e46facec5dfee382825139d800bcbb6704c15d0c760d0f977c12257d4af8db3295
|
||||
checksum: 10/10241f2e07826ce7d595cf9687a35c96cc9cf9eb68a9431d7dfa1b9df479dbef302874d28c0502cee2a536cf34722de7c49ed12d10a2b071e4e42ba4a278c516
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1534,9 +1534,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@uiw/codemirror-extensions-basic-setup@npm:4.25.8":
|
||||
version: 4.25.8
|
||||
resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.25.8"
|
||||
"@uiw/codemirror-extensions-basic-setup@npm:4.25.9":
|
||||
version: 4.25.9
|
||||
resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.25.9"
|
||||
dependencies:
|
||||
"@codemirror/autocomplete": "npm:^6.0.0"
|
||||
"@codemirror/commands": "npm:^6.0.0"
|
||||
@@ -1553,13 +1553,13 @@ __metadata:
|
||||
"@codemirror/search": ">=6.0.0"
|
||||
"@codemirror/state": ">=6.0.0"
|
||||
"@codemirror/view": ">=6.0.0"
|
||||
checksum: 10/a8d83465f9f3393b6e95d98ae7f3616ad57f819bce64224831d3db19647524538fc013973074a63551afa69daad9a8ab05f2e5c7441db7e30e722495d7e991d3
|
||||
checksum: 10/bab06a40bdd8fb99a0af5115511cdb812c93aac2b93ccd8a02bdf8ea06098d6be2ce1302efc560a3b577171a5cd87d34c3215e523f21450ba100b147dfcb975c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@uiw/codemirror-themes@npm:^4.23.7":
|
||||
version: 4.25.8
|
||||
resolution: "@uiw/codemirror-themes@npm:4.25.8"
|
||||
version: 4.25.9
|
||||
resolution: "@uiw/codemirror-themes@npm:4.25.9"
|
||||
dependencies:
|
||||
"@codemirror/language": "npm:^6.0.0"
|
||||
"@codemirror/state": "npm:^6.0.0"
|
||||
@@ -1568,19 +1568,19 @@ __metadata:
|
||||
"@codemirror/language": ">=6.0.0"
|
||||
"@codemirror/state": ">=6.0.0"
|
||||
"@codemirror/view": ">=6.0.0"
|
||||
checksum: 10/e9983b0f6e663ca200d36437b6b52b4061ce5ccefece6f738b15370a8a7ac6774e7139a82e9e28ae273692e25d0c0804693587ea0967e163a1c7ac8cf3859cd1
|
||||
checksum: 10/4a4fe7ae5f6c2bd37170b46c75ccabb67b47b7d3cee0de45c63fafe4f2b7569461b009e0ff5386480574e651627ed03c077833d53b6d3391102b79107ae39d15
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@uiw/react-codemirror@npm:^4.23.7":
|
||||
version: 4.25.8
|
||||
resolution: "@uiw/react-codemirror@npm:4.25.8"
|
||||
version: 4.25.9
|
||||
resolution: "@uiw/react-codemirror@npm:4.25.9"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.18.6"
|
||||
"@codemirror/commands": "npm:^6.1.0"
|
||||
"@codemirror/state": "npm:^6.1.1"
|
||||
"@codemirror/theme-one-dark": "npm:^6.0.0"
|
||||
"@uiw/codemirror-extensions-basic-setup": "npm:4.25.8"
|
||||
"@uiw/codemirror-extensions-basic-setup": "npm:4.25.9"
|
||||
codemirror: "npm:^6.0.0"
|
||||
peerDependencies:
|
||||
"@babel/runtime": ">=7.11.0"
|
||||
@@ -1590,7 +1590,7 @@ __metadata:
|
||||
codemirror: ">=6.0.0"
|
||||
react: ">=17.0.0"
|
||||
react-dom: ">=17.0.0"
|
||||
checksum: 10/8c974e22dad1ad6231f33f7db42cd5b68caaf9bea545539b06b8a89dda3427eebadf47c8f48ee0d74cdf5a25000a8fcc02bac9fe560b624955eedf1f9bb47a85
|
||||
checksum: 10/02c6ababa9307cf10aee5b32db1a0e5885485960819d708cd154bc218cf4d8b182b5fb49fa7386014401d71c8391ce211b29b2de201ad1fdece2c1716d09a74d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2338,7 +2338,7 @@ __metadata:
|
||||
"@mdx-js/react": "npm:^3.1.0"
|
||||
"@next/mdx": "npm:16.2.1"
|
||||
"@octokit/rest": "npm:^22.0.1"
|
||||
"@remixicon/react": "npm:^4.6.0"
|
||||
"@remixicon/react": "npm:>=4.6.0 <4.9.0"
|
||||
"@shikijs/transformers": "npm:^3.13.0"
|
||||
"@types/mdx": "npm:^2.0.13"
|
||||
"@types/node": "npm:^22.13.14"
|
||||
@@ -2351,9 +2351,9 @@ __metadata:
|
||||
eslint-config-next: "npm:16.2.1"
|
||||
html-react-parser: "npm:^5.2.5"
|
||||
motion: "npm:^12.4.1"
|
||||
next: "npm:16.2.1"
|
||||
next: "npm:16.2.3"
|
||||
next-mdx-remote-client: "npm:^2.1.2"
|
||||
postcss: "npm:^8.5.6"
|
||||
postcss: "npm:^8.5.10"
|
||||
postcss-import: "npm:^16.1.1"
|
||||
prop-types: "npm:^15.8.1"
|
||||
react: "npm:19.2.4"
|
||||
@@ -3103,12 +3103,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"framer-motion@npm:^12.35.2":
|
||||
version: 12.35.2
|
||||
resolution: "framer-motion@npm:12.35.2"
|
||||
"framer-motion@npm:^12.38.0":
|
||||
version: 12.38.0
|
||||
resolution: "framer-motion@npm:12.38.0"
|
||||
dependencies:
|
||||
motion-dom: "npm:^12.35.2"
|
||||
motion-utils: "npm:^12.29.2"
|
||||
motion-dom: "npm:^12.38.0"
|
||||
motion-utils: "npm:^12.36.0"
|
||||
tslib: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
"@emotion/is-prop-valid": "*"
|
||||
@@ -3121,7 +3121,7 @@ __metadata:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 10/10af699ff1e35a166ef60ceab464479b81624ef74de3ec9e11b427f86bd7bf2c8c8a9f24fb0646288b2d4b0c1b219203da351821fc568c7b91c6821594af4a3f
|
||||
checksum: 10/4d529d1648a8e31ec9859e7ff1296b7e4ef0028eb09cbc7d626068766ab53e486038b431fac33b1438a1cc076a244e6843c5a8c0f38442885832308452b4b25e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4500,27 +4500,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"motion-dom@npm:^12.35.2":
|
||||
version: 12.35.2
|
||||
resolution: "motion-dom@npm:12.35.2"
|
||||
"motion-dom@npm:^12.38.0":
|
||||
version: 12.38.0
|
||||
resolution: "motion-dom@npm:12.38.0"
|
||||
dependencies:
|
||||
motion-utils: "npm:^12.29.2"
|
||||
checksum: 10/dd009e58b178dd80b123a86199ae78ecd6b2fc6c8e03464b2daf43b4218dfcc36042ec0af8fad2c6c157198f56849f90dc033b58f46478b45fbaeaefcc2710ad
|
||||
motion-utils: "npm:^12.36.0"
|
||||
checksum: 10/78c040b46d93273932cf80c70e39845be5a442dcaf18d4345b45a9193de9dfa87c885b609943cb652115e4eac5d46ef40b452185073dd43fc328b134f9975e90
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"motion-utils@npm:^12.29.2":
|
||||
version: 12.29.2
|
||||
resolution: "motion-utils@npm:12.29.2"
|
||||
checksum: 10/ae5f9be58c07939af72334894ed1a18653d724946182a718dc3d11268ef26e63804c3f16dee5a6110596d4406b539c4513822b74f86adebef9488601c34b18b7
|
||||
"motion-utils@npm:^12.36.0":
|
||||
version: 12.36.0
|
||||
resolution: "motion-utils@npm:12.36.0"
|
||||
checksum: 10/c4a2a7ffac48ca44082d6d31b115f245025060a7e69d70dac062646d8f96c39e5662a7c8a51f255566fdf8e719ef1269a8e9aa3a04fc263bb65b5a7b61331901
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"motion@npm:^12.4.1":
|
||||
version: 12.35.2
|
||||
resolution: "motion@npm:12.35.2"
|
||||
version: 12.38.0
|
||||
resolution: "motion@npm:12.38.0"
|
||||
dependencies:
|
||||
framer-motion: "npm:^12.35.2"
|
||||
framer-motion: "npm:^12.38.0"
|
||||
tslib: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
"@emotion/is-prop-valid": "*"
|
||||
@@ -4533,7 +4533,7 @@ __metadata:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
checksum: 10/3d99a53816634cbee1b38ed8a9a5d88bafbd29eb3bc02e78fc741c604972b4b88d317cf374bba30a1486f727bb1657ef8826f83e669a3b04fd1ec3ef75bfb62d
|
||||
checksum: 10/d7ae2ba3cc112c4467822956b92065239640b9c62204d3bee1780da9fc0147185373534138d39975e82bf73b5f1b28d3fb3581031e4e7e0cfb230472767bd10d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4587,19 +4587,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "next@npm:16.2.1"
|
||||
"next@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "next@npm:16.2.3"
|
||||
dependencies:
|
||||
"@next/env": "npm:16.2.1"
|
||||
"@next/swc-darwin-arm64": "npm:16.2.1"
|
||||
"@next/swc-darwin-x64": "npm:16.2.1"
|
||||
"@next/swc-linux-arm64-gnu": "npm:16.2.1"
|
||||
"@next/swc-linux-arm64-musl": "npm:16.2.1"
|
||||
"@next/swc-linux-x64-gnu": "npm:16.2.1"
|
||||
"@next/swc-linux-x64-musl": "npm:16.2.1"
|
||||
"@next/swc-win32-arm64-msvc": "npm:16.2.1"
|
||||
"@next/swc-win32-x64-msvc": "npm:16.2.1"
|
||||
"@next/env": "npm:16.2.3"
|
||||
"@next/swc-darwin-arm64": "npm:16.2.3"
|
||||
"@next/swc-darwin-x64": "npm:16.2.3"
|
||||
"@next/swc-linux-arm64-gnu": "npm:16.2.3"
|
||||
"@next/swc-linux-arm64-musl": "npm:16.2.3"
|
||||
"@next/swc-linux-x64-gnu": "npm:16.2.3"
|
||||
"@next/swc-linux-x64-musl": "npm:16.2.3"
|
||||
"@next/swc-win32-arm64-msvc": "npm:16.2.3"
|
||||
"@next/swc-win32-x64-msvc": "npm:16.2.3"
|
||||
"@swc/helpers": "npm:0.5.15"
|
||||
baseline-browser-mapping: "npm:^2.9.19"
|
||||
caniuse-lite: "npm:^1.0.30001579"
|
||||
@@ -4643,7 +4643,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
next: dist/bin/next
|
||||
checksum: 10/319c0b18173a90e53b5e5ffafa8a8fecb7cc340b77728796743edd996c7ee7652201892bff60c32f6a3b75abdff1449b77f13f6fab8fd56d4f9da47cf0fb9299
|
||||
checksum: 10/5164885daacbb36a771380e1b5efba524863e1bdf2b5a6c80413cbf1e3ab4e8ddab5716cd91ff94ca5c5c5deb2a12d1312d6d6ae994e16ebfa985fdda6134bc6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4857,9 +4857,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"picomatch@npm:^2.3.1":
|
||||
version: 2.3.1
|
||||
resolution: "picomatch@npm:2.3.1"
|
||||
checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc
|
||||
version: 2.3.2
|
||||
resolution: "picomatch@npm:2.3.2"
|
||||
checksum: 10/b788ef8148a2415b9dec12f0bb350ae6a5830f8f1950e472abc2f5225494debf7d1b75eb031df0ceaea9e8ec3e7bad599e8dbf3c60d61b42be429ba41bff4426
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4915,14 +4915,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss@npm:^8.5.6":
|
||||
version: 8.5.8
|
||||
resolution: "postcss@npm:8.5.8"
|
||||
"postcss@npm:^8.5.10":
|
||||
version: 8.5.10
|
||||
resolution: "postcss@npm:8.5.10"
|
||||
dependencies:
|
||||
nanoid: "npm:^3.3.11"
|
||||
picocolors: "npm:^1.1.1"
|
||||
source-map-js: "npm:^1.2.1"
|
||||
checksum: 10/cbacbfd7f767e2c820d4bf09a3a744834dd7d14f69ff08d1f57b1a7defce9ae5efcf31981890d9697a972a64e9965de677932ef28e4c8ba23a87aad45b82c459
|
||||
checksum: 10/7eac6169e535b63c8412e94d4f6047fc23efa3e9dde804b541940043c831b25f1cd867d83cd2c4371ad2450c8abcb42c208aa25668c1f0f3650d7f72faf711a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6241,11 +6241,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"yaml@npm:^2.0.0":
|
||||
version: 2.8.1
|
||||
resolution: "yaml@npm:2.8.1"
|
||||
version: 2.8.3
|
||||
resolution: "yaml@npm:2.8.3"
|
||||
bin:
|
||||
yaml: bin.mjs
|
||||
checksum: 10/eae07b3947d405012672ec17ce27348aea7d1fa0534143355d24a43a58f5e05652157ea2182c4fe0604f0540be71f99f1173f9d61018379404507790dff17665
|
||||
checksum: 10/ecad41d39d34fae5cc17ea2d4b7f7f55faacd45cbce8983ba22d48d1ed1a92ed242ea49ea813a79ac39a69f75f9c5a03e7b5395fd954d55476f25e21a47c141d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ Even with feature discovery enabled, you can disable specific extensions via con
|
||||
app:
|
||||
extensions:
|
||||
- page:techdocs: false
|
||||
- nav-item:search: false
|
||||
- page:search: false
|
||||
```
|
||||
|
||||
### How Discovery Works with Manual Imports
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
"name": "plugin-full-frontend-system-migration",
|
||||
"description": "Fully migrate a Backstage plugin to the new frontend system, dropping all old system support. Use this skill for internal plugins that only need to run in a single app, or when you are ready to remove backward compatibility entirely.",
|
||||
"files": ["SKILL.md"]
|
||||
},
|
||||
{
|
||||
"name": "plugin-analytics-instrumentation",
|
||||
"description": "Instrument a Backstage frontend plugin with analytics events using the Backstage Analytics API. Use this skill when adding, reviewing, or extending event capture (captureEvent, AnalyticsContext) in plugin components, deciding whether an interaction warrants an event, or writing tests for analytics behavior.",
|
||||
"files": ["SKILL.md"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ description: Migrate Backstage plugins from Material-UI (MUI) to Backstage UI (B
|
||||
|
||||
# MUI to BUI Migration Skill
|
||||
|
||||
This skill helps migrate Backstage plugins from Material-UI (@material-ui/core, @material-ui/icons) to Backstage UI (
|
||||
@backstage/ui).
|
||||
This skill helps migrate Backstage plugins from Material-UI (@material-ui/core, @material-ui/icons) to
|
||||
Backstage UI (@backstage/ui).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -19,6 +19,7 @@ Before starting migration:
|
||||
```
|
||||
|
||||
2. Add the CSS import to your root file (typically `src/index.ts` or app entry point):
|
||||
|
||||
```typescript
|
||||
import '@backstage/ui/css/styles.css';
|
||||
```
|
||||
@@ -38,11 +39,14 @@ Before starting migration:
|
||||
- `Accordion` - Collapsible content panels (`Accordion`, `AccordionTrigger`, `AccordionPanel`, `AccordionGroup`)
|
||||
- `Alert` - Alert/notification banners (`status`, `title`, `description`)
|
||||
- `Avatar` - User/entity avatars
|
||||
- `Badge` - Inline badge/label with optional icon (`size`, `icon`)
|
||||
- `Button` - Action buttons (`variant="primary"`, `variant="secondary"`, `variant="tertiary"`, `isDisabled`, `destructive`, `loading`)
|
||||
- `ButtonIcon` - Icon-only buttons (`icon`, `onPress`, `variant`)
|
||||
- `ButtonLink` - Link styled as button
|
||||
- `Card` - Content cards (`Card`, `CardHeader`, `CardBody`, `CardFooter`)
|
||||
- `Checkbox` - Checkbox input
|
||||
- `CheckboxGroup` - Grouped checkboxes with shared label (`label`, `orientation`, `isRequired`)
|
||||
- `DateRangePicker` - Date range input field (`label`, `value`, `onChange`)
|
||||
- `Dialog` - Modal dialogs (`DialogTrigger`, `Dialog`, `DialogHeader`, `DialogBody`, `DialogFooter`)
|
||||
- `FieldLabel` - Form field label with description and secondary label
|
||||
- `Header` - Page headers with breadcrumbs and tabs
|
||||
@@ -57,6 +61,7 @@ Before starting migration:
|
||||
- `SearchField` - Search input
|
||||
- `Select` - Dropdown select (single and multiple selection modes)
|
||||
- `Skeleton` - Loading skeleton
|
||||
- `Slider` - Range slider input (`label`, `minValue`, `maxValue`, `step`)
|
||||
- `Switch` - Toggle switch
|
||||
- `Table` - Data tables (with `useTable` hook for data management)
|
||||
- `TablePagination` - Standalone pagination component
|
||||
@@ -103,9 +108,9 @@ Create a `.module.css` file alongside your component using BUI CSS variables.
|
||||
|
||||
**Before (MUI `makeStyles`):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
// MyComponent.tsx
|
||||
import {makeStyles, Theme} from '@material-ui/core/styles';
|
||||
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
container: {
|
||||
@@ -130,18 +135,16 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||
function MyComponent() {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<div className = {classes.container} >
|
||||
<Typography className = {classes.title} > Title < /Typography>
|
||||
< div
|
||||
className = {classes.listItem} >
|
||||
<div className = {classes.icon} >
|
||||
<SomeIcon / >
|
||||
<div className={classes.container}>
|
||||
<Typography className={classes.title}>Title</Typography>
|
||||
<div className={classes.listItem}>
|
||||
<div className={classes.icon}>
|
||||
<SomeIcon />
|
||||
</div>
|
||||
<span>Content</span>
|
||||
</div>
|
||||
</div>
|
||||
< span > Content < /span>
|
||||
< /div>
|
||||
< /div>
|
||||
)
|
||||
;
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -177,27 +180,24 @@ function MyComponent() {
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
// MyComponent.tsx
|
||||
import {Box, Text} from '@backstage/ui';
|
||||
import {RiSomeIcon} from '@remixicon/react';
|
||||
import { Box, Text } from '@backstage/ui';
|
||||
import { RiSomeIcon } from '@remixicon/react';
|
||||
import styles from './MyComponent.module.css';
|
||||
|
||||
function MyComponent() {
|
||||
return (
|
||||
<Box className = {styles.container} >
|
||||
<Text className = {styles.title} > Title < /Text>
|
||||
< div
|
||||
className = {styles.listItem} >
|
||||
<div className = {styles.icon} >
|
||||
<RiSomeIcon size = {24}
|
||||
/>
|
||||
< /div>
|
||||
< span > Content < /span>
|
||||
< /div>
|
||||
< /Box>
|
||||
)
|
||||
;
|
||||
<Box className={styles.container}>
|
||||
<Text className={styles.title}>Title</Text>
|
||||
<div className={styles.listItem}>
|
||||
<div className={styles.icon}>
|
||||
<RiSomeIcon size={24} />
|
||||
</div>
|
||||
<span>Content</span>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -205,38 +205,27 @@ function MyComponent() {
|
||||
|
||||
**Before (MUI Box with display prop):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
<Box
|
||||
display = "flex"
|
||||
flexDirection = "column"
|
||||
alignItems = "center"
|
||||
justifyContent = "space-between"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Box display = "flex"
|
||||
flexDirection = "row"
|
||||
gap = {2} >
|
||||
{children}
|
||||
< /Box>
|
||||
< /Box>
|
||||
<Box display="flex" flexDirection="row" gap={2}>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
```
|
||||
|
||||
**After (BUI `Flex` component):**
|
||||
|
||||
```typescript
|
||||
<Flex direction = "column"
|
||||
align = "center"
|
||||
justify = "between" >
|
||||
<Flex direction = "row"
|
||||
style = {
|
||||
{
|
||||
gap: 'var(--bui-space-4)'
|
||||
}
|
||||
}>
|
||||
{
|
||||
children
|
||||
}
|
||||
```tsx
|
||||
<Flex direction="column" align="center" justify="between">
|
||||
<Flex direction="row" style={{ gap: 'var(--bui-space-4)' }}>
|
||||
{children}
|
||||
</Flex>
|
||||
</Flex>
|
||||
< /Flex>
|
||||
```
|
||||
|
||||
Note: BUI `Flex` uses `justify="between"` not `justify="space-between"`.
|
||||
@@ -245,70 +234,40 @@ Note: BUI `Flex` uses `justify="between"` not `justify="space-between"`.
|
||||
|
||||
**Before (MUI Grid):**
|
||||
|
||||
```typescript
|
||||
<Grid container
|
||||
spacing = {3} >
|
||||
<Grid item
|
||||
xs = {12}
|
||||
md = {6} >
|
||||
{content}
|
||||
< /Grid>
|
||||
< /Grid>
|
||||
```tsx
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
{content}
|
||||
</Grid>
|
||||
</Grid>
|
||||
```
|
||||
|
||||
**After (BUI Grid):**
|
||||
|
||||
```typescript
|
||||
<Grid.Root columns = {
|
||||
{
|
||||
sm: '12'
|
||||
}
|
||||
}
|
||||
gap = "6" >
|
||||
<Grid.Item colSpan = {
|
||||
{
|
||||
sm: '12', md
|
||||
:
|
||||
'6'
|
||||
}
|
||||
}>
|
||||
{
|
||||
content
|
||||
}
|
||||
</Grid.Item>
|
||||
< /Grid.Root>
|
||||
```tsx
|
||||
<Grid.Root columns={{ sm: '12' }} gap="6">
|
||||
<Grid.Item colSpan={{ sm: '12', md: '6' }}>{content}</Grid.Item>
|
||||
</Grid.Root>
|
||||
```
|
||||
|
||||
### 5. Typography to Text
|
||||
|
||||
**Before (MUI Typography):**
|
||||
|
||||
```typescript
|
||||
<Typography variant = "h1" > Heading < /Typography>
|
||||
< Typography
|
||||
variant = "h6" > Subheading < /Typography>
|
||||
< Typography
|
||||
variant = "body1" > Body
|
||||
text < /Typography>
|
||||
< Typography
|
||||
variant = "body2"
|
||||
color = "textSecondary" > Secondary
|
||||
text < /Typography>
|
||||
```tsx
|
||||
<Typography variant="h1">Heading</Typography>
|
||||
<Typography variant="h6">Subheading</Typography>
|
||||
<Typography variant="body1">Body text</Typography>
|
||||
<Typography variant="body2" color="textSecondary">Secondary text</Typography>
|
||||
```
|
||||
|
||||
**After (BUI Text):**
|
||||
|
||||
```typescript
|
||||
<Text variant = "title-large" > Heading < /Text>
|
||||
< Text
|
||||
variant = "title-small" > Subheading < /Text>
|
||||
< Text
|
||||
variant = "body-medium" > Body
|
||||
text < /Text>
|
||||
< Text
|
||||
variant = "body-small"
|
||||
color = "secondary" > Secondary
|
||||
text < /Text>
|
||||
```tsx
|
||||
<Text variant="title-large">Heading</Text>
|
||||
<Text variant="title-small">Subheading</Text>
|
||||
<Text variant="body-medium">Body text</Text>
|
||||
<Text variant="body-small" color="secondary">Secondary text</Text>
|
||||
```
|
||||
|
||||
Valid Text variants: `title-large`, `title-medium`, `title-small`, `title-x-small`, `body-large`, `body-medium`,
|
||||
@@ -318,19 +277,17 @@ Valid Text variants: `title-large`, `title-medium`, `title-small`, `title-x-smal
|
||||
|
||||
**Before (MUI Tooltip):**
|
||||
|
||||
```typescript
|
||||
import {Tooltip, Typography} from '@material-ui/core';
|
||||
```tsx
|
||||
import { Tooltip, Typography } from '@material-ui/core';
|
||||
|
||||
<Tooltip title = { < Typography > Tooltip
|
||||
content < /Typography>}>
|
||||
< span > Hover
|
||||
me < /span>
|
||||
< /Tooltip>;
|
||||
<Tooltip title={<Typography>Tooltip content</Typography>}>
|
||||
<span>Hover me</span>
|
||||
</Tooltip>;
|
||||
```
|
||||
|
||||
**After (BUI TooltipTrigger pattern):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import { Tooltip, TooltipTrigger, Text } from '@backstage/ui';
|
||||
|
||||
<TooltipTrigger>
|
||||
@@ -343,26 +300,23 @@ import { Tooltip, TooltipTrigger, Text } from '@backstage/ui';
|
||||
|
||||
**Before (MUI Dialog):**
|
||||
|
||||
```typescript
|
||||
import {Dialog, DialogTitle, DialogActions, Button} from '@material-ui/core';
|
||||
```tsx
|
||||
import { Dialog, DialogTitle, DialogActions, Button } from '@material-ui/core';
|
||||
|
||||
<Dialog open = {isOpen}
|
||||
onClose = {onClose} >
|
||||
<DialogTitle>Title < /DialogTitle>
|
||||
< DialogActions >
|
||||
<Button onClick = {onClose} > Cancel < /Button>
|
||||
< Button
|
||||
onClick = {onConfirm}
|
||||
color = "primary" >
|
||||
Confirm
|
||||
< /Button>
|
||||
< /DialogActions>
|
||||
< /Dialog>;
|
||||
<Dialog open={isOpen} onClose={onClose}>
|
||||
<DialogTitle>Title</DialogTitle>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={onConfirm} color="primary">
|
||||
Confirm
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>;
|
||||
```
|
||||
|
||||
**After (BUI Dialog):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
@@ -373,78 +327,58 @@ import {
|
||||
|
||||
<DialogTrigger>
|
||||
<Dialog
|
||||
isOpen = {isOpen}
|
||||
isDismissable
|
||||
onOpenChange = {open
|
||||
=>
|
||||
{
|
||||
if (!open) onClose();
|
||||
}
|
||||
}
|
||||
>
|
||||
<DialogHeader>Title < /DialogHeader>
|
||||
< DialogFooter >
|
||||
<Button onClick = {onConfirm}
|
||||
variant = "primary" >
|
||||
Confirm
|
||||
< /Button>
|
||||
< Button
|
||||
onClick = {onClose}
|
||||
variant = "secondary"
|
||||
slot = "close" >
|
||||
Cancel
|
||||
< /Button>
|
||||
< /DialogFooter>
|
||||
< /Dialog>
|
||||
< /DialogTrigger>;
|
||||
isOpen={isOpen}
|
||||
isDismissable
|
||||
onOpenChange={open => {
|
||||
if (!open) onClose();
|
||||
}}
|
||||
>
|
||||
<DialogHeader>Title</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button onClick={onConfirm} variant="primary">
|
||||
Confirm
|
||||
</Button>
|
||||
<Button onClick={onClose} variant="secondary" slot="close">
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</DialogTrigger>;
|
||||
```
|
||||
|
||||
### 8. Button Changes
|
||||
|
||||
**Before (MUI Button):**
|
||||
|
||||
```typescript
|
||||
<Button variant = "contained"
|
||||
color = "primary"
|
||||
disabled = {loading}
|
||||
onClick = {handleClick} >
|
||||
```tsx
|
||||
<Button variant="contained" color="primary" disabled={loading} onClick={handleClick}>
|
||||
Submit
|
||||
< /Button>
|
||||
< IconButton
|
||||
onClick = {handleDelete}
|
||||
disabled = {!
|
||||
canDelete
|
||||
}>
|
||||
<DeleteIcon / >
|
||||
</Button>
|
||||
<IconButton onClick={handleDelete} disabled={!canDelete}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
```
|
||||
|
||||
**After (BUI Button):**
|
||||
|
||||
```typescript
|
||||
<Button variant = "primary"
|
||||
isDisabled = {loading}
|
||||
onClick = {handleClick} >
|
||||
```tsx
|
||||
<Button variant="primary" isDisabled={loading} onClick={handleClick}>
|
||||
Submit
|
||||
< /Button>
|
||||
< ButtonIcon
|
||||
aria - label = "delete"
|
||||
isDisabled = {!
|
||||
canDelete
|
||||
}
|
||||
onPress = {handleDelete}
|
||||
icon = { < RiDeleteBinLine
|
||||
size = {16}
|
||||
/>}
|
||||
variant = "secondary"
|
||||
/ >
|
||||
</Button>
|
||||
<ButtonIcon
|
||||
aria-label="delete"
|
||||
isDisabled={!canDelete}
|
||||
onPress={handleDelete}
|
||||
icon={<RiDeleteBinLine size={16} />}
|
||||
variant="secondary"
|
||||
/>
|
||||
```
|
||||
|
||||
### 9. TextField Changes
|
||||
|
||||
**Before (MUI TextField):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
<TextField
|
||||
required
|
||||
name="title"
|
||||
@@ -457,7 +391,7 @@ variant = "secondary"
|
||||
|
||||
**After (BUI TextField):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
<TextField
|
||||
isRequired
|
||||
id="title"
|
||||
@@ -473,89 +407,72 @@ Note: BUI TextField `onChange` receives the string value directly, not an event
|
||||
|
||||
**Before (MUI Tabs):**
|
||||
|
||||
```typescript
|
||||
import {Tab} from '@material-ui/core';
|
||||
import {TabContext, TabList, TabPanel} from '@material-ui/lab';
|
||||
```tsx
|
||||
import { Tab } from '@material-ui/core';
|
||||
import { TabContext, TabList, TabPanel } from '@material-ui/lab';
|
||||
|
||||
<TabContext value = {tab} >
|
||||
<TabList onChange = {handleChange} >
|
||||
<Tab label = "Tab 1"
|
||||
value = "tab1" / >
|
||||
<Tab label = "Tab 2"
|
||||
value = "tab2" / >
|
||||
<TabContext value={tab}>
|
||||
<TabList onChange={handleChange}>
|
||||
<Tab label="Tab 1" value="tab1" />
|
||||
<Tab label="Tab 2" value="tab2" />
|
||||
</TabList>
|
||||
< TabPanel
|
||||
value = "tab1" > Content
|
||||
1 < /TabPanel>
|
||||
< TabPanel
|
||||
value = "tab2" > Content
|
||||
2 < /TabPanel>
|
||||
< /TabContext>;
|
||||
<TabPanel value="tab1">Content 1</TabPanel>
|
||||
<TabPanel value="tab2">Content 2</TabPanel>
|
||||
</TabContext>;
|
||||
```
|
||||
|
||||
**After (BUI Tabs):**
|
||||
|
||||
```typescript
|
||||
import {Tabs, TabList, Tab, TabPanel} from '@backstage/ui';
|
||||
```tsx
|
||||
import { Tabs, TabList, Tab, TabPanel } from '@backstage/ui';
|
||||
|
||||
<Tabs defaultSelectedKey = "tab1" >
|
||||
<TabList>
|
||||
<Tab id = "tab1" > Tab
|
||||
1 < /Tab>
|
||||
< Tab
|
||||
id = "tab2" > Tab
|
||||
2 < /Tab>
|
||||
< /TabList>
|
||||
< TabPanel
|
||||
id = "tab1" > Content
|
||||
1 < /TabPanel>
|
||||
< TabPanel
|
||||
id = "tab2" > Content
|
||||
2 < /TabPanel>
|
||||
< /Tabs>;
|
||||
<Tabs defaultSelectedKey="tab1">
|
||||
<TabList>
|
||||
<Tab id="tab1">Tab 1</Tab>
|
||||
<Tab id="tab2">Tab 2</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="tab1">Content 1</TabPanel>
|
||||
<TabPanel id="tab2">Content 2</TabPanel>
|
||||
</Tabs>;
|
||||
```
|
||||
|
||||
### 11. Menu Pattern
|
||||
|
||||
**Before (MUI Menu):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import {IconButton, Popover, MenuList, MenuItem} from '@material-ui/core';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
|
||||
<IconButton onClick = {handleOpen} > <MoreVertIcon / > </IconButton>
|
||||
< Popover
|
||||
open = {open}
|
||||
anchorEl = {anchorEl}
|
||||
onClose = {handleClose} >
|
||||
<MenuList>
|
||||
<MenuItem onClick = {handleAction} > Action < /MenuItem>
|
||||
< /MenuList>
|
||||
< /Popover>
|
||||
<IconButton onClick={handleOpen}>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<Popover open={open} anchorEl={anchorEl} onClose={handleClose}>
|
||||
<MenuList>
|
||||
<MenuItem onClick={handleAction}>Action</MenuItem>
|
||||
</MenuList>
|
||||
</Popover>
|
||||
```
|
||||
|
||||
**After (BUI Menu):**
|
||||
|
||||
```typescript
|
||||
import {ButtonIcon, Menu, MenuItem, MenuTrigger} from '@backstage/ui';
|
||||
import {RiMore2Line} from '@remixicon/react';
|
||||
```tsx
|
||||
import { ButtonIcon, Menu, MenuItem, MenuTrigger } from '@backstage/ui';
|
||||
import { RiMore2Line } from '@remixicon/react';
|
||||
|
||||
<MenuTrigger>
|
||||
<ButtonIcon aria - label = "more"
|
||||
icon = { < RiMore2Line / >
|
||||
}
|
||||
variant = "secondary" / >
|
||||
<Menu>
|
||||
<MenuItem onAction = {handleAction} > Action < /MenuItem>
|
||||
< /Menu>
|
||||
< /MenuTrigger>;
|
||||
<ButtonIcon aria-label="more" icon={<RiMore2Line />} variant="secondary" />
|
||||
<Menu>
|
||||
<MenuItem onAction={handleAction}>Action</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>;
|
||||
```
|
||||
|
||||
### 12. List to BUI List
|
||||
|
||||
**Before (MUI List):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import { List, ListItem, ListItemIcon, ListItemText } from '@material-ui/core';
|
||||
|
||||
<List>
|
||||
@@ -570,7 +487,7 @@ import { List, ListItem, ListItemIcon, ListItemText } from '@material-ui/core';
|
||||
|
||||
**After (BUI List):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import { List, ListRow } from '@backstage/ui';
|
||||
import { RiSomeIcon } from '@remixicon/react';
|
||||
|
||||
@@ -587,7 +504,7 @@ Note: `ListRow` supports `icon`, `description`, `menuItems`, and `customActions`
|
||||
|
||||
**Before (MUI Chip):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import { Chip } from '@material-ui/core';
|
||||
|
||||
<Chip label="Category" size="small" />;
|
||||
@@ -595,17 +512,17 @@ import { Chip } from '@material-ui/core';
|
||||
|
||||
**After (BUI Tag):**
|
||||
|
||||
```typescript
|
||||
import {Tag} from '@backstage/ui';
|
||||
```tsx
|
||||
import { Tag } from '@backstage/ui';
|
||||
|
||||
<Tag size = "small" > Category < /Tag>;
|
||||
<Tag size="small">Category</Tag>;
|
||||
```
|
||||
|
||||
### 14. Alert Pattern
|
||||
|
||||
**Before (MUI Alert):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import { Alert, AlertTitle } from '@material-ui/lab';
|
||||
|
||||
<Alert severity="error">
|
||||
@@ -616,7 +533,7 @@ import { Alert, AlertTitle } from '@material-ui/lab';
|
||||
|
||||
**After (BUI Alert):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import { Alert } from '@backstage/ui';
|
||||
|
||||
<Alert
|
||||
@@ -637,22 +554,21 @@ Use `loading` for a loading spinner, and `customActions` for action buttons.
|
||||
|
||||
**Before (MUI Icons):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
|
||||
<CloseIcon / >
|
||||
<SearchIcon fontSize = "small" / >
|
||||
<CloseIcon />
|
||||
<SearchIcon fontSize="small" />
|
||||
```
|
||||
|
||||
**After (Remix Icons):**
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import {RiCloseLine, RiSearchLine} from '@remixicon/react';
|
||||
|
||||
<RiCloseLine / >
|
||||
<RiSearchLine size = {16}
|
||||
/>
|
||||
<RiCloseLine />
|
||||
<RiSearchLine size={16} />
|
||||
```
|
||||
|
||||
Common icon mappings:
|
||||
@@ -683,6 +599,239 @@ Common icon mappings:
|
||||
|
||||
Find more icons at: https://remixicon.com/
|
||||
|
||||
### 16. Paper to Card
|
||||
|
||||
**Before (MUI Paper):**
|
||||
|
||||
```tsx
|
||||
import { Paper, Typography } from '@material-ui/core';
|
||||
|
||||
<Paper elevation={2}>
|
||||
<Typography variant="h6">Title</Typography>
|
||||
<Typography>Body content</Typography>
|
||||
</Paper>;
|
||||
```
|
||||
|
||||
**After (BUI Card):**
|
||||
|
||||
```tsx
|
||||
import { Card, CardHeader, CardBody, Text } from '@backstage/ui';
|
||||
|
||||
<Card>
|
||||
<CardHeader>Title</CardHeader>
|
||||
<CardBody>
|
||||
<Text>Body content</Text>
|
||||
</CardBody>
|
||||
</Card>;
|
||||
```
|
||||
|
||||
### 17. Select
|
||||
|
||||
**Before (MUI Select):**
|
||||
|
||||
```tsx
|
||||
import { FormControl, InputLabel, Select, MenuItem } from '@material-ui/core';
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Framework</InputLabel>
|
||||
<Select value={value} onChange={e => setValue(e.target.value as string)}>
|
||||
<MenuItem value="react">React</MenuItem>
|
||||
<MenuItem value="angular">Angular</MenuItem>
|
||||
</Select>
|
||||
</FormControl>;
|
||||
```
|
||||
|
||||
**After (BUI Select):**
|
||||
|
||||
```tsx
|
||||
import { Select } from '@backstage/ui';
|
||||
|
||||
<Select
|
||||
label="Framework"
|
||||
selectedKey={value}
|
||||
onSelectionChange={key => setValue(key as string)}
|
||||
options={[
|
||||
{ value: 'react', label: 'React' },
|
||||
{ value: 'angular', label: 'Angular' },
|
||||
]}
|
||||
/>;
|
||||
```
|
||||
|
||||
Note: BUI `Select` accepts flat `options` arrays or grouped `OptionSection` arrays. Pass `multiple` for multi-select.
|
||||
|
||||
### 18. Accordion
|
||||
|
||||
**Before (MUI Accordion):**
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
} from '@material-ui/core';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
Section title
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>Content goes here</AccordionDetails>
|
||||
</Accordion>;
|
||||
```
|
||||
|
||||
**After (BUI Accordion):**
|
||||
|
||||
```tsx
|
||||
import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui';
|
||||
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Section title" />
|
||||
<AccordionPanel>Content goes here</AccordionPanel>
|
||||
</Accordion>;
|
||||
```
|
||||
|
||||
Use `AccordionGroup` to wrap multiple `Accordion` items and control whether multiple panels can be open simultaneously.
|
||||
|
||||
### 19. RadioGroup
|
||||
|
||||
**Before (MUI RadioGroup):**
|
||||
|
||||
```tsx
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
} from '@material-ui/core';
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>Frequency</FormLabel>
|
||||
<RadioGroup value={value} onChange={e => setValue(e.target.value)}>
|
||||
<FormControlLabel value="daily" control={<Radio />} label="Daily" />
|
||||
<FormControlLabel value="weekly" control={<Radio />} label="Weekly" />
|
||||
</RadioGroup>
|
||||
</FormControl>;
|
||||
```
|
||||
|
||||
**After (BUI RadioGroup):**
|
||||
|
||||
```tsx
|
||||
import { RadioGroup, Radio } from '@backstage/ui';
|
||||
|
||||
<RadioGroup label="Frequency" value={value} onChange={setValue}>
|
||||
<Radio value="daily">Daily</Radio>
|
||||
<Radio value="weekly">Weekly</Radio>
|
||||
</RadioGroup>;
|
||||
```
|
||||
|
||||
### 20. Badge
|
||||
|
||||
**Before (MUI Badge):**
|
||||
|
||||
```tsx
|
||||
import { Badge } from '@material-ui/core';
|
||||
|
||||
<Badge badgeContent={4} color="primary">
|
||||
<MailIcon />
|
||||
</Badge>;
|
||||
```
|
||||
|
||||
**After (BUI Badge):**
|
||||
|
||||
```tsx
|
||||
import { Badge } from '@backstage/ui';
|
||||
import { RiMailLine } from 'react-icons/ri';
|
||||
|
||||
<Badge>New</Badge>
|
||||
<Badge size="small" icon={<RiMailLine size={12} />}>4</Badge>
|
||||
```
|
||||
|
||||
Note: BUI `Badge` is a label-style badge (inline text with optional icon), not a notification counter overlay.
|
||||
For notification counters overlaid on icons, use CSS positioning.
|
||||
|
||||
### 21. Slider
|
||||
|
||||
**Before (MUI Slider):**
|
||||
|
||||
```tsx
|
||||
import { Slider } from '@material-ui/core';
|
||||
|
||||
<Slider
|
||||
value={value}
|
||||
onChange={(_, newValue) => setValue(newValue as number)}
|
||||
min={0}
|
||||
max={100}
|
||||
step={10}
|
||||
/>;
|
||||
```
|
||||
|
||||
**After (BUI Slider):**
|
||||
|
||||
```tsx
|
||||
import { Slider } from '@backstage/ui';
|
||||
|
||||
<Slider
|
||||
label="Volume"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
step={10}
|
||||
/>;
|
||||
```
|
||||
|
||||
Note: BUI `Slider` `onChange` receives the new value directly. Use `minValue`/`maxValue` instead of `min`/`max`.
|
||||
|
||||
### 22. CheckboxGroup
|
||||
|
||||
**Before (MUI FormGroup with Checkboxes):**
|
||||
|
||||
```tsx
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormGroup,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
} from '@material-ui/core';
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>Options</FormLabel>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.a}
|
||||
onChange={e => handleChange('a', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Option A"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.b}
|
||||
onChange={e => handleChange('b', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Option B"
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormControl>;
|
||||
```
|
||||
|
||||
**After (BUI CheckboxGroup):**
|
||||
|
||||
```tsx
|
||||
import { CheckboxGroup, Checkbox } from '@backstage/ui';
|
||||
|
||||
<CheckboxGroup label="Options" value={selected} onChange={setSelected}>
|
||||
<Checkbox value="a">Option A</Checkbox>
|
||||
<Checkbox value="b">Option B</Checkbox>
|
||||
</CheckboxGroup>;
|
||||
```
|
||||
|
||||
## CSS Variable Reference
|
||||
|
||||
### Spacing
|
||||
@@ -733,8 +882,7 @@ Find more icons at: https://remixicon.com/
|
||||
|
||||
Some Backstage APIs still require MUI-compatible icon types:
|
||||
|
||||
- **NavItemBlueprint** (`@backstage/frontend-plugin-api`): The `icon` prop expects MUI `IconComponent` type. Remix icons
|
||||
are not type-compatible.
|
||||
- **PageBlueprint** (`@backstage/frontend-plugin-api`): The `icon` param on page extensions expects an `IconElement`. MUI icon components can still be used via `<Icon fontSize="inherit" />`.
|
||||
- **Timeline** (`@material-ui/lab`): No BUI equivalent exists.
|
||||
|
||||
For these cases, keep using MUI components.
|
||||
@@ -758,18 +906,23 @@ When migrating a plugin:
|
||||
13. [ ] Replace MUI `Dialog` with BUI `DialogTrigger` pattern
|
||||
14. [ ] Replace MUI `Tooltip` with BUI `TooltipTrigger` pattern (both from `@backstage/ui`)
|
||||
15. [ ] Replace MUI `Tabs` with BUI `Tabs`
|
||||
16. [ ] Replace MUI `Menu` with BUI `MenuTrigger` pattern
|
||||
16. [ ] Replace MUI `Menu`/`Popover` with BUI `MenuTrigger` pattern
|
||||
17. [ ] Replace `Chip` with `Tag`
|
||||
18. [ ] Replace `IconButton` with `ButtonIcon`
|
||||
19. [ ] Replace MUI `Alert` with BUI `Alert`
|
||||
20. [ ] Replace MUI `List` with BUI `List` and `ListRow`
|
||||
21. [ ] Update `Button` props (`disabled` → `isDisabled`, `variant="contained"` → `variant="primary"`)
|
||||
22. [ ] Update `TextField` props (`required` → `isRequired`, `onChange` signature)
|
||||
23. [ ] Replace MUI icons with Remix icons
|
||||
24. [ ] Run `yarn tsc` to check for type errors
|
||||
25. [ ] Run `yarn build` to verify build
|
||||
26. [ ] Run `yarn lint` to check for missing dependencies
|
||||
27. [ ] Test component rendering and functionality
|
||||
21. [ ] Replace MUI `Select`/`FormControl` with BUI `Select`
|
||||
22. [ ] Replace MUI `Accordion` with BUI `Accordion`/`AccordionTrigger`/`AccordionPanel`
|
||||
23. [ ] Replace MUI `RadioGroup`/`FormControlLabel` with BUI `RadioGroup`/`Radio`
|
||||
24. [ ] Replace MUI `FormGroup` with BUI `CheckboxGroup`
|
||||
25. [ ] Replace MUI `Slider` with BUI `Slider`
|
||||
26. [ ] Update `Button` props (`disabled` → `isDisabled`, `variant="contained"` → `variant="primary"`)
|
||||
27. [ ] Update `TextField` props (`required` → `isRequired`, `onChange` signature)
|
||||
28. [ ] Replace MUI icons with Remix icons
|
||||
29. [ ] Run `yarn tsc` to check for type errors
|
||||
30. [ ] Run the project's build command (e.g. `yarn build`, `yarn build:all`, or `yarn workspace <pkg> build`) to verify build
|
||||
31. [ ] Run `yarn lint` to check for missing dependencies
|
||||
32. [ ] Test component rendering and functionality
|
||||
|
||||
## Reference
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
---
|
||||
name: plugin-analytics-instrumentation
|
||||
description: Instrument a Backstage frontend plugin with analytics events using the Backstage Analytics API. Use this skill when adding, reviewing, or extending event capture (`captureEvent`, `AnalyticsContext`) in plugin components, deciding whether an interaction warrants an event, or writing tests for analytics behavior.
|
||||
---
|
||||
|
||||
# Plugin Analytics Instrumentation Skill
|
||||
|
||||
This skill helps you add analytics instrumentation to a Backstage frontend plugin so that app integrators can measure how the plugin is used.
|
||||
|
||||
## Guiding principles
|
||||
|
||||
Follow these before writing a single `captureEvent` call.
|
||||
|
||||
### 1. Less is more — instrument semantic events, not every interaction
|
||||
|
||||
Capture events that represent things **your plugin is semantically responsible for** — the domain actions only your plugin knows how to describe. Events should reflect **user intent** (something a person chose to do), not the lifecycle of your UI. Avoid instrumenting generic UI noise that the framework or design system already handles.
|
||||
|
||||
Good candidates for plugin-owned events:
|
||||
|
||||
- A domain verb only your plugin performs (`deploy`, `create`, `merge`, `approve`, `trigger`, `refresh`, `rerun`).
|
||||
- An outcome you uniquely know about (a search returning N results, a scaffolder template saving Y minutes, a task transitioning to a terminal state).
|
||||
- A context-carrying interaction where the attributes matter (clicking a search result with its `rank` and `to` target).
|
||||
|
||||
Poor candidates — avoid these:
|
||||
|
||||
- Routine clicks on navigation links, buttons, tabs, menu items — these are covered by the `navigate` event and by built-in instrumentation in `@backstage/ui` (see next principle).
|
||||
- Low-value UI state toggles (expanding a panel, opening a tooltip, hovering).
|
||||
- Every field edit in a form — usually one `submit`-style event at the end captures the intent.
|
||||
- Component lifecycle signals — mounts, unmounts, re-renders, effect firings, data fetches. These describe the machinery of the UI, not the user, and will fire in plenty of contexts the user never initiated (route prefetches, Suspense boundaries, tab switches). Narrow exceptions exist for terminal states the user _lands on_ (e.g. `not-found`).
|
||||
- Events whose `action` and `subject` duplicate what is already captured upstream.
|
||||
|
||||
If you can't answer the question _"what question does this event help someone answer?"_ in one sentence, it's probably best not to add the event.
|
||||
|
||||
### 2. Prefer `@backstage/ui` components — they already instrument clicks
|
||||
|
||||
Components from `@backstage/ui` (BUI) have built-in click instrumentation wired to the Analytics API. As of today this includes at least `Link`, `ButtonLink`, `Tab`, `MenuItem`, `Tag`, and `Table` row clicks. When these components are used for navigation (i.e. rendered with an `href`), a `click` event is fired with the destination included as a `to` attribute. For most of them the `subject` is a best-effort human-readable label — the `aria-label`, the visible text, or the `href` as a fallback. `Table` rows are the exception: their `subject` is the `href` string itself, not derived from visible row content.
|
||||
|
||||
Consequences:
|
||||
|
||||
- If you render a `Link`/`ButtonLink` from `@backstage/ui`, you do **not** need to add a `click` event by hand. Doing so would produce duplicate events.
|
||||
- If a plain `<a>` or a MUI button handles a navigation or action that you care about analytically, migrate it to the BUI equivalent first (see the `mui-to-bui-migration` skill). You'll get the click event for free and can focus your manual instrumentation on plugin-specific actions.
|
||||
- Manual `captureEvent('click', ...)` calls are reserved for cases where **no** BUI component fits — for example, clicks on a canvas, a custom widget, or a non-link element whose interaction needs tracking.
|
||||
|
||||
#### Overriding the default event with `noTrack`
|
||||
|
||||
Occasionally a BUI component is the right UI primitive but the default event it fires isn't the one you want — for example, the interaction has a domain-specific verb (`approve`, `rerun`) rather than a generic `click`, or the subject should be a stable identifier rather than the visible link text. In that case, pass `noTrack` to suppress the built-in event and fire your own from the click handler:
|
||||
|
||||
```tsx
|
||||
import { Link } from '@backstage/ui';
|
||||
import { useAnalytics } from '@backstage/frontend-plugin-api';
|
||||
|
||||
function ApproveLink({ requestId, href }: Props) {
|
||||
const analytics = useAnalytics();
|
||||
return (
|
||||
<Link
|
||||
noTrack
|
||||
href={href}
|
||||
onClick={() => analytics.captureEvent('approve', requestId)}
|
||||
>
|
||||
Approve
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Reach for `noTrack` only when you're **replacing** the default event, not layering a second event on top of it. If both the default `click` and your custom event are useful, the custom one probably belongs on a different component or in a different handler. `noTrack` is available on all BUI components with built-in instrumentation (`Link`, `ButtonLink`, `Tab`, `MenuItem`, `Tag`, and `Table` rows).
|
||||
|
||||
### 3. Split events so analysis stays flexible
|
||||
|
||||
An `AnalyticsEvent` has an `action`, a `subject`, and surrounding `context` (which is filled in with `pluginId` and `extension` automatically). Keep each dimension disaggregated so questions can be answered at any level of granularity.
|
||||
|
||||
- **Action** is the verb — kept generic and reused across plugins (`click`, `search`, `filter`, `create`, `discover`). Avoid squashing what belongs in context into the action (e.g. don't use `filterEntityTable` — use `filter` and let the `extension` / `AnalyticsContext` identify the table).
|
||||
- **Subject** is the noun — the specific thing acted upon (a PR name, a template name, a search term, a result title).
|
||||
- **Attributes** are optional key/value dimensions available at capture time (`to`, `org`, `repo`, `entityRef`).
|
||||
- **Context** is for metadata coming from further up the React tree, or shared across many events in a region.
|
||||
|
||||
When in doubt about attribute naming, reuse what existing events in the repo use (e.g. `entityRef` for catalog entities, `to` for destinations, `searchTypes` for search). Consistency across plugins makes aggregation possible.
|
||||
|
||||
## How to capture an event
|
||||
|
||||
Get a tracker with `useAnalytics()` and call `captureEvent(action, subject, options?)`.
|
||||
|
||||
```tsx
|
||||
import { useAnalytics } from '@backstage/frontend-plugin-api';
|
||||
|
||||
function DeployButton({ serviceName }: { serviceName: string }) {
|
||||
const analytics = useAnalytics();
|
||||
const handleDeploy = () => {
|
||||
// ...perform the deploy
|
||||
analytics.captureEvent('deploy', serviceName);
|
||||
};
|
||||
return <Button onClick={handleDeploy}>Deploy</Button>;
|
||||
}
|
||||
```
|
||||
|
||||
For old-system plugins, the same hook is re-exported from `@backstage/core-plugin-api`; the behavior is identical. New plugins targeting the new frontend system should import from `@backstage/frontend-plugin-api`.
|
||||
|
||||
### Adding `value` and `attributes`
|
||||
|
||||
`value` is a single numeric metric associated with the event (duration, rank, count). `attributes` are dimensional string/number/boolean pairs.
|
||||
|
||||
```tsx
|
||||
analytics.captureEvent('merge', pullRequestName, {
|
||||
value: pullRequestAgeInMinutes,
|
||||
attributes: { org, repo },
|
||||
});
|
||||
```
|
||||
|
||||
Keep attributes flat and serializable. Don't stuff large objects or PII in here.
|
||||
|
||||
### Using `AnalyticsContext` for ambient metadata
|
||||
|
||||
When the same attribute applies to many events under a subtree — or when the metadata lives further up the tree than the component firing the event — wrap the subtree in an `<AnalyticsContext>` instead of passing props down:
|
||||
|
||||
```tsx
|
||||
import { AnalyticsContext } from '@backstage/frontend-plugin-api';
|
||||
|
||||
function TaskPage({ taskId, entityRef }: Props) {
|
||||
return (
|
||||
<AnalyticsContext attributes={{ taskId, entityRef }}>
|
||||
<TaskToolbar />
|
||||
<TaskTimeline />
|
||||
</AnalyticsContext>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Every `captureEvent` fired inside that subtree will have `taskId` and `entityRef` merged into its `context`. Contexts nest and merge; inner values override outer ones.
|
||||
|
||||
Good uses of `AnalyticsContext`:
|
||||
|
||||
- Page- or route-level attributes that apply to every interaction on that page (`entityRef`, `taskId`, a tab selection).
|
||||
- Cross-cutting aggregation keys that let app integrators group events (`segment`, `workspace`).
|
||||
|
||||
Don't wrap every small component in its own context — prefer to set context once at the boundary where the metadata first becomes available.
|
||||
|
||||
## Unit testing event capture
|
||||
|
||||
Use `mockApis.analytics()` from `@backstage/frontend-test-utils` — it returns a mock `AnalyticsApi` implementation with a `getEvents()` helper for assertions. Prefer one thorough test with multiple assertions over many small ones.
|
||||
|
||||
```tsx
|
||||
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
|
||||
import { analyticsApiRef } from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
wrapInTestApp,
|
||||
} from '@backstage/frontend-test-utils';
|
||||
|
||||
it('captures a deploy event with the service name', async () => {
|
||||
const analytics = mockApis.analytics();
|
||||
|
||||
render(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, analytics]]}>
|
||||
<DeployButton serviceName="payments-api" />
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: /deploy/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(analytics.getEvents()[0]).toMatchObject({
|
||||
action: 'deploy',
|
||||
subject: 'payments-api',
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Assert on `action`, `subject`, and any `attributes`/`value` you explicitly set. Don't assert on auto-populated context keys like `pluginId` — those are the framework's responsibility.
|
||||
|
||||
## Review checklist
|
||||
|
||||
Before submitting instrumentation changes:
|
||||
|
||||
1. [ ] Every new `captureEvent` call represents a **plugin-semantic, user-initiated** action (not a click already covered by BUI, a navigation, or a component-lifecycle trigger).
|
||||
2. [ ] Route to a BUI component (`Link`, `ButtonLink`, `Tab`, `MenuItem`, `Tag`, `Table`) wherever one fits, rather than instrumenting a plain element by hand.
|
||||
3. [ ] `action` is a short generic verb; plugin/extension identity is left to the auto-populated `context`.
|
||||
4. [ ] Attribute keys reuse established conventions where applicable (`entityRef`, `to`, `searchTypes`, etc.).
|
||||
5. [ ] Shared attributes are set via a single `<AnalyticsContext>` at a boundary, not duplicated across events.
|
||||
6. [ ] `value` is numeric and meaningful (duration, rank, count) — not a stand-in for a string dimension.
|
||||
7. [ ] No PII, secrets, tokens, or large serialized payloads in attributes.
|
||||
8. [ ] At least one unit test covers each new event using `MockAnalyticsApi`.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user